Reuse CP shared KV remaps across layer materialization
CP shared KV materialization repeatedly rebuilt the same logical-page slot remaps and page inverse metadata for each layer. Cache the token and paged remap metadata on the forward batch so MLA KV, index K/scale, and prefetch paths can reuse the layer-independent mapping while still materializing layer-specific data through the existing tai/torch runtime paths. Constraint: Only mapping metadata is batch-scoped; dense KV/index contents remain layer-specific and are not reused. Rejected: Cache fully materialized dense KV/index buffers | would add large per-layer memory residency and invalidation complexity. Confidence: medium Scope-risk: moderate Directive: Do not assume this removes materialize or CP all-reduce cost; profile tai fallback logs and Nsight kernels before attributing E2E gains or losses. Tested: git diff --check Tested: remote g0034 container PYTHONPATH=python python3 -m pytest test/registered/unit/mem_cache/test_cp_shared_kv_runtime.py -q (52 passed, 5 warnings) Not-tested: Full GLM-5 disaggregated E2E performance run
This commit is contained in:
@@ -9,15 +9,14 @@ import torch
|
||||
from sglang.srt.layers.attention.nsa.cp_shared_kv_runtime import (
|
||||
_all_reduce_materialized_buffer_async,
|
||||
_all_reduce_materialized_buffer_range,
|
||||
build_shared_token_kv_slot_remap,
|
||||
build_slot_page_inverse_optimized,
|
||||
build_slot_page_remap,
|
||||
cp_shared_kv_debug_enabled,
|
||||
cp_shared_kv_mla_prefetch_enabled,
|
||||
cp_shared_kv_mla_prefetch_log,
|
||||
cp_shared_kv_mla_prefetch_should_log_layer,
|
||||
filter_locs_mappable_to_physical_pool,
|
||||
filter_pages_mappable_to_physical_pool,
|
||||
get_or_build_shared_paged_buffer_slot_remap,
|
||||
get_or_build_shared_token_kv_slot_remap,
|
||||
materialize_local_paged_buffer_page_slots_into,
|
||||
materialize_local_token_kv_page_slots_into,
|
||||
remap_logical_pages_to_slot_dense_pages,
|
||||
@@ -195,9 +194,9 @@ class CpSharedKVMlaPrefetcher:
|
||||
try:
|
||||
first_layer_id = int(getattr(token_to_kv_pool, "start_layer", 0))
|
||||
kv_cache = token_to_kv_pool.get_key_buffer(first_layer_id)
|
||||
remap = build_shared_token_kv_slot_remap(
|
||||
remap = get_or_build_shared_token_kv_slot_remap(
|
||||
forward_batch,
|
||||
kv_cache=kv_cache,
|
||||
logical_locs=None,
|
||||
remap_logical_pages=real_page_table,
|
||||
layout=layout,
|
||||
page_size=page_size,
|
||||
@@ -612,18 +611,11 @@ class CpSharedKVIndexPrefetcher:
|
||||
page_buffer = token_to_kv_pool.get_index_k_with_scale_buffer(
|
||||
layer_id=first_layer_id
|
||||
)
|
||||
remap_logical_pages = filter_pages_mappable_to_physical_pool(
|
||||
remap = get_or_build_shared_paged_buffer_slot_remap(
|
||||
forward_batch,
|
||||
page_buffer=page_buffer,
|
||||
logical_pages=real_page_table,
|
||||
layout=layout,
|
||||
physical_page_capacity=page_buffer.shape[0],
|
||||
)
|
||||
slot_logical_pages, _ = build_slot_page_remap(remap_logical_pages)
|
||||
logical_page_capacity = max(int(page_buffer.shape[0]) - 1, 0) * (
|
||||
layout.cp_size
|
||||
) + 1
|
||||
page_inverse = build_slot_page_inverse_optimized(
|
||||
slot_logical_pages,
|
||||
logical_page_capacity=logical_page_capacity,
|
||||
)
|
||||
except Exception as exc:
|
||||
_index_prefetch_fallback_log(
|
||||
@@ -639,17 +631,17 @@ class CpSharedKVIndexPrefetcher:
|
||||
layout.cp_rank,
|
||||
layout.cp_size,
|
||||
prefix_pages,
|
||||
int(slot_logical_pages.numel()),
|
||||
int(slot_logical_pages.numel()) + 1,
|
||||
int(remap.slot_logical_pages.numel()),
|
||||
remap.dense_num_pages,
|
||||
page_size,
|
||||
)
|
||||
|
||||
return cls(
|
||||
layout=layout,
|
||||
prefix_pages=prefix_pages,
|
||||
slot_logical_pages=slot_logical_pages,
|
||||
page_inverse=page_inverse,
|
||||
dense_num_pages=int(slot_logical_pages.numel()) + 1,
|
||||
slot_logical_pages=remap.slot_logical_pages,
|
||||
page_inverse=remap.page_inverse,
|
||||
dense_num_pages=remap.dense_num_pages,
|
||||
)
|
||||
|
||||
def _layer_in_pool(self, token_to_kv_pool: Any, layer_id: int) -> bool:
|
||||
|
||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from functools import lru_cache
|
||||
from typing import Any
|
||||
|
||||
import torch
|
||||
|
||||
@@ -15,6 +16,7 @@ logger = logging.getLogger(__name__)
|
||||
_DEBUG_LOG_COUNTS: dict[str, int] = {}
|
||||
_TAI_MATERIALIZE_FALLBACK_LOG_COUNTS: dict[str, int] = {}
|
||||
_TAI_FUSED_MLA_STORE_FALLBACK_LOG_COUNTS: dict[str, int] = {}
|
||||
_SLOT_REMAP_CACHE_LOG_COUNTS: dict[str, int] = {}
|
||||
_MLA_PREFETCH_LOG_PROBE_LAYER = 2
|
||||
_SORT_NVTX_ENABLED = envs.SGLANG_DEBUG_SORT_NVTX.get()
|
||||
|
||||
@@ -65,6 +67,102 @@ class SharedTokenKVSlotRemap:
|
||||
dense_num_pages: int
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SharedPagedBufferSlotRemap:
|
||||
slot_logical_pages: torch.Tensor
|
||||
page_inverse: torch.Tensor
|
||||
dense_pages: torch.Tensor
|
||||
logical_page_capacity: int
|
||||
dense_num_pages: int
|
||||
|
||||
|
||||
def _tensor_identity_key(tensor: torch.Tensor) -> tuple[int, tuple[int, ...], str, str]:
|
||||
return (
|
||||
int(tensor.untyped_storage().data_ptr()),
|
||||
tuple(int(dim) for dim in tensor.shape),
|
||||
str(tensor.dtype),
|
||||
str(tensor.device),
|
||||
)
|
||||
|
||||
|
||||
def _slot_remap_cache_key(
|
||||
*,
|
||||
logical_pages: torch.Tensor,
|
||||
physical_page_capacity: int,
|
||||
layout: CpSharedKVLayout,
|
||||
page_size: int,
|
||||
) -> tuple[object, ...]:
|
||||
return (
|
||||
_tensor_identity_key(logical_pages),
|
||||
int(physical_page_capacity),
|
||||
int(layout.page_size),
|
||||
int(layout.cp_size),
|
||||
int(layout.cp_rank),
|
||||
int(page_size),
|
||||
)
|
||||
|
||||
|
||||
def _log_slot_remap_cache_not_reused(
|
||||
*,
|
||||
kind: str,
|
||||
reason: str,
|
||||
cached_key: tuple[object, ...] | None,
|
||||
new_key: tuple[object, ...],
|
||||
forward_batch: Any,
|
||||
layout: CpSharedKVLayout,
|
||||
limit: int = 8,
|
||||
) -> None:
|
||||
log_key = f"{kind}:{reason}"
|
||||
count = _SLOT_REMAP_CACHE_LOG_COUNTS.get(log_key, 0)
|
||||
if count >= limit:
|
||||
return
|
||||
_SLOT_REMAP_CACHE_LOG_COUNTS[log_key] = count + 1
|
||||
logger.warning(
|
||||
"CP shared KV %s slot remap cache not reused (%s): "
|
||||
"cp_rank=%s cp_size=%s batch_size=%s forward_mode=%s "
|
||||
"cached_key=%s new_key=%s",
|
||||
kind,
|
||||
reason,
|
||||
layout.cp_rank,
|
||||
layout.cp_size,
|
||||
getattr(forward_batch, "batch_size", None),
|
||||
getattr(forward_batch, "forward_mode", None),
|
||||
cached_key,
|
||||
new_key,
|
||||
)
|
||||
|
||||
|
||||
def _maybe_log_slot_remap_cache_not_reused(
|
||||
*,
|
||||
kind: str,
|
||||
cached_key: tuple[object, ...] | None,
|
||||
cached: object | None,
|
||||
new_key: tuple[object, ...],
|
||||
forward_batch: Any,
|
||||
layout: CpSharedKVLayout,
|
||||
) -> None:
|
||||
if cached is None and cached_key is None:
|
||||
# First build for this forward batch. This is the expected cold path.
|
||||
return
|
||||
if cached is None:
|
||||
reason = "missing_cached_value"
|
||||
elif cached_key is None:
|
||||
reason = "missing_cached_key"
|
||||
elif cached_key != new_key:
|
||||
reason = "key_mismatch"
|
||||
else:
|
||||
return
|
||||
|
||||
_log_slot_remap_cache_not_reused(
|
||||
kind=kind,
|
||||
reason=reason,
|
||||
cached_key=cached_key,
|
||||
new_key=new_key,
|
||||
forward_batch=forward_batch,
|
||||
layout=layout,
|
||||
)
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def _load_tai_materialize_kernels():
|
||||
try:
|
||||
@@ -863,6 +961,126 @@ def build_shared_token_kv_slot_remap(
|
||||
)
|
||||
|
||||
|
||||
def build_shared_paged_buffer_slot_remap(
|
||||
page_buffer: torch.Tensor,
|
||||
logical_pages: torch.Tensor,
|
||||
layout: CpSharedKVLayout,
|
||||
) -> SharedPagedBufferSlotRemap:
|
||||
"""Build the fixed slot-layout remap used by shared paged-buffer materialize."""
|
||||
|
||||
_debug_assert_no_negative_tensor_values(
|
||||
logical_pages,
|
||||
context="CP shared KV paged materialize",
|
||||
tensor_name="logical_pages",
|
||||
)
|
||||
logical_pages = filter_pages_mappable_to_physical_pool(
|
||||
logical_pages=logical_pages,
|
||||
layout=layout,
|
||||
physical_page_capacity=page_buffer.shape[0],
|
||||
)
|
||||
slot_logical_pages, dense_pages = build_slot_page_remap(logical_pages)
|
||||
logical_page_capacity = max(int(page_buffer.shape[0]) - 1, 0) * (
|
||||
layout.cp_size
|
||||
) + 1
|
||||
page_inverse = build_slot_page_inverse_optimized(
|
||||
slot_logical_pages,
|
||||
logical_page_capacity=logical_page_capacity,
|
||||
)
|
||||
return SharedPagedBufferSlotRemap(
|
||||
slot_logical_pages=slot_logical_pages,
|
||||
page_inverse=page_inverse,
|
||||
dense_pages=dense_pages,
|
||||
logical_page_capacity=logical_page_capacity,
|
||||
dense_num_pages=int(slot_logical_pages.numel()) + 1,
|
||||
)
|
||||
|
||||
|
||||
def get_or_build_shared_token_kv_slot_remap(
|
||||
forward_batch: Any,
|
||||
*,
|
||||
kv_cache: torch.Tensor,
|
||||
remap_logical_pages: torch.Tensor,
|
||||
layout: CpSharedKVLayout,
|
||||
page_size: int,
|
||||
) -> SharedTokenKVSlotRemap:
|
||||
"""Cache token-KV materialize slot metadata on a forward batch.
|
||||
|
||||
The logical page table and local physical pool capacity are batch-scoped,
|
||||
not layer-scoped. The actual KV rows are still materialized per layer, but
|
||||
the page inverse used to map layer-specific top-k locs into the dense view
|
||||
can be reused across all layers in the same forward pass.
|
||||
"""
|
||||
|
||||
physical_page_capacity = kv_cache.shape[0] // page_size
|
||||
key = _slot_remap_cache_key(
|
||||
logical_pages=remap_logical_pages,
|
||||
physical_page_capacity=physical_page_capacity,
|
||||
layout=layout,
|
||||
page_size=page_size,
|
||||
)
|
||||
cached_key = getattr(forward_batch, "cp_shared_kv_token_slot_remap_key", None)
|
||||
cached = getattr(forward_batch, "cp_shared_kv_token_slot_remap", None)
|
||||
if cached is not None and cached_key == key:
|
||||
return cached
|
||||
_maybe_log_slot_remap_cache_not_reused(
|
||||
kind="token",
|
||||
cached_key=cached_key,
|
||||
cached=cached,
|
||||
new_key=key,
|
||||
forward_batch=forward_batch,
|
||||
layout=layout,
|
||||
)
|
||||
|
||||
remap = build_shared_token_kv_slot_remap(
|
||||
kv_cache=kv_cache,
|
||||
logical_locs=None,
|
||||
remap_logical_pages=remap_logical_pages,
|
||||
layout=layout,
|
||||
page_size=page_size,
|
||||
)
|
||||
forward_batch.cp_shared_kv_token_slot_remap_key = key
|
||||
forward_batch.cp_shared_kv_token_slot_remap = remap
|
||||
return remap
|
||||
|
||||
|
||||
def get_or_build_shared_paged_buffer_slot_remap(
|
||||
forward_batch: Any,
|
||||
*,
|
||||
page_buffer: torch.Tensor,
|
||||
logical_pages: torch.Tensor,
|
||||
layout: CpSharedKVLayout,
|
||||
) -> SharedPagedBufferSlotRemap:
|
||||
"""Cache paged-buffer materialize slot metadata on a forward batch."""
|
||||
|
||||
key = _slot_remap_cache_key(
|
||||
logical_pages=logical_pages,
|
||||
physical_page_capacity=page_buffer.shape[0],
|
||||
layout=layout,
|
||||
page_size=layout.page_size,
|
||||
)
|
||||
cached_key = getattr(forward_batch, "cp_shared_kv_paged_slot_remap_key", None)
|
||||
cached = getattr(forward_batch, "cp_shared_kv_paged_slot_remap", None)
|
||||
if cached is not None and cached_key == key:
|
||||
return cached
|
||||
_maybe_log_slot_remap_cache_not_reused(
|
||||
kind="paged",
|
||||
cached_key=cached_key,
|
||||
cached=cached,
|
||||
new_key=key,
|
||||
forward_batch=forward_batch,
|
||||
layout=layout,
|
||||
)
|
||||
|
||||
remap = build_shared_paged_buffer_slot_remap(
|
||||
page_buffer=page_buffer,
|
||||
logical_pages=logical_pages,
|
||||
layout=layout,
|
||||
)
|
||||
forward_batch.cp_shared_kv_paged_slot_remap_key = key
|
||||
forward_batch.cp_shared_kv_paged_slot_remap = remap
|
||||
return remap
|
||||
|
||||
|
||||
def remap_logical_locs_to_dense_locs(
|
||||
logical_locs: torch.Tensor,
|
||||
unique_logical_pages: torch.Tensor,
|
||||
@@ -1483,6 +1701,7 @@ def materialize_shared_token_kv_buffer(
|
||||
page_size: int,
|
||||
remap_logical_locs: torch.Tensor | None = None,
|
||||
remap_logical_pages: torch.Tensor | None = None,
|
||||
slot_remap: SharedTokenKVSlotRemap | None = None,
|
||||
) -> tuple[torch.Tensor, torch.Tensor]:
|
||||
_debug_assert_no_tensor_values_below(
|
||||
logical_locs,
|
||||
@@ -1511,7 +1730,15 @@ def materialize_shared_token_kv_buffer(
|
||||
)
|
||||
|
||||
dense_kv_cache = None
|
||||
if remap_logical_pages is None:
|
||||
if slot_remap is not None:
|
||||
materialized_logical_pages = slot_remap.slot_logical_pages
|
||||
dense_locs = remap_logical_locs_to_slot_dense_locs_optimized(
|
||||
logical_locs,
|
||||
page_inverse=slot_remap.page_inverse,
|
||||
page_size=page_size,
|
||||
)
|
||||
use_slot_materialize = True
|
||||
elif remap_logical_pages is None:
|
||||
remap_pages_from_locs = logical_pages_from_locs(remap_logical_locs, page_size)
|
||||
materialized_logical_pages, _ = build_dense_page_remap(remap_pages_from_locs)
|
||||
dense_locs = remap_logical_locs_to_dense_locs(
|
||||
@@ -1624,6 +1851,7 @@ def materialize_shared_paged_buffer(
|
||||
page_buffer: torch.Tensor,
|
||||
logical_pages: torch.Tensor,
|
||||
layout: CpSharedKVLayout,
|
||||
slot_remap: SharedPagedBufferSlotRemap | None = None,
|
||||
) -> tuple[torch.Tensor, torch.Tensor]:
|
||||
_debug_assert_no_negative_tensor_values(
|
||||
logical_pages,
|
||||
@@ -1640,16 +1868,24 @@ def materialize_shared_paged_buffer(
|
||||
logical_pages=logical_pages,
|
||||
layout=layout,
|
||||
)
|
||||
if tai_result is None:
|
||||
materialized_logical_pages, dense_pages = build_slot_page_remap(logical_pages)
|
||||
if tai_result is not None:
|
||||
dense_page_buffer, dense_pages = tai_result
|
||||
materialized_logical_pages = logical_pages.reshape(-1)
|
||||
elif slot_remap is not None:
|
||||
materialized_logical_pages = slot_remap.slot_logical_pages
|
||||
dense_pages = slot_remap.dense_pages
|
||||
dense_page_buffer = materialize_local_paged_buffer_page_slots(
|
||||
page_buffer=page_buffer,
|
||||
slot_logical_pages=materialized_logical_pages,
|
||||
layout=layout,
|
||||
)
|
||||
else:
|
||||
dense_page_buffer, dense_pages = tai_result
|
||||
materialized_logical_pages = logical_pages.reshape(-1)
|
||||
materialized_logical_pages, dense_pages = build_slot_page_remap(logical_pages)
|
||||
dense_page_buffer = materialize_local_paged_buffer_page_slots(
|
||||
page_buffer=page_buffer,
|
||||
slot_logical_pages=materialized_logical_pages,
|
||||
layout=layout,
|
||||
)
|
||||
|
||||
if cp_shared_kv_debug_enabled():
|
||||
owned_pages = materialized_logical_pages[
|
||||
|
||||
@@ -19,6 +19,7 @@ from sglang.srt.layers.attention.nsa.cp_shared_kv_runtime import (
|
||||
cp_shared_kv_debug_log,
|
||||
cp_shared_kv_current_reuse_enabled,
|
||||
filter_owned_logical_locs,
|
||||
get_or_build_shared_paged_buffer_slot_remap,
|
||||
is_current_only_extend_batch,
|
||||
materialize_shared_paged_buffer,
|
||||
tensor_debug_checksum,
|
||||
@@ -344,10 +345,17 @@ class Indexer(MultiPlatformOp):
|
||||
layer_id,
|
||||
tensor_debug_summary(logical_page_table),
|
||||
)
|
||||
slot_remap = get_or_build_shared_paged_buffer_slot_remap(
|
||||
forward_batch,
|
||||
page_buffer=index_buffer,
|
||||
logical_pages=logical_page_table,
|
||||
layout=layout,
|
||||
)
|
||||
materialized, dense_pages = materialize_shared_paged_buffer(
|
||||
page_buffer=index_buffer,
|
||||
logical_pages=logical_page_table,
|
||||
layout=layout,
|
||||
slot_remap=slot_remap,
|
||||
)
|
||||
if cp_shared_kv_debug_enabled():
|
||||
cp_shared_kv_debug_log(
|
||||
|
||||
@@ -22,6 +22,7 @@ from sglang.srt.layers.attention.nsa.cp_shared_kv_runtime import (
|
||||
cp_shared_kv_mla_prefetch_log_enabled,
|
||||
cp_shared_kv_mla_prefetch_should_log_layer,
|
||||
filter_owned_logical_locs,
|
||||
get_or_build_shared_token_kv_slot_remap,
|
||||
is_current_only_extend_batch,
|
||||
materialize_shared_token_kv_buffer,
|
||||
tensor_debug_checksum,
|
||||
@@ -1730,6 +1731,13 @@ class NativeSparseAttnBackend(
|
||||
if prefetched_kv is not None:
|
||||
kv_cache, page_table_1 = prefetched_kv
|
||||
else:
|
||||
slot_remap = get_or_build_shared_token_kv_slot_remap(
|
||||
forward_batch,
|
||||
kv_cache=kv_cache,
|
||||
remap_logical_pages=metadata.real_page_table,
|
||||
layout=forward_batch.cp_shared_kv_layout,
|
||||
page_size=forward_batch.token_to_kv_pool.page_size,
|
||||
)
|
||||
kv_cache, page_table_1 = materialize_shared_token_kv_buffer(
|
||||
kv_cache=kv_cache,
|
||||
logical_locs=page_table_1,
|
||||
@@ -1737,6 +1745,7 @@ class NativeSparseAttnBackend(
|
||||
remap_logical_pages=metadata.real_page_table,
|
||||
layout=forward_batch.cp_shared_kv_layout,
|
||||
page_size=forward_batch.token_to_kv_pool.page_size,
|
||||
slot_remap=slot_remap,
|
||||
)
|
||||
|
||||
if mla_prefetcher is not None:
|
||||
|
||||
@@ -425,6 +425,10 @@ class ForwardBatch(ForwardBatchDeepSeekMHAMixin):
|
||||
cp_local_out_cache_loc: Optional[torch.Tensor] = None
|
||||
cp_local_physical_out_cache_loc: Optional[torch.Tensor] = None
|
||||
cp_shared_mla_direct_write_done: bool = False
|
||||
cp_shared_kv_token_slot_remap_key: Optional[Any] = None
|
||||
cp_shared_kv_token_slot_remap: Optional[Any] = None
|
||||
cp_shared_kv_paged_slot_remap_key: Optional[Any] = None
|
||||
cp_shared_kv_paged_slot_remap: Optional[Any] = None
|
||||
cp_shared_kv_mla_prefetcher: Optional[Any] = None
|
||||
cp_shared_kv_index_prefetcher: Optional[Any] = None
|
||||
|
||||
|
||||
Reference in New Issue
Block a user