Introduce req.kv container for coupled owned kv field lifecycle (#29427)
This commit is contained in:
@@ -8,6 +8,7 @@ Requires: torch, sglang (run in an environment with sglang installed)
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import torch
|
||||
@@ -34,7 +35,7 @@ def _make_mock_req(
|
||||
req.rid = rid
|
||||
req.req_pool_idx = req_pool_idx
|
||||
req.kv_committed_len = kv_committed_len
|
||||
req.kv_allocated_len = kv_allocated_len
|
||||
req.kv = SimpleNamespace(kv_allocated_len=kv_allocated_len)
|
||||
req.kv_committed_freed = False
|
||||
req.kv_overallocated_freed = False
|
||||
req.prefix_indices = list(range(prefix_indices_len))
|
||||
@@ -47,7 +48,7 @@ def _make_mock_req(
|
||||
def pop_overallocated():
|
||||
assert not req.kv_overallocated_freed
|
||||
req.kv_overallocated_freed = True
|
||||
return req.kv_committed_len, req.kv_allocated_len
|
||||
return req.kv_committed_len, req.kv.kv_allocated_len
|
||||
|
||||
req.pop_committed_kv_cache = pop_committed
|
||||
req.pop_overallocated_kv_cache = pop_overallocated
|
||||
|
||||
@@ -50,7 +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_allocated_len=0,
|
||||
kv=SimpleNamespace(kv_allocated_len=0),
|
||||
kv_committed_len=0,
|
||||
finished_reason=None,
|
||||
hisparse_staging=False,
|
||||
@@ -218,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_allocated_len = fill_len
|
||||
req.kv.kv_allocated_len = fill_len
|
||||
req.kv_committed_len = fill_len
|
||||
req.full_untruncated_fill_ids = array("q", range(fill_len))
|
||||
req.extend_range = Range(0, fill_len)
|
||||
@@ -578,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_allocated_len = seq_len
|
||||
req.kv.kv_allocated_len = seq_len
|
||||
req.kv_committed_len = seq_len
|
||||
|
||||
self.coordinator.map_last_loc_to_buffer(
|
||||
@@ -769,7 +769,7 @@ class TestHiSparseUnit(unittest.TestCase):
|
||||
self.coordinator.req_to_host_pool[req.req_pool_idx, :fill_len],
|
||||
)
|
||||
)
|
||||
self.assertEqual(req.kv_allocated_len, fill_len)
|
||||
self.assertEqual(req.kv.kv_allocated_len, fill_len)
|
||||
self.assertEqual(req.kv_committed_len, fill_len)
|
||||
self.assertEqual(req.extend_range.length, fill_len)
|
||||
|
||||
@@ -786,7 +786,7 @@ class TestHiSparseUnit(unittest.TestCase):
|
||||
self.assertEqual(allocated_host_indices.numel(), rounded_len)
|
||||
|
||||
kv_loc = self.req_to_token_pool.req_to_token[
|
||||
req.req_pool_idx, : req.kv_allocated_len
|
||||
req.req_pool_idx, : req.kv.kv_allocated_len
|
||||
].clone()
|
||||
self._cleanup_req(req, kv_loc, logical_only=True)
|
||||
self._assert_sizes_restored(initial, "pd_decode_prealloc_hisparse")
|
||||
|
||||
@@ -48,14 +48,14 @@ class _FakeReq:
|
||||
self.rid = rid
|
||||
self.req_pool_idx = rpi
|
||||
self.kv_committed_len = committed
|
||||
self.kv_allocated_len = allocated
|
||||
self.kv = SimpleNamespace(kv_allocated_len=allocated, swa_evicted_seqlen=0)
|
||||
|
||||
|
||||
class _FakeSlot:
|
||||
def __init__(self, rpi, committed, allocated):
|
||||
self.req_pool_idx = rpi
|
||||
self.kv_committed_len = committed
|
||||
self.kv_allocated_len = allocated
|
||||
self.kv = SimpleNamespace(kv_allocated_len=allocated, swa_evicted_seqlen=0)
|
||||
self.is_holding_kv = True
|
||||
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ register_amd_ci(est_time=10, suite="stage-b-test-1-gpu-small-amd")
|
||||
|
||||
import unittest
|
||||
from array import array
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import torch
|
||||
@@ -81,7 +82,7 @@ class MockReq:
|
||||
self.prefix_indices = torch.empty(0, dtype=torch.int64)
|
||||
self.priority = 0
|
||||
self.kv_committed_len = len(fill_ids)
|
||||
self.kv_allocated_len = len(fill_ids)
|
||||
self.kv = SimpleNamespace(kv_allocated_len=len(fill_ids))
|
||||
self.kv_committed_freed = False
|
||||
|
||||
def get_fill_ids(self):
|
||||
@@ -92,7 +93,7 @@ class MockReq:
|
||||
return self.kv_committed_len
|
||||
|
||||
def pop_overallocated_kv_cache(self):
|
||||
return (self.kv_committed_len, self.kv_allocated_len)
|
||||
return (self.kv_committed_len, self.kv.kv_allocated_len)
|
||||
|
||||
|
||||
def _make_req(fill_ids, req_pool_idx=0, cache_protected_len=0, last_node=None):
|
||||
|
||||
@@ -23,7 +23,7 @@ class _FakeAllocator:
|
||||
class _FakeReq:
|
||||
req_pool_idx = 0
|
||||
swa_evict_floor = 3
|
||||
swa_evicted_seqlen = 6
|
||||
kv = SimpleNamespace(swa_evicted_seqlen=6)
|
||||
|
||||
def pop_committed_kv_cache(self):
|
||||
return 8
|
||||
|
||||
@@ -57,13 +57,15 @@ class _FakeReq:
|
||||
)
|
||||
self.req_pool_idx = req_pool_idx
|
||||
self.kv_committed_len = committed
|
||||
self.kv_allocated_len = allocated
|
||||
self.kv = SimpleNamespace(
|
||||
kv_allocated_len=allocated,
|
||||
swa_evicted_seqlen=0,
|
||||
)
|
||||
self.kv_committed_freed = False
|
||||
self.kv_overallocated_freed = False
|
||||
self.origin_input_ids = list(range(committed))
|
||||
self.output_ids = []
|
||||
self.extra_key = None
|
||||
self.swa_evicted_seqlen = 0
|
||||
self.last_node = None
|
||||
self.cache_protected_len = 0
|
||||
self.swa_uuid_for_lock = None
|
||||
@@ -86,7 +88,7 @@ class _FakeReq:
|
||||
assert not self.kv_overallocated_freed
|
||||
self.pop_overallocated_calls += 1
|
||||
self.kv_overallocated_freed = True
|
||||
return self.kv_committed_len, self.kv_allocated_len
|
||||
return self.kv_committed_len, self.kv.kv_allocated_len
|
||||
|
||||
|
||||
def test_preabort_detaches_session_and_preserves_slot():
|
||||
@@ -112,7 +114,7 @@ def test_preabort_detaches_session_and_preserves_slot():
|
||||
tree_cache.slots["session-a"] = SessionSlot(
|
||||
req_pool_idx=0,
|
||||
kv_committed_len=48,
|
||||
kv_allocated_len=48,
|
||||
kv=SimpleNamespace(kv_allocated_len=48, swa_evicted_seqlen=0),
|
||||
cache_protected_len=16,
|
||||
)
|
||||
|
||||
@@ -132,7 +134,7 @@ def test_preabort_detaches_session_and_preserves_slot():
|
||||
slot = tree_cache.slots["session-a"]
|
||||
assert slot.req_pool_idx == 0
|
||||
assert slot.kv_committed_len == 48
|
||||
assert slot.kv_allocated_len == 48
|
||||
assert slot.kv.kv_allocated_len == 48
|
||||
assert len(result.device_indices) == 0
|
||||
|
||||
|
||||
@@ -179,7 +181,7 @@ def test_nth_mid_abort_nukes_session_slot():
|
||||
tree_cache.slots["session-a"] = SessionSlot(
|
||||
req_pool_idx=0,
|
||||
kv_committed_len=50,
|
||||
kv_allocated_len=50,
|
||||
kv=SimpleNamespace(kv_allocated_len=50, swa_evicted_seqlen=0),
|
||||
last_node=None,
|
||||
cache_protected_len=0,
|
||||
)
|
||||
@@ -230,14 +232,14 @@ def test_trim_overshoot_postcondition():
|
||||
req = _FakeReq("session-a", req_pool_idx=0, committed=40, allocated=44)
|
||||
req.origin_input_ids = list(range(26))
|
||||
req.output_ids = list(range(14))
|
||||
req.swa_evicted_seqlen = 42
|
||||
req.kv.swa_evicted_seqlen = 42
|
||||
|
||||
tree_cache._trim_overshoot(req, finished_len=12)
|
||||
|
||||
target = 38
|
||||
assert req.kv_committed_len == target
|
||||
assert req.kv_allocated_len == target
|
||||
assert req.swa_evicted_seqlen == target
|
||||
assert req.kv.kv_allocated_len == target
|
||||
assert req.kv.swa_evicted_seqlen == target
|
||||
assert len(req.output_ids) == 12
|
||||
# Tail [38, 44) freed by _free_kv_aligned.
|
||||
assert len(allocator.freed) == 1
|
||||
|
||||
@@ -106,7 +106,9 @@ def _make_req(req_pool_idx, token_ids, cache_protected_len, tree):
|
||||
origin_input_ids=token_ids,
|
||||
output_ids=[],
|
||||
cache_protected_len=cache_protected_len,
|
||||
swa_evicted_seqlen=0,
|
||||
kv=SimpleNamespace(
|
||||
swa_evicted_seqlen=0,
|
||||
),
|
||||
extra_key=None,
|
||||
last_node=tree.root_node,
|
||||
swa_uuid_for_lock=None,
|
||||
@@ -162,7 +164,7 @@ class TestSWAEvictionBoundary(unittest.TestCase):
|
||||
|
||||
insert_len = seq_len // page_size * page_size
|
||||
self.assertLess(
|
||||
req.swa_evicted_seqlen,
|
||||
req.kv.swa_evicted_seqlen,
|
||||
insert_len,
|
||||
f"page={page_size}, win={window}, seq={seq_len}",
|
||||
)
|
||||
@@ -187,7 +189,7 @@ class TestSWAEvictionBoundary(unittest.TestCase):
|
||||
ScheduleBatch._evict_swa(batch, req, seq_len - 1)
|
||||
|
||||
insert_len = seq_len // page_size * page_size
|
||||
self.assertLess(req.swa_evicted_seqlen, insert_len)
|
||||
self.assertLess(req.kv.swa_evicted_seqlen, insert_len)
|
||||
|
||||
tree.cache_finished_req(req, is_insert=True)
|
||||
tree.sanity_check()
|
||||
@@ -209,9 +211,9 @@ class TestSWAEvictionBoundary(unittest.TestCase):
|
||||
batch = _make_batch(tree, allocator, pool)
|
||||
ScheduleBatch._evict_swa(batch, req, seq_len - 1)
|
||||
|
||||
self.assertLess(req.swa_evicted_seqlen, seq_len)
|
||||
self.assertLess(req.kv.swa_evicted_seqlen, seq_len)
|
||||
self.assertEqual(
|
||||
req.swa_evicted_seqlen, max(0, seq_len - 1 - max(window, page_size))
|
||||
req.kv.swa_evicted_seqlen, max(0, seq_len - 1 - max(window, page_size))
|
||||
)
|
||||
|
||||
tree.cache_finished_req(req, is_insert=True)
|
||||
@@ -235,7 +237,7 @@ class TestSWAEvictionBoundary(unittest.TestCase):
|
||||
batch = _make_batch(tree, allocator, pool)
|
||||
ScheduleBatch._evict_swa(batch, req, seq_len - 1)
|
||||
|
||||
self.assertEqual(req.swa_evicted_seqlen, 0)
|
||||
self.assertEqual(req.kv.swa_evicted_seqlen, 0)
|
||||
|
||||
# -- Insert case 1: swa_evicted <= total_prefix_length --
|
||||
|
||||
@@ -264,7 +266,7 @@ class TestSWAEvictionBoundary(unittest.TestCase):
|
||||
|
||||
# pre_len=15: 15-2-8=5, floor to 8 -> 0. Eviction stays within matched.
|
||||
ScheduleBatch._evict_swa(batch, req2, first_len - 1)
|
||||
self.assertLessEqual(req2.swa_evicted_seqlen, first_len)
|
||||
self.assertLessEqual(req2.kv.swa_evicted_seqlen, first_len)
|
||||
|
||||
swa_evictable_before = tree.swa_evictable_size_
|
||||
tree.cache_finished_req(req2, is_insert=True)
|
||||
@@ -295,12 +297,12 @@ class TestSWAEvictionBoundary(unittest.TestCase):
|
||||
|
||||
ScheduleBatch._evict_swa(batch, req, seq_len - 1)
|
||||
insert_len = seq_len // page_size * page_size
|
||||
self.assertGreater(req.swa_evicted_seqlen, 0, "Should have some eviction")
|
||||
self.assertLess(req.swa_evicted_seqlen, insert_len, "Should be partial")
|
||||
self.assertGreater(req.kv.swa_evicted_seqlen, 0, "Should have some eviction")
|
||||
self.assertLess(req.kv.swa_evicted_seqlen, insert_len, "Should be partial")
|
||||
|
||||
tree.cache_finished_req(req, is_insert=True)
|
||||
|
||||
non_tombstone = insert_len - req.swa_evicted_seqlen
|
||||
non_tombstone = insert_len - req.kv.swa_evicted_seqlen
|
||||
self.assertEqual(tree.swa_evictable_size_, swa_evictable_before + non_tombstone)
|
||||
self.assertGreater(tree.full_evictable_size_, 0)
|
||||
tree.sanity_check()
|
||||
@@ -333,7 +335,7 @@ class TestSWAEvictionBoundary(unittest.TestCase):
|
||||
allocator.free_swa(pool.req_to_token[0, :old_evicted])
|
||||
|
||||
req = _make_req(0, list(range(seq_len)), 0, tree)
|
||||
req.swa_evicted_seqlen = old_evicted
|
||||
req.kv.swa_evicted_seqlen = old_evicted
|
||||
swa_evictable_before = tree.swa_evictable_size_
|
||||
|
||||
tree.cache_finished_req(req, is_insert=True)
|
||||
@@ -363,7 +365,7 @@ class TestSWAEvictionBoundary(unittest.TestCase):
|
||||
ScheduleBatch._evict_swa(batch, req, seq_len - 1)
|
||||
|
||||
insert_len = seq_len // page_size * page_size
|
||||
self.assertLess(req.swa_evicted_seqlen, insert_len, f"turn {turn}")
|
||||
self.assertLess(req.kv.swa_evicted_seqlen, insert_len, f"turn {turn}")
|
||||
|
||||
tree.cache_finished_req(req, is_insert=True)
|
||||
tree.sanity_check()
|
||||
@@ -386,7 +388,7 @@ class TestSWAEvictionBoundary(unittest.TestCase):
|
||||
ScheduleBatch._evict_swa(batch, req, seq_len - 1)
|
||||
|
||||
self.assertEqual(
|
||||
req.swa_evicted_seqlen, max(0, seq_len - 1 - max(window, page_size))
|
||||
req.kv.swa_evicted_seqlen, max(0, seq_len - 1 - max(window, page_size))
|
||||
)
|
||||
|
||||
tree.cache_finished_req(req, is_insert=True)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import unittest
|
||||
from array import array
|
||||
from types import SimpleNamespace
|
||||
|
||||
import torch
|
||||
|
||||
@@ -35,6 +36,7 @@ class _DummyReq:
|
||||
def __init__(self):
|
||||
self._kv_committed_len = 0
|
||||
self.swa_prefix_lock_released = False
|
||||
self.kv = SimpleNamespace(swa_evicted_seqlen=0)
|
||||
|
||||
def pop_committed_kv_cache(self):
|
||||
return self._kv_committed_len
|
||||
@@ -575,7 +577,7 @@ class TestSWA(unittest.TestCase):
|
||||
req.extra_key = None
|
||||
req.last_node = tree.root_node
|
||||
req.swa_uuid_for_lock = None
|
||||
req.swa_evicted_seqlen = 0
|
||||
req.kv.swa_evicted_seqlen = 0
|
||||
req.cache_protected_len = 1
|
||||
# Intentionally mismatch to ensure code does not use len(prefix_indices).
|
||||
req.prefix_indices = torch.tensor([7, 8, 9, 10, 11], device=tree.device)
|
||||
@@ -610,7 +612,7 @@ class TestSWA(unittest.TestCase):
|
||||
req2.extra_key = None
|
||||
req2.last_node = tree.root_node
|
||||
req2.swa_uuid_for_lock = None
|
||||
req2.swa_evicted_seqlen = 0
|
||||
req2.kv.swa_evicted_seqlen = 0
|
||||
req2.cache_protected_len = 1
|
||||
req2.prefix_indices = torch.tensor([21, 22, 23, 24, 25], device=tree.device)
|
||||
|
||||
|
||||
@@ -881,7 +881,7 @@ class UnifiedRadixCacheSuite:
|
||||
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_allocated_len = kv_len
|
||||
req.kv.kv_allocated_len = kv_len
|
||||
req.last_node = cache.root_node
|
||||
req.cache_protected_len = 0
|
||||
req.swa_uuid_for_lock = None
|
||||
@@ -998,7 +998,7 @@ class UnifiedRadixCacheSuite:
|
||||
req.cache_protected_len = 0
|
||||
req.swa_uuid_for_lock = None
|
||||
req.extra_key = None
|
||||
req.swa_evicted_seqlen = evicted_len
|
||||
req.kv.swa_evicted_seqlen = evicted_len
|
||||
|
||||
cache.cache_unfinished_req(req)
|
||||
|
||||
@@ -1220,7 +1220,7 @@ class UnifiedRadixCacheSuite:
|
||||
req.cache_protected_len = 0
|
||||
req.swa_uuid_for_lock = None
|
||||
req.extra_key = None
|
||||
req.swa_evicted_seqlen = 0
|
||||
req.kv.swa_evicted_seqlen = 0
|
||||
|
||||
full_available_before_insert = allocator.full_attn_allocator.available_size()
|
||||
|
||||
@@ -1761,7 +1761,7 @@ class UnifiedRadixCacheSuite:
|
||||
req.cache_protected_len = 0
|
||||
req.swa_uuid_for_lock = None
|
||||
req.extra_key = None
|
||||
req.swa_evicted_seqlen = 0
|
||||
req.kv.swa_evicted_seqlen = 0
|
||||
|
||||
swa_avail_before = allocator.swa_attn_allocator.available_size()
|
||||
|
||||
@@ -1771,10 +1771,10 @@ class UnifiedRadixCacheSuite:
|
||||
cushion = max(self.cfg.sliding_window_size, self.cfg.page_size)
|
||||
expected_evicted = (pre_len - 1) - cushion
|
||||
self.assertEqual(
|
||||
req.swa_evicted_seqlen,
|
||||
req.kv.swa_evicted_seqlen,
|
||||
expected_evicted,
|
||||
f"swa_evicted_seqlen should advance to (pre_len-1) - cushion = "
|
||||
f"{expected_evicted}, got {req.swa_evicted_seqlen}",
|
||||
f"{expected_evicted}, got {req.kv.swa_evicted_seqlen}",
|
||||
)
|
||||
|
||||
swa_avail_after = allocator.swa_attn_allocator.available_size()
|
||||
@@ -1813,13 +1813,13 @@ class UnifiedRadixCacheSuite:
|
||||
req.cache_protected_len = 0
|
||||
req.swa_uuid_for_lock = None
|
||||
req.extra_key = None
|
||||
req.swa_evicted_seqlen = 0
|
||||
req.kv.swa_evicted_seqlen = 0
|
||||
|
||||
with envs.SGLANG_OPT_UNIFIED_CACHE_FREE_OUT_OF_WINDOW_SLOTS.override(True):
|
||||
cache.cache_unfinished_req(req)
|
||||
|
||||
self.assertEqual(
|
||||
req.swa_evicted_seqlen,
|
||||
req.kv.swa_evicted_seqlen,
|
||||
0,
|
||||
"Nothing should be evicted when prefill fits inside the cushion",
|
||||
)
|
||||
|
||||
@@ -80,9 +80,7 @@ _OWNER_SITES = {
|
||||
): 1,
|
||||
# streaming session slot save/restore and tail trimming
|
||||
(_SS, "SessionSlot.save_from_req", "kv_committed_len"): 1,
|
||||
(_SS, "SessionSlot.save_from_req", "kv_allocated_len"): 1,
|
||||
(_SS, "SessionSlot.restore_to_req", "kv_committed_len"): 1,
|
||||
(_SS, "SessionSlot.restore_to_req", "kv_allocated_len"): 1,
|
||||
(_SS, "StreamingSession._free_tail", "kv_committed_len"): 2,
|
||||
(_SS, "StreamingSession._free_tail", "kv_allocated_len"): 2,
|
||||
(_SS, "StreamingSession._trim_overshoot", "kv_committed_len"): 1,
|
||||
|
||||
Reference in New Issue
Block a user