Skip to content
6 changes: 5 additions & 1 deletion python/sglang/srt/managers/schedule_batch.py
Original file line number Diff line number Diff line change
Expand Up @@ -2515,8 +2515,12 @@ def _evict_swa(self, req: Req, pre_len: int):
), "cache_protected_len must be page aligned"
req.swa_evicted_seqlen = max(req.swa_evicted_seqlen, req.cache_protected_len)

# Subtract an extra page_size to prevent over-eviction when page_size > sliding_window_size.
# Without this, the eviction frontier can reach the insert boundary (page_floor(seq_len)),
# causing _insert_helper to encounter a fully-evicted key with no valid non-tombstone tail.
new_swa_evicted_seqlen = max(
req.swa_evicted_seqlen, pre_len - sliding_window_size
req.swa_evicted_seqlen,
pre_len - sliding_window_size - self.tree_cache.page_size,
)

if self.tree_cache.page_size > 1:
Expand Down
23 changes: 20 additions & 3 deletions python/sglang/srt/managers/schedule_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -751,9 +751,15 @@ def add_one_req(
if req.sampling_params.ignore_eos and getattr(self.tree_cache, "disable", True):
return self.add_one_req_ignore_eos(req)

total_tokens = req.extend_input_len + min(
max(req.sampling_params.max_new_tokens - len(req.output_ids), 0),
CLIP_MAX_NEW_TOKENS,
# Reserve an extra page_size for page-alignment overhead: the paged allocator
# rounds up to page boundaries, consuming up to one extra page vs logical count.
total_tokens = (
req.extend_input_len
+ min(
max(req.sampling_params.max_new_tokens - len(req.output_ids), 0),
CLIP_MAX_NEW_TOKENS,
)
+ self.page_size
)

# adjusting the input_tokens based on host_hit_length and page_size
Expand Down Expand Up @@ -823,6 +829,17 @@ def add_one_req(
# When truncation align size is set, we want to assert that the prefill prefix length is multiple of truncation align size
# A typical use case is when deterministic inference is enabled with flashinfer attention backend,
# we need the prefill prefix length to be multiple of attention split size
# Align (prefix_len + trunc_len) to page boundary so that each
# chunk inserts page-aligned tokens into the radix tree.
# Without this, non-aligned chunk boundaries cause page
# calculation drift across successive chunks.
now_input_len = trunc_len + len(req.prefix_indices)
now_input_len = now_input_len // self.page_size * self.page_size
trunc_len = now_input_len - len(req.prefix_indices)

if trunc_len <= 0:
return AddReqResult.OTHER

if truncation_align_size is not None:
if trunc_len < truncation_align_size:
return AddReqResult.OTHER
Expand Down
17 changes: 17 additions & 0 deletions python/sglang/srt/mem_cache/swa_radix_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -1013,6 +1013,23 @@ def _insert_helper(
child_key = self.get_child_key_fn(key)

if len(key):
# Layout: |--- total_prefix_length ---|--- len(key) ---|
# ^ ^ ^
# 0 total_prefix_length total_length
#
# Cases based on swa_evicted_seqlen position:
# 1. swa_evicted_seqlen <= total_prefix_length:
# Already handled in the while loop above. All of len(key) is non-tombstone.
# 2. total_prefix_length < swa_evicted_seqlen < total_length:
# Split: [total_prefix_length, swa_evicted_seqlen) as tombstone,
# [swa_evicted_seqlen, total_length) as non-tombstone.
# 3. swa_evicted_seqlen == total_length:
# All remaining tokens are evicted. Free value and return without
# creating a node (leaf nodes must not be tombstone).
if swa_evicted_seqlen == total_prefix_length + len(key):
self.token_to_kv_pool_allocator.free(value)
return total_prefix_length

if (
swa_evicted_seqlen > total_prefix_length
and swa_evicted_seqlen < total_prefix_length + len(key)
Expand Down
Loading
Loading