Skip to content

spec_decode: propagate DCP size to V1 draft config (fixes MTP collapse under DCP)#72

Draft
m9e wants to merge 2 commits into
local-inference-lab:codex/eldritch-head66-dspark-tp4-native-20260629from
m9e:fix/v1-draft-dcp-parallel-config
Draft

spec_decode: propagate DCP size to V1 draft config (fixes MTP collapse under DCP)#72
m9e wants to merge 2 commits into
local-inference-lab:codex/eldritch-head66-dspark-tp4-native-20260629from
m9e:fix/v1-draft-dcp-parallel-config

Conversation

@m9e

@m9e m9e commented Jul 3, 2026

Copy link
Copy Markdown

Problem

Drafts built via the V1 proposer path (vllm/v1/spec_decode/llm_base_proposer.py) run their attention with dcp_world_size=1 while their KV cache, metadata, and sparse-indexer state are DCP-sharded.

Root cause: SpeculativeConfig.create_draft_parallel_config() builds the draft ParallelConfig by copying selected fields from the target — and decode_context_parallel_size is 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--1 top-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_rs vs a2a, 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: when VLLM_DCP_SHARD_DRAFT is enabled (default for method=="mtp", same gating as V2), replace the draft parallel config's decode_context_parallel_size with 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 a topk_scores_buffer at construction):

  • lazy topk_scores_buffer allocation in SparseAttnIndexer at DCP>1 with the B12X sparse indexer (otherwise the first decode step raises B12X sparse indexer DCP requires topk_scores_buffer);
  • MTP-layer scores buffer allocated whenever DCP>1 in deepseek_mtp;
  • build_for_drafting() on DeepseekV32IndexerMetadataBuilder with an unpadded decode view for draft_index>0 (otherwise iterative draft steps fail num_decode_tokens + num_prefill_tokens == num_tokens), plus skipping dcp-local KV addressing for dcp_replicated specs.

An alternative (possibly preferable) form of the core fix is to copy decode_context_parallel_size inside create_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:

before after
per-position acceptance 0.72 / 0.30 / 0.07 0.90 / 0.79 / 0.67
mean acceptance ratio 0.34–0.40 0.775–0.794
decode tok/s (hot) 14.6 22.0–23.0

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 dequantized fp8_ds_mla cache reproduces the kernel partials at cos ≥ 0.9999; the DCP4 dumps show impl.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.

m9e added 2 commits July 2, 2026 21:56
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.
@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown

👋 Hi! Thank you for contributing to the vLLM project.

💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in #pr-reviews, coordinate on features in #feat- channels, or join special interest groups in #sig- channels.

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 ready label to the PR or enable auto-merge.

If you have any questions, please reach out to us on Slack at https://slack.vllm.ai.

Agent Guidelines

IMPORTANT: 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.

🚀

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 26a2111c-0c67-4577-bba8-d462a5b86002

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@m9e

m9e commented Jul 3, 2026

Copy link
Copy Markdown
Author

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.

@doodleops999

Copy link
Copy Markdown

thank you for this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants