Repository navigation
Fix parallel external source forwarding recycled output buffers - #6413
Conversation
|
CI MESSAGE: [56318520]: BUILD STARTED |
|
| Filename | Overview |
|---|---|
| dali/pipeline/operator/builtin/input_operator.h | ForwardCurrentData now returns bool; copy_performed changed from true to false in ShareUserData — semantically correct but inadvertently triggers MakeOutputUnshareable for non-parallel no_copy=True CPU pipelines in executor2. |
| dali/pipeline/workspace/iteration_data.h | New UnshareableData class: interval map (end->start) for tracking unshareable memory ranges. Contains/Add correct for non-overlapping use case; Add does not perform three-way merge but is functionally correct for Contains. |
| dali/pipeline/operator/builtin/make_contiguous.h | New PipelineOutput mode added. SetPassthrough helper checks unshareable_data under lock and forces copy if buffer is flagged. Logic is correct. |
| dali/pipeline/operator/builtin/external_source.cc | Both CPU and GPU RunImpl capture bool from ForwardCurrentData and call MakeOutputUnshareable on swap. CPU path correct for parallel workers. |
| dali/pipeline/executor/executor2/exec_graph_analysis.cc | SetMakeContiguousMode sets PipelineOutput mode on MakeContiguous nodes feeding the output node. |
| dali/pipeline/executor/executor2/exec_graph.h | AddOutputNode simplified: dead name-registration code removed, replaced with duplicate-output guard. |
| dali/pipeline/operator/builtin/input_operator.cc | All four ForwardCurrentData specializations changed from void to bool, returning copy_performed or true when a copy was performed. |
| dali/test/python/test_external_source_parallel.py | New regression test holds all epoch outputs without copying and verifies them after the full epoch to catch recycled shared-memory buffers. |
Sequence Diagram
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant W as Parallel Worker
participant ES as ExternalSource RunImpl
participant IQ as InputQueue shared-mem
participant MC as MakeContiguous PipelineOutput
participant UD as UnshareableData
participant Out as Pipeline Output
W->>IQ: "place buffer in shared memory copy_performed=false"
ES->>IQ: ForwardCurrentData swap returns false
ES->>UD: MakeOutputUnshareable buffer range
ES->>MC: pass swapped TensorList
MC->>UD: SetPassthrough Lock Contains raw_tensor 0
UD-->>MC: found pass_through_ false
MC->>Out: copy to independent buffer
W->>IQ: recycle shared-memory buffer safe
Out-->>Out: data intact
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant W as Parallel Worker
participant ES as ExternalSource RunImpl
participant IQ as InputQueue shared-mem
participant MC as MakeContiguous PipelineOutput
participant UD as UnshareableData
participant Out as Pipeline Output
W->>IQ: "place buffer in shared memory copy_performed=false"
ES->>IQ: ForwardCurrentData swap returns false
ES->>UD: MakeOutputUnshareable buffer range
ES->>MC: pass swapped TensorList
MC->>UD: SetPassthrough Lock Contains raw_tensor 0
UD-->>MC: found pass_through_ false
MC->>Out: copy to independent buffer
W->>IQ: recycle shared-memory buffer safe
Out-->>Out: data intact
Comments Outside Diff (1)
-
dali/pipeline/operator/builtin/input_operator.h, line 383-393 (link)[Bug]
no_copy=Truecopy forced for non-parallel CPU pipelines in executor2Changing
copy_performedfromtruetofalsein the CPUShareUserDatapath is semantically correct (no copy was performed here), but it has an unintended side effect for non-parallel pipelines. When a user callsfeed_inputwithno_copy=True,ShareUserDatais the code path taken. After this change,ForwardCurrentDatareturnsfalse, which triggersMakeOutputUnshareableinExternalSource::RunImpl, registering the user's buffer inunshareable_data. The downstreamMakeContiguousinPipelineOutputmode then finds the buffer inunshareable_dataand forces a copy, silently undoing theno_copy=Truecontract for all executor2 pipelines — regardless of whether parallel workers are involved.Parallel workers can recycle shared-memory buffers (the real bug); user-provided
no_copybuffers cannot be recycled by a worker. A separate flag onInputQueueItem(e.g.,may_be_recycled) could distinguish the two cases, allowingMakeOutputUnshareableto be skipped for user-owned buffers while still protecting parallel-worker shared memory.
Reviews (6): Last reviewed commit: "Bug fix." | Re-trigger Greptile
|
CI MESSAGE: [56318520]: BUILD FAILED |
ec7fb74 to
6afb3d3
Compare
|
CI MESSAGE: [56337382]: BUILD STARTED |
| if (!node->instance_name.empty()) { | ||
| if (!name2node_.emplace(node->instance_name, node).second) { | ||
| nodes_.pop_back(); | ||
| throw std::invalid_argument( | ||
| make_string("Duplicate node name: \"", node->instance_name, "\"")); | ||
| } | ||
| } |
There was a problem hiding this comment.
This code was dead.
|
CI MESSAGE: [56338022]: BUILD STARTED |
|
CI MESSAGE: [56343673]: BUILD STARTED |
|
CI MESSAGE: [56346138]: BUILD STARTED |
|
CI MESSAGE: [56338022]: BUILD PASSED |
|
CI MESSAGE: [56346138]: BUILD PASSED |
In parallel mode ExternalSource could forward a worker's shared-memory buffer straight to the pipeline output without copying it. The worker was then free to recycle that buffer for a later sample, silently overwriting outputs the user still held (observed as garbage or stale values). ForwardCurrentData now reports whether it copied the data. When it only swapped the buffer in (no copy), the operator records the output range in the iteration's UnshareableData. The pipeline-output MakeContiguous runs in a new PipelineOutput mode that, before passing a contiguous input through, checks it against UnshareableData and forces a copy if the buffer is one that may be recycled. Add a regression test that holds every output of an epoch without copying and verifies them only after the epoch has run, which fails if a held buffer was recycled. Tests Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Fix Signed-off-by: Michał Zientkiewicz <mzient@gmail.com>
Signed-off-by: Michal Zientkiewicz <michalz@nvidia.com>
Signed-off-by: Michal Zientkiewicz <michalz@nvidia.com>
Signed-off-by: Michal Zientkiewicz <michalz@nvidia.com>
Signed-off-by: Michal Zientkiewicz <michalz@nvidia.com>
Signed-off-by: Michał Zientkiewicz <mzient@gmail.com>
63c8f82 to
5794ac4
Compare
|
CI MESSAGE: [56467605]: BUILD STARTED |
|
CI MESSAGE: [56467605]: BUILD FAILED |
|
CI MESSAGE: [56504565]: BUILD STARTED |
The old behavior wasn't a contract - it was a bug. The old executor always performed a copy at the output; the new one sometimes avoided it, resulting in unwanted buffer recycling in case of parallel external source - and possibly whenever the user passed data with NoCopy. |
|
That's a fair correction. The old Setting Acknowledged — the change is correct. |
|
CI MESSAGE: [56504565]: BUILD PASSED |
| auto lock = unshareable.Lock(); | ||
| if (!unshareable.Empty()) { | ||
| assert(is_contiguous); | ||
| if (unshareable.Contains(input.raw_tensor(0))) |
There was a problem hiding this comment.
Are we guarded against empty input (for .raw_tensor(0))?
There was a problem hiding this comment.
- Executor skips operators when all inputs have 0 samples.
- It's impossible to have 0 samples at the pipeline output.
Category:
Bug fix (non-breaking change which fixes an issue)
Description:
In parallel mode ExternalSource could forward a worker's shared-memory buffer straight to the pipeline output without copying it. The worker was then free to recycle that buffer for a later sample, silently overwriting outputs the user still held (observed as garbage or stale values).
ForwardCurrentData now reports whether it copied the data. When it only swapped the buffer in (no copy), the operator records the output range in the iteration's UnshareableData. The pipeline-output MakeContiguous runs in a new PipelineOutput mode that, before passing a contiguous input through, checks it against UnshareableData and forces a copy if the buffer is one that may be recycled.
Add a regression test that holds every output of an epoch without copying and verifies them only after the epoch has run, which fails if a held buffer was recycled.
See #6027
Additional information:
Affected modules and functionalities:
Key points relevant for the review:
Tests:
Checklist
Documentation
DALI team only
Requirements
REQ IDs: N/A
JIRA TASK: N/A