[Feature] Add SLRU eviction policy & fix RadixCache hit_count bug (#18843)
Co-authored-by: zhangheng <hzh0425@apache.org>
This commit is contained in:
@@ -44,3 +44,22 @@ class PriorityStrategy(EvictionStrategy):
|
||||
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)
|
||||
|
||||
|
||||
class SLRUStrategy(EvictionStrategy):
|
||||
def __init__(self, protected_threshold: int = 2):
|
||||
self.protected_threshold = protected_threshold
|
||||
|
||||
def get_priority(self, node: "TreeNode") -> Tuple[int, float]:
|
||||
# Priority Logic:
|
||||
# Smaller value = Evicted earlier.
|
||||
#
|
||||
# Segment 0 (Probationary): hit_count < threshold
|
||||
# Segment 1 (Protected): hit_count >= threshold
|
||||
#
|
||||
# Tuple comparison: (segment, last_access_time)
|
||||
# Nodes in segment 0 will always be evicted before segment 1.
|
||||
# Inside the same segment, older nodes (smaller time) are evicted first.
|
||||
|
||||
is_protected = 1 if node.hit_count >= self.protected_threshold else 0
|
||||
return (is_protected, node.last_access_time)
|
||||
|
||||
@@ -57,6 +57,7 @@ from sglang.srt.mem_cache.evict_policy import (
|
||||
LRUStrategy,
|
||||
MRUStrategy,
|
||||
PriorityStrategy,
|
||||
SLRUStrategy,
|
||||
)
|
||||
from sglang.srt.mem_cache.hicache_storage import get_hash_str, hash_str_to_int64
|
||||
|
||||
@@ -322,9 +323,12 @@ class RadixCache(BasePrefixCache):
|
||||
self.eviction_strategy: EvictionStrategy = FILOStrategy()
|
||||
elif self.eviction_policy == "priority":
|
||||
self.eviction_strategy: EvictionStrategy = PriorityStrategy()
|
||||
elif self.eviction_policy == "slru":
|
||||
self.eviction_strategy: EvictionStrategy = SLRUStrategy()
|
||||
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Unknown eviction policy: {self.eviction_policy}. Supported policies: 'lru', 'lfu', 'fifo', 'mru', 'filo', 'priority'."
|
||||
f"Unknown eviction policy: {self.eviction_policy}. Supported policies: 'lru', 'lfu', 'fifo', 'mru', 'filo', 'priority', 'slru'."
|
||||
)
|
||||
|
||||
self.evictable_leaves = set()
|
||||
@@ -447,13 +451,14 @@ class RadixCache(BasePrefixCache):
|
||||
key = params.key
|
||||
value = params.value
|
||||
priority = params.priority
|
||||
chunked = params.chunked
|
||||
|
||||
if value is None:
|
||||
value = torch.tensor(key.token_ids, dtype=torch.int64)
|
||||
|
||||
key, value = self.maybe_bigram_convert(key, value)
|
||||
|
||||
prefix_len = self._insert_helper(self.root_node, key, value, priority)
|
||||
prefix_len = self._insert_helper(self.root_node, key, value, priority, chunked)
|
||||
return InsertResult(prefix_len=prefix_len)
|
||||
|
||||
def cache_finished_req(self, req: Req, is_insert: bool = True):
|
||||
@@ -688,6 +693,7 @@ class RadixCache(BasePrefixCache):
|
||||
# new_node -> child
|
||||
# New node inherits child's priority (represents shared prefix)
|
||||
new_node = TreeNode(priority=child.priority)
|
||||
new_node.hit_count = child.hit_count
|
||||
new_node.children = {self.get_child_key_fn(key[split_len:]): child}
|
||||
new_node.parent = child.parent
|
||||
new_node.lock_ref = child.lock_ref
|
||||
@@ -705,7 +711,22 @@ class RadixCache(BasePrefixCache):
|
||||
|
||||
return new_node
|
||||
|
||||
def _insert_helper(self, node: TreeNode, key: RadixKey, value, priority: int = 0):
|
||||
def _inc_hit_count(self, node: TreeNode, chunked: bool = False):
|
||||
# Skip the hit count update for chunked requests to avoid self-referencing
|
||||
# inflation where a chunked request increments hit_count on nodes it created
|
||||
# in previous chunks.
|
||||
if chunked:
|
||||
return
|
||||
node.hit_count += 1
|
||||
|
||||
def _insert_helper(
|
||||
self,
|
||||
node: TreeNode,
|
||||
key: RadixKey,
|
||||
value,
|
||||
priority: int = 0,
|
||||
chunked: bool = False,
|
||||
):
|
||||
# Convert None priority to 0
|
||||
if priority is None:
|
||||
priority = 0
|
||||
@@ -730,10 +751,11 @@ class RadixCache(BasePrefixCache):
|
||||
if prefix_len < len(node.key):
|
||||
new_node = self._split_node(node.key, node, prefix_len)
|
||||
new_node.priority = max(new_node.priority, priority)
|
||||
self._inc_hit_count(new_node, chunked)
|
||||
node = new_node
|
||||
else:
|
||||
node.priority = max(node.priority, priority)
|
||||
|
||||
self._inc_hit_count(node, chunked)
|
||||
if len(key):
|
||||
child_key = self.get_child_key_fn(key)
|
||||
|
||||
@@ -742,6 +764,7 @@ class RadixCache(BasePrefixCache):
|
||||
new_node.parent = node
|
||||
new_node.key = key
|
||||
new_node.value = value.clone()
|
||||
self._inc_hit_count(new_node, chunked)
|
||||
node.children[child_key] = new_node
|
||||
self.evictable_size_ += len(key)
|
||||
self._update_leaf_status(node)
|
||||
|
||||
@@ -169,7 +169,7 @@ NSA_CHOICES = [
|
||||
"trtllm",
|
||||
]
|
||||
|
||||
RADIX_EVICTION_POLICY_CHOICES = ["lru", "lfu"]
|
||||
RADIX_EVICTION_POLICY_CHOICES = ["lru", "lfu", "slru"]
|
||||
|
||||
RL_ON_POLICY_TARGET_CHOICES = ["fsdp"]
|
||||
|
||||
@@ -3633,7 +3633,7 @@ class ServerArgs:
|
||||
type=str,
|
||||
choices=RADIX_EVICTION_POLICY_CHOICES,
|
||||
default=ServerArgs.radix_eviction_policy,
|
||||
help="The eviction policy of radix trees. 'lru' stands for Least Recently Used, 'lfu' stands for Least Frequently Used.",
|
||||
help="The eviction policy of radix trees. 'lru' stands for Least Recently Used, 'lfu' stands for Least Frequently Used, and 'slru' stands for Segmented Least Recently Used.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--enable-prefill-delayer",
|
||||
|
||||
Reference in New Issue
Block a user