Repository navigation
[None][fix] Preserve Mamba2/GDN replay state across KV cache V2 suspend - #21096
Closed
VALLIS-NERIA wants to merge 1 commit into
Closed
VALLIS-NERIA wants to merge 1 commit into
VALLIS-NERIA wants to merge 1 commit into
Conversation
…nd (By Agent) With MambaHybridCacheManagerV2 and the Mamba2 or GDN replay state update, the per-slot replay state (prev_num_accepted_tokens, cache_buf_idx, old_x / old_B / old_dt / old_dA_cumsum) lives outside the V2 pools and is indexed by SSM slot. A suspended generation request can resume in a different slot; its SSM/conv checkpoint follows the page but the replay state did not, silently corrupting the request's recurrent state. suspend_request now copies the slot's replay state to pinned host memory on the KV cache stream and forgets the slot, and the request's next generation step in _setup_state_indices copies it into the slot it resumes in. A request that comes back as a context request, or is freed, drops it. KDA and non-replay paths are unchanged. The replay tensors are sized once from the SSM pool's slot count and a KV pool rebalance can grow the pool past them, so enable_kv_pool_rebalance is rejected with Mamba2/GDN replay on V2, like the existing KDA guard. Signed-off-by: Xiwen Yu <13230610+VALLIS-NERIA@users.noreply.github.com>
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.
Description
Problem
Silent state corruption: with KV cache manager V2 (
MambaHybridCacheManagerV2) and the Mamba2 or GDN replay state update (speculative decoding,use_replay_state_update=True), suspending a generation request corrupts its recurrent state.The per-slot replay state lives outside the V2 pools, in plain torch tensors indexed by the SSM slot:
prev_num_accepted_tokens(PNAT)cache_buf_idxold_x/old_B/old_dt/old_dA_cumsum, shaped[layers, num_ssm_slots, 2, ...]A decoding request's true recurrent state is the V2 SSM checkpoint plus the replay history
history[cache_buf_idx][0:PNAT].A generation request is suspended in two places:
_try_evict_for_gen->kv_cache_manager.suspend_requestPyExecutor._rebalance_kv_pools_now: suspend all ->impl.adjust()-> resume allWhile the request is suspended, V2 may move its SSM page to the host tier and give the slot to another cache. On resume the page can come back in a different slot:
So the request resumes with whatever replay state its new slot holds: PNAT=0 in the repro, or a previous owner's stale history. KDA (Kimi) already handles this: it commits the pending drafts at suspend and re-seeds at resume. Mamba2/GDN had no equivalent.
Fix
The changes are in
MambaHybridCacheManagerV2and apply to Mamba2/GDN replay only. The KDA and non-replay paths are unchanged.suspend_request: for a non-dummy request that holds a slot, copy the slot's replay state to new pinned host tensors (async D2H). The copy covers PNAT,cache_buf_idxandold_x/old_B/old_dt/old_dA_cumsum, including both halves of the double-buffered history. The copies are kept per request id. Then forget the request's state-index entry, as KDA does, and call the base suspend._setup_state_indices:free_resources: drops the saved state too.Stream ordering mirrors the KDA path (
_on_kv_cache_stream):This orders the D2H after the request's last verify. It also orders it before anything a later owner of the slot writes: a context reset, a verify, or another request's restore. The H2D runs before the next forward.
Exactness: it is a pure copy, so the resumed state is bit-exact.
Dummy / padding requests are skipped. Their replay state is never read, and they keep their slot mapping.
The Philox seed buffer (
mamba_ssm_rand_seed) is intentionally not carried. The Mamba2 mixer passes only element 0 (rand_seed[:1]) to the kernels and advances the whole buffer on every forward. So the buffer acts as one shared Philox counter, not per-request state. Restoring an old per-slot value into slot 0 would rewind that shared counter.Size: each copy is the whole slot slice, which keeps the copy sync-free (no host read of PNAT or
cache_buf_idx). For a large Nemotron-H this is on the order of tens of MB of pinned host memory per suspended request. Copying only the livecache_buf_idxhalf would halve it; that is left out to keep this change small.Rebalance decision: config-time guard
_build_cache_confignow rejectskv_cache_config.enable_kv_pool_rebalance=Truewith Mamba2/GDN replay on V2. This mirrors the existing KDA guard. The reasons:_allocate_pool_replay_buffers).KvCacheManager::getPageIndexUpperBoundis derived from the pool group's current hot-level slot count.adjust()->_adjustLevel->adjustCacheLevelre-targets the pool-group ratios.So after a rebalance the SSM pool can hold more slots than the replay tensors cover, and a resumed request can land past their end. Rejecting the combination is safer than resizing these tensors in a short-term fix.
enable_kv_pool_rebalanceis an opt-in prototype (defaultFalse). The error message points toenable_kv_pool_rebalance=False, or toTRTLLM_USE_MAMBA_REPLAY=0for the non-replay path. Speculative decoding without replay can still use the rebalance.Scope
This is a short-term safety net. The long-term direction is to hold the replay buffers in a KVCM PRIVATE SSM lifecycle (see #21088). They would then move with the slot like the SSM/conv states do, and this evacuation would no longer be needed.
Test Coverage
New tests in
tests/unittest/_torch/executor/kv_cache/test_mamba_cache_manager.py:test_v2_replay_suspend_resume_keeps_the_replay_state[{slot_taken,slot_kept}-{mamba2_replay,gdn_replay}]_v2_kda_replay_manager_with_large_slotsand_take_ssm_slot.cache_buf_idx=1, random histories.slot_taken, its slot goes to another cache whose writes overwrite it._setup_state_indices, the test asserts that:test_v2_replay_suspended_request_resumed_as_context_starts_over[...]: a suspended request that comes back as a context request gets no stale history restored, either then or at its next generation step.test_v2_replay_suspend_keeps_nothing_for_freed_or_dummy_requests[...]: freeing a suspended request drops its saved state. A dummy's suspend saves nothing and keeps its slot mapping.test_v2_replay_refuses_kv_pool_rebalance[{replay,no_replay}-{mamba2,gdn}]: covers the new guard.How the tests were run
tensorrt-llm:pytorch-26.09-py3-x86_64-ubuntu24.04-skip-tritondevel-202610230003-19760.tensorrt_llm-1.4.0rc2+ngcpytorch2609, with this branch's git-tracked Python sources overlaid on top. This is a Python-only change on base011316470c.mamba_cache_manager.py(0113164)test_mamba_cache_manager.py, this PRtest_kv_cache_v2_scheduler.py+test_kv_pool_rebalance.py, this PROn the base, these 8 tests fail:
test_v2_replay_suspend_resume_keeps_the_replay_state[slot_taken-mamba2_replay]and[slot_taken-gdn_replay]:AssertionError: prev_num_accepted_tokens. The resumed slot has lost the history; this is the bug.test_v2_replay_refuses_kv_pool_rebalance[replay-*]: no guard, so nothing is raised.The 4 passing on the base are the
slot_keptandno_replaycases.The existing KDA suspend tests, including
test_v2_kda_replay_suspend_resume_matches_an_unsuspended_run, still pass with this change.PR Checklist
Please review the following before submitting your PR:
PR description clearly explains what and why. If using CodeRabbit's summary, please make sure it makes sense.
PR Follows TRT-LLM CODING GUIDELINES to the best of your knowledge.
Test cases are provided for new code paths (see test instructions)
If PR introduces API changes, an appropriate PR label is added - either
api-compatibleorapi-breaking. Forapi-breaking, includeBREAKINGin the PR title.Any new dependencies have been scanned for license and vulnerabilities
CODEOWNERS updated if ownership changes
Documentation updated as needed
Update tava architecture diagram if there is a significant design change in PR.
The reviewers assigned automatically/manually are appropriate for the PR.
Please check this after reviewing the above items as appropriate for this PR.
🤖 Generated with Claude Code