Repository navigation
Fix FlashInfer A2A IMA by DP-synchronizing the decode graph bucket (#30242) - #30450
Merged
Merged
Conversation
Fixes the CUDA illegal-memory-access on DeepSeek-V4 NVFP4 disagg decode (issue #30242), a regression from #29461. Root cause: with FlashInfer MoE A2A + DP attention, require_mlp_tp_gather was False (moe_dense_tp_size=1 + enable_dp_lm_head), so each rank picked its decode cuda-graph bucket from its *local* batch size. Ranks then replayed different-sized graphs and passed different runtime_max_tokens_ per_rank into MoeAlltoAll's fixed-geometry [ep_size, runtime_max, ...] buffers -> geometry mismatch -> IMA (surfacing later at the sampler). Fix (common.py): make require_mlp_tp_gather return True for FlashInfer A2A. That reuses the existing DP-synchronized machinery so every EP rank selects the same graph bucket (the cross-rank max) and the dispatcher's Case 1 (max(dp_global)) is used for DP attention in eager, capture, and replay -- a rank-invariant value that tracks the actual batch instead of the static capacity. No literal MLP TP-gather is introduced: the MoE stays SCATTERED (gated on get_moe_a2a_backend()) and the a2a op still owns dispatch/combine. Handles eager, capture/replay, prefill, and speculative decoding via the already-tested require_mlp_tp_gather=True path. Dispatcher (flashinfer.py): keep two rank-invariant cases and drop the old static-capacity middle branch. - Case 1 max(dp_global): DP attention feeding EP (per-rank counts differ). - Case 2 x.shape[0]: SP attention feeding EP (tokens are sequence-parallel scattered uniformly), a single EP rank, or capture of those -- x.shape[0] is already rank-invariant, so it stays right-sized (this preserves the private SP+EP use case). Add asserts making the invariants explicit: (1) DP attention must not reach the x.shape[0] branch with ep_size>1 (would be non-rank-invariant; catches the #30242 pattern and unsupported SKIP_ALL_GATHER); (2) the chosen value must cover this rank's own tokens. These run in Python during eager and capture; replay reuses the baked value. Also rewrite the stale 3-case comment.
merrymercy
requested review from
BBuf,
Edwardf0t1,
Fridge003,
HaiShaw,
Ying1123,
ch-wan and
ispobock
as code owners
July 8, 2026 01:04
Contributor
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
2 of 5 tasks
Contributor
Author
|
/tag-and-rerun-ci |
The FlashInfer A2A fix removes one get_global_server_args() call-site in the MoE token dispatcher (the require_mlp_tp_gather Case 2 branch was refactored out), so lower the decrease-only ratchet baseline to match.
5 tasks done
Chronostasys
pushed a commit
to MindLab-Research/sglang
that referenced
this pull request
Aug 24, 2026
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.
Motivation
Fixes #30242 — CUDA illegal-memory-access on DeepSeek-V4 NVFP4 disaggregated decode (GB300, TP/DP/EP=16,
moe-a2a-backend=flashinfer). Bisected by the reporter to #29461.Root cause
With FlashInfer MoE A2A + DP attention,
require_mlp_tp_gatherisFalse(standard DeepSeek config:moe_dense_tp_size=1+enable_dp_lm_head). So each rank picks its decode CUDA-graph bucket from its local batch size. Different DP/EP ranks then replay different-sized graphs and pass differentruntime_max_tokens_per_rankintoMoeAlltoAll's fixed-geometry[ep_size, runtime_max_tokens_per_rank, ...]buffers → geometry mismatch → IMA (surfacing later at the sampler'sargmax).#29461 routed graph capture to
x.shape[0](baked per-graph), which is exactly the value that diverges across ranks once they replay different buckets.Fix
Make
require_mlp_tp_gatherreturnTruefor FlashInfer A2A (one line inrequire_mlp_tp_gather). This reuses the existing DP-synchronized machinery so:max(global_num_tokens_cpu)), so every EP rank replays the same bucket;max(dp_global)path is used for DP attention in eager, capture, and replay — a rank-invariant value that tracks the actual batch instead of the static workspace capacity (memory-efficient, unlike the pre-Fix FlashInfer A2A dispatcher during CUDA graph capture #29461 static cap).No literal MLP TP-gather is introduced: the MoE layer stays
SCATTERED(gated onget_moe_a2a_backend()), and the a2a op still owns dispatch/combine. This covers eager, capture/replay, prefill, and speculative decoding via the already-testedrequire_mlp_tp_gather=Truepath. See #30242 (comment) for the same suggestion from @trevor-m.Dispatcher cleanup (
flashinfer.py)runtime_max_tokens_per_ranknow has two rank-invariant cases (the wasteful static-capacity middle branch is dropped):max(dp_global): DP attention feeding EP (per-rank token counts differ → take the cross-rank max).x.shape[0]: SP attention feeding EP (tokens are sequence-parallel scattered uniformly →x.shape[0]is already identical on every EP rank), a single EP rank, or capture of those. This preserves the SP+EP layout.Two asserts make the invariants explicit and fail fast (they run in Python during eager and capture; replay reuses the baked value):
x.shape[0]branch withep_size > 1(catches the [Bug] Deepseek V4 NVFP4 Disagg Decode node failure #30242 pattern and the unsupportedSGLANG_SCHEDULER_SKIP_ALL_GATHER);runtime_max_tokens_per_rankmust cover this rank's own tokens.Also see #30432 for the (now more) misleading
require_mlp_tp_gathername.Test Plan / Validation needed
I could not reproduce on the target multi-node GB300 hardware; the change compiles and passes pre-commit. Please validate:
require_mlp_tp_gather=TrueDP-attention run (ensure the shared bucket-selection path is unaffected).x.shape[0]).require_mlp_tp_gather=Truepath).Related
global_num_tokenscollapses to[local_num_tokens]in pure DP+EP layout #30432CI States
Latest PR Test (Base): ✅ Run #28926384044
Latest PR Test (Extra): ❌ Run #28926383999