Skip to content

[None][feat] Indexer topk opt - #13811

Merged
lfr-0531 merged 1 commit into
NVIDIA:feat/deepseek_v4from
pcastonguay:indexer_topk_opt
May 8, 2026
Merged

[None][feat] Indexer topk opt#13811
lfr-0531 merged 1 commit into
NVIDIA:feat/deepseek_v4from
pcastonguay:indexer_topk_opt

Conversation

@pcastonguay

@pcastonguay pcastonguay commented May 6, 2026

Copy link
Copy Markdown
Collaborator

@coderabbitai summary

Description

GSM8K accuracy (1319 samples, 5-shot, greedy)

Metric baseline opt Δ
exact_match,strict-match 0.9545 ± 0.0057 0.9553 ± 0.0057 +0.0008
exact_match,flexible-extract 0.9538 ± 0.0058 0.9545 ± 0.0057 +0.0008
eval time (s) 207.3 209.2 +1.9

Within stderr — accuracy unchanged.

Serving performance (concurrency 256, ISL 8192 / OSL 1024, gen_only)

Dataset: random-v2/deepseek-v4-flash-8192-1024-20000-ratio-08_for_serve.json. 1× ctx server + 1× gen server, max-concurrency 256, 1 round, non-streaming.

Metric baseline opt Δ
request throughput (req/s) 2.169 2.280 +5.13 %
output throughput (tok/s) 2024.4 2128.2 +5.13 %
total token throughput (tok/s) 18112 19041 +5.13 %
mean TTFT (ms) 84338.8 78120.9 −7.37 %
median TTFT (ms) 84337.5 78119.1 −7.37 %
p99 TTFT (ms) 84379.4 78150.7 −7.38 %
mean TPOT (ms) 33.29 33.72 +1.29 %
median TPOT (ms) 33.34 33.77 +1.29 %
p99 TPOT (ms) 33.40 33.80 +1.21 %
mean ITL (ms) 3150.9 3191.7 +1.29 %
median ITL (ms) 3457.7 3496.3 +1.12 %
mean E2EL (ms) 115368 109551 −5.04 %
duration (s) 118.0 112.3 −4.88 %

+5.1 % throughput, −7.4 % TTFT, −5.0 % E2EL. TPOT regresses ~1.3 %.

Before / After comparison — indexer top-K decode optimization

Target kernel: topKPerRowDecode

Metric Before After Delta
Mean per-call duration 123.12 us 10.48 us 11.7× faster
Min / Max per-call 122.78 / 124.61 us 2.24 / 19.17 us much wider range (split path)
Total kernel time 36.94 ms 6.29 ms −83.0%
Invocations 300 600 2× (split + merge passes)
Share of total GPU time 9.86% 1.92% −7.94 pp

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.

@Tabrizian
Tabrizian requested review from ChristinaZ and yweng0828 May 6, 2026 15:27
@pcastonguay
pcastonguay marked this pull request as draft May 6, 2026 18:12
@pcastonguay
pcastonguay marked this pull request as ready for review May 7, 2026 00:41
@lfr-0531

lfr-0531 commented May 7, 2026

Copy link
Copy Markdown
Collaborator

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #47097 [ run ] triggered by Bot. Commit: bf7f808 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #47097 [ run ] completed with state FAILURE. Commit: bf7f808

Link to invocation

@lfr-0531

lfr-0531 commented May 7, 2026

Copy link
Copy Markdown
Collaborator

/bot run --disable-fail-fast

@longcheng-nv longcheng-nv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

cc @pcastonguay

Before zooming into the diff, I'd like to align on the broader trajectory of this kernel.

Strategic context

The TRT-LLM top-K kernel is migrating from single-CTA → multi-CTA. The strategic implementation of that step already landed on main as #12354single-pass multi-CTA cluster top-K in CuTe DSL, which uses cluster barriers + DSMEM to do the cross-CTA histogram merge in one pass and reuses the exact same radix micro-kernel that topKPerRowDecode uses internally.

This PR introduces a parallel-but-different multi-CTA strategy on the CUDA C++ side: a "split + global aux + merge" pattern with two kernel launches (the pre-cluster idiom). Because both implementations share the same inner micro-kernel, the gap between them is purely in the launch / scheduling / merge layer, which makes a head-to-head benchmark cleanly attributable.

Asks before merging

  1. Head-to-head against CuteDSLTopKDecodeSinglePassMultiCTAClusterRunner (#12354) on B200.
    For the (BS × seq_len × cr) grid in (2). Since the micro-kernel is identical, this isolates "double-launch + global-aux merge" vs "cluster + DSMEM in-kernel". If #12354 wins on Blackwell, please reposition this PR's hardware scope (e.g., sm_90 / pre-cluster only) and state it explicitly in the description.

  2. Drive the dispatch constants from a systematic sweep, not from a single workload.
    kDecodeMinColsPerSubBlock=2048, kDecodeTargetTotalBlocks=132, and kMaxBlocksPerRowDecode=10 are currently picked ahead of time. Multi-CTA scheduling is tightly coupled with occupancy and L2 footprint; constants tuned from one (concurrency=256, ISL=8192, OSL=1024) trajectory will not generalize. Suggested grid:

    • BS ∈ {1, 2, 4, 8, 16, 32, 64, 128, 132, 256} — covers the smTarget=1 transition at 132
    • seq_len ∈ {4K, 8K, 16K, 32K, 64K, 128K, 256K} × cr ∈ {1, 4} — crosses both 12288 and 200000 boundaries
    • H100 and B200

    Report µs/call (before/after) as a heatmap + per-BS curves; pick the constants from the resulting envelope. Also: kDecodeTargetTotalBlocks=132 is hard-coded for H100; B200 is 148 SM. Consider runtime cudaDeviceGetAttribute(cudaDevAttrMultiProcessorCount, ...).

  3. Isolate baseline / bug-fix / perf in a 3-way table.
    The current "after" column bundles the bug fix in commit bf7f808111 together with the perf change. Please report:

    Variant GSM8K kernel µs/call serving throughput
    baseline
    baseline + commit bf7f808111 (bug fix only)
    baseline + all 4 commits

    Without this, the +5.13% throughput claim is partially attributable to the correctness fix.

  4. End-to-end coverage at lower concurrency.
    Commit messages 1–2 motivate the change with "BS=32", but the only serving run is at concurrency=256, where decoder steady-state BS is much larger than 32 and the new heuristic does not engage on the hot decode path. Please add a run at concurrency ∈ {32, 64} so the regime the PR targets is actually exercised end-to-end.

  5. Split commit bf7f808111 into a standalone [None][fix] PR to main.
    The two fixes (processHistogramStep<step==1> reset, finalIndices = -1 sentinel pairing) are pre-existing bugs in the multi-block merge path. They affect main too — large BS / numColumns ≥ 200000 already routes to the same multi-block radix and inherits both. Landing the fix on main should not depend on the DSv4 → main merge timeline.

Follow-ups for the GVR top-K side (not gating this PR)

  • GVR top-K is still a single-CTA implementation today. Once it evolves to a multi-CTA variant, the dispatch boundary between GVR and the multi-CTA radix path introduced here will need to be re-derived: rerun the same (BS × seq_len × cr) sweep with both kernels in their multi-CTA form, and pick the GVR ↔ radix crossover from the head-to-head envelope. Until that point, current single-CTA-GVR vs multi-CTA-radix numbers should be treated as an interim baseline only — the dispatch position cannot be considered final.
  • IndexerTopKOp.cpp now sizes aux buffers dynamically as {N, blocksPerRow, K} (range 1–10) instead of fixed {N, 10, K}. Please confirm CUDA Graph capture/replay on the GVR warm-up path is unaffected (no warmup-vs-replay shape diff).

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #47109 [ run ] triggered by Bot. Commit: bf7f808 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #47109 [ run ] completed with state SUCCESS. Commit: bf7f808
/LLM/main/L0_MergeRequest_PR pipeline #37078 completed with status: 'SUCCESS'

CI Report

Link to invocation

@lfr-0531
lfr-0531 force-pushed the feat/deepseek_v4 branch from 67b0b17 to 9c57516 Compare May 7, 2026 16:30
@lfr-0531
lfr-0531 requested review from a team as code owners May 7, 2026 16:30
@lfr-0531
lfr-0531 requested review from QiJune, achartier, arysef, greg-kwasniewski1, mlefeb01, niukuo and zheyuf and removed request for a team May 7, 2026 16:30
The decode top-K kernel previously launched gridDim=(numRows,1,1) for
numColumns < 200K, leaving most SMs idle when numRows is small (e.g.
DeepSeek-V4 decode with batch=32 hits ~24% SM utilization on H100/B200).
The split-and-merge multipleBlocksPerRow path was only triggered for
numColumns >= 200K.

Introduce computeIndexerTopKDecodeBlocksPerRow(numRows, numColumns) and
use it in both invokeIndexerTopKDecode and the IndexerTopKOp aux-buffer
allocation. The heuristic picks the smallest split that still saturates
SMs and keeps each sub-block large enough for the radix passes to be
efficient. It falls back to the original single-block path when numRows
is large enough to fill SMs and to the original 10-block path at >= 200K
columns, so existing behavior is unchanged at both ends.

Coverage extends down into the small-numColumns range: tiny rows
(numCols < 2048) still run as a single block, but moderate column
counts that previously short-circuited to blocksPerRow=1 now split
into 2-5 blocks per row to fill the GPU.

Two pre-existing bugs in the topKPerRowDecode<..., mergeBlocks=true>
merge step, only observable once the multi-block path is exercised at
smaller numColumns where rowLen <= topK seeds inputs with -FLT_MAX
padding, are also fixed:

1. processHistogramStep: when step 0 (fp16 fast path) cannot resolve
   top-K because the threshold bin exceeds kNumFinalItems, the fp32
   radix in steps 1-3 restarts from a full histogram. Reset
   smemFoundTopKValues and smemFinalDstIdx at the entry of step 1 so
   we don't double-count the candidates step 0 already wrote into
   smemOutput. Without this, valid entries that fall below the
   threshold in both fp16 and fp32 binnings were emitted twice,
   producing 2x duplicated indices.

2. topKPerRowJob radix-sort fall-through: pair finalIndices[ii] = -1
   with finalLogits[ii] = -FLT_MAX in the per-thread initialization
   so unused sort slots survive SortDescendingBlockedToStriped as -1
   rather than uninitialized register values. The bug surfaced when
   the threshold bin was dominated by -FLT_MAX padding (all sort items
   tied), causing the trailing top-K slots to receive garbage indices.

Adds test_indexer_topk_decode_sm_saturation that pins batch_size in
{16, 32, 128} and num_tokens up to 32K to exercise the multi-block
split path across the maxByCols guard, the moderate numColumns range,
and higher column counts.

Signed-off-by: Patrice Castonguay <55748270+pcastonguay@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants