Support HiCache for MambaRadixCache (#19663)
Co-authored-by: hzh0425 <hzh0425@apache.org>
This commit is contained in:
@@ -264,7 +264,12 @@ class HiCacheController:
|
|||||||
):
|
):
|
||||||
self.tp_group = tp_group
|
self.tp_group = tp_group
|
||||||
self.mem_pool_device_allocator = token_to_kv_pool_allocator
|
self.mem_pool_device_allocator = token_to_kv_pool_allocator
|
||||||
self.mem_pool_device = token_to_kv_pool_allocator.get_kvcache()
|
mem_pool_device = token_to_kv_pool_allocator.get_kvcache()
|
||||||
|
from sglang.srt.mem_cache.memory_pool import HybridLinearKVPool
|
||||||
|
|
||||||
|
if isinstance(mem_pool_device, HybridLinearKVPool):
|
||||||
|
mem_pool_device = mem_pool_device.full_kv_pool
|
||||||
|
self.mem_pool_device = mem_pool_device
|
||||||
self.mem_pool_host = mem_pool_host
|
self.mem_pool_host = mem_pool_host
|
||||||
self.write_policy = write_policy
|
self.write_policy = write_policy
|
||||||
self.page_size = page_size
|
self.page_size = page_size
|
||||||
|
|||||||
@@ -639,6 +639,7 @@ class Req(ReqDllmMixin):
|
|||||||
self.extend_logprob_start_len = 0
|
self.extend_logprob_start_len = 0
|
||||||
self.last_node: Any = None
|
self.last_node: Any = None
|
||||||
self.last_host_node: Any = None
|
self.last_host_node: Any = None
|
||||||
|
self.last_host_backup_node: Any = None
|
||||||
self.host_hit_length = 0
|
self.host_hit_length = 0
|
||||||
# Tokens loaded from storage backend (L3) during prefetch for this request
|
# Tokens loaded from storage backend (L3) during prefetch for this request
|
||||||
self.storage_hit_length = 0
|
self.storage_hit_length = 0
|
||||||
@@ -857,7 +858,11 @@ class Req(ReqDllmMixin):
|
|||||||
# Whether request reached finished condition
|
# Whether request reached finished condition
|
||||||
return self.finished_reason is not None
|
return self.finished_reason is not None
|
||||||
|
|
||||||
def init_next_round_input(self, tree_cache: Optional[BasePrefixCache] = None):
|
def init_next_round_input(
|
||||||
|
self,
|
||||||
|
tree_cache: Optional[BasePrefixCache] = None,
|
||||||
|
cow_mamba: Optional[bool] = None,
|
||||||
|
):
|
||||||
if self.is_dllm():
|
if self.is_dllm():
|
||||||
self._init_fill_ids_for_dllm()
|
self._init_fill_ids_for_dllm()
|
||||||
self.determine_dllm_phase()
|
self.determine_dllm_phase()
|
||||||
@@ -873,23 +878,27 @@ class Req(ReqDllmMixin):
|
|||||||
token_ids = self.fill_ids[:max_prefix_len]
|
token_ids = self.fill_ids[:max_prefix_len]
|
||||||
|
|
||||||
if tree_cache is not None:
|
if tree_cache is not None:
|
||||||
|
if cow_mamba is None:
|
||||||
|
cow_mamba = tree_cache.supports_mamba()
|
||||||
match_result = tree_cache.match_prefix(
|
match_result = tree_cache.match_prefix(
|
||||||
MatchPrefixParams(
|
MatchPrefixParams(
|
||||||
key=RadixKey(token_ids=token_ids, extra_key=self.extra_key),
|
key=RadixKey(token_ids=token_ids, extra_key=self.extra_key),
|
||||||
req=self,
|
req=self,
|
||||||
cow_mamba=tree_cache.supports_mamba(),
|
cow_mamba=cow_mamba,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
(
|
(
|
||||||
self.prefix_indices,
|
self.prefix_indices,
|
||||||
self.last_node,
|
self.last_node,
|
||||||
self.last_host_node,
|
self.last_host_node,
|
||||||
|
self.last_host_backup_node,
|
||||||
self.host_hit_length,
|
self.host_hit_length,
|
||||||
self.mamba_branching_seqlen,
|
self.mamba_branching_seqlen,
|
||||||
) = (
|
) = (
|
||||||
match_result.device_indices,
|
match_result.device_indices,
|
||||||
match_result.last_device_node,
|
match_result.last_device_node,
|
||||||
match_result.last_host_node,
|
match_result.last_host_node,
|
||||||
|
match_result.last_host_backup_node,
|
||||||
match_result.host_hit_length,
|
match_result.host_hit_length,
|
||||||
match_result.mamba_branching_seqlen,
|
match_result.mamba_branching_seqlen,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -694,9 +694,20 @@ class Scheduler(
|
|||||||
logger.info("Using experimental C++ radix tree implementation.")
|
logger.info("Using experimental C++ radix tree implementation.")
|
||||||
self.tree_cache = RadixCacheCpp(params=params, server_args=server_args)
|
self.tree_cache = RadixCacheCpp(params=params, server_args=server_args)
|
||||||
elif self.enable_hierarchical_cache:
|
elif self.enable_hierarchical_cache:
|
||||||
from sglang.srt.mem_cache.hiradix_cache import HiRadixCache
|
if self.is_hybrid_ssm:
|
||||||
|
from sglang.srt.mem_cache.hi_mamba_radix_cache import (
|
||||||
|
HiMambaRadixCache,
|
||||||
|
)
|
||||||
|
|
||||||
self.tree_cache = HiRadixCache(params=params, server_args=server_args)
|
self.tree_cache = HiMambaRadixCache(
|
||||||
|
params=params, server_args=server_args
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
from sglang.srt.mem_cache.hiradix_cache import HiRadixCache
|
||||||
|
|
||||||
|
self.tree_cache = HiRadixCache(
|
||||||
|
params=params, server_args=server_args
|
||||||
|
)
|
||||||
self.tp_worker.register_hicache_layer_transfer_counter(
|
self.tp_worker.register_hicache_layer_transfer_counter(
|
||||||
self.tree_cache.cache_controller.layer_done_counter
|
self.tree_cache.cache_controller.layer_done_counter
|
||||||
)
|
)
|
||||||
@@ -1707,22 +1718,25 @@ class Scheduler(
|
|||||||
|
|
||||||
def _prefetch_kvcache(self, req: Req):
|
def _prefetch_kvcache(self, req: Req):
|
||||||
if self.enable_hicache_storage:
|
if self.enable_hicache_storage:
|
||||||
req.init_next_round_input(self.tree_cache)
|
req.init_next_round_input(self.tree_cache, cow_mamba=False)
|
||||||
if req.last_node.backuped:
|
last_host_node = (
|
||||||
# only to initiate the prefetch if the last node is backuped
|
req.last_host_backup_node
|
||||||
# otherwise, the allocated GPU memory must be locked for integrity
|
if req.last_host_backup_node is not None
|
||||||
last_hash = req.last_host_node.get_last_hash_value()
|
else req.last_host_node
|
||||||
|
)
|
||||||
|
if last_host_node.backuped or last_host_node is self.tree_cache.root_node:
|
||||||
|
last_hash = last_host_node.get_last_hash_value()
|
||||||
matched_len = len(req.prefix_indices) + req.host_hit_length
|
matched_len = len(req.prefix_indices) + req.host_hit_length
|
||||||
new_input_tokens = req.fill_ids[matched_len:]
|
new_input_tokens = req.fill_ids[matched_len:]
|
||||||
|
|
||||||
prefix_keys = (
|
prefix_keys = (
|
||||||
req.last_node.get_prefix_hash_values(req.last_node.parent)
|
last_host_node.get_prefix_hash_values(last_host_node.parent)
|
||||||
if self.tree_cache.hicache_storage_pass_prefix_keys
|
if self.tree_cache.hicache_storage_pass_prefix_keys
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
self.tree_cache.prefetch_from_storage(
|
self.tree_cache.prefetch_from_storage(
|
||||||
req.rid,
|
req.rid,
|
||||||
req.last_host_node,
|
last_host_node,
|
||||||
new_input_tokens,
|
new_input_tokens,
|
||||||
last_hash,
|
last_hash,
|
||||||
prefix_keys,
|
prefix_keys,
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ class MatchResult(NamedTuple):
|
|||||||
last_host_node : The last TreeNode on the host that was matched.
|
last_host_node : The last TreeNode on the host that was matched.
|
||||||
Note that if HiCache is not enabled,
|
Note that if HiCache is not enabled,
|
||||||
this **must** be the same as `last_device_node`.
|
this **must** be the same as `last_device_node`.
|
||||||
|
last_host_backup_node: The deepest backuped node for prefetch from storage.
|
||||||
host_hit_length : Length of the KV cache hit on the host, if applicable.
|
host_hit_length : Length of the KV cache hit on the host, if applicable.
|
||||||
0 if HiCache is not enabled.
|
0 if HiCache is not enabled.
|
||||||
mamba_branching_seqlen: The mamba radix cache branching point, which is the longest
|
mamba_branching_seqlen: The mamba radix cache branching point, which is the longest
|
||||||
@@ -107,6 +108,7 @@ class MatchResult(NamedTuple):
|
|||||||
device_indices: torch.Tensor
|
device_indices: torch.Tensor
|
||||||
last_device_node: Any
|
last_device_node: Any
|
||||||
last_host_node: Any
|
last_host_node: Any
|
||||||
|
last_host_backup_node: Any = None
|
||||||
host_hit_length: int = 0
|
host_hit_length: int = 0
|
||||||
mamba_branching_seqlen: Optional[int] = None
|
mamba_branching_seqlen: Optional[int] = None
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -21,7 +21,7 @@ The radix tree data structure for managing the hybrid (full and Mamba) KV cache.
|
|||||||
|
|
||||||
import heapq
|
import heapq
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from functools import partial
|
from functools import lru_cache, partial
|
||||||
from typing import TYPE_CHECKING, List, Optional, Tuple
|
from typing import TYPE_CHECKING, List, Optional, Tuple
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
@@ -82,8 +82,11 @@ class TreeNode:
|
|||||||
self.last_access_time = get_last_access_time()
|
self.last_access_time = get_last_access_time()
|
||||||
|
|
||||||
self.hit_count = 0
|
self.hit_count = 0
|
||||||
|
self.host_ref_counter = 0
|
||||||
# store the host indices of KV cache
|
# store the host indices of KV cache
|
||||||
self.host_value = None
|
self.host_value = None
|
||||||
|
# store hash values of each pages
|
||||||
|
self.hash_value: Optional[List[str]] = None
|
||||||
|
|
||||||
# for lru list, invariant:
|
# for lru list, invariant:
|
||||||
# 1. prev has greater last_access_time
|
# 1. prev has greater last_access_time
|
||||||
@@ -104,6 +107,29 @@ class TreeNode:
|
|||||||
def backuped(self):
|
def backuped(self):
|
||||||
return self.host_value is not None
|
return self.host_value is not None
|
||||||
|
|
||||||
|
def protect_host(self):
|
||||||
|
"""Protect the host value from eviction."""
|
||||||
|
self.host_ref_counter += 1
|
||||||
|
|
||||||
|
def release_host(self):
|
||||||
|
"""Release the host value, allowing it to be evicted."""
|
||||||
|
if self.host_ref_counter > 0:
|
||||||
|
self.host_ref_counter -= 1
|
||||||
|
else:
|
||||||
|
raise RuntimeError("Host reference counter is already zero.")
|
||||||
|
|
||||||
|
def get_last_hash_value(self) -> Optional[str]:
|
||||||
|
"""Returns the hash value of the last page in this node."""
|
||||||
|
if self.hash_value is None or len(self.hash_value) == 0:
|
||||||
|
return None
|
||||||
|
return self.hash_value[-1]
|
||||||
|
|
||||||
|
@lru_cache(maxsize=1)
|
||||||
|
def get_prefix_hash_values(self, node: "TreeNode") -> List[str]:
|
||||||
|
if node is None or node.hash_value is None:
|
||||||
|
return []
|
||||||
|
return node.get_prefix_hash_values(node.parent) + node.hash_value
|
||||||
|
|
||||||
def __lt__(self, other: "TreeNode"):
|
def __lt__(self, other: "TreeNode"):
|
||||||
return self.last_access_time < other.last_access_time
|
return self.last_access_time < other.last_access_time
|
||||||
|
|
||||||
@@ -457,7 +483,7 @@ class MambaRadixCache(BasePrefixCache):
|
|||||||
if value is None:
|
if value is None:
|
||||||
value = torch.tensor([x for x in key.token_ids], dtype=torch.int64)
|
value = torch.tensor([x for x in key.token_ids], dtype=torch.int64)
|
||||||
prefix_len, mamba_exist = self._insert_helper(
|
prefix_len, mamba_exist = self._insert_helper(
|
||||||
self.root_node, key, value, mamba_value
|
self.root_node, key, value, mamba_value, params.chunked
|
||||||
)
|
)
|
||||||
return InsertResult(prefix_len=prefix_len, mamba_exist=mamba_exist)
|
return InsertResult(prefix_len=prefix_len, mamba_exist=mamba_exist)
|
||||||
|
|
||||||
@@ -1063,6 +1089,7 @@ class MambaRadixCache(BasePrefixCache):
|
|||||||
key: RadixKey,
|
key: RadixKey,
|
||||||
value,
|
value,
|
||||||
mamba_value,
|
mamba_value,
|
||||||
|
chunked: bool = False,
|
||||||
) -> Tuple[int, bool]:
|
) -> Tuple[int, bool]:
|
||||||
# Update the last access time from root to leaf, so that
|
# Update the last access time from root to leaf, so that
|
||||||
# mamba will tombstone the node closer to root first
|
# mamba will tombstone the node closer to root first
|
||||||
|
|||||||
Reference in New Issue
Block a user