[mem_cache] Move kv_committed_len into ReqKvInfo (#37078)

This commit is contained in:
Liangsheng Yin
2026-08-29 22:42:25 -07:00
committed by GitHub
parent 5b7c62d5d6
commit 0438b16154
37 changed files with 117 additions and 116 deletions
@@ -96,10 +96,10 @@ class TestSWABasic(ScriptedTestCase):
r = t.start_req(prompt_len=VERY_LONG_PROMPT_LEN, max_new_tokens=2)
for _ in range(400):
if r.is_chunking:
assert len(r.req.prefix_indices) <= r.req.kv_committed_len, (
assert len(r.req.prefix_indices) <= r.req.kv.kv_committed_len, (
f"prefix_indices must be bounded by kv_committed_len, "
f"got prefix_indices_len={len(r.req.prefix_indices)}, "
f"kv_committed_len={r.req.kv_committed_len}"
f"kv_committed_len={r.req.kv.kv_committed_len}"
)
if r.finished:
break
@@ -509,7 +509,7 @@ class TestRegressionGptOss(ScriptedTestCase):
r = t.start_req(prompt_len=VERY_LONG_PROMPT_LEN, max_new_tokens=2)
yield from run_until(r, lambda h: h.is_chunking and h.chunks_done >= 1)
committed = r.req.kv_committed_len
committed = r.req.kv.kv_committed_len
assert committed > 0
assert len(r.req.prefix_indices) <= committed, (
@@ -225,10 +225,10 @@ class TestSpecialCaseBasic(ScriptedTestCase):
)
for _ in range(DEFAULT_MAX_STEPS):
if r.is_chunking:
assert len(r.req.prefix_indices) <= r.req.kv_committed_len, (
assert len(r.req.prefix_indices) <= r.req.kv.kv_committed_len, (
f"streaming-session chunked stash must stay bounded by "
f"kv_committed_len; prefix_indices_len={len(r.req.prefix_indices)}, "
f"kv_committed_len={r.req.kv_committed_len}"
f"kv_committed_len={r.req.kv.kv_committed_len}"
)
if r.finished:
break
@@ -119,8 +119,9 @@ class _FakeAllocator:
class TestFreeMemberRows(CustomTestCase):
def _make_group(self, req_to_token, allocated_len):
leader = SimpleNamespace(
kv=SimpleNamespace(kv_allocated_len=allocated_len),
kv_committed_len=allocated_len,
kv=SimpleNamespace(
kv_allocated_len=allocated_len, kv_committed_len=allocated_len
),
)
return SimpleNamespace(
leader=leader,
@@ -146,7 +147,7 @@ class TestFreeMemberRows(CustomTestCase):
# Leader rewound to the prompt: its own release must not free the
# decode region a second time.
self.assertEqual(leader.kv.kv_allocated_len, 5)
self.assertEqual(leader.kv_committed_len, 5)
self.assertEqual(leader.kv.kv_committed_len, 5)
self.assertEqual(sorted(pool.freed), [1, 2])
self.assertIsNone(group.member_rows)
self.assertIsNone(group.member_rows_cpu)
@@ -207,7 +208,7 @@ class TestRetireReclaimsStagedOrphans(CustomTestCase):
allocator = _FakeAllocator()
group = SimpleNamespace(
leader=SimpleNamespace(
kv=SimpleNamespace(kv_allocated_len=8), kv_committed_len=8
kv=SimpleNamespace(kv_allocated_len=8, kv_committed_len=8),
),
prompt_len=5,
member_rows=torch.tensor([1, 2], dtype=torch.int64),
@@ -34,10 +34,11 @@ def _make_mock_req(
req = MagicMock()
req.rid = rid
req.req_pool_idx = req_pool_idx
req.kv_committed_len = kv_committed_len
req.kv = ReqKvInfo(kv_allocated_len=kv_allocated_len)
req.kv = ReqKvInfo(
kv_committed_len=kv_committed_len, kv_allocated_len=kv_allocated_len
)
req.prefix_indices = list(range(prefix_indices_len))
req.effective_kv_committed_len = lambda: req.kv_committed_len
req.effective_kv_committed_len = lambda: req.kv.kv_committed_len
return req
@@ -1521,7 +1521,6 @@ if _HAS_MLX:
self.req_pool_idx = None
self.mamba_pool_idx = None
self.inflight_middle_chunks = 0
self.kv_committed_len = 0
class FakeTpWorker:
def __init__(self, next_token_ids):
@@ -121,7 +121,6 @@ def _fake_req():
return SimpleNamespace(
req_pool_idx=None,
inflight_middle_chunks=0,
kv_committed_len=0,
mamba_pool_idx=None,
mamba_ping_pong_track_buffer=None,
)
@@ -68,7 +68,6 @@ def make_pool_and_req(capacity: int = 64):
req = SimpleNamespace(
req_pool_idx=None,
inflight_middle_chunks=0,
kv_committed_len=0,
)
req_pool_idx = pool.alloc([req])[0]
return pool, req, req_pool_idx, allocator
@@ -33,7 +33,7 @@ def _make_batch() -> tuple[Req, ScheduleBatch]:
vocab_size=128,
)
req.output_ids.append(3)
req.kv_committed_len = 2
req.kv.kv_committed_len = 2
batch = ScheduleBatch(reqs=[req])
batch.tree_cache = SimpleNamespace(page_size=TRACK_INTERVAL)
@@ -92,7 +92,7 @@ def _make_req(terminate_after: int) -> Req:
sampling_params=sp,
)
req.grammar = _FakeGrammar(terminate_after=terminate_after)
req.kv_committed_len = 0
req.kv.kv_committed_len = 0
return req
@@ -121,7 +121,7 @@ class TestSpecV2GrammarTruncation(CustomTestCase):
self.assertEqual(predict_tokens, [[101, 102]])
# No pre-claim: commit the full retained run (no -1 refund).
self.assertEqual(req.kv_committed_len, 2)
self.assertEqual(req.kv.kv_committed_len, 2)
def test_resolve_keeps_all_when_grammar_not_terminated(self):
req = _make_req(terminate_after=99)
@@ -131,7 +131,7 @@ class TestSpecV2GrammarTruncation(CustomTestCase):
predict_tokens = proc._resolve_spec_v2_tokens(result, _FakeBatch([req]))
self.assertEqual(predict_tokens, [[201, 202, 203]])
self.assertEqual(req.kv_committed_len, 3)
self.assertEqual(req.kv.kv_committed_len, 3)
class TestReasoningTokenAccounting(CustomTestCase):
@@ -50,8 +50,7 @@ def _make_req(rid="test-req-0", origin_input_ids=None, output_ids=None):
fill_ids=origin_input_ids + output_ids,
seqlen=len(origin_input_ids) + len(output_ids),
req_pool_idx=None,
kv=SimpleNamespace(kv_allocated_len=0),
kv_committed_len=0,
kv=SimpleNamespace(kv_allocated_len=0, kv_committed_len=0),
finished_reason=None,
hisparse_staging=False,
staging=False,
@@ -219,7 +218,7 @@ class TestHiSparseUnit(unittest.TestCase):
self.assertIsNotNone(kv_loc, "KV alloc failed")
self.req_to_token_pool.write((req.req_pool_idx, slice(0, len(kv_loc))), kv_loc)
req.kv.kv_allocated_len = fill_len
req.kv_committed_len = fill_len
req.kv.kv_committed_len = fill_len
req.full_untruncated_fill_ids = array("q", range(fill_len))
req.extend_range = Range(0, fill_len)
return kv_loc
@@ -579,7 +578,7 @@ class TestHiSparseUnit(unittest.TestCase):
seq_len = fill_len + 1
self.req_to_token_pool.write((req.req_pool_idx, fill_len), out_loc)
req.kv.kv_allocated_len = seq_len
req.kv_committed_len = seq_len
req.kv.kv_committed_len = seq_len
self.coordinator.map_last_loc_to_buffer(
seq_lens=torch.tensor([seq_len], dtype=torch.int64, device=device),
@@ -770,7 +769,7 @@ class TestHiSparseUnit(unittest.TestCase):
)
)
self.assertEqual(req.kv.kv_allocated_len, fill_len)
self.assertEqual(req.kv_committed_len, fill_len)
self.assertEqual(req.kv.kv_committed_len, fill_len)
self.assertEqual(req.extend_range.length, fill_len)
rounded_len = (fill_len + self.page_size - 1) // self.page_size * self.page_size
@@ -47,16 +47,18 @@ class _FakeReq:
def __init__(self, rid, rpi, committed, allocated):
self.rid = rid
self.req_pool_idx = rpi
self.kv_committed_len = committed
self.kv = SimpleNamespace(kv_allocated_len=allocated, swa_evicted_seqlen=0)
self.kv = SimpleNamespace(
kv_committed_len=committed, kv_allocated_len=allocated, swa_evicted_seqlen=0
)
self.is_holding_kv = True
class _FakeSlot:
def __init__(self, rpi, committed, allocated):
self.req_pool_idx = rpi
self.kv_committed_len = committed
self.kv = SimpleNamespace(kv_allocated_len=allocated, swa_evicted_seqlen=0)
self.kv = SimpleNamespace(
kv_committed_len=committed, kv_allocated_len=allocated, swa_evicted_seqlen=0
)
self.is_holding_kv = True
@@ -18,8 +18,7 @@ register_cpu_ci(est_time=5, suite="base-a-test-cpu")
def _make_req():
return types.SimpleNamespace(
decode_batch_idx=0,
kv_committed_len=3,
kv_allocated_len=3,
kv=types.SimpleNamespace(kv_committed_len=3, kv_allocated_len=3),
beam_group=None,
)
@@ -81,9 +81,10 @@ class MockReq:
self.cache_salt = None
self.prefix_indices = torch.empty(0, dtype=torch.int64)
self.priority = 0
self.kv_committed_len = len(fill_ids)
self.kv = SimpleNamespace(
kv_allocated_len=len(fill_ids), cache_protected_len=cache_protected_len
kv_committed_len=len(fill_ids),
kv_allocated_len=len(fill_ids),
cache_protected_len=cache_protected_len,
)
def get_fill_ids(self):
@@ -211,7 +212,7 @@ class TestDecodeLockRefScenarios(unittest.TestCase):
cache.cache_unfinished_req(req)
# Step 3: cache_finished_req with is_insert=True (dec lock)
cache.cache_finished_req(req, kv_len_to_handle=req.kv_committed_len)
cache.cache_finished_req(req, kv_len_to_handle=req.kv.kv_committed_len)
# Verify: all non-root nodes should have lock_ref == 0
# (root always has lock_ref == 1)
@@ -260,7 +261,7 @@ class TestDecodeLockRefScenarios(unittest.TestCase):
cache.cache_unfinished_req(req)
# Step 3: cache_finished_req (dec leaf)
cache.cache_finished_req(req, kv_len_to_handle=req.kv_committed_len)
cache.cache_finished_req(req, kv_len_to_handle=req.kv.kv_committed_len)
# Root lock unchanged, all nodes unlocked
self.assertEqual(cache.root_node.lock_ref, root_lock_before)
@@ -304,7 +305,7 @@ class TestDecodeLockRefScenarios(unittest.TestCase):
# Transfer fails -> cache_finished_req with is_insert=False
# This frees delta tokens and dec_lock_ref on last_node
cache.cache_finished_req(
req, is_insert=False, kv_len_to_handle=req.kv_committed_len
req, is_insert=False, kv_len_to_handle=req.kv.kv_committed_len
)
# The prefix node should be unlocked (back to evictable)
@@ -351,7 +352,7 @@ class TestDecodeLockRefScenarios(unittest.TestCase):
# Transfer fails -> cache_finished_req with is_insert=False
# dec_lock_ref(root) is a no-op
cache.cache_finished_req(
req, is_insert=False, kv_len_to_handle=req.kv_committed_len
req, is_insert=False, kv_len_to_handle=req.kv.kv_committed_len
)
# Root lock unchanged, nothing protected or evictable
@@ -482,7 +483,7 @@ class TestDecodeLockRefScenarios(unittest.TestCase):
)
cache.cache_unfinished_req(req)
cache.cache_finished_req(req, kv_len_to_handle=req.kv_committed_len)
cache.cache_finished_req(req, kv_len_to_handle=req.kv.kv_committed_len)
# After all iterations, root lock should be 1, no protected nodes
self.assertEqual(cache.root_node.lock_ref, 1)
@@ -64,9 +64,11 @@ def _make_req(rid, prefix, block_size, *, req_pool_idx=None, reuse=False):
req_pool_idx=req_pool_idx,
dllm_incomplete_ids=array("q", range(block_size)) if reuse else array("q"),
inflight_middle_chunks=1 if req_pool_idx is not None else 0,
kv_committed_len=len(prefix) if req_pool_idx is not None else 0,
kv=SimpleNamespace(
kv_allocated_len=len(prefix) + block_size if req_pool_idx is not None else 0
kv_committed_len=len(prefix) if req_pool_idx is not None else 0,
kv_allocated_len=(
len(prefix) + block_size if req_pool_idx is not None else 0
),
),
)
@@ -153,7 +153,7 @@ class TestDeepSeekV4HiSparseAllocator(CustomTestCase):
self.assertEqual(kwargs["swa_tail_len"], swa_tail_len)
self.assertEqual(req.kv.swa_evicted_seqlen, fill_len - swa_tail_len)
self.assertEqual(req.kv.kv_allocated_len, fill_len)
self.assertEqual(req.kv_committed_len, fill_len)
self.assertEqual(req.kv.kv_committed_len, fill_len)
self.assertEqual(req.extend_range.length, fill_len)
self.assertEqual(len(req_to_token_pool.writes), 1)
coordinator.host_token_len.assert_called_once_with(fill_len)
@@ -166,7 +166,6 @@ def register(cache, token_ids, session_id, generation=None):
).last_device_node,
origin_input_ids=array("q", token_ids),
output_ids=array("q"),
kv_committed_len=len(token_ids),
extra_key=None,
)
)
@@ -68,8 +68,8 @@ class _FakeReq:
_inflight=False,
)
self.req_pool_idx = req_pool_idx
self.kv_committed_len = committed
self.kv = SimpleNamespace(
kv_committed_len=committed,
kv_allocated_len=allocated,
swa_evicted_seqlen=0,
cache_protected_len=0,
@@ -113,9 +113,11 @@ def test_preabort_detaches_session_and_preserves_slot():
tree_cache = StreamingSession(inner)
tree_cache.slots["session-a"] = SessionSlot(
req_pool_idx=0,
kv_committed_len=48,
kv=SimpleNamespace(
kv_allocated_len=48, swa_evicted_seqlen=0, cache_protected_len=16
kv_committed_len=48,
kv_allocated_len=48,
swa_evicted_seqlen=0,
cache_protected_len=16,
),
)
@@ -134,7 +136,7 @@ def test_preabort_detaches_session_and_preserves_slot():
# Slot untouched.
slot = tree_cache.slots["session-a"]
assert slot.req_pool_idx == 0
assert slot.kv_committed_len == 48
assert slot.kv.kv_committed_len == 48
assert slot.kv.kv_allocated_len == 48
assert len(result.device_indices) == 0
@@ -178,9 +180,11 @@ def test_nth_mid_abort_nukes_session_slot():
# Session already has a slot from a previous turn.
tree_cache.slots["session-a"] = SessionSlot(
req_pool_idx=0,
kv_committed_len=50,
kv=SimpleNamespace(
kv_allocated_len=50, swa_evicted_seqlen=0, cache_protected_len=0
kv_committed_len=50,
kv_allocated_len=50,
swa_evicted_seqlen=0,
cache_protected_len=0,
),
last_node=None,
)
@@ -217,9 +221,11 @@ def test_release_session_threads_mamba_skip_ids():
lock_node = SimpleNamespace(id=42)
tree_cache.slots["session-a"] = SessionSlot(
req_pool_idx=0,
kv_committed_len=50,
kv=SimpleNamespace(
kv_allocated_len=50, swa_evicted_seqlen=0, cache_protected_len=0
kv_committed_len=50,
kv_allocated_len=50,
swa_evicted_seqlen=0,
cache_protected_len=0,
),
last_node=lock_node,
skip_lock_node_ids={ComponentType.MAMBA: {42}},
@@ -265,7 +271,7 @@ def test_trim_overshoot_postcondition():
tree_cache._trim_overshoot(req, finished_len=12)
target = 38
assert req.kv_committed_len == target
assert req.kv.kv_committed_len == target
assert req.kv.kv_allocated_len == target
assert req.kv.swa_evicted_seqlen == target
assert len(req.output_ids) == 12
@@ -643,7 +643,7 @@ def bench_cache_finished(
)
req.last_node = node
req.kv.cache_protected_len = matched_len
req.kv_committed_len = len(seq)
req.kv.kv_committed_len = len(seq)
if hasattr(lr, "swa_uuid_for_lock"):
req.swa_uuid_for_lock = lr.swa_uuid_for_lock
env.rtp.req_to_token[req.req_pool_idx, : len(kv_indices)] = kv_indices
@@ -657,7 +657,7 @@ def bench_cache_finished(
"cache_finished",
lambda: req_items,
lambda req: env.tree.cache_finished_req(
req, is_insert=True, kv_len_to_handle=req.kv_committed_len
req, is_insert=True, kv_len_to_handle=req.kv.kv_committed_len
),
len(req_items) - warmup,
env.avg_tokens,
@@ -1226,7 +1226,7 @@ class UnifiedRadixCacheSuite:
kv_len = len(input_ids) + len(output_ids)
kv_indices = self._alloc(allocator, kv_len)
req_to_token_pool.write((req.req_pool_idx, slice(0, kv_len)), kv_indices)
req.kv_committed_len = kv_len
req.kv.kv_committed_len = kv_len
req.last_node = cache.root_node_handle()
req.kv.cache_protected_len = 0
req.swa_uuid_for_lock = None
@@ -1266,7 +1266,7 @@ class UnifiedRadixCacheSuite:
kv_len = req.extend_range.end
kv_indices = self._alloc(allocator, kv_len)
req_to_token_pool.write((req.req_pool_idx, slice(0, kv_len)), kv_indices)
req.kv_committed_len = kv_len
req.kv.kv_committed_len = kv_len
req.kv.kv_allocated_len = kv_len
req.last_node = cache.root_node_handle()
req.kv.cache_protected_len = 0
@@ -1311,7 +1311,7 @@ class UnifiedRadixCacheSuite:
kv_len = len(tokens)
kv_indices = self._alloc(allocator, kv_len)
req_to_token_pool.write((req.req_pool_idx, slice(0, kv_len)), kv_indices)
req.kv_committed_len = kv_len
req.kv.kv_committed_len = kv_len
req.last_node = cache.root_node_handle()
req.kv.cache_protected_len = 0
req.swa_uuid_for_lock = None
@@ -1346,7 +1346,7 @@ class UnifiedRadixCacheSuite:
kv_len = len(tokens)
kv_indices = self._alloc(allocator, kv_len)
req_to_token_pool.write((req.req_pool_idx, slice(0, kv_len)), kv_indices)
req.kv_committed_len = kv_len
req.kv.kv_committed_len = kv_len
req.last_node = cache.root_node_handle()
req.kv.cache_protected_len = 0
req.swa_uuid_for_lock = None
@@ -1383,7 +1383,7 @@ class UnifiedRadixCacheSuite:
req.set_extend_range(0, len(req.full_untruncated_fill_ids))
kv_indices = self._alloc(allocator, len(tokens))
req_to_token_pool.write((req.req_pool_idx, slice(0, len(tokens))), kv_indices)
req.kv_committed_len = len(tokens)
req.kv.kv_committed_len = len(tokens)
req.last_node = cache.root_node_handle()
req.kv.cache_protected_len = 0
req.swa_uuid_for_lock = None
@@ -1485,7 +1485,7 @@ class UnifiedRadixCacheSuite:
kv_len = len(input_ids)
kv_indices = self._alloc(allocator, kv_len)
req_to_token_pool.write((req.req_pool_idx, slice(0, kv_len)), kv_indices)
req.kv_committed_len = kv_len
req.kv.kv_committed_len = kv_len
req.last_node = cache.root_node_handle()
req.kv.cache_protected_len = 0
req.swa_uuid_for_lock = None
@@ -1610,7 +1610,7 @@ class UnifiedRadixCacheSuite:
kv_len = len(tokens)
fresh_value = self._alloc(allocator, kv_len)
req_to_token_pool.write((req.req_pool_idx, slice(0, kv_len)), fresh_value)
req.kv_committed_len = kv_len
req.kv.kv_committed_len = kv_len
req.last_node = cache.root_node_handle()
req.kv.cache_protected_len = 0
req.swa_uuid_for_lock = None
@@ -2197,7 +2197,7 @@ class UnifiedRadixCacheSuite:
req.set_extend_range(0, len(req.full_untruncated_fill_ids))
kv_indices = self._alloc(allocator, pre_len)
req_to_token_pool.write((req.req_pool_idx, slice(0, pre_len)), kv_indices)
req.kv_committed_len = pre_len
req.kv.kv_committed_len = pre_len
req.last_node = cache.root_node_handle()
req.kv.cache_protected_len = 0
req.swa_uuid_for_lock = None
@@ -2286,7 +2286,7 @@ class UnifiedRadixCacheSuite:
req.set_extend_range(0, len(req.full_untruncated_fill_ids))
kv_indices = self._alloc(allocator, pre_len)
req_to_token_pool.write((req.req_pool_idx, slice(0, pre_len)), kv_indices)
req.kv_committed_len = pre_len
req.kv.kv_committed_len = pre_len
req.last_node = cache.root_node_handle()
req.kv.cache_protected_len = 0
req.swa_uuid_for_lock = None
@@ -6768,7 +6768,7 @@ class TestUnifiedRadixCacheInt8MambaCheckpoint(CustomTestCase):
)
req_to_token_pool.alloc([req])
req.output_ids = array("q")
req.kv_committed_len = len(tokens)
req.kv.kv_committed_len = len(tokens)
req.kv.kv_allocated_len = len(tokens)
req.kv.cache_protected_len = 0
req.swa_uuid_for_lock = None
@@ -8095,7 +8095,7 @@ class TestSWAWindowUnderBigramKey(CustomTestCase):
req.set_extend_range(0, len(req.full_untruncated_fill_ids))
kv_indices = self._alloc_paged(allocator, seq_len)
req_to_token_pool.write((req.req_pool_idx, slice(0, seq_len)), kv_indices)
req.kv_committed_len = seq_len
req.kv.kv_committed_len = seq_len
req.last_node = cache.root_node_handle()
req.kv.cache_protected_len = 0
req.swa_uuid_for_lock = None
@@ -104,9 +104,7 @@ _OWNER_SITES = {
"free_member_rows",
"kv_allocated_len",
): 1,
# streaming session slot save/restore and tail trimming
(_SS, "SessionSlot.save_from_req", "kv_committed_len"): 1,
(_SS, "SessionSlot.restore_to_req", "kv_committed_len"): 1,
# streaming session tail trimming
(_SS, "StreamingSession._free_tail", "kv_committed_len"): 2,
(_SS, "StreamingSession._free_tail", "kv_allocated_len"): 2,
(_SS, "StreamingSession._trim_overshoot", "kv_committed_len"): 1,
@@ -72,10 +72,9 @@ def _make_req(rid, req_pool_idx, token_ids, tree):
extra_key=None,
cache_salt=None,
last_node=tree.root_node,
kv=SimpleNamespace(cache_protected_len=0),
kv=SimpleNamespace(cache_protected_len=0, kv_committed_len=len(token_ids)),
priority=0,
kv_committed_freed=False,
kv_committed_len=len(token_ids),
)
req.pop_committed_kv_cache = lambda: len(token_ids)
return req
@@ -163,11 +162,7 @@ class TestLMCRadixCacheXPU(unittest.TestCase):
# commit it as a finished request (inserts into radix + stores to
# LMCache on tree.store_stream).
req_pool_idx = req_to_token_pool.alloc(
[
SimpleNamespace(
req_pool_idx=None, inflight_middle_chunks=0, kv_committed_len=0
)
]
[SimpleNamespace(req_pool_idx=None, inflight_middle_chunks=0)]
)[0]
kv_slots = allocator.alloc(self.INPUT_LEN)
self.assertIsNotNone(kv_slots)