Conversation
Add a JIT transfer kernel that stages KV rows through a shared-memory ring filled by cp.async.bulk (one op per page run, 2D tensor-map boxes for strided page-first host views) and drained by store warps, so a CTA keeps the whole ring in flight instead of ~64 KB of register loads. Reaches the host-link ceiling with 4 blocks where the register kernel needs 6+, and serves any row size that is a multiple of 16 bytes. Routed from the existing transfer_hicache_* entry points behind SGLANG_HICACHE_TMA_TRANSFER (default on) when the GPU is sm_90+, the row size is a multiple of 16 B and the page size tiles a chunk; pools pass their page size so smaller pages keep the register kernel.
cctry
requested review from
BBuf,
DarkSharpness,
HaiShaw,
HydraQYH,
Ying1123,
alphabetc1,
celve,
hanming-lu,
hnyls2002,
huangtingwei9988,
hzh0425,
ispobock,
merrymercy,
xiezhq-hermann,
yizhang2077 and
yuan-luo
as code owners
September 19, 2026 00:59
added 2 commits
September 18, 2026 18:11
Splitting a contiguous span between the TMA store engine and vector stores measured no faster than one bulk store, and store warps beyond four add nothing on strided destinations either.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
HiCache brings cached prefixes back from the pinned host pool into the device pool layer by layer, on a side stream, while the forward pass runs on the same GPU. Two properties of the transfer kernel therefore matter: how fast it moves a page, because the load gates the prefix hit, and how many SMs it takes from the forward pass to do so.
The register-staging JIT kernel in
hicache.cuhis bounded by bytes in flight, not by the link. Each of its 1024 threads holds one 64 B slice of a row in registers between its load and its store, so a CTA has at most 64 KB outstanding at any time. Withthe measured ~48 GB/s per CTA on a GB300 (NVLink-C2C host link) corresponds to an effective host round trip of ~1.3 us. The copy-engine ceiling of the same link is ~210 GB/s H2D and ~192 GB/s D2H, so the default 2-block launch reaches ~90 GB/s, and saturating the link takes 6+ blocks of 1024 threads, every one of them issuing dependent loads for the whole transfer. Making the register pipeline deeper does not help: two rows in flight per thread measured 40 GB/s instead of 48, because at 1024 threads the extra registers cost more occupancy than the extra outstanding bytes buy.
TMA bulk copies remove that trade-off. One thread issues a 32 KB
cp.async.bulkand the data lands in shared memory with no registers involved, so a single warp keeps a 192 KB ring in flight: three times the register kernel's outstanding bytes with nine warps per CTA instead of 32. Two hardware limits, measured with raw probe kernels on the same GPU, then set what any staged copy can reach:The SM write port (32 B/clk) caps one CTA around 66 GB/s in either design, and the TMA unit handles roughly one bulk op per ~50 cycles, which makes per-row bulk copies a trap. The achievable goal is therefore to run each CTA at its write port instead of at its load latency: 4 blocks saturate the link instead of 6+, and 1 or 2 blocks move more per SM than today.
Modifications
New JIT kernel
hicache_tma.cuh, routed from the existingtransfer_hicache_*entry points.cp.async.bulk; completion is counted on per-stage mbarriers, so no thread holds data.layers * row_bytes) is one 2D tensor-map box (rows mapped as 8-byte elements, box[rows_per_chunk][row_bytes/8]), encoded per call withcuTensorMapEncodeTiledvia the driver entry point (no-lcuda). Per-row ops remain only for scattered tokens.SGLANG_HICACHE_TMA_TRANSFER(default on) selects the kernel when the GPU is sm_90+, the row size is a multiple of 16 B, andpage_size % rows_per_chunk == 0(pools now pass their page size); otherwise the register kernel is used. ROCm and pre-Hopper are unaffected.Invariants: correctness never depends on the run detection (it only picks the op shape); every stage is reused only after its readers release it through the
emptymbarrier; bulk stores are waited withcp.async.bulk.wait_group 0before the grid completes.Accuracy Tests
test/registered/kernels/ops/kvcache/test_hicache_tma.py: H2D then D2H round trips through layer-first and page-first host views, int32 and int64 indices, whole pages and fully scattered rows, a partial tail chunk, untouched rows verified unchanged; all-layer pointer-table D2H into a page-first host; MLA single-buffer one-layer and all-layer. 10 cases pass (GB300).test/registered/kernels/ops/kvcache/test_hicache.pyandtest_hicache_page_first_write_back.pypass through the pool API with the new default (59) and withSGLANG_HICACHE_TMA_TRANSFER=0(47).Speed Tests and Profiling
H2D, 512 B rows (bf16, 4 KV heads x 64), 128-token pages, 32768 tokens, page-first host, GB/s (TMA / register kernel), GB300:
At 4 blocks the TMA kernel reaches the copy-engine ceiling of the link; the register kernel needs 6+. D2H to a layer-first host is at the host write cap for both kernels (~45 GB/s per block). For scattered single-token indices (
page_size=1) the register kernel stays faster for small rows, which is why the gate keeps it for pages that do not tile a chunk.bench_hicache.pygained atmaprovider line.Checklist
CI States
Latest PR Test (Base): ❌ Run #35412153100
Latest PR Test (Extra): ❌ Run #35412152921
Latest PR Test (AMD ROCm 10): ❌ Run #35412153051