[None][feat] KVCacheManagerV2 C++ translation - #14047
Open
lowsfer wants to merge 30 commits into
Open
Conversation
Collaborator
|
/bot run --disable-fail-fast --stage-list "PerfSanity" |
lowsfer
force-pushed
the
kvCacheManagerV2-cpp
branch
2 times, most recently
from
May 26, 2026 08:44
5bd984d to
456cfa8
Compare
lowsfer
force-pushed
the
kvCacheManagerV2-cpp
branch
3 times, most recently
from
June 15, 2026 10:25
4f4dd8d to
0802c6c
Compare
lowsfer
force-pushed
the
kvCacheManagerV2-cpp
branch
6 times, most recently
from
June 18, 2026 09:30
4c24d8a to
44c797c
Compare
lowsfer
force-pushed
the
kvCacheManagerV2-cpp
branch
5 times, most recently
from
July 10, 2026 10:10
b4592c1 to
5a1fa77
Compare
lowsfer
force-pushed
the
kvCacheManagerV2-cpp
branch
3 times, most recently
from
July 20, 2026 03:03
fd7d167 to
99bfc50
Compare
lowsfer
marked this pull request as ready for review
July 20, 2026 05:16
ReuseScope.salt (low 8 bytes of a sha256 digest) and lora_id can occupy the full unsigned 64-bit range, but the C++ ReuseScope stored them as int64_t and the nanobind casters used std::optional<int64_t>. Passing a salt >= 2**63 from Python raised TypeError at ReuseScope(salt=...) on the C++ backend. Type them as uint64 end-to-end, mirroring the RequestIdType approach already used for request ids (rather than reinterpret-casting at the nanobind boundary): - ReuseScope::loraId -> std::optional<LoraTaskIdType>, salt -> std::optional<std::uint64_t>; add a LoraTaskIdType alias in the V2 common.h next to RequestIdType. - Propagate uint64 through the V1 event-hash path (V1RootAttrs, hashV1BlockKey) so no implicit narrowing occurs. - Update the nanobind ReuseScope binding, optionalIntToObject, castOptionalIntAttr, and the int lora_task_id fallback to uint64. Block-key and V1 event hashes are unchanged: toBytes/hash mixing serialize the raw little-endian 8 bytes, which are bit-identical for a given value whether stored as int64 or uint64, and the Python side already serializes with signed=False. Signed-off-by: Yao Yao <lowsfer@users.noreply.github.com>
…key-gen helpers as public API Two related cleanups to KVCacheManagerV2's reuse-key handling. C++ hashing: - Add Hasher::update(ReuseScope) that serializes the scope straight into the SHA-256 state via a shared emitReuseScopeBytes() emitter, removing the std::vector allocation the Hasher(ReuseScope) constructor incurred on the key-generation hot path. - Drop ReuseScope::toBytes() and its nanobind `to_bytes` binding. Its only consumer was a unit test; production hashing never used it (a C++ ReuseScope is hashed internally and never round-tripped through Python). Public key-gen API: - Move the pure-Python reference helpers (gen_multimodal_cache_key_tokens, Hasher, reuse_scope_to_bytes, sequence_to_blockchain_keys) into a new backend-neutral module _cache_key.py and export gen_multimodal_cache_key_tokens and sequence_to_blockchain_keys from the package on both backends (added to __all__ and the .pyi stub). - _block_radix_tree.py re-exports from _cache_key; ReuseScope.to_bytes() delegates to reuse_scope_to_bytes(). Removes the cpp dispatcher branch's duplicate gen_multimodal_cache_key_tokens. - reuse_scope_to_bytes() serializes from the scope's fields, so it is backend-neutral (no dependency on a .to_bytes() method) and byte-identical to the C++ emitReuseScopeBytes() and the Python signed=False layout. Tests: - Import sequence_to_blockchain_keys from the public API in test_router and test_kv_cache_salting. - Rewrite the salting test to assert distinct scopes seed distinct blockchain keys (digests) instead of asserting on raw to_bytes output. Signed-off-by: Yao Yao <lowsfer@users.noreply.github.com>
The nanobind module registers its own OutOfPagesError exception class, so 'except OutOfPagesError' clauses importing the pure-Python class from kv_cache_manager_v2._exceptions never match on the C++ backend and the guarded allocation paths crash instead of backing off. The package __init__ already re-exports the backend-appropriate class; import that. Signed-off-by: Lance Liao <108499334+lancelly@users.noreply.github.com> (cherry picked from commit fdbe4f9)
release_index_slot passes None to clear the DSA index buffer, but the buf argument rejected None at the nanobind layer (missing .none() annotation) even though the lambda body already handles is_none(). On the C++ backend every context worker died with 'incompatible function arguments ... NoneType' at the first index-slot release. Split from Lance Liao's commit 15a90e1 (the constraint-floor half of that change lands in a separate commit). Signed-off-by: Yao Yao <lowsfer@users.noreply.github.com>
Port the Python-side fix from PR NVIDIA#16484 (nvbugs/6438658) to the C++ StorageManager so both backends behave identically: thread max_util_for_resume from KVCacheManagerConfig through the StorageManager constructor, and scale each constraint-derived min-slot floor by 1/max_util_for_resume in computeMinSlotsFromConstraints so a declared batch stays below the utilization gate checked by KvCache::resume. This mirrors main's behavior exactly: an explicit initial_pool_ratio still overrides constraints for min-slots (the gating is kept), and the scale factor is applied unconditionally (no clamping). Signed-off-by: Yao Yao <lowsfer@users.noreply.github.com>
REVIEW NOTE: this is the cpp-branch behavior delta on top of the previous commit (which mirrors main's PR NVIDIA#16484). It is isolated so it can be reviewed or dropped independently. If we decide to match main exactly, revert just this commit. Diverges from main's resume-utilization fix in two ways, on both the Python and C++ backends: - Constraints stay feasibility floors even under an explicit initial_pool_ratio (main drops them): a declared batch that needs more than its target pool-group share clamps that share up instead of starving during warmup. Follows Lanyu's C++ commit 15a90e1 (which cited PR NVIDIA#16269). - Clamp max_util_for_resume to the binding (0, 1) range before scaling, so a degenerate 0 can't divide-by-zero and a value >= 1 can't shrink floors. Renames test_initial_pool_ratio_overrides_typical_step_and_constraints to test_constraint_floor_overrides_infeasible_initial_pool_ratio to assert the new behavior (main's test_constraint_reserves_resume_headroom is kept as-is). Signed-off-by: Yao Yao <lowsfer@users.noreply.github.com>
The license_checker rejected pendingStats.h and stats.h because their boilerplate wrapped as 'permissions and limitations under / the License' instead of the canonical 'permissions and / limitations under the License' used by every passing C++ source file. Fix the wrap in the two flagged headers plus the sibling kvCacheManagerV2StatsTest.cpp (same style). Signed-off-by: Yao Yao <lowsfer@users.noreply.github.com>
test_kv_pool_rebalance imported OutOfPagesError from the ._exceptions submodule (pure-python class), but py_executor catches the dispatcher package's OutOfPagesError, which is _cpp.OutOfPagesError under the C++ backend. The test raised the python class while the code caught the C++ class, so the expected-failure path was not swallowed and the test failed under the cpp backend (it passed under the python backend where both are the same class). Import the backend-aware symbol so the class matches the one py_executor catches, mirroring the py_executor import. Signed-off-by: Yao Yao <lowsfer@users.noreply.github.com>
…for unregistered role get_index_k_buffer returns None when a layer has no INDEX_KEY buffer registered (dense layers, base V2 manager). It detected this by catching KeyError from the role probe. That holds on the python backend (dict miss -> KeyError), but the C++ backend's getBufferAttr throws std::out_of_range, which nanobind maps to IndexError, so the guard missed it and the exception escaped -> the None-path contract broke and TestIndexKeyBufferAccessor::test_accessor_returns_none_on_dense_layer and ::test_accessor_returns_none_on_base_v2_default_manager failed under the C++ backend on H100/B300. Catch (KeyError, IndexError) so 'role not registered -> None' holds on either backend. Verified on H100 with TLLM_KV_CACHE_MANAGER_V2_BACKEND=cpp: the full TestIndexKeyBufferAccessor class passes (was 2 failing). Signed-off-by: Yao Yao <lowsfer@users.noreply.github.com>
The radix tree is a globally shared cross-request/cross-tenant cache index whose prefix matching is decided purely by digest equality (no token re-verification), and its hashed input (tokens, user-supplied cache_salt, multimodal bytes) is attacker-influenceable. Document, on both the C++ (blockRadixTree Hasher/BlockKey) and Python (_cache_key Hasher) sides plus the kvcache doc, that the block-key hash must stay cryptographic, collision-resistant, and >=256-bit (SHA-256), and must not be swapped for a non-cryptographic hash or truncated without adding a token-content equality check on match. Comment/doc-only. Signed-off-by: Yao Yao <lowsfer@users.noreply.github.com>
Port PlannedDropHandle and plan_committed_block_drop() to the C++ backend, mirroring the Python implementation added in fd9c32f. - CommittedPage gains a planned_drop_count field. - PlannedDropHandle holds weak references to committed pages, bumps their planned-drop count on construction, and on drop() decrements each live page's count, removing an already-droppable/queued page from eviction tracking on its final plan. Double drop raises ValueError. - KvCache::planCommittedBlockDrop() collects the SWA in-window committed pages for each attention life cycle (excluding full-attention and sink blocks), returning nullptr if any required page is unavailable. - nanobind exposes PlannedDropHandle and _KVCache.plan_committed_block_drop; the dispatcher now resolves PlannedDropHandle from the C++ module. Signed-off-by: Yao Yao <lowsfer@users.noreply.github.com>
…ager-devs The new implementation directory cpp/tensorrt_llm/batch_manager/kv_cache_manager_v2/ is not matched by the existing kvCache*/blockKey* globs (a CODEOWNERS '*' does not cross '/'), so it fell through to trt-llm-runtime-devs. Add an explicit directory entry so the KV cache manager team owns the C++ v2 core. The nanobind binding, *Utils, and unit tests are already covered by existing globs. Signed-off-by: Yao Yao <lowsfer@users.noreply.github.com>
Fixes verified-valid issues from PR NVIDIA#14047 review: C++ (cpp/tensorrt_llm/batch_manager/kv_cache_manager_v2): - config.cpp: guard tokensPerBlockOverride against 0 (avoid % by zero UB) - storage/core.cpp: assert non-empty slotSizeList before max_element; check cuCtxGetDevice/cuDeviceTotalMem via cuCheck; guard log2(0) for quotas below 1 GiB - copyEngine.cpp: short-circuit empty/zero-byte transfers (two-hop div-by-zero) - utils/math.h: fix overlap() doc comment; fix DynamicBitset::resize to keep numSetBits accurate and clear stale bits on shrink - utils/cudaEvent.h: increment SimplePool outstanding count only after a successful create (no leak if create throws) - CMakeLists.txt: bump copyright year Python: - _utils.py: mirror the DynamicBitset.resize fix - _storage_manager.py: validate max_util_for_resume via ValueError not assert - pyexecutor/kv_cache_manager_v2.py: narrow the INDEX_KEY try block; use {role!s} Docs: - kvcache.md: distinguish SHA-256 digest size from collision resistance Signed-off-by: Yao Yao <lowsfer@users.noreply.github.com>
…es.replace-compatible The DeepSeek-V4 cache manager (_build_cache_config) and our own host-tier fallback call dataclasses.replace() on the KVCacheManagerConfig. On the Python backend this was a @DataClass; the C++ translation exposes a nanobind class instead, so dataclasses.replace() raised 'TypeError: replace() should be called on dataclass instances', failing all DeepSeek-V4 cache-manager unit tests (compressor_module, cache_manager, indices_transform, sparse_mla, kv_transfer). dataclasses.replace() is a free function keyed on __dataclass_fields__: it reads each field via getattr and rebuilds via cls(**fields). The C++ binding already has a full keyword __init__ and readable fields, so advertise the dataclass field set on the binding. The read-only enable_swa_scratch_reuse property is excluded (not a constructor field), matching the Python dataclass. Python-only change; no C++ rebuild required. Signed-off-by: Yao Yao <lowsfer@users.noreply.github.com>
…c bindings The Python KVCacheDesc/BatchDesc were @DataClass(slots=True, frozen=True), so they compared by value. The C++ translation replaced them with plain structs whose nanobind bindings exposed no __eq__, so Python equality fell back to identity and always returned False for distinct objects. This broke tests/unittest/_torch/executor/test_kv_cache_manager_v2.py (test_builds_warmup_constraints, test_avg_seq_len_updates_typical_step, test_extra_tokens_are_in_context_capacity), which assert config.typical_step / config.constraints against expected BatchDesc values. Add value operator== (and !=) to the C++ structs and bind __eq__ + __repr__ so the bindings match the original dataclass semantics; __repr__ also gives readable assertion diffs. Nothing hashes these types, so __hash__ is not added (BatchDesc holds a list and was not reliably hashable in Python). Signed-off-by: Yao Yao <lowsfer@users.noreply.github.com>
…offset The NVFP4 block-scale pool pointer in _prepare_page_table_tensor must share the SAME origin convention as the KEY pool pointer, so that the 'block_scale_offset == offset' invariant holds on both backends. The KEY pointer is a (representative-layer, 0-offset) origin against which each layer's kv_cache_pool_mapping offset is resolved by _kv_pool_mapping_offset(). That offset function is overridable: the base manager makes it relative to the representative layer (offset(rep) == 0), while address-ranked sparse subclasses (MiniMax-M3 / DeepSeek-V4) make it the layer's rank in physical-address order (offset(rep) may be non-zero, since layer_grouping iteration order is NOT a V2 API contract and the C++ unordered_map need not return the pool-base layer first). Compute the block-scale origin the same way: mirror the KEY base for the representative layer, then shift it back by that layer's offset(rep) * scale_stride. For the base manager this is just the rep layer's scale base; for address-ranked subclasses the shift lands the origin on the pool's slot-0 scale address. Either way block_scale_offset reproduces the same per-layer offset() as KEY, with no dependency on layer_grouping order. This supersedes the earlier pool_group_descs approach, which fixed the address-ranked subclasses but broke the base manager: it pinned the scale base to the pool's slot-0 address while KEY stayed relative to a non-slot-0 representative layer, so the two origins disagreed and test_kv_cache_v2_extra_buffers::test_default_hook_keeps_nvfp4_scale_buffers failed. The now-unused _pool_base_addr_by_group_role() helper is removed. Verified on 1x H100: test_kv_cache_v2_extra_buffers (15), test_per_layer_head_dim (8), test_minimax_m3_kv_transfer (55) and the full executor KV-cache suite (342) all pass. Signed-off-by: Yao Yao <lowsfer@users.noreply.github.com>
Signed-off-by: Tyler Burt <195370667+tburt-nv@users.noreply.github.com>
…ude() kv_cache_manager_v2/CMakeLists.txt is include()'d from batch_manager/ CMakeLists.txt, so CMAKE_CURRENT_SOURCE_DIR is batch_manager/, not this file's own directory. SHA256_DIR used ../../common/sha256, which resolved to cpp/common/sha256 (nonexistent) and failed CMake generation on both x86_64 and SBSA builds. Use ../common/sha256 (one level up from batch_manager/) so it resolves to cpp/tensorrt_llm/common/sha256, consistent with the file's existing CMAKE_CURRENT_SOURCE_DIR=batch_manager convention (SRCS listed as kv_cache_manager_v2/*.cpp; KV_CACHE_MANAGER_V2_INCLUDE_DIR). Signed-off-by: Yao Yao <lowsfer@users.noreply.github.com>
The Release-Check stage runs `license_checker -config jenkins/license_cpp.json`, which requires an Apache header for all files under tensorrt_llm/. The newly added SHA-256 implementation under cpp/tensorrt_llm/common/sha256/ is vendored from Bitcoin Core and carries its upstream MIT license header, so the checker reports "license not exist" (errcount: 6) and fails the pipeline (fail-fast then aborts the downstream GPU test stages). Add the six vendored files to the skip map, matching the existing convention for external / dual-licensed third-party sources (selectiveScan.h, flashMLA, deep_gemm, causalConv1d, vec_dtypes). Signed-off-by: Yao Yao <lowsfer@users.noreply.github.com>
Collaborator
|
/bot run --disable-fail-fast |
Member
Author
|
/bot run |
Collaborator
|
PR_Github #62144 [ run ] triggered by Bot. Commit: |
Collaborator
|
PR_Github #62146 [ run ] triggered by Bot. Commit: |
Collaborator
|
PR_Github #62144 [ run ] completed with state |
tongyuantongyu
approved these changes
Jul 28, 2026
tongyuantongyu
left a comment
Member
There was a problem hiding this comment.
LGTM for the runtime modifications that's not in KV Cache manager source files. I'm trusting trt-llm-kv-cache-manager-devs for them.
Shixiaowei02
approved these changes
Jul 28, 2026
Shixiaowei02
left a comment
Collaborator
There was a problem hiding this comment.
LGTM for the dis-agg related changes.
1 task
Collaborator
|
PR_Github #62146 [ run ] completed with state
|
1 task
Member
Author
|
/bot run |
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.
Dev Engineer Review
QA Engineer Review
kvCacheManagerV2HostMemTest.cppkvCacheManagerV2StatsTest.cppkvCacheManagerV2TypedIndexTest.cpptests/integration/test_lists/,test-db/, orqa/coverage changes are listed. Test functions should be mapped to CI or manual-QA entries.