[Bugfix] Align Qwen GDN gates with speculative tokens - #51812
Conversation
Co-authored-by: OpenAI Codex <codex@openai.com> Signed-off-by: zjy0516 <riverclouds.zhu@qq.com>
26c679f to
109be29
Compare
|
/ci run |
|
✅ Triggered Buildkite CI #83348 for commit |
|
/ci run |
|
✅ Triggered Buildkite CI #83349 for commit |
…-spec tokens precede spec tokens in a mixed batch Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
LGTM |
`Qwen3_5ForConditionalGeneration` is already registered at 0.17.0, so that is the floor. The runs behind this recipe used the pinned Docker image (a fork build in the 0.27 range), so older releases are unverified rather than known-good; `spec_decoding` in particular wants the gated-delta-net speculative fix (vllm-project/vllm#51812 / #51674), which no released tag carries yet. The floor stays a NUMBER rather than the string "nightly" the 2.4T sibling uses, because maxVersion() parses each field through `parseInt(...) || 0` — "nightly" scores 0.0.0 and loses to the kv_store YAMLs' 0.21.0, understating the requirement in the Install header whenever a Mooncake KV-offload option is selected. Difficulty drops to beginner: every variant loads on a single GPU at TP1. The MXFP4 troubleshooting note is scoped to NVIDIA, where the missing linear method support actually bites, rather than reading as a blanket statement. Signed-off-by: Tiezhen WANG <38108242+xianbaoqian@users.noreply.github.com>
|
This landed before we could contribute to it, so this is not a report — it is the reachability datum We instrumented Three arms, one physical RTX 5090, MTP-3, 8 concurrent streams, adversarial-by-design traffic:
Two readings, and the second is the interesting one:
The mechanism that supplies the pseudo-speculative row is the scheduler's speculative padding: a We also predicted, from source, a second mechanism that needs no prefix caching — a speculative What we deliberately do not claim: that those three miscomputed forward passes changed any Relevant to the neighbouring open issues in this family, all of which turn on a row being classified Method, counts, the pre-registered decision rule (fixed before the GPU window opened), the reorder Unrelated to this PR but from the same measurement campaign, in case the flags look familiar: the align-mode admission livelock we hit at a 262 k window is #52520 — #52520. |
…A/B profile + bench script Overlay: single-file COPY of the GDN linear-attn module with upstream PR vllm-project#51812 applied (align a/b gate gathers with spec_token_indx — correctness fix for MTP on hybrid GDN, absent from v0.27.1; patch applies cleanly to the v0.27.1 blob, pure Python, static sanity check at build). Profile: image/model/kv-dtype/mtp-tokens parameterized for the 4-variant same-night A/B grid (fp8+MTP3 GS-32 / MTP-8 / TQ+MTP-3+canary / GS-128 philbert440). Bench: ab_bench.py — canary, acceptance (pre/post load), single-stream 3x300, N=16 aggregate. Co-Authored-By: Claude-Code <noreply@anthropic.com>
…ut, GS-128 no gain, noise dominates Bracket 3.6-A(462)->3.6-B(844) N=16 t/s over one hour invalidates cross-model comparison this window; machine regression shown FLUCTUATING (844 = 88% of the 08-14 reference at 19:26). Correctness findings hold regardless of noise: turboquant_k8v4+MTP-3 produces silently degenerate output on stock v0.27.1+vllm-project#51812 (canary r4g 0.94+, sample captured); MTP-8 halves acceptance (0.22) and single-stream vs MTP-3; GS-128 brings no gain over GS-32 (Todd-gap hypothesis eliminated). Co-Authored-By: Claude-Code <noreply@anthropic.com>
…vllm#51812) In a mixed batch where non-spec tokens precede spec tokens, GdnLinearAttention._forward_core's spec-decode path gathered mixed_qkv (and therefore q/k/v) by spec_token_indx but passed the raw, un-indexed a/b sigmoid gate tensors into fused_sigmoid_gating_delta_rule_update. Row i of q/k/v then no longer described the same original token as row i of a/b -- the fused recurrent update applied gate values from the wrong token. Upstream reproduced this at a max_model_len boundary with 2 MTP draft tokens on Qwen/Qwen3.5-2B; the boundary-condition framing (short remaining context, mixed non-spec/spec batch) is generic and plausible under our 524K-context, many-concurrent-request agentic workload. Fix: compute a_spec/b_spec via the same .index_select(0, spec_token_indx) already used for mixed_qkv_spec, in both branches of the "1. Convolution sequence transformation" section, and use them in the "2.1: Process the multi-query part" call instead of raw a/b. Note on file path: upstream's diff (and this task's brief) named vllm/model_executor/layers/mamba/gdn/qwen_gdn_linear_attn.py / vllm/v1/attention/backends/gdn_attn.py respectively; this fork predates upstream's gdn/ subdirectory split and keeps the layer at vllm/model_executor/layers/mamba/gdn_linear_attn.py (gdn_attn.py is the separate attention-*backend* metadata-builder file, home of our #51508 port -- untouched here, next in the harvest queue for #52078/#53077). Verified `_forward_core` at gdn_linear_attn.py:1299 is the sole call site matching upstream's context (only one `q=query_spec` callsite with `a=a, b=b` in the file). Added tests/v1/worker/test_gdn_spec_gate_alignment.py: _forward_core is CUDA-only (Triton, causal_conv1d, forward-context), so per the sibling #51508 port's precedent (test_mamba_stale_rows.py), this replicates the fixed indexing expression on small CPU tensors as an alignment contract test, plus a companion test proving the pre-fix (raw a/b) behavior actually misaligns on a mixed non-spec-then-spec batch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports of upstream PRs that apply to this fork's Qwen3.8-27B GDN + MTP3 stack. Each was rebased by hand onto v0.27.1 + the sm75/qwen3.8 base. - vllm-project#51812 [Bugfix] Align Qwen GDN gates with speculative tokens (v0.28): gather `a`/`b` with the same `spec_token_indx` as mixed_qkv before the fused recurrent update, so a mixed batch (non-spec tokens ahead of spec tokens) no longer feeds another token's gates to the kernel. - vllm-project#56196 [Bugfix] Mamba: store a short prefill chunk's conv state in its own block: with prefix caching a chunk shorter than the conv state wrote its new state through the pointer it read the initial state from, overwriting the shared prefix block. The destination block is now resolved once from `block_idx_last_scheduled_token` and used in every branch. Ported with its regression test. - vllm-project#49436 [Perf] 3D-grid tiling of the state-copy Triton kernels (v0.28): new `_memcpy_u64_tiled` head/body/tail device function partitions the temporal state's u64 body across `_TEMPORAL_TILES=16` CTAs (small-batch HBM fill), lifts the 8B-alignment assert to a warning, and collapses to a single-CTA copy for conv states. Ported with its tests. Not ported: vllm-project#52789 (internal prefill checkpoints for Mamba prefix caching). It is Kimi-K3 KDA only — `num_prefill_checkpoint_blocks` is set solely by `vllm/models/kimi_k3/nvidia/kda.py` under the flashkda backend, so the scheduler/manager infrastructure is inert for GDN models. The analogous capability here is the durable-anchor work in the kv commit. Tests: 149 CPU (host-tier/spill/prefix/mamba-align/api) pass; kernel suites pass on the 2080Ti (memcpy 120, precopy 75, causal_conv1d 156; the 8 float64-reference `varlen` failures are pre-existing on this machine and fail identically without the port). The vllm-project#56196 test fails on the unpatched kernel and passes with it. E2E on the 9.6e9 profile (real NVMe SSD tier): S resident 384,704 -> V0 deep revert cached 350,400 -> T evict -> R SSD restore cached 382,400 (sha db8b8e836881534b, same as the pre-port baseline) -> V2 post-restore revert cached 350,400; stores=3 restores=2.
Purpose
Fix Qwen GDN speculative decoding when a mixed batch places non-speculative tokens before speculative tokens.
mixed_qkvis gathered withspec_token_indx, but the fused recurrent update previously received the unsortedaandbgate tensors. The kernel consumes the firstT_specgate rows, so the gates could belong to different tokens than the gathered Q/K/V rows. Gatheraandbwith the same indices, while keeping the pure-spec fast path unchanged.Model evaluation
Reproduced end-to-end with
Qwen/Qwen3.5-2B, BF16, TP1, V1 runner, eager mode, two MTP draft tokens, andmax_model_len=128. The small model-length limit makes the boundary condition quick to reach; the same condition can occur near any configured context limit.Prompts of 120 and 64 tokens produced a mixed step with a one-token non-speculative row before a three-token speculative-decode row. The GDN metadata builder initially classifies any one-token non-speculative row as a decode based only on query length, without consulting whether the request is still prefilling. When a speculative decode is present, it reclassifies that row to the prefill kernel, but the row remains non-speculative for token splitting. Therefore either a true one-token decode or a final one-token prefill chunk can produce this mixed layout. Runtime metadata showed:
For the speculative request, compared with a no-MTP target-model reference from the first post-mixed-step token onward:
The greedy token IDs were identical before and after because the logit drift did not cross an argmax boundary in this case. The same workload on the V2 runner put speculative tokens first and was already unaffected.
Tests
qwen 3.5 blackwell tests passed https://buildkite.com/vllm/ci/builds/83349/canvas?jid=019ff0e3-ac1a-4a4e-a797-ef903d9c7985&tab=output
AI assistance
OpenAI Codex assisted with diagnosis, implementation, testing, and preparation of this PR. The human submitter must review every changed line and remains responsible for understanding and defending the change end-to-end.