Unify memory management across (overlap, non-overlap) x (page>=1) x (spec, non-spec, spec v2) x (retract, finished) (#12224)

This commit is contained in:
Liangsheng Yin
2025-11-11 02:56:22 +08:00
committed by GitHub
parent 838bcb0d93
commit 665416f6dd
24 changed files with 193 additions and 156 deletions
+50 -1
View File
@@ -66,6 +66,7 @@ from sglang.srt.mem_cache.common import (
alloc_for_decode,
alloc_for_extend,
evict_from_tree_cache,
release_kv_cache,
)
from sglang.srt.mem_cache.mamba_radix_cache import MambaRadixCache
from sglang.srt.mem_cache.memory_pool import ReqToTokenPool
@@ -479,6 +480,12 @@ class Req:
self.session_id = session_id
self.input_embeds = input_embeds
# For req-level memory management
self.kv_committed_len = 0
self.kv_allocated_len = 0
self.kv_committed_freed = False
self.kv_overallocated_freed = False
# for corss-endoder model
self.token_type_ids = token_type_ids
@@ -693,6 +700,35 @@ class Req:
return self.output_ids[: self.finished_len]
return self.output_ids
def pop_committed_kv_cache(self) -> int:
"""Return the length of committed KV cache and mark them as freed."""
# NOTE: This function is called exactly once after the request is finished.
global_server_args = get_global_server_args()
topk = global_server_args.speculative_eagle_topk
enable_kv_committed_len = topk is None or topk == 1
if enable_kv_committed_len:
assert (
not self.kv_committed_freed
), f"Committed KV cache already freed ({self.kv_committed_len=})"
self.kv_committed_freed = True
return self.kv_committed_len
else:
return len(self.origin_input_ids) + max(len(self.output_ids) - 1, 0)
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_allocated_len=}"
self.kv_overallocated_freed = True
return self.kv_committed_len, self.kv_allocated_len
def add_latency(self, stage: RequestStage):
if self.metrics_collector is None:
return
@@ -918,6 +954,10 @@ class Req:
self.is_chunked = 0
self.mamba_pool_idx = None
self.already_computed = 0
self.kv_allocated_len = 0
self.kv_committed_len = 0
self.kv_committed_freed = False
self.kv_overallocated_freed = False
def offload_kv_cache(self, req_to_token_pool, token_to_kv_pool_allocator):
token_indices = req_to_token_pool.req_to_token[
@@ -1262,6 +1302,10 @@ class ScheduleBatch(ScheduleBatchDisaggregationDecodeMixin):
req.req_pool_idx = req_pool_indices[i]
assert seq_len - pre_len == req.extend_input_len
# update req-level memory management fields
req.kv_committed_len = seq_len
req.kv_allocated_len = seq_len
# If input_embeds are available, store them
if req.input_embeds is not None:
# If req.input_embeds is already a list, append its content directly
@@ -1536,7 +1580,7 @@ class ScheduleBatch(ScheduleBatchDisaggregationDecodeMixin):
self.req_to_token_pool, self.token_to_kv_pool_allocator
)
# TODO (csy): for preempted requests, we may want to insert into the tree
self.tree_cache.cache_finished_req(req, is_insert=False)
release_kv_cache(req, self.tree_cache, is_insert=False)
# NOTE(lsyin): we should use the newly evictable memory instantly.
num_tokens = remaing_req_count * envs.SGLANG_RETRACT_DECODE_STEPS.get()
evict_from_tree_cache(self.tree_cache, num_tokens)
@@ -1614,6 +1658,11 @@ class ScheduleBatch(ScheduleBatchDisaggregationDecodeMixin):
# Allocate memory
self.out_cache_loc = alloc_for_decode(self, token_per_req=1)
# Update req-level memory management fields
for req in self.reqs:
req.kv_committed_len += 1
req.kv_allocated_len += 1
# Update seq_lens after allocation
if self.enable_overlap:
# Do not use in-place operations in the overlap mode