Conversation
ivanium
marked this pull request as ready for review
September 10, 2026 07:45
ivanium
requested review from
ApostaC,
WoosukKwon,
alexm-redhat,
heheda12345,
njhill,
robertgshaw2-redhat and
ywang96
as code owners
September 10, 2026 07:45
ivanium
requested review from
orozery,
pavanimajety and
yewentao256
as code owners
September 10, 2026 07:45
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.
Tip: disable this comment in your organization's Code Review settings.
4 tasks
zyongye
requested review from
AndreasKaratzas,
aarnphm,
chaunceyjiang,
dllehr-amd,
hongxiayang,
sfeng33,
tjtanaa and
zyongye
as code owners
September 11, 2026 00:49
Contributor
|
This pull request has merge conflicts that must be resolved before it can be |
ivanium
force-pushed
the
feat/dsv41-swa-bounded-replay
branch
from
September 11, 2026 18:20
464ddaa to
958593a
Compare
ivanium
force-pushed
the
feat/dsv41-swa-bounded-replay
branch
from
September 13, 2026 00:28
958593a to
49cfff3
Compare
Contributor
|
This pull request has merge conflicts that must be resolved before it can be |
ivanium
force-pushed
the
feat/dsv41-swa-bounded-replay
branch
2 times, most recently
from
September 13, 2026 22:49
40d594c to
6842399
Compare
This was referenced Sep 13, 2026
DeepSeek-V4.1 keeps a 128-token sliding-window (SWA) KV cache next to its prefix-cacheable MLA/indexer caches. Storing that window for prefix caching and KV connectors costs more than it saves, so this implements DeepSeek's "SWA bounded replay": the SWA groups opt out of prefix caching, and after a hit ending at H the scheduler recomputes the hit's last window [H-128, H) to rebuild them. Those tokens keep their cached KV in every other group, and their window attention ignores positions below H-128. - Spec: `SlidingWindowMLASpec(bounded_replay=True)` derives `prefix_cacheable` (False) and `prefix_replay_tokens` (the window); `UniformTypeKVCacheSpecs` forwards the latter so the worker reads it through packed groups. - Scheduler: `_mark_prefix_replay` records the request's `replay_start` and rewinds its computed count by the window. Hits are replayed at fresh admission (local and sync connector) and after an async connector load. Chunks that end inside the replayed range allocate nothing past the hit. A hit no longer than the window is not adopted at all (it would be recomputed in full), so a replaying request always keeps computed tokens. All replaying groups must agree on one window. - Worker: `replay_start` lives in `RequestState`, is gathered per batch by the existing pos/seq_lens kernel and reaches the backends through `CommonAttentionMetadata`; the slot-mapping kernel pads the replayed window's writes in prefix-cacheable groups. Compressed slot mappings (MLA and indexer) inherit that padding inside their kernel. - DeepSeek-V4.1 kernels clamp the window at `replay_start`: the SWA index and gather-length kernels, the FlashInfer mixed index kernel and the FlashMLA combine kernel. - Policy: `CacheConfig.swa_bounded_replay` (`--swa-bounded-replay`, default on), read by the V4.1 attention layer. Requires model runner V2; on V1 it logs and keeps the SWA cache in prefix caching. Two-pass gsm8k on TP4 GB200 (pass 2 = 92.6% full-prompt hits): pass 1 0.8954, pass 2 0.8976 with replay vs 0.898 with the SWA cache stored. GPQA-diamond two-pass (n=198): 0.6869 / 0.6919 vs 0.7020 / 0.6869. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Yifan Qiao <yifanqiao@inferact.ai>
Dummy batches (profiling, CUDA graph capture) carry all-zero block tables. Every other cache write goes through the PAD slot mapping and is skipped, but the ratio-2 compressor derives its ring slot from the block table, so capture wrote a FP32 ring row into block 0. In the packed layout every group's block b overlays the same physical page, so the shared null block came up non-zero in every layer's sliding-window and main-cache view (layers 0-19). Nothing reads the null block in normal operation, but any window that does reach it (e.g. a bounded replay without its clamp) then attends garbage instead of zeros. Pad ring slots whose block is 0. Verified with an init-only probe: block 0 of every KV cache view is zero after CUDA graph capture, where it held a full ring row before. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Yifan Qiao <yifanqiao@inferact.ai>
… too The indexer builder derives its compressed slot mapping per rank and, under prefill context parallelism, passed no token slot mapping to inherit the runner's write mask from, because the one it holds is in the gathered layout. That layout is each rank's local tokens, padded, in rank order, so this rank's segment lines up with its query_start_loc: pass that slice. Replayed tokens then leave the indexer's cached K alone under PCP as they already do without it, and replicated decodes on non-zero ranks stop writing compressed slots, matching the KV write mask. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Yifan Qiao <yifanqiao@inferact.ai>
Review feedback on the SWA bounded replay: the compressed slot mapping kernel does not need a HAS_TOKEN_SLOT_MAPPING specialization. Every builder has the tokens' slot mapping at hand, and a token whose own slot is padded closing no compressed state holds whether or not bounded replay is on, so the kernel always reads it. That drops the constexpr branch, the doubled warm-up keys and the pointer aliasing for the absent case; the DeepSeek-V4 sparse MLA builder now passes its slot mapping like the V4.1 builders do. The input keeps vLLM's name for the tokens' slots, `slot_mapping`, and sits with the other inputs; the output is the `compressed_slot_mapping` the function is named after. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Yifan Qiao <yifanqiao@inferact.ai>
Contributor
|
This pull request has merge conflicts that must be resolved before it can be |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Purpose
DeepSeek-V4.1 keeps a 128-token sliding-window (SWA) KV cache per layer beside its prefix-cacheable MLA/indexer caches. Storing that window for prefix caching and KV connectors costs more than it saves, so this implements DeepSeek's encoder-side "SWA bounded replay": the SWA groups opt out of prefix caching, and after a Main/Indexer hit ending at H the scheduler recomputes the hit's last window [H-128, H) to rebuild them. Replayed tokens keep their cached KV in every other group (their paged-KV writes are padded), their window attention ignores positions below H-128, and decode always sees a full window. Outputs after a hit are therefore not bit-identical to a cold run, by design.
SlidingWindowMLASpec(bounded_replay=True)derivesprefix_cacheable=Falseandprefix_replay_tokens=sliding_window;UniformTypeKVCacheSpecsforwards it so the worker reads it through packed groups.Request.replay_start = H-128and rewinds the computed count by the window; async connector loads replay once the KV arrives; chunks ending inside the replayed range allocate nothing new; hits no longer than the window are not adopted (they would be recomputed in full).replay_startlives inRequestState, is gathered per batch by the existing pos/seq_lens kernel and reaches attention metadata asCommonAttentionMetadata.replay_start; the slot-mapping kernel pads the replayed window's writes in prefix-cacheable groups, and compressed slot mappings (MLA and indexer) inherit that padding inside their kernel.replay_start: SWA index and gather-length kernels, the FlashInfer mixed index kernel and the FlashMLA combine kernel.--swa-bounded-replay(CacheConfig.swa_bounded_replay, default on), read by the V4.1 attention layer. Needs model runner V2; on V1 it logs and keeps the SWA cache in prefix caching.KV-connector load failures: with #50388 a hybrid request that lost blocks is recomputed from position 0. Async loads then go back through admission, which recomputes the replay boundary. A running request under
kv_load_failure_policy=recomputeis restarted in place and keeps its worker-side replay boundary, so it would recompute with a stale clamp and write mask; a follow-up will preempt such requests instead (in-place restart also writes sliding-window KV of positions below the old window into the shared null block, replay or not). No other open PR implements SWA replay; #53558 (KV cache config builder) is orthogonal.Usage
Nothing to configure: DeepSeek-V4.1 replays by default on model runner V2 (the default runner).
--no-swa-bounded-replayrestores the previous behaviour (SWA KV stored in the prefix cache). With replay on, KV connectors transfer only the MLA/indexer caches; the SWA cache is rebuilt locally after every hit, and hits of at most 128 tokens are not adopted.Test Plan
Accuracy:
deepseek-ai/DeepSeek-V4.1-Flashon TP4 GB200 withvllm serve, gsm8k (5-shot) run twice against the same server; pass 2 re-sends every prompt, so each request is a full-prompt prefix hit that replays only its last window (92.7% prefix-cache hit rate).Test Result
All listed tests pass; pre-commit and mypy are clean. A per-layer probe (cold prefill vs. prefix-hit replay of one prompt) confirms the replayed rows follow the clamped window and the cached Main/indexer KV is untouched.
--no-swa-bounded-replay)The 0.3% gap is the same in pass 1, where almost nothing is replayed, so it is run-to-run noise rather than a replay effect. An earlier two-pass GPQA-diamond run on an internal pre-release checkpoint showed the same parity (0.6869 / 0.6919 with replay vs 0.7020 / 0.6869 without, n=198).
🤖 Developed with Claude Code; all changes reviewed and tested by the author.
Essential Elements of an Effective PR Description Checklist
supported_models.mdandexamplesfor a new model.