Repository navigation
Conversation
… KVCacheManagerV2 copyHostToDevice / copyDeviceToHost launched the batched copy kernel with lowBandwidth=true: one 128-thread CTA per (page, pool) task with a 4-deep ring of 16 B grains, i.e. 8 KiB in flight, so every host-involved task was latency-bound to a few GB/s whatever the link. On a disaggregated prefill deployment of a hybrid attention/SSM model, one prefill rank spent 31.5 s of a 44.9 s window in 1,107 such single-CTA kernels (p50 1.9 ms, p95 136 ms, max 237.7 ms); a 434 MB fp32 recurrent-state page took 24.8-237.7 ms (1.8-17.5 GB/s per CTA) and the 217 MB bf16 SSM-state page of the production recipe extrapolates to 12-120 ms where the copy engines of the same NVLink-C2C system need well under a millisecond. Consumers wait in-stream on the events recorded after these copies, so the time lands on the execution stream of a compute-bound prefill. Route host<->device calls with few, large tasks to cuMemcpyHtoDAsync / cuMemcpyDtoHAsync on the caller's stream (copy engines, no SMs) and keep the kernel for everything else: a per-task size below TLLM_KV_CACHE_MANAGER_V2_HOST_COPY_DMA_MIN_BYTES (default 4 MiB; 0 = any size, negative = never), more than TLLM_KV_CACHE_MANAGER_V2_HOST_COPY_DMA_MAX_TASKS tasks per call (default 8; 0 or negative = no limit), chunked host registration (one memcpy may not span two cuMemHostRegister ranges), and all device->device copies. Many-task calls keep the kernel because N tasks already run as N concurrent CTAs from one launch, while the copy-engine path costs one driver call per task. The memcpys are issued on the same stream, after the prior-event waits and before the finish event, so the recorded events still cover them. A non-integer knob value is reported and ignored rather than read as 0. The copy entry points keep their signatures (CopyEngine::transfer is the only caller); the internal kv_cache_manager_v2_utils bindings gain use_copy_engine_for_host_copy so tests can ask which path a shape takes. Measured end to end on the deployment above (32 GPUs, fixed concurrency, 1 h windows, same configuration and observability): output throughput per GPU +6.0 %, TTFT p50 5.98 -> 5.19 s, prefill iteration p50 2.59 -> 2.39 s; host bytes per request nearly unchanged (offload 458 -> 443 MB), the copies get faster, not fewer. Add kvCacheManagerV2HostCopyTest: predicate vs environment, byte-exact HostMem -> device -> HostMem round trips on both sides of both knobs (including 300 tasks for the multi-launch remainder), the kernel path's multiple-of-16 rejection (asserted on every host with a sub-threshold size) next to a copy-engine round trip of a size that is not a multiple of 16, an empty task list, the NULL stream, cross-stream ordering through a recorded event, and the measured GB/s of one 217 MB page recorded as test properties. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Tzu-Ling <tzulingk@nvidia.com>
… copy helpers Round trips through copy_host_to_device / copy_device_to_host of the kv_cache_manager_v2_utils bindings with torch pinned and device buffers on a non-default stream, for shapes on both sides of the copy-engine size threshold and task limit (884,736 B x 140 / 300, 16 B x 300, 15,261,696 B x 1 / 12, 217,055,232 B x 1, 4 MiB + 8 B x 3), compared byte for byte after the stream is drained, with the path chosen by the new use_copy_engine_for_host_copy predicate; the predicate against the documented defaults; the kernel path's multiple-of-16 rejection for a 24 B task, which is below any positive threshold; and a consumer stream that observes the copy through a recorded event alone. Needs one GPU; the directory is already in the GPU test lists. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Tzu-Ling <tzulingk@nvidia.com>
Plain sentences, one idea each: what the single-CTA copy kernel costs and why, the two knobs as an aligned list with their defaults and sentinel values, and the reasons for the task limit and for issuing the memcpys on the caller's stream as separate short paragraphs. The header notes and the gtest header get the same treatment. Comments only, no code change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Tzu-Ling <tzulingk@nvidia.com>
… kernel comment bytes in flight = 4 x 128 x 16 B = 8 KiB; 8,192 B / (1 to 4 us) = 8 to 2 GB/s per task, next to the measured 1.8 to 17.5 GB/s. Comment only. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Tzu-Ling <tzulingk@nvidia.com>
… and drop the task limit
The copy-engine path issued one cuMemcpy{HtoD,DtoH}Async per task, so a
call with many large tasks cost one driver call per page; the task
limit TLLM_KV_CACHE_MANAGER_V2_HOST_COPY_DMA_MAX_TASKS (default 8) kept
such calls on the kernel to avoid that. TensorRT-LLM builds with CUDA
13, where cuMemcpyBatchAsync is always available (batchedPageCopy.cu
already uses it), so submit all tasks of a call with one driver call
instead and remove the limit: the size threshold alone decides, and
large eviction or prefetch batches now leave the kernel too. The
legacy NULL stream is not accepted by the batched call and falls back
to one memcpy per task. useCopyEngineForHostCopy() and the
use_copy_engine_for_host_copy binding take only the task size now; the
tests follow (the multi-task large shapes exercise the batched call,
NullStreamAccepted exercises the per-task fallback with 1 and 3 tasks).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Tzu-Ling <tzulingk@nvidia.com>
…e task limit The commit that dropped the task limit rewrote the use_copy_engine_for_host_copy binding but left the old argument list and docstring behind it and re-indented the two registrations that follow, so the translation unit no longer parsed. Restore the copy_device_to_device and copy_batch_block_offsets_to_device registrations exactly as on main after the one-argument binding. The host-copy gtest compares the raw device pointer with nullptr instead of the unique_ptr, so the assertion does not depend on gtest's unique_ptr printer. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Tzu-Ling <tzulingk@nvidia.com>
…of the process NonMultipleOf16 and the pytest counterpart assumed that a 24 B task always takes the kernel. That holds at the default threshold but not when TLLM_KV_CACHE_MANAGER_V2_HOST_COPY_DMA_MIN_BYTES=0 sends every size to the copy engines, where the size is legal and the call round-trips instead of throwing (seen on a GB300 run of the gtest with the knob set to 0). Both tests now ask the policy predicate per size and either round-trip or assert the kernel's rejection. The pytest policy check mirrors the knob the same way the gtest does instead of hard-coding the default. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Tzu-Ling <tzulingk@nvidia.com>
… their dispatch On main the hot<->host migrations go through IKvCacheColdPageCodec and BatchedPageCopier (NVIDIA#17512, NVIDIA#18541); copyHostToDevice / copyDeviceToHost serve CopyEngine::transfer for migrations between two cold levels when one of them is a GPU_MEM level, and the bindings. Say so in the kernel comment, the header and the binding docstrings, and retire the task-limit wording that 07ce992 left behind. Dispatch: one copyAcrossHostLink<HostToDevice> holds the guard for both directions, rejects a negative byte count and the byte counts the kernel launcher's uint32_t cannot carry, and the copy-engine branch falls back to one memcpy per task for CU_STREAM_LEGACY as well as for a null handle, since cuMemcpyBatchAsync rejects both. The knob parser reports an out-of-range value as such, guards its warning so a bad value cannot reach std::terminate from a noexcept function, and is exposed as hostCopyDmaMinBytesFor(rawValue) with kHostCopyDmaMinBytesEnv so tests can drive the documented semantics without resetting the process-wide cache. Comments no longer claim an ordering inside a cuMemcpyBatchAsync batch or an unmeasured small-page result. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Tzu-Ling <tzulingk@nvidia.com>
…on and deterministic The policy test checked the knob against a copy of the production parser; it now checks hostCopyDmaMinBytesFor() on raw values (unset, empty, garbage, out of range, 0, -1, 16 MiB) and the cached accessor against the process environment. The fill pattern is pseudo-random per 64-bit word: the affine byte pattern repeated every 256 B, and every production page size here is a multiple of 256 B, so permuted or slot-0 source reads went undetected. The stream-ordering test holds the producer stream with a host-function gate, owns its event and stream handles and drains on every exit. Shapes: 64 pages of 4 MiB travel in one batched call; a negative byte count and the CU_STREAM_LEGACY handle are covered. CudaDeleter / CudaAllocation / allocateCuda and ScopedEnv move to kvCacheManagerV2TestUtils.h (the sibling tests keep their copies for now). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Tzu-Ling <tzulingk@nvidia.com>
…vironment The pytest derived its expectations from the predicate under test and accepted an all-False answer on every host. It now computes the expected path from the knob (parsed like strtoll) and the kernel version (HostMem::shouldUseChunkedRegistration() reads the first four characters of the release string), asserts the predicate equals it for every size, and branches the round trips on that oracle. Payloads are generated straight into pinned memory, the raw copies are drained in finally blocks, the rejection is asserted as RuntimeError, the pinned host cache is released when the module ends, 44 pages of 4 MiB travel in one batched call, and the legacy NULL stream round-trips. test_copy_helper_exceptions.py skips its host-helper cases when the knob sends a 1 B task to the copy engines, which accept any byte count. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Tzu-Ling <tzulingk@nvidia.com>
…der in the host-copy gtest nvcc fails to parse the torch headers when kvCacheManagerV2TestUtils.h is included before kvCacheManagerV2Utils.h. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Tzu-Ling <tzulingk@nvidia.com>
…p before the host gate An empty call now returns CUDA_SUCCESS before any range check or path choice, as the header promises, instead of reaching the kernel branch's byte-count bound. The binding docstrings name the NULL-stream fallback. The stream-ordering gtest runs both copies once before it shuts the producer stream with the host gate, so a first launch of the copy kernel, which may load its module and synchronize the context, cannot deadlock against the gate; the file comment mentions the chunked-registration exception. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Tzu-Ling <tzulingk@nvidia.com>
|
Thanks for spelling out the On an SM107 system with NVLink-C2C (Kimi K3, 8 GPUs, CUDA graphs) we see that SM copy kernels touching registered host memory leave the whole process slower afterwards: every kernel boundary in later CUDA graph replays gets ~0.2 us longer, about +1.2 ms (~7%) per decode step, and it never recovers (details and nsys findings in #20363). It is triggered by small pages too, not only by the large recurrent-state pages:
So with the default |
|
I think this is mostly for a deprecated code path. With recent code page codec change, the H2D and H2D of pages mostly go through the default codec, which has both kernel and DMA path, and the kernel path is also well optimized for perf/resource (not absolute perf because the migration is meant to run concurrently with inference, and I wanted it to use minimum amount of resource and achieve >80% peak throughput). Did you see perf problems for page H2D/D2H with the default codec path? BTW, python backend has been removed, so TLLM_KV_CACHE_MANAGER_V2_BACKEND=python has no impact. |
mikeiovine
left a comment
There was a problem hiding this comment.
Stamp on behalf of runtime devs, delegating proper review to @NVIDIA/trt-llm-kv-cache-manager-devs
|
[by Claude Code] @yizhang-nv Could you review this PR? Thanks! |
1 similar comment
|
[by Claude Code] @yizhang-nv Could you review this PR? Thanks! |
@coderabbitai summary
Description
KVCacheManagerV2 has two generic host<->GPU copy helpers,
copyHostToDeviceandcopyDeviceToHost, that move a batch of pages with a batched copy kernel: one 128-thread CTA per page with 8 KiB in flight, which caps every page at a few GB/s whatever the link. That is fine for the many small attention pages of a batch, but a 217 MB recurrent-state page of a hybrid model such as Kimi K3 then takes tens of milliseconds, and the execution stream waits for it. This PR sends calls with large pages to the GPU copy engines instead (onecuMemcpyBatchAsyncper call on the caller's stream) and leaves everything else on the kernel.Where this applies on main. Since #17512 and #18541, hot<->host migrations (eviction, onboard, prefetch,
copySlotData) go throughIKvCacheColdPageCodecandBatchedPageCopier, not through these two helpers. On main they serveCopyEngine::transfer, i.e. migrations between two cold levels when one of them is a GPU memory level, and thecopy_host_to_device/copy_device_to_hostbindings. The end-to-end gain below (+6.0 % output tokens/s per GPU, 0.8 s less TTFT at the median) was measured on a deployment built from a branch that predates the cold-page codec, where these helpers were the only GPU<->host copy path. The change is posted as the measured, self-contained version of the idea; the section "Measurements on main's path" at the end shows what the same comparison looks like againstBatchedPageCopierand what a follow-up for main would be. If the maintainers consider the helper-level branch redundant on main, closing this PR in favour of that follow-up is a fine outcome.The problem
The host copy kernel moves one page with one thread block.
launchBatchedCopy(lowBandwidth=true, ...)setsnbSplits = 1, so each (page, pool) task gets a single 128-thread CTA. The block copies 16 B per thread per step through a ring of four shared-memory buffers, so it never has more than 4 x 128 x 16 B = 8 KiB of loads outstanding. Bandwidth is bytes in flight divided by the latency of those loads, so one task is capped at a few to low tens of GB/s however fast the link is:The measured 1.8 to 17.5 GB/s per CTA fit that range. The stores are posted, so device->host is bounded by device-memory load latency and only host->device by the link round trip, which is why the two directions differ. Keeping a 400 GB/s link busy would need about 400 GB/s x 2 us = 800 KiB in flight; one task supplies 8 KiB, about 1 % of that. A batch of many small pages is still fast because every page gets its own block; a single large page is not.
Hybrid models have large pages. Kimi K3 (MLA attention plus KDA linear-attention layers, NVFP4) keeps two recurrent-state pages per rank, 217,055,232 B (KDA SSM state) and 15,261,696 B (conv state), next to its 884,736 B MLA pages. Every request restores one state snapshot from the host tier and later evicts one to it, so the large pages cross the link twice per request.
What a profile of one prefill rank shows (44.9 s window at steady load, on the branch described above):
The time is on the critical path. The storage manager records an event after each copy and the consumers wait on it in-stream, so a compute-bound prefill stalls until the page has landed. Per request the traffic is modest, about 458 MB out and 442 MB in; the problem is how slowly each large page moves, not how much data moves.
Adding blocks is the wrong fix here. The prefill already uses every SM, while the copy engines sit idle. [None][perf] Speed up burst KVCM2 resize for very long sequences #18541 reached the same conclusion for PCIe parts and uses
cuMemcpyBatchAsyncthere, "which also costs zero SMs".The change
copyHostToDevice/copyDeviceToHostincpp/tensorrt_llm/batch_manager/kvCacheManagerV2Utils.cushare one dispatch,copyAcrossHostLink<HostToDevice>: the copy-engine branch when the call is non-empty,numBytes > 0anduseCopyEngineForHostCopy(numBytes)is true,launchBatchedCopy(true, ...)unchanged otherwise.copyDeviceToDeviceis untouched. The predicate isTLLM_KV_CACHE_MANAGER_V2_HOST_COPY_DMA_MIN_BYTES0= any size, negative = never (today's behaviour)With the default an 884,736 B attention page stays on the kernel and the two state pages go to the copy engines. The knob is read once per process; a value that is not an integer, or is out of range, is reported at WARNING and ignored. The copy-engine branch submits all tasks of the call with one
cuMemcpyBatchAsyncon the caller's stream and returns theCUresult; on the legacy NULL stream (0orCU_STREAM_LEGACY), which the batched call does not accept, it issues onecuMemcpy*Asyncper task. The dispatch also rejects a negative byte count, and a byte count the kernel launcher'suint32_tcannot carry, withTLLM_CHECK_WITH_INFOinstead of letting them wrap.Other files:
kvCacheManagerV2Utils.h: declarations ofhostCopyDmaMinBytes(),hostCopyDmaMinBytesFor(rawValue)(the knob's semantics on a raw value, so tests can drive them without resetting the process-wide cache),kHostCopyDmaMinBytesEnvanduseCopyEngineForHostCopy(), plus the contract of the two entry points: the kernel path requires a multiple of 16 and 16 B-aligned task addresses, the copy-engine path accepts any size and alignment, and the reach stated above.nanobind/batch_manager/kvCacheManagerV2Utils.cpp:use_copy_engine_for_host_copy(num_bytes)next to the copy helpers, so tests can ask which path a task size takes. Thecopy_*signatures are unchanged.cpp/tests/unit_tests/batch_manager/kvCacheManagerV2TestUtils.h: theCudaAllocation/allocateCudaandScopedEnvhelpers that the sibling KVCM2 tests each carried a copy of, as shared inline definitions (the siblings keep their copies for now).Design notes
copyHostToDevice/copyDeviceToHostinvocation;CopyEngine::transferissues one per pool, so all tasks (pages) of a call have the same size but sit at scattered addresses.cuMemcpyBatchAsynctakes the destination, source and size of every task and submits them in one driver call, so the CPU cost does not grow with the page count and no task limit is needed. It is the same callbatchedPageCopy.cuuses on PCIe parts, with the same attributes, and TensorRT-LLM builds with CUDA 13, where it is always available.cuMemHostRegisterranges, andHostMemregisters the host tier in 2 GiB chunks on Linux 6.11-6.13. Those hosts keep the kernel, which reaches pinned memory through ordinary loads and stores. Same rule asbatchedPageCopy.cu.BatchedPageCopier. It is driven byPoolCopyArgs(base, stride, index pairs) for bulk uniform-page copies and sizes a CTA grid per platform. This path receives the pages of one migration batch as a plain task vector (one or two large state pages per request, a whole batch on eviction or prefetch), where a copy-engine enqueue needs no tuning and no SMs.TLLM_CHECK); they keep that signature and now also throw on a negative byte count. The policy helpers (hostCopyDmaMinBytes,hostCopyDmaMinBytesFor,useCopyEngineForHostCopy) arenoexcept; the warning for a bad knob value is guarded so it cannot turn intostd::terminate.copyWithCopyEngineis notnoexcept, because its descriptor vectors can throwstd::bad_alloc, which the entry points propagate.Measured effect
A/B on a Kimi K3 disaggregated deployment (16 single-GPU prefill workers plus a 16-GPU decode worker, 32 GPUs): fixed concurrency, 1 h windows, identical configuration and observability.
The copies get faster, not fewer. The attention-page batches (140 x 884,736 B per request) keep the kernel and are unaffected.
Which build was measured. The A/B ran the first version of this change: one
cuMemcpyHtoDAsync/cuMemcpyDtoHAsyncper task on the caller's stream, with calls of more than 8 tasks kept on the kernel (a task-limit knob, since removed), compiled into a production image whose KV cache manager predates the cold-page codec (#17512), so thatcopyHostToDevice/copyDeviceToHostwere its only GPU<->host copy path. The code in this PR submits each call as onecuMemcpyBatchAsyncwithCU_MEMCPY_FLAG_PREFER_OVERLAP_WITH_COMPUTEand no task limit. Per-request state migrations carry one task per pool, so the shapes behind the numbers above are single-task calls on both variants; the submitted path has been timed per page (tables below) but not A/B-tested end to end.Measurements on main's path
To see whether the idea still has value on main, where hot<->host migrations run through
BatchedPageCopier, the three paths were timed on the same shapes on one GB300 node (NVLink-C2C, 152 SMs, CUDA 13.4): this PR's helpers at the default threshold (copy engines), the same helpers withMIN_BYTES=-1(the pre-PR single-block kernel) andBatchedPageCopier::launch, which selected its multi-CTA kernel on that coherent link (4 blocks for offload, 6 for onboard, sized for the 224 GB/s fallback link figure because NVML reported no C2C bandwidth on that node). Event-to-event, 10 iterations after warm-up, pinnedHostMembuffers.Idle GPU:
BatchedPageCopierkernel (main)Both the copy engines and main's kernel sit at the link ceiling of that node (about 210 GB/s per direction per GPU); the copy engines are 4 to 5 % ahead. The single-block kernel is 13 to 22x slower on a single large page, which is the gap the A/B above closed.
Same node, every SM thread slot occupied by a spin kernel on another stream (blocks of 0.1 ms or 5 ms, launched back to back), host->device, milliseconds per copy:
BatchedPageCopierkernel (main)The copy engines use no SM, so their time does not change under load; the kernel's blocks wait for a free slot, and giving the copy stream the highest priority did not help, because resident blocks are not preempted. Persistent-scheduled GEMM and attention kernels in a long prefill behave like the 5 ms case. So on coherent links main's production path ties the copy engines when the GPU is idle and loses 1.2 to 4x in copy latency when it is saturated. The follow-up for main is #20426, an option that lets
BatchedPageCopierprefer its existing copy-engine path on coherent links (keeping the kernel where pinned memory is registered in chunks); this PR does not touchBatchedPageCopier.Test Coverage
New tests
cpp/tests/unit_tests/batch_manager/kvCacheManagerV2HostCopyTest.cu(gtest targetkvCacheManagerV2HostCopyTest, one GPU, linked withkvCacheManagerV2PoisonCheck.cpplike its neighbours). The knob is cached per process, so the tests pick shapes on both sides of the default instead of changing the environment, and every path-dependent expectation asks the predicate first, which lets the same binary run under any knob value:ThresholdFromRawValue:hostCopyDmaMinBytesFor()on raw values: unset, empty,4abcand a 20-digit value give the default,0gives 0,-1gives never,16777216gives 16 MiB.ThresholdFollowsEnvironment: the cached accessor equals the parser applied to the process environment, and the predicate over {16 B, 884,736 B, 4 MiB - 1, 4 MiB, 15,261,696 B, 217,055,232 B} follows it.HostToDeviceToHost(parametrised): byte-exactHostMem->cudaMalloc->HostMemround trips in the slot layout of a host pool for (884,736 B x 1 / 140 / 300), (16 B x 300), (15,261,696 B x 1 / 12), (217,055,232 B x 1) and (4 MiB x 8 / 64). 300 > 256 exercises the kernel's multi-launch remainder; the x 12 and x 64 shapes send many large pages through one batched call. The fill is pseudo-random per 64-bit word, so no two slots carry the same bytes and a permuted or slot-0 source read is caught.NonMultipleOf16: 3 x 24 B and 3 x (4 MiB + 8 B). A size the predicate sends to the copy engines must round-trip (they accept any size); a size it keeps on the kernel must throwTllmExceptionfrom both entry points before any CUDA work. At the default the first throws and the second round-trips.NegativeByteCountIsRejected,EmptyTaskListIsANoOp,NullStreamAccepted(the per-task fallback on0and onCU_STREAM_LEGACYwith 1 and 3 large pages, and the kernel) andStreamOrderingThroughEvents(producer stream -> recorded event -> consumer stream sees the bytes; a host-function gate holds the producer until everything is enqueued, so the check is deterministic rather than won by timing).StateSlotBandwidthInfo: records the GB/s of one 217 MB page per direction and the path that moved it as test properties. Not asserted.tests/unittest/kv_cache_manager_v2_tests/test_copy_engine_host_copy.py(one GPU; the directory is already in thel0_a10,l0_h100andl0_b200lists): round trips through the bindings for (884,736 B x 140 / 300), (16 B x 300), (4 MiB x 44), (15,261,696 B x 1), (217,055,232 B x 1) and 3 x (4 MiB + 8 B), with torch pinned and device buffers on a non-default stream; the predicate against an expectation computed from the environment and the kernel version, not from the predicate itself; a 24 B task, which the kernel rejects and the copy engines accept; the legacy NULL stream; and a consumer stream observing a copy through a recorded event.test_copy_helper_exceptions.pyskips its two host-helper cases when the knob sends a 1 B task to the copy engines, so the directory runs underMIN_BYTES=0as well.Existing coverage
test_copy_helper_exceptions.pyis the only existing test that callscopy_host_to_device/copy_device_to_host; it never performs a copy (1 B, unaligned, fake addresses) and, with the default threshold, is rejected on the kernel branch before any CUDA work. The host-tier cases oftest_kv_cache_manager_v2.pydo not reach the two entry points:StorageManager::submitMigrationBatchroutes GPU<->host migrations throughIKvCacheColdPageCodec::encode/decode. The two new tests are therefore the only coverage of the copy-engine branch and ofuseCopyEngineForHostCopy.Validation
cuMemcpy*Async, 8-task limit) compiled and run in production on an earlier base; the image's unit-test lanes show no new failure against the stock image (kvv2_core123/123)cuMemcpyBatchAsyncsubmission in this PR)kvCacheManagerV2HostCopyTestat the PR head (a141d550d9) on a GB300 node (aarch64, CUDA 13.4, Linux 6.17, so the copy-engine path is active;nvcr.io/nvidia/tensorrt-llm/devel:1.4.0rc0,build_wheel.py --cuda_architectures 103-real --fast_build --install), in four environments: default,MIN_BYTES=-1(kernel only),MIN_BYTES=0(copy engines for every size) andMIN_BYTES=abc(warning, default kept)4abcand 20-digit values are reported as "not an integer" and "out of range for a 64-bit integer"; neighbouringkvCacheManagerV2HostMemTest9 passedtest_copy_engine_host_copy.pyandtest_copy_helper_exceptions.pythrough the nanobind module on the same node, same four environmentsMIN_BYTES=-1,MIN_BYTES=abc); 15 passed, 2 skipped (MIN_BYTES=0: the existing unaligned-count test skips its two host-helper cases, as designed)PR Checklist
Please review the following before submitting your PR:
PR description clearly explains what and why. If using CodeRabbit's summary, please make sure it makes sense.
PR Follows TRT-LLM CODING GUIDELINES to the best of your knowledge.
Test cases are provided for new code paths (see test instructions)
If PR introduces API changes, an appropriate PR label is added - either
api-compatibleorapi-breaking. Forapi-breaking, includeBREAKINGin the PR title.Any new dependencies have been scanned for license and vulnerabilities
CODEOWNERS updated if ownership changes
Documentation updated as needed
Update tava architecture diagram if there is a significant design change in PR.
The reviewers assigned automatically/manually are appropriate for the PR.
Please check this after reviewing the above items as appropriate for this PR.
GitHub Bot Help
To see a list of available CI bot commands, please comment
/bot help.🤖 Generated with Claude Code