[TRTLLM-11159][feat] Wire KVCacheBlock to UnifiedBlockTree, replacing mPrevBlock/mNextBlocks with lookup-node pointers. - #11919
Conversation
|
/bot run --disable-fail-fast |
|
PR_Github #37747 [ run ] triggered by Bot. Commit: |
📝 WalkthroughWalkthroughThis PR introduces a radix tree-based KV cache block management system for TensorRT LLM, consisting of a templated trie data structure, block key abstractions with multimodal support, a specialized radix block tree for cache block lookups, and integration into the existing KV cache manager's block linking infrastructure. Changes
Sequence Diagram(s)sequenceDiagram
participant Req as LLM Request
participant BM as Block Manager
participant RBT as Radix<br/>Block Tree
participant BK as BlockKey
participant KB as KV Cache Block
Req->>BM: Process request with tokens
BM->>BK: generateBlockHashExtraKeys(request)
BK-->>BM: Vector of MmKey entries
BM->>BK: buildBlockKeys(blocked tokens)
BK-->>BM: Vector of BlockKey instances
BM->>RBT: insertBlock(BlockKey prefix, windowSize)
RBT->>RBT: Create/reuse trie nodes
RBT->>KB: Store BlockPtr in tree node
RBT-->>BM: Insertion complete
Note over BM: Later lookup
BM->>RBT: lookupBlock(BlockKey prefix, windowSize)
RBT->>RBT: Traverse trie by token prefix
RBT->>RBT: Match partial keys if needed
RBT-->>BM: Optional BlockPtr
BM->>KB: attachToLookupNode(lookup node)
KB-->>BM: Block linked to tree
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes 🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
cpp/include/tensorrt_llm/batch_manager/common.h (1)
1-2:⚠️ Potential issue | 🟡 MinorUpdate the header year range for this modified file.
This header now has 2026 changes, but the copyright range still ends at 2024.
As per coding guidelines: "All TensorRT-LLM source files should contain an NVIDIA copyright header with the year of the latest meaningful modification."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cpp/include/tensorrt_llm/batch_manager/common.h` around lines 1 - 2, Update the copyright header at the top of this file (the file-level comment in cpp/include/tensorrt_llm/batch_manager/common.h) so the year range includes the latest modification year 2026; replace the existing "2023-2024" range with "2023-2026" (or otherwise ensure 2026 is the ending year) to comply with the project header guideline.cpp/include/tensorrt_llm/batch_manager/kvCacheManager.h (1)
1-2:⚠️ Potential issue | 🟡 MinorUpdate the header year range for this modified file.
This header contains 2026 changes, but the copyright range still ends at 2024.
As per coding guidelines: "All TensorRT-LLM source files should contain an NVIDIA copyright header with the year of the latest meaningful modification."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cpp/include/tensorrt_llm/batch_manager/kvCacheManager.h` around lines 1 - 2, Update the copyright header in kvCacheManager.h: replace the existing "Copyright (c) 2022-2024, NVIDIA CORPORATION." comment block with a header that includes the latest modification year (e.g., "2022-2026" or "2026" per project convention) so the range reflects the 2026 changes; ensure the change is made in the top-of-file header comment where the current copyright line appears.
🧹 Nitpick comments (1)
cpp/tests/unit_tests/batch_manager/blockKeyTest.cpp (1)
164-170: Add a regression test for multimodal start-offset semantics.These additions cover
extraKeyshash differences, but not the case where multimodal content starts inside a block vs before a block. A focused test ongenerateBlockHashExtraKeys(...).startOffsetwould lock this behavior down.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cpp/tests/unit_tests/batch_manager/blockKeyTest.cpp` around lines 164 - 170, Add a unit test (e.g., TEST_F(BlockKeyTest, MultimodalStartOffsetSemantics)) that constructs two BlockKey instances using makeMmKey with the same multimodal token but different start positions (one where the multimodal content starts before the block and one where it starts inside the block), call generateBlockHashExtraKeys(...) on each and assert the produced .startOffset values differ and that BlockKeyHasher::hash(...) reflects that difference (i.e., hashes are not equal); use the existing helpers BlockKey, BlockKeyHasher, makeMmKey and generateBlockHashExtraKeys to locate the code to modify.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@cpp/include/tensorrt_llm/batch_manager/blockKey.h`:
- Around line 96-97: The precheck in numMatchingTokens() omits usesExtraIds so
keys with different usesExtraIds can proceed and yield false matches; update the
equality check that currently compares loraTaskId, extraKeys, and cacheSaltID to
also compare usesExtraIds (i.e., ensure usesExtraIds == other.usesExtraIds)
before running the token-prefix logic in numMatchingTokens(), so the function
returns non-matching for keys that differ in usesExtraIds.
In `@cpp/include/tensorrt_llm/batch_manager/kvCacheManager.h`:
- Around line 208-214: The two docblocks are inconsistent about getPrevBlock()
behavior: update the comments for the dummy root wiring (the block documenting
mCachedBlocksRoot and the later doc around lines 239-243) to state the same
contract: that after initialising the dummy root lookup-node link (the routine
described in the root block comment) direct children of the root will return the
root block from getPrevBlock(); before that initialization getPrevBlock() will
return nullptr. Reference getPrevBlock(), mCachedBlocksRoot, the dummy root
lookup-node initialization description and WindowBlockManager in both places and
make the wording symmetric so both docblocks describe the same initialized vs
uninitialized behavior.
In `@cpp/include/tensorrt_llm/batch_manager/radixBlockTree.h`:
- Around line 84-90: lookupBlock currently returns the first (shallowest) valid
vm from valueMatches.matches because it iterates forward; change it to select
the most specific (deepest) valid match by scanning valueMatches.matches from
the end toward the front (or otherwise choose the last valid entry) before
returning vm.value. Update the logic in lookupBlock to examine
valueMatches.matches in reverse order and return the first encountered entry
where vm.isValid && vm.value, leaving lookupValues, allowPartialMatch, and
windowSize usage unchanged.
In `@cpp/include/tensorrt_llm/batch_manager/templatedTrie.h`:
- Around line 401-411: The current _getEdges implementation stops recursion when
mValue is non-empty, so descendant edges are dropped; change the logic in
_getEdges/getEdges so that after adding the current node's edge when mValue is
not empty (using edges.emplace_back(edge)), the method continues to iterate
mNextNodes and call node->_getEdges(edge, edges) for each child; ensure mValue,
edges, _getEdges and mNextNodes are referenced to locate and update the
function.
In `@cpp/tensorrt_llm/batch_manager/kvCacheManager.cpp`:
- Around line 133-155: The attachToLookupNode and detachFromLookupNode functions
currently ignore the boolean results from node->setValue(...) and
mLookupNode->clearValue(...), which lets local members (mLookupNode,
mWindowSize) become out-of-sync with the trie; update attachToLookupNode to
check the return value of node->setValue(windowSize, std::move(self),
/*overwrite=*/false) and only assign mLookupNode and mWindowSize when setValue
returns true (otherwise log or propagate an error), and update
detachFromLookupNode to only reset mLookupNode and mWindowSize when the prior
mLookupNode->clearValue(mWindowSize) returns true (otherwise leave state intact
and log/handle the failure or retry); reference these exact symbols
(attachToLookupNode, detachFromLookupNode, node->setValue,
mLookupNode->clearValue, mLookupNode, mWindowSize) when making the change.
---
Outside diff comments:
In `@cpp/include/tensorrt_llm/batch_manager/common.h`:
- Around line 1-2: Update the copyright header at the top of this file (the
file-level comment in cpp/include/tensorrt_llm/batch_manager/common.h) so the
year range includes the latest modification year 2026; replace the existing
"2023-2024" range with "2023-2026" (or otherwise ensure 2026 is the ending year)
to comply with the project header guideline.
In `@cpp/include/tensorrt_llm/batch_manager/kvCacheManager.h`:
- Around line 1-2: Update the copyright header in kvCacheManager.h: replace the
existing "Copyright (c) 2022-2024, NVIDIA CORPORATION." comment block with a
header that includes the latest modification year (e.g., "2022-2026" or "2026"
per project convention) so the range reflects the 2026 changes; ensure the
change is made in the top-of-file header comment where the current copyright
line appears.
---
Nitpick comments:
In `@cpp/tests/unit_tests/batch_manager/blockKeyTest.cpp`:
- Around line 164-170: Add a unit test (e.g., TEST_F(BlockKeyTest,
MultimodalStartOffsetSemantics)) that constructs two BlockKey instances using
makeMmKey with the same multimodal token but different start positions (one
where the multimodal content starts before the block and one where it starts
inside the block), call generateBlockHashExtraKeys(...) on each and assert the
produced .startOffset values differ and that BlockKeyHasher::hash(...) reflects
that difference (i.e., hashes are not equal); use the existing helpers BlockKey,
BlockKeyHasher, makeMmKey and generateBlockHashExtraKeys to locate the code to
modify.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: c5eb5f0b-720d-41de-b74f-fda5a3cee44a
📒 Files selected for processing (13)
cpp/include/tensorrt_llm/batch_manager/blockKey.hcpp/include/tensorrt_llm/batch_manager/common.hcpp/include/tensorrt_llm/batch_manager/kvCacheManager.hcpp/include/tensorrt_llm/batch_manager/radixBlockTree.hcpp/include/tensorrt_llm/batch_manager/stringSetTrie.hcpp/include/tensorrt_llm/batch_manager/templatedTrie.hcpp/tensorrt_llm/batch_manager/CMakeLists.txtcpp/tensorrt_llm/batch_manager/blockKey.cppcpp/tensorrt_llm/batch_manager/kvCacheManager.cppcpp/tests/unit_tests/batch_manager/CMakeLists.txtcpp/tests/unit_tests/batch_manager/blockKeyTest.cppcpp/tests/unit_tests/batch_manager/radixBlockTreeTest.cppcpp/tests/unit_tests/batch_manager/radixTreeTest.cpp
thorjohnsen
left a comment
There was a problem hiding this comment.
This PR replaces the old search structure with the new constant radix search tree from templatedTrie.h, and it does it in a way that preserves the behavior of the old WindowBlockManager and KVCacheBlock APIs, thus none of the existing unit- and integration tests should break. It opens up the possibility of loosening the block eviction restrictions gradually. For example, we can keep the rule that evicts all children of a newly evicted block for full attention layers and remove it for sliding window attention layers. Evicting children makes sense for full attention layers, because a block cannot be reused if one of it's ancestors have been evicted, but it does not make sense for sliding window attention layers because the evicted ancestor block likely will be outside current attention window and thus is not needed for reuse. Very good initiative Simeng!
I found one or more bugs that must be fixed before merge (see follow-up review).
|
PR_Github #37747 [ run ] completed with state
|
There was a problem hiding this comment.
- Having the utilities
attachToLookupNode,detachFromLookupNode,setAsRootwithLookupNodeas the caller will be more natural. - Mutex is removed, how do we guarantee a safe read to a node now?
- I think its better to do a unified search tree in this merge request rather than defer to a "PR B".
- Please evaluate and see if we need those
[[maybe_unused]]variables. For instance, you can return them in its parent function and log them. - Update copyright header for the edited files
- Its a good time to implement
freeDescendantsRecursivelywith a stack. Calling it recursively may impose stack overflow.
eopXD
left a comment
There was a problem hiding this comment.
Trie nodes are CPU heap objects not tracked by the block pool. In the worst case the number of live trie nodes is block_pool_size × max_depth, which maybe pretty large.
We should be aware of this, and possibly add logging of this in follow-up work.
|
/bot run --disable-fail-fast |
|
PR_Github #37923 [ run ] triggered by Bot. Commit: |
Prefer to keep them in KVCacheBlock and they are "operations" of the block and use/update private variable of KVCacheBlock.
The mCachedBlocksRootMutex should server the purpose.
Implemented in the second commit.
Updated in the second commit.
Updated in the second commit.
Implemented in the second commit. |
thorjohnsen
left a comment
There was a problem hiding this comment.
Great work, just have a couple of questions.
|
PR_Github #37923 [ run ] completed with state
|
57fdf79 to
748f77c
Compare
Updated in the new commit.
The calls are guarded by
Nice call, fixed in the new commit. |
|
/bot run --disable-fail-fast |
|
PR_Github #38067 [ run ] triggered by Bot. Commit: |
|
PR_Github #38067 [ run ] completed with state
|
…ock/mNextBlocks with lookup-node pointers. Signed-off-by: SimengLiu-nv <simengl@nvidia.com>
Signed-off-by: SimengLiu-nv <simengl@nvidia.com>
Signed-off-by: SimengLiu-nv <simengl@nvidia.com>
|
/bot run --disable-fail-fast |
|
PR_Github #38108 [ run ] triggered by Bot. Commit: |
|
PR_Github #38108 [ run ] completed with state
|
|
/bot run |
|
PR_Github #38116 [ run ] triggered by Bot. Commit: |
|
PR_Github #38116 [ run ] completed with state |
… mPrevBlock/mNextBlocks with lookup-node pointers. (NVIDIA#11919) Signed-off-by: SimengLiu-nv <simengl@nvidia.com>
… mPrevBlock/mNextBlocks with lookup-node pointers. (NVIDIA#11919) Signed-off-by: SimengLiu-nv <simengl@nvidia.com>
…ertion failure addNextBlock() silently skipped attaching a block to the lookup tree when the slot was already occupied by another block. After PR NVIDIA#11919 replaced the explicit setPrevBlock() pointer with lookup-node-derived navigation, this caused getPrevBlock() to return stale/null values, triggering: Assertion failed: storedBlocks.empty() || block->getPrevBlock() == storedBlocks.back() (kvCacheManager.cpp:1674) This manifests in disaggregated serving when enable_partial_reuse is True (the default) and requests have no common prefix. Fix: evict the existing block from the occupied slot before attaching the new one. Re-lookup the child node after eviction since cascade-pruning may have removed it. Signed-off-by: Aurelien Chartier <2567591+achartier@users.noreply.github.com>
… mPrevBlock/mNextBlocks with lookup-node pointers. (NVIDIA#11919) Signed-off-by: SimengLiu-nv <simengl@nvidia.com>
…l_reuse Fixes three chained assertion failures in disaggregated serving when enable_partial_reuse=True (the default) and requests lack a common prefix. 1. Duplicate block IDs in storeBlocks (kvCacheManager.cpp): The same physical block can appear at multiple positions in a sequence's block list. storeBlocks now skips insertion when the block is already the current searchRoot, preventing self-referential tree corruption. 2. addNextBlock silent no-op (kvCacheManager.cpp): After PR NVIDIA#11919 replaced setPrevBlock() with lookup-node navigation, addNextBlock silently skipped attachment when a slot was occupied. Changed to evict-and-replace: detach the existing block and attach the new one, keeping getPrevBlock() consistent. 3. fromReuseTree fallback (kvCacheUtils.h, cacheFormatter.cpp): When storeBlocks skips a duplicate, the reuse tree is incomplete. Changed fromReuseTree to return std::optional and fall back to fromAllBlockIds in getBlockRangeForSending. Resolves the existing TODO about handling missing blocks. 4. Termination race (py_executor.py): When enable_partial_reuse_for_disagg was True, _handle_responses terminated requests without checking is_disagg_context_transmission_state, racing with the async CacheSender thread. Unified the termination guard to always check transmission state before terminating. Signed-off-by: Aurelien Chartier <2567591+achartier@users.noreply.github.com>
…ertion failure addNextBlock() silently skipped attaching a block to the lookup tree when the slot was already occupied by another block. After PR NVIDIA#11919 replaced the explicit setPrevBlock() pointer with lookup-node-derived navigation, this caused getPrevBlock() to return stale/null values, triggering: Assertion failed: storedBlocks.empty() || block->getPrevBlock() == storedBlocks.back() (kvCacheManager.cpp:1674) This manifests in disaggregated serving when enable_partial_reuse is True (the default) and requests have no common prefix. Fix: evict the existing block from the occupied slot before attaching the new one. Re-lookup the child node after eviction since cascade-pruning may have removed it. Signed-off-by: Aurelien Chartier <2567591+achartier@users.noreply.github.com>
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes
Tests
Description
Test Coverage
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)
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.