Skip to content

[BugFix] Mamba sparse retention keeps a state below each boundary under EAGLE/MTP - #556

Open
tobymao wants to merge 2 commits into
local-inference-lab:dev/jovian-judgementfrom
tobymao:fix/mamba-retention-eagle-boundary
Open

[BugFix] Mamba sparse retention keeps a state below each boundary under EAGLE/MTP#556
tobymao wants to merge 2 commits into
local-inference-lab:dev/jovian-judgementfrom
tobymao:fix/mamba-retention-eagle-boundary

Conversation

@tobymao

@tobymao tobymao commented Sep 1, 2026

Copy link
Copy Markdown

Prefix caching never hits on hybrid models running MTP/EAGLE spec decode. Measured on GLM-5.3-Flash in production: 0 hit tokens against 16,897 queried, silently.

The full-attention EAGLE lookup drops one block below what it matched, but latest-only Mamba retention (prefix_cache_retention_interval=0, the default) keeps only the state at the boundary itself, one block above anything a lookup can ever request. The reconciled hit is always 0.

Fix: when the group runs under EAGLE, also retain the state one block below each reachable boundary. Costs at most one extra state page per request; non-speculative behavior unchanged.

Verified live on a 4x DGX Spark GLM-5.3-Flash deployment (MTP k=3, TP=4): an identical 8,901-token resend now hits 4,608 tokens, warm TTFT 2.66 s vs 5.88 s cold, warm output byte-identical at temperature 0. Regression test included (fails without the fix); tests/v1/core/test_prefix_caching.py passes 92/92.

Summary by CodeRabbit

  • Bug Fixes
    • Improved prompt caching for hybrid attention and Mamba models using EAGLE/MTP.
    • Preserves an additional boundary state so requests that fall one block below a match can still reuse cached data.
    • Enables expected two-block cache hits for eligible repeated prompts.

…er EAGLE/MTP

The full-attention EAGLE lookup drops one block below what it matched, so
until a request decodes past the block boundary after its prompt, the only
candidate the coordinator can offer a Mamba group is one block below the
replay boundary. Latest-only retention (prefix_cache_retention_interval=0,
the default) kept exactly the boundary state, leaving every retained state
one block above every reachable candidate: the reconciled prefix-cache hit
was always zero on hybrid models running MTP/EAGLE spec decode.

Measured live on GLM-5.3-Flash (MTP k=3, TP=4): 0 hits across 16,897
queries; per-group lookups on an identical resent 8,901-token prompt
returned (6912, 6912, 6912) for the Mamba groups against 4608 for the
eagle-dropped full-attention group, reconciling to 0.

Keep the state one block below each reachable boundary as well when the
group runs under EAGLE. The extra state only materializes where a block
boundary coincides with an aligned prefill chunk end (the running-state
block), which is how the align-mode scheduler chunks prompts.
@coderabbitai

coderabbitai Bot commented Sep 1, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

Next included review available in 30 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 9dcafd4f-9c54-4b01-9751-9bd3f3e74599

📥 Commits

Reviewing files that changed from the base of the PR and between b367b04 and a93cc7f.

📒 Files selected for processing (3)
  • tests/v1/core/test_prefix_caching.py
  • vllm/v1/core/kv_cache_coordinator.py
  • vllm/v1/core/single_type_kv_cache_manager.py

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 4b92787f-ff26-4caa-87c7-8e17b2307fa3

📥 Commits

Reviewing files that changed from the base of the PR and between 0230ec1 and b367b04.

📒 Files selected for processing (2)
  • tests/v1/core/test_prefix_caching.py
  • vllm/v1/core/single_type_kv_cache_manager.py

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The Mamba reachable-block mask now retains the boundary state and the preceding state for EAGLE. A hybrid prefix-caching test verifies a two-block hit after replaying a 127-token prompt.

Changes

Mamba EAGLE retention

Layer / File(s) Summary
Retain the preceding reachable boundary state
vllm/v1/core/single_type_kv_cache_manager.py
reachable_block_mask retains boundary_block and boundary_block - 1 when use_eagle is enabled. The comment documents the segment-tail behavior.
Verify hybrid prefix reuse
tests/v1/core/test_prefix_caching.py
The test verifies that latest-only Mamba retention preserves hashes 1 and 2 and produces a two-block computed hit after replaying the prompt.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to b367b

The PR restores prefix-cache hits for hybrid EAGLE/MTP requests by retaining one additional predecessor state while leaving non-speculative behavior unchanged. No actionable merge-blocking risk remains after normal checks and review.

Suggested reviewers: lucaswilkinson, yewentao256, zjy0516

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 4 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: retaining a Mamba state below each boundary for EAGLE/MTP sparse retention.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…group's drop

Two defects found reviewing the first cut, both caught by new tests:

1. The back-off was hard-coded as one Mamba block, but the drop the
   full-attention finder applies is min(alignment_tokens, its block_size)
   followed by a re-floor to the alignment -- which lands exactly one
   ALIGNMENT unit below the boundary either way. When a group's block size
   differs from the alignment (their LCM), one block is the wrong step: the
   retained state sits at an offset Mamba's own finder rejects, so the hit
   stays 0 and the extra block is dead weight. Now computed in tokens via
   reachable_hit_positions(), which is exact for alignment >, == and < the
   block size.

2. Retention keyed off the group's OWN use_eagle bit, which is 'this group
   holds draft layers'. The right predicate is 'some group's lookup shortens
   the candidate offered to me', since the coordinator reconciles all groups
   to one hit length. It worked only via the coordinator's flag-all fallback
   (no annotator exists for glm5_next); the day one lands, the zero-hit bug
   returns silently. The coordinator now sets lookup_drops_eagle_block on
   every manager from bool(self.eagle_group_ids).

Tests: parametrize the MTP test over the three annotation routes (fallback,
full-only, both) and give it real speculative blocks + lookahead; add a
differing-block-size test pinning the alignment-unit back-off. Both fail on
the previous cut (full_only: 'mamba hash 1 should be cached'; backoff:
'reachable state missing; cached=[5]'). 95/95 in test_prefix_caching.py.
@tobymao

tobymao commented Sep 1, 2026

Copy link
Copy Markdown
Author

Updated after review: two defects in the first cut, both now pinned by tests.

The back-off is one alignment unit, not one block. The full-attention finder subtracts min(alignment_tokens, its block_size) then re-floors to the alignment, landing exactly one alignment unit below the boundary either way. Backing off one Mamba block is only correct when the two are equal; when the groups' block sizes differ (alignment = their LCM) the retained state sits where Mamba's own finder rejects it, so the hit stays 0 and the extra block is dead weight. Now computed in tokens by reachable_hit_positions(), exact for alignment greater than, equal to, and less than the block size.

Retention must key off "any group drops", not "this group is eagle". The coordinator reconciles every group to one hit length, so a drop anywhere shortens the candidate offered here. The first cut read the group's own use_eagle bit and worked only through the coordinator's flag-all fallback (no annotator exists for glm5_next); the day one lands, the zero-hit bug returns silently. The coordinator now sets lookup_drops_eagle_block on every manager from bool(self.eagle_group_ids).

Tests: the MTP test is parametrized over all three annotation routes (flag-all fallback, full-attention-only, both) and now runs with real speculative blocks and lookahead; a new differing-block-size test pins the alignment-unit back-off. Both fail on the previous cut (full_only: "mamba hash 1 should be cached"; back-off: "reachable state missing; cached=[5]"). 95/95 in test_prefix_caching.py.

Two related defects found in review and deliberately not fixed here, since each is pre-existing and independent:

  • SlidingWindowManager.reachable_block_mask has the same shape of bug on this path: its shift=+1 retention keeps a run the post-drop lookup cannot see, so an SWA group reconciles to 0 exactly as Mamba did whenever the full-attention group also drops.
  • Section (2) of the SWA and Mamba masks are now near-duplicates; a shared helper on the base class would give a third sparse-retention spec the behavior for free. Happy to fold either in here if preferred.

@voipmonitor

Copy link
Copy Markdown

Independent validation of a93cc7f77434d0fa6fbc37c0d0b3539a68ca0b80:

This is a local vLLM automatic-prefix-cache correctness fix, not an LMCache
fix. It is required when a hybrid cache combines full attention with sparse
Mamba/GDN state and an EAGLE-family target lookup drops the volatile lookahead
unit. Every cache group must retain a state reachable at the same reconciled
token boundary. An external KV connector may supply that state, but it neither
causes nor removes the local retention invariant.

#589 separately classifies DFlash/DSpark as preserving target KV cache, so its
DFlash path must not inherit the EAGLE/MTP target-tail drop addressed here.

@logprobz

logprobz commented Sep 2, 2026

Copy link
Copy Markdown

Validation against a93cc7f, merged with #596/#560/#589 onto 9c4dd05 and run in the r15 image (the merge conflicts only in NVFP4/B12X sparse-MLA files against #589 and one kernel test against #560, artifacts of this branch predating 9c4dd05; the validation tree takes the newer side and none of the retention files is affected): both of your tests pass (the merged tree has one unrelated failure in the same file, test_hybrid_cache_mamba_align_shared_prefix_detection, which reproduces on #589 alone: #589's rename to drop_last_prefix_cache_block outran the test's SimpleNamespace mock), and the hybrid Mamba cases from the closed #583 (tests/v1/core/test_lp14_hybrid_cache.py::test_mamba_retains_eagle_reachable_boundary[None|full_only|both] and test_mamba_eagle_backoff_uses_alignment_unit, plus tests/v1/core/test_lp15_upstream_cache_regressions.py::test_exact_aligned_prompt_retains_post_eagle_resume_state and test_shared_system_boundary_retains_post_eagle_resume_state, all at 8dbf46366e) pass on this branch as well. Three items.

1. Please fold the SWA reachable_block_mask fix in here

The pre-existing SWA defect you flagged is observable through the public KVCacheManager path, identically on the base commit. Hybrid MLAAttentionSpec + MambaSpec(align) + SlidingWindowSpec(sliding_window=2048, extra_retained_tokens=2048), block_size=2304, use_eagle=True, prefix_cache_retention_interval=0: a 122871-token warm prompt followed by a turn sharing its first 122869 tokens reconciles to a 0-token hit (find_longest_cache_hit_per_group gives (119808, 122112, 0); the SWA group holds nothing at the post-drop candidate). With prefix_cache_retention_interval=2304 the same setup hits 119808, and the closed #583 hit 119808 in both cases. Since 0 is the default interval and the shape is exactly the Mamba bug this PR fixes, it belongs here rather than in a follow-up; reachable_hit_positions already yields the positions SlidingWindowManager.reachable_block_mask needs to cover. Under #589 DFlash no longer drops, so this affects eagle/eagle3/mtp hybrids only, which is the case this PR is about.

2. Retention floors to scheduler_block_size, lookup floors to _cache_hit_alignment_tokens

cache_blocks passes alignment_tokens=self.scheduler_block_size (vllm/v1/core/single_type_kv_cache_manager.py:489), while both hybrid lookup paths use HybridKVCacheCoordinator._cache_hit_alignment_tokens (vllm/v1/core/kv_cache_coordinator.py:669-677, used at :872 and :941), which becomes hash_block_size once enable_partial_hash_hits is on. reachable_hit_positions is exact only while the two agree; with fine-grained hits enabled the lookup asks for hash_block_size-aligned positions the mask never retained. Whether the LP15 profile is protected is unverified: production pins --block-size 256 with Mamba align and no prefix-match unit, in which case resolve_kv_cache_block_sizes sets the hash size to the gcd of the group sizes (256), and the recorded results pin only scheduler_block_size: 2304, not hash_block_size. Since this PR's point is computing the exact reachable positions, either pass the coordinator's alignment through to cache_blocks (the closed #583 did this as prefix_cache_alignment_tokens) or assert equality when a sparse-retention manager is present.

3. Pre-existing and out of scope unless you want it: BlockStored events for sparse groups

BlockPool.cache_full_blocks (vllm/v1/core/block_pool.py:301-341) builds one BlockStored spanning num_cached_blocks..num_full_blocks while new_hashes only holds the unmasked blocks. For a sliding-window group that yields events with len(token_ids) != block_size * len(block_hashes) (a 1024-token SWA prefill with block_size=16: 1024 token ids, 4 hashes), and during decode events with empty block_hashes whenever the newly full block is masked out (2 of 32 decode steps in my probe). Under retention it also merges disjoint runs into a single event under one parent field. One event per contiguous run of newly cached indices fixes all three; a patch and two tests exist if you want to take it here, otherwise it becomes a separate issue.


This review was written by Claude Fable 5.1 (Anthropic) and posted by me.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants