Skip to content

[Feature][MRV2] Enable Qwen3.5 GDN ReplaySSM standard decode - #64699

Draft
wunnii9 wants to merge 16 commits into
vllm-project:mainfrom
wunnii9:codex/replayssm-gdn-v2
Draft

wunnii9 wants to merge 16 commits into
vllm-project:mainfrom
wunnii9:codex/replayssm-gdn-v2

Conversation

@wunnii9

@wunnii9 wunnii9 commented Oct 30, 2026

Copy link
Copy Markdown

Overview

Enable Qwen3.5 GDN standard-decode ReplaySSM on Model Runner V2 by forwarding each request's decode origin to the existing GDN metadata builder.

Stacked on #62004 at 3c8678368a6358cea60db7841a5cca7c3263ce69; draft until that dependency lands. Only the final commit is new. All preceding ReplaySSM commits retain their original authors and SHAs. This adapter adds no ReplaySSM kernel or Skinny GEMM implementation.

Claims

  • Permit Triton ReplaySSM with V2 for qwen3_5_text, preserving the guard for other models.
  • Snapshot V2's batch-ordered CPU prefill_len_np, including resumed-request endpoints, as replayssm_decode_base_cpu; zero-fill graph padding.
  • Reuse the existing GDN ring/flush calculations and persistent CUDA-graph buffers, without GPU-to-CPU reads or new synchronization.
  • Add configuration, metadata, and compiled V2 engine regression coverage, plus usage documentation. Existing compilation mode and Breakable graph settings remain user choices.

Validation

Single GB10 (SM121), Qwen3.5-4B BF16, FP32 state, TP=1, L16, no speculative decoding or prefix caching.

The proposed branch is tested on the real #62004 head. Offline fixtures use the available local checkpoint configuration; submitted tests contain no local paths. Test commands:

.venv/bin/python -m pytest \
  tests/v1/worker/test_mamba_hybrid_model_state.py \
  tests/v1/attention/test_gdn_metadata_builder.py \
  tests/v1/worker/test_gpu_batch_ordering.py -q
.venv/bin/python -m pytest tests/test_config.py \
  -k 'triton_replayssm_v2_model_scope or kda_recoverssm_derivation' -q
.venv/bin/python -m pytest \
  tests/kernels/test_replayssm_standard_decode_gdn.py \
  tests/kernels/test_replayssm_teacher_decode_equivalence_gdn.py \
  -k '(qwen4b and s32_a16) or (per_row and s32_a16) or flush_keeps' -q
VLLM_GPU_SYNC_CHECK=error .venv/bin/python -m pytest \
  tests/v1/e2e/test_replayssm_decode.py \
  -k test_replayssm_gdn_v2_decode_matches_baseline -q
pre-commit run

On the final dependency-head branch: 128 passed, 551 deselected; full pre-commit run and commit hooks pass. The compiled V2 engine comparison ran with VLLM_GPU_SYNC_CHECK=error. Tests completed without failures. The earlier standalone checkout had 131 passing cases; the count changes because the real dependency head contains a different upstream test revision.

The V2 engine case explicitly uses compilation mode 3, Breakable off, graph capture size 4, and two prompts with up to 32 output tokens, exercising L16 flushes. It uses the existing parity helper: identical greedy tokens, or mutual top-five membership at the first divergence. This is a limited output regression, not full-vocabulary equivalence or GSM8K/MMLU evaluation.

Earlier standalone validation also passed 24/24 arithmetic checks, C1/C8 generation, and 32/32 mixed requests under GPU sync checking. Historical experiment on main a9fe30e9f6 plus #62004, both runners compiled with Breakable off, 512 input / 128 output tokens:

Runner + Replay L16 C1 TPOT ms C64 TPOT ms C64 output tok/s
V1 48.287 77.576 649.696
V2 48.092 75.189 666.496

Three rounds per condition, one startup per runner; C1 four requests per round and C64 one burst of 64. TPOT ranges: C1 V1 48.184–48.368 / V2 48.056–48.130; C64 V1 77.369–77.761 / V2 75.083–75.260 ms. These historical measurements support the compiled V2 path on this workload; they are not a fresh benchmark of this exact dependency-head branch. That benchmark build contained a disabled Skinny hook, absent here.

Details

Relaxing the V2 configuration guard alone leaves the GDN builder without its required CPU decode base. V2 already gathers the prefill endpoint in batch order and refreshes it after preemption. Forwarding a per-step CPU snapshot supplies the builder's existing cursor calculation while keeping request-state ownership in V2. Tests cover a flush boundary, resume origin, final single-token prompt chunk, null graph padding, and persistent device-buffer reuse.

This extends #62004, whose stated scope excludes V2. Related RecoverSSM speculative decoding and Mamba2 FlashInfer lifecycle PRs address different paths. Duplicate searches on 2026-10-30 (62004 in:body, ReplaySSM V2, and GDN standard-decode keywords) found no existing open PR implementing this adapter. #62004 explicitly lists Model Runner V2 as out of scope.

Standard decoding on one GB10 is validated. Other models, TP>1, V2 align-mode prefix caching, and speculative decoding remain outside this validation. The adapter preserves the kernel mathematics and existing compilation defaults.

AI assistance was used: OpenAI Codex assisted with investigation, implementation, test execution, and this description. Tests were executed by Codex at the submitter's explicit request.


Pull Request Checklist
  • I used vLLM's /pr-checklist skill. (Mandatory for agents, optional for humans).
  • AI assistance was used during the creation of this PR.
  • Design Fit: Minimizes impact on core components, reuses existing functionality, and justifies added complexity.
  • Testing and Validation: Validates the change and ensures any added tests are meaningful and reliable, with CI coverage or documented CI resource constraints and validation performed outside CI.
  • Code Quality and Style: Keeps code and comments clear and concise, and updates relevant documentation and examples.
  • Pull Request Contents: Includes a brief summary and relevant links, supports claims with evidence, explains root causes and implementation trade-offs, and follows the contributing guide.

Johnny-Liou and others added 16 commits September 30, 2026 13:19
Signed-off-by: Johnny-Liou <a897111@gmail.com>
…che, write_pos)

Signed-off-by: Johnny-Liou <a897111@gmail.com>
Signed-off-by: Johnny-Liou <a897111@gmail.com>
Signed-off-by: Johnny-Liou <a897111@gmail.com>
…ySSM

The GDN ring shape and dtype came from combined helpers that each model branched into on both the layer path and the config path, the pattern the Mamba2 sub-PR removed. Replace them with append_gated_delta_net_replayssm_ring on each calculator, called by both paths so the config-path page size cannot drift from the layer-path allocation. The five-tensor shape and dtypes are unchanged.

Add the SupportsReplaySSM capability to the Qwen3.5 GDN classes so validate_mamba_cached_kernel admits cached decode: Qwen3_5ForCausalLMBase for the text-only dense/MoE arches and Qwen3_5ForConditionalGeneration for the multimodal ones, each with MoE inheriting. Both config paths append the ring, matching the layer path they share. Reword the rejection message that named only Nemotron-H and extend the registry test to all four arches.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Johnny-Liou <a897111@gmail.com>
The GDN builder derived write_pos from num_prompt_tokens_cpu, which the Mamba2 sub-PR dropped after finding the value wrong across preemption: a resumed request replays prompt+generated through prefill and writes a fresh checkpoint state, so counting write_pos from the prompt boundary folds a stale ring onto a current checkpoint and corrupts the output. Read replayssm_decode_base_cpu, which anchors at the ring's last full-state write, so a resumed request decodes from write_pos 0. The shared plumbing arrived with the rebase; only the GDN builder consumed the old field.

Add write_pos cases to test_gdn_metadata_builder.py covering fresh, flush-boundary, wrap, and the resume re-anchor.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Johnny-Liou <a897111@gmail.com>
The cached decode kernel derived its flush flag from write_pos == L - 1, which only materializes the checkpoint on a full buffer. Prefix caching in align mode needs the exact state at each mamba block boundary, so make is_flush a per-row kernel input. The metadata builder re-anchors the ring at each block start (effective_base = max(decode_base, block_start)) and forces a flush on the step that completes a block; the flush branch already rebuilds from the committed ring window, so a mid-buffer flush is correct.

A single-token prompt step replayed as decode (num_computed < decode_base) now takes a one-token flush off the checkpoint instead of raising, matching the baseline decode for that row.

Extends the GDN kernel and metadata-builder tests to cover is_flush, the align re-anchor, the block-boundary flush, and the leftover-prompt row.

Signed-off-by: Johnny-Liou <a897111@gmail.com>
Signed-off-by: Johnny-Liou <a897111@gmail.com>
Rename the GDN mixer and metadata-builder attributes to match the two config flags: use_cache_kernel / use_cached_kernel become use_replayssm, and max_cache_len becomes replayssm_buffer_len (None when ReplaySSM is off). The leaf kernel op keeps its local max_cache_len parameter.

Refresh the launch-config module docstring, which named only the Mamba2 kernel, and document the GDN config tuple (block_v, num_warps, num_stages, nk).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Johnny-Liou <a897111@gmail.com>
Under use_replayssm the GDN builder splits with
treat_short_extends_as_decodes=False, which sends a still-prefilling
single-token row to the prefill group. FULL-cudagraph dispatch is
shape-based, so a batch of all-1-token queries containing a final
1-token prefill chunk still replays the decode graph, while
num_prefills > 0 skips the persistent-buffer refresh. The replayed
graph then reads last step's state indices, ring cursor, and flush
flag, which corrupts the output and the checkpoint without raising.

Clear is_prefilling for single-token rows that have prior state before
the split, as mamba_attn.py does. Those rows stay decodes, num_prefills
stays 0, and the write-position derivation turns them into one-token
flushes off the exact checkpoint, which matches the baseline because a
row that has never decoded has an empty ring. Rows with seq_lens == 1
have no prior state and stay prefills.

Add two builder cases: a full-cudagraph batch mixing a decode with a
final 1-token prefill chunk, asserting the row lands in the decode
group and the cursors come from the captured buffers, and a first-token
prefill that must stay a prefill.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Johnny-Liou <a897111@gmail.com>
Every align-mode write_pos case used block_size == replayssm_buffer_len == 16,
so the block anchor and the ring period were indistinguishable. Parameterize
block_size per case and add one with block_size 64: decode_base wins over the
block start while the prefill ended mid-block, the block-completing step
flushes mid-ring, and the next step re-anchors once that boundary holds the
checkpoint.

Mid-block prefill ends became reachable when vllm-project#50000 replaced the align-mode
block_size <= max_num_batched_tokens assert with sub-block progress, so note
why the anchor takes the max rather than the block start.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Johnny-Liou <a897111@gmail.com>
Use sequence and query lengths for ring positions. Handle five-tensor
GDN caches, reject FlashInfer GDN replay, and keep KDA RecoverSSM separate.

Co-authored-by: OpenAI Codex <noreply@openai.com>
Signed-off-by: Johnny-Liou <a897111@gmail.com>
Cover one-token flushes and use production FP16 ring storage in BF16
tests. Account for BF16 state rounding between flushes.

Co-authored-by: OpenAI Codex <noreply@openai.com>
Signed-off-by: Johnny-Liou <a897111@gmail.com>
Use the supported runner in parity tests and benchmarks. Keep FlashInfer
V2 tests on Mamba2 and add GDN prefix-cache parity at TP2.

Co-authored-by: OpenAI Codex <noreply@openai.com>
Signed-off-by: Johnny-Liou <a897111@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Johnny-Liou <a897111@gmail.com>
Forward the batch-ordered prefill endpoint to the existing GDN metadata
builder so V2 can derive ReplaySSM ring positions without GPU host reads.
Preserve other-model guards and cover compiled V2 with regression tests.

Depends on vllm-project#62004. The parent keeps the original authors'
ReplaySSM commits unchanged; this commit adds only the V2 adapter.

Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: wunnii9 <1515543516@qq.com>
@mergify

mergify Bot commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

Documentation preview: https://vllm--64699.org.readthedocs.build/en/64699/

@mergify mergify Bot added documentation Improvements or additions to documentation performance Performance-related issues qwen Related to Qwen models kimi k3 mrv2 Model Runner V2 specific labels Oct 30, 2026
@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. Reviewers with write access and configured trusted contributors can comment /ci run for upstream CI or /amd-ci run for AMD CI only whenever CI signals are needed.

Once the PR is approved or has the ready label, the PR author can also use the corresponding /ci run, /ci retry, and /ci cancel commands, or their /amd-ci variants. New commits do not start upstream CI automatically.

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 commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @wunnii9.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@mergify mergify Bot added the needs-rebase label Nov 9, 2026

This branch has not been deployed

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

Labels

documentation Improvements or additions to documentation k3 kimi mrv2 Model Runner V2 specific needs-rebase performance Performance-related issues qwen Related to Qwen models

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants