Enable CP HiCache direct transfers to use layer-page host layout
CP shared-KV HiCache transfers are per-layer, so the host layout should match the access pattern instead of forcing page-major strides through every layer. This adds a direct-only layer_page_first layout, routes per-layer KV and NSA index backup/load through the TAI LF<->LPF direct kernels, and keeps storage/page-buffer metadata paths fail-fast until their page-level contract is redesigned.\n\nThe direct controller keeps host indices in caller order for both page_first_direct and layer_page_first because the TAI direct path requires CPU index descriptors and owns descriptor coalescing. All-layer backup intentionally loops over per-layer direct kernels rather than using the sgl-kernel all-layer direct ABI.\n\nConstraint: layer_page_first is currently host-only CP HiCache; storage backends assume page-major contiguous page metadata.\nConstraint: TAI LPF direct kernels require CPU int64 page indices and complete page spans.\nRejected: silently fallback to SM copy when TAI LPF kernels are missing | that hides production performance regressions.\nRejected: support storage page metadata in this commit | LPF requires a layer-page-level storage contract, not a one-pointer-per-page contract.\nConfidence: medium\nScope-risk: moderate\nDirective: Do not enable storage or kernel backend for layer_page_first without redesigning page-buffer metadata and adding remote ETE coverage.\nTested: local py_compile for touched runtime files.\nTested: remote py_compile in g0034 container for touched runtime files.\nTested: remote targeted pytest: 5 passed for parser/storage/layout/move_indices smoke coverage.\nNot-tested: full CP HiCache ETE with --hicache-mem-layout layer_page_first after this commit step.\nNot-tested: combined CUDA roundtrip tests in one pytest process; previous independent runs passed but combined run exposed a host-memory registration lifecycle issue.
This commit is contained in:
@@ -1697,7 +1697,7 @@ class HiCacheController:
|
||||
device_indices = device_indices.cpu()
|
||||
host_indices, idx = host_indices.sort()
|
||||
return host_indices, device_indices.index_select(0, idx)
|
||||
elif mem_pool_host.layout == "page_first_direct":
|
||||
elif mem_pool_host.layout in ("page_first_direct", "layer_page_first"):
|
||||
return host_indices, device_indices.cpu()
|
||||
else:
|
||||
raise ValueError(
|
||||
|
||||
@@ -78,6 +78,14 @@ class HiCacheStorage(ABC):
|
||||
# todo, the page size of storage backend does not have to be the same as the same as host memory pool
|
||||
|
||||
def register_mem_pool_host(self, mem_pool_host: HostKVCache):
|
||||
if getattr(mem_pool_host, "layout", None) == "layer_page_first":
|
||||
raise RuntimeError(
|
||||
"[CP_HICACHE_FAILFAST][layer_page_first_storage_unsupported] "
|
||||
"layer_page_first is currently a host-only direct CP HiCache "
|
||||
"layout. Storage backends are not supported for this layout "
|
||||
"because their page-level zero-copy/metadata contracts assume "
|
||||
"page-major contiguous pages."
|
||||
)
|
||||
self.mem_pool_host = mem_pool_host
|
||||
|
||||
def batch_get_v1(
|
||||
|
||||
@@ -83,6 +83,22 @@ def _load_tai_transfer_kv_per_layer_direct_lf_pf():
|
||||
) from exc
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def _load_tai_transfer_kv_per_layer_direct_lf_lpf():
|
||||
try:
|
||||
from tai_kernel.nsa_prefill import transfer_kv_per_layer_direct_lf_lpf
|
||||
|
||||
return transfer_kv_per_layer_direct_lf_lpf
|
||||
except Exception as exc:
|
||||
raise RuntimeError(
|
||||
"[CP_HICACHE_FAILFAST][missing_tai_layer_page_first_direct_lf_lpf] "
|
||||
"direct+layer_page_first per-layer D2H backup requires "
|
||||
"tai_kernel.nsa_prefill.transfer_kv_per_layer_direct_lf_lpf. "
|
||||
"Build/sync tai-kernel; this path intentionally does not fall back "
|
||||
"to an SM-consuming copy kernel."
|
||||
) from exc
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def _load_tai_transfer_kv_per_layer_direct_pf_lf():
|
||||
try:
|
||||
@@ -99,6 +115,32 @@ def _load_tai_transfer_kv_per_layer_direct_pf_lf():
|
||||
) from exc
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def _load_tai_transfer_kv_per_layer_direct_lpf_lf():
|
||||
try:
|
||||
from tai_kernel.nsa_prefill import transfer_kv_per_layer_direct_lpf_lf
|
||||
|
||||
return transfer_kv_per_layer_direct_lpf_lf
|
||||
except Exception as exc:
|
||||
raise RuntimeError(
|
||||
"[CP_HICACHE_FAILFAST][missing_tai_layer_page_first_direct_lpf_lf] "
|
||||
"direct+layer_page_first per-layer H2D load requires "
|
||||
"tai_kernel.nsa_prefill.transfer_kv_per_layer_direct_lpf_lf. "
|
||||
"Build/sync tai-kernel; this path intentionally does not fall back "
|
||||
"to an SM-consuming copy kernel."
|
||||
) from exc
|
||||
|
||||
|
||||
def _raise_layer_page_first_page_buffer_meta_unsupported() -> None:
|
||||
raise RuntimeError(
|
||||
"[CP_HICACHE_FAILFAST][layer_page_first_page_buffer_meta_unsupported] "
|
||||
"layer_page_first is currently a host-only direct CP HiCache layout. "
|
||||
"Storage/zero-copy page buffer metadata is not supported because a "
|
||||
"single page across all layers is not stored as one page-major "
|
||||
"contiguous block. Disable storage or use page_first_direct."
|
||||
)
|
||||
|
||||
|
||||
def synchronized(func):
|
||||
@wraps(func)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
@@ -768,6 +810,15 @@ class MHATokenToKVPoolHost(HostKVCache):
|
||||
self.head_num,
|
||||
self.head_dim,
|
||||
)
|
||||
elif self.layout == "layer_page_first":
|
||||
dims = (
|
||||
2,
|
||||
self.layer_num,
|
||||
self.page_num,
|
||||
self.page_size,
|
||||
self.head_num,
|
||||
self.head_dim,
|
||||
)
|
||||
elif self.layout == "page_head":
|
||||
dims = (
|
||||
2,
|
||||
@@ -882,6 +933,18 @@ class MHATokenToKVPoolHost(HostKVCache):
|
||||
layer_id=layer_id,
|
||||
page_size=self.page_size,
|
||||
)
|
||||
elif self.layout == "layer_page_first":
|
||||
_load_tai_transfer_kv_per_layer_direct_lpf_lf()(
|
||||
src_ptrs=[self.k_buffer, self.v_buffer],
|
||||
dst_ptrs=[
|
||||
device_pool.k_buffer[layer_id],
|
||||
device_pool.v_buffer[layer_id],
|
||||
],
|
||||
src_indices=host_indices,
|
||||
dst_indices=device_indices,
|
||||
layer_id=layer_id,
|
||||
page_size=self.page_size,
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Unsupported layout: {self.layout}")
|
||||
elif io_backend == "kernel_ascend":
|
||||
@@ -986,6 +1049,20 @@ class MHATokenToKVPoolHost(HostKVCache):
|
||||
layer_id=layer_id,
|
||||
page_size=self.page_size,
|
||||
)
|
||||
elif self.layout == "layer_page_first":
|
||||
tai_transfer = _load_tai_transfer_kv_per_layer_direct_lf_lpf()
|
||||
for layer_id in range(self.layer_num):
|
||||
tai_transfer(
|
||||
src_ptrs=[
|
||||
device_pool.k_buffer[layer_id],
|
||||
device_pool.v_buffer[layer_id],
|
||||
],
|
||||
dst_ptrs=[self.k_buffer, self.v_buffer],
|
||||
src_indices=device_indices,
|
||||
dst_indices=host_indices,
|
||||
layer_id=layer_id,
|
||||
page_size=self.page_size,
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Unsupported layout: {self.layout}")
|
||||
elif io_backend == "kernel_ascend":
|
||||
@@ -1056,6 +1133,18 @@ class MHATokenToKVPoolHost(HostKVCache):
|
||||
layer_id=layer_id,
|
||||
page_size=self.page_size,
|
||||
)
|
||||
elif self.layout == "layer_page_first":
|
||||
_load_tai_transfer_kv_per_layer_direct_lf_lpf()(
|
||||
src_ptrs=[
|
||||
device_pool.k_buffer[layer_id],
|
||||
device_pool.v_buffer[layer_id],
|
||||
],
|
||||
dst_ptrs=[self.k_buffer, self.v_buffer],
|
||||
src_indices=device_indices,
|
||||
dst_indices=host_indices,
|
||||
layer_id=layer_id,
|
||||
page_size=self.page_size,
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Unsupported layout: {self.layout}")
|
||||
elif io_backend == "kernel_ascend":
|
||||
@@ -1084,6 +1173,9 @@ class MHATokenToKVPoolHost(HostKVCache):
|
||||
elif self.layout in ["page_first_direct", "page_head"]:
|
||||
real_index = index // self.page_size
|
||||
data_page = self.kv_buffer[:, real_index : real_index + 1, :, :, :, :]
|
||||
elif self.layout == "layer_page_first":
|
||||
real_index = index // self.page_size
|
||||
data_page = self.kv_buffer[:, :, real_index : real_index + 1, :, :, :]
|
||||
else:
|
||||
raise ValueError(f"Unsupported layout: {self.layout}")
|
||||
if flat:
|
||||
@@ -1122,6 +1214,13 @@ class MHATokenToKVPoolHost(HostKVCache):
|
||||
2, 1, self.layer_num, self.page_size, self.head_num, self.head_dim
|
||||
)
|
||||
)
|
||||
elif self.layout == "layer_page_first":
|
||||
real_index = index // self.page_size
|
||||
self.kv_buffer[:, :, real_index : real_index + 1, :, :, :] = (
|
||||
data_page.reshape(
|
||||
2, self.layer_num, 1, self.page_size, self.head_num, self.head_dim
|
||||
)
|
||||
)
|
||||
elif self.layout == "page_head":
|
||||
real_index = index // self.page_size
|
||||
self.kv_buffer[:, real_index : real_index + 1, :, :, :, :] = (
|
||||
@@ -1238,6 +1337,8 @@ class MHATokenToKVPoolHost(HostKVCache):
|
||||
* self.head_dim
|
||||
)
|
||||
element_size_list = [element_size] * len(ptr_list)
|
||||
elif self.layout == "layer_page_first":
|
||||
_raise_layer_page_first_page_buffer_meta_unsupported()
|
||||
else:
|
||||
raise ValueError(f"Unsupported layout: {self.layout}")
|
||||
return ptr_list, element_size_list
|
||||
@@ -1313,6 +1414,14 @@ class MLATokenToKVPoolHost(HostKVCache):
|
||||
1,
|
||||
self.kv_cache_dim,
|
||||
)
|
||||
elif self.layout == "layer_page_first":
|
||||
dims = (
|
||||
self.layer_num,
|
||||
self.page_num,
|
||||
self.page_size,
|
||||
1,
|
||||
self.kv_cache_dim,
|
||||
)
|
||||
# Ascend-specific: Aligns with NPUMLATokenToKVPool layout
|
||||
# Separately allocate k_buffer and v_buffer for easier data transfer.
|
||||
elif self.layout == "page_first_kv_split":
|
||||
@@ -1406,6 +1515,15 @@ class MLATokenToKVPoolHost(HostKVCache):
|
||||
layer_id=layer_id,
|
||||
page_size=self.page_size,
|
||||
)
|
||||
elif self.layout == "layer_page_first":
|
||||
_load_tai_transfer_kv_per_layer_direct_lpf_lf()(
|
||||
src_ptrs=[self.kv_buffer],
|
||||
dst_ptrs=[device_pool.kv_buffer[layer_id]],
|
||||
src_indices=host_indices,
|
||||
dst_indices=device_indices,
|
||||
layer_id=layer_id,
|
||||
page_size=self.page_size,
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Unsupported layout: {self.layout}")
|
||||
elif io_backend == "kernel_ascend":
|
||||
@@ -1478,6 +1596,17 @@ class MLATokenToKVPoolHost(HostKVCache):
|
||||
layer_id=layer_id,
|
||||
page_size=self.page_size,
|
||||
)
|
||||
elif self.layout == "layer_page_first":
|
||||
tai_transfer = _load_tai_transfer_kv_per_layer_direct_lf_lpf()
|
||||
for layer_id in range(self.layer_num):
|
||||
tai_transfer(
|
||||
src_ptrs=[device_pool.kv_buffer[layer_id]],
|
||||
dst_ptrs=[self.kv_buffer],
|
||||
src_indices=device_indices,
|
||||
dst_indices=host_indices,
|
||||
layer_id=layer_id,
|
||||
page_size=self.page_size,
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Unsupported layout: {self.layout}")
|
||||
elif io_backend == "kernel_ascend":
|
||||
@@ -1546,6 +1675,15 @@ class MLATokenToKVPoolHost(HostKVCache):
|
||||
layer_id=layer_id,
|
||||
page_size=self.page_size,
|
||||
)
|
||||
elif self.layout == "layer_page_first":
|
||||
_load_tai_transfer_kv_per_layer_direct_lf_lpf()(
|
||||
src_ptrs=[device_pool.kv_buffer[layer_id]],
|
||||
dst_ptrs=[self.kv_buffer],
|
||||
src_indices=device_indices,
|
||||
dst_indices=host_indices,
|
||||
layer_id=layer_id,
|
||||
page_size=self.page_size,
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Unsupported layout: {self.layout}")
|
||||
elif io_backend == "kernel_ascend":
|
||||
@@ -1576,6 +1714,9 @@ class MLATokenToKVPoolHost(HostKVCache):
|
||||
elif self.layout == "page_first_direct":
|
||||
real_index = index // self.page_size
|
||||
data_page = self.kv_buffer[real_index : real_index + 1, :, :, :, :]
|
||||
elif self.layout == "layer_page_first":
|
||||
real_index = index // self.page_size
|
||||
data_page = self.kv_buffer[:, real_index : real_index + 1, :, :, :]
|
||||
else:
|
||||
raise ValueError(f"Unsupported layout: {self.layout}")
|
||||
if flat:
|
||||
@@ -1619,6 +1760,15 @@ class MLATokenToKVPoolHost(HostKVCache):
|
||||
1,
|
||||
self.kv_cache_dim,
|
||||
)
|
||||
elif self.layout == "layer_page_first":
|
||||
real_index = index // self.page_size
|
||||
self.kv_buffer[:, real_index : real_index + 1, :, :, :] = data_page.reshape(
|
||||
self.layer_num,
|
||||
1,
|
||||
self.page_size,
|
||||
1,
|
||||
self.kv_cache_dim,
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Unsupported layout: {self.layout}")
|
||||
|
||||
@@ -1658,6 +1808,8 @@ class MLATokenToKVPoolHost(HostKVCache):
|
||||
* self.kv_cache_dim
|
||||
)
|
||||
element_size_list = [element_size] * len(ptr_list)
|
||||
elif self.layout == "layer_page_first":
|
||||
_raise_layer_page_first_page_buffer_meta_unsupported()
|
||||
else:
|
||||
raise ValueError(f"Unsupported layout: {self.layout}")
|
||||
return ptr_list, element_size_list
|
||||
@@ -1754,6 +1906,27 @@ class NSATokenToKVPoolHost(MLATokenToKVPoolHost):
|
||||
pin_memory=self.pin_memory,
|
||||
allocator=self.allocator,
|
||||
)
|
||||
elif self.layout == "layer_page_first":
|
||||
self.index_k_with_scale_buffer = alloc_func(
|
||||
(
|
||||
self.layer_num,
|
||||
self.indexer_page_num,
|
||||
1,
|
||||
self.indexer_page_stride_size,
|
||||
),
|
||||
dtype=self.indexer_dtype,
|
||||
device=self.device,
|
||||
pin_memory=self.pin_memory,
|
||||
allocator=self.allocator,
|
||||
)
|
||||
self.index_k_data_refs = [
|
||||
self.index_k_with_scale_buffer[i] for i in range(self.layer_num)
|
||||
]
|
||||
self.index_k_data_ptrs = torch.tensor(
|
||||
[x.data_ptr() for x in self.index_k_data_refs],
|
||||
dtype=torch.uint64,
|
||||
device=self.device_pool.device,
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Unsupported layout: {self.layout}")
|
||||
|
||||
@@ -1833,6 +2006,15 @@ class NSATokenToKVPoolHost(MLATokenToKVPoolHost):
|
||||
layer_id=layer_id,
|
||||
page_size=1,
|
||||
)
|
||||
elif self.layout == "layer_page_first":
|
||||
_load_tai_transfer_kv_per_layer_direct_lpf_lf()(
|
||||
src_ptrs=[self.index_k_with_scale_buffer],
|
||||
dst_ptrs=[device_pool.index_k_with_scale_buffer[layer_id]],
|
||||
src_indices=host_page_indices,
|
||||
dst_indices=device_page_indices,
|
||||
layer_id=layer_id,
|
||||
page_size=1,
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Unsupported layout: {self.layout}")
|
||||
else:
|
||||
@@ -1892,6 +2074,17 @@ class NSATokenToKVPoolHost(MLATokenToKVPoolHost):
|
||||
layer_id=layer_id,
|
||||
page_size=1,
|
||||
)
|
||||
elif self.layout == "layer_page_first":
|
||||
tai_transfer = _load_tai_transfer_kv_per_layer_direct_lf_lpf()
|
||||
for layer_id in range(self.layer_num):
|
||||
tai_transfer(
|
||||
src_ptrs=[device_pool.index_k_with_scale_buffer[layer_id]],
|
||||
dst_ptrs=[self.index_k_with_scale_buffer],
|
||||
src_indices=device_page_indices,
|
||||
dst_indices=host_page_indices,
|
||||
layer_id=layer_id,
|
||||
page_size=1,
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Unsupported layout: {self.layout}")
|
||||
else:
|
||||
@@ -1943,6 +2136,15 @@ class NSATokenToKVPoolHost(MLATokenToKVPoolHost):
|
||||
layer_id=layer_id,
|
||||
page_size=1,
|
||||
)
|
||||
elif self.layout == "layer_page_first":
|
||||
_load_tai_transfer_kv_per_layer_direct_lf_lpf()(
|
||||
src_ptrs=[device_pool.index_k_with_scale_buffer[layer_id]],
|
||||
dst_ptrs=[self.index_k_with_scale_buffer],
|
||||
src_indices=device_page_indices,
|
||||
dst_indices=host_page_indices,
|
||||
layer_id=layer_id,
|
||||
page_size=1,
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Unsupported layout: {self.layout}")
|
||||
else:
|
||||
|
||||
@@ -962,6 +962,7 @@ class ServerArgs:
|
||||
("kernel", "page_first"),
|
||||
("direct", "layer_first"),
|
||||
("direct", "page_first_direct"),
|
||||
("direct", "layer_page_first"),
|
||||
}
|
||||
current_pair = (self.hicache_io_backend, self.hicache_mem_layout)
|
||||
if current_pair in supported_pairs:
|
||||
@@ -970,12 +971,14 @@ class ServerArgs:
|
||||
raise ValueError(
|
||||
"CP shared KV HiCache supports only "
|
||||
"kernel/layer_first, kernel/page_first, direct/layer_first, "
|
||||
"and direct/page_first_direct after HiCache normalization. "
|
||||
"direct/page_first_direct, and direct/layer_page_first after "
|
||||
"HiCache normalization. "
|
||||
f"Got hicache_io_backend={self.hicache_io_backend!r} "
|
||||
f"hicache_mem_layout={self.hicache_mem_layout!r}. "
|
||||
"page_head is MHA/Mooncake-specific, page_first_kv_split is "
|
||||
"kernel_ascend-specific, and kernel_ascend CP shared KV HiCache "
|
||||
"is not implemented in the CUDA NSA host path."
|
||||
"is not implemented in the CUDA NSA host path. "
|
||||
"layer_page_first is supported only with the direct IO backend."
|
||||
)
|
||||
|
||||
def _handle_deprecated_args(self):
|
||||
@@ -3027,6 +3030,17 @@ class ServerArgs:
|
||||
)
|
||||
|
||||
def _resolve_storage_layout_compatibility(self):
|
||||
if (
|
||||
self.hicache_storage_backend is not None
|
||||
and self.hicache_mem_layout == "layer_page_first"
|
||||
):
|
||||
raise ValueError(
|
||||
"[CP_HICACHE_FAILFAST][layer_page_first_storage_unsupported] "
|
||||
"layer_page_first is currently a host-only direct CP HiCache "
|
||||
"layout and does not support storage backends. Disable "
|
||||
"hicache_storage_backend or use page_first_direct."
|
||||
)
|
||||
|
||||
if (
|
||||
self.hicache_storage_backend != "mooncake"
|
||||
or self.hicache_mem_layout != "layer_first"
|
||||
@@ -5225,6 +5239,7 @@ class ServerArgs:
|
||||
"layer_first",
|
||||
"page_first",
|
||||
"page_first_direct",
|
||||
"layer_page_first",
|
||||
"page_first_kv_split",
|
||||
"page_head",
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user