[Auto Sync] Update evict_policy.py, radix_cache.py (20251120) (#13669)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: cctry <shiyang@x.ai>
This commit is contained in:
Lianmin Zheng
2025-11-21 22:07:36 -08:00
committed by GitHub
co-authored by github-actions[bot] cctry
parent 94ae816f29
commit b41afa3754
4 changed files with 58 additions and 13 deletions
@@ -36,3 +36,11 @@ class MRUStrategy(EvictionStrategy):
class FILOStrategy(EvictionStrategy): class FILOStrategy(EvictionStrategy):
def get_priority(self, node: "TreeNode") -> float: def get_priority(self, node: "TreeNode") -> float:
return -node.creation_time return -node.creation_time
class PriorityStrategy(EvictionStrategy):
"""Priority-aware eviction: lower priority values evicted first, then LRU within same priority."""
def get_priority(self, node: "TreeNode") -> Tuple[int, float]:
# Return (priority, last_access_time) so lower priority nodes are evicted first
return (node.priority, node.last_access_time)
+15 -4
View File
@@ -786,7 +786,7 @@ class HiRadixCache(RadixCache):
child_key = self.get_child_key_fn(key) child_key = self.get_child_key_fn(key)
if len(key): if len(key):
new_node = TreeNode() new_node = TreeNode(priority=node.priority)
new_node.parent = node new_node.parent = node
new_node.key = key new_node.key = key
new_node.value = None new_node.value = None
@@ -823,7 +823,7 @@ class HiRadixCache(RadixCache):
def _split_node(self, key: RadixKey, child: TreeNode, split_len: int): def _split_node(self, key: RadixKey, child: TreeNode, split_len: int):
# child node split into new_node -> child # child node split into new_node -> child
new_node = TreeNode() new_node = TreeNode(priority=child.priority)
new_node.children = {self.get_child_key_fn(key[split_len:]): child} new_node.children = {self.get_child_key_fn(key[split_len:]): child}
new_node.parent = child.parent new_node.parent = child.parent
new_node.lock_ref = child.lock_ref new_node.lock_ref = child.lock_ref
@@ -848,7 +848,15 @@ class HiRadixCache(RadixCache):
new_node.parent.children[self.get_child_key_fn(key)] = new_node new_node.parent.children[self.get_child_key_fn(key)] = new_node
return new_node return new_node
def insert(self, key: RadixKey, value=None, chunked=False): def insert(
self,
key: RadixKey,
value=None,
chunked: bool = False,
priority: int | None = None,
):
if priority is None:
priority = 0
key.token_ids = self.key_convert_fn(key.token_ids) key.token_ids = self.key_convert_fn(key.token_ids)
if len(key) == 0: if len(key) == 0:
@@ -865,6 +873,7 @@ class HiRadixCache(RadixCache):
while len(key) > 0 and child_key in node.children.keys(): while len(key) > 0 and child_key in node.children.keys():
node = node.children[child_key] node = node.children[child_key]
node.last_access_time = time.monotonic() node.last_access_time = time.monotonic()
node.priority = max(node.priority, priority)
prefix_len = self.key_match_fn(node.key, key) prefix_len = self.key_match_fn(node.key, key)
if prefix_len == len(node.key): if prefix_len == len(node.key):
@@ -879,6 +888,8 @@ class HiRadixCache(RadixCache):
else: else:
# partial match, split the node # partial match, split the node
new_node = self._split_node(node.key, node, prefix_len) new_node = self._split_node(node.key, node, prefix_len)
# shared-prefix node should also reflect max priority
new_node.priority = max(new_node.priority, priority)
if new_node.evicted: if new_node.evicted:
new_node.value = value[:prefix_len] new_node.value = value[:prefix_len]
self.evictable_size_ += len(new_node.value) self.evictable_size_ += len(new_node.value)
@@ -894,7 +905,7 @@ class HiRadixCache(RadixCache):
child_key = self.get_child_key_fn(key) child_key = self.get_child_key_fn(key)
if len(key): if len(key):
new_node = TreeNode() new_node = TreeNode(priority=priority)
new_node.parent = node new_node.parent = node
new_node.key = key new_node.key = key
new_node.value = value new_node.value = value
+34 -8
View File
@@ -20,6 +20,7 @@ The radix tree data structure for managing the KV cache.
""" """
import heapq import heapq
import sys
import time import time
from collections import defaultdict from collections import defaultdict
from functools import lru_cache, partial from functools import lru_cache, partial
@@ -41,6 +42,7 @@ from sglang.srt.mem_cache.evict_policy import (
LFUStrategy, LFUStrategy,
LRUStrategy, LRUStrategy,
MRUStrategy, MRUStrategy,
PriorityStrategy,
) )
from sglang.srt.mem_cache.memory_pool import ReqToTokenPool from sglang.srt.mem_cache.memory_pool import ReqToTokenPool
@@ -76,7 +78,7 @@ class TreeNode:
counter = 0 counter = 0
def __init__(self, id: Optional[int] = None): def __init__(self, id: Optional[int] = None, priority: int = 0):
self.children = defaultdict(TreeNode) self.children = defaultdict(TreeNode)
self.parent: TreeNode = None self.parent: TreeNode = None
self.key: RadixKey = None self.key: RadixKey = None
@@ -93,6 +95,8 @@ class TreeNode:
self.host_value: Optional[torch.Tensor] = None self.host_value: Optional[torch.Tensor] = None
# store hash values of each pages # store hash values of each pages
self.hash_value: Optional[List[str]] = None self.hash_value: Optional[List[str]] = None
# priority for priority-aware eviction
self.priority = priority
self.id = TreeNode.counter if id is None else id self.id = TreeNode.counter if id is None else id
TreeNode.counter += 1 TreeNode.counter += 1
@@ -195,6 +199,7 @@ class RadixCache(BasePrefixCache):
enable_kv_cache_events: bool = False, enable_kv_cache_events: bool = False,
eviction_policy: str = "lru", eviction_policy: str = "lru",
is_eagle: bool = False, is_eagle: bool = False,
disable_finished_insert: bool = False,
): ):
self.req_to_token_pool = req_to_token_pool self.req_to_token_pool = req_to_token_pool
self.token_to_kv_pool_allocator = token_to_kv_pool_allocator self.token_to_kv_pool_allocator = token_to_kv_pool_allocator
@@ -203,6 +208,7 @@ class RadixCache(BasePrefixCache):
self.enable_kv_cache_events = enable_kv_cache_events self.enable_kv_cache_events = enable_kv_cache_events
self.kv_event_queue = [] self.kv_event_queue = []
self.is_eagle = is_eagle self.is_eagle = is_eagle
self.disable_finished_insert = disable_finished_insert
if enable_metrics: if enable_metrics:
self.init_metrics_collector() self.init_metrics_collector()
@@ -234,16 +240,19 @@ class RadixCache(BasePrefixCache):
self.eviction_strategy: EvictionStrategy = MRUStrategy() self.eviction_strategy: EvictionStrategy = MRUStrategy()
elif eviction_policy.lower() == "filo": elif eviction_policy.lower() == "filo":
self.eviction_strategy: EvictionStrategy = FILOStrategy() self.eviction_strategy: EvictionStrategy = FILOStrategy()
elif eviction_policy.lower() == "priority":
self.eviction_strategy: EvictionStrategy = PriorityStrategy()
else: else:
raise ValueError( raise ValueError(
f"Unknown eviction policy: {eviction_policy}. Supported policies: 'lru', 'lfu', 'fifo', 'mru', 'filo'." f"Unknown eviction policy: {eviction_policy}. Supported policies: 'lru', 'lfu', 'fifo', 'mru', 'filo', 'priority'."
) )
self.reset() self.reset()
##### Public API ##### ##### Public API #####
def reset(self): def reset(self):
self.root_node = TreeNode() # Initialize root with minimum priority so any real priority overrides it
self.root_node = TreeNode(priority=-sys.maxsize)
self.root_node.key = RadixKey(token_ids=[], extra_key=None) self.root_node.key = RadixKey(token_ids=[], extra_key=None)
self.root_node.value = [] self.root_node.value = []
self.root_node.host_value = [] self.root_node.host_value = []
@@ -324,7 +333,7 @@ class RadixCache(BasePrefixCache):
last_host_node=last_node, last_host_node=last_node,
) )
def insert(self, key: RadixKey, value=None, chunked=False): def insert(self, key: RadixKey, value=None, chunked=False, priority: int = 0):
if self.disable: if self.disable:
return 0 return 0
@@ -337,10 +346,14 @@ class RadixCache(BasePrefixCache):
# Make sure the value len equal to the EAGLE bigram key len # Make sure the value len equal to the EAGLE bigram key len
value = value[: len(key)] value = value[: len(key)]
return self._insert_helper(self.root_node, key, value) return self._insert_helper(self.root_node, key, value, priority)
def cache_finished_req(self, req: Req, is_insert: bool = True): def cache_finished_req(self, req: Req, is_insert: bool = True):
"""Cache request when it finishes.""" """Cache request when it finishes."""
# In deterministic mode, disable finished request insertion to radix cache
if self.disable_finished_insert:
is_insert = False
committed_kv_len = req.pop_committed_kv_cache() committed_kv_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[
@@ -379,9 +392,11 @@ class RadixCache(BasePrefixCache):
# Radix Cache takes one ref in memory pool # Radix Cache takes one ref in memory pool
if is_insert: if is_insert:
priority = getattr(req, "priority", 0) or 0
new_prefix_len = self.insert( new_prefix_len = self.insert(
RadixKey(token_ids[:page_aligned_token_len], req.extra_key), RadixKey(token_ids[:page_aligned_token_len], req.extra_key),
page_aligned_kv_indices, page_aligned_kv_indices,
priority=priority,
) )
# Free the duplicates that were already in the tree # Free the duplicates that were already in the tree
self.token_to_kv_pool_allocator.free( self.token_to_kv_pool_allocator.free(
@@ -435,10 +450,12 @@ class RadixCache(BasePrefixCache):
old_prefix_len -= 1 old_prefix_len -= 1
# Radix Cache takes one ref in memory pool # Radix Cache takes one ref in memory pool
priority = getattr(req, "priority", 0) or 0
new_prefix_len = self.insert( new_prefix_len = self.insert(
RadixKey(page_aligned_token_ids, req.extra_key), RadixKey(page_aligned_token_ids, req.extra_key),
page_aligned_kv_indices, page_aligned_kv_indices,
chunked=chunked, chunked=chunked,
priority=priority,
) )
self.token_to_kv_pool_allocator.free(kv_indices[old_prefix_len:new_prefix_len]) self.token_to_kv_pool_allocator.free(kv_indices[old_prefix_len:new_prefix_len])
@@ -590,8 +607,9 @@ class RadixCache(BasePrefixCache):
def _split_node(self, key: RadixKey, child: TreeNode, split_len: int): def _split_node(self, key: RadixKey, child: TreeNode, split_len: int):
# new_node -> child # new_node -> child
# New node inherits child's priority (represents shared prefix)
self._record_remove_event(child) self._record_remove_event(child)
new_node = TreeNode() new_node = TreeNode(priority=child.priority)
new_node.children = {self.get_child_key_fn(key[split_len:]): child} new_node.children = {self.get_child_key_fn(key[split_len:]): child}
new_node.parent = child.parent new_node.parent = child.parent
new_node.lock_ref = child.lock_ref new_node.lock_ref = child.lock_ref
@@ -607,9 +625,14 @@ class RadixCache(BasePrefixCache):
return new_node return new_node
def _insert_helper(self, node: TreeNode, key: RadixKey, value): def _insert_helper(self, node: TreeNode, key: RadixKey, value, priority: int = 0):
# Convert None priority to 0
if priority is None:
priority = 0
access_time = time.monotonic() access_time = time.monotonic()
node.last_access_time = access_time node.last_access_time = access_time
# Update priority along the path (take max to propagate higher priority)
node.priority = max(node.priority, priority)
if len(key) == 0: if len(key) == 0:
return 0 return 0
@@ -626,13 +649,16 @@ class RadixCache(BasePrefixCache):
if prefix_len < len(node.key): if prefix_len < len(node.key):
new_node = self._split_node(node.key, node, prefix_len) new_node = self._split_node(node.key, node, prefix_len)
new_node.priority = max(new_node.priority, priority)
node = new_node node = new_node
else:
node.priority = max(node.priority, priority)
if len(key): if len(key):
child_key = self.get_child_key_fn(key) child_key = self.get_child_key_fn(key)
if len(key): if len(key):
new_node = TreeNode() new_node = TreeNode(priority=priority)
new_node.parent = node new_node.parent = node
new_node.key = key new_node.key = key
new_node.value = value new_node.value = value
@@ -196,7 +196,7 @@ class LMCRadixCache(RadixCache):
if num_retrieved > 0: if num_retrieved > 0:
fetched = num_retrieved - prefix_pad fetched = num_retrieved - prefix_pad
new_node = TreeNode() new_node = TreeNode(priority=last_node.priority)
start = value.numel() start = value.numel()
end = start + fetched end = start + fetched
new_node.key = key[start:end] new_node.key = key[start:end]