Let cache backend do not couple with owned committed kv details and avoid kv_committed_freed/kv_overallocated_freed fields (#29428)
This commit is contained in:
@@ -255,7 +255,7 @@ class DecodeKVCacheOffloadManager:
|
|||||||
if req.req_pool_idx is None or req.req_pool_idx == -1:
|
if req.req_pool_idx is None or req.req_pool_idx == -1:
|
||||||
return
|
return
|
||||||
|
|
||||||
kv_committed_len = req.pop_committed_kv_cache()
|
kv_committed_len = req.effective_kv_committed_len()
|
||||||
|
|
||||||
# Free the prefill-aligned slots. Previously this was done
|
# Free the prefill-aligned slots. Previously this was done
|
||||||
# eagerly in offload_kv_cache (mid-decode), which raced with
|
# eagerly in offload_kv_cache (mid-decode), which raced with
|
||||||
@@ -276,7 +276,7 @@ class DecodeKVCacheOffloadManager:
|
|||||||
|
|
||||||
# Free over-allocated KV cache slots (e.g. from speculative decoding v2).
|
# Free over-allocated KV cache slots (e.g. from speculative decoding v2).
|
||||||
# Without spec v2, start_p == end_p so this is a no-op.
|
# Without spec v2, start_p == end_p so this is a no-op.
|
||||||
start_p, end_p = req.pop_overallocated_kv_cache()
|
start_p, end_p = kv_committed_len, req.kv.kv_allocated_len
|
||||||
if self.page_size > 1:
|
if self.page_size > 1:
|
||||||
start_p = ceil_align(start_p, self.page_size)
|
start_p = ceil_align(start_p, self.page_size)
|
||||||
if start_p < end_p:
|
if start_p < end_p:
|
||||||
|
|||||||
@@ -749,8 +749,6 @@ class Req(ReqDllmMixin):
|
|||||||
# For req-level memory management
|
# For req-level memory management
|
||||||
self.kv_committed_len = 0
|
self.kv_committed_len = 0
|
||||||
self.kv: ReqKvInfo = ReqKvInfo(kv_allocated_len=0, swa_evicted_seqlen=0)
|
self.kv: ReqKvInfo = ReqKvInfo(kv_allocated_len=0, swa_evicted_seqlen=0)
|
||||||
self.kv_committed_freed = False
|
|
||||||
self.kv_overallocated_freed = False
|
|
||||||
|
|
||||||
# for cross-encoder model
|
# for cross-encoder model
|
||||||
self.token_type_ids = token_type_ids
|
self.token_type_ids = token_type_ids
|
||||||
@@ -1076,33 +1074,13 @@ class Req(ReqDllmMixin):
|
|||||||
or self.mamba_host_hit_length > 0
|
or self.mamba_host_hit_length > 0
|
||||||
)
|
)
|
||||||
|
|
||||||
def _cache_commit_len(self) -> int:
|
def effective_kv_committed_len(self) -> int:
|
||||||
# Report only the prompt prefix so thinking + answer fall into the
|
# Report only the prompt prefix so thinking + answer fall into the
|
||||||
# overallocated range and are reclaimed by release_kv_cache. #22373.
|
# overallocated range and are reclaimed by release_kv_cache. #22373.
|
||||||
if get_server_args().strip_thinking_cache and self.reasoning_tokens > 0:
|
if get_server_args().strip_thinking_cache and self.reasoning_tokens > 0:
|
||||||
return min(self.kv_committed_len, len(self.origin_input_ids))
|
return min(self.kv_committed_len, len(self.origin_input_ids))
|
||||||
return self.kv_committed_len
|
return self.kv_committed_len
|
||||||
|
|
||||||
def pop_committed_kv_cache(self) -> int:
|
|
||||||
"""Return the length of committed KV cache and mark them as freed."""
|
|
||||||
assert (
|
|
||||||
not self.kv_committed_freed
|
|
||||||
), f"Committed KV cache already freed ({self.kv_committed_len=})"
|
|
||||||
self.kv_committed_freed = True
|
|
||||||
return self._cache_commit_len()
|
|
||||||
|
|
||||||
def pop_overallocated_kv_cache(self) -> Tuple[int, int]:
|
|
||||||
"""Return the range of over-allocated KV cache and mark them as freed."""
|
|
||||||
|
|
||||||
# NOTE: This function is called when there is over-allocation of KV cache.
|
|
||||||
# Over-allocation: we allocate more KV cache than the committed length.
|
|
||||||
# e.g., speculative decoding may allocate more KV cache than actually used.
|
|
||||||
assert (
|
|
||||||
not self.kv_overallocated_freed
|
|
||||||
), f"Overallocated KV cache already freed, {self.kv_committed_len=}, {self.kv.kv_allocated_len=}"
|
|
||||||
self.kv_overallocated_freed = True
|
|
||||||
return self._cache_commit_len(), self.kv.kv_allocated_len
|
|
||||||
|
|
||||||
def update_spec_correct_drafts_histogram(self, num_correct_drafts: int):
|
def update_spec_correct_drafts_histogram(self, num_correct_drafts: int):
|
||||||
"""Update the speculative decoding acceptance histogram.
|
"""Update the speculative decoding acceptance histogram.
|
||||||
|
|
||||||
@@ -1525,8 +1503,6 @@ class Req(ReqDllmMixin):
|
|||||||
self.already_computed = 0
|
self.already_computed = 0
|
||||||
self.kv.kv_allocated_len = 0
|
self.kv.kv_allocated_len = 0
|
||||||
self.kv_committed_len = 0
|
self.kv_committed_len = 0
|
||||||
self.kv_committed_freed = False
|
|
||||||
self.kv_overallocated_freed = False
|
|
||||||
self.kv.swa_evicted_seqlen = 0
|
self.kv.swa_evicted_seqlen = 0
|
||||||
self.extend_batch_idx = 0
|
self.extend_batch_idx = 0
|
||||||
self.decode_batch_idx = 0
|
self.decode_batch_idx = 0
|
||||||
|
|||||||
@@ -2564,9 +2564,7 @@ class Scheduler(
|
|||||||
req.pending_bootstrap = False
|
req.pending_bootstrap = False
|
||||||
if self.enable_hicache_storage:
|
if self.enable_hicache_storage:
|
||||||
self.tree_cache.release_aborted_request(req.rid)
|
self.tree_cache.release_aborted_request(req.rid)
|
||||||
if (
|
if req.req_pool_idx is not None or self.tree_cache.supports_mamba():
|
||||||
req.req_pool_idx is not None or self.tree_cache.supports_mamba()
|
|
||||||
) and not req.kv_committed_freed:
|
|
||||||
release_kv_cache(req, self.tree_cache, is_insert=False)
|
release_kv_cache(req, self.tree_cache, is_insert=False)
|
||||||
|
|
||||||
self.chunked_req = None
|
self.chunked_req = None
|
||||||
|
|||||||
@@ -246,8 +246,7 @@ class SchedulerInvariantChecker:
|
|||||||
swa_uncached = 0
|
swa_uncached = 0
|
||||||
for batch in batches:
|
for batch in batches:
|
||||||
for req in batch.reqs:
|
for req in batch.reqs:
|
||||||
assert req.kv_committed_freed == req.kv_overallocated_freed
|
if req.req_pool_idx is None:
|
||||||
if req.kv_committed_freed or req.req_pool_idx is None:
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
allocated_len = req.kv.kv_allocated_len
|
allocated_len = req.kv.kv_allocated_len
|
||||||
|
|||||||
@@ -76,11 +76,12 @@ class ChunkCache(BasePrefixCache):
|
|||||||
# ChunkCache does not support prefix caching, so insert is a no-op
|
# ChunkCache does not support prefix caching, so insert is a no-op
|
||||||
return InsertResult(prefix_len=0)
|
return InsertResult(prefix_len=0)
|
||||||
|
|
||||||
def cache_finished_req(self, req: Req, is_insert: bool = True):
|
def cache_finished_req(
|
||||||
kv_committed_len = req.pop_committed_kv_cache()
|
self, req: Req, is_insert: bool = True, *, kv_len_to_handle: int
|
||||||
|
):
|
||||||
# For decode server: if req.output_ids is empty, we want to free all req.origin_input_ids
|
# For decode server: if req.output_ids is empty, we want to free all req.origin_input_ids
|
||||||
kv_indices = self.req_to_token_pool.req_to_token[
|
kv_indices = self.req_to_token_pool.req_to_token[
|
||||||
req.req_pool_idx, :kv_committed_len
|
req.req_pool_idx, :kv_len_to_handle
|
||||||
]
|
]
|
||||||
self.token_to_kv_pool_allocator.free(kv_indices)
|
self.token_to_kv_pool_allocator.free(kv_indices)
|
||||||
|
|
||||||
@@ -150,8 +151,10 @@ class PureSWAChunkCache(SWAChunkCache):
|
|||||||
prefix is released here when the request finishes.
|
prefix is released here when the request finishes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def cache_finished_req(self, req: Req, is_insert: bool = True):
|
def cache_finished_req(
|
||||||
kv_committed_len = req.pop_committed_kv_cache()
|
self, req: Req, is_insert: bool = True, *, kv_len_to_handle: int
|
||||||
|
):
|
||||||
|
kv_committed_len = kv_len_to_handle
|
||||||
kv_indices = self.req_to_token_pool.req_to_token[
|
kv_indices = self.req_to_token_pool.req_to_token[
|
||||||
req.req_pool_idx, :kv_committed_len
|
req.req_pool_idx, :kv_committed_len
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -640,17 +640,19 @@ def release_kv_cache(req: Req, tree_cache: BasePrefixCache, is_insert: bool = Tr
|
|||||||
req.mamba_pool_idx = None
|
req.mamba_pool_idx = None
|
||||||
return
|
return
|
||||||
|
|
||||||
|
effective_kv_committed_len = req.effective_kv_committed_len()
|
||||||
tree_cache.cache_finished_req(
|
tree_cache.cache_finished_req(
|
||||||
req,
|
req,
|
||||||
is_insert=is_insert and not getattr(req, "skip_radix_cache_insert", False),
|
is_insert=is_insert and not getattr(req, "skip_radix_cache_insert", False),
|
||||||
|
kv_len_to_handle=effective_kv_committed_len,
|
||||||
)
|
)
|
||||||
|
|
||||||
# StreamingSession.cache_finished_req handles speculative tail trim
|
# StreamingSession.cache_finished_req handles speculative tail trim
|
||||||
# and bookkeeping flag sync internally, then sets req_pool_idx = None.
|
# internally, then sets req_pool_idx = None.
|
||||||
if req.req_pool_idx is None:
|
if req.req_pool_idx is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
start_p, end_p = req.pop_overallocated_kv_cache()
|
start_p, end_p = effective_kv_committed_len, req.kv.kv_allocated_len
|
||||||
|
|
||||||
global_server_args = get_server_args()
|
global_server_args = get_server_args()
|
||||||
page_size = global_server_args.page_size
|
page_size = global_server_args.page_size
|
||||||
|
|||||||
@@ -523,20 +523,21 @@ class MambaRadixCache(KVCacheEventMixin, BasePrefixCache):
|
|||||||
)
|
)
|
||||||
return InsertResult(prefix_len=prefix_len, mamba_exist=mamba_exist)
|
return InsertResult(prefix_len=prefix_len, mamba_exist=mamba_exist)
|
||||||
|
|
||||||
def cache_finished_req(self, req: Req, is_insert: bool = True) -> None:
|
def cache_finished_req(
|
||||||
|
self, req: Req, is_insert: bool = True, *, kv_len_to_handle: int
|
||||||
|
) -> None:
|
||||||
"""Cache request when it finishes."""
|
"""Cache request when it finishes."""
|
||||||
kv_committed_len = req.pop_committed_kv_cache()
|
|
||||||
if self.disable:
|
if self.disable:
|
||||||
kv_indices = self.req_to_token_pool.req_to_token[
|
kv_indices = self.req_to_token_pool.req_to_token[
|
||||||
req.req_pool_idx, :kv_committed_len
|
req.req_pool_idx, :kv_len_to_handle
|
||||||
]
|
]
|
||||||
self.token_to_kv_pool_allocator.free(kv_indices)
|
self.token_to_kv_pool_allocator.free(kv_indices)
|
||||||
self.req_to_token_pool.free_mamba_cache(req)
|
self.req_to_token_pool.free_mamba_cache(req)
|
||||||
return
|
return
|
||||||
|
|
||||||
token_ids = (req.origin_input_ids + req.output_ids)[:kv_committed_len]
|
token_ids = (req.origin_input_ids + req.output_ids)[:kv_len_to_handle]
|
||||||
kv_indices = self.req_to_token_pool.req_to_token[
|
kv_indices = self.req_to_token_pool.req_to_token[
|
||||||
req.req_pool_idx, :kv_committed_len
|
req.req_pool_idx, :kv_len_to_handle
|
||||||
]
|
]
|
||||||
|
|
||||||
if is_insert:
|
if is_insert:
|
||||||
@@ -572,7 +573,7 @@ class MambaRadixCache(KVCacheEventMixin, BasePrefixCache):
|
|||||||
|
|
||||||
assert (
|
assert (
|
||||||
cache_len == page_aligned_len
|
cache_len == page_aligned_len
|
||||||
), f"It is required {cache_len=}, {page_aligned_len=}, {kv_committed_len=}, {len(req.origin_input_ids)=}, {len(req.output_ids)=} ping @yizhang2077 if you see this"
|
), f"It is required {cache_len=}, {page_aligned_len=}, {kv_len_to_handle=}, {len(req.origin_input_ids)=}, {len(req.output_ids)=} ping @yizhang2077 if you see this"
|
||||||
|
|
||||||
# Radix Cache takes one ref in memory pool
|
# Radix Cache takes one ref in memory pool
|
||||||
# insert the token_ids and kv_indices into the radix tree
|
# insert the token_ids and kv_indices into the radix tree
|
||||||
|
|||||||
@@ -62,7 +62,9 @@ class PureSWARadixCache(RadixCache):
|
|||||||
num_tokens = max(params.num_tokens, params.swa_num_tokens)
|
num_tokens = max(params.num_tokens, params.swa_num_tokens)
|
||||||
return super().evict(EvictParams(num_tokens=num_tokens))
|
return super().evict(EvictParams(num_tokens=num_tokens))
|
||||||
|
|
||||||
def cache_finished_req(self, req: Req, is_insert: bool = True):
|
def cache_finished_req(
|
||||||
|
self, req: Req, is_insert: bool = True, *, kv_len_to_handle: int
|
||||||
|
):
|
||||||
"""Cache request when it finishes.
|
"""Cache request when it finishes.
|
||||||
|
|
||||||
Only inserts the prefill portion [0, evict_floor) into the radix tree.
|
Only inserts the prefill portion [0, evict_floor) into the radix tree.
|
||||||
@@ -73,7 +75,7 @@ class PureSWARadixCache(RadixCache):
|
|||||||
if self.disable_finished_insert:
|
if self.disable_finished_insert:
|
||||||
is_insert = False
|
is_insert = False
|
||||||
|
|
||||||
kv_committed_len = req.pop_committed_kv_cache()
|
kv_committed_len = kv_len_to_handle
|
||||||
if self.disable:
|
if self.disable:
|
||||||
kv_indices = self.req_to_token_pool.req_to_token[
|
kv_indices = self.req_to_token_pool.req_to_token[
|
||||||
req.req_pool_idx, :kv_committed_len
|
req.req_pool_idx, :kv_committed_len
|
||||||
|
|||||||
@@ -434,21 +434,22 @@ class RadixCache(SessionRadixCacheMixin, KVCacheEventMixin, BasePrefixCache):
|
|||||||
)
|
)
|
||||||
return InsertResult(prefix_len=prefix_len, last_device_node=last_node)
|
return InsertResult(prefix_len=prefix_len, last_device_node=last_node)
|
||||||
|
|
||||||
def cache_finished_req(self, req: Req, is_insert: bool = True):
|
def cache_finished_req(
|
||||||
|
self, req: Req, is_insert: bool = True, *, kv_len_to_handle: int
|
||||||
|
):
|
||||||
"""Cache request when it finishes."""
|
"""Cache request when it finishes."""
|
||||||
# In deterministic mode, disable finished request insertion to radix cache
|
# In deterministic mode, disable finished request insertion to radix cache
|
||||||
if self.disable_finished_insert:
|
if self.disable_finished_insert:
|
||||||
is_insert = False
|
is_insert = False
|
||||||
|
|
||||||
kv_committed_len = req.pop_committed_kv_cache()
|
|
||||||
if self.disable:
|
if self.disable:
|
||||||
kv_indices = self.req_to_token_pool.req_to_token[
|
kv_indices = self.req_to_token_pool.req_to_token[
|
||||||
req.req_pool_idx, :kv_committed_len
|
req.req_pool_idx, :kv_len_to_handle
|
||||||
]
|
]
|
||||||
self.token_to_kv_pool_allocator.free(kv_indices)
|
self.token_to_kv_pool_allocator.free(kv_indices)
|
||||||
return
|
return
|
||||||
|
|
||||||
token_ids = (req.origin_input_ids + req.output_ids)[:kv_committed_len]
|
token_ids = (req.origin_input_ids + req.output_ids)[:kv_len_to_handle]
|
||||||
kv_indices = self.req_to_token_pool.req_to_token[
|
kv_indices = self.req_to_token_pool.req_to_token[
|
||||||
req.req_pool_idx, : len(token_ids)
|
req.req_pool_idx, : len(token_ids)
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -169,19 +169,20 @@ class RadixCacheCpp(BasePrefixCache):
|
|||||||
def total_size(self):
|
def total_size(self):
|
||||||
return self.tree.total_size()
|
return self.tree.total_size()
|
||||||
|
|
||||||
def cache_finished_req(self, req: Req, is_insert: bool = True):
|
def cache_finished_req(
|
||||||
|
self, req: Req, is_insert: bool = True, *, kv_len_to_handle: int
|
||||||
|
):
|
||||||
"""Cache request when it finishes."""
|
"""Cache request when it finishes."""
|
||||||
assert req.req_pool_idx is not None
|
assert req.req_pool_idx is not None
|
||||||
kv_committed_len = req.pop_committed_kv_cache()
|
token_ids = (req.origin_input_ids + req.output_ids)[:kv_len_to_handle]
|
||||||
token_ids = (req.origin_input_ids + req.output_ids)[:kv_committed_len]
|
|
||||||
kv_indices = self.req_to_token_pool.req_to_token[
|
kv_indices = self.req_to_token_pool.req_to_token[
|
||||||
req.req_pool_idx, :kv_committed_len
|
req.req_pool_idx, :kv_len_to_handle
|
||||||
].to(dtype=torch.int64, copy=True)
|
].to(dtype=torch.int64, copy=True)
|
||||||
|
|
||||||
# NOTE: our C++ implementation don't need `token_ids` and `kv_indices` to be page-aligned
|
# NOTE: our C++ implementation don't need `token_ids` and `kv_indices` to be page-aligned
|
||||||
# it will automatically align them, but length of them should be equal
|
# it will automatically align them, but length of them should be equal
|
||||||
old_prefix_len = len(req.prefix_indices) // self.page_size * self.page_size
|
old_prefix_len = len(req.prefix_indices) // self.page_size * self.page_size
|
||||||
page_aligned_overall_len = kv_committed_len // self.page_size * self.page_size
|
page_aligned_overall_len = kv_len_to_handle // self.page_size * self.page_size
|
||||||
|
|
||||||
if is_insert:
|
if is_insert:
|
||||||
new_prefix_len = self._insert(
|
new_prefix_len = self._insert(
|
||||||
@@ -200,7 +201,7 @@ class RadixCacheCpp(BasePrefixCache):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# need to free the unaligned part, since it cannot be inserted into the radix tree
|
# need to free the unaligned part, since it cannot be inserted into the radix tree
|
||||||
if page_aligned_overall_len < kv_committed_len:
|
if page_aligned_overall_len < kv_len_to_handle:
|
||||||
# NOTE: sglang PagedAllocator support unaligned free (which will automatically align it)
|
# NOTE: sglang PagedAllocator support unaligned free (which will automatically align it)
|
||||||
self.token_to_kv_pool_allocator.free(kv_indices[page_aligned_overall_len:])
|
self.token_to_kv_pool_allocator.free(kv_indices[page_aligned_overall_len:])
|
||||||
|
|
||||||
|
|||||||
@@ -379,10 +379,12 @@ class FlexKVRadixCache(RadixCache):
|
|||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
def cache_finished_req( # type: ignore[override]
|
def cache_finished_req( # type: ignore[override]
|
||||||
self, req: Req, is_insert: bool = True
|
self, req: Req, is_insert: bool = True, *, kv_len_to_handle: int
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Base cache_finished_req then fire an async FlexKV store."""
|
"""Base cache_finished_req then fire an async FlexKV store."""
|
||||||
super().cache_finished_req(req, is_insert=is_insert)
|
super().cache_finished_req(
|
||||||
|
req, is_insert=is_insert, kv_len_to_handle=kv_len_to_handle
|
||||||
|
)
|
||||||
if not is_insert:
|
if not is_insert:
|
||||||
self._load_markers.pop(req.rid, None)
|
self._load_markers.pop(req.rid, None)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -428,10 +428,14 @@ class LMCRadixCache(RadixCache):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def cache_finished_req(self, req: Req, is_insert: bool = True) -> None:
|
def cache_finished_req(
|
||||||
|
self, req: Req, is_insert: bool = True, *, kv_len_to_handle: int
|
||||||
|
) -> None:
|
||||||
"""On request completion, insert device KV into radix and store to LMCache."""
|
"""On request completion, insert device KV into radix and store to LMCache."""
|
||||||
|
|
||||||
super().cache_finished_req(req, is_insert=is_insert)
|
super().cache_finished_req(
|
||||||
|
req, is_insert=is_insert, kv_len_to_handle=kv_len_to_handle
|
||||||
|
)
|
||||||
if not is_insert:
|
if not is_insert:
|
||||||
if self._mode is LMCacheMode.MP:
|
if self._mode is LMCacheMode.MP:
|
||||||
self._mp_load_back_markers.pop(req.rid, None)
|
self._mp_load_back_markers.pop(req.rid, None)
|
||||||
|
|||||||
@@ -456,19 +456,20 @@ class SWARadixCache(KVCacheEventMixin, BasePrefixCache):
|
|||||||
)
|
)
|
||||||
return InsertResult(prefix_len=prefix_len)
|
return InsertResult(prefix_len=prefix_len)
|
||||||
|
|
||||||
def cache_finished_req(self, req: Req, is_insert: bool = True) -> None:
|
def cache_finished_req(
|
||||||
|
self, req: Req, is_insert: bool = True, *, kv_len_to_handle: int
|
||||||
|
) -> None:
|
||||||
"""Cache request when it finishes."""
|
"""Cache request when it finishes."""
|
||||||
kv_committed_len = req.pop_committed_kv_cache()
|
|
||||||
if self.disable:
|
if self.disable:
|
||||||
kv_indices = self.req_to_token_pool.req_to_token[
|
kv_indices = self.req_to_token_pool.req_to_token[
|
||||||
req.req_pool_idx, :kv_committed_len
|
req.req_pool_idx, :kv_len_to_handle
|
||||||
]
|
]
|
||||||
self.token_to_kv_pool_allocator.free(kv_indices)
|
self.token_to_kv_pool_allocator.free(kv_indices)
|
||||||
return
|
return
|
||||||
|
|
||||||
token_ids = (req.origin_input_ids + req.output_ids)[:kv_committed_len]
|
token_ids = (req.origin_input_ids + req.output_ids)[:kv_len_to_handle]
|
||||||
kv_indices = self.req_to_token_pool.req_to_token[
|
kv_indices = self.req_to_token_pool.req_to_token[
|
||||||
req.req_pool_idx, :kv_committed_len
|
req.req_pool_idx, :kv_len_to_handle
|
||||||
]
|
]
|
||||||
|
|
||||||
radix_key = RadixKey(
|
radix_key = RadixKey(
|
||||||
|
|||||||
@@ -199,14 +199,14 @@ Unlock a previously locked node path.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### `cache_finished_req(req: Req, is_insert: bool = True)`
|
### `cache_finished_req(req: Req, is_insert: bool = True, *, kv_len_to_handle: int)`
|
||||||
|
|
||||||
Cache a completed request's KV data into the tree.
|
Cache a completed request's KV data into the tree.
|
||||||
|
|
||||||
| Aspect | Detail |
|
| Aspect | Detail |
|
||||||
|--------|--------|
|
|--------|--------|
|
||||||
| **Purpose** | After a request finishes, insert its token/KV data into the tree for future reuse |
|
| **Purpose** | After a request finishes, insert its token/KV data into the tree for future reuse |
|
||||||
| **Inputs** | `req` — the finished request; `is_insert` — whether to insert (True) or just release locks (False) |
|
| **Inputs** | `req` — the finished request; `is_insert` — whether to insert (True) or just release locks (False); `kv_len_to_handle` — committed KV length supplied by the caller |
|
||||||
| **Output** | `None` |
|
| **Output** | `None` |
|
||||||
| **Mutation** | Calls component hooks → `insert` → `dec_lock_ref` → component cleanup. Frees unaligned tail KV indices; frees non-inserted KV indices when `is_insert=False`. |
|
| **Mutation** | Calls component hooks → `insert` → `dec_lock_ref` → component cleanup. Frees unaligned tail KV indices; frees non-inserted KV indices when `is_insert=False`. |
|
||||||
| **Complexity** | **O(K + D·C)** — insert O(K + D·C) + lock release O(D). Simplifies to **O(K)**. |
|
| **Complexity** | **O(K + D·C)** — insert O(K + D·C) + lock release O(D). Simplifies to **O(K)**. |
|
||||||
|
|||||||
@@ -712,24 +712,24 @@ class UnifiedRadixCache(KVCacheEventMixin, BasePrefixCache):
|
|||||||
self._update_evictable_leaf_sets(node)
|
self._update_evictable_leaf_sets(node)
|
||||||
return DecLockRefResult()
|
return DecLockRefResult()
|
||||||
|
|
||||||
def cache_finished_req(self, req: Req, is_insert: bool = True, **kwargs) -> None:
|
def cache_finished_req(
|
||||||
|
self, req: Req, is_insert: bool = True, *, kv_len_to_handle: int, **kwargs
|
||||||
|
) -> None:
|
||||||
if self.session.try_cache_finished_req(req, is_insert=is_insert, **kwargs):
|
if self.session.try_cache_finished_req(req, is_insert=is_insert, **kwargs):
|
||||||
return
|
return
|
||||||
|
|
||||||
kv_committed_len = req.pop_committed_kv_cache()
|
|
||||||
|
|
||||||
if self.disable:
|
if self.disable:
|
||||||
kv_indices = self.req_to_token_pool.req_to_token[
|
kv_indices = self.req_to_token_pool.req_to_token[
|
||||||
req.req_pool_idx, :kv_committed_len
|
req.req_pool_idx, :kv_len_to_handle
|
||||||
]
|
]
|
||||||
self.token_to_kv_pool_allocator.free(kv_indices)
|
self.token_to_kv_pool_allocator.free(kv_indices)
|
||||||
for comp in self._components_tuple:
|
for comp in self._components_tuple:
|
||||||
comp.cleanup_after_caching_req(req, is_finished=True)
|
comp.cleanup_after_caching_req(req, is_finished=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
token_ids = (req.origin_input_ids + req.output_ids)[:kv_committed_len]
|
token_ids = (req.origin_input_ids + req.output_ids)[:kv_len_to_handle]
|
||||||
kv_indices = self.req_to_token_pool.req_to_token[
|
kv_indices = self.req_to_token_pool.req_to_token[
|
||||||
req.req_pool_idx, :kv_committed_len
|
req.req_pool_idx, :kv_len_to_handle
|
||||||
]
|
]
|
||||||
|
|
||||||
result = None
|
result = None
|
||||||
|
|||||||
@@ -325,7 +325,6 @@ class StreamingSession(BasePrefixCache):
|
|||||||
self.release_session(session_id)
|
self.release_session(session_id)
|
||||||
req.req_pool_idx = None
|
req.req_pool_idx = None
|
||||||
req.session.abort_req()
|
req.session.abort_req()
|
||||||
self._mark_kv_freed(req)
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if is_first:
|
if is_first:
|
||||||
@@ -348,7 +347,6 @@ class StreamingSession(BasePrefixCache):
|
|||||||
# Update req_nodes to this successfully finished request.
|
# Update req_nodes to this successfully finished request.
|
||||||
req.session.finish_req(req)
|
req.session.finish_req(req)
|
||||||
|
|
||||||
self._mark_kv_freed(req)
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def try_cache_unfinished_req(
|
def try_cache_unfinished_req(
|
||||||
@@ -570,14 +568,6 @@ class StreamingSession(BasePrefixCache):
|
|||||||
tail = self.req_to_token_pool.req_to_token[pool_idx, start:end]
|
tail = self.req_to_token_pool.req_to_token[pool_idx, start:end]
|
||||||
self.token_to_kv_pool_allocator.free(tail)
|
self.token_to_kv_pool_allocator.free(tail)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _mark_kv_freed(req: Req) -> None:
|
|
||||||
"""Set bookkeeping flags so busy check skips this finished req."""
|
|
||||||
if not req.kv_committed_freed:
|
|
||||||
req.pop_committed_kv_cache()
|
|
||||||
if not req.kv_overallocated_freed:
|
|
||||||
req.pop_overallocated_kv_cache()
|
|
||||||
|
|
||||||
# -- Pass-through methods --
|
# -- Pass-through methods --
|
||||||
|
|
||||||
def evictable_size(self):
|
def evictable_size(self):
|
||||||
|
|||||||
@@ -36,22 +36,8 @@ def _make_mock_req(
|
|||||||
req.req_pool_idx = req_pool_idx
|
req.req_pool_idx = req_pool_idx
|
||||||
req.kv_committed_len = kv_committed_len
|
req.kv_committed_len = kv_committed_len
|
||||||
req.kv = SimpleNamespace(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))
|
req.prefix_indices = list(range(prefix_indices_len))
|
||||||
|
req.effective_kv_committed_len = lambda: req.kv_committed_len
|
||||||
def pop_committed():
|
|
||||||
assert not req.kv_committed_freed
|
|
||||||
req.kv_committed_freed = True
|
|
||||||
return req.kv_committed_len
|
|
||||||
|
|
||||||
def pop_overallocated():
|
|
||||||
assert not req.kv_overallocated_freed
|
|
||||||
req.kv_overallocated_freed = True
|
|
||||||
return req.kv_committed_len, req.kv.kv_allocated_len
|
|
||||||
|
|
||||||
req.pop_committed_kv_cache = pop_committed
|
|
||||||
req.pop_overallocated_kv_cache = pop_overallocated
|
|
||||||
return req
|
return req
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -83,18 +83,10 @@ class MockReq:
|
|||||||
self.priority = 0
|
self.priority = 0
|
||||||
self.kv_committed_len = len(fill_ids)
|
self.kv_committed_len = len(fill_ids)
|
||||||
self.kv = SimpleNamespace(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):
|
def get_fill_ids(self):
|
||||||
return self.full_untruncated_fill_ids[: self.extend_range.end]
|
return self.full_untruncated_fill_ids[: self.extend_range.end]
|
||||||
|
|
||||||
def pop_committed_kv_cache(self):
|
|
||||||
self.kv_committed_freed = True
|
|
||||||
return self.kv_committed_len
|
|
||||||
|
|
||||||
def pop_overallocated_kv_cache(self):
|
|
||||||
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):
|
def _make_req(fill_ids, req_pool_idx=0, cache_protected_len=0, last_node=None):
|
||||||
return MockReq(fill_ids, req_pool_idx, cache_protected_len, last_node)
|
return MockReq(fill_ids, req_pool_idx, cache_protected_len, last_node)
|
||||||
@@ -152,7 +144,7 @@ class TestDecodeLockRefScenarios(unittest.TestCase):
|
|||||||
cache.cache_unfinished_req(req)
|
cache.cache_unfinished_req(req)
|
||||||
|
|
||||||
# Step 3: cache_finished_req with is_insert=True (dec lock)
|
# Step 3: cache_finished_req with is_insert=True (dec lock)
|
||||||
cache.cache_finished_req(req)
|
cache.cache_finished_req(req, kv_len_to_handle=req.kv_committed_len)
|
||||||
|
|
||||||
# Verify: all non-root nodes should have lock_ref == 0
|
# Verify: all non-root nodes should have lock_ref == 0
|
||||||
# (root always has lock_ref == 1)
|
# (root always has lock_ref == 1)
|
||||||
@@ -201,7 +193,7 @@ class TestDecodeLockRefScenarios(unittest.TestCase):
|
|||||||
cache.cache_unfinished_req(req)
|
cache.cache_unfinished_req(req)
|
||||||
|
|
||||||
# Step 3: cache_finished_req (dec leaf)
|
# Step 3: cache_finished_req (dec leaf)
|
||||||
cache.cache_finished_req(req)
|
cache.cache_finished_req(req, kv_len_to_handle=req.kv_committed_len)
|
||||||
|
|
||||||
# Root lock unchanged, all nodes unlocked
|
# Root lock unchanged, all nodes unlocked
|
||||||
self.assertEqual(cache.root_node.lock_ref, root_lock_before)
|
self.assertEqual(cache.root_node.lock_ref, root_lock_before)
|
||||||
@@ -244,7 +236,9 @@ class TestDecodeLockRefScenarios(unittest.TestCase):
|
|||||||
|
|
||||||
# Transfer fails -> cache_finished_req with is_insert=False
|
# Transfer fails -> cache_finished_req with is_insert=False
|
||||||
# This frees delta tokens and dec_lock_ref on last_node
|
# This frees delta tokens and dec_lock_ref on last_node
|
||||||
cache.cache_finished_req(req, is_insert=False)
|
cache.cache_finished_req(
|
||||||
|
req, is_insert=False, kv_len_to_handle=req.kv_committed_len
|
||||||
|
)
|
||||||
|
|
||||||
# The prefix node should be unlocked (back to evictable)
|
# The prefix node should be unlocked (back to evictable)
|
||||||
self.assertEqual(cache.root_node.lock_ref, 1)
|
self.assertEqual(cache.root_node.lock_ref, 1)
|
||||||
@@ -289,7 +283,9 @@ class TestDecodeLockRefScenarios(unittest.TestCase):
|
|||||||
|
|
||||||
# Transfer fails -> cache_finished_req with is_insert=False
|
# Transfer fails -> cache_finished_req with is_insert=False
|
||||||
# dec_lock_ref(root) is a no-op
|
# dec_lock_ref(root) is a no-op
|
||||||
cache.cache_finished_req(req, is_insert=False)
|
cache.cache_finished_req(
|
||||||
|
req, is_insert=False, kv_len_to_handle=req.kv_committed_len
|
||||||
|
)
|
||||||
|
|
||||||
# Root lock unchanged, nothing protected or evictable
|
# Root lock unchanged, nothing protected or evictable
|
||||||
self.assertEqual(cache.root_node.lock_ref, root_lock_before)
|
self.assertEqual(cache.root_node.lock_ref, root_lock_before)
|
||||||
@@ -400,7 +396,7 @@ class TestDecodeLockRefScenarios(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
cache.cache_unfinished_req(req)
|
cache.cache_unfinished_req(req)
|
||||||
cache.cache_finished_req(req)
|
cache.cache_finished_req(req, kv_len_to_handle=req.kv_committed_len)
|
||||||
|
|
||||||
# After all iterations, root lock should be 1, no protected nodes
|
# After all iterations, root lock should be 1, no protected nodes
|
||||||
self.assertEqual(cache.root_node.lock_ref, 1)
|
self.assertEqual(cache.root_node.lock_ref, 1)
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class TestPureSWAChunkCache(CustomTestCase):
|
|||||||
)
|
)
|
||||||
cache.token_to_kv_pool_allocator = _FakeAllocator()
|
cache.token_to_kv_pool_allocator = _FakeAllocator()
|
||||||
|
|
||||||
cache.cache_finished_req(_FakeReq())
|
cache.cache_finished_req(_FakeReq(), kv_len_to_handle=8)
|
||||||
|
|
||||||
self.assertEqual(len(cache.token_to_kv_pool_allocator.freed), 1)
|
self.assertEqual(len(cache.token_to_kv_pool_allocator.freed), 1)
|
||||||
freed = cache.token_to_kv_pool_allocator.freed[0]
|
freed = cache.token_to_kv_pool_allocator.freed[0]
|
||||||
|
|||||||
@@ -61,8 +61,6 @@ class _FakeReq:
|
|||||||
kv_allocated_len=allocated,
|
kv_allocated_len=allocated,
|
||||||
swa_evicted_seqlen=0,
|
swa_evicted_seqlen=0,
|
||||||
)
|
)
|
||||||
self.kv_committed_freed = False
|
|
||||||
self.kv_overallocated_freed = False
|
|
||||||
self.origin_input_ids = list(range(committed))
|
self.origin_input_ids = list(range(committed))
|
||||||
self.output_ids = []
|
self.output_ids = []
|
||||||
self.extra_key = None
|
self.extra_key = None
|
||||||
@@ -74,22 +72,10 @@ class _FakeReq:
|
|||||||
self.mamba_next_track_idx = None
|
self.mamba_next_track_idx = None
|
||||||
self.mamba_last_track_seqlen = None
|
self.mamba_last_track_seqlen = None
|
||||||
self.mamba_branching_seqlen = None
|
self.mamba_branching_seqlen = None
|
||||||
self.pop_overallocated_calls = 0
|
|
||||||
self.to_finish = None
|
self.to_finish = None
|
||||||
self.finished_reason = None
|
self.finished_reason = None
|
||||||
self.finished_len = None
|
self.finished_len = None
|
||||||
|
|
||||||
def pop_committed_kv_cache(self):
|
|
||||||
assert not self.kv_committed_freed
|
|
||||||
self.kv_committed_freed = True
|
|
||||||
return self.kv_committed_len
|
|
||||||
|
|
||||||
def pop_overallocated_kv_cache(self):
|
|
||||||
assert not self.kv_overallocated_freed
|
|
||||||
self.pop_overallocated_calls += 1
|
|
||||||
self.kv_overallocated_freed = True
|
|
||||||
return self.kv_committed_len, self.kv.kv_allocated_len
|
|
||||||
|
|
||||||
|
|
||||||
def test_preabort_detaches_session_and_preserves_slot():
|
def test_preabort_detaches_session_and_preserves_slot():
|
||||||
"""Pre-aborted req (to_finish set before match_prefix) is detached from
|
"""Pre-aborted req (to_finish set before match_prefix) is detached from
|
||||||
@@ -161,9 +147,6 @@ def test_first_mid_abort_nukes_ephemeral_slot():
|
|||||||
assert req_to_token_pool.free_slots == [0]
|
assert req_to_token_pool.free_slots == [0]
|
||||||
assert len(allocator.freed) == 1
|
assert len(allocator.freed) == 1
|
||||||
assert allocator.freed[0].tolist() == list(range(20))
|
assert allocator.freed[0].tolist() == list(range(20))
|
||||||
# Bookkeeping flags set.
|
|
||||||
assert req.kv_committed_freed is True
|
|
||||||
assert req.kv_overallocated_freed is True
|
|
||||||
|
|
||||||
|
|
||||||
def test_nth_mid_abort_nukes_session_slot():
|
def test_nth_mid_abort_nukes_session_slot():
|
||||||
@@ -200,9 +183,6 @@ def test_nth_mid_abort_nukes_session_slot():
|
|||||||
# Pool slot returned.
|
# Pool slot returned.
|
||||||
assert req_to_token_pool.free_slots == [0]
|
assert req_to_token_pool.free_slots == [0]
|
||||||
assert req.req_pool_idx is None
|
assert req.req_pool_idx is None
|
||||||
# Bookkeeping flags set.
|
|
||||||
assert req.kv_committed_freed is True
|
|
||||||
assert req.kv_overallocated_freed is True
|
|
||||||
|
|
||||||
|
|
||||||
# Shrink tests removed: streaming sessions are append-only after the
|
# Shrink tests removed: streaming sessions are append-only after the
|
||||||
|
|||||||
@@ -116,7 +116,6 @@ def _make_req(req_pool_idx, token_ids, cache_protected_len, tree):
|
|||||||
prefix_indices=torch.tensor([], dtype=torch.int64, device=tree.device),
|
prefix_indices=torch.tensor([], dtype=torch.int64, device=tree.device),
|
||||||
_kv_committed_len=len(token_ids),
|
_kv_committed_len=len(token_ids),
|
||||||
)
|
)
|
||||||
req.pop_committed_kv_cache = lambda: req._kv_committed_len
|
|
||||||
return req
|
return req
|
||||||
|
|
||||||
|
|
||||||
@@ -191,7 +190,9 @@ class TestSWAEvictionBoundary(unittest.TestCase):
|
|||||||
insert_len = seq_len // page_size * page_size
|
insert_len = seq_len // page_size * page_size
|
||||||
self.assertLess(req.kv.swa_evicted_seqlen, insert_len)
|
self.assertLess(req.kv.swa_evicted_seqlen, insert_len)
|
||||||
|
|
||||||
tree.cache_finished_req(req, is_insert=True)
|
tree.cache_finished_req(
|
||||||
|
req, is_insert=True, kv_len_to_handle=req._kv_committed_len
|
||||||
|
)
|
||||||
tree.sanity_check()
|
tree.sanity_check()
|
||||||
|
|
||||||
# -- Eviction formula: page_size == 1 --
|
# -- Eviction formula: page_size == 1 --
|
||||||
@@ -216,7 +217,9 @@ class TestSWAEvictionBoundary(unittest.TestCase):
|
|||||||
req.kv.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)
|
tree.cache_finished_req(
|
||||||
|
req, is_insert=True, kv_len_to_handle=req._kv_committed_len
|
||||||
|
)
|
||||||
tree.sanity_check()
|
tree.sanity_check()
|
||||||
|
|
||||||
# -- Eviction formula: no-op when seq too short --
|
# -- Eviction formula: no-op when seq too short --
|
||||||
@@ -253,7 +256,9 @@ class TestSWAEvictionBoundary(unittest.TestCase):
|
|||||||
kv1 = _swa_alloc(allocator, first_len)
|
kv1 = _swa_alloc(allocator, first_len)
|
||||||
pool.write((0, slice(0, first_len)), kv1)
|
pool.write((0, slice(0, first_len)), kv1)
|
||||||
req1 = _make_req(0, list(range(first_len)), 0, tree)
|
req1 = _make_req(0, list(range(first_len)), 0, tree)
|
||||||
tree.cache_finished_req(req1, is_insert=True)
|
tree.cache_finished_req(
|
||||||
|
req1, is_insert=True, kv_len_to_handle=req1._kv_committed_len
|
||||||
|
)
|
||||||
tree.sanity_check()
|
tree.sanity_check()
|
||||||
|
|
||||||
# Second request: 24 tokens, first 16 overlap with tree
|
# Second request: 24 tokens, first 16 overlap with tree
|
||||||
@@ -269,7 +274,9 @@ class TestSWAEvictionBoundary(unittest.TestCase):
|
|||||||
self.assertLessEqual(req2.kv.swa_evicted_seqlen, first_len)
|
self.assertLessEqual(req2.kv.swa_evicted_seqlen, first_len)
|
||||||
|
|
||||||
swa_evictable_before = tree.swa_evictable_size_
|
swa_evictable_before = tree.swa_evictable_size_
|
||||||
tree.cache_finished_req(req2, is_insert=True)
|
tree.cache_finished_req(
|
||||||
|
req2, is_insert=True, kv_len_to_handle=req2._kv_committed_len
|
||||||
|
)
|
||||||
|
|
||||||
# New tokens [16, 24) should all be non-tombstone
|
# New tokens [16, 24) should all be non-tombstone
|
||||||
new_tokens = second_len // page_size * page_size - first_len
|
new_tokens = second_len // page_size * page_size - first_len
|
||||||
@@ -300,7 +307,9 @@ class TestSWAEvictionBoundary(unittest.TestCase):
|
|||||||
self.assertGreater(req.kv.swa_evicted_seqlen, 0, "Should have some eviction")
|
self.assertGreater(req.kv.swa_evicted_seqlen, 0, "Should have some eviction")
|
||||||
self.assertLess(req.kv.swa_evicted_seqlen, insert_len, "Should be partial")
|
self.assertLess(req.kv.swa_evicted_seqlen, insert_len, "Should be partial")
|
||||||
|
|
||||||
tree.cache_finished_req(req, is_insert=True)
|
tree.cache_finished_req(
|
||||||
|
req, is_insert=True, kv_len_to_handle=req._kv_committed_len
|
||||||
|
)
|
||||||
|
|
||||||
non_tombstone = insert_len - req.kv.swa_evicted_seqlen
|
non_tombstone = insert_len - req.kv.swa_evicted_seqlen
|
||||||
self.assertEqual(tree.swa_evictable_size_, swa_evictable_before + non_tombstone)
|
self.assertEqual(tree.swa_evictable_size_, swa_evictable_before + non_tombstone)
|
||||||
@@ -338,7 +347,9 @@ class TestSWAEvictionBoundary(unittest.TestCase):
|
|||||||
req.kv.swa_evicted_seqlen = old_evicted
|
req.kv.swa_evicted_seqlen = old_evicted
|
||||||
swa_evictable_before = tree.swa_evictable_size_
|
swa_evictable_before = tree.swa_evictable_size_
|
||||||
|
|
||||||
tree.cache_finished_req(req, is_insert=True)
|
tree.cache_finished_req(
|
||||||
|
req, is_insert=True, kv_len_to_handle=req._kv_committed_len
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(tree.swa_evictable_size_, swa_evictable_before)
|
self.assertEqual(tree.swa_evictable_size_, swa_evictable_before)
|
||||||
|
|
||||||
@@ -367,7 +378,9 @@ class TestSWAEvictionBoundary(unittest.TestCase):
|
|||||||
insert_len = seq_len // page_size * page_size
|
insert_len = seq_len // page_size * page_size
|
||||||
self.assertLess(req.kv.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.cache_finished_req(
|
||||||
|
req, is_insert=True, kv_len_to_handle=req._kv_committed_len
|
||||||
|
)
|
||||||
tree.sanity_check()
|
tree.sanity_check()
|
||||||
|
|
||||||
# -- Integration: page_size=1 full flow --
|
# -- Integration: page_size=1 full flow --
|
||||||
@@ -391,7 +404,9 @@ class TestSWAEvictionBoundary(unittest.TestCase):
|
|||||||
req.kv.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)
|
tree.cache_finished_req(
|
||||||
|
req, is_insert=True, kv_len_to_handle=req._kv_committed_len
|
||||||
|
)
|
||||||
tree.sanity_check()
|
tree.sanity_check()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -38,9 +38,6 @@ class _DummyReq:
|
|||||||
self.swa_prefix_lock_released = False
|
self.swa_prefix_lock_released = False
|
||||||
self.kv = SimpleNamespace(swa_evicted_seqlen=0)
|
self.kv = SimpleNamespace(swa_evicted_seqlen=0)
|
||||||
|
|
||||||
def pop_committed_kv_cache(self):
|
|
||||||
return self._kv_committed_len
|
|
||||||
|
|
||||||
|
|
||||||
def _build_swa_tree(
|
def _build_swa_tree(
|
||||||
is_eagle: bool,
|
is_eagle: bool,
|
||||||
@@ -592,7 +589,9 @@ class TestSWA(unittest.TestCase):
|
|||||||
return original_insert(params)
|
return original_insert(params)
|
||||||
|
|
||||||
tree.insert = wrapped_insert
|
tree.insert = wrapped_insert
|
||||||
tree.cache_finished_req(req, is_insert=True)
|
tree.cache_finished_req(
|
||||||
|
req, is_insert=True, kv_len_to_handle=req._kv_committed_len
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(captured["prev_prefix_len"], req.cache_protected_len)
|
self.assertEqual(captured["prev_prefix_len"], req.cache_protected_len)
|
||||||
self.assertTrue(captured["is_bigram"])
|
self.assertTrue(captured["is_bigram"])
|
||||||
@@ -624,7 +623,9 @@ class TestSWA(unittest.TestCase):
|
|||||||
return original_free(indices)
|
return original_free(indices)
|
||||||
|
|
||||||
allocator.free = wrapped_free
|
allocator.free = wrapped_free
|
||||||
tree.cache_finished_req(req2, is_insert=False)
|
tree.cache_finished_req(
|
||||||
|
req2, is_insert=False, kv_len_to_handle=req2._kv_committed_len
|
||||||
|
)
|
||||||
|
|
||||||
# EAGLE + page_size=1 => page_aligned_len = committed_len - 1 = 5
|
# EAGLE + page_size=1 => page_aligned_len = committed_len - 1 = 5
|
||||||
# Expected frees:
|
# Expected frees:
|
||||||
|
|||||||
@@ -643,7 +643,6 @@ def bench_cache_finished(
|
|||||||
req.last_node = node
|
req.last_node = node
|
||||||
req.cache_protected_len = matched_len
|
req.cache_protected_len = matched_len
|
||||||
req.kv_committed_len = len(seq)
|
req.kv_committed_len = len(seq)
|
||||||
req.kv_committed_freed = False
|
|
||||||
if hasattr(lr, "swa_uuid_for_lock"):
|
if hasattr(lr, "swa_uuid_for_lock"):
|
||||||
req.swa_uuid_for_lock = 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
|
env.rtp.req_to_token[req.req_pool_idx, : len(kv_indices)] = kv_indices
|
||||||
@@ -656,7 +655,9 @@ def bench_cache_finished(
|
|||||||
return bench_api(
|
return bench_api(
|
||||||
"cache_finished",
|
"cache_finished",
|
||||||
lambda: req_items,
|
lambda: req_items,
|
||||||
lambda req: env.tree.cache_finished_req(req, is_insert=True),
|
lambda req: env.tree.cache_finished_req(
|
||||||
|
req, is_insert=True, kv_len_to_handle=req.kv_committed_len
|
||||||
|
),
|
||||||
len(req_items) - warmup,
|
len(req_items) - warmup,
|
||||||
env.avg_tokens,
|
env.avg_tokens,
|
||||||
warmup,
|
warmup,
|
||||||
|
|||||||
@@ -854,7 +854,9 @@ class UnifiedRadixCacheSuite:
|
|||||||
if self.cfg.has_mamba:
|
if self.cfg.has_mamba:
|
||||||
req.mamba_last_track_seqlen = kv_len
|
req.mamba_last_track_seqlen = kv_len
|
||||||
|
|
||||||
cache.cache_finished_req(req, is_insert=True)
|
cache.cache_finished_req(
|
||||||
|
req, is_insert=True, kv_len_to_handle=req.effective_kv_committed_len()
|
||||||
|
)
|
||||||
|
|
||||||
all_ids = input_ids + output_ids
|
all_ids = input_ids + output_ids
|
||||||
aligned_len = (len(all_ids) // ps) * ps
|
aligned_len = (len(all_ids) // ps) * ps
|
||||||
@@ -893,8 +895,10 @@ class UnifiedRadixCacheSuite:
|
|||||||
get_server_args().strip_thinking_cache = True
|
get_server_args().strip_thinking_cache = True
|
||||||
try:
|
try:
|
||||||
avail_before = allocator.available_size()
|
avail_before = allocator.available_size()
|
||||||
cache.cache_finished_req(req, is_insert=True)
|
cache.cache_finished_req(
|
||||||
start_p, end_p = req.pop_overallocated_kv_cache()
|
req, is_insert=True, kv_len_to_handle=req.effective_kv_committed_len()
|
||||||
|
)
|
||||||
|
start_p, end_p = req.effective_kv_committed_len(), req.kv.kv_allocated_len
|
||||||
finally:
|
finally:
|
||||||
get_server_args().strip_thinking_cache = False
|
get_server_args().strip_thinking_cache = False
|
||||||
if ps > 1:
|
if ps > 1:
|
||||||
@@ -936,7 +940,9 @@ class UnifiedRadixCacheSuite:
|
|||||||
)
|
)
|
||||||
|
|
||||||
avail_before = allocator.available_size()
|
avail_before = allocator.available_size()
|
||||||
cache.cache_finished_req(req, is_insert=False)
|
cache.cache_finished_req(
|
||||||
|
req, is_insert=False, kv_len_to_handle=req.effective_kv_committed_len()
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(allocator.available_size(), avail_before + kv_len)
|
self.assertEqual(allocator.available_size(), avail_before + kv_len)
|
||||||
m = cache.match_prefix(MatchPrefixParams(key=RadixKey(array("q", tokens))))
|
m = cache.match_prefix(MatchPrefixParams(key=RadixKey(array("q", tokens))))
|
||||||
@@ -1108,7 +1114,9 @@ class UnifiedRadixCacheSuite:
|
|||||||
req.mamba_last_track_seqlen = kv_len
|
req.mamba_last_track_seqlen = kv_len
|
||||||
|
|
||||||
avail_before = allocator.available_size()
|
avail_before = allocator.available_size()
|
||||||
cache.cache_finished_req(req, is_insert=True)
|
cache.cache_finished_req(
|
||||||
|
req, is_insert=True, kv_len_to_handle=req.effective_kv_committed_len()
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(allocator.available_size(), avail_before + tail_extra)
|
self.assertEqual(allocator.available_size(), avail_before + tail_extra)
|
||||||
aligned = input_ids[: (len(input_ids) // ps) * ps]
|
aligned = input_ids[: (len(input_ids) // ps) * ps]
|
||||||
|
|||||||
Reference in New Issue
Block a user