refactor: host prefix cache — device↔host KV-cache transfer framework (4/n).#1861
refactor: host prefix cache — device↔host KV-cache transfer framework (4/n).#1861Kang-Meng wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Code Review
This pull request re-enables and refactors the hierarchical KV cache transfer and block manager pool, which were temporarily disabled during a block-manager refactor. It introduces platform-independent abstractions like BatchMemcpy and LayerSynchronizer (with NPU implementations), updates various KVCacheImpl subclasses to support host cache allocation, and integrates these components back into WorkerImpl and LLMEngine. The code review feedback focuses on enforcing repository style guides, including reserving vector capacity, using fixed-width integers instead of plain int, annotating constant arguments with parameter names, avoiding relative include paths, avoiding auto for primitive types, and adding defensive bounds checks for tensor indexing.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
cbcbc2f to
b5b409f
Compare
f7c7566 to
6ad85c4
Compare
Background
Previously
HierarchyBlockManagerPoolwas selected whenhost_blocks_factor > 1but theactual D2H/H2D transfer path was disabled: blocks were never offloaded to host and host
prefix-cache hits were never reloaded to device. This PR re-enables the full offload→reload
pipeline for
BlockType::KV.Changes
Commit 1 — allocate host KV cache as page-aligned pinned memory in KVCache impls (
ae8ba1a1)Ensures host-side tensors used for D2H/H2D DMA are allocated as pinned, page-aligned memory:
HostPageAlignedRegion: on NPU builds, callsaclrtHostRegister(..., ACL_HOST_REGISTER_MAPPED, ...)immediately aftermmap+mlockso the host buffer is mapped into device address space. Destructor calls
aclrtHostUnregisterbeforemunlock/munmap. Move-assignment operator cleaned up (wasleaking if
thisheld existing memory).kv_cache_utils:scale_host_block_count(clamps factor to≥ 1.0),
build_host_tensor_shape(scales dim-0 by factor),build_host_group_tensor_shape(inserts a layer dimension at index 1, yielding[host_blocks, layer_count, ...]), andcreate_host_page_aligned_tensor(allocatesregion + creates
torch::from_blobview).KVCacheImpland all impl subclasses (Standard, DeepSeekV4, Indexed, LinearAttention)gain a host constructor
(KVCacheShape, KVCacheCreateOptions, BlockType, layer_count)that allocates the grouped host tensor for its block type, and a
get_block_type_tensors(BlockType)virtual method queried by the transfer layer.kv_cache_test.cppcovers host tensor allocation and shape contract foreach impl.
Commit 2 — restore D2H offload collection (
cbcbc2f1)Restores the D2H offload trigger (removed in commit 1 to keep the diff reviewable) and
fixes a race in the H2D path:
collect_offload_pairs(): extracted fromdeallocate(). Allocates host blocks to matchnot-yet-offloaded device blocks (aborts without partial copy if the host pool is
exhausted), then enqueues
OffloadBlockPairs for blocks withref_count == 2(uniquelyowned; beam-shared blocks with
ref_count > 2are skipped). Bounded bykv_cache_tokens_numso uncomputed in-batch blocks are never copied.LayerSynchronizeris now created and inserted intolayer_wise_load_synchronizer_synchronously insidetransfer_kv_blocks(), beforethe async copy task is scheduled. Previously the synchronizer was created inside the
threadpool task; the forward's
set_layer_synchronizer()could look up the map beforethe task ran, miss the entry, skip the wait, and read KV before H2D completed.
offload_threadpool_removed: D2H now runs synchronously on the caller (RemoteWorkercopy thread) so the copied-block count can be returned to the scheduler. The shared
stream pool is sized to
load_threadpool_.size() + kOffloadStreamCount (4)to coverconcurrent D2H callers.
min(hbm_blocks.size(), host_blocks.size())to prevent out-of-bounds when achunked-prefill allocation produces fewer device blocks than the host prefix match covers.
Test
Validated on A3 (Ascend 910) with Qwen3-32B TP2,
MAX_MEM_UTIL=0.70(device pool: 480blocks),
generated-shared-prefixworkload (60 groups × 8 prompts, 2560-token sharedprefix, concurrency=4):
HierarchyKVCacheTransfer: created host cache for 1 block type groups.num_prefix_cache_tokens: 256,TTFT 74 ms → 45 ms, byte-consistent output.
No enough memory to scheduleevents; idle recovery HTTP 200after full workload.
finish_reason=stop, coherent French output, idle and under load.