Support mxfp8 KV cache in PD transfer (#35718)

This commit is contained in:
Ke Bao
2026-08-21 13:05:06 +08:00
committed by GitHub
parent f64080fbaf
commit 73a2c117c6
8 changed files with 121 additions and 15 deletions
@@ -24,6 +24,11 @@ class StateType(str, enum.Enum):
SWA_RING = "swa_ring"
# DeepSeek-V4 online C128 request-scoped state.
C128_STATE = "c128_state"
# A block-scaled KV dtype keeps its per-block scales in buffers parallel to
# K/V, one component per sub-pool so each carries the index payload of the
# KV it describes (whole sequence for full attention, window for SWA).
BLOCK_SCALE = "block_scale"
BLOCK_SCALE_SWA = "block_scale_swa"
@dataclasses.dataclass
+5 -5
View File
@@ -1379,7 +1379,7 @@ class DecodePreallocQueue(DecodeHiCachePreallocMixin):
)
return kv_to_page_indices(window_kv_indices_swa, page_size)
def _dsa_payload():
def _full_kv_pages_payload():
kv_indices_full = self.req_to_token_pool.req_to_token[
decode_req.req.req_pool_idx, :seq_len
]
@@ -1415,15 +1415,15 @@ class DecodePreallocQueue(DecodeHiCachePreallocMixin):
)
if clear_c128_state is not None:
clear_c128_state(int(decode_req.req.req_pool_idx))
# MINIMAX_INDEX_K reuses _dsa_payload: index rows live at the same loc
# as main KV on the same page_size.
payloads = {
StateType.MAMBA: _mamba_payload,
StateType.SWA: _swa_payload,
StateType.DSA: _dsa_payload,
StateType.MINIMAX_INDEX_K: _dsa_payload,
StateType.DSA: _full_kv_pages_payload,
StateType.MINIMAX_INDEX_K: _full_kv_pages_payload,
StateType.SWA_RING: _swa_ring_payload,
StateType.C128_STATE: _c128_state_payload,
StateType.BLOCK_SCALE: _full_kv_pages_payload,
StateType.BLOCK_SCALE_SWA: _swa_payload,
}
if _is_npu and isinstance(self.token_to_kv_pool, DeepSeekV4TokenToKVPool):
from sglang.srt.hardware_backend.npu.dsv4.dsv4_common_hooks import (
@@ -1255,6 +1255,8 @@ class MooncakeKVManager(CommonKVManager):
StateType.DSA,
StateType.SWA_RING,
StateType.C128_STATE,
StateType.BLOCK_SCALE,
StateType.BLOCK_SCALE_SWA,
)
def _requires_exact_state_index_match(self, st: StateType) -> bool:
+5 -5
View File
@@ -1212,7 +1212,7 @@ class SchedulerDisaggregationPrefillMixin:
)
return kv_to_page_indices(window_kv_indices_swa, page_size)
def _dsa_payload():
def _full_kv_pages_payload():
kv_indices_full = self.req_to_token_pool.req_to_token[
req.req_pool_idx, :seq_len
]
@@ -1250,15 +1250,15 @@ class SchedulerDisaggregationPrefillMixin:
state_types = (
self.disagg_prefill_bootstrap_queue.kv_manager.kv_args.state_types
)
# MINIMAX_INDEX_K reuses _dsa_payload: index rows live at the same loc
# as main KV on the same page_size.
payloads = {
StateType.MAMBA: _mamba_payload,
StateType.SWA: _swa_payload,
StateType.DSA: _dsa_payload,
StateType.MINIMAX_INDEX_K: _dsa_payload,
StateType.DSA: _full_kv_pages_payload,
StateType.MINIMAX_INDEX_K: _full_kv_pages_payload,
StateType.SWA_RING: _swa_ring_payload,
StateType.C128_STATE: _c128_state_payload,
StateType.BLOCK_SCALE: _full_kv_pages_payload,
StateType.BLOCK_SCALE_SWA: _swa_payload,
}
if _is_npu and isinstance(
self.token_to_kv_pool_allocator.get_kvcache(),
+26
View File
@@ -1051,8 +1051,10 @@ def setup_state_kv_args(
from sglang.srt.mem_cache.memory_pool import (
DSATokenToKVPool,
HybridLinearKVPool,
MHATokenToKVPoolMXFP8,
MiniMaxSparseKVPool,
)
from sglang.srt.mem_cache.swa_memory_pool import SWAKVPool
kv_args.state_types = []
kv_args.state_data_ptrs = []
@@ -1064,6 +1066,13 @@ def setup_state_kv_args(
kv_args.is_hybrid_mla_backend = False
kv_args.state_conv_shard_groups = []
if isinstance(token_to_kv_pool, MHATokenToKVPoolMXFP8):
append_state_component(
kv_args,
StateType.BLOCK_SCALE,
*token_to_kv_pool.get_kv_scale_buf_infos(),
)
if isinstance(token_to_kv_pool, MiniMaxSparseKVPool):
if token_to_kv_pool.index_kv_pool is not None:
raise NotImplementedError(
@@ -1082,6 +1091,23 @@ def setup_state_kv_args(
append_state_component(
kv_args, StateType.SWA, data_ptrs, data_lens, item_lens
)
# MXFP8 KV: each sub-pool's block scales ride as their own component
# so they inherit the index payload of the KV they describe.
# Only the concrete SWAKVPool owns a full sub-pool; other
# BaseSWAKVPool implementations describe their state per entry.
if isinstance(token_to_kv_pool, SWAKVPool) and isinstance(
token_to_kv_pool.full_kv_pool, MHATokenToKVPoolMXFP8
):
append_state_component(
kv_args,
StateType.BLOCK_SCALE,
*token_to_kv_pool.get_kv_scale_buf_infos(),
)
append_state_component(
kv_args,
StateType.BLOCK_SCALE_SWA,
*token_to_kv_pool.get_swa_kv_scale_buf_infos(),
)
# unified_kv: the SWA ring lives in the unified buffers (no separate
# swa_kv_pool) and is addressed per-row, so ship it as SWA_RING.
if getattr(token_to_kv_pool, "_unified_kv", False) and hasattr(
+18 -5
View File
@@ -3403,6 +3403,9 @@ class MHATokenToKVPoolMXFP8(MHATokenToKVPool):
],
device=self.device,
)
# This override replaces the base allocation, so the PD-transfer
# descriptors for the packed data buffers are built here too.
self._kv_buffer_descs = self._build_kv_buffer_descs()
def _clear_buffers(self):
del self.k_buffer
@@ -3552,11 +3555,21 @@ class MHATokenToKVPoolMXFP8(MHATokenToKVPool):
def load_cpu_copy(self, kv_cache_cpu, indices, mamba_indices=None):
raise NotImplementedError("CPU offloading is unsupported for MXFP8 KV cache.")
def get_contiguous_buf_infos(self):
raise NotImplementedError(
"KV transfer / disaggregation is unsupported for MXFP8 KV cache "
"(scale buffers are not exposed)."
)
def get_kv_scale_buf_infos(self):
"""(ptrs, lens, item_lens) for the UE8M0 scale buffers, k then v.
The interleaved layout puts pages on the leading axis, so a page's
scales are one contiguous row; the flat layout is per slot.
"""
tensors = self.k_scale_buffer + self.v_scale_buffer
ptrs = [t.data_ptr() for t in tensors]
lens = [t.nbytes for t in tensors]
row_bytes = [t[0].nbytes for t in tensors]
if self.mxfp8_sf_interleaved:
item_lens = row_bytes
else:
item_lens = [rb * self.page_size for rb in row_bytes]
return ptrs, lens, item_lens
def set_kv_buffer_prefix_valid(self, *args, **kwargs):
raise NotImplementedError(
@@ -137,6 +137,12 @@ class SWAKVPool(BaseSWAKVPool):
full_kv_item_lens,
)
def get_kv_scale_buf_infos(self):
return self.full_kv_pool.get_kv_scale_buf_infos()
def get_swa_kv_scale_buf_infos(self):
return self.swa_kv_pool.get_kv_scale_buf_infos()
def get_state_buf_infos(self):
swa_kv_data_ptrs, swa_kv_data_lens, swa_kv_item_lens = (
self.swa_kv_pool.get_contiguous_buf_infos()