Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
54 changes: 51 additions & 3 deletions cpp/include/tensorrt_llm/batch_manager/kvCacheManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,46 @@ struct LinearAttentionMetadata
return false;
}

[[nodiscard]] SizeType32 calcNumBlocksNeededForReq(
SizeType32 promptLen, SizeType32 tokensPerBlock, bool enableReuse) const
{
if (!enableReuse)
{
return 1;
}
SizeType32 count = 0;
if (statesSnapshotInterval > 0)
{
count += promptLen / statesSnapshotInterval; // round down
}
if (saveLastSnapshot
&& (promptLen / tokensPerBlock * tokensPerBlock
!= promptLen / statesSnapshotInterval * statesSnapshotInterval))
Comment thread
Wanli-Jiang marked this conversation as resolved.
{
count += 1;
}
if (promptLen % tokensPerBlock == 0)
{
// corner case
count += 1;
}
return count;
}

[[nodiscard]] SizeType32 calcNumAdditionalBlocksNeededForReq(
SizeType32 numTokens, SizeType32 promptLen, SizeType32 tokensPerBlock, bool enableReuse) const
{
if (!enableReuse)
{
return 0;
}
if (promptLen % tokensPerBlock == 0 && numTokens <= promptLen + 1)
{
return 1;
}
return 0;
}

[[nodiscard]] bool hasRecurrentStatesCache() const
{
return hasRecurrentStatesCache(cacheType);
Expand Down Expand Up @@ -872,7 +912,9 @@ class WindowBlockManager

//! \brief According to request's current position, copy data from the last full block to the next block (ignoring
//! the placeholder block). It should be called after every context chunk is processed.
void copyLinearAttentionBlock(GenerationRequest& sequence, LlmRequest const& llmRequest);
//! \return true iff at least one async block transfer was actually issued. Callers can use this to decide
//! whether a subsequent refreshBlocks()/syncTransfers() is necessary.
bool copyLinearAttentionBlock(GenerationRequest& sequence, LlmRequest const& llmRequest);

void replaceSharedBlock(GenerationRequest& sequence, SizeType32 blockIdx);

Expand Down Expand Up @@ -1387,7 +1429,8 @@ class BlockManager

//! \brief According to request's current position, copy data from the last full block to the next block (ignoring
//! the placeholder block). It should be called after every context chunk is processed.
void copyLinearAttentionBlock(GenerationRequest& sequence, LlmRequest const& llmRequest);
//! \return true iff at least one async block transfer was actually issued.
bool copyLinearAttentionBlock(GenerationRequest& sequence, LlmRequest const& llmRequest);

void replaceSharedBlock(GenerationRequest& sequence, SizeType32 windowSize, SizeType32 blockIdx);

Expand Down Expand Up @@ -2217,7 +2260,12 @@ class KVCacheManager : public BaseKVCacheManager

//! \brief According to request's current position, copy data from the last full block to the next block (ignoring
//! the placeholder block). It should be called before every forward step, after adding new tokens.
void copyLinearAttentionBlock(LlmRequest const& llmRequest);
//! \return true iff at least one async block transfer was actually issued for this request. The caller can
//! aggregate this across requests and skip refreshBlocks() (which performs a stream sync) when no copies happened.
bool copyLinearAttentionBlock(LlmRequest const& llmRequest);

//! \brief Batch variant of copyLinearAttentionBlock. Returns true iff at least one copy was issued.
bool copyLinearAttentionBlockBatch(std::vector<std::shared_ptr<LlmRequest>> const& llmRequests);

void addSequenceBatch(
std::vector<std::tuple<LlmRequest::RequestIdType, SizeType32, SizeType32>> const& requestInfos,
Expand Down
2 changes: 1 addition & 1 deletion cpp/tensorrt_llm/batch_manager/capacityScheduler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ std::tuple<RequestVector, RequestVector> MaxUtilizationScheduler::operator()(
// Here we simulate freeing the kvCache blocks associated with that sequence
kvCacheManager.schedulingRemoveSequence((*lastStartedReqIt)->mRequestId);
pausedRequests.emplace_back(*lastStartedReqIt);
TLLM_LOG_DEBUG("MaxUtilizationScheduler: request ID %lu -> pause", (*lastStartedReqIt)->mRequestId);
TLLM_LOG_INFO("MaxUtilizationScheduler: request ID %lu -> pause", (*lastStartedReqIt)->mRequestId);
reqItEnd = std::next(lastStartedReqIt).base();
}
else
Expand Down
73 changes: 54 additions & 19 deletions cpp/tensorrt_llm/batch_manager/kvCacheManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2222,12 +2222,16 @@ void BlockManager::allocateBlock(GenerationRequest& sequence, SizeType32 windowS
mWindowBlockManagers.at(windowSize).allocateBlock(sequence, false);
}

void BlockManager::copyLinearAttentionBlock(GenerationRequest& sequence, LlmRequest const& llmRequest)
bool BlockManager::copyLinearAttentionBlock(GenerationRequest& sequence, LlmRequest const& llmRequest)
{
bool didCopy = false;
for (auto& [windowSize, manager] : mWindowBlockManagers)
{
manager.copyLinearAttentionBlock(sequence, llmRequest);
// Use a temp to avoid short-circuiting; every window must run.
bool const windowDidCopy = manager.copyLinearAttentionBlock(sequence, llmRequest);
didCopy = didCopy || windowDidCopy;
}
return didCopy;
}

bool WindowBlockManager::tryAllocatePlaceholderForLinearAttention(GenerationRequest& sequence, bool shareAmongBeams)
Expand Down Expand Up @@ -2363,11 +2367,11 @@ void WindowBlockManager::allocateBlock(GenerationRequest& sequence, bool shareAm
}
}

void WindowBlockManager::copyLinearAttentionBlock(GenerationRequest& sequence, LlmRequest const& request)
bool WindowBlockManager::copyLinearAttentionBlock(GenerationRequest& sequence, LlmRequest const& request)
{
if (!isRecurrentState())
{
return;
return false;
}

auto const requestId = request.mRequestId;
Expand All @@ -2376,7 +2380,7 @@ void WindowBlockManager::copyLinearAttentionBlock(GenerationRequest& sequence, L
if (mAllocatedBlocksPerSeq.find(requestId) == mAllocatedBlocksPerSeq.end())
{
TLLM_LOG_WARNING("%s::copyLinearAttentionBlock - Request %lu not found", mLogPrefix.c_str(), requestId);
return;
return false;
}

// It points to the next token to be processed/generated
Expand All @@ -2391,9 +2395,10 @@ void WindowBlockManager::copyLinearAttentionBlock(GenerationRequest& sequence, L
if (TLLM_LIKELY(sequence.getBeamWidth() == 1))
{
// the block of beam0 is inherited from context phase, no need to copy
return;
return false;
}
// copy beam 0 to other beams
// copy beam 0 to other beams: beamWidth >= 2 here, loop runs at least once
// with no skip path, so an onboard is always issued.
auto beam0Block = getBlockById(sequence.getCacheBlockIds(mWindowSize).at(0).back());
for (auto beamIdx = 1; beamIdx < sequence.getBeamWidth(); ++beamIdx)
{
Expand All @@ -2406,17 +2411,18 @@ void WindowBlockManager::copyLinearAttentionBlock(GenerationRequest& sequence, L
// transfer manager to copy the entire block.
sequence.getTransferMode(), sequence.getDirectory());
}
return;
return true;
}

// copy only happens in context phase or the corner case above
if (currentPosition % mTokensPerBlock != 0 || currentPosition > request.getPromptLen() || currentPosition == 0)
{
return;
return false;
}

auto prevBlockIndex = currentPosition / mTokensPerBlock - 1; // signed
std::set<std::pair<KVCacheBlock::IdType, KVCacheBlock::IdType>> onboardedBlocks;
bool didCopy = false;
for (auto beamIdx = 0; beamIdx < sequence.getBeamWidth(); ++beamIdx)
{
auto const& beamBlockIds = sequence.getCacheBlockIds(mWindowSize).at(beamIdx);
Expand Down Expand Up @@ -2455,7 +2461,9 @@ void WindowBlockManager::copyLinearAttentionBlock(GenerationRequest& sequence, L
// manager to copy the entire block.
sequence.getTransferMode(), sequence.getDirectory());
onboardedBlocks.insert({prevBlockId, nextBlockId});
didCopy = true;
}
return didCopy;
}

std::pair<SizeType32, std::vector<KVCacheBlock::IdType>> WindowBlockManager::storeBlocks(
Expand Down Expand Up @@ -3311,6 +3319,11 @@ SizeType32 KVCacheManager::getNeededBlocksOneStep(LlmRequest const& req, bool tw
= std::min((isCrossKv() ? req.getEncoderOutputLen() : req.mPromptLen) + maxDraftTokensToAdd,
windowSize + mChunkSize)
+ mSinkBubbleLength;
if (LinearAttentionMetadata::hasLinearCache(windowSize))
{
return mBlockManager.getLinearAttentionMetadata()->calcNumBlocksNeededForReq(
promptCacheLen, getTokensPerBlock(), mEnableBlockReuse);
}
auto const numSharedBlocks = promptCacheLen / getTokensPerBlock();
auto const numUnSharedTokens = promptCacheLen % getTokensPerBlock();
auto const numUnSharedBlocks
Expand Down Expand Up @@ -3355,6 +3368,12 @@ SizeType32 KVCacheManager::getNeededBlocksOneStep(LlmRequest const& req, bool tw
auto const maxTokensToAdd = std::min((twoStepsLookAhead ? 2 : 1) * tokensPerStep, maxTokensToAddToKVCache);
auto const numNextTokens = numCurrTokens + maxTokensToAdd;

if (LinearAttentionMetadata::hasLinearCache(windowSize))
{
return mBlockManager.getLinearAttentionMetadata()->calcNumAdditionalBlocksNeededForReq(
numCurrTokens, req.getPromptLen(), getTokensPerBlock(), mEnableBlockReuse);
}

if (numNextTokens > mBlockManager.getWindowSizeMetadata(windowSize).maxTokenNum)
{
return 0;
Expand Down Expand Up @@ -3389,7 +3408,8 @@ SizeType32 KVCacheManager::getRemainingBlocksToCompletion(
{
if (req.isGenerationInProgressState())
{
return 0; // no need to allocate blocks for recurrent states during generation
return mBlockManager.getLinearAttentionMetadata()->calcNumAdditionalBlocksNeededForReq(
req.getNumTokens(0), req.getPromptLen(), getTokensPerBlock(), mEnableBlockReuse);
}
else if (!req.isContextFinished())
{
Expand All @@ -3399,12 +3419,8 @@ SizeType32 KVCacheManager::getRemainingBlocksToCompletion(
{
return 0;
}
if (mEnableBlockReuse)
{
return req.getPromptLen() / mBlockManager.getLinearAttentionMetadata()->statesSnapshotInterval + 1
+ (mBlockManager.getLinearAttentionMetadata()->saveLastSnapshot ? 1 : 0);
}
return 1;
return mBlockManager.getLinearAttentionMetadata()->calcNumBlocksNeededForReq(
req.mPromptLen, getTokensPerBlock(), mEnableBlockReuse);
}
}

Expand Down Expand Up @@ -3548,10 +3564,29 @@ void KVCacheManager::addToken(RequestIdType requestId)
mBlockManager.adjustBlocksIfNeeded(sequence);
}

void KVCacheManager::copyLinearAttentionBlock(LlmRequest const& llmRequest)
bool KVCacheManager::copyLinearAttentionBlock(LlmRequest const& llmRequest)
{
if (!mEnableBlockReuse)
{
// When block reuse is disabled, we always use a single slot for a sequence and move it to
// the end of blocks list, so copying is not needed.
return false;
}
auto& sequence = getSequence(llmRequest.mRequestId);
mBlockManager.copyLinearAttentionBlock(sequence, llmRequest);
return mBlockManager.copyLinearAttentionBlock(sequence, llmRequest);
}

bool KVCacheManager::copyLinearAttentionBlockBatch(std::vector<std::shared_ptr<LlmRequest>> const& llmRequests)
{
bool copiedAny = false;
for (auto const& req : llmRequests)
{
if (copyLinearAttentionBlock(*req))
{
copiedAny = true;
}
}
return copiedAny;
}

void WindowBlockManager::detachFrontBlock(GenerationRequest& sequence)
Expand Down Expand Up @@ -3741,7 +3776,7 @@ void KVCacheManager::storeContextBlocks(LlmRequest const& llmRequest)
}
else
{
TLLM_LOG_WARNING("[kv cache manager] storeContextBlocks: Can not find sequence for request %lu", requestId);
// TLLM_LOG_WARNING("[kv cache manager] storeContextBlocks: Can not find sequence for request %lu", requestId);
}
}

Expand Down
40 changes: 28 additions & 12 deletions cpp/tensorrt_llm/batch_manager/kvCacheTransferManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,22 +114,38 @@ void KVCacheTransferManager::copyBlock(BlockPtr const& src, BlockPtr const& dst,
auto const& pool = pools[poolIdx];

// For layer-first layout pools, block data is non-contiguous across layers.
// Copy each layer's block data separately.
// Pool shape: {numLayers, numBlocks, kvFactor, blockSize}. For a fixed block
// index, per-layer slices are contiguous rows of (kvFactor * blockSize) elements,
// separated by a stride of numBlocks rows between layers. Issue this as a single
// pitched cudaMemcpy2DAsync instead of one cudaMemcpyAsync per layer.
if (pool.layerFirstLayout)
{
auto srcPool = src->isPrimary() ? pool.primaryPtr : pool.secondaryPtr;
auto dstPool = dst->isPrimary() ? pool.primaryPtr : pool.secondaryPtr;
auto const srcBlockIdx = static_cast<tr::ITensor::DimType64>(src->getMemoryPoolBlockIndex());
auto const dstBlockIdx = static_cast<tr::ITensor::DimType64>(dst->getMemoryPoolBlockIndex());

for (SizeType32 layerIdx = 0; layerIdx < pool.numLayers; ++layerIdx)
{
// pool shape: {numLayers, numBlocks, kvFactor, blockSize}
// slice at {layerIdx, blockIdx} gives {1, kvFactor, blockSize}
auto srcBlock = tr::ITensor::slice(srcPool, {layerIdx, srcBlockIdx}, 1);
auto dstBlock = tr::ITensor::slice(dstPool, {layerIdx, dstBlockIdx}, 1);
(isOffload ? mOffloadManager : mOnboardManager).copy(*srcBlock, *dstBlock);
}
auto const srcBlockIdx = static_cast<size_t>(src->getMemoryPoolBlockIndex());
auto const dstBlockIdx = static_cast<size_t>(dst->getMemoryPoolBlockIndex());

// Compute pitches from each pool independently: primary and secondary pools
// may have different block counts (mNumPrimaryBlocks vs mNumSecondaryBlocks),
// so their per-layer strides differ. Using the primary shape for both pitches
// would corrupt host-offloaded recurrent state on CPU<->GPU transfers.
auto const& srcShape = srcPool->getShape();
auto const& dstShape = dstPool->getShape();
TLLM_CHECK_WITH_INFO(srcShape.nbDims >= 2,
"Expected layer-first KVCache pool to have at least 2 dims, got %d", srcShape.nbDims);
TLLM_CHECK_WITH_INFO(dstShape.nbDims >= 2,
"Expected layer-first KVCache pool to have at least 2 dims, got %d", dstShape.nbDims);
auto const srcLayerStrideBytes = srcPool->getSizeInBytes() / static_cast<size_t>(pool.numLayers);
auto const dstLayerStrideBytes = dstPool->getSizeInBytes() / static_cast<size_t>(pool.numLayers);
// rowBytes is the per-block per-layer payload — identical for primary and secondary.
auto const rowBytes = srcLayerStrideBytes / static_cast<size_t>(srcShape.d[1]);

auto* srcBase = static_cast<char*>(srcPool->data()) + srcBlockIdx * rowBytes;
auto* dstBase = static_cast<char*>(dstPool->data()) + dstBlockIdx * rowBytes;

auto stream = (isOffload ? mOffloadManager : mOnboardManager).getStream().get();
TLLM_CUDA_CHECK(cudaMemcpy2DAsync(dstBase, dstLayerStrideBytes, srcBase, srcLayerStrideBytes, rowBytes,
static_cast<size_t>(pool.numLayers), cudaMemcpyDefault, stream));
continue;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -641,7 +641,9 @@ void tb::kv_cache_manager::KVCacheManagerBindings::initBindings(nb::module_& m)
.def_prop_ro(
"is_variable_window", [](tbk::KVCacheManager& self) { return self.getBlockManager().isVariableWindow(); })
.def("copy_linear_attention_block", &tbk::KVCacheManager::copyLinearAttentionBlock, nb::arg("llm_request"),
nb::call_guard<nb::gil_scoped_release>());
nb::call_guard<nb::gil_scoped_release>())
.def("copy_linear_attention_block_batch", &tbk::KVCacheManager::copyLinearAttentionBlockBatch,
nb::arg("llm_requests"), nb::call_guard<nb::gil_scoped_release>());
}

void tb::BasePeftCacheManagerBindings::initBindings(nb::module_& m)
Expand Down
21 changes: 15 additions & 6 deletions tensorrt_llm/_torch/auto_deploy/models/quant_config_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
from abc import ABC, abstractmethod
from typing import Any, Callable, Dict, Optional, Tuple, Type

from tensorrt_llm.quantization.modelopt_config import (
is_modelopt_quant_config,
read_modelopt_quant_config,
)

from ..utils.logger import ad_logger


Expand Down Expand Up @@ -110,12 +115,16 @@ class ModelOPTQuantConfigReader(QuantConfigReader):
DEFAULT_KV_CACHE_DTYPE = "fp8"

def read_config(self, config: Dict) -> Dict:
producer = config.get("producer", {}).get("name")
# sanity check
if producer != "modelopt":
raise ValueError(f"Expected producer 'modelopt', got '{producer}'")

quant_config = config.get("quantization", {})
# Accept either modelopt shape: legacy (producer.name == "modelopt"
# with a "quantization" wrapper) or flat (quant_method == "modelopt").
if not is_modelopt_quant_config(config):
raise ValueError(
"Expected a modelopt quant config "
f"(producer={config.get('producer')}, "
f"quant_method={config.get('quant_method')})"
)
# Downstream auto-deploy transforms operate on the legacy field names.
quant_config = read_modelopt_quant_config(config)

quant_algo = quant_config.get("quant_algo", "").upper()

Expand Down
Loading
Loading