Count evictable device cache when gating HiCache load-back
HiCache host hits can be skipped before load-back when the quota gate only counts immediately free KV allocator space. Under CP shared-KV pressure most reusable capacity may be represented as evictable radix-cache leaves, so the gate can incorrectly reject a host hit and leave prefill with cached-token zero despite host residency. Count device evictable cache in the quota estimate while leaving actual owner-lane allocation and eviction checks in the load path. Constraint: CP HiCache load-back still has to respect owner-lane allocation and allocator eviction semantics. Rejected: Force load-back regardless of quota | would bypass the scheduler pressure signal and increase OOM risk. Rejected: Treat cache-hit zero as a transfer issue | logs showed host hits were found but skipped by quota before transfer. Confidence: medium Scope-risk: moderate Directive: Do not remove evictable cache from load-back capacity accounting without checking CP HiCache host-hit behavior under device pressure. Tested: git diff --check Tested: remote g0034 container pytest -q test/registered/unit/managers/test_prefill_adder.py test/registered/unit/managers/test_hicache_controller_cp.py test/registered/unit/mem_cache/test_cp_hicache_metadata.py test/registered/unit/mem_cache/test_alloc_pages_with_owners.py (90 passed, 3 warnings) Not-tested: Full ETE GLM5 CP+HiCache+EAGLE pressure run after this quota change Co-authored-by: OmX <omx@oh-my-codex.dev>
This commit is contained in:
@@ -76,6 +76,20 @@ except (ImportError, RuntimeError):
|
||||
if "sgl_kernel.kvcacheio" not in sys.modules:
|
||||
sys.modules["sgl_kernel.kvcacheio"] = types.ModuleType("sgl_kernel.kvcacheio")
|
||||
|
||||
_sgl_kernel_lib = torch.library.Library("sgl_kernel", "FRAGMENT")
|
||||
for _schema in (
|
||||
"sgl_per_token_group_quant_8bit(Tensor input, Tensor(a!) output_q, Tensor(b!) output_s, int group_size, float eps, float fp8_min, float fp8_max, bool scale_ue8m0) -> ()",
|
||||
"sgl_per_token_group_quant_fp8(Tensor input, Tensor(a!) output_q, Tensor(b!) output_s, int group_size, float eps, float fp8_min, float fp8_max, bool scale_ue8m0) -> ()",
|
||||
"sgl_per_token_quant_fp8(Tensor input, Tensor(a!) output_q, Tensor(b!) output_s) -> ()",
|
||||
"fp8_scaled_mm(Tensor mat_a, Tensor mat_b, Tensor scales_a, Tensor scales_b, ScalarType out_dtype, Tensor? bias=None) -> Tensor",
|
||||
"fp8_blockwise_scaled_mm(Tensor mat_a, Tensor mat_b, Tensor scales_a, Tensor scales_b, ScalarType out_dtype) -> Tensor",
|
||||
):
|
||||
try:
|
||||
_sgl_kernel_lib.define(_schema)
|
||||
except RuntimeError as exc:
|
||||
if "already" not in str(exc).lower() and "duplicate" not in str(exc).lower():
|
||||
raise
|
||||
|
||||
from sglang.srt.mem_cache.base_prefix_cache import EvictParams, MatchPrefixParams
|
||||
from sglang.srt.mem_cache.hiradix_cache import CpHiCacheNodeMetadata, HiRadixCache
|
||||
from sglang.srt.mem_cache.radix_cache import RadixKey, TreeNode
|
||||
@@ -483,6 +497,34 @@ class TestHiRadixCacheCPBackup(CustomTestCase):
|
||||
|
||||
self.assertTrue(cache._node_backuped(node))
|
||||
|
||||
def test_single_node_write_lock_updates_device_evictable_leaf_set(self):
|
||||
cache = HiRadixCache.__new__(HiRadixCache)
|
||||
cache.disable = False
|
||||
cache.root_node = TreeNode()
|
||||
cache.root_node.key = RadixKey([])
|
||||
cache.evictable_leaves = set()
|
||||
cache.evictable_size_ = 4
|
||||
cache.protected_size_ = 0
|
||||
|
||||
node = TreeNode()
|
||||
node.parent = cache.root_node
|
||||
node.key = RadixKey([1, 2, 3, 4])
|
||||
node.value = torch.arange(4, dtype=torch.int64)
|
||||
cache.root_node.children[1] = node
|
||||
cache.evictable_leaves.add(node)
|
||||
|
||||
cache.inc_node_lock_ref(node)
|
||||
|
||||
self.assertNotIn(node, cache.evictable_leaves)
|
||||
self.assertEqual(cache.evictable_size(), 0)
|
||||
self.assertEqual(cache.protected_size(), 4)
|
||||
|
||||
cache.dec_node_lock_ref(node)
|
||||
|
||||
self.assertIn(node, cache.evictable_leaves)
|
||||
self.assertEqual(cache.evictable_size(), 4)
|
||||
self.assertEqual(cache.protected_size(), 0)
|
||||
|
||||
def test_inc_hit_count_does_not_rewrite_cp_backed_node(self):
|
||||
cache = HiRadixCache.__new__(HiRadixCache)
|
||||
cache._uses_cp_hicache = True
|
||||
@@ -867,18 +909,15 @@ class TestHiRadixCacheCPSplitEvict(CustomTestCase):
|
||||
cache.root_node.children[1] = node
|
||||
cache.evictable_host_leaves.add(node)
|
||||
|
||||
all_done_states = iter([False, True])
|
||||
cache._cp_all_ranks_true = lambda done: next(all_done_states, True)
|
||||
cache._cp_broadcast_node_ids = lambda node_ids, max_ids: node_ids[:max_ids]
|
||||
cache._cp_filter_all_ranks_safe_node_ids = (
|
||||
lambda node_ids, is_safe, **_kwargs: [
|
||||
node_id
|
||||
for node_id in node_ids
|
||||
if is_safe(cache._cp_node_by_id(node_id))
|
||||
]
|
||||
)
|
||||
all_done_states = iter([0, 1])
|
||||
|
||||
physical_freed = cache._cp_evict_host_for_physical_slots(0)
|
||||
def fake_all_reduce(done, op=None, group=None):
|
||||
done.fill_(next(all_done_states, 1))
|
||||
|
||||
with patch("torch.distributed.all_reduce", side_effect=fake_all_reduce):
|
||||
physical_freed = cache._evict_host_for_physical_slots(
|
||||
0, synchronize_across_ranks=True
|
||||
)
|
||||
|
||||
self.assertEqual(physical_freed, 0)
|
||||
self.assertEqual(freed, [])
|
||||
|
||||
Reference in New Issue
Block a user