Repository navigation
[dev] Make DSv4 bit-reproducible under --deterministic-mode - #7441
Conversation
|
Hi @HaochenYuan, two things we hit while stacking #7706 on this PR:
If you rebase onto |
Use the training compressed-first order for deterministic no-grad forwards, preserving the existing default-mode order. Add dispatch coverage and real learned-indexer forward parity tests for zero and positive sparse KL loss. Adapted from Bo Li (@bobboli)'s Megatron-LM PR NVIDIA#7169. The existing stable Top-K helper also originates in that PR. Deterministic sparse-attention and indexer backward in this series builds on Haochen Yuan (@HaochenYuan)'s PR NVIDIA#7441, with our batch-indexing and clipped-KL corrections in NVIDIA#7706. Co-authored-by: Bo Li <22713281+bobboli@users.noreply.github.com> Signed-off-by: Yan Xu <yxu1@nvidia.com>
Use compressed-first indices in deterministic packed forwards regardless of grad mode, compacting no-grad indices as in training. Enforce sink-only padding at both attention wrapper boundaries without mutating caller data. Add real CP1/2/4 forward parity and focused wrapper regressions. Extends Bo Li (@bobboli)'s key-ordering approach from PR NVIDIA#7169 to main's packed CP implementation; that PR explicitly left CP ordering for follow-up. The wrapper padding guard is adapted from the same PR. Existing deterministic backward builds on Haochen Yuan (@HaochenYuan)'s PR NVIDIA#7441 and the batch/KL corrections in NVIDIA#7706; this commit adds the packed forward contract. Co-authored-by: Bo Li <22713281+bobboli@users.noreply.github.com> Signed-off-by: Yan Xu <yxu1@nvidia.com>
Co-authored-by: Yan Xu <yxu1@nvidia.com> Signed-off-by: HaochenYuan <haocheny@nvidia.com>
|
/review model=claude mode=strict |
svcnvidia-nemo-ci
left a comment
There was a problem hiding this comment.
Strict review: PR #7441
Findings: CRITICAL: 0 · IMPORTANT: 3 · SUGGESTION: 0
Overall risk: Medium. Through megatron/training, --deterministic-mode also turns on torch's global deterministic switch, and on that path the gating is consistent. The main gap: of the three CSA autograd functions, only one honours the Megatron Core TransformerConfig.deterministic_mode flag.
Most impactful
- Deterministic gating is inconsistent. See fused_sparse_attention.py L1872, L3650 and L3903.
CSASparseAttnFunc(Path A: layers without an indexer) andFusedCSAIndexerSparseAttnFromTopkFunc(the CP path) only check torch's global switch.- So a run that sets only
deterministic_mode=Truein the config still uses the atomic dK/dV kernels on those layers, plus the atomic indexer dK on CP. Nothing reports it.
- The workspace cache does not only grow, as its comment says. It keeps one buffer per distinct cuDNN workspace size and never frees any of them (L1585).
- Deterministic mode with NCCL-EP zero-copy. In deterministic mode the routing probs are applied after FC2. Combine then receives a new tensor instead of the shared symmetric-memory buffer that zero-copy relies on (experts.py L855).
Checked, no issues found
_deterministic_sparse_indexer_grads_wk:- per-sample key offsets for B>1, including chunks that cross a sample boundary;
- the valid-slot mask and
kept_targetclipping atexp(-100); - the score gradient
predict·Σkept_target − kept_target; - normalisation
loss_coeff/(B·S_q)·sm_scale·grad_loss; - padding slots spread over dummy rows that
[:total_keys]drops; - both reduction paths (
index_add_and stable sort + prefix sum).
_canonicalize_compact_topk: padding sorts last, logits are permuted with the sameperm, and rows that are all padding become 0 rather than NaN.- experts.py: applying the probs on the FC2 output gives the same result as the fused op when FC2 has no bias, and the code raises when it has one. The determinism MoE test presets set
add_bias_linear=False(configs.py L63), so the new guard does not break them. - determinism.py:
moe_router_fusion: Falsefollows the existing check-only pattern. - Unused-identifier check: every new identifier (
ctx.deterministic,requested,compute_grad_w,det_w/det_k,post_probs) is read at runtime.
Scope notes
- This is the first bot review on this PR. I covered all six changed files.
- To check the findings I also read
csa.py(the Path A caller),token_dispatcher.py(the NCCL-EP zero-copy contract) and the determinism test presets. style-guide.mdis not in the base tree.- The testing guide mentions a kernel manifest at
tests/unit_tests/determinism/kernels/manifest.py. It does not exist in this tree, so I did not flag it. - All conclusions come from reading the source; I ran no tests.
Review generated by claude
FDecaYed
left a comment
There was a problem hiding this comment.
I re-reviewed d3b32b3 and independently confirmed the three open findings. Requesting changes before approval:
-
Propagate the explicit deterministic configuration through every CSA path.
CSASparseAttnFunc(no-indexer) andFusedCSAIndexerSparseAttnFromTopkFunc(caller-supplied top-k / CP) currently fall back to the global PyTorch switch, unlike the integrated indexer path. WithTransformerConfig(deterministic_mode=True)and the global switch off, these paths can still use atomic backward kernels. Please pass/save the flag through the callers and autograd contexts, preserve existing apply-call arities, and test both paths with the global switch disabled. Existing thread: #7441 (comment) -
Make scratch-workspace retention bounded and concurrency-safe.
(device, required_bytes)permanently retains a separate allocation for every distinct size and does not reuse a larger allocation for a smaller request. Please use an explicit reuse/growth policy, preserve any pointers captured by live CUDA graphs, and account for overlapping CUDA streams rather than sharing a mutable device-global scratch buffer without synchronization. Cover shrink/reuse, growth, graph lifetime, and stream isolation. Existing thread: #7441 (comment) -
Preserve or explicitly reject the NCCL-EP zero-copy contract. In deterministic mode the fused FC2 writes an unscaled result to
output_buffer, but post-FC2 probability scaling returns a different allocation. The returned result is therefore no longer the registered symmetric output buffer. Unless that combination is implemented and validated end-to-end, please reject it explicitly before launching the fused op and add a regression test. I am not assuming how the external TE implementation handles the mismatched pointer. Existing thread: #7441 (comment)
CI follow-up: I inspected the exact-head failing/cancelled GDN bucket and its per-rank artifact: https://github.com/NVIDIA/Megatron-LM/actions/runs/38258534867/job/114861040388. Rank 0 reports TestFusedPreGatedDeltaRule::test_fused_and_unfused_pre_gated_delta_rule_match FAILED, while ranks 1-7 pass it and advance. Rank 0 subsequently times out in the teardown barrier; cancellation prevents the original assertion details from being printed. This is not enough evidence to classify the primary failure as a transient NCCL issue. Please reproduce the individual test and preserve the first assertion details before teardown; do not merely loosen tolerance or retry the whole matrix.
This is not the same test as the recent #7336 fixes: those adjusted deterministic mHC functional recipes (--gdn-kernel-backend torch), their GB200 goldens, and a separate fused-RoPE inference fixture. There is related GDN normalization work earlier in #7336, but these artifacts do not establish a shared root cause. Please reconcile the normalization/reference contracts and re-run this bucket on the updated exact head.
Signed-off-by: HaochenYuan <haocheny@nvidia.com>
Signed-off-by: HaochenYuan <haocheny@nvidia.com>
|
/review model=claude mode=strict |
svcnvidia-nemo-ci
left a comment
There was a problem hiding this comment.
Strict follow-up review: PR #7441
Findings: CRITICAL: 0 · IMPORTANT: 0 · SUGGESTION: 0
Overall risk: Low. The current head fixes all three findings from the previous bot review (review 5492800782 at d3b32b3). Nothing changed since then introduces a regression above the prior impact ceiling (moderate). Strict review passed with no significant issues found. LGTM.
Prior findings: fixed
TransformerConfig.deterministic_modewas ignored on Path A and the CP path. Fixed.- Path A:
CSASparseAttnFuncnow takes trailingdeterministic/cuda_grapharguments and stores them onctx(L1841-L1874). Its backward passesrequested=ctx.deterministic(L1902-L1913).csa_sparse_attnforwards both flags (L2008-L2009)._forward_fused_no_indexerpassesself.config.deterministic_mode(csa.py L2471-L2480). - CP path:
FusedCSAIndexerSparseAttnFromTopkFuncnow calls_deterministic_requested(deterministic)for the indexer dK (L3699) and passesrequested=to the sparse backward (L3954-L3965). - Arity: I counted the positional arguments of the CP
applycall (csa.py L3712-L3727).deterministicandcuda_graphland at forward slots 29 and 30. The 31-entrygradientstuple is still truncated bynum_forward_inputs. The fused-indexer function's backward now returns 30 entries, matching its 30 forward inputs. - Test: the new
test_csa_sparse_attn_honours_config_flag_without_torch_switchcovers Path A with torch's global switch off. The CP fix was checked by reading the source; no unit test exercises it directly.
- Path A:
- The workspace cache kept one buffer per distinct size. Fixed.
- The cache now holds one buffer per device and reuses it whenever it is large enough, returning
workspace[:required_bytes](L1595-L1604). - A replaced buffer is kept in
_DETERMINISTIC_SPARSE_BWD_RETIRED_WORKSPACESonly whencuda_graph_impl != "none".cuda_graph_implis an existingTransformerConfigfield. test_sparse_bwd_kwargs_select_deterministic_kernel_and_bound_workspacecovers these cases: same size (samedata_ptr), smaller request (samedata_ptr), growth without graphs (nothing retired), and growth with graphs (one retired buffer).
- The cache now holds one buffer per device and reuses it whenever it is large enough, returning
- Deterministic mode with NCCL-EP zero-copy. Fixed.
_fused_forwardnow raises whenoutput_buffer is not Noneunderdeterministic_mode. The check runs beforeops(...)is called (experts.py L855-L863).- Deterministic mode without zero-copy, and zero-copy without deterministic mode, take the same paths as before.
test_fused_forward_deterministic_mode_applies_probs_on_fc2_outputnow asserts thatRuntimeErrormatcheszero_copy.
Follow-up scope
- I compared the head with the previously reviewed tree (
d3b32b3) in the areas changed since then:cuda_graph/deterministicthreading through every fused CSA call site (training, inference, THD and CP);- the new workspace retention policy;
- the zero-copy guard;
- the updated tests.
- On the inference call sites, the new
deterministicflag only reachesctxand is read only in backward, so forward behaviour is unchanged. The default path (deterministic_mode=Falseand torch's switch off) still gets{}from_deterministic_sparse_bwd_kwargs. - The CI concern another reviewer raised (
TestFusedPreGatedDeltaRule, the GDN bucket) involves code this PR does not change, so it is outside this review's scope. - All conclusions come from reading the source; I ran no tests.
Review generated by claude
Note: formal approval is not available for this request, so this review is posted as a comment only.
Review generated by claude
FDecaYed
left a comment
There was a problem hiding this comment.
Follow-up review of ee85072: the explicit deterministic flag now reaches Path A and FromTopk/CP, and the deterministic + NCCL-EP zero-copy combination is rejected before the fused op. I checked those changes independently with isolated CPU autograd/contract probes; this is not GPU validation.
The workspace item is not fully addressed yet: the cache still lacks stream ownership and can discard an allocation previously used by a graph when the next caller is non-graph. Details and the missing regression sequences are inline. Keeping changes requested for that remaining item.
The new exact-head CICD run 38385527145 is active, including the GDN fusion bucket. I have not retried it, and the previous-head GDN result is not a result for this SHA. Request NVSkills CI failed before creating any jobs; that startup failure provides no numerical-test evidence.
Signed-off-by: HaochenYuan <haocheny@nvidia.com>
Signed-off-by: Deyu Fu <deyuf@nvidia.com>
FDecaYed
left a comment
There was a problem hiding this comment.
Reviewed 6b8676e with the agreed support boundary: Megatron's existing ordered compute schedule, with mixed graph/non-graph deterministic CSA configurations on one device explicitly rejected. The guard executes before workspace reuse/growth, preserves graph-mode warmup/capture and retention, and has focused regression coverage. GB200 selection and the two-stream test input-readiness fence are fixed.
No remaining blocker from my review. Validation: 37 CPU diagnostics pass; the four new guard cases reject the previous implementation; 11 GB200 cases are selected by collection; mandatory formatter/lint checks pass. Actual CUDA/cuDNN/FlashMLA validation is pending in exact-head CICD 38689052085, so this approval does not assert new-head GPU CI has already passed or bypass required checks.
|
/review model=claude mode=strict |
svcnvidia-nemo-ci
left a comment
There was a problem hiding this comment.
Strict follow-up review: PR #7441
Findings: CRITICAL: 0 · IMPORTANT: 0 · SUGGESTION: 0
Overall risk: Low. I compared this head with the head of my last review (ee85072). Nothing that changed since then adds a regression above the prior impact ceiling (moderate). All earlier bot findings are still fixed. Strict review passed — no significant issues found. LGTM.
What changed since ee85072
Only two things changed:
- the workspace helper
_get_deterministic_sparse_attention_workspace, with its ownership notes at L1521-L1556; - the workspace tests in test_csa_deterministic_indexer.py.
The csa.py call sites, experts.py and determinism.py are the same as at ee85072.
Earlier findings: still fixed
- Config flag on Path A and the CP path.
CSASparseAttnFunc.backwardpassesrequested=ctx.deterministic(L1955-L1966).FusedCSAIndexerSparseAttnFromTopkFuncuses_deterministic_requested(deterministic)for the indexer dK (L3752) and passesrequested=to the sparse backward (L4007-L4018).
- Bounded workspace. There is still one buffer per device. It is reused whenever it is large enough, and the helper returns an exact-size slice (L1635-L1656).
- Deterministic mode with NCCL-EP zero-copy. The combination is still rejected before the fused op runs (experts.py L855-L863).
The other reviewer's workspace-ownership thread
I checked both of FDecaYed's sequences against the current helper.
- Two streams sharing the scratch.
- A hand-out on a stream that does not own the buffer, outside capture, now calls
stream.wait_stream(owner)andrecord_stream, then takes ownership (L1649-L1654). - Releasing a non-graph buffer after growth is therefore covered by the allocator's stream tracking.
- A hand-out on a stream that does not own the buffer, outside capture, now calls
- Graph warmup at 4096 bytes, capture, then a non-graph caller asking for 8192.
- That call now raises at the
graph_modecheck (L1630-L1634) before the entry is replaced, so the graph's allocation is kept. - Growth in graph mode keeps the old buffer whenever
graph_ownedis set (L1636-L1638). That flag is set during capture or by thecuda_graphconfig (L1655), so retention no longer depends on who makes the request.
- That call now raises at the
- Same
cuda_graphvalue everywhere. Every training call site incsa.pytakes it from the sameself.config.cuda_graph_impl != "none"expression, for example csa.py L3725-L3726 and L3757-L3758. One model config therefore cannot trip the new check. - Capture. During capture no handover is issued, and the stream that owns the buffer does not change. This matches the support boundary agreed in the thread and what
test_deterministic_sparse_bwd_workspace_survives_graph_replay_after_growthasserts. - Tests. The new mixed-mode test checks:
- that both directions are rejected, for both reuse and growth;
- that no reallocation happens and the entry's buffer, owner stream and graph flags are unchanged.
Scope and limitations
- Policy. This is a follow-up review with the default policy: inspect a subset of files, and publish only findings above the prior impact. The pinned
pr-reviewskill does not override that. - Comparison. I compared against the previously reviewed tree at
/context/previous-review/. - Style guide.
style-guide.mddoes not exist in the base tree. - What I did not check. All conclusions come from reading the source; I ran no tests. I did not check the CUDA paths (
record_stream, capture, replay) or the real FlashMLA/cuDNN two-stream test by execution. Per the PR thread, GPU validation for this head is pending in CICD 38689052085.
Note: formal approval is not available for this request, so this review is posted as a comment only.
Review generated by claude
Signed-off-by: HaochenYuan <haocheny@nvidia.com>
FDecaYed
left a comment
There was a problem hiding this comment.
Re-reviewed the test-only update at 5a20d5f and investigated why the preceding merge-group left the queue. The queue removal was explicitly failed_checks at 13:55:47Z, before this push, not a consequence of Codecov or the head update.
The two functional-test blockers below are still present because their production code/config/goldens are unchanged in this head. I also found an input-dependent assertion in the new coverage test. My earlier workspace/graph-mode concern remains resolved under the agreed Megatron schedule boundary; I am not reopening it. The separate merge-queue notification token failure is infrastructure, not a requested code change. Please address these focused test/config/baseline items before the next landing attempt.
Thanks, all three addressed in a28c359:
|
FDecaYed
left a comment
There was a problem hiding this comment.
Re-reviewed e48ce3c; the three follow-up findings are addressed.
- The blanket router-fusion restriction was too broad. The scoped-graph recipe has zero auxiliary-loss coefficient, and I checked the pinned TE backward plus its CUDA kernel: gradients use the saved coefficient and token counts, not the reduced aux-loss scalar. MCore's autoscaler supplies a value-independent gradient. Restoring dev behavior with the explicit logging caveat is appropriate.
- BF16 now follows the original probability-scaling path; the post-FC2 workaround is limited to the quantized recipes. The exact-method CPU branch test passes, and the old implementation fails its new BF16 check. No golden rewrite is needed for that BF16 regression.
- The FromTopk test now constructs positive Q/K instead of relying only on a seed. My independent exact-helper CPU probe has 0/200 failures with the fix versus 23/200 before it, with global determinism both off and on.
These CPU/source checks do not claim native TE/CUDA end-to-end validation. My three threads are resolved; the earlier workspace/graph-mode thread remains resolved under the accepted Megatron schedule boundary. No remaining blocking review finding. Exact-head CI 38886359330 is still running; required checks and normal merge protections still apply.
Keep both the deterministic sparse-loss dK regression and the compact-softmax validation added by NVIDIA#7811. Preserve both parent histories and the previously reviewed determinism fixes. Signed-off-by: Deyu Fu <deyuf@nvidia.com>
What does this PR do?
Under --deterministic-mode, a DSv4 run was not reproducible even against itself: the loss diverges from iteration 2 (grad norm from iteration 1). All sources are kernel-level and independent of CUDA graphs. This PR fixes them, gated on deterministic mode only; the default path is unchanged.
Source 1: cuDNN compact indexer Top-K returns the selected ids in a run-dependent slot order, and its compact
softmax is accumulated in that order (FlashMLA and the indexer backward consume both in slot order)
Fix: _canonicalize_compact_topk: sort ids per row (padding last), recompute the softmax in that order.
Source 2: cuDNN sparse-attention backward accumulates dK/dV with atomics
Fix: pass deterministic=True + a retained scratch workspace on all three sparse_attention_backward_wrapper call
sites; raise if the installed cudnn-frontend / GPU / head count cannot provide the deterministic kernel.
Source 3: cuDNN indexer backward accumulates dK with atomics
Fix: deterministic_sparse_indexer_grads_wk: atomic-free dK (dW on SM90) via torch's deterministic index_add (fallback: stable sort + prefix sum), padding slots spread over dummy rows; computes the same clipped-log KL gradient as cuDNN (predict · Σ kept_target − kept_target, targets clamped at e⁻¹⁰⁰) with per-sample key ids, and overrides cuDNN's dK on the fused and CP call sites (indexing and gradient-formula fixes by @Connor-XY, from #7706)
Source 4: TE fused grouped-MLP backward accumulates the routing-prob gradient (dprob) with atomics (newer TE
raises for this)
Fix: feed a unit scale to the fused op and apply the probs on the FC2 output (exact without FC2 bias; raises
otherwise).
Source 5: TE fused router aux loss (fused_moe_aux_loss) uses float atomicAdd
Fix: --moe-router-fusion is rejected in deterministic mode via ARG_VALUES_REQUIRED_FOR_DETERMINISM (explicit,
no silent kernel swap)
Relation to #7169: that PR aligns the id order between the no-grad and grad forward paths and stable-sorts the standalone Top-K fallback. It does not touch the compact cuDNN Top-K path used in training (slot and softmax order still vary run to run) nor any backward kernel; with #7169 alone the DSv4 proxy is still not reproducible. This PR builds on it.
Performance (iteration time vs the non-deterministic run, same kernels otherwise)
Notes
Issue tracking
For PRs from open-source community contributors:
Linked issue:
Contribution process
Pre-checks
jit_fuser/torch.compile, CUDA extension, TE or external-library dispatch, or a scatter/index accumulation), I have added or updated its bit-exact determinism test and registered it intests/unit_tests/determinism/kernels/manifest.py(guide)Code review
Feel free to message or comment @NVIDIA/mcore-oncall to help accelerate your merge into main. The less complex your PR is, the faster it will be approved and merged!
All PRs start as draft. If you open a non-draft PR, it will be automatically converted to draft.
Step 1: Mark PR as "Ready for Review"
.github/CODEOWNERS.Final Review might get declined if these requirements are not fulfilled.
Step 2: Final Review
For PRs that change
megatron/core, once all expert reviewers have approved, theFinal Reviewlabel is applied automatically and final reviewers are assigned.For PRs outside
megatron/core, this step is skipped.Step 3: Approved
Once all required reviewers have approved, the
Approvedlabel is applied automatically.Merge
Any member of mcore-engineers will be able to merge your PR.