@@ -1747,6 +1747,79 @@ def test_unaligned_continuation_redirects_inherited_partial_block(self):
17471747 assert ctx .kv_block_allocator .block_ref_counts [block_id ].item () == 2
17481748
17491749
1750+ @pytest .mark .internal
1751+ @pytest .mark .parametrize ("record_mamba_match" , [False , True ])
1752+ @pytest .mark .parametrize (
1753+ "mamba_boundaries" ,
1754+ [None , [], [2 ], [2 , 6 , 9 ]],
1755+ ids = ["memory_only" , "snapshots_evicted" , "earlier_snapshot" , "later_snapshots" ],
1756+ )
1757+ def test_hybrid_cached_continuation_preserves_recurrent_position (
1758+ mamba_boundaries , record_mamba_match
1759+ ):
1760+ """KV reuse must not skip transitions in a continuation's live Mamba state."""
1761+ ctx = object .__new__ (DynamicInferenceContext )
1762+ ctx .block_size_tokens = 256
1763+ ctx .enable_prefix_caching = True
1764+ ctx .enable_mtp_kv_cache = False
1765+ ctx .is_hybrid_model = True
1766+ hashes = list (range (1 , 11 ))
1767+ ctx .kv_block_allocator = SimpleNamespace (kv_hash_to_block_id = {h : h for h in hashes })
1768+ ctx .mamba_slot_allocator = (
1769+ None
1770+ if mamba_boundaries is None
1771+ else SimpleNamespace (hash_to_block_id = {h : h for h in mamba_boundaries })
1772+ )
1773+ req = SimpleNamespace (finished_chunk_token_count = 0 , precomputed_block_hashes = hashes )
1774+ bs = ctx .block_size_tokens
1775+ recurrent_position = 0
1776+
1777+ for chunk_length in (4 * bs , 4 * bs , 2 * bs + 2 ):
1778+ finished = req .finished_chunk_token_count
1779+ match = ctx ._compute_prefix_match (req , chunk_length , record_mamba_match = record_mamba_match )
1780+ allocated = match .already_allocated_blocks
1781+ required = match .overall_required_blocks
1782+ assert match .matched_block_ids == hashes [allocated :required ]
1783+ assert match .num_blocks_from_pool == required - allocated - len (match .matched_block_ids )
1784+ if finished == 0 :
1785+ # A valid first-chunk snapshot still permits its matching prefix skip.
1786+ assert match .prefix_skip_tokens == (2 * bs if mamba_boundaries else 0 )
1787+ recurrent_position = match .prefix_skip_tokens
1788+ else :
1789+ # No Mamba restore occurs on continuation admission. Sharing KV must
1790+ # not advance the logical position past uncomputed recurrent state.
1791+ assert match .prefix_skip_tokens == 0
1792+ assert match .effective_prefill_chunk_length == chunk_length
1793+ recurrent_position += match .effective_prefill_chunk_length
1794+ req .finished_chunk_token_count += chunk_length
1795+ assert recurrent_position == req .finished_chunk_token_count
1796+
1797+
1798+ @pytest .mark .internal
1799+ def test_attention_only_cached_continuation_still_skips ():
1800+ """Attention-only continuations can still skip matching KV blocks."""
1801+ ctx = object .__new__ (DynamicInferenceContext )
1802+ ctx .block_size_tokens = 256
1803+ ctx .enable_prefix_caching = True
1804+ ctx .enable_mtp_kv_cache = False
1805+ ctx .is_hybrid_model = False
1806+ ctx .mamba_slot_allocator = None
1807+ hashes = list (range (1 , 11 ))
1808+ ctx .kv_block_allocator = SimpleNamespace (kv_hash_to_block_id = {h : h for h in hashes })
1809+ req = SimpleNamespace (
1810+ finished_chunk_token_count = 4 * ctx .block_size_tokens , precomputed_block_hashes = hashes
1811+ )
1812+
1813+ match = ctx ._compute_prefix_match (req , 6 * ctx .block_size_tokens + 2 )
1814+
1815+ assert match .matched_block_ids == hashes [4 :]
1816+ assert match .num_blocks_from_pool == 1
1817+ assert match .already_allocated_blocks == 4
1818+ assert match .overall_required_blocks == 11
1819+ assert match .prefix_skip_tokens == 6 * ctx .block_size_tokens
1820+ assert match .effective_prefill_chunk_length == 2
1821+
1822+
17501823def _make_cpu_mamba_slot_allocator (
17511824 monkeypatch , * , total_blocks : int , max_slots : int
17521825) -> MambaSlotAllocator :
0 commit comments