[Kimi Bug] Fix kimi k3 AssertionError assert 0 <= checkpoint_idx < len(blocks) (#55747)

Signed-off-by: yewentao256 <zhyanwentao@126.com>

Wentao Ye committed Sep 7, 2026 at 22:34 UTC 869f78732b64454293d2ba42ae0008386fdaa6a6
2 files changed +17 -12
tests/v1/core/prefix_cache/test_partial_prefix_cache_hits.py
+1 -1
@@ -651,7 +651,7 @@ def test_hash_aligned_query_end_uses_regular_partial_tail():
651
652 assert new_blocks is not None
653 mamba_manager = manager.coordinator.single_type_managers[1]
654 - assert mamba_manager._checkpoint_positions[request.request_id] == 0
654 + assert request.request_id not in mamba_manager._checkpoints
655 mamba_blocks = manager.get_blocks(request.request_id).blocks[1]
656 assert sum(not block.is_null for block in mamba_blocks) == 1
657
vllm/v1/core/single_type_kv_cache_manager.py
+16 -11
@@ -1431,9 +1431,9 @@ class MambaManager(SingleTypeKVCacheManager):
1431 self.last_state_block_idx: dict[str, int] = {}
1432 # The set of the requests that have been allocated blocks
1433 self._allocated_block_reqs: set[str] = set()
1434 - # Absolute checkpoint position selected by the scheduler for the
1435 - # current allocation.
1436 - self._checkpoint_positions: dict[str, int] = {}
1434 + # checkpoint position and reserved block index for the current
1435 + # allocation.
1436 + self._checkpoints: dict[str, tuple[int, int]] = {}
1437 # Requests that registered their own last-prompt-boundary partial
1438 # tail (producers). A later CoW hands its private copy to the
1439 # connector; a request that finishes first hands off this table
@@ -1722,7 +1722,14 @@ class MambaManager(SingleTypeKVCacheManager):
1722 checkpoint_position = 0
1723 checkpoint_block = int(checkpoint_position > 0)
1724 if not apply_admission_cap:
1725 - self._checkpoint_positions[request_id] = checkpoint_position
1725 + if checkpoint_position > 0:
1726 + checkpoint_idx = cdiv(num_tokens, self.block_size) - 2
1727 + self._checkpoints[request_id] = (
1728 + checkpoint_position,
1729 + checkpoint_idx,
1730 + )
1731 + else:
1732 + self._checkpoints.pop(request_id, None)
1733 if num_new_blocks > 0:
1734 blocks_allocated = request_id in self._allocated_block_reqs
1735 if not (checkpoint_block and blocks_allocated):
@@ -1760,8 +1767,7 @@ class MambaManager(SingleTypeKVCacheManager):
1767 num_required_blocks = (
1768 cdiv(num_tokens, self.block_size) + self.num_speculative_blocks
1769 )
1763 - checkpoint_position = self._checkpoint_positions.get(request_id, 0)
1764 - checkpoint_block = int(checkpoint_position > 0)
1770 + checkpoint_block = int(request_id in self._checkpoints)
1771 partial_hit = self._partial_hit_reqs.get(request_id)
1772 has_partial_hit = partial_hit is not None
1773 # `num_required_blocks` might be less than `len(req_blocks)` if blocks are
@@ -1893,7 +1899,7 @@ class MambaManager(SingleTypeKVCacheManager):
1899 if self.mamba_cache_mode == "align":
1900 self._allocated_block_reqs.discard(request_id)
1901 self.last_state_block_idx.pop(request_id, None)
1896 - self._checkpoint_positions.pop(request_id, None)
1902 + self._checkpoints.pop(request_id, None)
1903 self._producer_partial_tail_reqs.pop(request_id, None)
1904 # An offer is only guaranteed to hold committed bytes until the end
1905 # of the pass that made it. This request's blocks are going back to
@@ -1963,10 +1969,9 @@ class MambaManager(SingleTypeKVCacheManager):
1969 ) -> BlockHashWithGroupId | None:
1970 hash_block_size = self.block_pool.hash_block_size
1971 # Re-key the reserved block at its exported checkpoint boundary.
1966 - checkpoint_position = self._checkpoint_positions.get(request.request_id, 0)
1967 - if checkpoint_position > 0:
1968 - # TODO: Store the reserved slot explicitly for multi-module MTP.
1969 - checkpoint_idx = cdiv(num_tokens, self.block_size) - 2
1972 + checkpoint = self._checkpoints.get(request.request_id)
1973 + if checkpoint is not None:
1974 + checkpoint_position, checkpoint_idx = checkpoint
1975 blocks = self.req_to_blocks[request.request_id]
1976 assert 0 <= checkpoint_idx < len(blocks)
1977 checkpoint_block = blocks[checkpoint_idx]