Surface CP shared-KV fast-path misses

CP shared-KV performance debugging depends on seeing when the runtime leaves the intended TAI, IPC, prefetch, or current-reuse paths. This change makes those misses visible through standardized warning markers while keeping per-reason log limits to avoid per-layer log floods.\n\nThe warnings intentionally distinguish fallback from fail-fast: unsupported correctness-sensitive states still raise, while performance-path misses emit [CP_SHARED_KV_FALLBACK] with the concrete reason.\n\nConstraint: Production ETE debugging needs visible fallback evidence without enabling heavy debug mode, which can itself disable fast paths.\nRejected: Rely only on optional MLA prefetch debug logs | they are env-gated, layer-limited, and miss non-prefetch TAI/IPC/current-reuse fallbacks.\nRejected: Log every per-layer event without limits | would drown useful transfer/cache diagnostics under steady-state traffic.\nConfidence: high\nScope-risk: moderate\nDirective: Do not remove these fallback warnings unless an equivalent low-noise observability path exists for every fast-path miss.\nTested: local py_compile for touched files; local git diff --check for touched files; remote g0034 py_compile and pytest for test_nsa_cp_utils.py, test_cp_shared_kv_layout.py, test_cp_shared_kv_runtime.py passed before commit (156 passed, 5 warnings, 2 subtests passed).\nNot-tested: full ETE serving traffic after warning additions.
This commit is contained in:
laoyao0822
2026-06-02 06:47:40 +08:00
parent 6a25c312c7
commit 8be4a3a8b5
4 changed files with 489 additions and 20 deletions

View File

@@ -21,6 +21,7 @@ _DEBUG_LOG_COUNTS: dict[str, int] = {}
_TAI_MATERIALIZE_FALLBACK_LOG_COUNTS: dict[str, int] = {}
_TAI_IPC_MATERIALIZE_FALLBACK_LOG_COUNTS: dict[str, int] = {}
_TAI_FUSED_MLA_STORE_FALLBACK_LOG_COUNTS: dict[str, int] = {}
_TAI_INDEX_MQA_PREPARE_FALLBACK_LOG_COUNTS: dict[str, int] = {}
_CURRENT_REUSE_FALLBACK_LOG_COUNTS: dict[str, int] = {}
_SLOT_REMAP_CACHE_LOG_COUNTS: dict[str, int] = {}
_TAI_IPC_PEER_PTR_CACHE: dict[tuple[object, ...], torch.Tensor] = {}
@@ -240,9 +241,11 @@ def _log_slot_remap_cache_not_reused(
return
_SLOT_REMAP_CACHE_LOG_COUNTS[log_key] = count + 1
logger.warning(
"[CP_SHARED_KV_FALLBACK][slot_remap_cache] reason=%s "
"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",
reason,
kind,
reason,
layout.cp_rank,
@@ -635,6 +638,64 @@ def _log_tai_fused_mla_store_fallback(
)
def _log_tai_index_mqa_prepare_fallback(
key: str,
message: str,
*args,
limit: int = 8,
) -> None:
count = _TAI_INDEX_MQA_PREPARE_FALLBACK_LOG_COUNTS.get(key, 0)
if count >= limit:
return
_TAI_INDEX_MQA_PREPARE_FALLBACK_LOG_COUNTS[key] = count + 1
logger.warning(
"[CP_SHARED_KV_FALLBACK][tai_index_mqa_prepare] reason=%s " + message,
key,
*args,
)
def _tai_materialize_runtime_disabled_reason() -> str | None:
if not cp_shared_kv_tai_materialize_enabled():
return "env_disabled"
if cp_shared_kv_debug_enabled():
return "debug_enabled"
return None
def _log_tai_materialize_runtime_disabled(
source: str,
*,
ipc: bool = False,
limit: int = 1,
) -> None:
reason = _tai_materialize_runtime_disabled_reason()
if reason is None:
return
if ipc:
_log_tai_ipc_materialize_fallback(
reason,
"CP shared KV tai IPC materialize fast path is disabled; "
"falling back to collective materialize. source=%s "
"tai_materialize_env=%s debug=%s",
source,
cp_shared_kv_tai_materialize_enabled(),
cp_shared_kv_debug_enabled(),
limit=limit,
)
else:
_log_tai_materialize_fallback(
reason,
"CP shared KV tai materialize fast path is disabled; "
"falling back to torch/collective materialize. source=%s "
"tai_materialize_env=%s debug=%s",
source,
cp_shared_kv_tai_materialize_enabled(),
cp_shared_kv_debug_enabled(),
limit=limit,
)
def _contiguous_for_tai(tensor: torch.Tensor) -> torch.Tensor:
return tensor if tensor.is_contiguous() else tensor.contiguous()
@@ -657,17 +718,63 @@ def try_tai_prepare_cp_mqa_index(
"""
if not cp_shared_kv_tai_index_mqa_prepare_enabled():
_log_tai_index_mqa_prepare_fallback(
"env_disabled",
"CP shared KV TAI index MQA prepare fast path is disabled; "
"falling back to GetK/GetS plus torch range prepare. kv_len=%s "
"valid_q_count=%s page_size=%s index_head_dim=%s",
kv_len,
valid_q_count,
page_size,
index_head_dim,
limit=1,
)
return None
kernel = _load_tai_index_mqa_prepare_kernel()
if kernel is None:
_log_tai_index_mqa_prepare_fallback(
"kernel_missing",
"CP shared KV TAI index MQA prepare kernel is unavailable; "
"falling back to GetK/GetS plus torch range prepare. kv_len=%s "
"valid_q_count=%s page_size=%s index_head_dim=%s",
kv_len,
valid_q_count,
page_size,
index_head_dim,
limit=1,
)
return None
if index_buffer.dtype != torch.uint8:
_log_tai_index_mqa_prepare_fallback(
"unsupported_dtype",
"CP shared KV TAI index MQA prepare requires uint8 page buffer; "
"falling back to GetK/GetS. dtype=%s",
index_buffer.dtype,
limit=4,
)
return None
if not index_buffer.is_contiguous():
_log_tai_index_mqa_prepare_fallback(
"non_contiguous",
"CP shared KV TAI index MQA prepare requires contiguous page buffer; "
"falling back to GetK/GetS. shape=%s stride=%s",
tuple(index_buffer.shape),
tuple(index_buffer.stride()),
limit=4,
)
return None
if index_head_dim != 128 or page_size != 64:
_log_tai_index_mqa_prepare_fallback(
"unsupported_layout",
"CP shared KV TAI index MQA prepare supports page_size=64 and "
"index_head_dim=128 only; falling back to GetK/GetS. "
"page_size=%s index_head_dim=%s",
page_size,
index_head_dim,
limit=4,
)
return None
try:
@@ -680,7 +787,16 @@ def try_tai_prepare_cp_mqa_index(
page_size=int(page_size),
index_head_dim=int(index_head_dim),
)
except Exception:
except Exception as exc:
_log_tai_index_mqa_prepare_fallback(
"kernel_failed",
"CP shared KV TAI index MQA prepare failed; falling back to "
"GetK/GetS plus torch range prepare. error=%s kv_len=%s "
"valid_q_count=%s",
exc,
kv_len,
valid_q_count,
)
return None
@@ -697,10 +813,26 @@ def try_tai_prepare_cp_mqa_range(
"""
if not cp_shared_kv_tai_index_mqa_prepare_enabled():
_log_tai_index_mqa_prepare_fallback(
"range_env_disabled",
"CP shared KV TAI MQA range fast path is disabled; falling back to "
"torch range prepare. valid_q_count=%s ke_start=%s",
valid_q_count,
ke_start,
limit=1,
)
return None
kernel = _load_tai_index_mqa_range_kernel()
if kernel is None:
_log_tai_index_mqa_prepare_fallback(
"range_kernel_missing",
"CP shared KV TAI MQA range kernel is unavailable; falling back to "
"torch range prepare. valid_q_count=%s ke_start=%s",
valid_q_count,
ke_start,
limit=1,
)
return None
try:
@@ -709,7 +841,15 @@ def try_tai_prepare_cp_mqa_range(
ke_start=int(ke_start),
device=device,
)
except Exception:
except Exception as exc:
_log_tai_index_mqa_prepare_fallback(
"range_kernel_failed",
"CP shared KV TAI MQA range prepare failed; falling back to torch "
"range prepare. error=%s valid_q_count=%s ke_start=%s",
exc,
valid_q_count,
ke_start,
)
return None
@@ -732,6 +872,15 @@ def try_tai_fused_mla_store(
"""
if not cp_shared_kv_tai_fused_mla_store_enabled():
_log_tai_fused_mla_store_fallback(
"env_disabled",
"CP shared KV tai fused MLA store fast path is disabled; falling "
"back to torch MLA store. layer=%s cp_rank=%s tokens=%s",
getattr(layer, "layer_id", None),
layout.cp_rank,
logical_locs.numel(),
limit=1,
)
return False
if cp_shared_kv_debug_enabled():
_log_tai_fused_mla_store_fallback(
@@ -787,6 +936,7 @@ def _try_tai_materialize_shared_pages(
layout: CpSharedKVLayout,
) -> tuple[torch.Tensor, torch.Tensor] | None:
if not _tai_materialize_runtime_enabled():
_log_tai_materialize_runtime_disabled("paged_materialize")
return None
kernels = _load_tai_materialize_kernels()
@@ -819,6 +969,7 @@ def _try_tai_materialize_token_kv_pages_and_locs(
page_size: int,
) -> tuple[torch.Tensor, torch.Tensor] | None:
if not _tai_materialize_runtime_enabled():
_log_tai_materialize_runtime_disabled("token_pages_and_locs")
return None
kernels = _load_tai_materialize_kernels()
@@ -859,6 +1010,7 @@ def _try_tai_build_slot_page_inverse(
logical_page_capacity: int,
) -> torch.Tensor | None:
if not _tai_materialize_runtime_enabled():
_log_tai_materialize_runtime_disabled("slot_page_inverse")
return None
kernels = _load_tai_materialize_kernels()
@@ -936,6 +1088,7 @@ def _try_tai_fill_current_kv_page_slots_and_remap_locs(
mask_non_current_in_current_pages: bool,
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor] | None:
if not _tai_materialize_runtime_enabled():
_log_tai_materialize_runtime_disabled("fill_current_kv_page_slots")
return None
kernels = _load_tai_materialize_kernels()
@@ -1091,6 +1244,18 @@ def fill_current_index_page_slots(
current_rows = int(current_locs.numel())
if current_rows == 0:
return dense_page_buffer
if dense_page_buffer.is_cuda:
_log_tai_materialize_fallback(
"index_current_fill_torch_reference_cuda",
"CP shared KV index current-slot fill is using the torch reference "
"on CUDA; this is a fallback and should not be the steady-state hot "
"path. page_size=%s index_head_dim=%s current_rows=%s dense_pages=%s",
page_size,
index_head_dim,
current_rows,
int(dense_page_buffer.shape[0]),
limit=1,
)
if (
int(current_index_k.shape[0]) < current_rows
or int(current_index_scale.shape[0]) < current_rows
@@ -1194,6 +1359,7 @@ def _try_tai_materialize_token_kv_page_slots_into(
end_slot: int,
) -> bool:
if not _tai_materialize_runtime_enabled():
_log_tai_materialize_runtime_disabled("token_kv_page_slots_into")
return False
kernels = _load_tai_materialize_kernels()
@@ -1311,6 +1477,7 @@ def _get_or_open_tai_ipc_peer_ptrs(
if layout.cp_size <= 1:
return None
if not _tai_materialize_runtime_enabled():
_log_tai_materialize_runtime_disabled("open_ipc_peer_ptrs", ipc=True)
return None
if not tensor.is_cuda or not tensor.is_contiguous():
_log_tai_ipc_materialize_fallback(
@@ -1408,8 +1575,30 @@ def _try_tai_ipc_materialize_token_kv_page_slots_into(
if start_slot == end_slot:
return True
if start_slot != 0:
_log_tai_ipc_materialize_fallback(
"start_slot_nonzero",
"CP shared KV tai IPC token materialize only supports slot ranges "
"starting at zero; falling back to local materialize plus "
"collective. cp_rank=%s cp_size=%s start_slot=%s end_slot=%s "
"page_size=%s",
layout.cp_rank,
layout.cp_size,
start_slot,
end_slot,
page_size,
)
return False
if not dense_kv_cache.is_cuda or not dense_kv_cache.is_contiguous():
_log_tai_ipc_materialize_fallback(
"dense_token_tensor_unsupported",
"CP shared KV tai IPC token materialize requires a contiguous CUDA "
"dense tensor; falling back to local materialize plus collective. "
"device=%s contiguous=%s dense_shape=%s",
dense_kv_cache.device,
dense_kv_cache.is_contiguous(),
tuple(dense_kv_cache.shape),
limit=4,
)
return False
ipc_state = _get_or_open_tai_ipc_peer_ptrs(kv_cache, layout)
@@ -1470,8 +1659,28 @@ def _try_tai_ipc_materialize_paged_buffer_page_slots_into(
if start_slot == end_slot:
return True
if start_slot != 0:
_log_tai_ipc_materialize_fallback(
"paged_start_slot_nonzero",
"CP shared KV tai IPC paged materialize only supports slot ranges "
"starting at zero; falling back to local materialize plus "
"collective. cp_rank=%s cp_size=%s start_slot=%s end_slot=%s",
layout.cp_rank,
layout.cp_size,
start_slot,
end_slot,
)
return False
if not dense_page_buffer.is_cuda or not dense_page_buffer.is_contiguous():
_log_tai_ipc_materialize_fallback(
"dense_paged_tensor_unsupported",
"CP shared KV tai IPC paged materialize requires a contiguous CUDA "
"dense tensor; falling back to local materialize plus collective. "
"device=%s contiguous=%s dense_shape=%s",
dense_page_buffer.device,
dense_page_buffer.is_contiguous(),
tuple(dense_page_buffer.shape),
limit=4,
)
return False
ipc_state = _get_or_open_tai_ipc_peer_ptrs(page_buffer, layout)
@@ -1562,32 +1771,72 @@ def can_reuse_current_extend_kv(forward_batch) -> bool:
enabled, in-seq-split mode, and tensor shape compatibility.
"""
if forward_batch is None:
return False
forward_mode = getattr(forward_batch, "forward_mode", None)
if forward_mode is None or not forward_mode.is_extend_without_speculative():
return False
return _current_extend_kv_reuse_miss_reason(forward_batch) is None
if int(getattr(forward_batch, "batch_size", 0)) != 1:
return False
def _current_extend_kv_reuse_miss_reason(forward_batch) -> str | None:
if forward_batch is None:
return "missing_forward_batch"
forward_mode = getattr(forward_batch, "forward_mode", None)
if forward_mode is None:
return "missing_forward_mode"
if not forward_mode.is_extend_without_speculative():
return "not_extend_without_speculative"
batch_size = int(getattr(forward_batch, "batch_size", 0))
if batch_size != 1:
return "batch_size_not_one"
extend_seq_lens_cpu = getattr(forward_batch, "extend_seq_lens_cpu", None)
seq_lens_cpu = getattr(forward_batch, "seq_lens_cpu", None)
out_cache_loc = getattr(forward_batch, "out_cache_loc", None)
if extend_seq_lens_cpu is None or seq_lens_cpu is None or out_cache_loc is None:
return False
if len(extend_seq_lens_cpu) != 1 or int(seq_lens_cpu.numel()) != 1:
return False
if extend_seq_lens_cpu is None:
return "missing_extend_seq_lens_cpu"
if seq_lens_cpu is None:
return "missing_seq_lens_cpu"
if out_cache_loc is None:
return "missing_out_cache_loc"
if len(extend_seq_lens_cpu) != 1:
return "extend_batch_not_one"
if int(seq_lens_cpu.numel()) != 1:
return "seq_lens_batch_not_one"
extend_len = int(extend_seq_lens_cpu[0])
seq_len = int(seq_lens_cpu[0].item())
if extend_len <= 0 or seq_len < extend_len:
return False
if extend_len <= 0:
return "non_positive_extend_len"
if seq_len < extend_len:
return "seq_len_smaller_than_extend_len"
# ForwardBatch pads tensors such as out_cache_loc at the tail for CUDA graph
# and CP alignment. The first extend_len rows still cover the valid current
# suffix, so padded batches remain eligible for current reuse as long as the
# valid suffix is fully present.
return int(out_cache_loc.numel()) >= extend_len
if int(out_cache_loc.numel()) < extend_len:
return "out_cache_loc_shorter_than_extend"
return None
def _log_current_extend_kv_reuse_miss(forward_batch, reason: str) -> None:
extend_prefix_lens_cpu = getattr(forward_batch, "extend_prefix_lens_cpu", None)
extend_seq_lens_cpu = getattr(forward_batch, "extend_seq_lens_cpu", None)
seq_lens_cpu = getattr(forward_batch, "seq_lens_cpu", None)
out_cache_loc = getattr(forward_batch, "out_cache_loc", None)
_log_current_reuse_fallback(
reason,
"CP shared KV current/partial-current reuse fast path is not used. "
"batch_size=%s forward_mode=%s prefix_lens=%s extend_lens=%s "
"seq_lens_shape=%s out_cache_loc_shape=%s",
getattr(forward_batch, "batch_size", None),
getattr(forward_batch, "forward_mode", None),
[int(x) for x in extend_prefix_lens_cpu]
if extend_prefix_lens_cpu is not None
else None,
[int(x) for x in extend_seq_lens_cpu]
if extend_seq_lens_cpu is not None
else None,
tuple(seq_lens_cpu.shape) if seq_lens_cpu is not None else None,
tuple(out_cache_loc.shape) if out_cache_loc is not None else None,
)
def should_reuse_current_extend_kv(forward_batch) -> bool:
@@ -1602,14 +1851,25 @@ def should_reuse_current_extend_kv(forward_batch) -> bool:
"""
if not cp_shared_kv_current_reuse_enabled():
_log_current_reuse_fallback(
"env_disabled",
"CP shared KV current/partial-current reuse fast path is disabled "
"by SGLANG_CP_SHARED_KV_CURRENT_REUSE. batch_size=%s forward_mode=%s",
getattr(forward_batch, "batch_size", None),
getattr(forward_batch, "forward_mode", None),
limit=1,
)
return False
current_only = is_current_only_extend_batch(forward_batch)
if current_only:
return True
partial_current = can_reuse_current_extend_kv(forward_batch)
return current_only or partial_current
reason = _current_extend_kv_reuse_miss_reason(forward_batch)
if reason is None:
return True
_log_current_extend_kv_reuse_miss(forward_batch, reason)
return False
def current_extend_kv_rows_for_reuse(

View File

@@ -371,6 +371,30 @@ class Indexer(MultiPlatformOp):
)
if prefetched is not None:
return prefetched
_log_cp_shared_kv_index_prefetch_fallback(
"current_consume_miss",
"prefetcher did not provide current index prefix buffer; "
"falling back to sync partial-current index compose. "
"layer=%s cp_rank=%s prefix_lens=%s extend_lens=%s "
"logical_page_table_shape=%s",
layer_id,
layout.cp_rank,
prefix_lens,
extend_lens,
tuple(logical_page_table.shape),
)
else:
_log_cp_shared_kv_index_prefetch_fallback(
"current_missing_prefetcher",
"index prefetcher is unavailable; falling back to sync "
"partial-current index compose. layer=%s cp_rank=%s "
"prefix_lens=%s extend_lens=%s logical_page_table_shape=%s",
layer_id,
layout.cp_rank,
prefix_lens,
extend_lens,
tuple(logical_page_table.shape),
)
slot_remap = get_or_build_shared_paged_buffer_slot_remap(
forward_batch,
page_buffer=index_buffer,
@@ -438,6 +462,15 @@ class Indexer(MultiPlatformOp):
layout.cp_rank,
tuple(logical_page_table.shape),
)
else:
_log_cp_shared_kv_index_prefetch_fallback(
"missing_prefetcher",
"index prefetcher is unavailable; falling back to sync paged "
"materialize. layer=%s cp_rank=%s logical_page_table_shape=%s",
layer_id,
layout.cp_rank,
tuple(logical_page_table.shape),
)
if cp_shared_kv_debug_enabled():
cp_shared_kv_debug_log(

View File

@@ -74,6 +74,24 @@ if TYPE_CHECKING:
_is_hip = is_hip()
logger = logging.getLogger(__name__)
_EAGLE_ACCEPT_DRAFT_MLA_PATH_DEBUG_COUNTS: Dict[Tuple[int, int], int] = {}
_CP_SHARED_KV_MLA_PREFETCH_FALLBACK_LOG_COUNTS: Dict[str, int] = {}
def _log_cp_shared_kv_mla_prefetch_fallback(
reason: str,
message: str,
*args,
limit: int = 8,
) -> None:
count = _CP_SHARED_KV_MLA_PREFETCH_FALLBACK_LOG_COUNTS.get(reason, 0)
if count >= limit:
return
_CP_SHARED_KV_MLA_PREFETCH_FALLBACK_LOG_COUNTS[reason] = count + 1
logger.warning(
"[CP_SHARED_KV_FALLBACK][mla_prefetch] reason=%s " + message,
reason,
*args,
)
if _is_hip:
from sglang.srt.layers.attention.nsa.triton_kernel import get_valid_kv_indices
@@ -1956,6 +1974,19 @@ class NativeSparseAttnBackend(
else None
)
page_size = int(forward_batch.token_to_kv_pool.page_size)
_log_cp_shared_kv_mla_prefetch_fallback(
reason,
"MLA prefetch fast path did not provide partial-current "
"prefix; using synchronous compose. cp_rank=%s "
"layer=%s prefix_lens=%s extend_lens=%s "
"current_rows=%s page_table_shape=%s",
forward_batch.cp_shared_kv_layout.cp_rank,
layer.layer_id,
prefix_lens,
extend_lens,
int(current_kv_cache.shape[0]),
tuple(logical_page_table_1.shape),
)
if (
prefix_lens_cpu is None
or len(prefix_lens_cpu) != 1
@@ -2070,6 +2101,20 @@ class NativeSparseAttnBackend(
eagle_draft_mla_used_prefetch = True
kv_cache, page_table_1 = prefetched_kv
else:
reason = (
"missing_prefetcher"
if mla_prefetcher is None
else "prefetch_consume_returned_none"
)
_log_cp_shared_kv_mla_prefetch_fallback(
reason,
"MLA prefetch fast path did not provide full materialize "
"buffer; using synchronous materialize. cp_rank=%s "
"layer=%s page_table_shape=%s",
forward_batch.cp_shared_kv_layout.cp_rank,
layer.layer_id,
tuple(page_table_1.shape),
)
slot_remap = get_or_build_shared_token_kv_slot_remap(
forward_batch,
kv_cache=kv_cache,

View File

@@ -885,7 +885,9 @@ class TestCpSharedKVRuntimeHelpers(unittest.TestCase):
runtime._CURRENT_REUSE_FALLBACK_LOG_COUNTS.clear()
runtime._TAI_MATERIALIZE_FALLBACK_LOG_COUNTS.clear()
runtime._TAI_IPC_MATERIALIZE_FALLBACK_LOG_COUNTS.clear()
runtime._TAI_FUSED_MLA_STORE_FALLBACK_LOG_COUNTS.clear()
runtime._TAI_INDEX_MQA_PREPARE_FALLBACK_LOG_COUNTS.clear()
with self.assertLogs(runtime.logger.name, level="WARNING") as logs:
runtime._log_current_reuse_fallback(
@@ -903,6 +905,16 @@ class TestCpSharedKVRuntimeHelpers(unittest.TestCase):
"store fallback %s",
3,
)
runtime._log_tai_ipc_materialize_fallback(
"ipc_reason",
"ipc fallback %s",
4,
)
runtime._log_tai_index_mqa_prepare_fallback(
"index_reason",
"index fallback %s",
5,
)
self.assertIn("[CP_SHARED_KV_FALLBACK][current_reuse]", logs.output[0])
self.assertIn("current_reason", logs.output[0])
@@ -913,6 +925,115 @@ class TestCpSharedKVRuntimeHelpers(unittest.TestCase):
self.assertIn("[CP_SHARED_KV_FALLBACK][tai_fused_mla_store]", logs.output[2])
self.assertIn("store_reason", logs.output[2])
self.assertIn("store fallback 3", logs.output[2])
self.assertIn("[CP_SHARED_KV_FALLBACK][tai_ipc_materialize]", logs.output[3])
self.assertIn("ipc_reason", logs.output[3])
self.assertIn("ipc fallback 4", logs.output[3])
self.assertIn("[CP_SHARED_KV_FALLBACK][tai_index_mqa_prepare]", logs.output[4])
self.assertIn("index_reason", logs.output[4])
self.assertIn("index fallback 5", logs.output[4])
def test_current_reuse_fast_path_miss_logs_warning(self):
from sglang.srt.environ import envs
from sglang.srt.layers.attention.nsa import cp_shared_kv_runtime as runtime
runtime._CURRENT_REUSE_FALLBACK_LOG_COUNTS.clear()
forward_batch = SimpleNamespace(
forward_mode=_FakeExtendForwardMode(),
batch_size=2,
extend_prefix_lens_cpu=[64, 64],
extend_seq_lens_cpu=[64, 64],
seq_lens_cpu=torch.tensor([128, 128], dtype=torch.int32),
out_cache_loc=torch.arange(128, dtype=torch.int64),
)
with envs.SGLANG_CP_SHARED_KV_CURRENT_REUSE.override(True):
with self.assertLogs(runtime.logger.name, level="WARNING") as logs:
self.assertFalse(runtime.should_reuse_current_extend_kv(forward_batch))
joined = "\n".join(logs.output)
self.assertIn("[CP_SHARED_KV_FALLBACK][current_reuse]", joined)
self.assertIn("batch_size_not_one", joined)
def test_tai_index_mqa_prepare_fast_path_miss_logs_warning(self):
from sglang.srt.environ import envs
from sglang.srt.layers.attention.nsa import cp_shared_kv_runtime as runtime
runtime._TAI_INDEX_MQA_PREPARE_FALLBACK_LOG_COUNTS.clear()
with envs.SGLANG_CP_SHARED_KV_FUSED_INDEX_MQA_PREPARE.override(True):
with patch.object(
runtime, "_load_tai_index_mqa_prepare_kernel", return_value=None
):
with self.assertLogs(runtime.logger.name, level="WARNING") as logs:
self.assertIsNone(
runtime.try_tai_prepare_cp_mqa_index(
index_buffer=torch.empty((4, 32), dtype=torch.uint8),
page_indices=torch.tensor([1], dtype=torch.int64),
kv_len=64,
valid_q_count=1,
ke_start=0,
page_size=64,
index_head_dim=128,
)
)
joined = "\n".join(logs.output)
self.assertIn("[CP_SHARED_KV_FALLBACK][tai_index_mqa_prepare]", joined)
self.assertIn("kernel_missing", joined)
def test_tai_ipc_token_materialize_fast_path_miss_logs_warning(self):
from sglang.srt.layers.attention.nsa import cp_shared_kv_runtime as runtime
from sglang.srt.mem_cache.cp_shared_kv_layout import CpSharedKVLayout
runtime._TAI_IPC_MATERIALIZE_FALLBACK_LOG_COUNTS.clear()
layout = CpSharedKVLayout(page_size=4, cp_size=2, cp_rank=0)
with self.assertLogs(runtime.logger.name, level="WARNING") as logs:
self.assertFalse(
runtime._try_tai_ipc_materialize_token_kv_page_slots_into(
kv_cache=torch.empty((16, 1), dtype=torch.float32),
dense_kv_cache=torch.empty((16, 1), dtype=torch.float32),
slot_logical_pages=torch.tensor([1, 2], dtype=torch.int64),
layout=layout,
page_size=4,
start_slot=1,
end_slot=2,
)
)
joined = "\n".join(logs.output)
self.assertIn("[CP_SHARED_KV_FALLBACK][tai_ipc_materialize]", joined)
self.assertIn("start_slot_nonzero", joined)
def test_mla_prefetch_sync_compose_paths_log_warning_in_source(self):
from pathlib import Path
source = (
Path(__file__).resolve().parents[4]
/ "python/sglang/srt/layers/attention/nsa_backend.py"
).read_text()
self.assertIn("def _log_cp_shared_kv_mla_prefetch_fallback", source)
self.assertIn("[CP_SHARED_KV_FALLBACK][mla_prefetch]", source)
self.assertIn(
"MLA prefetch fast path did not provide partial-current", source
)
self.assertIn(
"MLA prefetch fast path did not provide full materialize", source
)
def test_index_prefetch_sync_compose_paths_log_warning_in_source(self):
from pathlib import Path
source = (
Path(__file__).resolve().parents[4]
/ "python/sglang/srt/layers/attention/nsa/nsa_indexer.py"
).read_text()
self.assertIn("[CP_SHARED_KV_FALLBACK][index_prefetch]", source)
self.assertIn("current_missing_prefetcher", source)
self.assertIn("current_consume_miss", source)
self.assertIn("missing_prefetcher", source)
def test_current_loc_remap_fast_path_args_only_for_current_only_extend(self):
from sglang.srt.layers.attention.nsa.cp_shared_kv_runtime import (
@@ -1881,7 +2002,12 @@ class TestCpSharedKVRuntimeHelpers(unittest.TestCase):
layout=layout,
page_size=4,
)
warning.assert_not_called()
self.assertFalse(
any(
"[CP_SHARED_KV_FALLBACK][slot_remap_cache]" in str(call)
for call in warning.call_args_list
)
)
with self.assertLogs(runtime.logger.name, level="WARNING") as logs:
remap_b = runtime.get_or_build_shared_token_kv_slot_remap(
@@ -1993,7 +2119,12 @@ class TestCpSharedKVRuntimeHelpers(unittest.TestCase):
logical_pages=logical_pages_a,
layout=layout,
)
warning.assert_not_called()
self.assertFalse(
any(
"[CP_SHARED_KV_FALLBACK][slot_remap_cache]" in str(call)
for call in warning.call_args_list
)
)
with self.assertLogs(runtime.logger.name, level="WARNING") as logs:
remap_b = runtime.get_or_build_shared_paged_buffer_slot_remap(