Skip to content

[None][perf] Block-skip fast path for self-sampling GVR top-K decode (stacked on #17821) - #18122

Draft
longcheng-nv wants to merge 42 commits into
NVIDIA:mainfrom
longcheng-nv:feat/gvr-selfsampling-topk-blockskip
Draft

[None][perf] Block-skip fast path for self-sampling GVR top-K decode (stacked on #17821)#18122
longcheng-nv wants to merge 42 commits into
NVIDIA:mainfrom
longcheng-nv:feat/gvr-selfsampling-topk-blockskip

Conversation

@longcheng-nv

@longcheng-nv longcheng-nv commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Description

Stacked on #17821 — this branch is the #17821 head plus one commit; the review scope is the top commit only. Kept as draft until #17821 merges.

Adds an opt-in block-skip row pass to the self-sampling GVR top-K decode kernel (gvr_main family):

  • The row pass consumes a per-32-element block-maxima side tensor (bmax), builds a compact list of the blocks whose maximum can clear the sampled threshold, and walks only those (warp-per-block, eight blocks in flight, ballot-aggregated emission, one atomic per round). If the survivor list overflows its fixed capacity, the pass falls back to an identity walk over the row window.
  • Emission, verify, and the retry ladder are shared with the baseline pass, so the exactness contract is unchanged: exact (tie-interchangeable) top-K of logits[:, :n] per row.
  • Dispatch is the shape-only bsk_gate(b, n, npad, k) = (b * npad * 4 >= 128 MiB) and (n >= 72 * k) — no data-dependent terms. Outside the gate, run_bsk routes to the baseline run.
  • The production route() is untouched. In deployment the bmax tensor is produced by the indexer-GEMM emission epilogue (the [None][perf] Emission-assisted GVR top-K decode for the DeepSeek V4 indexer #16953 producer model); this PR carries the consumer side as an explicit run_bsk entry so the kernel capability can be reviewed and validated independently of the producer wiring.

Performance

Real-capture grid, 886 cells x 11 batch sizes (9,746 combos; DSV3.2 + DSV4 Flash/Pro decode captures), nsys pure-kernel medians, cold-L2, same-GPU paired A/B against the #17821 head:

  • gate-open (582 pairs): gm 1.733, worst 0.942, best 2.67; 99.8% of pairs > 1.0x, 75% >= 1.5x, 26% >= 2x
  • the single sub-1.0 pair (v32_256k_L00, bs=512) is an isolated anomaly — the next-worst pair is 1.048
  • gated shapes ship as the baseline (fallback band re-measured neutral, 0.96–1.05)

Gate-open speedup vs #17821, split one axis at a time:

by model pairs gm worst
DSV3.2 (v32) 183 1.908 0.942
DSV4 Flash 189 1.766 1.050
DSV4 Pro 210 1.566 1.048
by capture ISL pairs gm worst
256k 225 1.764 0.942
512k 153 1.524 1.048
1024k 204 1.871 1.190
by batch size pairs gm worst
128 51 1.706 1.190
256 163 1.867 1.071
512 184 1.658 0.942
1024 184 1.703 1.000

Combined model_isl x BS (gm/worst; "—" = gated or routed to a non-main family):

model_isl bs 128 bs 256 bs 512 bs 1024
flash_1024k 2.00/1.70 2.23/1.81 2.06/1.63 2.06/1.67
flash_512k 1.82/1.25 1.79/1.12 1.73/1.16
flash_256k 1.23/1.05 1.27/1.11
pro_1024k 1.53/1.19 1.90/1.63 1.73/1.47 1.81/1.55
pro_512k 1.41/1.13 1.33/1.05 1.36/1.12
v32_256k 2.02/1.07 1.81/0.94 1.90/1.00

Note: the commit message quotes an earlier 531-pair tally whose gate reconstruction used the unpadded row length in the working-set term; the kernel gate uses the padded row stride (64-element multiples), which additionally opens the long-ISL bs=128 column (all wins). The 582-pair numbers here are authoritative.

Test Coverage

tests/unittest/_torch/thop/parallel/test_gvr_selfsampling_topk_blockskip.py:

  • gate-open exactness vs both the baseline kernel and torch.topk (hostile +3e38 pads);
  • gate-fallback exactness and shape-only gate boundary semantics (128 MiB and n = 72k edges);
  • adversarial capacity overflow (a tie spike in every block) forcing the identity-walk fallback.

The existing test_gvr_selfsampling_topk.py suite (84 cases) passes unchanged on this branch.

PR Checklist

🤖 Generated with Claude Code

longcheng-nv and others added 30 commits August 17, 2026 10:28
…ndalone)

Add a self-sampling variant of the GVR (Guess-Verify-Refine) heuristic
top-K decode for the DSA indexer, translated to CuTeDSL from the
optimized CUDA line (fork branch GVR-selfsampling-CuTeDSL), as two
standalone modules under cute_dsl_kernels/blackwell/top_k:

- gvr_topk_decode_self_sampling.py: merged device module — four kernel
  families (sampling-ladder main / register-resident reg / cluster clus /
  cluster-register reg_clus), lazily JIT-compiled per constexpr tuple.
- gvr_topk_decode_self_sampling_host.py: host companion — pure-function
  dispatch route(b, n, npad, k) (bit-exact transcription of the CUDA
  host dispatch, cross-checked by a 1,159,168-case boundary+fuzz sweep
  plus a 300k-case parity fuzz of this merged form), per-device
  workspace slab, and run/run_ws DPS entries with the CUDA binding's
  hardening battery.

Contract (documented in the host module): batch-uniform host-int
n_valid in compressed index space; fp32; K in {512, 1024, 2048};
64-element-multiple row stride. Exact (tie-interchangeable) top-K.
NOT wired into the decode path: the production engine reads per-request
seq_lens on-device with per-row MTP offsets (heuristicTopKDecode.cu);
adopting that per-row contract inside these kernels is follow-up work,
so this module must not substitute for the tiered path under continuous
batching, MTP, or CUDA-graph capture.

Evidence (B200): 886-cell x 11-BS real-decode-capture grid = 9,746
cases vs the production CUDA arm: 0 INEXACT, geomean ratio 0.974.
Unit tests: tie-aware exactness (signed-zero normalized, poisoned-pad
immunity) across gate-edge and envelope shapes, run_ws with caller
workspace, guard predicates, dispatch totality; sm_100-gated, picked up
by the existing unittest/_torch/thop/parallel sweep.

Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
… raw indices, uniform across DSv3.2/Flash/Pro

The contract docstring previously instructed callers to apply the cr==1
+1 temporal shift to pre_idx (mirroring heuristicTopKDecode.cu). Drop
it: hints only steer the sampling ladder — exactness never depends on
them — and on real V3.2 decode captures raw prev-step hints overlap the
current top-K at 0.773 vs 0.536 when +1-shifted (15 cells x 14
consecutive step-pairs, gap widening with ISL). One offset-free hint
convention now serves all three models; the kernels already consume
pre_idx as-is, so this is a contract-documentation fix only.

Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
…ction pad convention)

When the row has no more than topK valid entries every valid position
is in the top-K: emit identity indices and pad the tail with -1,
mirroring heuristicTopKDecode.cu:72-84. Host-level torch-op branch for
the standalone module (the CUDA-graph-safe per-row rewrite will move it
in-kernel, where a per-row fallback is impossible inside a graph).
Closes the 'n <= topK unproven' gap from the integration audit.

Tests: 8 boundary shapes (n in {64..2048}, k in {512,1024,2048},
n < k / n == k-1 / n == k) x bs {1,4} with poisoned padding, plus
kernel-path regression just above the boundary (k512_n4099,
k2048_n4111) verified exact on B200.

Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
…lt off)

Optional `values` DPS output on run()/run_ws(), production parity for
the heuristicTopKDecode values writeback. Default None = OFF, matching
dsa.py, which allocates the values scratch only for the non-CuTeDSL
path. The indices are exact, so a gather epilogue reproduces the
in-kernel writeback bit-for-bit at zero cost when disabled; the
constexpr in-kernel form rides the CUDA-graph per-row rewrite. Short
path pads values with -FLT_MAX (production convention).

Tests: kernel path (values == gathered top-K == torch.topk multiset),
short path (head copies logits, -FLT_MAX tail), wide-buffer packed
re-view and dtype guard, verified on B200.

Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
…ests

Production can hand the kernel degenerate hint buffers: dsa.py
initializes heuristic_prev_topk with zero_() (all-zero cold start; the
prefill->decode seeding covers the common path but zero-init corners
remain), and nothing forbids duplicated hints. Exactness must never
depend on hint quality (hint-robustness bug class of PR NVIDIA#17550).

Adds all-zero / all-same / all-max / half-duplicated hint cases on
(k512, n8192) and the k2048 gate-edge n131075 — all verified exact on
B200 (also probed on n131072/k1024 x bs{1,4} pre-commit, 24 configs,
zero failures).

Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
…act entry (reference engine)

Adds run_varlen(logits, pre_idx, kv_lens, indices, next_n=,
compress_ratio=, values=None) — the heuristicTopKDecode contract:
per-request device kv_lens (TOTAL cache length, uncompressed token
space), per-row n = (kv_len - next_n + row%next_n + 1)/compressRatio
(the MTP window formula, cr 1 = DSv3.2 / 4 = DSv4 Flash+Pro),
request-level raw pre_idx shared by a request's next_n rows, per-row
n <= k short path.

REFERENCE engine: one documented host read of kv_lens (raises under
CUDA-graph capture), rows driven as b=1 launches through the
batch-uniform engine. This pins the varlen/MTP contract and its test
battery; the per-row in-kernel engine (device kv_lens reads, fixed-R
thin-slicing, n-band parameter table) replaces the loop next without
changing either.

Tests: heterogeneous lengths with per-row poisoned padding, cr in
{1,4} x next_n in {1,2,4} incl. in-request n variation and
compressed-boundary-crossing rows, mixed short rows, values output,
contract guards — all verified exact on B200.

Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
…it (route_static/route_dynamic)

Factors route(b, n, npad, k) into route_static — family, compile tuple,
grid/cluster/block and the rt scalars that change only at discrete
n-thresholds (freezable at CUDA-graph capture time, mirroring the
in-tree runner's pick_tuning(graph_capture=...) pattern) — and
route_dynamic — the n-continuous scalars a per-row kernel recomputes
from its own row length (CMP and the reg smem footprint; the
SMP/TGT/SS2/TGT2/Q sampling ladder for the streaming families). The
device-side per-row engine will mirror exactly the route_dynamic
formulas.

Lossless by construction and by fuzz: recombining the halves reproduces
route() bit-exactly on 163,755 (b, n, k) points (threshold windows,
R-boundaries, prime-stride sweep, LCG random; full result-dict
equality). route_bands() enumerates the static-constant n-intervals —
the (b=8, k=1024, 262144) envelope collapses to 10 contiguous bands, so
the eventual in-kernel band table is tiny. Host-side groundwork only;
no kernel behavior change.

Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
…gine (gvr_main port)

The gvr_main family gains a per-row varlen mode (production
heuristicTopKDecode contract): each CTA reads its row's kv_len from a
device kv_lens tensor and re-derives n plus the whole sampling ladder
(SMP/SS2/TGT/TGT2/Q) via an exact device transcription of the
route_dynamic() host formulas — integer round(sqrt(6n)) with an isqrt
fixup (bit-parity with the host double form), Int64 TGT products
(the CUDA host math is 64-bit), and constexpr next_n / cr_shift /
r_const so every division strength-reduces. All values are pure
functions of the row index, so the R split CTAs of a row stay
grid-uniform by construction (workspace handshake unchanged).

Design points:
- No runtime return in CuTe DSL (in-tree gvr_topk_decode.py precedent):
  n <= k rows run the body as a zero-work pass (n=0, TGT=INT_MAX so no
  rung accepts, Q=0 empties the split slices) and an epilogue emits the
  production identity/-1-pad short path.
- The TSH-floor staging gate becomes per-row runtime (tsh_en && per-row
  n4 <= 32768), exactly the CUDA original's grid-uniform runtime gate;
  legacy compiles keep bit-identical behavior (tsh_run == 1).
- pre_idx is REQUEST-level in varlen mode (row // next_n mapping).
- run_varlen(engine="auto") launches the batch in ONE kernel; with
  max_seq_len given (capture-stable engine constant) the call performs
  no host reads. engine="reference" keeps the b=1 loop as the
  differential oracle. Capture-time tuning comes from route_streaming()
  (the streaming half of route(), 110,003-point fuzz agreement).
- Legacy batch-uniform ABI extended with dead trailing args (dummy
  kv_lens + five zeros) — one kern body, no duplication.

Validated on B200: legacy regression 5/5 exact (extended ABI), engine
vs reference differential 9/9 mixed-batch configs row-for-row equal —
deep SPLIT (b=1, n=200k), 8-row cr=4 mix to n=225k, tsh band (b=16),
MTP next_n {2,4}, all-short batches, 200-row small_dense k=2048, and
the no-host-read max_seq_len path.

Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
…est for the varlen engine

Warm up, capture one run_varlen(engine=auto, max_seq_len=...) launch —
no host reads, no JIT inside capture — then replay while kv_lens grows
in place: a row crossing the n <= topK short-path boundary INSIDE the
graph, a row walking the 131072 band edge, and a 200k deep row. Every
replay verified tie-aware exact on B200 (8-replay standalone run all
green). This closes the CUDA-graph-safety design goal for the gvr_main
varlen port: geometry and tuple are frozen from capture-stable
quantities (route_streaming at max_seq_len), all N-dependence is
per-row device arithmetic.

Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
…TRTLLM_GVR_SELF_SAMPLING=1)

Wires the varlen engine into the DSA indexer decode top-K dispatch as
the highest-priority branch, env-gated (TRTLLM_GVR_SELF_SAMPLING=1) and
contract-gated at init (cutlass DSL present, sm100+, index_topk in
{512,1024,2048}, compress_ratio in {1,4}) — covering DSv3.2 (K=2048,
cr=1), DSv4 Flash (K=512, cr=4) and DSv4 Pro (K=1024, cr=4). The call
reuses the exact buffers of the existing cute_dsl_gvr_topk_decode
branch (request-level heuristic_prev_topk, kv_lens_cuda_runtime,
topk_indices_buffer, indexer_max_seq_len as the capture-stable tuning
constant) — no new metadata plumbing. Lazy import behind the gate;
contract violations raise loudly (explicit experiment flag, not a
silent fallback). First warmup call pays the one-time DSL JIT.

Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
…ive the TGT*2 scan target

The zero-work short pass used TGT = 0x7FFFFFFF, whose TGT*2 third scan
target overflows Int32 to -2, flipping every tot0 >= TGT*2 gate on the
all-zero histogram (benign downstream today — empty candidate sets emit
nothing — but an unnecessary cliff). Use 2^30-1 so the doubled target
stays positive; 'never accepts' semantics unchanged. Full differential
battery (9/9 mixed-batch configs) + graph capture/replay (8 replays)
re-verified green on B200.

Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
… of the varlen engine and seam

Fixes from a 4-dimension adversarial review of the varlen stack:

- CRITICAL seam unit bug: indexer_max_seq_len is COMPRESSED index space
  (metadata divides by compress_ratio) but run_varlen's max_seq_len is
  kv-token space — the seam shifted twice, freezing a 4x-too-small
  tuning envelope for cr=4. Seam now multiplies back (exact identity:
  n_env == indexer_max_seq_len).
- Seam crash on legal configs: the DSL paged-MQA logits arena is a
  256-aligned buffer column-sliced to max_seq_len — a NON-contiguous
  view the engine used to reject. The engine now accepts row-major
  views (inner stride 1) and widens them back to a compact
  [rows, row_stride] view over the same storage (as_strided, zero
  copy; the tail columns are never classified — per-row n gates all
  reads). A dispatch-site hardware-format gate (stride %4, 16B base)
  falls through to the existing branches for layouts the kernel cannot
  address (odd-npad DeepGEMM).
- OOB-write guard (three reviewers converged): the engine never
  validated indices/values batch dims — a request-level-shaped buffer
  under MTP would be silently written past its end (grid comes from
  logits rows). Full B1-style battery now runs on the engine path
  (CUDA/dtype/2-D/contiguity/batch/width/alignment), kv_lens
  contiguity included.
- Engine/reference convention alignment: wider-than-k buffers now
  follow the flat-packed contract identically on both engines; the
  reference clamps kv_len < next_n to the empty row (all -1) exactly
  like the kernel — padded/evicted graph slots are a legal input, and
  the differential oracle can now cover them.
- Eager-mode compile churn: without max_seq_len the data-dependent
  envelope is quantized up to the next power of two (bounded plan set
  and _VARLEN_CACHE; a growing decode no longer recompiles at every
  R increment).
- Multi-stream escape hatch: run_varlen(workspace=...) (run_ws parity)
  so concurrent streams do not share the SPLIT publish slab.
- Legacy hot path: per-device cached dummy kv_lens (no per-call
  allocation for the dead ABI slot).

New tests: b=16 SPLIT + per-row TSH runtime gate, b=200 BLK=512
non-split, zero-kv slot mixed with live MTP rows (both engines),
wide-buffer flat-packed convention (both engines), num_rows /
strided-kv_lens guards. Full battery re-verified on B200 (10/10 incl.
a non-contiguous arena-view differential).

Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
Release-Check (PR_Github #67203) failed on pre-commit: ruff-format reflow
on 3 files, one ruff F841 (unused next_n unpack in the varlen differential
test), one codespell hit (statics -> static fields). Formatting-only plus
the two mechanical fixes; kernel exactness re-verified on GPU after the
reflow: full unit file 65/65 passed (sm100, standalone overlay stack).

Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
…ng-only surface

Public package exports now carry ONLY the production contract
(selfsampling_topk_run_varlen: per-request device kv_lens, no
batch-uniformity assumption — mirrors the single-op shape of the CUDA
indexer_topk_decode integration). run/run_ws keep serving as the bench DPS
contract and the reference-oracle plumbing (_run_impl is what
engine="reference" walks row by row), but are no longer package-exported
and carry TESTING/BENCH ONLY docstring warnings. Tests already import the
host module directly — zero test churn.

Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
…y, hint/domain tests

Framework-integration audit hardening (all GPU-verified, 70/70 unit file):
- dispatch gate now also requires fp32 logits (falls through loudly instead
  of feeding a non-fp32 tensor into the fp32-typed DSL engine; production
  DSA logits are always fp32 today, this is belt-and-braces for future paths)
- logger.info_once on first engagement + logger.warning_once on first
  hardware-format fall-through: operators can tell which arm served without
  profiling
- new tests: hints containing -1 (the production short-row pad tail that
  flows back through heuristic_prev_topk; engine's unsigned-compare guard
  verified exact) and route() domain up to 8192 rows (max_batch x next_n
  exceeds the bench grid's b<=1024 envelope)

Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
…elper

Mirrors warmup_heuristic_topk_decode / warmup_cute_dsl_radix_topk: one tiny
real launch per requested num_rows compiles the varlen engine's envelope
tuples so no live request pays the first-touch DSL JIT (measured: 9.1 s
cold compile for two tuples on a fresh cache; idempotent re-call 0 ms;
post-warmup first real call 0.1 ms). Exposed as a module-level helper —
CUDA-graph capture warmup already compiles the captured batch sizes, and
wiring an automatic init hook needs max_seq_len plumbing that is not
available at Indexer.__init__ time (follow-up).

Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
…e.warmup

Completes the warmup story: DSAtrtllmAttentionMetadata.warmup_selfsampling_topk
mirrors warmup_cute_dsl_radix_topk (same ModelEngine hook, which is where
max_seq_len is actually available — Indexer.__init__ is not). Gated on the
env flag + the same init-contract conditions; compiles the eager first-touch
(num_rows=next_n) tuple via warmup_varlen so no live request pays the DSL
JIT. Also: seam init comment now states the two-guard reality (format gate
falls through with a one-time warning; in-engine contract violations raise),
and run_varlen documents the inherited NaN-ordering limitation (finite
inputs incl. +/-inf are tie-aware exact).

Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
… next_n=3 coverage

run_varlen now rejects non-fp32 logits with a clear contract error (the
dispatch seam falls through before this; direct callers previously hit an
opaque CuTe typing failure) — regression-tested. The varlen differential
gains a next_n=3 (MTP2) case: no production config uses it today, but the
per-row window formula is now verified to generalize (both engines exact),
closing the gap noted in the PR's MTP-coverage section. GPU-verified:
3/3 targeted tests green.

Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
…age + robustness)

Adopted from the first review round:

- warmup_varlen records its done-key only after every launch succeeds, so
  a failed or interrupted warmup retries instead of short-circuiting to an
  uncompiled engine; one max-geometry allocation is reused across row
  counts (prefix views — compile keys depend on shapes only).
- The ModelEngine warmup hook now passes the configured CUDA-graph batch
  sizes: the varlen launcher is keyed by the exact row count, so capture
  no longer depends on a prior warmup-forward having compiled its key, and
  warmed geometries never pay first-touch JIT. Best-effort under OOM
  (warn + lazy JIT) since dispatch works without warmup outside capture.
- route/route_streaming reject b < 1 with a contract error instead of
  ZeroDivisionError.
- run/_run_impl: the launch try block no longer swallows routing/compile
  errors as 'launch failed', and the values epilogue matches run_varlen
  (clamp + mask instead of a context-poisoning gather assert).
- Stale module docstring refreshed (run_varlen IS the wired production
  entry); warmup_varlen exported in __all__; public entry points annotated.
- Test SM gate aligned with the production dispatch gate (>= 100; the
  module previously skipped everywhere except exactly sm_100).

GPU-verified: full battery 73/73; smoke 8/8 (b<1 guards, multi-rows
warmup + idempotency, CUDA-graph capture at a warmed row count exact,
unwarmed capture raises loudly, injected-failure warmup retries with a
real recompile).

Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
…ch actually looks up

The varlen launcher key includes the logits row stride (npad), and the two
sides derived it differently: the DSL paged-MQA arena rounds each row up to
256 elements (CuteDSLPagedMQALogitsRunner: compute_block_kv=128, SPLIT_KV=
256), while warmup_varlen synthesized its logits at 64-element rounding.
Whenever the indexer max_seq_len is not a 256 multiple, warmup compiled a
variant dispatch never looks up: with CUDA graphs the pre-capture warmup
forwards masked this (init just wasted a few seconds on phantom keys), but
in eager serving the first live decode paid the full first-touch DSL JIT —
exactly the stall the helper exists to remove. Never a correctness issue:
a key miss lazily JITs the true variant.

- warmup_varlen gains row_stride; the caller passes the producer's actual
  row stride and the done-key includes it.
- metadata.warmup_selfsampling_topk mirrors the active producer: DSL
  paged-MQA -> round up to 256; DeepGEMM -> exact width (non-float4 widths
  fall through at the dispatch format gate, so nothing to warm). A future
  drift only degrades warmup back to unused keys, never to wrong results.
- warmup now also applies the same hardware gates as the dispatch flag
  (CUTLASS DSL available, SM100+), so setting the env var on an unsupported
  stack can no longer make engine warmup compile Blackwell kernels.
- New tests: capture immediately after a row_stride warmup on a
  column-sliced arena view (fails loudly with the old 64-rounding), and
  kv_lens < next_n zero-window rows (graph-padding dummies) pad-emit -1
  while cohabiting normal requests stay exact.

GPU-verified: full battery 75/75 (73 prior + 2 new).
Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
…d datacenter Blackwell

The previous review round aligned the test gate with the dispatch gate at
>= 100, which silently admits consumer Blackwell (sm_120/121): those parts
lack thread-block clusters, the kernels have never been validated there,
and CI stages like RTX5080/5090 and GB10 would start collecting the suite.
Tighten all three gates (dispatch flag, warmup hook, test module) to the
validated set {sm_100, sm_103} — B200/B300-class datacenter parts — which
was the reviewer's suggested alternative. Spot-checked on sm_100: module
still collected, 4/4 targeted tests green, hooks clean.

Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
…e (fall through above 32 rows)

At high in-flight row counts the varlen streaming engine stops splitting
rows across CTAs (route_streaming keeps multi-CTA R only for b <= 32,
R=2 up to b <= 74, then one CTA per row) and long rows lose ~5x per call
to the in-tree per-row-split kernels — measured in DSv4 Pro 1M-ISL MTP7
EP16 serving: rows=304, 2.63 ms vs 0.49 ms per top-K call, ~4x TPOT end
to end. Gate the DSA dispatch on a shared MAX_VARLEN_ROWS=32 admission
envelope (info_once fall-through to the in-tree path), drop over-envelope
row counts from warmup_varlen so engine init never compiles keys dispatch
cannot admit, and document the envelope on run_varlen. Large batches keep
taking the in-tree path until a throughput tier lands (see roadmap).

Two new tests: the envelope constant matches route_streaming's multi-CTA
region (rows=304 collapses to R=1), and warmup drops over-envelope row
counts while still warming the admitted prefix.

Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
… single-row width gate

Two residuals from the production audit. (1) Warmup previously covered only
{next_n} and the CUDA-graph batch sizes, so eager mixed prefill+decode
batches at other in-flight generation counts paid a multi-second first-touch
JIT on the serving path (4-13 reachable unwarmed r_const bands per model
with padded graph lists). Warm every admissible row count instead — next_n
multiples up to MAX_VARLEN_ROWS — which bounds init cost at <= 32/next_n
tiny launches over the same few engine compiles and leaves nothing to
compile at serving time inside the envelope. (2) The dispatch format gate
checked only stride(0), but single-row batches derive their row window from
shape[1] (arena last-row safety), so a non-float4 indexer_max_seq_len with
next_n==1 raised inside run_varlen instead of falling through; gate that
width explicitly.

Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
…r small batches; dedup dead TSHG key slot

The varlen launcher gated TSH-floor staging on num_rows > 15, stranding
small batches in SPLIT-main without the staged floor. On real deep-layer
captures this is a distribution-dependent ~6x tail (v4_pro_512k L46/L52,
n4=32768, rows 1-8: 142-151 us -> 25 us with staging; tie-aware exact
preserved, healthy layers and n4 > 32768 rows unchanged within noise).
The kernel compiles the TSH machinery whenever SPLIT and gates it per row
at runtime, so this is a runtime-scalar-only change: no new engines, no
compile-key change, CUDA-graph safe. Also normalize the dead TSHG slot
out of the varlen compile key (the ctor overrides it under SPLIT), so row
counts differing only in that slot share one engine instead of compiling
twice. Varlen/envelope test subset: 22/22.

Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
…clustered register-resident family

Lift the varlen production path's force-main restriction for its first
specialist family: GvrRegClusKernel gains the same per-row varlen mode
already shipped in GvrMainKernel — n is re-derived per row in-kernel from
device kv_lens (production heuristicTopKDecode contract), hints map to the
request row, and short rows (n <= k) emit identity + (-1) tail from rank 0
and skip the body entirely (a zero-work pass would reach the degenerate
crossing-overflow emitter). All derived quantities are pure functions of
the row, so the whole-body guard is cluster-uniform and the cluster
barriers stay aligned. The varlen launcher admits the family exactly where
the free route() picks it (route parity tier 1) — a pure function of the
capture-stable cache key, so CUDA-graph replay safety is unchanged; its
whole admission window (n4 <= 32768) fits capture-frozen envelopes.

On real captures this recovers most of the streaming-only integration tax
in the family's window: the deep-layer distribution tail (v4_pro_512k
L46/L52, rows 8) goes 147 us (pre-TSH-fix) / 25 us (TSH fix) -> 20.7 us,
against 16-18 us for the batch-uniform standalone. Full self-sampling UT
suite green (79/79) incl. two new tests: heterogeneous kv_lens + MTP row
windows vs the reference oracle, and cluster-family CUDA-graph
capture/replay exactness.

Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
… row count and envelope goes through the self-sampling engines

Remove the rows admission envelope: with TRTLLM_GVR_SELF_SAMPLING=1 the
self-sampling engines now serve the full production range (BS 1..1024+ x
next_n, envelopes to 1M kv tokens) instead of falling through to the
in-tree path above 32 rows — restoring the standalone design intent.
Warmup switches from row-count filtering to band-aware enumeration: one
representative row per distinct engine compile key (bounded time and
memory for arbitrarily large CUDA-graph batch lists), then per-row-count
launcher-cache population (pure host work) so capture at any requested
geometry finds its key immediately — preserving the warm-the-key-dispatch-
looks-up discipline the arena-stride capture test pins.

Production-lessons checklist re-validated on this change: capture-stable
pure-function dispatch (family from the launcher key, per-row n in-kernel
from device kv_lens), loud-fail on uncompiled keys under capture, two-pass
pre-capture warmup coverage, producer row-stride key mirroring, workspace
slab grow-never/keep-alive, cluster-family graph capture, heterogeneous
kv_lens + MTP row-window oracle tests. Full suite 79/79.

Real-capture record (vs the in-tree production op, cold-L2, all exact):
rows 64..1024 wins 2.9-4.7x at N=262144, 3.1-3.7x on the former
deep-layer pathology band, 1.6-3.5x on DSv3.2 163k; short-N cells
(N <= ~2k) trade 0.67-0.94x at microsecond scale — accepted by design:
enabled means self-sampling, everywhere.

Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
…ndependence test at throughput scale

Pin the varlen row-independence contract on the streaming main family:
one 304-row batch mixing random per-request kv_lens with full-length,
short (n <= k), zero-window and boundary rows under MTP row windows
(next_n=4) — every row must match its own per-prefix torch.topk value
multiset, short rows the in-kernel identity + (-1) tail, zero-window rows
all -1. No engine may assume batch-uniform indexer-logits lengths.

Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
…register-resident family (reg/regimg)

Route-parity tier 2: the varlen launcher now admits the register-resident
family (and its img-window flavor) exactly where the free route picks it,
instead of forcing those bands through the streaming main kernel. This
family owns the small/mid-N band across all row counts in the standalone
dispatch, where main pays a 1.5x tax.

Kernel side (GvrRegClusKernel discipline): per-row n re-derived in-kernel
from device kv_lens (request windows via next_n/cr_shift, envelope clamp);
short rows (n <= k) emit identity + (-1) tail in-kernel and skip the body
-- k can exceed BLK on this family, so the emit is a strided loop, not the
reg_clus single predicate. kv_lens joins the ABI as a dead dummy slot in
batch-uniform mode (gvr_main precedent). CMP/QC/smem stay envelope-derived
launch constants: in-kernel they are pure capacity clamps, a fast-path
threshold and the launch smem size -- safe upper bounds for every
per-row n <= envelope.

Production lessons re-checked: capture-stable dispatch (family remains a
pure function of the launcher cache key), warmup band enumeration gains the
reg engine key, loud-fail on uncompiled keys under capture unchanged.

Tests: route-parity + heterogeneous oracle (reg, k>BLK strided short-row
emit, regimg window) and CUDA-graph capture/replay; suite 82/82.

Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
…emaining clus/reg_clus reciprocals

Convert the last three IEEE-div reciprocal sites (clus sample-hist scale,
clus window scale, reg_clus bin-transform scale) to cute.arch.rcp_approx,
completing the fix-4 CUDA --use_fast_math parity (bare MUFU.RCP instead of
the div.rn rcp+Newton+CALL chain). All three feed classify bucketing only,
which is scale-invariant for any SC > 0; inputs are clamped positive finite
by the existing guards. UT 82/82; paired same-GPU A/B on clus/reg_clus
cells is noise-level (<=1.3%), no regression (the fix-1 spill hazard that
kept main's blk!=512 arm on div.rn does not reproduce in these families).

The cute.math.div(..., fastmath=True) spelling (per DSL-team guidance) is
not available on cutlass 4.5.0 (this PR's validated stack); adopt it when
the tree moves to DSL >= 4.6.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
…cluster-split family (clus)

Route-parity tier 3, completing the family coverage: the varlen production
entry now admits every family exactly where the free route picks it (main /
reg_clus / reg / regimg / clus) -- no force-main fallback band remains.

GvrClusKernel gains the per-row varlen mode (GvrMainKernel / GvrRegClusKernel
discipline): n is re-derived per row from device kv_lens; the sampling-ladder
scalars (SMP/TGT/Q/SS2/TGT2, dead launch slots in this mode) are re-derived
per row by the route_dynamic clus mirror (exact-integer isqrt aim ladder,
Int64 target products, QUAD 16-multiplier geometry). SCAP/CMP are pure
functions of (rows, CS, k) -- never of n -- so the envelope launch values are
the per-row values; smem extents unchanged. The 'big' occupancy flag is
launch-computed in the jit wrapper (rows * CS <= 148). One deviation from
route_dynamic, documented in-kernel: the QUAD schedule is computed for every
non-short row instead of only n > SCAP -- the host only ever launches this
family with n > SCAP, so the SMP == 0 no-sample path is untested; per-row
n <= SCAP rows get a small valid schedule (sampling only steers the rung;
exactness is schedule-invariant, and sample positions stay under the row's
own n). Short rows (n <= k) emit identity + (-1) tail from cluster rank 0
and skip the whole body -- 'short' is a pure function of 'row', identical
across all CS ranks, so every cluster barrier stays aligned.

Verification: UT 84/84 (2 new: heterogeneous parity+oracle over CS=2 and
CS=4 clusters with mid rows below the standalone admission floor, and a
CUDA-graph capture/replay test). Real-capture clus zone (153 cells / 255
cases from the 886-grid census): all exact, integration tax (same-session
CUDA-events pairing, cmp886_r3 protocol) 1.319 -> 1.137 gm (worst 1.216),
now at the same intrinsic per-row level as the other families. Graph-replay
protocol on clus shapes: tax 1.096-1.124.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
longcheng-nv and others added 12 commits August 22, 2026 10:29
…mirror chain

The per-row re-derivation of the sampling-ladder scalars was the dominant
varlen-vs-standalone kernel tax (l1p1 ncu: inst x1.58-1.73 on main shapes,
x1.26-1.28 on clus -- all of it the DSL's runtime Int32/Int64 divides and
isqrt fixup loops, redundantly issued by every thread).  Two fixes, chosen
per family by measured mirror share:

- main: warp0 alone walks the ladder and publishes SMP/SS2/TGT/TGT2
  through a 4-word smem block; the other warps spend the wait issuing
  register-free L2 hints for this CTA's own P3 slice, then everyone reads
  the scalars back after one CTA barrier.  The chain itself swaps runtime
  divides for MUFU.RCP multiplies and collapses the isqrt fixup loops to
  single steps (f32 sqrt of an exactly-representable int is within 1).
- clus: same cheap-chain spelling, kept all-thread in place.  This
  family's mirror share is small, so a warp0+barrier hoist EXPOSES the
  chain's serial latency at ~1 CTA/SM (measured 1.14->1.31 tax regression)
  while the redundant form hides it across warps.

Schedule quantities may drift +-1 vs the host double form; exactness is
schedule-invariant (same argument as the clus port's always-sample
deviation).  A one-step window guard keeps SMP*SS2 inside the sampled
row span.  Q (chunk ownership) keeps its exact form in both families.
Legacy (batch-uniform) codegen is untouched: every change sits under
const_expr(varlen), including the smem slots.

nsys verdict (interleaved eager sa/vl, cold-L2, 200 paired reps/cell,
umbriel-b200-027): v32_128k r1 1.165->1.093, pro_1024k r8 1.184->1.103,
pro_1024k r32 1.135->1.083, pro_512k r64 1.131->1.071; reg_clus control
cells unchanged.  Treated-cell tax gm 1.154->1.087.  UT 84/84; exactness
smoke (main x3 + clus x2 + short/long heterogeneous kv_lens) all exact
under the tie-aware value-multiset oracle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
Adopts the TopK module refactor (NVIDIA#17622): the self-sampling engine is now
TopKImplementation.CUTE_DSL_GVR_V2 with the hardware-format gate living in
the module and falling through to the CUDA GVR body; the env opt-in
(TRTLLM_GVR_SELF_SAMPLING=1) promotes the implementation at the indexer's
selection site. The module receives max_seq_len in compressed index space,
so the V2 branch multiplies back to kv-token space for run_varlen.

Two latent branch bugs surfaced by the merge audit are fixed in the same
resolution: the warmup gate read metadata.enable_heuristic_topk, which
main renamed to enable_gvr_topk, and it referenced MAX_VARLEN_ROWS, which
the full-range dispatch commit removed from the host module (replaced by
a local eager warm-span constant). Both would have raised AttributeError
on the in-tree warmup path under the env opt-in; the e2e evidence runs
never hit them because the ss arm used the pre-refactor overlay seam.

Validated on the merged tree: wheel build, TopK-V2 module smoke (exact
through the module path + bf16 fall-through leg), kernel UT 84/84.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
Main advanced two commits during the first merge's validation; the only
overlap is positional (warmup_selfsampling_topk sits next to
on_update_kv_lens, whose signature gained an annotation). Python-only
delta upstream — no rebuild; TopK-V2 module smoke re-passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
…the TopK module edit

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
…module

Drop the dead per-row Q re-derivation in the clus varlen prologue (the Q
launch slot has no in-kernel consumer in this family; chunk ownership
derives from n4/STEPC — ruff F841), give the two torch-facing debug
entries local torch imports (the DSL module is deliberately torch-free at
import time — ruff F821), and take ruff-format's layout on the unit test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
…in the register family

Pure layout (long-line wrapping in GvrTopkRegKernel, no code change);
validated against the exact pushed tree state this time — the previous
lint pass ran on a local tree whose pending changes masked these lines.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
…TLASS DSL 4.6.1

cute.make_fragment was removed in nvidia-cutlass-dsl 4.6.1 — the version
this repo pins — so the self-sampling engines raised AttributeError at
first JIT under the shipping dependency set (undetected by CI: the unit
tests only collect on SM100/103 stages, which do not run this suite).
cute.make_rmem_tensor is the same-signature replacement, already the
convention in the in-tree DSL kernels, and exists in 4.5.0 as well, so
every environment keeps working. Mechanical rename, 23 sites.

Validated under BOTH pins: UT 84/84 on 4.6.1 (isolated overlay) and on
4.5.0; exactness smoke (main x3 + clus x2 + heterogeneous kv_lens) all
exact on both; nsys spot-check in the normal range. A full-grid perf
re-run under 4.6.1 is queued — all prior grid evidence compiled under
4.5.0, and codegen shifts measurably between compiler minors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
…o ld.global.nc.v4.f32

DSL 4.6.1's NVVM rewrites adjacent 128-bit f32 copy-atom loads into
v2.b64 register-PAIR loads plus mov.b64 unpacks (PTX: v4.b32 30->12,
v2.b64 0->18 on gvr_clus). The even-aligned pair constraint fragments
register allocation at the 64-register wall: the SAME ptxas 13.2 gives
64 regs / 4B spill from the 4.5.0 PTX but 63 regs / 80B spill from the
4.6.1 PTX — measured +20% on clus and +8.5% on the register family at
runtime (the dominant share of the 4.6.1 grid regression, clus zone tax
1.068 -> 1.223). An inline-asm boundary pins the four-scalar-f32 shape
on every DSL version; the isolated copy atom still lowers correctly, so
this guards against the context-dependent rewrite only.

nsys verdict under 4.6.1 (200 paired cold-L2 reps, replicated): clus r32
tax 1.163 -> 1.074 (vl 16.83 -> 13.87us), reg r12 1.207 -> 1.127, main
and reg_clus controls neutral. UT 84/84 on 4.6.1 and 4.5.0; exactness
smoke all exact on both pins.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
…time smem carveout in the register family

DSL 4.6.1 moved the shared-memory carveout derivation into a compile-time
MLIR attribute (smem.max_smem_per_mp, emitted when min_blocks_per_mp > 1)
that only accounts for static smem. With the register family's dynamic
launch smem this selects a 16 KiB shared-memory config, pinning each SM to
a single resident CTA (achieved occupancy 67.6% -> 11.6%) and slowing
saturating small-N grids up to 3.25x (N=1027 @ 1024 rows: 23.2us -> 7.1us
varlen after the fix). Extend the existing _no_carveout() compile scope to
also drop that attribute; clus/main/reg_clus launch with
min_blocks_per_mp == 1 and are unaffected (verified neutral).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
Condense narrative comment blocks to the technical invariant they encode
and rewrite module/class docstrings as plain functional descriptions.
Comment-and-docstring-only change: AST-verified equivalent (bare-string
sections and assert messages normalized), tie-aware exactness smoke green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
Add an opt-in block-skip row pass to the gvr_main family: a per-32-element
block-maxima side tensor lets the kernel visit only logit blocks whose
maximum can clear the sampled threshold (compact survivor list, warp-per-
block walk with ballot-aggregated emission; capacity overflow falls back to
an identity walk over the row window).  Emission, verify and the retry
ladder are shared with the baseline pass, so exactness semantics are
unchanged.  Dispatch is the shape-only bsk_gate (batch working set >=
128 MiB and N >= 72K); outside the gate run_bsk routes to the baseline.

Real-capture 886-cell x 11-BS A/B (nsys pure-kernel, cold-L2, same-GPU
paired): gate-open 531 pairs gm 1.735 (worst 0.942, single-cell anomaly;
next-worst 1.048), gated band neutral 0.96-1.05.

In production the block-maxima tensor is produced by the indexer-GEMM
emission epilogue (PR NVIDIA#16953 model); this change carries the consumer side
only and leaves the production route() untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant