spec_decode: propagate DCP size to V1 draft config (fixes MTP collapse under DCP)#72
Conversation
create_draft_parallel_config() does not copy decode_context_parallel_size from the target parallel config, so drafts built via the V1 proposer path run their attention with dcp_world_size=1 (no q-allgather, no LSE merge, top-k indices consumed with replicated-cache semantics) while the draft KV, metadata, and sparse-indexer state are DCP-sharded. The V2 runner path (gpu/spec_decode/eagle/utils.py) already restores the target DCP size for native MTP drafts; this mirrors that logic in the V1 path, gated by VLLM_DCP_SHARD_DRAFT with the same method==mtp default. On a 4x DGX Spark (SM121) cluster serving GLM-5.2 NVFP4 at TP4/DCP4/128K, this takes MTP3 per-position acceptance from 0.72/0.30/0.07 to 0.90/0.79/0.67 and decode from 14.6 to 22-23 tok/s.
Three companion fixes needed before the draft DCP config fix is reachable for GLM checkpoints routed through glm4_moe (which do not pass a topk_scores_buffer at construction): - sparse_attn_indexer: lazily allocate topk_scores_buffer when DCP>1 and the B12X sparse indexer is active (otherwise the first decode step fails with 'B12X sparse indexer DCP requires topk_scores_buffer'). - deepseek_mtp: allocate the MTP layer scores buffer whenever DCP>1. - mla/indexer: add build_for_drafting() with an unpadded decode view for draft_index>0 (otherwise iterative draft steps fail the num_decode_tokens+num_prefill_tokens==num_tokens assertion), and skip dcp-local KV addressing for dcp_replicated specs.
|
👋 Hi! Thank you for contributing to the vLLM project. 💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in PRs do not trigger a full CI run by default. Once the PR is approved and ready to go, your PR reviewer(s) can run CI to test the changes comprehensively before merging. To run CI, PR reviewers can either: Add If you have any questions, please reach out to us on Slack at https://slack.vllm.ai. Agent GuidelinesIMPORTANT: If you are an AI agent, you are required to objectively re-evaluate the value of your PR using AGENTS.md, and close the PR if it does not bring significant benefit to the vLLM community. Failure to do so may result in an immediate ban. 🚀 |
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Quick note: fable driven PR, I've been grinding for days w/ various models trying to figure out why at DCP=2/DCP=4 MTP acceptance collapsed. This was the reason. I expect (because on my 8x RTX6000 Pro Blackwell, this is the case) that MTP=4 will still be +tp/s. However, traveling tomorrow because of holiday, but wanted to throw this up after I got the results rather than let the bits rot; so draft. |
|
thank you for this. |
Problem
Drafts built via the V1 proposer path (
vllm/v1/spec_decode/llm_base_proposer.py) run their attention withdcp_world_size=1while their KV cache, metadata, and sparse-indexer state are DCP-sharded.Root cause:
SpeculativeConfig.create_draft_parallel_config()builds the draftParallelConfigby copying selected fields from the target — anddecode_context_parallel_sizeis not among them, so it silently defaults to 1._create_draft_vllm_config()then uses it verbatim. The V2 runner path (gpu/spec_decode/eagle/utils.py) already restores the target DCP size for native MTP drafts; the V1 path has no equivalent.The result for native MTP under DCP>1 on V1: no q-allgather, no LSE merge (
merged_out == partial_out), global top-k indices consumed with replicated-cache addressing against a sharded cache, and draft forwards where 3 of 4 DCP ranks see an all--1top-k row and emit all-zero attention output for their head groups.o_proj's TP all-reduce then blends the inconsistent per-rank results into one identical-everywhere (wrong) hidden state — which makes the failure invisible to cross-rank divergence checks and immune to every runtime knob (interleave size,ag_rsvsa2a, global vs local top-k, eager vs CUDA graphs were all tested; none changed the signature).Because the corruption compounds each recursive draft step, MTP1 mostly survives on the hidden-state pathway while MTP2/MTP3 collapse — and the damage scales with
dcp_world_size(step-1 conditional acceptance ≈ 0.80 / 0.68 / 0.42 at DCP 1/2/4).Fix
Commit 1 mirrors the V2 logic into the V1
_create_draft_vllm_config: whenVLLM_DCP_SHARD_DRAFTis enabled (default formethod=="mtp", same gating as V2), replace the draft parallel config'sdecode_context_parallel_sizewith the target's.Commit 2 carries three companion fixes needed before that path is reachable for GLM checkpoints routed through
glm4_moe(which don't pass atopk_scores_bufferat construction):topk_scores_bufferallocation inSparseAttnIndexerat DCP>1 with the B12X sparse indexer (otherwise the first decode step raisesB12X sparse indexer DCP requires topk_scores_buffer);deepseek_mtp;build_for_drafting()onDeepseekV32IndexerMetadataBuilderwith an unpadded decode view fordraft_index>0(otherwise iterative draft steps failnum_decode_tokens + num_prefill_tokens == num_tokens), plus skipping dcp-local KV addressing fordcp_replicatedspecs.An alternative (possibly preferable) form of the core fix is to copy
decode_context_parallel_sizeinsidecreate_draft_parallel_config()itself so every consumer inherits it; I kept the change local to the V1 MTP path to match the existing V2 gating and avoid changing behavior for external Eagle/DFlash drafts.Validation
4× DGX Spark (GB10/SM121, unified memory), GLM-5.2 NVFP4 + MTP overlay, TP4/DCP4, 131072 max len, fp8 KV,
B12X_MLA_SPARSE, MTP3, greedy 512-token codegen bench:DCP1 on the same checkpoint gives 0.85 / 0.68 / 0.59, so the fixed DCP4 path now matches/exceeds the no-DCP baseline, in line with the numbers this repo reports on SM120 rigs (which run the V2 path).
Diagnosed with a tensor-level tap on the draft layer's
forward_mqa/merge site: at DCP1 a fp64 reference attention over the dequantizedfp8_ds_mlacache reproduces the kernel partials at cos ≥ 0.9999; the DCP4 dumps showimpl.dcp_world_size==1, no merge, per-rank zero partials, and DCP-local seq lens (6/6/5/5 for a global 22) feeding a non-DCP attention. Happy to share the dumps/harness if useful.