[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:
co-authored by
github-actions[bot]
cctry
parent
94ae816f29
commit
b41afa3754
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
Reference in New Issue
Block a user