Repository navigation
Conversation
RecoverSSM's commit-plan and conv-window compaction kernels are model independent. Move them from models/kimi_k3 into model_executor/layers/mamba/ops/recoverssm.py (Kimi-K3 keeps its private names as import aliases; kernel source unchanged), add mamba/recoverssm_utils.py (activation predicate, argument validation) and the generic spec-row/request mapping helpers to v1/attention/backends/recoverssm_metadata.py. CacheConfig.use_kda_recoverssm becomes use_recoverssm: the switch now selects RecoverSSM for any model whose recurrent layers implement it, not only KDA. No functional change for Kimi-K3. Signed-off-by: Jürgen Schmied <juergenschmied70@gmail.com> Co-authored-by: Claude <noreply@anthropic.com>
With --use-replayssm and speculative decoding, Qwen4Exp's Gated DeltaNet layers verify the draft window from a read-only checkpoint and keep a per-token replay record [correction(V), normalized key(K), gate(1)] in fp32. After sampling, one commit replays the accepted tokens and writes the recurrent state once (plus the block-boundary state in align mode), instead of snapshotting the state after every draft position. The PLE dilated short conv follows the same protocol (accepted-suffix compaction). num_speculative_blocks = 0, so no per-draft Mamba blocks are reserved. - GDN verify/commit kernels: model_executor/layers/mamba/gdn/recoverssm_gdn.py - metadata/backends: v1/attention/backends/gdn_recoverssm.py, ple_recoverssm.py - the verify reads strided q/k/v views of the conv output and writes in place - builders declare AttentionCGSupport.NEVER: FULL_AND_PIECEWISE falls back to PIECEWISE; the fallback now also counts breakable CUDA graphs (empty splitting_ops, with or without torch.compile) as piecewise attention instead of dropping to NONE - the generic Mamba metadata builder skips the Mamba2 ReplaySSM ring-buffer workspaces when --use-replayssm selects RecoverSSM (the PLE short conv uses that builder) - tests: GDN verify/commit vs the native kernels (fp32 and bf16 state, align boundaries, mixed and ragged batches, extreme gates), PLE vs token-by-token decode, config selection Signed-off-by: Jürgen Schmied <juergenschmied70@gmail.com> Co-authored-by: Claude <noreply@anthropic.com>
…PLE short conv
|
Question: would you take a change that makes the RecoverSSM commit cheaper to launch, either by grouping it across KV cache groups or by putting it into the step's graph? Here or as a follow-up? Details below (we can only measure on XPU). On XPU (2× Arc Pro B70, Qwen3.8-Flash-Next, PP=2 with our follow-up #59983, MTP K=1) the commit shows up in single-stream latency on the last PP rank, and most of its cost is per-group launch overhead:
So two options we can see: (1) a grouped commit, one set of launches covering all groups of a rank (e.g. with a [group, batch] state-index table), or (2) moving the commit into the step's captured graph. On CUDA the per-launch host cost is probably smaller, so this may matter mainly for XPU. We can measure either option on XPU if that helps. Written with AI assistance (Claude Code). — TSUMUGI-XE |
|
@TSUMUGI-XE thanks for measuring it. Yes, please send it as a follow-up PR; this one is big enough already. Of the two options, I'd prefer the grouped commit: one set of plan/conv/state launches over a On the GB10 (PP=1, CUDA graphs) I can measure the host time of the per-group loop before and after on your branch. (Written with AI assistance.) |
|
This pull request has merge conflicts that must be resolved before it can be |
…ecode (#618) [GDN] Add gated_delta_rule_spec_recoverssm for vLLM RecoverSSM vLLM's RecoverSSM spec decode (--use-replayssm, vllm-project/vllm#58863) verifies drafts from a read-only checkpoint: each request owns one state block, the verify step records a float32 replay entry per token, and the accepted prefix is committed after sampling. gated_delta_rule_spec writes one state per draft position instead, so XPU cannot serve RecoverSSM with the native GDN kernels today. Add a spec-decode delta op with that contract. The recurrence is the same as gated_delta_rule_spec_kernel; the differences are: - the initial state is read from spec_state_indices_tensor[n] (1D, one block per request) and never written; - per token it stores replay_state[block, hv, t] = [corr (V), normalised k (K), decay (1)] in float32, the layout of vLLM's gdn_recoverssm_verify; - a request whose block index is <= null_block_id gets zero outputs and its replay block is not touched; - the gate input a + dt_bias is added in float32 so that the recorded decay matches vLLM's float32 verify/commit. The conv stage needs no change (causal_conv1d_spec with the block repeated across the index row and num_accepted_tokens = 1). Existing ops are unchanged. Signed-off-by: tsumugi-xe <291260587+tsumugi-xe@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
vllm/config/vllm.py: main removed ROCM_DEFAULT_MRV1_ARCHITECTURES (vllm-project#56360); keep only _QWEN4EXP_RECOVERSSM_ARCHS. tests/kernels/mamba/test_recoverssm_ple.py: ple_conv moved to vllm.models.qwen4_exp.common.ops.ple upstream (same signature). Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Jürgen Schmied <juergenschmied70@gmail.com>
|
@jschmied the grouped commit is up as #61017, stacked on this PR (only the last commit is new). On XPU, for 3 KV cache groups × 6 GDN layers, it cut the commit's host time from about 400 to 140 µs per step. With PP=2 it lowered the single-stream decode slope by 0.19 ms/token. Your GB10 numbers (PP=1) would show how much it helps on CUDA. Written with AI assistance (Claude Code). — TSUMUGI-XE |
|
This pull request has merge conflicts that must be resolved before it can be |
|
Thanks for this PR. We ported the same verify/commit protocol to the Qwen3.5 GDN layers on top of v0.31.0 locally. The GDN layer code is shared with Qwen4Exp; only Setup:
Output tok/s at c=32, stock spec-decode kernels → RecoverSSM:
At c=1 the two are within ±1% for every K. At c=8 we measured K=3 +8%, K=5 +2%, K=7 +3% (single runs). Accept length is unchanged in every run (e.g. K=5: 3.941 vs 3.939; per-position .856/.694/.558/.457/.376 vs .856/.693/.558/.456/.375). The gain grows with batch size and K. At c=1, and at K=1, the commit's fixed cost cancels the saving, which matches the K=2 / c=1 slowdown you mention. It may be worth noting in the docs that RecoverSSM pays off at K ≥ 3 under load. Correctness. Outputs aren't bit-identical to the stock kernels, as expected when float math is reordered. We checked with greedy decoding + top-5 logprobs:
Observation: record in the page vs. prefix reuse. You note the record makes the Mamba page larger, which widens the attention block. In our setup it grew the block from 576 to 656 tokens. On a workload where requests share a ~1.9k-token system prompt ending mid-block, shared-prefix hits land on block multiples:
The record only lives from a step's verify to the same step's commit, so we moved it out of the KV pool into a per-layer buffer ( Proposal: two small follow-ups stacked on this PR:
Happy to open these once this lands, or adjust if you'd rather fold either into this PR. Does that direction sound OK? |
Signed-off-by: Zihua Wu <13583761+lucifer1004@users.noreply.github.com>
|
Opened #61309 as a draft stacked on this PR to enable Qwen3.5 / Qwen3-Next (proposal 1 above). It also opts out the subclasses that would otherwise inherit SupportsReplaySSM from the Qwen3.5 base (Intern-S2, and the AMD Qwen4Exp model). |
…PLE short conv Conflicts with main resolved as in vllm#59366's main merge (2e5ea8b): kimi_k3/nvidia/ops/recoverssm.py keeps vllm-project#58863's version (the kernels moved to layers/mamba/ops/recoverssm.py, which already carries main's vllm-project#59962 fix); qwen4_exp/nvidia/mtp.py keeps main's HC sequence parallelism with SupportsReplaySSM.
vllm/models/kimi_k3/nvidia/ops/recoverssm.py: vllm-project#59962 fixed the commit plan's block index at exact block boundaries in _prepare_commit_plan_kernel; this branch moves that kernel to vllm/model_executor/layers/mamba/ops/recoverssm.py, which already carries the same fix, so the moved copy stays and the old one stays removed. vllm/models/qwen4_exp/nvidia/mtp.py: Qwen4ExpMTP keeps main's SupportsHCSP (vllm-project#56322) beside SupportsReplaySSM. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Jürgen Schmied <juergenschmied70@gmail.com>
|
This pull request has merge conflicts that must be resolved before it can be |
Signed-off-by: Zihua Wu <13583761+lucifer1004@users.noreply.github.com>
Resolve vllm/config/cache.py: take main's use_replayssm docstring (vllm-project#60043) and keep vllm-project#58863's use_recoverssm name. Rename the use_kda_recoverssm references main added since (glm5next KDA state test, DSv4.1 decoder replay test, Kimi-K3 specialized-decode runtime) to use_recoverssm. Signed-off-by: Zihua Wu <13583761+lucifer1004@users.noreply.github.com>
…PLE short conv Signed-off-by: Zihua Wu <13583761+lucifer1004@users.noreply.github.com> # Conflicts: # vllm/config/cache.py
Purpose
Speculative decoding on Gated DeltaNet today snapshots the full recurrent state after every draft position, and
reserves one Mamba block per draft position. This PR brings the RecoverSSM protocol that Kimi-K3 KDA already uses
to the Qwen GDN layers of Qwen4Exp (Qwen3.8-Flash-Next), plus its PLE dilated short conv:
[correction (V), normalized key (K), gate (1)]in fp32.block-boundary state in align mode. The PLE short conv compacts its accepted window with the same plan.
num_speculative_blocks = 0: no per-draft Mamba blocks.Enabled like KDA's:
--use-replayssmtogether with speculative decoding. Qwen4Exp (CausalLM, ConditionalGeneration,MTP) declares
SupportsReplaySSM; without spec decoding the flag is rejected on Qwen4Exp (there is no plain ReplaySSMpath for its GDN/PLE layers). Requires the V2 model runner for align mode, the Triton mamba backend and PP=1 (the same
checks as KDA). The GDN and PLE RecoverSSM builders declare
AttentionCGSupport.UNIFORM_BATCH, so the defaultFULL_AND_PIECEWISEcaptures FULL graphs for uniform spec-decode batches (and PIECEWISE for mixed ones). Graph paddingrows get a null state slot and a zero-length window, so the verify writes nothing for them. With breakable CUDA graphs
(auto-enabled on this setup; they split at attention with an empty
splitting_ops) a PIECEWISE fallback used to dropto NONE; it now resolves to PIECEWISE there.
Note: under FULL decode graphs
resolve_cudagraph_mode_and_sizesrequiresmax_num_seqs <= num_blocks. The replayrecord makes the unified page about 6 % larger, so a pool that just fits the default
max_num_seqsunder native MTPcan fall short with
--use-replayssm(seen on one H200 at TP1, 983 Mamba blocks); lower--max-num-seqsthere.The larger page also widens the attention block that hybrid models align to the Mamba page (Qwen3.8-Flash-Next on
one GB10: 1,600 → 1,664 tokens). Prefix reuse counts whole blocks, so a repeated prompt can reuse up to one block
less (a 9,698-token repeat reused 6,656 instead of 8,000 tokens in @k3dani's run); long repeats are unaffected.
The decode gain comes from dropping the per-draft state write-back, so it grows with the draft length: measured at
MTP K=3 and K=5 here; at K=2 with PIECEWISE graphs @k3dani measured c=1 decode 1.9–4 % slower.
Commits:
[Mamba] Share RecoverSSM commit kernels and helpers across models: the commit-plan and conv-compaction kernelsmove from
models/kimi_k3tomodel_executor/layers/mamba/ops/recoverssm.py(Kimi keeps its private names asaliases; kernel source unchanged);
mamba/recoverssm_utils.py; generic request-mapping helpers inv1/attention/backends/recoverssm_metadata.py;CacheConfig.use_kda_recoverssm→use_recoverssm. No functionalchange for Kimi-K3.
[Spec Decode][Qwen4Exp] RecoverSSM for Qwen GDN and the PLE short conv: GDN verify/commit kernels(
mamba/gdn/recoverssm_gdn.py), metadata builders (gdn_recoverssm.py,ple_recoverssm.py), the layer and modelintegration. The verify reads strided q/k/v views of the conv output and writes straight into the layer output.
The generic Mamba metadata builder no longer allocates the Mamba2 ReplaySSM ring-buffer workspaces when
--use-replayssmselects RecoverSSM (the PLE short conv uses that builder).[Mamba] RecoverSSM align: keep a boundary-ending state in its block: a window whose accepted tokens end exactly ona block boundary commits into the block holding its last token (
(n - 1) // block), not the next, possiblyunallocated column.
_teardown_profiling_state(its commit contexts kept the profiling KV cache alive; ~26 GiB on H200 and a startup OOM); read the RecoverSSM flag
with
getattrin_forward_core; one fold per program in the align-mode commit (290 → 197 µs on H200).[Spec Decode][Qwen4Exp] RecoverSSM: FULL cudagraphs for uniform spec-decode batches(above).[Bugfix][Mamba] RecoverSSM align commit: fold final states before boundary states: the split commit ran both foldsin one launch, and a boundary state can land in the window's source slot, so a program could read a source tile
another had overwritten. Final folds now run first, boundary folds second (skipping windows whose boundary slot is
their final slot).
Related: #49887 and #56466 (ReplaySSM for GDN) take the ring-buffer route; this PR reuses the merged RecoverSSM
protocol instead (discussed in #56466 (comment)).
Test Plan
tests/kernels/mamba/test_recoverssm_gdn.py: verify bit-identical to the native fused kernel (checkpoint untouched);commit bit-identical to the native per-token states (accepted 1–4); align boundaries (8 cases, incl.
exact-boundary and crossing);
mixed prefill/decode mapping and non-contiguous request rows; extreme gates × 3 seeds; strided q/k/v views +
out=(uniform and ragged windows 4/1/3) and the layer's view/fallback decision; BF16 recurrent state (verify exact; commit
rounds once: within one BF16 ulp of the FP32 reference and no further from it than the native path);
malformed-input rejection.
tests/kernels/mamba/test_recoverssm_ple.py: spec conv + commit vs the stock token-by-token decode (accepted 1, 2,full window; 4 align cases; non-contiguous request rows), bit-identical states and outputs.
tests/config/test_recoverssm_config.py: selection through the real validator (target and the MTP drafter's derivedconfig), runtime-check rejections, no spec decoding, unsupported architectures, the FULL-graph declaration.
tests/kernels/mamba/test_recoverssm_gdn.pyalso pads a batch the way graph capture does and checks that padding rowschange no state;
tests/v1/worker/test_gpu_model_runner_v2_cudagraph_profiling.pychecks the teardown releases therecorded step.
caching, 2 starts per arm: output hashes, cache-hit replay, decode and agent-loop timing.
Test Result
Tests on GB10 (sm_121): GDN + PLE kernel tests 72 passed, config tests 18 passed. ruff, mypy (tools/pre_commit/mypy.py 3.12), typos: clean.
Server, native GDN spec decode (align) vs RecoverSSM (align), 2 starts per arm
(data):
the per-draft state write-back is gone (GDN
out_proj101 → 74 µs, no dirty-L2 aftershock); the commit runs at theDRAM floor (224 GB/s).
(§5g).
514102a1db(suggested by @ArtyomITA)the verify step records the decay it multiplied by,
exp(g), and the commit replays the accepted tokens forward withthe same update,
h = fma(c, k, h * decay), so the committed state equals the native per-token state bit for bit:the FP32 kernel tests now assert
torch.equal(the earlier closed form fails 28 of those 62 cases on a GB10).@ArtyomITA measured greedy tokens and top-20 logprobs identical to a run without
--use-replayssmon sm_120(8 prompts of 120–245k tokens, 1 and 4 users, matched block size and a deterministic top-k, [Kernel] persistent_topk: deterministic select, faster than the exact-topk workaround #55122); not repeated
here. Before that change, text differed from the native path at the size of a reduction-order change (first greedy
divergence at a median of 26 tokens).
--use-replayssmwith explicit PIECEWISE: c=1 21.22 ms/tok,output hashes and cache-hit replay identical to the validated reference; default
FULL_AND_PIECEWISE: resolves toPIECEWISE, c=1 21.31 ms/tok, c=1 and c=4 hashes identical to the reference, cache-hit replay 8/8.
ms/tok (−1.1…−2.1 %), c=4 level, identical greedy text. On 2× RTX PRO 6000 (TP2) and one H200 (TP1) @lucifer1004
measured this PR plus the FULL graphs and a fused CUDA verify at parity with native MTP3 at C1, −1…−2 % at C2–C16,
and up to +17 % output tok/s at high concurrency (RTX C32, H200 C64), with H200 preemptions at C64 going from 188 to 0
([Spec Decode][Qwen4Exp] RecoverSSM for Qwen GDN and the PLE short conv #58863 (comment)).
3388ba1a25on GB10: the five test files above 121 passed;test_recoverssm_gdn.py20 of 20 repeated runspass (the split commit without the ordering fix failed 9 of 12); greedy hashes identical to the reference.
Essential Elements of an Effective PR Description Checklist
supported_models.mdandexamplesfor a new model.Developed with AI assistance (Claude Code); all changes reviewed by the author.