Files
wehub-resource-sync 94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:38:16 +08:00

310 lines
12 KiB
Python

"""HiCache integration mixins for the decode side of PD disaggregation"""
from __future__ import annotations
import logging
from dataclasses import dataclass
from enum import Enum
from typing import TYPE_CHECKING, Any, List, Optional
import torch
from sglang.srt.disaggregation.base import KVPoll
from sglang.srt.managers.schedule_policy import match_prefix_for_req
from sglang.srt.mem_cache.base_prefix_cache import InitLoadBackParams
if TYPE_CHECKING:
from sglang.srt.disaggregation.decode import DecodeRequest
from sglang.srt.managers.schedule_batch import Req
logger = logging.getLogger(__name__)
@dataclass
class DecodePrefixMatch:
prefix_indices: torch.Tensor
l2_host_hit_length: int
l3_storage_hit_length: int
last_device_node: Any
last_host_node: Any = None
prefetch_registered: bool = False
@property
def l1_prefix_len(self) -> int:
return len(self.prefix_indices)
@property
def decode_prefix_len(self) -> int:
return self.l1_prefix_len + self.l2_host_hit_length + self.l3_storage_hit_length
@property
def needs_local_restore(self) -> bool:
return self.decode_prefix_len > self.l1_prefix_len
@property
def restore_token_count(self) -> int:
"""Number of tokens that need L2/L3 load_back to device."""
return self.decode_prefix_len - self.l1_prefix_len
class HiCacheRestoreResult(Enum):
"""Outcome of one tick of the HiCache local-restore state machine."""
PENDING = "pending"
READY = "ready"
FAILED = "failed"
class DecodeHiCachePreallocMixin:
"""HiCache hooks for ``DecodePreallocQueue``: issue prefetch + reserve tokens."""
def _build_decode_prefix_match(self, req: Req, result: Any) -> DecodePrefixMatch:
"""Convert a ``match_prefix_for_req`` result into ``DecodePrefixMatch``.
Performs the optional L3 storage hit length query when decode-side
HiCache is enabled and the last host node is backed up.
"""
prefix_indices = result.device_indices
l1_prefix_len = len(prefix_indices)
l2_host_hit_length = result.host_hit_length
l3_storage_hit_length = 0
last_host_node = None
if self.scheduler.enable_decode_hicache:
last_host_node = result.last_host_node
if last_host_node.backuped or last_host_node is self.tree_cache.root_node:
matched_len = l1_prefix_len + l2_host_hit_length
suffix_tokens = req.origin_input_ids[matched_len:]
last_hash = last_host_node.get_last_hash_value()
prefix_keys = (
last_host_node.get_prefix_hash_values(last_host_node.parent)
if self.tree_cache.hicache_storage_pass_prefix_keys
else None
)
l3_storage_hit_length = self.tree_cache.query_storage_hit_length(
last_host_node,
suffix_tokens,
last_hash,
prefix_keys,
)
return DecodePrefixMatch(
prefix_indices=prefix_indices,
l2_host_hit_length=l2_host_hit_length,
l3_storage_hit_length=l3_storage_hit_length,
last_device_node=result.last_device_node,
last_host_node=last_host_node if l3_storage_hit_length > 0 else None,
)
def _start_hicache_prefetch(
self, req: Req, prefix_match: Optional[DecodePrefixMatch]
) -> None:
"""Issue L3 storage prefetch after admission succeeds.
On failure, degrades to L2-only restore by clearing l3 fields.
"""
if (
prefix_match is None
or prefix_match.l3_storage_hit_length <= 0
or prefix_match.last_host_node is None
):
return
try:
node = prefix_match.last_host_node
matched_len = prefix_match.l1_prefix_len + prefix_match.l2_host_hit_length
suffix = req.origin_input_ids[
matched_len : matched_len + prefix_match.l3_storage_hit_length
]
last_hash = node.get_last_hash_value()
prefix_keys = (
node.get_prefix_hash_values(node.parent)
if self.tree_cache.hicache_storage_pass_prefix_keys
else None
)
self.tree_cache.prefetch_from_storage(
req.rid, node, suffix, last_hash, prefix_keys
)
prefix_match.prefetch_registered = (
req.rid in self.tree_cache.ongoing_prefetch
)
except Exception as e:
logger.warning(
"HiCache L3 prefetch failed for rid=%s: %s; falling back to L2-only LoadingBack",
req.rid,
e,
)
prefix_match.l3_storage_hit_length = 0
prefix_match.prefetch_registered = False
def _hicache_pending_restore_tokens(self) -> int:
"""Total device tokens reserved for pending HiCache L2/L3 load_back."""
if not self.scheduler.enable_decode_hicache:
return 0
return sum(
dr.prefix_match.restore_token_count
for dr in self.transfer_queue.queue
if dr.prefix_match is not None
and dr.hicache_restore_status == HiCacheRestoreResult.PENDING
and dr.hicache_restored_node is None
)
class HiCacheRestoreGatedKVReceiver:
"""Wraps a kv_receiver so KVPoll.Success is gated on HiCache restore READY."""
def __init__(self, decode_req: DecodeRequest):
self.decode_req = decode_req
def poll(self) -> KVPoll:
poll = self.decode_req.kv_receiver.poll()
if (
poll == KVPoll.Success
and self.decode_req.hicache_restore_status == HiCacheRestoreResult.PENDING
):
return KVPoll.Transferring
return poll
class DecodeHiCacheTransferMixin:
"""HiCache hooks for ``DecodeTransferQueue``: drive restore state machine."""
def _clean_hicache_prefetch_resources(self, decode_req: DecodeRequest) -> None:
if (
decode_req.prefix_match is not None
and decode_req.prefix_match.prefetch_registered
):
self.tree_cache.release_aborted_request(decode_req.req.rid)
if decode_req.hicache_restored_node is not None:
self.tree_cache.dec_lock_ref(decode_req.hicache_restored_node)
decode_req.hicache_restored_node = None
def _try_hicache_queue_load_back(self, dr: DecodeRequest) -> bool:
"""Queue one L2->L1 load_back op for ``dr``; True iff a DMA was queued.
On success, ``dr.hicache_restored_node`` and ``hicache_restored_kv_indices``
are populated, and an inc_lock_ref is held until commit/abort.
Trivial cases (all-on-device / no needed coverage) auto-flip to READY.
Failback paths flip to FAILED.
"""
pm = dr.prefix_match
# Wait for L3 -> L2 prefetch to drain (skip when no L3 hit).
if pm.l3_storage_hit_length > 0:
if not self.tree_cache.check_prefetch_progress(dr.req.rid):
return False
self.tree_cache.pop_prefetch_loaded_tokens(dr.req.rid)
# Re-match: req.last_node / prefix_indices updated to current device state.
rematch = match_prefix_for_req(
self.tree_cache,
dr.req,
dr.req.origin_input_ids,
cow_mamba=False,
include_req=True,
)
new_indices, restored_node = self.tree_cache.init_load_back(
InitLoadBackParams(
best_match_node=rematch.best_match_node,
host_hit_length=rematch.host_hit_length,
req=dr.req,
)
)
# Failback: total coverage < required prefix means device alloc likely failed.
if len(rematch.device_indices) + len(new_indices) < pm.decode_prefix_len:
logger.warning(
"HiCache load_back failed for rid=%s: device_indices=%d, "
"new_indices=%d, expected decode_prefix_len=%d (l1=%d, l2=%d, l3=%d)",
dr.req.rid,
len(rematch.device_indices),
len(new_indices),
pm.decode_prefix_len,
pm.l1_prefix_len,
pm.l2_host_hit_length,
pm.l3_storage_hit_length,
)
dr.hicache_restore_status = HiCacheRestoreResult.FAILED
return False
dr.hicache_restored_kv_indices = torch.cat(
[rematch.device_indices[pm.l1_prefix_len :], new_indices]
)
dr.hicache_restored_node = restored_node
self.tree_cache.inc_lock_ref(restored_node)
if len(new_indices) == 0:
# Whole prefix already on device; no DMA needed.
dr.hicache_restore_status = HiCacheRestoreResult.READY
return False
return True
def _process_hicache_local_restores(self, decode_reqs: List[DecodeRequest]) -> None:
if not hasattr(self.tree_cache, "is_load_back_event_done"):
return
# Filter once: keep only PENDING reqs that still need restore work;
# trivially-done reqs (no prefix_match / nothing to restore) flip to READY.
active: List[DecodeRequest] = []
for dr in decode_reqs:
if dr.hicache_restore_status != HiCacheRestoreResult.PENDING:
continue
pm = dr.prefix_match
if pm is None or not pm.needs_local_restore:
dr.hicache_restore_status = HiCacheRestoreResult.READY
continue
active.append(dr)
# Phase A: advance in-flight DMAs to READY.
for dr in active:
if (
dr.hicache_restored_node is not None
and self.tree_cache.is_load_back_event_done(
dr.hicache_load_consumer_index
)
):
dr.hicache_restore_status = HiCacheRestoreResult.READY
# Phase B: queue new load_back ops if the next slot is free.
# The (producer_index + 1) check ensures we never overwrite a still-in-flight slot:
# if a previous req holds that slot and isn't done, its event won't be signaled.
counter = self.tree_cache.cache_controller.layer_done_counter
if not self.tree_cache.is_load_back_event_done(
(counter.producer_index + 1) % counter.num_counters
):
return
queued = [
dr
for dr in active
if dr.hicache_restored_node is None
and self._try_hicache_queue_load_back(dr)
]
if not queued:
return
# Phase C: kick off merged DMA, bind consumer_index for Phase A polling next tick.
consumer_index = self.tree_cache.ready_to_load_host_cache()
if consumer_index < 0:
for dr in queued:
dr.hicache_restore_status = HiCacheRestoreResult.READY
return
for dr in queued:
dr.hicache_load_consumer_index = consumer_index
def _commit_hicache_local_restore_to_req(self, decode_req: DecodeRequest) -> None:
prefix_match = decode_req.prefix_match
if prefix_match is None or not prefix_match.needs_local_restore:
return
self.tree_cache.dec_lock_ref(prefix_match.last_device_node)
self.tree_cache.req_to_token_pool.write(
(
decode_req.req.req_pool_idx,
slice(prefix_match.l1_prefix_len, prefix_match.decode_prefix_len),
),
decode_req.hicache_restored_kv_indices,
)
decode_req.req.prefix_indices = torch.cat(
[prefix_match.prefix_indices, decode_req.hicache_restored_kv_indices]
)
decode_req.req.last_node = decode_req.hicache_restored_node