Fix SWA eviction boundary and page-align chunked prefill (#22470)
This commit is contained in:
@@ -2515,8 +2515,14 @@ class ScheduleBatch(ScheduleBatchDisaggregationDecodeMixin):
|
|||||||
), "cache_protected_len must be page aligned"
|
), "cache_protected_len must be page aligned"
|
||||||
req.swa_evicted_seqlen = max(req.swa_evicted_seqlen, req.cache_protected_len)
|
req.swa_evicted_seqlen = max(req.swa_evicted_seqlen, req.cache_protected_len)
|
||||||
|
|
||||||
|
# Subtract an extra page_size so the eviction frontier never reaches the
|
||||||
|
# radix tree insert boundary (page_floor(seq_len)). This keeps at least one
|
||||||
|
# page of non-evicted SWA KV for the tree to store as a non-tombstone node,
|
||||||
|
# preserving cache reuse in multi-turn scenarios.
|
||||||
|
# See also: _insert_helper case 3 in swa_radix_cache.py (defensive counterpart).
|
||||||
new_swa_evicted_seqlen = max(
|
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:
|
if self.tree_cache.page_size > 1:
|
||||||
|
|||||||
@@ -1013,6 +1013,26 @@ class SWARadixCache(BasePrefixCache):
|
|||||||
child_key = self.get_child_key_fn(key)
|
child_key = self.get_child_key_fn(key)
|
||||||
|
|
||||||
if len(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).
|
||||||
|
# Note: the -page_size fix in _evict_swa prevents this case from
|
||||||
|
# occurring in normal operation. This check is a defensive guard
|
||||||
|
# against unexpected eviction states from other code paths.
|
||||||
|
if swa_evicted_seqlen == total_prefix_length + len(key):
|
||||||
|
self.token_to_kv_pool_allocator.free(value)
|
||||||
|
return total_prefix_length
|
||||||
|
|
||||||
if (
|
if (
|
||||||
swa_evicted_seqlen > total_prefix_length
|
swa_evicted_seqlen > total_prefix_length
|
||||||
and swa_evicted_seqlen < total_prefix_length + len(key)
|
and swa_evicted_seqlen < total_prefix_length + len(key)
|
||||||
|
|||||||
@@ -0,0 +1,392 @@
|
|||||||
|
"""Unit tests for SWA eviction boundary fixes.
|
||||||
|
|
||||||
|
Bug: when page_size > sliding_window_size, _evict_swa could advance the
|
||||||
|
eviction frontier to exactly page_floor(seq_len), making all tokens being
|
||||||
|
inserted into the radix tree fully evicted (case 3). _insert_helper had no
|
||||||
|
handling for this, creating an incorrect non-tombstone node that caused
|
||||||
|
inflated swa_evictable_size_, negative usage, and potential double-free.
|
||||||
|
|
||||||
|
Two-sided fix:
|
||||||
|
1. _evict_swa subtracts extra page_size (preventive).
|
||||||
|
2. _insert_helper early-returns on case 3 (defensive).
|
||||||
|
|
||||||
|
Tests use real tree/allocator/pool with mock Req/ScheduleBatch wrappers.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
import torch
|
||||||
|
|
||||||
|
from sglang.srt.managers.schedule_batch import ScheduleBatch
|
||||||
|
from sglang.srt.mem_cache.cache_init_params import CacheInitParams
|
||||||
|
from sglang.srt.mem_cache.memory_pool import ReqToTokenPool
|
||||||
|
from sglang.srt.mem_cache.swa_memory_pool import SWAKVPool, SWATokenToKVPoolAllocator
|
||||||
|
from sglang.srt.mem_cache.swa_radix_cache import SWARadixCache
|
||||||
|
from sglang.srt.utils import get_device
|
||||||
|
from sglang.test.ci.ci_register import register_amd_ci, register_cuda_ci
|
||||||
|
|
||||||
|
register_cuda_ci(est_time=8, suite="stage-b-test-1-gpu-large")
|
||||||
|
register_amd_ci(est_time=10, suite="stage-b-test-1-gpu-small-amd")
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Infrastructure helpers (shared setup, not logic)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def _swa_alloc(allocator, need_size):
|
||||||
|
"""Allocate from SWA allocator for any page_size.
|
||||||
|
|
||||||
|
SWATokenToKVPoolAllocator.alloc() asserts page_size == 1. For page_size > 1,
|
||||||
|
allocate from the underlying paged allocators directly and set up the mapping.
|
||||||
|
"""
|
||||||
|
if allocator.page_size == 1:
|
||||||
|
return allocator.alloc(need_size)
|
||||||
|
|
||||||
|
if need_size > allocator.full_attn_allocator.available_size():
|
||||||
|
return None
|
||||||
|
if need_size > allocator.swa_attn_allocator.available_size():
|
||||||
|
return None
|
||||||
|
|
||||||
|
full_indices = allocator.full_attn_allocator.alloc(need_size)
|
||||||
|
swa_indices = allocator.swa_attn_allocator.alloc(need_size)
|
||||||
|
assert full_indices is not None and swa_indices is not None
|
||||||
|
allocator.full_to_swa_index_mapping[full_indices] = swa_indices
|
||||||
|
return full_indices
|
||||||
|
|
||||||
|
|
||||||
|
def _build_swa_tree(page_size, sliding_window_size, kv_size=1024, kv_size_swa=512):
|
||||||
|
head_num, head_dim, num_layers, global_interval = 8, 128, 24, 4
|
||||||
|
dtype = torch.bfloat16
|
||||||
|
device = get_device()
|
||||||
|
full_ids = list(range(0, num_layers, global_interval))
|
||||||
|
swa_ids = [i for i in range(num_layers) if i not in set(full_ids)]
|
||||||
|
|
||||||
|
pool = ReqToTokenPool(
|
||||||
|
size=8, max_context_len=2048, device=device, enable_memory_saver=False
|
||||||
|
)
|
||||||
|
kv_pool = SWAKVPool(
|
||||||
|
size=kv_size,
|
||||||
|
size_swa=kv_size_swa,
|
||||||
|
page_size=page_size,
|
||||||
|
dtype=dtype,
|
||||||
|
head_num=head_num,
|
||||||
|
head_dim=head_dim,
|
||||||
|
swa_attention_layer_ids=swa_ids,
|
||||||
|
full_attention_layer_ids=full_ids,
|
||||||
|
enable_kvcache_transpose=False,
|
||||||
|
device=device,
|
||||||
|
)
|
||||||
|
allocator = SWATokenToKVPoolAllocator(
|
||||||
|
size=kv_size,
|
||||||
|
size_swa=kv_size_swa,
|
||||||
|
page_size=page_size,
|
||||||
|
dtype=dtype,
|
||||||
|
device=device,
|
||||||
|
kvcache=kv_pool,
|
||||||
|
need_sort=False,
|
||||||
|
)
|
||||||
|
tree = SWARadixCache(
|
||||||
|
params=CacheInitParams(
|
||||||
|
req_to_token_pool=pool,
|
||||||
|
token_to_kv_pool_allocator=allocator,
|
||||||
|
page_size=page_size,
|
||||||
|
disable=False,
|
||||||
|
is_eagle=False,
|
||||||
|
sliding_window_size=sliding_window_size,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return tree, allocator, pool
|
||||||
|
|
||||||
|
|
||||||
|
def _make_req(req_pool_idx, token_ids, cache_protected_len, tree):
|
||||||
|
"""Mock Req with fields needed by _evict_swa and cache_finished_req."""
|
||||||
|
req = SimpleNamespace(
|
||||||
|
req_pool_idx=req_pool_idx,
|
||||||
|
origin_input_ids=token_ids,
|
||||||
|
output_ids=[],
|
||||||
|
cache_protected_len=cache_protected_len,
|
||||||
|
swa_evicted_seqlen=0,
|
||||||
|
extra_key=None,
|
||||||
|
last_node=tree.root_node,
|
||||||
|
swa_uuid_for_lock=None,
|
||||||
|
prefix_indices=torch.tensor([], dtype=torch.int64, device=tree.device),
|
||||||
|
_kv_committed_len=len(token_ids),
|
||||||
|
)
|
||||||
|
req.pop_committed_kv_cache = lambda: req._kv_committed_len
|
||||||
|
return req
|
||||||
|
|
||||||
|
|
||||||
|
def _make_batch(tree, allocator, pool):
|
||||||
|
"""Mock ScheduleBatch with fields needed by _evict_swa."""
|
||||||
|
return SimpleNamespace(
|
||||||
|
tree_cache=tree,
|
||||||
|
req_to_token_pool=pool,
|
||||||
|
token_to_kv_pool_allocator=allocator,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Tests
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
class TestSWAEvictionBoundary(unittest.TestCase):
|
||||||
|
|
||||||
|
# -- Eviction formula: page_size > window --
|
||||||
|
|
||||||
|
def test_formula_page_gt_window_sweep(self):
|
||||||
|
"""Sweep page_size > window combinations. The -page_size fix must
|
||||||
|
prevent eviction from reaching page_floor(seq_len)."""
|
||||||
|
for page_size in [4, 8, 16, 32, 64, 128, 256]:
|
||||||
|
for window in [1, 2, 4, 8]:
|
||||||
|
if page_size <= window:
|
||||||
|
continue
|
||||||
|
tree, allocator, pool = _build_swa_tree(
|
||||||
|
page_size=page_size,
|
||||||
|
sliding_window_size=window,
|
||||||
|
kv_size=max(4096, page_size * 20),
|
||||||
|
kv_size_swa=max(2048, page_size * 10),
|
||||||
|
)
|
||||||
|
for seq_len in range(page_size + 1, page_size * 5):
|
||||||
|
alloc_size = (seq_len + page_size - 1) // page_size * page_size
|
||||||
|
kv = _swa_alloc(allocator, alloc_size)
|
||||||
|
if kv is None:
|
||||||
|
break
|
||||||
|
pool.write((0, slice(0, alloc_size)), kv)
|
||||||
|
|
||||||
|
req = _make_req(0, list(range(seq_len)), 0, tree)
|
||||||
|
batch = _make_batch(tree, allocator, pool)
|
||||||
|
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"page={page_size}, win={window}, seq={seq_len}",
|
||||||
|
)
|
||||||
|
allocator.free(kv)
|
||||||
|
|
||||||
|
# -- Eviction formula: page_size <= window --
|
||||||
|
|
||||||
|
def test_formula_page_leq_window(self):
|
||||||
|
"""page_size <= window: -page_size fix causes no regression."""
|
||||||
|
page_size, window = 4, 8
|
||||||
|
tree, allocator, pool = _build_swa_tree(
|
||||||
|
page_size=page_size, sliding_window_size=window
|
||||||
|
)
|
||||||
|
|
||||||
|
for seq_len in [13, 17, 25, 33]:
|
||||||
|
alloc_size = (seq_len + page_size - 1) // page_size * page_size
|
||||||
|
kv = _swa_alloc(allocator, alloc_size)
|
||||||
|
pool.write((0, slice(0, alloc_size)), kv)
|
||||||
|
|
||||||
|
req = _make_req(0, list(range(seq_len)), 0, tree)
|
||||||
|
batch = _make_batch(tree, allocator, pool)
|
||||||
|
ScheduleBatch._evict_swa(batch, req, seq_len - 1)
|
||||||
|
|
||||||
|
insert_len = seq_len // page_size * page_size
|
||||||
|
self.assertLess(req.swa_evicted_seqlen, insert_len)
|
||||||
|
|
||||||
|
tree.cache_finished_req(req, is_insert=True)
|
||||||
|
tree.sanity_check()
|
||||||
|
|
||||||
|
# -- Eviction formula: page_size == 1 --
|
||||||
|
|
||||||
|
def test_formula_page_size_1(self):
|
||||||
|
"""page_size=1: no floor alignment, -1 just means one less token evicted."""
|
||||||
|
page_size, window = 1, 4
|
||||||
|
tree, allocator, pool = _build_swa_tree(
|
||||||
|
page_size=page_size, sliding_window_size=window
|
||||||
|
)
|
||||||
|
|
||||||
|
for seq_len in range(window + 2, 30):
|
||||||
|
kv = _swa_alloc(allocator, seq_len)
|
||||||
|
pool.write((0, slice(0, seq_len)), kv)
|
||||||
|
|
||||||
|
req = _make_req(0, list(range(seq_len)), 0, tree)
|
||||||
|
batch = _make_batch(tree, allocator, pool)
|
||||||
|
ScheduleBatch._evict_swa(batch, req, seq_len - 1)
|
||||||
|
|
||||||
|
self.assertLess(req.swa_evicted_seqlen, seq_len)
|
||||||
|
self.assertEqual(req.swa_evicted_seqlen, max(0, seq_len - 1 - window - 1))
|
||||||
|
|
||||||
|
tree.cache_finished_req(req, is_insert=True)
|
||||||
|
tree.sanity_check()
|
||||||
|
|
||||||
|
# -- Eviction formula: no-op when seq too short --
|
||||||
|
|
||||||
|
def test_formula_noop_short_sequence(self):
|
||||||
|
"""pre_len - window - page_size < 0: eviction stays at 0."""
|
||||||
|
page_size, window = 8, 4
|
||||||
|
tree, allocator, pool = _build_swa_tree(
|
||||||
|
page_size=page_size, sliding_window_size=window
|
||||||
|
)
|
||||||
|
|
||||||
|
seq_len = page_size + window - 2 # = 10, formula gives 10-1-4-8 = -3
|
||||||
|
alloc_size = (seq_len + page_size - 1) // page_size * page_size
|
||||||
|
kv = _swa_alloc(allocator, alloc_size)
|
||||||
|
pool.write((0, slice(0, alloc_size)), kv)
|
||||||
|
|
||||||
|
req = _make_req(0, list(range(seq_len)), 0, tree)
|
||||||
|
batch = _make_batch(tree, allocator, pool)
|
||||||
|
ScheduleBatch._evict_swa(batch, req, seq_len - 1)
|
||||||
|
|
||||||
|
self.assertEqual(req.swa_evicted_seqlen, 0)
|
||||||
|
|
||||||
|
# -- Insert case 1: swa_evicted <= total_prefix_length --
|
||||||
|
|
||||||
|
def test_insert_case1_evicted_within_matched(self):
|
||||||
|
"""Eviction within matched region. New tokens all non-tombstone."""
|
||||||
|
page_size, window = 8, 2
|
||||||
|
tree, allocator, pool = _build_swa_tree(
|
||||||
|
page_size=page_size, sliding_window_size=window
|
||||||
|
)
|
||||||
|
|
||||||
|
# First request: populate tree with 16 tokens (2 pages)
|
||||||
|
first_len = page_size * 2
|
||||||
|
kv1 = _swa_alloc(allocator, first_len)
|
||||||
|
pool.write((0, slice(0, first_len)), kv1)
|
||||||
|
req1 = _make_req(0, list(range(first_len)), 0, tree)
|
||||||
|
tree.cache_finished_req(req1, is_insert=True)
|
||||||
|
tree.sanity_check()
|
||||||
|
|
||||||
|
# Second request: 24 tokens, first 16 overlap with tree
|
||||||
|
second_len = page_size * 3
|
||||||
|
kv2 = _swa_alloc(allocator, second_len)
|
||||||
|
pool.write((1, slice(0, second_len)), kv2)
|
||||||
|
|
||||||
|
req2 = _make_req(1, list(range(second_len)), 0, tree)
|
||||||
|
batch = _make_batch(tree, allocator, pool)
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
swa_evictable_before = tree.swa_evictable_size_
|
||||||
|
tree.cache_finished_req(req2, is_insert=True)
|
||||||
|
|
||||||
|
# New tokens [16, 24) should all be non-tombstone
|
||||||
|
new_tokens = second_len // page_size * page_size - first_len
|
||||||
|
self.assertEqual(tree.swa_evictable_size_, swa_evictable_before + new_tokens)
|
||||||
|
tree.sanity_check()
|
||||||
|
|
||||||
|
# -- Insert case 2: total_prefix_length < swa_evicted < total_length --
|
||||||
|
|
||||||
|
def test_insert_case2_partial_tombstone(self):
|
||||||
|
"""Partial eviction: some tombstone, some non-tombstone."""
|
||||||
|
page_size, window = 8, 2
|
||||||
|
tree, allocator, pool = _build_swa_tree(
|
||||||
|
page_size=page_size, sliding_window_size=window
|
||||||
|
)
|
||||||
|
|
||||||
|
# seq_len=25: insert_length=24, evicted should be 8 (1 page)
|
||||||
|
seq_len = page_size * 3 + 1
|
||||||
|
alloc_size = (seq_len + page_size - 1) // page_size * page_size
|
||||||
|
kv = _swa_alloc(allocator, alloc_size)
|
||||||
|
pool.write((0, slice(0, alloc_size)), kv)
|
||||||
|
|
||||||
|
req = _make_req(0, list(range(seq_len)), 0, tree)
|
||||||
|
batch = _make_batch(tree, allocator, pool)
|
||||||
|
swa_evictable_before = tree.swa_evictable_size_
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
tree.cache_finished_req(req, is_insert=True)
|
||||||
|
|
||||||
|
non_tombstone = insert_len - req.swa_evicted_seqlen
|
||||||
|
self.assertEqual(tree.swa_evictable_size_, swa_evictable_before + non_tombstone)
|
||||||
|
self.assertGreater(tree.full_evictable_size_, 0)
|
||||||
|
tree.sanity_check()
|
||||||
|
|
||||||
|
# -- Insert case 3: swa_evicted == total_length (defensive) --
|
||||||
|
|
||||||
|
def test_insert_case3_defensive_early_return(self):
|
||||||
|
"""Simulate OLD formula to trigger case 3. Defensive early return
|
||||||
|
must prevent non-tombstone node creation."""
|
||||||
|
page_size, window = 8, 2
|
||||||
|
tree, allocator, pool = _build_swa_tree(
|
||||||
|
page_size=page_size, sliding_window_size=window
|
||||||
|
)
|
||||||
|
|
||||||
|
# seq_len=11: OLD formula -> evicted=8 == insert_length=8
|
||||||
|
seq_len = page_size + window + 1
|
||||||
|
alloc_size = (seq_len + page_size - 1) // page_size * page_size
|
||||||
|
kv = _swa_alloc(allocator, alloc_size)
|
||||||
|
pool.write((0, slice(0, alloc_size)), kv)
|
||||||
|
|
||||||
|
# OLD formula (without -page_size)
|
||||||
|
pre_len = seq_len - 1
|
||||||
|
old_evicted = max(0, (pre_len - window) // page_size * page_size)
|
||||||
|
insert_len = seq_len // page_size * page_size
|
||||||
|
self.assertEqual(
|
||||||
|
old_evicted, insert_len, "Precondition: old formula hits boundary"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Manually free SWA as _evict_swa would
|
||||||
|
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
|
||||||
|
swa_evictable_before = tree.swa_evictable_size_
|
||||||
|
|
||||||
|
tree.cache_finished_req(req, is_insert=True)
|
||||||
|
|
||||||
|
self.assertEqual(tree.swa_evictable_size_, swa_evictable_before)
|
||||||
|
|
||||||
|
# -- Integration: multiple decode turns --
|
||||||
|
|
||||||
|
def test_multiple_decodes(self):
|
||||||
|
"""Multiple decode turns with page_size > window. No over-eviction,
|
||||||
|
tree stays consistent throughout."""
|
||||||
|
page_size, window = 8, 2
|
||||||
|
tree, allocator, pool = _build_swa_tree(
|
||||||
|
page_size=page_size, sliding_window_size=window
|
||||||
|
)
|
||||||
|
|
||||||
|
for turn in range(4):
|
||||||
|
seq_len = page_size * (turn + 2) + 1
|
||||||
|
idx = turn % pool.size
|
||||||
|
alloc_size = (seq_len + page_size - 1) // page_size * page_size
|
||||||
|
kv = _swa_alloc(allocator, alloc_size)
|
||||||
|
assert kv is not None, f"Alloc failed at turn {turn}"
|
||||||
|
pool.write((idx, slice(0, alloc_size)), kv)
|
||||||
|
|
||||||
|
req = _make_req(idx, list(range(seq_len)), 0, tree)
|
||||||
|
batch = _make_batch(tree, allocator, pool)
|
||||||
|
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}")
|
||||||
|
|
||||||
|
tree.cache_finished_req(req, is_insert=True)
|
||||||
|
tree.sanity_check()
|
||||||
|
|
||||||
|
# -- Integration: page_size=1 full flow --
|
||||||
|
|
||||||
|
def test_page_size_1_full_flow(self):
|
||||||
|
"""End-to-end with page_size=1. Fix is near no-op."""
|
||||||
|
page_size, window = 1, 4
|
||||||
|
tree, allocator, pool = _build_swa_tree(
|
||||||
|
page_size=page_size, sliding_window_size=window
|
||||||
|
)
|
||||||
|
|
||||||
|
for seq_len in [10, 20, 30]:
|
||||||
|
kv = _swa_alloc(allocator, seq_len)
|
||||||
|
pool.write((0, slice(0, seq_len)), kv)
|
||||||
|
|
||||||
|
req = _make_req(0, list(range(seq_len)), 0, tree)
|
||||||
|
batch = _make_batch(tree, allocator, pool)
|
||||||
|
ScheduleBatch._evict_swa(batch, req, seq_len - 1)
|
||||||
|
|
||||||
|
self.assertEqual(req.swa_evicted_seqlen, max(0, seq_len - 1 - window - 1))
|
||||||
|
|
||||||
|
tree.cache_finished_req(req, is_insert=True)
|
||||||
|
tree.sanity_check()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user