[Mamba] Fix checkpoint depth for prefixes that end off the radix page (#39115)

Co-authored-by: Ke Bao <ispobaoke@gmail.com>
This commit is contained in:
Kurt Shuster
2026-09-17 14:30:57 +08:00
committed by GitHub
co-authored by Ke Bao
parent fa8d22e665
commit 6460082c05
2 changed files with 43 additions and 13 deletions
+13 -9
View File
@@ -2965,7 +2965,18 @@ class ScheduleBatch(ScheduleBatchDisaggregationDecodeMixin):
# to force the math calculation to retrieve the correct mamba state from h.
return i + 1
mask = req.extend_range.length >= checkpoint_grid
# Pick the depth on the absolute checkpoint grid: a chunk boundary can leave
# the prefix off the (DCP-widened) tree page, and a prefix-relative depth then
# names a position no page can hold. Donate only where an h snapshot exists.
prefix_len = len(req.prefix_indices)
seq_end = prefix_len + req.extend_range.length
# mamba_track_seqlen_aligned/mamba_last_track_seqlen is actual tracked seqlen. Used to pass to
# mamba radix cache to track which seqlen this mamba state should store at.
mamba_track_seqlen_aligned = (seq_end // checkpoint_grid) * checkpoint_grid
mask = (
mamba_track_seqlen_aligned > prefix_len
and (mamba_track_seqlen_aligned - prefix_len) % cache_chunk_size == 0
)
track_index = req.kv.mamba_ping_pong_track_buffer[
req.kv.mamba_next_track_idx
].item()
@@ -2978,14 +2989,7 @@ class ScheduleBatch(ScheduleBatchDisaggregationDecodeMixin):
# otherwise retrieved from h (i.e. unaligned).
# We need to pass the non-aligned seqlen to the calculation. Even though
# we pass in mamba_track_seqlen, the actual tracked seqlen is mamba_last_track_seqlen.
mamba_track_seqlen = len(req.prefix_indices) + req.extend_range.length
# mamba_track_seqlen_aligned/mamba_last_track_seqlen is actual tracked seqlen. Used to pass to
# mamba radix cache to track which seqlen this mamba state should store at.
mamba_track_seqlen_aligned = (
len(req.prefix_indices)
+ (req.extend_range.length // checkpoint_grid) * checkpoint_grid
)
mamba_track_seqlen = seq_end
# A coarser checkpoint grid may not be a model-state boundary, so
# force retrieval from the intermediate h state in that case.
@@ -3,7 +3,10 @@
DCP widens the tree page past the mamba chunk grid. A checkpoint picked on the
finer grid names a depth no radix node can carry, so it gets attached to the
preceding node and a later request resumes from a state that already covers
tokens past that node.
tokens past that node. The same happens when the depth is measured from a
prefix that a chunked prefill left on the scheduler page but off the widened
tree page: 64 + 512 = 576 is no 512-page boundary, and the finished request
then frees two kv-row segments that share the page holding 576.
"""
import unittest
@@ -27,8 +30,9 @@ register_cpu_ci(est_time=10, suite="base-a-test-cpu")
CHUNK = 64
def _track_seqlen(*, tree_page: int, prefix_len: int, extend_len: int) -> int:
"""Run one extend through the tracker and report the donated depth."""
def _track_seqlen(*, tree_page: int, prefix_len: int, extend_len: int) -> int | None:
"""Run one extend through the tracker and report the donated depth, or
None when the extend donates no checkpoint."""
server_args = ServerArgs(model_path="dummy", page_size=CHUNK)
# The property would otherwise load the HF config for the dummy model.
server_args._mamba_cache_chunk_size = CHUNK
@@ -57,7 +61,10 @@ def _track_seqlen(*, tree_page: int, prefix_len: int, extend_len: int) -> int:
batch.req_to_token_pool = MagicMock()
batch.req_to_token_pool.get_mamba_ping_pong_other_idx.return_value = 1
batch._mamba_radix_cache_v2_req_prepare_for_extend(req)
entry = batch._mamba_radix_cache_v2_req_prepare_for_extend(req)
if not entry.track_mask:
assert req.kv.mamba_last_track_seqlen is None
return None
return req.kv.mamba_last_track_seqlen
@@ -73,6 +80,25 @@ class TestMambaCheckpointDepth(unittest.TestCase):
depth = _track_seqlen(tree_page=CHUNK, prefix_len=16384, extend_len=4066)
self.assertEqual(depth, 20416)
def test_depth_is_picked_on_the_absolute_grid(self):
# (tree_page, prefix_len, extend_len) -> donated depth, or None when the
# extend crosses no page or the prefix is off the kernel chunk grid.
for tree_page, prefix_len, extend_len, expected in (
(512, 64, 540, 512),
(512, 448, 128, 512),
(512, 64, 400, None),
(512, 37, 540, None),
):
with self.subTest(prefix_len=prefix_len, extend_len=extend_len):
self.assertEqual(
_track_seqlen(
tree_page=tree_page,
prefix_len=prefix_len,
extend_len=extend_len,
),
expected,
)
class TestMambaTrackGrid(unittest.TestCase):
def _grid(self, *, interval: int, tree_page: int, chunk: int = CHUNK) -> int: