Index skip reduces the number of target layers that own NSA index state, but PD transfer and HiCache still assumed dense full-layer state buffers. This change carries explicit state layer IDs through prefill/decode registration, compacts device and host index buffers to active layers, and maps logical layer IDs to compact slots on transfer paths. The PD side fails fast when prefill/decode disagree on NSA state layer identity instead of silently truncating or copying mismatched buffers. Host direct tests now use the same CPU-index descriptor contract required by the TAI cudaMemcpyBatchAsync path, and host registered memory is unregistered on tensor finalization to avoid stale cudaHostRegister state across CUDA tests. Constraint: CP shared-KV with index_topk skip must keep target/draft state identity explicit before compacting buffers Constraint: Direct HiCache TAI transfer rejects CUDA indices to avoid hidden D2H copies on the control path Rejected: Keep full-layer L1/L2 index buffers | wastes the memory/bandwidth that index skip is meant to save Rejected: Infer state buffer order by count only | can silently corrupt cache when active layer sets differ Confidence: high Scope-risk: moderate Directive: Do not compact or reorder NSA state buffers without carrying logical layer IDs through PD registration and validating both sides Tested: Remote container py_compile for touched runtime files Tested: Remote container pytest: test_nsa_pool_host_unit.py, test_model_runner_kv_cache_mixin.py, test_cp_shared_kv_transfer_mapping.py, test_pd_state_layer_ids.py, test_cp_per_layer_transfer.py, test_cp_shared_kv_runtime.py -> 200 passed, 2 subtests passed Not-tested: Full ETE GSM8K/replay after compacted P3-P6 changes Co-authored-by: OmX <omx@oh-my-codex.dev>
179 lines
4.1 KiB
Python
179 lines
4.1 KiB
Python
from __future__ import annotations
|
|
|
|
import dataclasses
|
|
from abc import ABC, abstractmethod
|
|
from typing import TYPE_CHECKING, List, Optional
|
|
|
|
import numpy as np
|
|
import numpy.typing as npt
|
|
|
|
from sglang.srt.server_args import ServerArgs
|
|
|
|
if TYPE_CHECKING:
|
|
from sglang.srt.disaggregation.utils import DisaggregationMode
|
|
|
|
|
|
@dataclasses.dataclass
|
|
class KVTransferMetric:
|
|
# Backends that cannot isolate transfer latency can leave this as None.
|
|
transfer_latency_s: Optional[float] = None
|
|
transfer_total_bytes: Optional[int] = None
|
|
|
|
|
|
class KVArgs:
|
|
engine_rank: int
|
|
kv_data_ptrs: List[int]
|
|
kv_data_lens: List[int]
|
|
kv_item_lens: List[int]
|
|
aux_data_ptrs: List[int]
|
|
aux_data_lens: List[int]
|
|
aux_item_lens: List[int]
|
|
state_data_ptrs: List[int]
|
|
state_data_lens: List[int]
|
|
state_item_lens: List[int]
|
|
state_layer_ids: List[int]
|
|
state_type: str # "none", "mamba", "swa"
|
|
# for mamba state different tp slice transfer
|
|
state_dim_per_tensor: List[int] # dimension to slice for each state tensor
|
|
ib_device: str
|
|
ib_traffic_class: str
|
|
gpu_id: int
|
|
kv_head_num: int
|
|
total_kv_head_num: int
|
|
page_size: int
|
|
# for pp prefill
|
|
pp_rank: int
|
|
prefill_start_layer: int
|
|
# for system dp
|
|
system_dp_rank: int
|
|
|
|
|
|
class KVPoll:
|
|
Failed = 0
|
|
Bootstrapping = 1
|
|
WaitingForInput = 2
|
|
Transferring = 3
|
|
Success = 4
|
|
|
|
|
|
class BaseKVManager(ABC):
|
|
"""Base class for managing transfer states"""
|
|
|
|
@abstractmethod
|
|
def __init__(
|
|
self,
|
|
args: KVArgs,
|
|
disaggregation_mode: DisaggregationMode,
|
|
server_args: ServerArgs,
|
|
is_mla_backend: Optional[bool] = False,
|
|
): ...
|
|
|
|
@abstractmethod
|
|
def register_to_bootstrap(self):
|
|
"""Register prefill server info to the bootstrap server."""
|
|
...
|
|
|
|
|
|
class BaseKVSender(ABC):
|
|
|
|
@abstractmethod
|
|
def __init__(
|
|
self,
|
|
mgr: BaseKVManager,
|
|
bootstrap_addr: str,
|
|
bootstrap_room: int,
|
|
dest_tp_ranks: List[int],
|
|
pp_rank: int,
|
|
): ...
|
|
|
|
@abstractmethod
|
|
def init(self, num_kv_indices: int, aux_index: Optional[int] = None):
|
|
"""
|
|
Set req's index metadata locally or notify the decoder server about the kv indices length and aux index.
|
|
"""
|
|
...
|
|
|
|
@abstractmethod
|
|
def send(
|
|
self,
|
|
kv_indices: npt.NDArray[np.int32],
|
|
state_indices: Optional[List[int]] = None,
|
|
):
|
|
"""
|
|
Send the kv cache at the given kv indices and the extra cache/state at the given indices to the decoder server.
|
|
"""
|
|
...
|
|
|
|
@abstractmethod
|
|
def get_transfer_metric(self) -> KVTransferMetric:
|
|
"""Return backend-specific transfer metrics for this sender."""
|
|
...
|
|
|
|
@abstractmethod
|
|
def poll(self) -> KVPoll:
|
|
"""
|
|
Check the status of the kv cache transfer.
|
|
"""
|
|
...
|
|
|
|
@abstractmethod
|
|
def failure_exception(self):
|
|
"""
|
|
Raise an exception if the kv cache transfer fails.
|
|
"""
|
|
...
|
|
|
|
|
|
class BaseKVReceiver(ABC):
|
|
|
|
@abstractmethod
|
|
def __init__(
|
|
self,
|
|
mgr: BaseKVManager,
|
|
bootstrap_addr: str,
|
|
bootstrap_room: Optional[int] = None,
|
|
): ...
|
|
|
|
@abstractmethod
|
|
def init(
|
|
self,
|
|
kv_indices: npt.NDArray[np.int32],
|
|
aux_index: Optional[int] = None,
|
|
state_indices: Optional[List[int]] = None,
|
|
):
|
|
"""
|
|
Set req's index metadata locally or notify the prefill server about the kv indices, aux index, and state_indices.
|
|
"""
|
|
...
|
|
|
|
@abstractmethod
|
|
def poll(self) -> KVPoll:
|
|
"""
|
|
Check the status of the kv cache transfer.
|
|
"""
|
|
...
|
|
|
|
@abstractmethod
|
|
def failure_exception(self):
|
|
"""
|
|
Raise an exception if the kv cache transfer fails.
|
|
"""
|
|
...
|
|
|
|
def clear(self):
|
|
"""
|
|
Clear any internal states.
|
|
"""
|
|
pass
|
|
|
|
def abort(self):
|
|
"""
|
|
Abort the current transfer.
|
|
"""
|
|
pass
|
|
|
|
|
|
class BaseKVBootstrapServer(ABC):
|
|
@abstractmethod
|
|
def __init__(self, host: str, port: int): ...
|