[BugFix] Synchronize Breakable CUDA Graph after warmup before capture - #34286
Phoenix3334 wants to merge 2 commits into
Conversation
BreakableCudaGraphBackend.capture_one runs two eager warmup iterations, each followed by post_warmup_hook, and then immediately constructs the BreakableCUDAGraph and enters capture. There was no device synchronize and no TP barrier between the final warmup/hook and that construction, so asynchronous work still in flight from the last warmup (or from the hook) could straddle the capture boundary, and the TP ranks were not aligned before one of them started capturing. Drain the device and rendezvous the TP ranks after the last warmup, mirroring the sync that already precedes each warmup iteration. This is the same warmup-to-capture completion gap that sgl-project#33795 proposes to close for FullCudaGraphBackend; BreakableCudaGraphBackend is the default prefill graph backend on CUDA. Add a CPU-only ordering regression that records the call sequence of capture_one and asserts a synchronize() followed by a barrier() lands between the final post_warmup_hook and the graph construction. The test fails on the pre-fix code and passes with the fix.
|
Cross-backend provenance note for review: #33795 independently identified the same final warmup-to-capture completion requirement in The current evidence map is: I am intentionally not using #34286 to explain the B300 DSpark producer failure. That path is separately localized to #32467 ( So the value of this PR is narrower: make the capture-initialization invariant consistent and testable in the second backend without adding replay-hot-path synchronization. |
|
2026-08-11 body refresh: I updated this PR's context section to match the current root-cause map in #34297 and today's B300 evidence. The important scope boundary is unchanged: #34286 is BreakableCudaGraphBackend capture-boundary hardening, not the primary fix for the tested B300 compact-ragged producer failure. That producer bug is #32467/#32470 ( Today's independent TP8/DP8 B30Z decode-only regression of the final no-init stack completed 18560/18560 requests through C1024 with no observed CUDA illegal-address/device-assert/scheduler failures and I added these data only as investigation context; they are not evidence that #34286 itself caused the B300 runtime improvement. |
|
@merrymercy @ispobock @Fridge003 @hnyls2002 — adding the remaining requested Code Owners to the CI rerun request above. Could one of you help trigger |
Motivation
BreakableCudaGraphBackend.capture_one()runs two eager warmup iterations, each followed bypost_warmup_hook, and then immediately constructsBreakableCUDAGraphand enters capture.There is currently no device synchronization or TP-group rendezvous between the final warmup/hook and graph construction. That leaves a warmup-to-capture completion gap: asynchronous work triggered by the last warmup or its hook can still be in flight when capture starts, and TP ranks are not explicitly aligned at that boundary.
This is the same warmup-to-capture completion invariant that #33795 proposes to enforce for
FullCudaGraphBackend; this PR pins the corresponding invariant forBreakableCudaGraphBackend.This PR does not claim a natural BCG crash reproducer and does not claim to be the primary fix for the B300 DSpark compact-ragged producer failure. That failure class is separately localized to #32467 / #32470. The source-of-truth investigation map is #34297.
Modifications
synchronize()after the final BCG warmup/hook.barrier()immediately after the device sync and beforeBreakableCUDAGraphconstruction.BreakableCudaGraphBackend.capture_one().The regression records call order and requires:
It also checks that
capture_one()still performs two warmup forwards plus one captured forward, and thatpost_warmup_hookruns only for the two warmups.Tests
The regression was first run against the unpatched production code as a negative control. It failed specifically because no
synchronize()/barrier()existed between the final warmup hook and BCG construction; the forward/hook/count assertions had already passed.After applying the production change, the same targeted test passed.
Final local validation:
The CPU regression is registered in
base-a-test-cpuand does not require CUDA or model weights.Scope / performance
The additional sync + barrier run only during one-time graph capture, after warmup for each captured shape. There is no steady-state replay-path change.
No kernel, model-forward, or numerical logic is modified.
Root-cause map and related work
The B300/DSpark investigation separated multiple correctness layers rather than treating every illegal-memory symptom as one bug:
Producer-side bug: #32467 / #32470
The primary B300 producer root cause is a write-write race in
plan_compress_prefill_kernel0: redundant initialization of sharedwarp_min/warp_maxscratch can overwrite a completed per-warp reduction, making ragged input appear uniform. The wrong uniform/MTP fast path can then emit out-of-rangeragged_id; a downstream kernel merely surfaces the resulting illegal access.The final reviewer-selected #32467 implementation is the no-init formulation: remove the redundant scratch initialization instead of adding an extra barrier. Same-session A/B/C kernel regression:
This is intentionally separate from the capture-ordering invariant in this PR.
2026-08-11 B300 runtime evidence
An independent TP8/DP8 B30Z runtime regression of the final #32467 no-init implementation was completed on a v0.5.16 stack with the required DSpark DP/disaggregation integration backports (#33098 merged upstream; #31513 open/unmerged at validation time).
Decode-only topology included DP Attention, DSPARK, fake disaggregation transfer, compact ragged verify,
max-running-requests=1024, and per-rank CUDA-Graph max batch 128.The sustained sweep completed 18560/18560 requests across C64/C256/C512/C1024 with zero observed illegal-address faults, device assertions, scheduler exceptions, or client failures;
/health = 200. C1024 reached per-rank running batch 128 and ~24.8k generated tok/s.A forced compact-ragged window (
dspark_force_budget_frac=0.5) produced verify lengths 1–6; 39/43 fully parsed multi-request blocks were truly ragged, with no GPU fault/NaN/budget violation observed.A subsequent external-concurrency sweep up to C2560 also completed without request failures or observed GPU correctness faults, but
max-running-requests=1024kept the true running peak at 128 per rank. Those higher external-concurrency points are admission/queue pressure evidence, not proof of >1024 simultaneously running requests.This runtime evidence strengthens the separation of concerns: #32467 is the primary producer-side fix for the tested B300 failure class, while #34286 remains framework hardening for the Breakable capture boundary.
Related
CI States
Latest PR Test (Base): ❌ Run #34925147699
Latest PR Test (Extra): ❌ Run #34925147675
Latest PR Test (AMD ROCm 10): ❌ Run #34925147711