Fix crashes in the ring and jaccl distributed backends - #3933
Open
jzdziarski wants to merge 1 commit into
Open
Conversation
Three unrelated stability issues in the CPU-side distributed backends, all of which show up as SIGSEGVs on long-running multi-node jobs. Use-after-free in the collectives. `Encoder::dispatch` only enqueues the lambda, and `set_input_array`/`set_output_array` are no-ops on the CPU backend, so nothing keeps the arrays' buffers alive until the task runs. `cpu::eval` queues a keep-alive holding the inputs, but excludes the output, which the collectives allocate themselves. Under memory pressure the allocator can reclaim and reuse the output block before the collective runs, and the stream thread then writes through a stale pointer. Capture `data_shared_ptr()` alongside the raw pointers in the jaccl and ring collectives so the buffers are pinned for the task's lifetime. Unvalidated work completions in the jaccl ring. A failed or spurious completion carries an undefined `wr_id`, and the wire/buff decoded from it was used unchecked to index the per-wire counters, offsets, limits and the buffer pools. Out-of-range values produced a wild pointer in `reduce_op`/`std::copy`. Check `wc.status` and range-check the decoded indices, then drop the completion and keep draining. The in-flight counter is decremented before the check so the loop still terminates. 32-bit offset overflow. `read_offset`/`write_offset` in the mesh all_gather and `n_steps` in the ring all_gather were `int` while indexing payloads sized by `int64_t`, wrapping past 2GB and ~4GB per rank respectively. Widen them to `int64_t`.
This comment was marked as low quality.
This comment was marked as low quality.
This comment was marked as low quality.
This comment was marked as low quality.
zcbenz
requested changes
Aug 5, 2026
zcbenz
left a comment
Member
There was a problem hiding this comment.
Do you happen to still have the stack trace of the crashes? It would help diagnose whether the fix is correct.
|
|
||
| in_flight--; | ||
| if (wc[i].status != IBV_WC_SUCCESS || lr >= MAX_DIR || | ||
| lw >= n_wires || buff >= PIPELINE) { |
Member
There was a problem hiding this comment.
Since wr_id is undefined on failure, checking lr/lw/buff would not be meaningful?
I think the code should:
- ensure
wc[i].statusis notIBV_WC_SUCCESSbefore polling; - only check
wc[i].status != IBV_WC_SUCCESSfor error.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Proposed changes
Three unrelated stability issues in the CPU-side distributed backends, all of which show up as SIGSEGVs on long-running multi-node jobs.
Use-after-free in the collectives.
Encoder::dispatchonly enqueues the lambda, andset_input_array/set_output_arrayare no-ops on the CPU backend, so nothing keeps the arrays' buffers alive until the task runs.cpu::evalqueues a keep-alive holding the inputs, but excludes the output, which the collectives allocate themselves. Under memory pressure the allocator can reclaim and reuse the output block before the collective runs, and the stream thread then writes through a stale pointer. Capturedata_shared_ptr()alongside the raw pointers in the jaccl and ring collectives so the buffers are pinned for the task's lifetime.Unvalidated work completions in the jaccl ring. A failed or spurious completion carries an undefined
wr_id, and the wire/buff decoded from it was used unchecked to index the per-wire counters, offsets, limits and the buffer pools. Out-of-range values produced a wild pointer inreduce_op/std::copy. Checkwc.statusand range-check the decoded indices, then drop the completion and keep draining. The in-flight counter is decremented before the check so the loop still terminates.32-bit offset overflow.
read_offset/write_offsetin the mesh all_gather andn_stepsin the ring all_gather wereintwhile indexing payloads sized byint64_t, wrapping past 2GB and ~4GB per rank respectively. Widen them toint64_t.Checklist
NOTE: Only reproducible around 420GB, so was not able to write a test for this fix.
Put an
xin the boxes that apply.pre-commit run --all-filesto format my code / installed pre-commit prior to committing changes