Skip to content

[Bugfix][Spec Decode] Size DFlash query buffers for cudagraph-padded batches - #50065

Merged
MatthewBonanni merged 9 commits into
vllm-project:mainfrom
siddhant-bharti:fix-dflash-query-buffer-cudagraph-padding
Jul 28, 2026
Merged

MatthewBonanni merged 9 commits into
vllm-project:mainfrom
siddhant-bharti:fix-dflash-query-buffer-cudagraph-padding

Conversation

@siddhant-bharti

@siddhant-bharti siddhant-bharti commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Purpose

Fix a startup crash (and a latent runtime overflow) in DFlash speculative decoding on the V1 GPU model runner for any odd --max-num-seqs.

DFlashProposer.__init__ allocates its persistent query buffers (positions, _slot_mapping_buffer) to exactly max_query_tokens = max_num_seqs * (1 + num_speculative_tokens) (vllm/v1/spec_decode/dflash.py). But _determine_batch_execution_and_paddingcudagraph_dispatcher.dispatch() pads drafter batches up to the next cudagraph capture size. When max_query_tokens is not itself a capture size, the padded batch exceeds the buffers, and slicing them at the padded size silently returns short tensors. The compiled draft model then receives input_ids at the padded length but positions at the unpadded length, and engine init fails in drafter.dummy_run during cudagraph memory profiling:

AssertionError: expected size 132==136, stride 1==1 at dim=0

(132 = 33 × 4 with max_num_seqs=33, num_speculative_tokens=3; the default capture ladder is multiples of 8 in that range — ..., 128, 136, ... — so 132 pads to 136.) Any odd max_num_seqs hits this; even values happen to land on capture sizes, which is why the bug is easy to miss. The same overflow exists in the runtime propose() path (build_model_inputs_first_pass slices the same buffers) once a full batch is scheduled.

Scope: only the V1 model runner path (vllm/v1/spec_decode/dflash.py) is affected. The V2 model runner has a separate DFlash speculator implementation with its own capture sizing and is unaffected — verified experimentally below. The V1 path remains the fallback for configurations V2 doesn't cover (and is selectable via VLLM_USE_V2_MODEL_RUNNER=0).

Fix: allocate the query buffers to max(max_query_tokens, compilation_config.max_cudagraph_capture_size). Safe by construction: dispatch() never pads a batch beyond max_cudagraph_capture_size (larger batches run unpadded without graphs), and later capture-size adjustment (resolve_cudagraph_mode_and_sizes) only ever shrinks that bound, so the buffer capacity cannot be invalidated. Cost: a few KB of int64 per buffer.

Test Plan

Reproduction and fix verification on a single GPU (the bug is hardware-agnostic), public models only, image vllm/vllm-openai:nightly (0.23.1rc1.dev1533+g49f31d7ce; the affected code is identical on current main):

# bug path: V1 runner, odd max_num_seqs
VLLM_USE_V2_MODEL_RUNNER=0 python3 -m vllm.entrypoints.openai.api_server \
  --model Qwen/Qwen3-8B --max-num-seqs 33 --max-model-len 8192 \
  --speculative-config '{"model": "z-lab/Qwen3-8B-DFlash-b16", "num_speculative_tokens": 3, "method": "dflash"}'

Matrix: {unpatched, patched} × {V2 runner default, V1 runner forced} × {33, 32}, plus a 33-way concurrent full-batch completion burst against each surviving server to exercise the runtime propose() path. Lint: pre-commit run --files ... on both changed files (all hooks incl. mypy pass).

Test Result

Config Unpatched Patched
V1 runner, max-num-seqs 33, startup crash: AssertionError: expected size 132==136, stride 1==1 at dim=0 in drafter.dummy_run starts clean
V1 runner, max-num-seqs 33, 33-way full-batch burst n/a (never starts) 33/33 HTTP 200, spec decode active (16374 drafted / 4442 accepted), zero errors
V1 runner, max-num-seqs 32 (control) starts clean starts clean
V2 runner (default), max-num-seqs 33 starts clean + 33/33 burst OK (V2 unaffected, as expected) unchanged

A regression test was validated against a live install in both directions (failed unpatched at 33, passed patched) and then removed per review feedback.

Not a duplicate

Checked open PRs/issues for dflash, drafter padding, and this assertion. Closest is #48725 — same class (spec-decode buffer headroom) but different mechanism (scheduler token budget vs. cudagraph padding), different buffers, different file. No open PR touches DFlash query-buffer sizing.

AI assistance disclosure

This fix was developed with AI assistance (Claude Code): root-cause analysis, patch, reproduction, and test were AI-assisted; the human submitter has reviewed the change and is accountable for it per the contribution policy.

🤖 Generated with Claude Code

…batches

DFlashProposer allocates its persistent query buffers (positions,
_slot_mapping_buffer) to exactly max_num_seqs * (num_speculative_tokens + 1)
tokens, but the cudagraph dispatcher pads drafter batches up to the next
capture size. Whenever the exact size is not itself a capture size (any odd
max_num_seqs: e.g. 33 * 4 = 132, padded to 136), slicing the buffers at the
padded size silently yields short tensors and engine startup dies in the
drafter dummy_run with 'AssertionError: expected size 132==136'. The same
overflow exists in the runtime propose() path at full batch.

Size the buffers to max_cudagraph_capture_size instead, the upper bound of
any padded batch: dispatch never pads beyond it, and later capture-size
adjustment (resolve_cudagraph_mode_and_sizes) can only shrink it. Costs a
few KB of int64 per buffer.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: siddhant-bharti <sbharti@together.ai>
@github-actions

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.

🚀

@mergify mergify Bot added the bug Something isn't working label Jul 28, 2026
@siddhant-bharti
siddhant-bharti marked this pull request as ready for review July 28, 2026 02:40

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@jcastillo-boop

Copy link
Copy Markdown

looks good

siddhant-bharti and others added 2 commits July 27, 2026 19:44
…acity

Verified against a live install: fails on unpatched code for max_num_seqs=33
(buffers 132 < padded 136), passes for 32, and passes for both with the fix.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: siddhant-bharti <sbharti@together.ai>

@MatthewBonanni MatthewBonanni left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the fix! Just some nits, this should be a pretty clean and tiny PR

Comment thread tests/v1/spec_decode/test_dflash_buffers.py Outdated
Comment thread vllm/v1/spec_decode/dflash.py Outdated
siddhant-bharti and others added 2 commits July 28, 2026 09:24
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: siddhant-bharti <sbharti@together.ai>

@MatthewBonanni MatthewBonanni left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@github-project-automation github-project-automation Bot moved this to Ready in NVIDIA Jul 28, 2026
@MatthewBonanni MatthewBonanni added the ready ONLY add when PR is ready to merge/full CI is needed label Jul 28, 2026
@MatthewBonanni
MatthewBonanni enabled auto-merge (squash) July 28, 2026 16:35
@MatthewBonanni
MatthewBonanni merged commit 0b6aa3c into vllm-project:main Jul 28, 2026
89 checks passed
@github-project-automation github-project-automation Bot moved this from Ready to Done in NVIDIA Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working nvidia ready ONLY add when PR is ready to merge/full CI is needed speculative-decoding v1

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

5 participants