Repository navigation
[None][feat] Deterministic mode for the self-sampling GVR V2 top-K decode (fp32) - #20100
Conversation
7cd8c9c to
2807acd
Compare
|
/bot run |
|
PR_Github #77151 [ run ] triggered by Bot. Commit: |
|
PR_Github #77151 [ run ] completed with state
|
2807acd to
a2b51d5
Compare
|
/bot run |
|
PR_Github #77153 [ run ] triggered by Bot. Commit: |
a2b51d5 to
37056a8
Compare
|
/bot run |
|
PR_Github #77155 [ run ] triggered by Bot. Commit: |
|
PR_Github #77153 [ run ] completed with state |
|
PR_Github #77155 [ run ] completed with state
|
37056a8 to
cb88296
Compare
|
/bot run |
|
PR_Github #77168 [ run ] triggered by Bot. Commit: |
|
PR_Github #77168 [ run ] completed with state
|
cb88296 to
7fb624c
Compare
|
/bot run |
|
PR_Github #77204 [ run ] triggered by Bot. Commit: |
|
PR_Github #77204 [ run ] completed with state
|
…code
Opt-in `deterministic` / `sorting` arguments on the fp32 self-sampling host
API (run, run_ws, run_varlen, warmup_varlen, run_prefill, prefill_ready,
warmup_prefill), the TopK module (decode and prefill branches) and the
DeepSeek sparse attention config (`gvr_deterministic_topk`, plumbed through
DSAParams / DSAMetadataParams to the DSA indexer and its warmup). With
`deterministic=True` the kernels compile their `cutlass.const_expr(self.det)`
branches and compute a data-defined result:
the selected set is {score > tau} plus the smallest indices among the ties,
and the output order is a fixed per-route order (no sort); `sorting=
"ascending"` additionally sorts the rows. Default arguments compile the
original code path (same cache keys, identical results to main).
The deterministic main R=1 kernel stages 20480 candidates at K=2048 (was
16384; the sampling ladder keeps the original capacity) so that real indexer
rows, whose candidate lists are clustered, do not overflow the per-warp
segments; an overflow re-sweeps the row once into exact segments.
Cold-L2 added kernel time (B200): synthetic rows geomean about +10 %, median
+0.9 us; real captured logits (Flash / Pro / V3.2) +0.2 .. +3.3 us per cell.
Battery of 696 + 88 records, 0 mismatches.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
7fb624c to
d0bab4a
Compare
|
/bot run |
|
PR_Github #77233 [ run ] triggered by Bot. Commit: |
|
PR_Github #77233 [ run ] completed with state
|
|
/bot run --stage-list "CPU-Generic-arm-1" |
|
PR_Github #77271 [ run ] triggered by Bot. Commit: |
|
PR_Github #77271 [ run ] completed with state |
mikeiovine
left a comment
There was a problem hiding this comment.
Stamp on behalf of runtime devs, delegating proper review to @NVIDIA/trt-llm-topk-devs; please ping me if you think this is not accurate
|
/bot run |
|
PR_Github #77366 [ run ] triggered by Bot. Commit: |
|
PR_Github #77366 [ run ] completed with state |
|
Automatically added "ci: full pre-merge approved" because this PR has satisfied the required GitHub review approvals. Unresolved review conversations and other required checks remain independent merge requirements. |
|
@yuxianq, this PR is already assigned to you as trt-llm-torch-attention-devs based on your earlier review. Could you help with the renewed review request? Thanks~ Assignment explanation
Affinity and workload are snapshots at selection, not live counters. Existing reservations on this PR are counted once. |
…atches_committed_golden Re-enable the premerge privacy gate and regenerate the golden manifest, which drifted again while the waiver hid the test: NVIDIA#20100 added sparse_attention_config.gvr_deterministic_topk to llm_args.py without regenerating. Signed-off-by: Faraz Khoubsirat <farazkh80@users.noreply.github.com>
…atches_committed_golden Re-enable the premerge privacy gate and regenerate the golden manifest, which drifted again while the waiver hid the test: NVIDIA#20100 added sparse_attention_config.gvr_deterministic_topk to llm_args.py without regenerating. Signed-off-by: Faraz Khoubsirat <58580514+farazkh80@users.noreply.github.com>
Summary
Opt-in deterministic mode for the fp32 self-sampling GVR V2 top-K decode kernel (
gvr_topk_decode_self_sampling):deterministic=Truegives a repeatable index set and a repeatable output order,sorting="ascending"additionally sorts each row. The default arguments run the unchanged original module (bit-identical tomain).Why: the production kernel reserves output positions through atomics, so the output order follows arrival order even when every score is distinct, and when scores tie at the K-th boundary the selected set itself follows arrival order. Bitwise reproducibility of the indexer (and anything downstream) is impossible without a defined rule.
What changes:
run_varlen/run/run_ws/warmup_varlenand the prefill entry pointsrun_prefill/prefill_ready/warmup_prefillgaindeterministic: bool = Falseandsorting: str | None = None;TopK(...)gainsgvr_deterministic/gvr_sortingand forwards them on both its decode and prefill self-sampling branches. Nothing else in the default path changes.DeepSeekSparseAttentionConfig.gvr_deterministic_topk(default False, also on the V4 config) reachesDSAParams/DSAMetadataParams; the DSA indexer constructsTopK(gvr_deterministic=...)from it and the DSA metadata warmup pre-compiles the deterministic decode and prefill engines, so CUDA-graph batches keep their warmed launchers. YAML:sparse_attention_config: {enable_heuristic_topk: true, gvr_deterministic_topk: true}.cutlass.const_expr(self.det)branches (adetconstructor flag on the four kernel classes, plusfused_k, the compile-time top-K, on the main kernel): selected set ={score > tau}plus the smallest indices among{score == tau}; output order is a fixed function of the input per kernel route (slot order on the register routes, per-warp staging segments in program order on main R=1 and clus, a bitmap / bitonic canonicalizer on the SPLIT merge CTA). Withdet=Falsethe branches fold away and the original code path is compiled; the only additions on that path are three typed predeclarations (idv,bq,xvin the main kernel) that the DSL needs once the names are also assigned in the other branch.Scope: the self-sampling GVR engine (
use_self_sampling_topk=True, the default underenable_heuristic_topk) for decode and prefill. The temporal-hint engine (use_self_sampling_topk=False) and the radix paths are untouched, sogvr_deterministic_topkis a no-op there; the QSA indexer only uses GVR for prefill and is covered by the same prefill path.Cost (B200, cold L2, added kernel time over the original): synthetic rows geomean +10.2 % / median +1.26 us / max +4.29 us over 84 cells; real captured logits geomean +10.5 % / median +0.91 us / max +3.00 us over 100 cells. Per-route table and per-cell tables below.
Semantics
All modes select the K largest scores and use the same boundary tie-break: at equal scores the smaller index wins (index-min).
sortingorders the output indices, not the scores. Repeatability assumes identical inputs, shape and software.deterministicsorting-1pads at the tailValueError: sorting requires deterministic=True(sorting a changing set would only hide it)Example 1, distinct scores, K=4 (adapted from the GVR README; the set
{1, 3, 6, 7}is unique, only the order differs):[7, 1, 6, 3][3, 6, 1, 7](may differ)deterministic=True[7, 1, 6, 3][7, 1, 6, 3](a fixed permutation, not necessarily this one)deterministic=True, sorting="ascending"[1, 3, 6, 7][1, 3, 6, 7]Example 2, boundary tie, K=4: indices 1, 3, 7 fill three slots; indices 2, 4, 6 tie at 7.0 for the last one. Index-min selects 2, so every deterministic launch returns the set
{1, 2, 3, 7}(ascending[1, 2, 3, 7]); the default mode returns 2, 4 or 6 depending on arrival.Measured, N=32768, K=512, bf16-rounded scores (129 ties at tau, 447 strictly above, 65 slots for the ties), B=64, three launches each: the default returns three different index sets (
25795c,3f2a9f,feceafas set hashes) in different orders;deterministic=Truereturns the same set (1722e9) in the same order every time (8222, 66, 12359, ..., the main route's per-warp segment order); withsorting="ascending"the same set comes back as66, 180, 306, 312, ....Overhead
B200, nsys GPU spans, 24 reps per cell, L2 flushed before every rep ("cold"). "Added" is deterministic kernel time minus original kernel time on the same input; the original against itself (A/A) is within about +/-1 %. Synthetic rows: one unique fp32 row per N (B in 1/8/64/128, N 4K .. 1M). Real rows: captured fp32 indexer decode logits, 8 layers per capture, one capture per ISL, batch cycles the 8 rows, 16-aligned buffer stride with
lengthscarrying the valid N.Added time per K and kernel route
Reading the table: the register routes (reg, reg_clus) cost 0.2 - 0.8 us; clus 0.6 - 1.2 us; main 1.6 - 2.7 us on average with a maximum of +4.29 us (synthetic, B=1 N=1M K=2048, where the SPLIT merge CTA sorts the 2048 indices because the bitmap canonicalizer is bounded to N <= 256K). Percentages above 20 % only occur on 9 - 13 us kernels (B <= 8 on the main route). On the long-row cells (B >= 64, N >= 256K, 20 - 110 us) the added time is within a few percent. Warm L2 on the synthetic cells: geomean +10.8 %, median +1.35 us.
Without the staging change the V3.2 rows at B >= 64 would have cost +4 to +47 us (warp-segment overflow -> scalar re-sweep + sort); with it they are +0.51 to +2.78 us.
Synthetic rows, K = 512: geomean +8.1 %, median added +0.76 us, max +1.98 us (cell: original us -> added us (added %), route)
Synthetic rows, K = 1024: geomean +11.0 %, median added +0.99 us, max +3.52 us (cell: original us -> added us (added %), route)
Synthetic rows, K = 2048: geomean +11.4 %, median added +1.81 us, max +4.29 us (cell: original us -> added us (added %), route)
DSv4 Flash, K=512 (9 captures): geomean +8.3 %, median added +0.73 us, max +2.04 us (cell: original us -> added us (added %), route)
DSv4 Pro, K=1024 (9 captures): geomean +9.3 %, median added +0.91 us, max +3.00 us (cell: original us -> added us (added %), route)
DeepSeek-V3.2, K=2048 (7 captures): geomean +14.8 %, median added +2.08 us, max +2.78 us (cell: original us -> added us (added %), route)
Default path: pre-merge module vs this PR with
deterministic=FalseSame cold protocol, B 1/8/64/128;
old= the device module before this PR, baseline = this PR's default path on the same inputs. Positive means the old module took longer. All differences are inside the +/-1-2 % A/A band (the -0.17 us minimum is a 3.5 us register kernel).Validation
run_prefill, deterministic): rows in {1, 8, 149, 297, 400} (all three row tiers, BLK 1024 / 512 / 256), N in {4K, 32K, 128K}, K in {512, 1024, 2048}, unique / bf16-rounded / all-equal / 8-value plateau scores, random windows and one short row per batch: 180 cases, 0 failures (oracle set in the local frame, identical output across launches, ascending option). This battery found and fixed one bug in the BLK=256 deterministic emission (output written in the global frame instead of column - ks).sorting="ascending": rows sorted, same set as the unsorted deterministic output.Test Coverage
tests/unittest/_torch/attention/sparse/test_cute_dsl_gvr_topk_deterministic.py: oracle set + repeat identity on one shape per kernel route x 4 data patterns (unique, bf16-rounded, all-equal, boundary ties), ascending rows, default-path argument validation, and a prefill case per row tier (1 / 149 / 297 rows) with random windows.test_cute_dsl_gvr_topk_decode.py/test_cute_dsl_gvr_topk_tiers.pykeep covering the unchanged default path.PR Checklist
[None][feat] ...🤖 Generated with Claude Code
Dev Engineer Review
The PR adds opt-in deterministic selection to self-sampling GVR V2 Top-K. The new options flow through
TopK, the GVR host APIs, and DeepSeek sparse-attention configuration. The reported defaults preserve the existing path; deterministic selection is intended for self-sampling GVR, not temporal-hint or radix paths.The available diff for
llm_args.pyshows import-formatting changes, but does not show the reportedgvr_deterministic_topkaddition. The supplied change summary describes the feature wiring; I could not verify all implementation details against the available diff.QA Engineer Review
The PR adds
tests/unittest/_torch/attention/sparse/test_cute_dsl_gvr_topk_deterministic.pyand modifiestests/unittest/_torch/modules/test_top_k.py. The new tests cover repeatability, tie-breaking, ascending output, prefill selection, and rejection of sorting without deterministic mode. TheTopKtest checks option forwarding.The test-list search returned no matching entries under
tests/integration/test_lists/. The supplied test summary reports hardware-gated tests, but no test-run results were independently verified here. Coverage verdict: needs follow-up.Per-File QA Perspective
tensorrt_llm/_torch/attention/backends/sparse/dsa/indexer.py— Verify that the deterministic configuration reachesTopKwithout changing decode or prefill implementation selection.tensorrt_llm/_torch/attention/backends/sparse/dsa/metadata.py— Verify that metadata warmup uses deterministic mode only when configured.tensorrt_llm/_torch/attention/backends/sparse/dsa/params.py— Verify that the new configuration fields default toFalseand propagate through runtime and metadata parameters.tensorrt_llm/_torch/cute_dsl_kernels/blackwell/top_k/gvr_topk_decode_self_sampling_host.py— Verify deterministic cache-key handling, output ordering, padding behavior, and the error for sorting without deterministic mode.tensorrt_llm/_torch/modules/top_k.py— Verify option forwarding for self-sampling decode and prefill, and unchanged behavior with default arguments.tensorrt_llm/llmapi/llm_args.py— The reported configuration addsgvr_deterministic_topk; verify its propagation to DSA parameters. The available diff shows formatting changes, not that addition.tests/unittest/_torch/attention/sparse/test_cute_dsl_gvr_topk_deterministic.py— Tests deterministic selection, tie-breaking, ordering, prefill, and invalid sorting options. No integration test-list entry was found.tests/unittest/_torch/modules/test_top_k.py— Tests thatTopKforwards deterministic and sorting options. No integration test-list entry was found.