Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
3766c41
[None][feat] Expose DataTransceiverState via LLM API for arbitrary KV…
Tabrizian Apr 13, 2026
b06fc18
[none][feat] add support for arbitrary KVCache transfer
Tabrizian Apr 14, 2026
1450716
[none][feat] add integration test for arbitrary KV cache transfer
Tabrizian Apr 14, 2026
3fe9b6a
[none][fix] Pin reuse-tree blocks during arbitrary KV cache transfer
Tabrizian Apr 20, 2026
aeee58a
[none][test] use max_tokens=1 for KV cache transfer request
Tabrizian Apr 23, 2026
a8a597a
[none][fix] Unify block pinning into pinBlock/unpinBlock helpers
Tabrizian Jul 1, 2026
a52c9f5
[none][fix] Do not fabricate request ID 0 for llmRequest-agnostic tra…
Tabrizian Jul 1, 2026
9031352
[none][refactor] Single source of truth for request identity in Cache…
Tabrizian Jul 6, 2026
01bb2e5
[none][refactor] Pass pinnedBlockIds by reference in findBlocksInReus…
Tabrizian Jul 6, 2026
5c75930
[none][fix] Do not send duplicate READY signal on reuse-tree transfer…
Tabrizian Jul 6, 2026
8610911
[none][fix] Decouple measurement export from LlmRequest
Tabrizian Jul 6, 2026
def0509
[none][fix] Call is_generation_only_request as a method in activation…
Tabrizian Jul 6, 2026
3ae8176
[none][chore] Include <optional> directly in dataTransceiver.h
Tabrizian Jul 6, 2026
a3c9e3a
[none][chore] Add return annotation to get_cache_transceiver
Tabrizian Jul 6, 2026
83c037d
[none][fix] Propagate RPC errors from get_data_transceiver_state
Tabrizian Jul 6, 2026
168fd99
[none][chore] Remove extraneous f prefix from assertion message (F541)
Tabrizian Jul 6, 2026
4db0be2
[none][test] Add arbitrary KV cache transfer tests to QA core test list
Tabrizian Jul 6, 2026
e562d9e
[none][chore] Fix include order in dataTransceiver.cpp (clang-format)
Tabrizian Jul 6, 2026
9f30754
[none][fix] Exclude request-free transfers from KV cache time capture
Tabrizian Jul 6, 2026
a885e10
[none][chore] Remove disagg debug logging from py_executor
Tabrizian Jul 6, 2026
7ced4b7
[none][fix] Do not misroute normal transfers to the reuse-tree path
Tabrizian Jul 6, 2026
b3444fd
[none][refactor] Carry arbitrary-transfer intent via DataTransceiverS…
Tabrizian Jul 13, 2026
a049628
[none][fix] Serialize block bookkeeping against cache-transceiver thr…
Tabrizian Jul 13, 2026
277c56b
Merge branch 'main' into user/imant/arbitraryKVCacheTransfer
Tabrizian Jul 13, 2026
b5fa342
[none][fix] Heap-allocate UCX recvConnect state to survive teardown
Tabrizian Jul 13, 2026
f5034ab
[none][fix] Reject partially matched blocks in reuse-tree transfer lo…
Tabrizian Jul 13, 2026
5d2c70b
[none][chore] Fix stray escape introduced during merge resolution
Tabrizian Jul 13, 2026
b242c8f
Revert "[none][fix] Reject partially matched blocks in reuse-tree tra…
Tabrizian Jul 15, 2026
5d45081
Merge branch 'main' into user/imant/arbitraryKVCacheTransfer
Tabrizian Jul 15, 2026
e1e5df4
[none][chore] Use renamed discardTransferState in reuse-tree miss path
Tabrizian Jul 15, 2026
24ab488
Merge branch 'main' into user/imant/arbitraryKVCacheTransfer
Tabrizian Jul 15, 2026
17484bb
[none][fix] Keep pinned-block release from throwing in noexcept send …
Tabrizian Jul 21, 2026
1b98c46
[none][fix] Restrict reuse-tree transfer lookup to fully matched prim…
Tabrizian Jul 21, 2026
e1f100f
[none][chore] Log request-free transfers without a fabricated request ID
Tabrizian Jul 21, 2026
d5016c4
[none][test] Add arbitrary KV cache transfer coverage to cacheTransce…
Tabrizian Jul 21, 2026
ea660da
Merge remote-tracking branch 'tabrizian/user/imant/arbitraryKVCacheTr…
Tabrizian Jul 21, 2026
12af636
[none][chore] Tighten comments to constraint statements
Tabrizian Jul 21, 2026
cda7caa
Merge branch 'main' into user/imant/arbitraryKVCacheTransfer
Tabrizian Jul 21, 2026
045e072
[TRTLLM-9920][test] Fix ArbitraryTransferTest storage fidelity and mi…
Tabrizian Jul 21, 2026
4c2c4cb
Merge remote-tracking branch 'origin/main' into user/imant/arbitraryK…
Tabrizian Jul 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions cpp/include/tensorrt_llm/batch_manager/cacheTransceiver.h
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,12 @@ class BaseCacheTransceiver

virtual bool cancelRequest(std::shared_ptr<LlmRequest> llmRequest) = 0;

/// Get the serialized DataTransceiverState (CacheState + CommState) for this transceiver.
[[nodiscard]] virtual std::vector<char> getSerializedDataTransceiverState() const
{
return {};
}

[[nodiscard]] virtual bool hasPoisonedTransferBuffer() const
{
return false;
Expand Down Expand Up @@ -277,6 +283,8 @@ class CacheTransceiver : public BaseCacheTransceiver

virtual bool cancelRequest(std::shared_ptr<LlmRequest> llmRequest) override;

[[nodiscard]] std::vector<char> getSerializedDataTransceiverState() const override;

[[nodiscard]] bool hasPoisonedTransferBuffer() const override;

private:
Expand Down
36 changes: 36 additions & 0 deletions cpp/include/tensorrt_llm/batch_manager/kvCacheManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -1251,14 +1251,29 @@ class WindowBlockManager
return mEnablePartialReuse;
}

//! \brief Look up the block chain matching blockKey in the reuse tree.
[[nodiscard]] std::shared_ptr<KVCacheBlock> findBlocksInReuseTreeByBlockKey(BlockKey const& blockKey);

//! \brief Same lookup, additionally pinning matched blocks; on a miss all pins are
//! rolled back and pinnedBlockIds is cleared.
[[nodiscard]] std::shared_ptr<KVCacheBlock> findBlocksInReuseTreeByBlockKey(
BlockKey const& blockKey, std::vector<KVCacheBlock::IdType>& pinnedBlockIds);

[[nodiscard]] std::shared_ptr<KVCacheBlock> findBlocksInReuseTreeByBlockKeys(
std::vector<BlockKey> const& blockKeys);

//! \brief Unpin blocks by block ids directly
void unpinBlocksById(std::vector<KVCacheBlock::IdType> const& blockIds);

//! \brief Pin a block: claim it from the eviction policy if free, then take a reference.
//! Safe to call from cache-transceiver threads: block bookkeeping is serialized by the
//! lookup-tree mutex, which every mutating entry point acquires.
void pinBlock(BlockPtr const& block);

//! \brief Inverse of pinBlock: drop one reference and release the block back to the
//! eviction policy once no references remain.
void unpinBlock(BlockPtr const& block);

void truncateBlocks(LlmRequest::VecTokens const& targetTokens, SizeType32 numTokensToKeep);

void resetReuseState()
Expand All @@ -1274,6 +1289,10 @@ class WindowBlockManager
}

private:
//! \brief Shared implementation of the findBlocksInReuseTreeByBlockKey overloads.
[[nodiscard]] std::shared_ptr<KVCacheBlock> findBlocksInReuseTreeByBlockKeyImpl(
BlockKey const& blockKey, bool pinBlocks, std::vector<KVCacheBlock::IdType>& pinnedBlockIds);

//! \brief Walk the reuse tree with precomputed per-block keys (no lock; callers must hold mLookupTree->getMutex()).
[[nodiscard]] std::shared_ptr<KVCacheBlock> searchReuseTree(std::vector<BlockKey> const& blockKeys);

Expand Down Expand Up @@ -1836,6 +1855,12 @@ class BlockManager
return mWindowBlockManagers.at(windowSize).findBlocksInReuseTreeByBlockKey(blockKey);
}

[[nodiscard]] std::shared_ptr<KVCacheBlock> findBlocksInReuseTreeByBlockKey(
BlockKey const& blockKey, SizeType32 windowSize, std::vector<KVCacheBlock::IdType>& pinnedBlockIds)
{
return mWindowBlockManagers.at(windowSize).findBlocksInReuseTreeByBlockKey(blockKey, pinnedBlockIds);
}

[[nodiscard]] std::shared_ptr<KVCacheBlock> findBlocksInReuseTreeByBlockKeys(
std::vector<BlockKey> const& blockKeys, SizeType32 windowSize)
{
Expand Down Expand Up @@ -2210,6 +2235,11 @@ class BaseKVCacheManager
BlockKey const& blockKey, SizeType32 windowSize)
= 0;

//! \brief Pinning lookup: pins matched blocks and records their ids for unpinBlocksById.
[[nodiscard]] virtual std::shared_ptr<KVCacheBlock> findBlocksInReuseTreeByBlockKey(
BlockKey const& blockKey, SizeType32 windowSize, std::vector<KVCacheBlock::IdType>& pinnedBlockIds)
= 0;

[[nodiscard]] virtual std::shared_ptr<KVCacheBlock> findBlocksInReuseTreeByBlockKeys(
std::vector<BlockKey> const& blockKeys, SizeType32 windowSize)
= 0;
Expand Down Expand Up @@ -2650,6 +2680,12 @@ class KVCacheManager : public BaseKVCacheManager
return mBlockManager.findBlocksInReuseTreeByBlockKey(blockKey, windowSize);
}

std::shared_ptr<KVCacheBlock> findBlocksInReuseTreeByBlockKey(
BlockKey const& blockKey, SizeType32 windowSize, std::vector<KVCacheBlock::IdType>& pinnedBlockIds) override
{
return mBlockManager.findBlocksInReuseTreeByBlockKey(blockKey, windowSize, pinnedBlockIds);
}

std::shared_ptr<KVCacheBlock> findBlocksInReuseTreeByBlockKeys(
std::vector<BlockKey> const& blockKeys, SizeType32 windowSize) override
{
Expand Down
16 changes: 15 additions & 1 deletion cpp/include/tensorrt_llm/executor/dataTransceiverState.h
Original file line number Diff line number Diff line change
Expand Up @@ -619,9 +619,22 @@ class DataTransceiverState final
return mCacheState.has_value() && mCacheState->hasRnnConfig();
}

/// @brief Set only when exported via CacheTransceiver::getSerializedDataTransceiverState:
/// transfers driven by such a state have no LlmRequest on the sender.
[[nodiscard]] bool isArbitraryTransferState() const noexcept
{
return mIsArbitraryTransferState;
}

void setIsArbitraryTransferState(bool isArbitraryTransferState) noexcept
{
mIsArbitraryTransferState = isArbitraryTransferState;
}

[[nodiscard]] bool operator==(DataTransceiverState const& other) const noexcept
{
return mCacheState == other.mCacheState && mCommState == other.mCommState;
return mCacheState == other.mCacheState && mCommState == other.mCommState
&& mIsArbitraryTransferState == other.mIsArbitraryTransferState;
}

[[nodiscard]] std::string toString() const
Expand All @@ -642,6 +655,7 @@ class DataTransceiverState final
friend class Serialization;
std::optional<kv_cache::CacheState> mCacheState;
std::optional<kv_cache::CommState> mCommState;
bool mIsArbitraryTransferState{false};
};

} // namespace tensorrt_llm::executor
66 changes: 42 additions & 24 deletions cpp/tensorrt_llm/batch_manager/cacheFormatter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ void sendAllBuffers(TransferSession& session, int deviceId,
namespace tensorrt_llm::batch_manager::kv_cache_manager
{

BlockRange getBlockRangeForSending(BaseKVCacheManager* cacheManager, LlmRequest const& llmRequest,
BlockRange getBlockRangeForSending(BaseKVCacheManager* cacheManager, std::optional<LlmRequest const*> llmRequest,
BlockKey const& lastBlockKey, int32_t indexFromEnd, bool recvSideHasCP, SizeType32 ppSize)
{
auto poolNum = cacheManager->getBlockManager().getNumPools(
Expand All @@ -197,9 +197,10 @@ BlockRange getBlockRangeForSending(BaseKVCacheManager* cacheManager, LlmRequest
|| lastBlockKey.uniqueTokens.size() == 0 || recvSideHasCP || ppSize > 1)
{
// disable reuse path, and vwsa don't support reuse.
TLLM_CHECK_WITH_INFO(llmRequest.has_value(), "LlmRequest required for non-reuse-tree transfer path");
Comment thread
Tabrizian marked this conversation as resolved.
bool needSendAllForWindow = common::getEnvKVCacheTransferAllBlocksForWindow();

auto blockRange = BlockRange::fromAllBlockIds(*cacheManager, llmRequest.mRequestId);
auto blockRange = BlockRange::fromAllBlockIds(*cacheManager, (*llmRequest)->mRequestId);

auto const& windowsMetadata = cacheManager->getBlockManager().getWindowSizesMetadata();

Expand Down Expand Up @@ -235,16 +236,20 @@ BlockRange getBlockRangeForSending(BaseKVCacheManager* cacheManager, LlmRequest

TLLM_CHECK_WITH_INFO(lastBlockKey.uniqueTokens.size() > 0, "lastBlockKey must be non-empty when reuse is enabled");

auto multimodalHashes = llmRequest.getMultimodalHashes();
bool isMultimodal = multimodalHashes.has_value() && *multimodalHashes && !(*multimodalHashes)->empty();
if (isMultimodal)
// No request on the reuse-tree path: fall through to the plain lastBlockKey lookup.
if (llmRequest.has_value())
{
auto tokensPerBlock = cacheManager->getBlockManager().getTokensPerBlock();
auto const usableSize = static_cast<SizeType32>(lastBlockKey.uniqueTokens.size());
auto blockedUniqueTokens = chopVectorIntoBlocks<UniqueToken>(
lastBlockKey.uniqueTokens, usableSize, tokensPerBlock, /*allowPartial=*/true);
auto blockKeys = buildBlockKeys(blockedUniqueTokens, llmRequest);
return BlockRange::fromReuseTree(*cacheManager, blockKeys, indexFromEnd);
auto multimodalHashes = (*llmRequest)->getMultimodalHashes();
bool isMultimodal = multimodalHashes.has_value() && *multimodalHashes && !(*multimodalHashes)->empty();
if (isMultimodal)
{
auto tokensPerBlock = cacheManager->getBlockManager().getTokensPerBlock();
auto const usableSize = static_cast<SizeType32>(lastBlockKey.uniqueTokens.size());
auto blockedUniqueTokens = chopVectorIntoBlocks<UniqueToken>(
lastBlockKey.uniqueTokens, usableSize, tokensPerBlock, /*allowPartial=*/true);
auto blockKeys = buildBlockKeys(blockedUniqueTokens, **llmRequest);
return BlockRange::fromReuseTree(*cacheManager, blockKeys, indexFromEnd);
}
}

return BlockRange::fromReuseTree(*cacheManager, lastBlockKey, indexFromEnd);
Expand Down Expand Up @@ -363,11 +368,15 @@ void CacheFormatter::format(tensorrt_llm::batch_manager::TransferSession& sessio
{
NVTX3_SCOPED_RANGE(CacheFormatter_format);
session.setTime(TransferSession::kTimeFormatter);
auto const& llmRequest = session.getLlmRequest();
TLLM_LOG_DEBUG(
mpi::MpiComm::world().getRank(), "Start sending KV cache for request ID: %ld.", llmRequest.mRequestId);
auto llmRequest = session.getLlmRequest();
if (llmRequest.has_value())
{
TLLM_LOG_DEBUG(
mpi::MpiComm::world().getRank(), "Start sending KV cache for request ID: %ld.", (*llmRequest)->mRequestId);
TLLM_CHECK_WITH_INFO(
(*llmRequest)->mSamplingConfig.beamWidth == 1, "Currently, only beam width 1 is supported.");
}

TLLM_CHECK_WITH_INFO(llmRequest.mSamplingConfig.beamWidth == 1, "Currently, only beam width 1 is supported.");
auto const& connections = session.getConnections();
auto const& selfConfig = session.getSelfState().getCacheState().value();
auto const& destConfig = session.getOtherState().getCacheState().value();
Expand All @@ -385,7 +394,6 @@ void CacheFormatter::format(tensorrt_llm::batch_manager::TransferSession& sessio
size_t targetNum = pickUpConnections.size();
if (targetNum == 0)
{
TLLM_LOG_DEBUG("No targets to send KV cache to for request ID: %ld", llmRequest.mRequestId);
return;
}

Expand Down Expand Up @@ -413,13 +421,15 @@ void CacheFormatter::format(tensorrt_llm::batch_manager::TransferSession& sessio

SizeType32 const numKvPools = static_cast<SizeType32>(kvWindowSizes.size());

TLLM_LOG_DEBUG("CacheFormatter::format: allWindowSizes=%zu, kvWindowSizes=%d, numPools=%d, requestId=%lu",
allWindowSizes.size(), numKvPools, numPools, llmRequest.mRequestId);
TLLM_LOG_DEBUG("CacheFormatter::format: allWindowSizes=%zu, kvWindowSizes=%d, numPools=%d, requestId=%s",
allWindowSizes.size(), numKvPools, numPools,
llmRequest.has_value() ? std::to_string((*llmRequest)->mRequestId).c_str() : "<request-free>");

bool layerWise = common::getEnvDisaggLayerwise() && numKvPools == 1;
if (layerWise)
{
auto& progress = llmRequest.getContextProgress();
TLLM_CHECK_WITH_INFO(llmRequest.has_value(), "LlmRequest required for layer-wise transfer");
auto& progress = (*llmRequest)->getContextProgress();
SizeType32 const numLayers = blockManager.getNumLayers();
runtime::ITensor::Shape offset = runtime::ITensor::makeShape({0, 0});
for (SizeType32 layerIdx = 0; layerIdx < numLayers; layerIdx++)
Expand Down Expand Up @@ -515,8 +525,11 @@ void CacheFormatter::format(tensorrt_llm::batch_manager::TransferSession& sessio
}
}
}
TLLM_LOG_DEBUG(mpi::MpiComm::world().getRank(), "End the sending of KV cache for the request ID: %ld.",
llmRequest.mRequestId);
if (llmRequest.has_value())
{
TLLM_LOG_DEBUG(mpi::MpiComm::world().getRank(), "End the sending of KV cache for the request ID: %ld.",
(*llmRequest)->mRequestId);
}

return;
}
Expand Down Expand Up @@ -635,15 +648,20 @@ void CacheFormatter::format(tensorrt_llm::batch_manager::TransferSession& sessio
sendHolder.release();
session.setTime(TransferSession::kTimePostprocess);
}
TLLM_LOG_DEBUG(
mpi::MpiComm::world().getRank(), "End the sending of KV cache for the request ID:%ld ", llmRequest.mRequestId);
if (llmRequest.has_value())
{
TLLM_LOG_DEBUG(mpi::MpiComm::world().getRank(), "End the sending of KV cache for the request ID:%ld ",
(*llmRequest)->mRequestId);
}
}

void CacheFormatter::unformat(tensorrt_llm::batch_manager::TransferSession& session)
{
NVTX3_SCOPED_RANGE(CacheFormatter_unformat);
session.setTime(TransferSession::kTimeFormatter);
auto const& llmRequest = session.getLlmRequest();
auto llmRequestOpt = session.getLlmRequest();
TLLM_CHECK_WITH_INFO(llmRequestOpt.has_value(), "LlmRequest required for receiving KV cache");
auto const& llmRequest = **llmRequestOpt;
auto const ctxReqId = llmRequest.getContextPhaseParams().value().getReqId();
TLLM_LOG_DEBUG(mpi::MpiComm::world().getRank(),
"Start receiving KV cache for request ID: %ld, context request ID: %ld.", llmRequest.mRequestId, ctxReqId);
Expand Down
2 changes: 1 addition & 1 deletion cpp/tensorrt_llm/batch_manager/cacheFormatter.h
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ inline std::pair<std::vector<size_t>, std::vector<size_t>> pickRecvConnections(s

namespace tensorrt_llm::batch_manager::kv_cache_manager
{
BlockRange getBlockRangeForSending(BaseKVCacheManager* cacheManager, LlmRequest const& llmRequest,
BlockRange getBlockRangeForSending(BaseKVCacheManager* cacheManager, std::optional<LlmRequest const*> llmRequest,
BlockKey const& lastBlockKey, SizeType32 indexFromEnd, bool recvSideHasCP = false, SizeType32 ppSize = 1);

using DataContext = tensorrt_llm::executor::kv_cache::DataContext;
Expand Down
12 changes: 12 additions & 0 deletions cpp/tensorrt_llm/batch_manager/cacheTransceiver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
#include "tensorrt_llm/common/tllmDataType.h"
#include "tensorrt_llm/executor/cache_transmission/mpi_utils/connection.h"
#include "tensorrt_llm/executor/dataTransceiverState.h"
#include "tensorrt_llm/executor/serialization.h"
#include "tensorrt_llm/executor/serializeUtils.h"
#include "tensorrt_llm/runtime/utils/mpiUtils.h"
#include "tensorrt_llm/runtime/utils/pgUtils.h"
Expand Down Expand Up @@ -591,6 +592,17 @@ void CacheTransceiver::initializeCommState()
mCommState = std::addressof(mCacheSender->getCommState());
}

std::vector<char> CacheTransceiver::getSerializedDataTransceiverState() const
{
TLLM_CHECK(mCommState != nullptr && mCacheState != nullptr);
executor::DataTransceiverState state;
state.setCommState(*mCommState);
state.setCacheState(*mCacheState);
// Only this API marks the state; context responses leave it unset.
state.setIsArbitraryTransferState(true);
return executor::Serialization::serialize(state);
}

void CacheTransceiver::setContextState(LlmRequest* llmRequest)
{
TLLM_CHECK(llmRequest && llmRequest->isContextOnlyRequest());
Expand Down
Loading
Loading