Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
55 changes: 47 additions & 8 deletions benchmarks/bench_trtllm_gen_mla.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def bench_trtllm_mla(
dtype,
backend="auto",
with_sinks=False,
seq_lens_list=None,
):
"""Benchmark a single (config, backend, sinks?) cell.

Expand All @@ -38,16 +39,20 @@ def bench_trtllm_mla(
device=device,
).to(dtype)

num_tokens = seq_len * batch_size
num_blocks = (num_tokens + page_size - 1) // page_size
if seq_lens_list is not None:
assert len(seq_lens_list) == batch_size, (
f"seq_lens_list length {len(seq_lens_list)} != batch_size {batch_size}"
)
seq_lens = list(seq_lens_list)
max_seq_len = max(seq_lens)
else:
seq_lens = [torch.randint(1, seq_len, (1,)).item() for _ in range(batch_size)]
seq_lens[-1] = seq_len
max_seq_len = max(seq_lens)
Comment thread
coderabbitai[bot] marked this conversation as resolved.

# Sequence lengths and block tables
seq_lens = [torch.randint(1, seq_len, (1,)).item() for _ in range(batch_size)]
seq_lens[-1] = seq_len
max_seq_len = max(seq_lens)
seq_lens_tensor = torch.tensor(seq_lens, dtype=torch.int, device=device)

blocks_per_seq = (seq_lens_tensor + page_size - 1) // page_size
num_blocks = int(blocks_per_seq.sum().item())
max_num_blocks_per_seq = blocks_per_seq.max().item()

# Generate random but unique block IDs for all sequences
Expand Down Expand Up @@ -80,7 +85,7 @@ def bench_trtllm_mla(

# Allocate workspace buffer
# todo(Yingyi): calculate the actual size of workspace buffer
workspace_buffer = torch.empty(128 * 1024 * 1024, dtype=torch.int8, device=device)
workspace_buffer = torch.empty(1024 * 1024 * 1024, dtype=torch.int8, device=device)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Zero-initialize decode workspace before first kernel use.

Line 88 allocates workspace_buffer with torch.empty(...), but this path requires zeroed workspace for the counter region; uninitialized bytes can make results/timings flaky.

Proposed fix
-    workspace_buffer = torch.empty(1024 * 1024 * 1024, dtype=torch.int8, device=device)
+    workspace_buffer = torch.zeros(1024 * 1024 * 1024, dtype=torch.int8, device=device)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@benchmarks/bench_trtllm_gen_mla.py` at line 88, The workspace_buffer is
allocated with torch.empty which leaves uninitialized bytes and can corrupt the
decode counter region; replace the allocation with a zero-initialized buffer or
explicitly zero it after creation (e.g., use torch.zeros with same shape, dtype
and device or call workspace_buffer.zero_()) so the counter region is
deterministic before first kernel use; refer to workspace_buffer in the
allocation site to apply this change.


sinks = (
torch.randn(num_q_heads, dtype=torch.float32, device=device)
Expand Down Expand Up @@ -155,6 +160,16 @@ def bench_trtllm_mla(
print(f"FLOPs: {flops / ms / 1e9:.2f} TFLOPs/s")


def sample_prod_distribution(batch_size: int, seed: int = 42) -> list[int]:
"""Sample sequence lengths from a production-like distribution."""
import random

rng = random.Random(seed)
seq_lens = [4096, 8192, 16384, 32768, 65536, 131072]
weights = [35, 20, 15, 12, 10, 8]
return rng.choices(seq_lens, weights=weights, k=batch_size)


if __name__ == "__main__":
import argparse

Expand Down Expand Up @@ -238,3 +253,27 @@ def bench_trtllm_mla(
f"{type(e).__name__}: {e}"
)
print()

# Mixed sequence length benchmark (production-like distribution)
for batch_size in [16, 32, 64, 128, 256]:
sl = sample_prod_distribution(batch_size, seed=batch_size)
try:
bench_trtllm_mla(
batch_size,
1,
max(sl),
32,
torch.bfloat16,
backend=args.backend,
seq_lens_list=sl,
)
except ValueError as e:
print(f"SKIPPED: {e}")
print()
except Exception as e:
print(
f"ERROR: batch_size={batch_size}, q_len=1, "
f"seq_len=mixed, page_size=32, dtype=torch.bfloat16, "
f"backend={args.backend}: {type(e).__name__}: {e}"
)
print()
2 changes: 1 addition & 1 deletion csrc/fmhaReduction.cu
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ void runFmhaReduction(TllmGenFmhaKernelMetaInfo const& kernelMeta, KernelParams
int32_t const maxNumCtasForReduction{(multiProcessorCount * 2) /
static_cast<int32_t>(gridDim.x * gridDim.y * gridDim.z)};
// The number of Ctas for the reduction work.
int32_t const numCtasForReduction{std::min(maxNumCtasForReduction, numSlices)};
int32_t const numCtasForReduction{std::max(1, std::min(maxNumCtasForReduction, numSlices))};
// Launch more CTAs to split the reduction work if needed.
gridDim.x *= numCtasForReduction;

Expand Down
30 changes: 22 additions & 8 deletions include/flashinfer/trtllm/fmha/fmhaKernels.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,7 @@ class TllmGenFmhaKernel {
}

// Prepare the kernel parameters.
auto kernelParams = KernelParams::setKernelParams(
params, kernelMeta, ctaLaunchParams.mMaxNumCtasQ, ctaLaunchParams.mMaxNumCtasKv);
auto kernelParams = KernelParams::setKernelParams(params, kernelMeta, ctaLaunchParams);

// Override SageAttention parameters.
auto sageParamEncode = [](int blockSize) -> int32_t {
Expand Down Expand Up @@ -318,8 +317,7 @@ class TllmGenFmhaKernel {
"fallback to GmemReduction.");
// Rebuild kernelParams: setKernelParams uses kernelMeta (TMA descriptors, tile shapes)
// which changed when switching from CgaSmemReduction to GmemReduction kernel.
kernelParams = KernelParams::setKernelParams(
params, kernelMeta, ctaLaunchParams.mMaxNumCtasQ, ctaLaunchParams.mMaxNumCtasKv);
kernelParams = KernelParams::setKernelParams(params, kernelMeta, ctaLaunchParams);
buildLaunchConfig(launch_config, launch_attribute, kernelMeta, ctaLaunchParams, params);
setNonPortableClusterIfNeeded(func, ctaLaunchParams);
}
Expand Down Expand Up @@ -492,10 +490,26 @@ class TllmGenFmhaKernel {
// benefits of a shorter mainloop.
int const maxNumCtasPerSeqKv =
(maxAttentionWindow + 2 * kernelMeta.mStepKv - 1) / (2 * kernelMeta.mStepKv);
// Compute numCtasPerSeqKv.
numCtasPerSeqKv = std::min(
maxNumCtasPerSeqKv,
std::max(1, int32_t(params.mMultiProcessorCount / (numCtasX * numCtasY * numCtasZ))));

int const baseCtas = numCtasX * numCtasY * numCtasZ;
numCtasPerSeqKv = std::min(maxNumCtasPerSeqKv,
std::max(1, int32_t(params.mMultiProcessorCount / baseCtas)));

// When the longest sequence needs more KV splits than the standard
// heuristic provides, oversubscribe the SMs. This helps mixed-length batches
// where long sequences get insufficient KV parallelism.
int constexpr kMinTokensPerCta = 2048;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The three constant values are too specific and might impact other cases. I would make this as an optional and user-provided options (using ENVs or something).
or I am thinking that we can have a callback function or something so that the heuristic function can be passed.
@bkryu might have better suggestions.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added ENVs and disabled by default.

int constexpr kMaxOccupancyWaves = 16;
int constexpr kMaxSplits = 32;
int const desiredSplits = (params.mMaxSeqLenKv + kMinTokensPerCta - 1) / kMinTokensPerCta;
if (numCtasPerSeqKv < desiredSplits && desiredSplits > 4) {
Comment thread
PatrykSaffer marked this conversation as resolved.
Outdated
int const maxSplitsFromSMs =
std::max(1, int32_t(kMaxOccupancyWaves * params.mMultiProcessorCount / baseCtas));
numCtasPerSeqKv =
std::max(numCtasPerSeqKv,
std::min({desiredSplits, maxSplitsFromSMs, maxNumCtasPerSeqKv, kMaxSplits}));
}

// Update the numCtasX.
numCtasX *= numCtasPerSeqKv;
// The current total number of CTAs.
Expand Down
14 changes: 9 additions & 5 deletions include/flashinfer/trtllm/fmha/kernelParams.h
Original file line number Diff line number Diff line change
Expand Up @@ -647,9 +647,9 @@ struct KernelParams {
}

// Setup the kernel parameters.
template <class FmhaOptions_, class KernelMeta>
template <class FmhaOptions_, class KernelMeta, class CtaLaunchParams>
static KernelParams setKernelParams(FmhaOptions_ const& options, KernelMeta const& kernelMeta,
int32_t maxNumCtasQ, int32_t maxNumCtasKv) {
CtaLaunchParams const& ctaLaunchParams) {
// Create the return struct.
KernelParams params;
// Memset the kernel parameters to 0.
Expand Down Expand Up @@ -841,7 +841,11 @@ struct KernelParams {
params.ptrAttentionSinks = options.ptrAttentionSinks;

// The partial buffers' pointers when the multiCtasKv mode is enabled.
int64_t partialStatsBufferSize = options.mMultiProcessorCount * kernelMeta.mStepQ;
// partialStats and partialO are packed sequentially in the workspace. Each
// CTA writes to a unique slot, so the offset must accommodate all CTAs.
int64_t partialStatsBufferSize = static_cast<int64_t>(ctaLaunchParams.mNumCtasX) *
ctaLaunchParams.mNumCtasY * ctaLaunchParams.mNumCtasZ *
kernelMeta.mStepQ;
params.ptrMultiCtasKvCounter = options.multiCtasKvCounterPtr;
params.ptrPartialStats = reinterpret_cast<float2*>(options.multiCtasKvScratchPtr);
params.ptrPartialO = params.ptrPartialStats + partialStatsBufferSize;
Expand Down Expand Up @@ -876,8 +880,8 @@ struct KernelParams {

params.mMaxSeqLenQ = options.mMaxSeqLenQ;
params.mMaxSeqLenKv = options.mMaxSeqLenKv;
params.mMaxNumCtasQ = maxNumCtasQ;
params.mMaxNumCtasKv = maxNumCtasKv;
params.mMaxNumCtasQ = ctaLaunchParams.mMaxNumCtasQ;
params.mMaxNumCtasKv = ctaLaunchParams.mMaxNumCtasKv;
params.mMaxNumPagesPerSeqKv = options.mMaxNumPagesPerSeqKv;
// TODO: just use mMaxSeqLenQ for number of MTP tokens.
params.mSumOfSeqLensQ = options.mSumOfSeqLensQ;
Expand Down
Loading