Coerce hybrid-model speculative decode off full decode CUDAGraphs on MUSA#97
Coerce hybrid-model speculative decode off full decode CUDAGraphs on MUSA#97yeahdongcn wants to merge 1 commit into
Conversation
🤖 Augment PR SummarySummary: Avoids silently wrong outputs on MUSA when using MTP speculative decoding with hybrid (GatedDeltaNet/Mamba) models by preventing full-decode CUDAGraph capture. 🤖 Was this summary useful? React with 👍 or 👎 |
8ad536d to
e28e84e
Compare
|
I independently re-reviewed and reran exact head Remote verification used a fresh, non-privileged container on 8x S5000, driver Why PIECEWISE works but
|
| Row | Effective verify mode | Result |
|---|---|---|
explicit PIECEWISE |
PIECEWISE | coherent reference |
| PR guarded default | PIECEWISE | token-for-token equal to the reference for both prompts |
FULL_AND_PIECEWISE with the escape hatch |
FULL at qlen=2 | first divergence at generated tokens 17 and 14; then constant-token suffixes of 107 and 105 tokens |
| non-spec default | FULL at qlen=1 | coherent; only late differences from the speculative reference at tokens 159 and 183 |
This is not a blanket “MUSA full graphs never work” issue. It is the uniform multi-token verify under the full-model capture context. As an additional control, isolated qlen=1/2 attention write+read graph replays matched eager with max|diff|=0 in four cases, so the evidence does not justify claiming a narrower single-kernel root cause yet.
Blocking findings
-
The dFlash carve-out is a real correctness hole. The PR excludes dFlash because it manages a draft graph (PR code), but the target forward happens before the later draft proposal (target, draft). I staged the compatible hybrid pair
Qwen/Qwen3.5-4B+z-lab/Qwen3.5-4B-DFlashand ran TP1 with 16 draft tokens, exact capture size 17, max_num_seqs=1, prompts longer than 17 tokens, sequential BS1 requests, andVLLM_MUSA_DRAFT_FULL_WRAPunset:- PIECEWISE: runtime logged PIECEWISE for every qlen=17 verify; coherent; both repeats token-identical.
VLLM_MUSA_DFLASH_FULL_WRAP=0+FULL_AND_PIECEWISE: runtime logged FULL for every qlen=17 verify, isolating the target full graph without the special draft wrapper. Both prompts first diverged at token 5 and collapsed into deterministic constant-token suffixes of 50 and 40 tokens.- The PR-default dFlash full-wrap path resolved to pure FULL and reproduced the corruption.
- Eager dFlash and non-spec qlen=1 FULL controls remained coherent.
Please remove the exclusion, or separate target and drafter graph configuration so the target verify is PIECEWISE while the dFlash drafter can retain its required full graph.
-
Later config normalization can undo the correctness guard. vLLM calls the platform hook before
set_splitting_ops_for_v1(call order). RealEngineArgs -> VllmConfigconstruction on both Qwen3.6-35B-A3B and Qwen3.6-27B confirmed:- sequence parallel / fused GEMM comms promotes the PR's PIECEWISE result back to
FULL(logic); - attention-quant fusion also promotes it to
FULL(logic).
Enforce the safety invariant after splitting-op normalization, or fall back to
NONEwhen PIECEWISE is incompatible. - sequence parallel / fused GEMM comms promotes the PR's PIECEWISE result back to
-
Compilation-disabled full modes now fail startup. With compilation mode
NONE, both explicitFULLandFULL_DECODE_ONLYare changed to PIECEWISE after the earlier compatibility handling, then fail the final assertion that PIECEWISE requires compilation. The safe result in this case should beCUDAGraphMode.NONE. -
The “correct + lossless” wording is too broad. The fresh MTP run proved guarded-default == explicit PIECEWISE, but speculative PIECEWISE was not byte-equal to non-spec at tokens 159/183. In the dFlash run, PIECEWISE was coherent and matched eager exactly on one prompt, while the other had a benign wording divergence at token 6. Please state the demonstrated guarantee narrowly: it avoids the early full-graph corruption and preserves the PIECEWISE result.
The exact-head platform test class passed (26 passed, 1 xfailed), but its direct-hook tests do not exercise the final config lifecycle above. Please add final EngineArgs/VllmConfig tests for the re-promotion and compilation-disabled cases, plus a hybrid+dFlash correctness regression.
…s on MUSA On MUSA a full decode CUDAGraph corrupts the multi-query speculative verify of a hybrid (GatedDeltaNet / Mamba) target: the paged multi-query verify attention miscomputes when captured inside the full model graph at query_len = num_speculative_tokens + 1, so the decoded output silently diverges from the correct result. query_len=1 decode under the same graph is correct. For any hybrid-model speculative decode (method-agnostic, including dFlash's target verify) set cudagraph_mode to PIECEWISE so the verify runs eager, and disable the full-graph-only fusions (sequence parallel / fused GEMM comms / attn-quant) that set_splitting_ops_for_v1 would otherwise use to re-promote it back to FULL. When compilation is disabled, fall back to NONE, since PIECEWISE requires compilation. Opt back into full decode capture with VLLM_MUSA_HYBRID_SPEC_ALLOW_FULL=1.
e28e84e to
0e6badd
Compare
|
Thanks for the thorough review — all four findings are addressed in 1. dFlash carve-out removed. The target verify is coerced for dFlash too (it hits the same corruption; the drafter graph is configured separately, earlier in the hook). Covered by a unit test and a dense-dFlash negative control. 2. Later normalization no longer undoes the guard. Confirmed the ordering: 3. Compilation-disabled full modes no longer crash. The guard falls back to 4. Wording narrowed to "avoids the full-graph corruption and preserves the piecewise result"; dropped the "correct + lossless" claim. Regression: default + MTP is byte-identical to explicit The hybrid-dFlash E2E is deferred — the |
|
I re-reviewed and rebuilt exact head I still recommend fixing one lifecycle hole before merge. An incoming
|
| Requested configuration | Completed config | Final flags |
|---|---|---|
| combined/default + attn-quant | PIECEWISE |
cleared |
initial PIECEWISE + attn-quant |
FULL |
attn-quant remains on |
initial PIECEWISE + SP/GEMM |
FULL |
SP/GEMM remain on |
The real TP8 run exposed a second fallback rather than wrong output: the frontend config remained FULL with attn-quant enabled, but each spawned worker reconstructed the config, ran the platform hook a second time, changed it to PIECEWISE, then vLLM saw the already-empty splitting_ops and downgraded it again to NONE. Runtime qLen=2 dispatch was therefore NONE, not FULL, and the output remained coherent. In contrast, the normal guarded default dispatched qLen=2 as PIECEWISE and was byte-equal to the explicit-PIECEWISE reference on both 128-token prompts.
So this exact spawned-worker topology is safe, but it reveals a non-idempotent, construction-count-dependent guard: the first VllmConfig finalizes to FULL, while worker reconstruction falls back to NONE and loses PCG. Reconstruction is not a guaranteed safety boundary: vLLM supports an in-process client that passes the same finalized config directly into EngineCore (selection, InprocClient); at world size 1 it selects UniProcExecutor, which passes that same config object directly to the worker (backend, worker init). The safety invariant should hold after the first lifecycle rather than depending on a later reconstruction.
The new tests do not cover this: the fusion-cleanup test starts from FULL_AND_PIECEWISE, while the separate preservation test starts from PIECEWISE only after manually setting all three flags false (tests L573-L596, L686-L701). Please enforce the invariant for an already-PIECEWISE input too and add full-lifecycle regressions for PIECEWISE + each promotion flag, asserting both the canonical and worker-effective modes.
dFlash follow-up
The target verify is now routed safely to PIECEWISE, but the reply/test comment that the drafter remains "configured separately" is not what the code does. The earlier dFlash block writes FULL and capture sizes to the shared compilation_config (platform.py L470-L497); the new generic guard then overwrites that same mode with PIECEWISE. The draft dispatcher is constructed from the same config and adds its FULL keys only when the supplied mode still has full graphs (dispatcher patch).
The exact-head hybrid+dFlash E2E (Qwen/Qwen3.5-4B + z-lab/Qwen3.5-4B-DFlash, 16 draft tokens, BS1, capture size 17) resolved to PIECEWISE; every qLen=17 runtime dispatch was PIECEWISE, both 96-token prompts were deterministic across repeats, and both were byte-equal to the eager dFlash control. That verifies the target fix. However, startup captured only PIECEWISE graphs and draft-FULL eligibility was false. This disables the claimed dFlash full-draft path rather than preserving it separately. If that performance tradeoff is intentional, please document it and correct the test comment; otherwise it needs a genuinely separate draft graph mode/configuration.
Two smaller follow-ups:
- breakable CUDAGraph supports PIECEWISE with compilation mode
NONE(upstream handling), but the newmode != VLLM_COMPILEbranch forces it toNONE, unnecessarily disabling PCG; - under Inductor graph partitioning, upstream does not perform the incompatible SP/fusion promotion (upstream gates), but an incoming combined mode still has all flags unconditionally cleared. Also, the compilation-disabled warning says the flags were disabled although that branch does not clear them.
Exact-head platform tests passed (30 passed, 1 xfailed).
Coerce hybrid-model speculative decode off full decode CUDAGraphs on MUSA
Summary
Enabling speculative decoding (e.g. MTP, dFlash) on a hybrid GatedDeltaNet /
Mamba model on MUSA with the default
cudagraph_mode=FULL_AND_PIECEWISEproduces silently wrong output. This makes the default configuration
correct: for hybrid-model speculative decode the verify runs piecewise, and the
optimizations that would force it back to a full graph are disabled.
The bug
On MUSA a full decode CUDAGraph corrupts the multi-query speculative verify step
of a hybrid target:
query_len = num_speculative_tokens + 1. Thepaged multi-query verify attention miscomputes when captured inside the full
model graph at that shape, so the decoded output silently diverges from the
correct (eager /
PIECEWISE) result and degrades into garbage after ~a dozentokens.
query_len = 1decode under the same full graph is byte-correct; the defectis specific to the
query_len > 1verify.The defect was localized to the MUSA full-cudagraph capture context, below
vLLM: every input the verify attention receives is byte-identical
FULLvsPIECEWISE, each op is capture-correct in isolation, and it is independent ofthe cudagraph memory pool and the mamba cache layout — yet the attention output
diverges only inside the full captured graph. It is a torch_musa / mate
FlashAttention capture-context defect, not a vLLM or single-kernel bug.
The change
vllm_musa/platform.py::MUSAPlatformBase.check_and_update_config: for anyspeculative decode (
num_speculative_tokens > 0) on a hybrid model(
model_config.is_hybrid) whose cudagraph mode includes full decode graphs:cudagraph_mode = PIECEWISEso the verify runs eager; orNONEwhencompilation is not
VLLM_COMPILE(piecewise cudagraphs require compilation);pass_config.enable_sp,fuse_gemm_comms, andfuse_attn_quant.Step 2 is required because those optimizations need full-graph compilation, so
CompilationConfig.set_splitting_ops_for_v1()(run later inVllmConfig.__post_init__, after this hook) would otherwise re-promotePIECEWISEback toFULLand reintroduce the corruption. Opt back into fulldecode capture with
VLLM_MUSA_HYBRID_SPEC_ALLOW_FULL=1.unknown/renamed draft method cannot silently slip into the corrupt path.
is coerced too (the dFlash drafter graph is configured separately).
native change.
Guarantee
This avoids the full-graph corruption and preserves the piecewise result. It
does not claim bit-exactness of speculative decode versus non-spec decode.
Validation
Env: MTT S5000 ×8, TP8, driver 3.3.5; vLLM 0.24.1.dev0, torchada 0.1.69,
mate 0.2.3, torch / torch_musa 2.9.0 / musa40305. Test model: Qwen3.6-35B-A3B
(hybrid GatedDeltaNet + 256-expert MoE,
Qwen3_5MoeForConditionalGeneration),bf16, greedy, num_spec=1.
byte-identical to explicit
cudagraph_mode=PIECEWISEon both prompts(192 tokens each); the token-17 corruption is eliminated.
resolved
cudagraph_modeisPIECEWISE(not promoted toFULL) and theruntime captures piecewise; without the fusion-clearing it resolves to
FULL.NONE, the guardresolves to
cudagraph_mode=NONE— no "PIECEWISE requires compilation"startup assertion.
Unit + regression tests (
tests/test_patches.py::TestMUSAPlatformDefaults)dFlash target verify → PIECEWISE; non-hybrid keeps FULL; non-spec keeps FULL;
VLLM_MUSA_HYBRID_SPEC_ALLOW_FULL=1keeps FULL.enable_sp/fuse_gemm_comms/fuse_attn_quant; compilation-disabled → NONE; and two real-CompilationConfigset_splitting_ops_for_v1cases — SP promotesFULL_AND_PIECEWISE → FULL(the hazard), and with the fusions cleared
PIECEWISEsurvives.Review response (maintainer review on the prior revision)
now coerced for dFlash too.
full-graph-only fusions so
set_splitting_ops_for_v1cannot re-promote; alifecycle test proves
PIECEWISEsurvives.NONE.result".
cases above.
Test plan
dFlash target coerced, SP promotion + survival)
the hybrid-dFlash E2E is deferred (the
Qwen3.5-4B-DFlashdraftercheckpoint is not staged on the test cluster)