Skip to content

Commit 687af83

Browse files
committed
[None][fix] Address code review: API safety, assertion, and comment accuracy
- Replace getFragmentPointerDevice()/getFragmentPointerHost() with a single getFragmentPointerSlot() that returns both rows atomically and advances workIdx once, making it impossible to call them in the wrong order (gripe 2). - Add TLLM_CHECK that cacheBlockIds is non-empty on the cross-KV path before accessing cacheBlockIds[0] (gripe 5). - Fix the Fix-3 description in encDecBeamSearchTest.cpp: this branch uses a repaired kernel (per-slot fragmentPointerDevice), not the direct-copy path described by the stale comment (gripe 3). Signed-off-by: Aurelien Chartier <2567591+achartier@users.noreply.github.com>
1 parent 8204325 commit 687af83

4 files changed

Lines changed: 19 additions & 22 deletions

File tree

cpp/include/tensorrt_llm/batch_manager/runtimeBuffers.h

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -224,17 +224,14 @@ class RuntimeBuffers
224224
workIdx = (workIdx + 1) % (fragmentPointerHost->getShape().d[0]);
225225
}
226226

227-
[[nodiscard]] TensorPtr getFragmentPointerHost()
227+
//! Returns matching host and device pointer rows for the current workIdx, then advances
228+
//! workIdx. Always call this instead of the individual getters to avoid ordering bugs.
229+
[[nodiscard]] std::pair<TensorPtr, TensorPtr> getFragmentPointerSlot()
228230
{
229-
TensorPtr slice = runtime::ITensor::slice(fragmentPointerHost, workIdx, 1);
231+
TensorPtr host = runtime::ITensor::slice(fragmentPointerHost, workIdx, 1);
232+
TensorPtr device = runtime::ITensor::slice(fragmentPointerDevice, workIdx, 1);
230233
cycleWorkIdx();
231-
return slice;
232-
}
233-
234-
//! Returns the device pointer row for the current workIdx (called before cycleWorkIdx).
235-
[[nodiscard]] TensorPtr getFragmentPointerDevice()
236-
{
237-
return runtime::ITensor::slice(fragmentPointerDevice, workIdx, 1);
234+
return {std::move(host), std::move(device)};
238235
};
239236
};
240237

cpp/tensorrt_llm/batch_manager/kvCacheManager.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3948,6 +3948,8 @@ SizeType32 KVCacheManager::copyBlockOffsets(ITensor& output, SizeType32 outputSl
39483948
// For cross-KV (encoder features), all beams of a request share the same encoder output.
39493949
// Always use beam 0's block IDs so all beams attend to the correct encoder features.
39503950
auto const srcBeamIdx = isCrossKv() ? 0 : beamIdx;
3951+
TLLM_CHECK_WITH_INFO(!isCrossKv() || !cacheBlockIds.empty(),
3952+
"Cross-KV sequence has no block IDs for request %lu", requestId);
39513953
auto const effectiveBlockCount = isCrossKv() ? cacheBlockIds[0].size() : cacheBlockIds[beamIdx].size();
39523954
auto const copyChunkSize = effectiveBlockCount * sizeof(tk::KVCacheIndex);
39533955
for (auto xIdx : {kIdx, vIdx})

cpp/tensorrt_llm/batch_manager/utils/inflightBatchingUtils.cpp

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,11 @@ void copyGenerationLogits(RuntimeBuffers::GenerationLogitsCache& generationLogit
103103

104104
auto const fragmentSize = llmReq.getGenerationLogitsFragmentsSize();
105105

106-
// Merge logits fragments on device.
107-
// getFragmentPointerDevice() reads the current workIdx row; getFragmentPointerHost() reads the
108-
// same row then advances workIdx. Calling device-slot first preserves the pairing so both
109-
// host and device pointer arrays use the same per-batch-slot row, preventing concurrent
110-
// flushes for different requests from clobbering each other's pointer arrays.
106+
// Merge logits fragments on device. getFragmentPointerSlot() returns the matching host and
107+
// device rows for the current workIdx and advances the index atomically, so concurrent flushes
108+
// for different requests in the same batch never clobber each other's pointer arrays.
111109
auto const& transposeBufferPtr = generationLogitsCache.transposedLogits;
112-
auto const cachePointerDevice = generationLogitsCache.getFragmentPointerDevice();
113-
auto const cachePointerHost = generationLogitsCache.getFragmentPointerHost();
110+
auto [cachePointerHost, cachePointerDevice] = generationLogitsCache.getFragmentPointerSlot();
114111
tensorrt_llm::runtime::kernels::mergeLogitsFragments(bufferManager, *transposeBufferPtr,
115112
llmReq.getGenerationLogitsFragments(), *cachePointerDevice, *cachePointerHost, 0, 1, reqBeamWidth,
116113
bufferManager.getStream(), 0);

cpp/tests/unit_tests/batch_manager/encDecBeamSearchTest.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,13 @@
2525
// beam when isCrossKv() is true, since the encoder output is identical for
2626
// all beams of a request.
2727
//
28-
// Fix 3 — copyGenerationLogits direct-copy places each (beam, step) logit at
29-
// the correct host offset.
30-
// Before: the mergeLogitsFragmentsKernel pointer-indirection caused
31-
// intermittent GPU-state corruption when gather_generation_logits=True was
32-
// combined with concurrent mixed beam-width requests.
33-
// After: each fragment is copied directly to the host without the kernel.
28+
// Fix 3 — fragmentPointerDevice is now per-batch-slot ([maxBatchSize, kCACHE_LENGTH]).
29+
// Before: fragmentPointerDevice was a single shared row ([kCACHE_LENGTH]); sequential
30+
// flushes from different requests in the same batch clobbered each other's GPU pointer
31+
// arrays, causing the mergeLogitsFragmentsKernel to read stale fragment addresses and
32+
// produce degenerate output with gather_generation_logits=True.
33+
// After: each request gets its own device-side pointer row via getFragmentPointerSlot(),
34+
// eliminating cross-request interference.
3435

3536
#include "tensorrt_llm/batch_manager/kvCacheManager.h"
3637
#include "tensorrt_llm/batch_manager/llmRequest.h"

0 commit comments

Comments
 (0)