Skip to content

[TRTLLM-35882][feat] Add cute dsl gvr top-k decode kernel#14602

Merged
yuxianq merged 29 commits into
NVIDIA:mainfrom
limin2021:cute-dsl-gvr-topk
Jun 2, 2026
Merged

[TRTLLM-35882][feat] Add cute dsl gvr top-k decode kernel#14602
yuxianq merged 29 commits into
NVIDIA:mainfrom
limin2021:cute-dsl-gvr-topk

Conversation

@limin2021

@limin2021 limin2021 commented May 27, 2026

Copy link
Copy Markdown
Collaborator

Review Change Stack

Description

Add cute dsl gvr top-k decode kernel.
(1) port cuda gvr kernel to cute dsl. Thx for Long's help, who gives the 1st version porting code.
(2) add some extra optimizations, e.g., unroll, 256bits vectorization, num_threads tune, to further improve the perf.

for more visualization:

Cheng, Long, et al. "Guess-Verify-Refine: Data-Aware Top-K for Sparse-Attention Decoding on Blackwell via Temporal Correlation." arXiv preprint arXiv:2604.22312 (2026). https://doi.org/10.48550/arXiv.2604.22312

Test Coverage

# ut running in CI, used for production.
python -m pytest tests/unittest/_torch/attention/sparse/test_cute_dsl_gvr_topk_decode.py
# standard alone ut, which is not dependent on trtllm env. It don't run in CI.
python tests/scripts/cute_dsl_kernels/top_k/run_gvr_topk.py
python -m pytest tests/scripts/cute_dsl_kernels/top_k/run_gvr_topk.py

Performance

Hardware: B200 SXM5 1000W + Intel Xeon Platinum 8570
Data: swebench-temporal-synth beta_deep bundles
Bench: bench_kineto n_iters=30, warmup=1, L2 flush
Env: TRTLLM_FORCE_HEURISTIC_TOPK=1 TRTLLM_HEURISTIC_NMIN=1024 DSL_AUTO=1
Baseline: CUDA heuristicTopKMultiRowKernel (production GVR Top-K)

dtype K N BS CUDA µs DSL µs speedup
bf16 512 4096 1 8.943 8.021 1.115×
bf16 512 4096 4 8.608 7.690 1.119×
bf16 512 4096 16 8.734 7.749 1.127×
bf16 512 4096 64 8.923 7.903 1.129×
bf16 512 4096 128 9.084 8.178 1.111×
bf16 512 4096 256 10.606 9.830 1.079×
bf16 512 4096 384 12.434 11.933 1.042×
bf16 512 4096 512 18.226 16.625 1.096×
bf16 512 8192 1 9.665 8.812 1.097×
bf16 512 8192 4 9.486 8.538 1.111×
bf16 512 8192 16 9.507 8.697 1.093×
bf16 512 8192 64 9.640 8.934 1.079×
bf16 512 8192 128 9.979 9.084 1.099×
bf16 512 8192 256 11.766 11.237 1.047×
bf16 512 8192 384 13.877 13.522 1.026×
bf16 512 8192 512 19.965 18.919 1.055×
bf16 512 16384 1 9.863 8.997 1.096×
bf16 512 16384 4 9.918 9.070 1.094×
bf16 512 16384 16 9.985 9.149 1.091×
bf16 512 16384 64 10.281 9.499 1.082×
bf16 512 16384 128 10.804 9.916 1.090×
bf16 512 16384 256 13.331 12.725 1.048×
bf16 512 16384 384 16.637 16.028 1.038×
bf16 512 16384 512 22.473 21.167 1.062×
bf16 512 32768 1 11.717 10.810 1.084×
bf16 512 32768 4 11.389 10.624 1.072×
bf16 512 32768 16 11.534 10.607 1.087×
bf16 512 32768 64 12.153 11.284 1.077×
bf16 512 32768 128 12.766 11.998 1.064×
bf16 512 32768 256 17.047 16.225 1.051×
bf16 512 32768 384 21.617 20.770 1.041×
bf16 512 32768 512 28.557 26.861 1.063×
bf16 512 65536 1 16.842 13.404 1.256×
bf16 512 65536 4 16.449 13.281 1.239×
bf16 512 65536 16 16.701 13.375 1.249×
bf16 512 65536 64 17.537 14.340 1.223×
bf16 512 65536 128 18.532 15.148 1.223×
bf16 512 65536 256 23.837 22.885 1.042×
bf16 512 65536 384 30.805 29.880 1.031×
bf16 512 65536 512 45.030 40.401 1.115×
bf16 512 131072 1 24.356 17.979 1.355×
bf16 512 131072 4 23.932 17.642 1.357×
bf16 512 131072 16 24.496 18.071 1.356×
bf16 512 131072 64 25.680 19.197 1.338×
bf16 512 131072 128 27.351 20.880 1.310×
bf16 512 131072 256 38.761 35.703 1.086×
bf16 512 131072 384 55.333 51.092 1.083×
bf16 512 131072 512 83.954 76.985 1.091×
bf16 1024 4096 1 9.775 8.608 1.136×
bf16 1024 4096 4 9.830 8.533 1.152×
bf16 1024 4096 16 9.828 8.551 1.149×
bf16 1024 4096 64 10.000 8.677 1.152×
bf16 1024 4096 128 10.306 9.044 1.140×
bf16 1024 4096 256 12.074 10.907 1.107×
bf16 1024 4096 384 14.480 13.429 1.078×
bf16 1024 4096 512 20.800 18.328 1.135×
bf16 1024 8192 1 10.337 8.904 1.161×
bf16 1024 8192 4 10.193 8.667 1.176×
bf16 1024 8192 16 10.331 8.799 1.174×
bf16 1024 8192 64 10.556 9.082 1.162×
bf16 1024 8192 128 10.829 9.398 1.152×
bf16 1024 8192 256 13.035 11.629 1.121×
bf16 1024 8192 384 15.409 14.372 1.072×
bf16 1024 8192 512 22.325 19.542 1.142×
bf16 1024 16384 1 10.812 9.153 1.181×
bf16 1024 16384 4 10.755 8.939 1.203×
bf16 1024 16384 16 10.984 9.111 1.206×
bf16 1024 16384 64 11.285 9.452 1.194×
bf16 1024 16384 128 11.785 9.989 1.180×
bf16 1024 16384 256 14.385 13.011 1.106×
bf16 1024 16384 384 17.733 16.391 1.082×
bf16 1024 16384 512 25.019 21.384 1.170×
bf16 1024 32768 1 12.933 11.188 1.156×
bf16 1024 32768 4 12.897 11.115 1.160×
bf16 1024 32768 16 13.046 11.205 1.164×
bf16 1024 32768 64 13.671 11.807 1.158×
bf16 1024 32768 128 14.405 12.675 1.136×
bf16 1024 32768 256 18.854 17.477 1.079×
bf16 1024 32768 384 23.934 22.701 1.054×
bf16 1024 32768 512 32.615 28.803 1.132×
bf16 1024 65536 1 17.643 13.278 1.329×
bf16 1024 65536 4 17.749 13.226 1.342×
bf16 1024 65536 16 17.878 13.423 1.332×
bf16 1024 65536 64 18.728 14.367 1.304×
bf16 1024 65536 128 19.713 15.500 1.272×
bf16 1024 65536 256 25.227 23.752 1.062×
bf16 1024 65536 384 33.175 31.683 1.047×
bf16 1024 65536 512 46.804 41.079 1.139×
bf16 1024 131072 1 26.180 18.954 1.381×
bf16 1024 131072 4 26.286 18.881 1.392×
bf16 1024 131072 16 26.632 19.268 1.382×
bf16 1024 131072 64 27.841 20.611 1.351×
bf16 1024 131072 128 29.601 22.686 1.305×
bf16 1024 131072 256 40.856 38.096 1.072×
bf16 1024 131072 384 58.818 54.003 1.089×
bf16 1024 131072 512 90.337 81.632 1.107×
bf16 2048 8192 1 13.235 11.386 1.162×
bf16 2048 8192 4 13.182 11.362 1.160×
bf16 2048 8192 16 13.168 11.370 1.158×
bf16 2048 8192 64 13.493 11.771 1.146×
bf16 2048 8192 128 13.834 12.257 1.129×
bf16 2048 8192 256 16.768 14.906 1.125×
bf16 2048 8192 384 27.791 19.359 1.436×
bf16 2048 8192 512 31.080 25.960 1.197×
bf16 2048 16384 1 14.621 12.128 1.206×
bf16 2048 16384 4 14.399 11.916 1.208×
bf16 2048 16384 16 14.429 11.936 1.209×
bf16 2048 16384 64 14.870 12.406 1.199×
bf16 2048 16384 128 15.378 13.211 1.164×
bf16 2048 16384 256 19.240 16.955 1.135×
bf16 2048 16384 384 31.112 22.022 1.413×
bf16 2048 16384 512 35.229 28.913 1.218×
bf16 2048 32768 1 16.132 13.728 1.175×
bf16 2048 32768 4 16.324 13.783 1.184×
bf16 2048 32768 16 16.305 13.784 1.183×
bf16 2048 32768 64 17.013 14.579 1.167×
bf16 2048 32768 128 17.896 15.611 1.146×
bf16 2048 32768 256 23.308 21.595 1.079×
bf16 2048 32768 384 37.145 28.704 1.294×
bf16 2048 32768 512 43.085 36.473 1.181×
bf16 2048 65536 1 20.999 15.977 1.314×
bf16 2048 65536 4 20.960 15.913 1.317×
bf16 2048 65536 16 21.310 16.087 1.325×
bf16 2048 65536 64 22.432 17.059 1.315×
bf16 2048 65536 128 23.841 18.602 1.282×
bf16 2048 65536 256 31.727 30.007 1.057×
bf16 2048 65536 384 51.290 40.321 1.272×
bf16 2048 65536 512 59.452 52.024 1.143×
bf16 2048 131072 1 29.686 21.204 1.400×
bf16 2048 131072 4 30.206 21.195 1.425×
bf16 2048 131072 16 30.606 21.364 1.433×
bf16 2048 131072 64 32.054 23.059 1.390×
bf16 2048 131072 128 34.603 25.495 1.357×
bf16 2048 131072 256 46.719 45.489 1.027×
bf16 2048 131072 384 75.900 63.878 1.188×
bf16 2048 131072 512 87.285 91.249 0.957×
fp16 512 4096 1 9.427 8.414 1.120×
fp16 512 4096 4 9.079 8.099 1.121×
fp16 512 4096 16 9.196 8.222 1.118×
fp16 512 4096 64 9.298 8.374 1.110×
fp16 512 4096 128 9.557 8.604 1.111×
fp16 512 4096 256 11.162 10.307 1.083×
fp16 512 4096 384 13.159 12.608 1.044×
fp16 512 4096 512 19.085 17.397 1.097×
fp16 512 8192 1 10.113 9.229 1.096×
fp16 512 8192 4 9.877 8.991 1.099×
fp16 512 8192 16 9.959 9.016 1.105×
fp16 512 8192 64 10.196 9.277 1.099×
fp16 512 8192 128 10.399 9.453 1.100×
fp16 512 8192 256 12.401 11.803 1.051×
fp16 512 8192 384 14.581 14.135 1.032×
fp16 512 8192 512 20.927 19.847 1.054×
fp16 512 16384 1 9.731 8.854 1.099×
fp16 512 16384 4 9.903 8.959 1.105×
fp16 512 16384 16 9.988 8.994 1.111×
fp16 512 16384 64 10.171 9.296 1.094×
fp16 512 16384 128 10.710 9.870 1.085×
fp16 512 16384 256 13.295 12.556 1.059×
fp16 512 16384 384 16.492 15.670 1.052×
fp16 512 16384 512 22.246 20.990 1.060×
fp16 512 32768 1 12.620 11.611 1.087×
fp16 512 32768 4 12.441 11.374 1.094×
fp16 512 32768 16 12.479 11.453 1.090×
fp16 512 32768 64 13.056 12.117 1.077×
fp16 512 32768 128 13.860 12.816 1.081×
fp16 512 32768 256 18.262 17.210 1.061×
fp16 512 32768 384 23.090 21.934 1.053×
fp16 512 32768 512 30.495 28.700 1.063×
fp16 512 65536 1 16.670 13.534 1.232×
fp16 512 65536 4 16.521 13.227 1.249×
fp16 512 65536 16 16.606 13.376 1.241×
fp16 512 65536 64 17.604 14.300 1.231×
fp16 512 65536 128 18.468 15.108 1.222×
fp16 512 65536 256 23.653 22.437 1.054×
fp16 512 65536 384 30.681 29.431 1.042×
fp16 512 65536 512 43.126 39.733 1.085×
fp16 512 131072 1 24.837 18.362 1.353×
fp16 512 131072 4 24.555 18.160 1.352×
fp16 512 131072 16 24.847 18.534 1.341×
fp16 512 131072 64 26.102 19.683 1.326×
fp16 512 131072 128 27.779 21.277 1.306×
fp16 512 131072 256 38.617 35.754 1.080×
fp16 512 131072 384 56.023 51.297 1.092×
fp16 512 131072 512 83.389 77.725 1.073×
fp16 1024 4096 1 10.103 9.085 1.112×
fp16 1024 4096 4 9.991 8.986 1.112×
fp16 1024 4096 16 9.863 8.962 1.100×
fp16 1024 4096 64 10.123 9.073 1.116×
fp16 1024 4096 128 10.244 9.375 1.093×
fp16 1024 4096 256 12.291 11.696 1.051×
fp16 1024 4096 384 20.044 14.361 1.396×
fp16 1024 4096 512 22.331 19.685 1.134×
fp16 1024 8192 1 10.933 9.663 1.131×
fp16 1024 8192 4 10.705 9.407 1.138×
fp16 1024 8192 16 10.879 9.500 1.145×
fp16 1024 8192 64 11.057 9.765 1.132×
fp16 1024 8192 128 11.288 10.095 1.118×
fp16 1024 8192 256 13.605 12.716 1.070×
fp16 1024 8192 384 22.320 15.899 1.404×
fp16 1024 8192 512 24.801 21.394 1.159×
fp16 1024 16384 1 11.216 9.772 1.148×
fp16 1024 16384 4 11.436 9.705 1.178×
fp16 1024 16384 16 11.479 9.771 1.175×
fp16 1024 16384 64 11.810 10.190 1.159×
fp16 1024 16384 128 12.298 10.816 1.137×
fp16 1024 16384 256 15.065 14.096 1.069×
fp16 1024 16384 384 24.288 17.863 1.360×
fp16 1024 16384 512 27.005 23.548 1.147×
fp16 1024 32768 1 13.006 11.558 1.125×
fp16 1024 32768 4 12.925 11.390 1.135×
fp16 1024 32768 16 13.054 11.680 1.118×
fp16 1024 32768 64 13.781 12.309 1.120×
fp16 1024 32768 128 14.422 13.086 1.102×
fp16 1024 32768 256 18.602 17.974 1.035×
fp16 1024 32768 384 29.377 23.258 1.263×
fp16 1024 32768 512 34.239 29.833 1.148×
fp16 1024 65536 1 17.191 14.328 1.200×
fp16 1024 65536 4 17.279 14.172 1.219×
fp16 1024 65536 16 17.437 14.264 1.222×
fp16 1024 65536 64 18.465 15.335 1.204×
fp16 1024 65536 128 19.231 16.398 1.173×
fp16 1024 65536 256 25.423 24.715 1.029×
fp16 1024 65536 384 42.205 32.882 1.284×
fp16 1024 65536 512 48.393 42.472 1.139×
fp16 1024 131072 1 25.727 19.249 1.337×
fp16 1024 131072 4 25.799 19.301 1.337×
fp16 1024 131072 16 26.193 19.612 1.336×
fp16 1024 131072 64 27.408 20.859 1.314×
fp16 1024 131072 128 29.111 22.779 1.278×
fp16 1024 131072 256 39.654 38.155 1.039×
fp16 1024 131072 384 69.876 54.157 1.290×
fp16 1024 131072 512 74.290 81.459 0.912×
fp16 2048 8192 1 13.181 11.283 1.168×
fp16 2048 8192 4 13.115 11.140 1.177×
fp16 2048 8192 16 13.215 11.212 1.179×
fp16 2048 8192 64 13.489 11.581 1.165×
fp16 2048 8192 128 13.804 12.032 1.147×
fp16 2048 8192 256 16.843 15.046 1.119×
fp16 2048 8192 384 27.651 19.457 1.421×
fp16 2048 8192 512 31.072 25.708 1.209×
fp16 2048 16384 1 14.415 12.012 1.200×
fp16 2048 16384 4 14.103 11.738 1.202×
fp16 2048 16384 16 14.147 11.730 1.206×
fp16 2048 16384 64 14.545 12.187 1.194×
fp16 2048 16384 128 15.143 12.848 1.179×
fp16 2048 16384 256 18.967 16.639 1.140×
fp16 2048 16384 384 30.364 21.724 1.398×
fp16 2048 16384 512 34.795 28.347 1.227×
fp16 2048 32768 1 15.713 13.349 1.177×
fp16 2048 32768 4 15.775 13.308 1.185×
fp16 2048 32768 16 15.896 13.383 1.188×
fp16 2048 32768 64 16.687 14.209 1.174×
fp16 2048 32768 128 17.568 15.313 1.147×
fp16 2048 32768 256 22.948 21.236 1.081×
fp16 2048 32768 384 36.303 27.932 1.300×
fp16 2048 32768 512 42.271 35.613 1.187×
fp16 2048 65536 1 20.410 15.751 1.296×
fp16 2048 65536 4 20.503 15.682 1.307×
fp16 2048 65536 16 20.758 15.878 1.307×
fp16 2048 65536 64 21.866 16.894 1.294×
fp16 2048 65536 128 23.141 18.264 1.267×
fp16 2048 65536 256 31.026 29.232 1.061×
fp16 2048 65536 384 50.030 39.184 1.277×
fp16 2048 65536 512 58.109 50.823 1.143×
fp16 2048 131072 1 29.040 20.987 1.384×
fp16 2048 131072 4 29.501 21.036 1.402×
fp16 2048 131072 16 29.777 21.232 1.402×
fp16 2048 131072 64 31.325 22.757 1.376×
fp16 2048 131072 128 33.972 25.220 1.347×
fp16 2048 131072 256 45.912 44.400 1.034×
fp16 2048 131072 384 74.600 62.395 1.196×
fp16 2048 131072 512 85.402 91.581 0.933×
fp32 512 4096 1 10.725 9.773 1.097×
fp32 512 4096 4 10.298 9.379 1.098×
fp32 512 4096 16 10.425 9.421 1.107×
fp32 512 4096 64 10.712 9.616 1.114×
fp32 512 4096 128 10.899 9.961 1.094×
fp32 512 4096 256 12.933 11.847 1.092×
fp32 512 4096 384 15.377 19.781 0.777×
fp32 512 4096 512 22.194 21.072 1.053×
fp32 512 8192 1 10.511 9.530 1.103×
fp32 512 8192 4 10.221 9.187 1.112×
fp32 512 8192 16 10.364 9.285 1.116×
fp32 512 8192 64 10.687 9.603 1.113×
fp32 512 8192 128 11.155 10.095 1.105×
fp32 512 8192 256 13.324 12.431 1.072×
fp32 512 8192 384 16.202 15.978 1.014×
fp32 512 8192 512 22.955 21.855 1.050×
fp32 512 16384 1 11.360 9.926 1.144×
fp32 512 16384 4 11.462 10.187 1.125×
fp32 512 16384 16 11.633 10.192 1.141×
fp32 512 16384 64 12.224 10.891 1.122×
fp32 512 16384 128 12.783 11.493 1.112×
fp32 512 16384 256 16.020 14.979 1.069×
fp32 512 16384 384 19.924 19.500 1.022×
fp32 512 16384 512 28.046 26.341 1.065×
fp32 512 32768 1 14.206 11.591 1.226×
fp32 512 32768 4 14.004 11.349 1.234×
fp32 512 32768 16 14.109 11.397 1.238×
fp32 512 32768 64 15.053 12.355 1.218×
fp32 512 32768 128 16.016 13.559 1.181×
fp32 512 32768 256 20.980 18.989 1.105×
fp32 512 32768 384 27.665 26.116 1.059×
fp32 512 32768 512 40.200 34.721 1.158×
fp32 512 65536 1 18.997 14.886 1.276×
fp32 512 65536 4 18.948 14.771 1.283×
fp32 512 65536 16 19.249 15.071 1.277×
fp32 512 65536 64 20.633 16.792 1.229×
fp32 512 65536 128 22.582 19.298 1.170×
fp32 512 65536 256 32.200 29.458 1.093×
fp32 512 65536 384 48.550 50.128 0.969×
fp32 512 65536 512 72.610 54.504 1.332×
fp32 512 131072 1 29.030 19.726 1.472×
fp32 512 131072 4 28.804 19.475 1.479×
fp32 512 131072 16 29.361 19.986 1.469×
fp32 512 131072 64 31.751 22.996 1.381×
fp32 512 131072 128 39.928 29.369 1.360×
fp32 512 131072 256 64.303 56.786 1.132×
fp32 512 131072 384 85.660 88.940 0.963×
fp32 512 131072 512 128.305 101.348 1.266×
fp32 1024 4096 1 10.285 9.480 1.085×
fp32 1024 4096 4 10.362 9.250 1.120×
fp32 1024 4096 16 10.385 9.312 1.115×
fp32 1024 4096 64 10.577 9.444 1.120×
fp32 1024 4096 128 10.846 9.837 1.103×
fp32 1024 4096 256 12.904 11.862 1.088×
fp32 1024 4096 384 21.153 19.523 1.083×
fp32 1024 4096 512 25.843 21.117 1.224×
fp32 1024 8192 1 11.573 10.244 1.130×
fp32 1024 8192 4 11.514 10.000 1.151×
fp32 1024 8192 16 11.672 10.184 1.146×
fp32 1024 8192 64 12.040 10.371 1.161×
fp32 1024 8192 128 12.502 11.112 1.125×
fp32 1024 8192 256 15.078 13.712 1.100×
fp32 1024 8192 384 24.608 18.033 1.365×
fp32 1024 8192 512 27.144 24.343 1.115×
fp32 1024 16384 1 12.615 10.722 1.177×
fp32 1024 16384 4 12.704 10.584 1.200×
fp32 1024 16384 16 12.871 10.740 1.198×
fp32 1024 16384 64 13.357 11.444 1.167×
fp32 1024 16384 128 13.967 12.182 1.146×
fp32 1024 16384 256 17.413 15.896 1.095×
fp32 1024 16384 384 28.282 21.190 1.335×
fp32 1024 16384 512 31.989 28.514 1.122×
fp32 1024 32768 1 14.820 12.098 1.225×
fp32 1024 32768 4 14.845 11.902 1.247×
fp32 1024 32768 16 14.980 12.121 1.236×
fp32 1024 32768 64 15.895 13.093 1.214×
fp32 1024 32768 128 16.919 14.396 1.175×
fp32 1024 32768 256 22.141 20.566 1.077×
fp32 1024 32768 384 37.389 28.947 1.292×
fp32 1024 32768 512 42.185 37.447 1.127×
fp32 1024 65536 1 21.355 16.962 1.259×
fp32 1024 65536 4 21.437 16.834 1.273×
fp32 1024 65536 16 21.894 17.170 1.275×
fp32 1024 65536 64 23.276 18.728 1.243×
fp32 1024 65536 128 25.271 21.431 1.179×
fp32 1024 65536 256 35.442 32.272 1.098×
fp32 1024 65536 384 61.950 54.236 1.142×
fp32 1024 65536 512 65.747 59.239 1.110×
fp32 1024 131072 1 30.385 20.772 1.463×
fp32 1024 131072 4 30.355 20.829 1.457×
fp32 1024 131072 16 30.991 21.299 1.455×
fp32 1024 131072 64 33.447 24.186 1.383×
fp32 1024 131072 128 40.990 30.957 1.324×
fp32 1024 131072 256 66.424 59.249 1.121×
fp32 1024 131072 384 104.031 93.521 1.112×
fp32 1024 131072 512 118.808 105.624 1.125×
fp32 2048 8192 1 14.467 11.986 1.207×
fp32 2048 8192 4 14.491 11.901 1.218×
fp32 2048 8192 16 14.560 11.888 1.225×
fp32 2048 8192 64 14.940 12.422 1.203×
fp32 2048 8192 128 15.517 13.085 1.186×
fp32 2048 8192 256 20.226 16.681 1.213×
fp32 2048 8192 384 25.362 23.694 1.070×
fp32 2048 8192 512 35.658 30.233 1.179×
fp32 2048 16384 1 17.666 13.802 1.280×
fp32 2048 16384 4 17.472 13.555 1.289×
fp32 2048 16384 16 17.689 13.597 1.301×
fp32 2048 16384 64 18.398 14.489 1.270×
fp32 2048 16384 128 19.076 15.366 1.241×
fp32 2048 16384 256 25.399 20.309 1.251×
fp32 2048 16384 384 32.050 30.066 1.066×
fp32 2048 16384 512 45.140 37.260 1.211×
fp32 2048 32768 1 19.858 14.053 1.413×
fp32 2048 32768 4 19.996 14.046 1.424×
fp32 2048 32768 16 20.173 14.196 1.421×
fp32 2048 32768 64 21.256 15.310 1.388×
fp32 2048 32768 128 22.421 16.844 1.331×
fp32 2048 32768 256 30.391 24.117 1.260×
fp32 2048 32768 384 39.745 39.347 1.010×
fp32 2048 32768 512 57.592 44.666 1.289×
fp32 2048 65536 1 26.165 17.796 1.470×
fp32 2048 65536 4 26.315 17.771 1.481×
fp32 2048 65536 16 26.811 18.089 1.482×
fp32 2048 65536 64 28.333 19.702 1.438×
fp32 2048 65536 128 30.401 22.772 1.335×
fp32 2048 65536 256 43.428 35.555 1.221×
fp32 2048 65536 384 62.408 54.473 1.146×
fp32 2048 65536 512 97.401 64.192 1.517×
fp32 2048 131072 1 35.712 22.213 1.608×
fp32 2048 131072 4 35.860 22.395 1.601×
fp32 2048 131072 16 36.432 22.746 1.602×
fp32 2048 131072 64 38.993 25.775 1.513×
fp32 2048 131072 128 45.728 32.536 1.405×
fp32 2048 131072 256 76.719 64.098 1.197×
fp32 2048 131072 384 100.864 101.833 0.990×
fp32 2048 131072 512 149.382 113.273 1.319×
  1. For different dsl optimizations performance on all sync data:

Per-optimization class gain (cumulative)

v1: t=512, v=128,enable_unroll_4=false, enable_phase3_unroll=False, min_block=0. use_256bit=False num_threads=512, return_output_values=True
v2: t=512, v=128,enable_unroll_4=true, enable_phase3_unroll=true, min_block=0. use_256bit=False num_threads=512, return_output_values=True
v3: t=512, v=128,enable_unroll_4=true, enable_phase3_unroll=true, min_block=None. use_256bit=False num_threads=512, return_output_values=True
v4: t=512, v=128,enable_unroll_4=true, enable_phase3_unroll=true, min_block=None. use_256bit=None num_threads=None, return_output_values=True
v5: t=512, v=128,enable_unroll_4=true, enable_phase3_unroll=true, min_block=None. use_256bit=None num_threads=None, return_output_values=False
Version Added (cumulative) Median sp sp<1 Per-class delta
v1 Parity port 0.964× 230/408
v2 + 4-way vec unroll 1.079× 29/408 +11.5 pp (unroll)
v3 + min_blocks_per_mp auto 1.111× 9/408 +3.2 pp (mb heuristic)
v4 + T (num_threads) / V (vec_width) auto 1.132× 7/408 +2.1 pp (T+V heuristic)
v5 + return_output_values=False 1.152× 7/408 +2.0 pp (skip STG.value)
image

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)

  • If PR introduces API changes, an appropriate PR label is added - either api-compatible or api-breaking. For api-breaking, include BREAKING in the PR title.

  • 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.

limin2021 added 11 commits May 20, 2026 08:29
Signed-off-by: Mindy Li <11663212+limin2021@users.noreply.github.com>
…invariant

- Replace from_dlpack/static-shape compile with make_fake_compact_tensor +
  sym_int for batch/num_tokens dims, keeping (dtype, top_k, next_n) as the
  cache key. Reduces unique compile entries from 810 to 27 across the bench
  sweep; correctness verified (no OOB writes from cache reuse with wrong
  shape) via 288-config pytest + cross-impl A/B match.

- Fix test_gvr_topk_decode: (1) pre_idx_count now uses top_k (matches CUDA
  dispatch precondition preIdxCount == topK at heuristic_topk.cuh:810);
  (2) tie-aware reference now masks logits to per-row effective_len
  = seq_len - next_n + 1, avoiding false negatives when next_n > 1 makes
  the kernel skip the last next_n-1 columns.

Signed-off-by: Mindy Li <11663212+limin2021@users.noreply.github.com>
…imit / mask guard)

Four small mechanical alignments — each isolated, removes only redundant
work the CUDA reference does not do. Correctness verified: 288/288 in
test_gvr_topk_decode.py. Perf delta within measurement noise (~0.15us
estimated, 21us baseline DSL — under the ~0.5us spread floor) but the
changes match heuristic_topk.cuh semantics 1:1 and pave the way for
later batches.

- block_count_ge: drop the trailing barrier (gvr_topk_decode.py:422 ->
  removed). CUDA blockCountGE (heuristic_topk.cuh:441) returns without
  a sync because callers already insert their own __syncthreads after
  their tid==0 post-processing. The previous DSL trailing barrier was
  redundant (tid==0 reads its own write in-thread, no sync needed).

- Phase 4 snap_limit: change from cand_count>128 ? cand_count/4 : 32
  to cand_count (matches heuristic_topk.cuh:985). The older bound
  silently accepted a non-converged threshold in ~0.09 % of adversarial
  distributions; correctness improvement only, common case still
  converges in 1-3 iters.

- Phase 4 block_min/max: every thread now recomputes block_min/max from
  the warp-staged smem slots into local registers (matches
  heuristic_topk.cuh:891-898). Replaces the prior `tid==0 writes
  s_thr[1]/s_thr[2] then broadcast via __syncthreads` pattern, saving
  one block barrier in Phase 4.

- Phase 4 Pass 1/Pass 2 writeback: wrap popc + atomicAdd + shuffle in
  `if mask != 0` warp-uniform guard (mirrors heuristic_topk.cuh:1020,
  1045). Skips the atomic round-trip when no lane in the warp emits,
  most impactful for Pass 2 where only K-th-rank ties emit.

Signed-off-by: Mindy Li <11663212+limin2021@users.noreply.github.com>
…le + early break)

Convert the Phase 2 secant refinement loop and the Phase 3 retry-shrink
loop from Python-unrolled `for in range(N)` (every body wrapped in an
`if not done:` guard) to runtime `while` with the convergence condition
in the loop predicate. This matches CUDA's pattern at heuristic_topk.cuh:
683 (Phase 2) and :769 (Phase 3 retry).

Previously, after the kernel converged at iteration k, the remaining
N-k unrolled bodies still each issued an LDS+ICMP+branch guard. With
secant typically converging at iter 3 of 15 and retry-shrink usually 0
of 10, this saved ~12 + ~10 = ~22 wasted guard sites per kernel call.

Tradeoff: lose Python-time const-fold of `if it == 0: f = min(f, 0.5)`,
which now becomes a runtime compare. CUDA does the same runtime compare
(heuristic_topk.cuh:698-699), so this is alignment not regression.

Measured impact (median config bf16 K=1024 N=32768 BS=1 next_n=2,
same-process A/B vs CUDA GVR, 5 reps alternating order):
  DSL_us  21.01 -> 20.28  (-0.73 us, -3.5%)
  C/G     0.869 -> 0.903  (+3.4 percentage points)
Above the ~1.5% bench_kineto spread floor. 288/288 tests pass.

Signed-off-by: Mindy Li <11663212+limin2021@users.noreply.github.com>
Two SASS-alignment changes verified against the CUDA reference at the
median config (bf16 K=1024 N=32768 BS=1 next_n=2):

1. cute.make_ptr(..., cute.AddressSpace.gmem, ...) at the two 128-bit
   vec-load sites in block_count_ge and phase3_collect_candidates.
   Default AddressSpace.generic lowered to SASS LD.E.128; explicit gmem
   hint flips to LDG.E.128 (matches CUDA __ldg path, minus .CONSTANT
   which still requires CopyG2ROp+invariant).

2. phase1_preidx_stats: replace the runtime `while i < pre_idx_count`
   strided loop with `range_constexpr(pre_idx_count // num_threads)`.
   pre_idx.shape[1] is a compile-time constant (top_k baked into JIT
   cache key); supported top_k in {512, 1024, 2048} are all multiples
   of num_threads (512), so n_iters ∈ {1, 2, 4} unrolls cleanly. cute
   emits straight-line code (no BRA / ISETP / counter update) and
   issues both preIdx LDG.E and input LDG.E.U16 back-to-back, enabling
   LSU pipelining (in flight ILP). Mirrors what nvcc/ptxas does for
   the equivalent CUDA loop via auto-partial-unroll.

Bench (same-process A/B, 5 repeats × 100 iters, kineto + L2 flush):
   Before: C/G = 0.903 (DSL 10.7% slow)  -- post-Batch 2 baseline
   After:  C/G = 0.922 (DSL  8.5% slow)
   Δ = +1.9pp

Resource use after changes:
   regs/thread:  34 -> 39  (still 3 blocks/SM, occupancy unchanged 75%)
   dynamic smem: unchanged (~44 KB)
   total SASS instructions: 2935 -> 2944 (codegen ripple, mostly
   FMNMX3 +6; loop overhead ISETP/BRA -6/-2/-3 offset by +25 IMAD)

Signed-off-by: Mindy Li <11663212+limin2021@users.noreply.github.com>
…tail)

Replaces the runtime `while i + (vec_w - 1) < N` vec loop in
block_count_ge with a 4-way unrolled fast path + 1-way tail. The fast
path issues 4 independent LDG.E.128 per round (separate fragments so
cute schedules them concurrently), mirroring what nvcc/ptxas does for
the equivalent CUDA loop via auto-partial-unroll.

SASS verification at median config (bf16 K=1024 N=32768 BS=1 nn=2):
- 4 LDG.E.128 per inline at addresses base / base+0x2000 / +0x4000 /
  +0x6000 — exact match to CUDA's LDG.E.128.CONSTANT pattern (minus
  the CONSTANT cache hint, which still requires CopyG2ROp+invariant).
- Total LDG.E.128 count: 5 -> 21 (4 inlines * 4 + 4 tails + 1 phase3).
- Cute software-pipelines: 3 LDGs issued back-to-back, then consume of
  iter 0 starts while iter 3's LDG is issued in parallel. All 4 are
  in flight before HBM responds (latency ~600 cy >> 23 inst slots).

Resource impact:
- Regs/thread: 39 -> 39  (cute reuses fragment regs across loop body;
  Phase 4 likely remains the kernel-wide peak)
- Dynamic smem: unchanged (~44 KB)
- Static SASS size: 2944 -> 3672 inst (+25%)  -- code bloat acceptable,
  well within icache; Block Limit Reg = 3 unchanged at occupancy=75%.

Bench results (kineto, L2 flush, n_iters=30):

Median config (bf16 K=1024 N=32768 BS=1 nn=2), same-process A/B:
   Before this commit:  C/G = 0.922  (DSL  8.5% slow)
   After this commit:   C/G = 0.976  (DSL  2.4% slow)
   Delta:               +5.4pp

Full sweep (804 configs = 3 dtype * 3 top_k * 6 N * 5 BS * 3 next_n):
   Median  C/G: 0.860 (baseline post-Batch-2) -> 0.988 (now)
   Geomean C/G:           0.869 -> 0.999  (parity with CUDA)
   DSL faster:             17% ->  46%
   Within 5%:              13% ->  34%
   Within 10%:             28% ->  55%

By dtype: bf16 1.000, fp16 1.022, fp32 0.951 (fp32 has slightly less
runway since vec_w=4 vs 8 for bf16/fp16).
By N: gap remains at large N (>=64K: median ~0.87-0.90), where the
LSU-pipelining win is already saturated and other phases dominate.

The single-config worst slowdowns observed (C/G ~0.4) are concentrated
in nn=3 + small-mid N (4-32K) + BS>=64 configs whose CUDA-side numbers
also moved 5-15x between runs -- short-runtime measurement noise, not
real regressions.

This commit completes the SASS-alignment campaign objective (gap < 5%
on median config). Remaining ~10-13% at very large N is deferred.

Signed-off-by: Mindy Li <11663212+limin2021@users.noreply.github.com>
Three new switches gate the block_count_ge vec-load fast path:

  enable_unroll_4 (default True): 4-way unrolled fast path
  enable_unroll_2 (default by dtype): 2-way cascade between fast and tail
  use_strided_layout (default by dtype): True → single make_ptr +
      (UNROLL, vec_w) strided layout (cute emits 4 LDG.E.128 sharing
      base reg with +0x2000/+0x4000/+0x6000 imm offsets, matching the
      CUDA SASS pattern). False → 4 separate make_ptr calls (matches
      the prior b459a8f commit style with 4 independent base regs).

Dtype-aware defaults (validated via per-config A/B testing on B200):

  bf16 / fp16: enable_unroll_2=True, use_strided_layout=True
      Strided cascade gives clean wins: cascade flips DSL from CUDA
      parity to consistently faster on small-N where the 4-way fast
      path doesn't fully cover N, and the medium 2-way path keeps two
      LDG.E.128 in flight. Strided layout keeps the SASS shared-base
      pattern that nvcc/ptxas auto-partial-unroll also produces.

  fp32: enable_unroll_2=False, use_strided_layout=False
      For fp32 (vec_w=4) the strided layout pushes regs 38 → 40 and
      regresses fp32 large-grid configs by 30-60pp (worst observed:
      K=1024 BS=128 nn=2 → 0.753 vs 1.364 with separate-ptrs). The
      cascade similarly hurts in 12% of fp32 configs. Separate-ptrs
      4-way unroll alone is the sweet spot.

Cache key includes the three switches so different settings produce
separate compiled kernels.

Full sweep results (804 configs, n_iters=30 kineto, L2 flush):

                       baseline   cascade-all   dtype-policy
  Median C/G:           0.988      1.011        1.006
  Geomean C/G:          0.999      1.047        1.038
  DSL faster %:           46%        54%          52%
  Within 10%:             55%        65%          65%

By dtype:
  bf16:  1.000 -> 1.043 (cascade wins preserved)
  fp16:  1.022 -> 1.038
  fp32:  0.951 -> 0.960 (anom K=1024 BS=128 fixed: 0.610 -> 1.038)

By N (the original "large-N gap"):
  N=8192:    1.097 -> 1.172 (+7pp, cascade hides medium-path remainder)
  N=65536:   0.897 -> 0.928 (+3pp)
  N=131072:  0.866 -> 0.923 (+6pp)

Remaining slow configs (fp32 K=2048 + BS>=64) were already <0.7 in
the baseline -- this commit doesn't introduce new regressions there.

Signed-off-by: Mindy Li <11663212+limin2021@users.noreply.github.com>
Adds two new switches to the DSL GVR kernel:

  enable_phase3_unroll (default True): master gate for phase3_collect
      unrolling. When ON, the inner enable_unroll_4 / enable_unroll_2
      switches independently control 4-way fast and 2-way medium paths
      in phase3 (same semantics as block_count_ge). When OFF, only the
      tail 1-way loop runs.

  use_constant_hint (default False): True → CopyG2ROp(invariant=True)
      → SASS LDG.E.*.CONSTANT (read-only data cache, matches CUDA
      __ldg). Default False because cute's invariant lowering triggers
      aggressive rematerialization in LLVM/NVPTX (+272 inst, 4 spills,
      net -7pp geomean), outweighing the cache hint benefit.

Phase3_collect is now a 3-tier cascade (4-way fast + 2-way medium +
1-way tail) mirroring block_count_ge. The cascade gives:
  N>=65K:  +5-7%  (large-N main path, LSU pipelining wins)
  N<=32K:  -1-3%  (unroll setup overhead exceeds benefit at small N)
  Median geomean: +2.2pp from phase3 unroll alone

Resource analysis (bf16/fp16/fp32 x phase3 ON/OFF):
  REG/thread:
    bf16: 39 -> 39 (no change, cute reuses fragments)
    fp16: 39 -> 39 (no change)
    fp32: 38 -> 40 (+2, separate-ptrs path)
  Static SASS:
    bf16: 3936 -> 4368 (+11%)
    fp16: 4096 -> 4512 (+10%)
    fp32: 3368 -> 3480 (+3%)
  Theoretical Occupancy: 75% all configs (smem-limited to 3 blocks/SM,
    binding limit unaffected by phase3 unroll). Phase3 unroll has
    *zero* occupancy cost.

Wrapper signature gains both switches; cache key includes them so
different settings produce separate compiled kernels. A small helper
method _make_load_copy_atom() factors out the CopyG2ROp/Universal
selection to avoid Python if-else NameError inside @cute.jit scope.

Signed-off-by: Mindy Li <11663212+limin2021@users.noreply.github.com>
…uristic

Phase 2 (block_count_ge) and Phase 3 (phase3_collect_candidates) replace
the manual `while + range_constexpr(UNROLL)` fast/medium-cascade unrolling
with a single `for k in cutlass.range(big_iters, unroll=4)` loop. LLVM's
loop unroll pass + GVN/CSE folds the 4 derived vec loads into the
CUDA-style shared base + immediate offsets pattern, emitting 4 back-to-back
LDG.E.128 [base+0x2000/0x4000/0x6000] instructions.

Add `min_blocks_per_mp` field on `GvrTopKKernel` and a 3-tier shape-aware
heuristic in the host wrapper:
  * n_vec_iters < 4         -> 0 (no launch_bounds, natural ptxas allocation)
  * num_rows <= 148 (B200 SMs) -> 1 (allow many regs, 4xLDG fold survives)
  * else                       -> 3 (keep 3 CTA/SM occupancy, ~42 reg cap)

The heuristic lifts fp32 K=512 large-N out of its regression zone (worst
case C/G 0.62 -> 1.07 at K=512 N=131072 BS=16 nn=2). Cache key extended so
each min_blocks value gets its own compiled kernel.

Random sweep vs phase3_unroll baseline (804 configs):
  geomean 1.060 -> 1.149, faster%-than-CUDA 62% -> 92%, losses 304 -> 63.

CUDA Graph: heuristic reads `logits.shape` (host int, no GPU sync) so
capture is safe; per-graph capture selects the right kernel per shape.
For dynamic-shape single-graph use, caller can pin `min_blocks_per_mp=3`
to disable the heuristic.

Signed-off-by: Mindy Li <11663212+limin2021@users.noreply.github.com>
Three new kernel knobs on GvrTopKKernel + gvr_topk_decode host wrapper:

  * use_256bit_load (default False): emit LDG.E.256 (8 fp32 / 16 bf16-fp16
    elements per LDG) instead of LDG.E.128. Address alignment hint is
    raised from 16 to 32 bytes. Phase 2/3 unroll factor is dtype-aware:
    fp32 keeps unroll=4 (no cvt-to-fp32 overhead); bf16/fp16 drops to
    unroll=2 to limit the cvt register pressure that otherwise spills
    under min_blocks=3.

  * num_threads_per_block (default 512): configurable per-instance.
    BLOCK_SIZE / WARP_SIZE / NUM_WARPS are moved from module-level to
    GvrTopKKernel instance attrs (self.WARP_SIZE, self.num_threads,
    self.num_warps). Phase 1 preIdx loop gains an else branch for the
    K < num_threads case (e.g. num_threads=1024 with K=512): only the
    first K threads load a preIdx, others keep reduction-identity
    values which the warp/block reduces naturally absorb.

  * vec_bits / vec_align_bytes derived from use_256bit_load; cache key
    extended with use_256bit_load + num_threads_per_block.

Heuristic uses the resolved num_threads_per_block (not a hardcoded 512)
when computing n_vec_iters.

Tests parametrize use_256bit_load and num_threads_per_block; pytest
runs 288/288 PASS at use_256bit_load=True and at num_threads=1024.

Synth bench on BS<=128:
  - 128-bit + heuristic baseline: gm=1.131, faster%=99%, lose=9
  - 256-bit + heuristic        : gm=1.121 (fp32 wins +3pp; bf16/fp16
    flat-to-negative due to cvt-to-fp32 reg pressure spills under mb=3)

Random sweep on BS up to 128: 256-bit shows niche win on
(fp32, num_rows<=148, large N); should be opt-in.

Synth data generator (multi-BS) and bench script env vars
(DSL_USE_256BIT/DSL_MIN_BLOCKS/DSL_NUM_THREADS) live in the gvr-topk-opt
workspace and are not part of this commit.

Signed-off-by: Mindy Li <11663212+limin2021@users.noreply.github.com>
…nobs

Add a fourth perf knob `enable_warp_parallel_reduce` to GvrTopKKernel +
gvr_topk_decode and replace the four `tid==0` serial loops over
num_warps slots with warp-parallel reduce/scan in warp 0:

  * Phase 1 block aggregate (4-way reduce):
        min/max/sum_f32/sum_i32 -> 4x warp_reduce in warp 0.
  * Phase 2 / blockCountGE total (1-way reduce):
        sum_i32 -> warp_reduce_sum_i32.
  * Phase 3 collect block prefix sum (exclusive scan):
        Hillis-Steele inclusive scan via block_scan.warp_scan, then
        exclusive = inclusive - val; total = inclusive at last lane.
  * Phase 2 secant aggregate (3-way reduce):
        packed sum_i32 + min_f32 + max_f32, with bound update on lane 0.

Default is False since at num_threads=512 (num_warps=16) the per-warp
ILP loss exceeds the serial-loop savings (~2pp regression on synth).
At num_threads=1024 (num_warps=32) the switch is essential -- without
it 1024 regresses vs baseline (gm 1.131 -> 1.123); with it 1024 wins
(gm -> 1.154 on synth BS<=128). Pair as
`enable_warp_parallel_reduce = (num_threads_per_block >= 1024)`.

Phase 1 also gains an `active_preidx_warps` optimization: when
`pre_idx_count < num_threads` (e.g. K=512 with num_threads=1024) only
the first ceil(K/32) warps have real data, so the warp_reduce + smem
write step is now gated to those warps. Saves ~30 cy/dummy-warp; the
full barrier afterwards still keeps all 1024 threads aligned for
Phase 2. The constexpr is clamped to num_warps so the K>num_threads
case (K=2048 with num_threads=512) doesn't index past the smem
buffers, and the same value drives both the warp_reduce gate and the
Site-1 block aggregate's smem read range.

Remove two now-dead switches:
  * `enable_unroll_2` -- only referenced in the commented-out manual
    2-way medium path that the `cutlass.range(unroll=4)` rewrite
    replaced.
  * `use_strided_layout` -- only referenced in the commented-out manual
    4-way strided-layout path, also replaced.

Cache key drops the two dead entries and gains
`enable_warp_parallel_reduce`. The cleanup is a no-op functionally
(the dead values were ignored by the active code paths) but removes
two cache-bucket dimensions.

Test parametrize expanded to 4-way matrix:
  next_n in {1, 2} (was {1, 2, 3, 4} -- trimmed to keep walltime)
  use_256bit_load in {False, True}
  num_threads_per_block in {512, 1024}
  enable_warp_parallel_reduce in {False, True}
1152 / 1152 PASS in 20:22.

Synth bench (BS<=128, threads=512 baseline -> threads=1024+wpON):
  geomean 1.131 -> 1.154  (+2.3pp)
  fp32 geomean 1.127 -> 1.177  (+5.0pp; up to +28pp at fp32 K=2048
                                N=131072 -- 1.50x vs CUDA)

Signed-off-by: Mindy Li <11663212+limin2021@users.noreply.github.com>
@limin2021
limin2021 requested a review from a team as a code owner May 27, 2026 00:39
@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR adds test coverage and public API exposure for the Blackwell CuTE gvr_topk_decode kernel. It exports kernel components through the public top_k module and provides a comprehensive parameterized CUDA correctness test with tie-aware validation logic.

Changes

GVR Top-K Kernel Export and Testing

Layer / File(s) Summary
Public API export
tensorrt_llm/_torch/cute_dsl_kernels/blackwell/top_k/__init__.py
GvrParams, GvrTopKKernel, and gvr_topk_decode are now imported and added to __all__, making these symbols available to external consumers of the top_k package.
Correctness test with tie-aware validation
tensorrt_llm/_torch/cute_dsl_kernels/blackwell/top_k/test_gvr_topk_decode.py
Added _make_inputs helper to generate deterministic single-row test tensors, _tie_aware_correct helper to validate kernel output using masked torch.topk reference, and parameterized test_gvr_topk_decode_correctness covering multiple dtypes, vocabulary sizes, K values, seeds, next_n configurations, and kernel launch flags, with CUDA synchronization and detailed failure reporting.

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and specifically describes the main change: adding a CuTE DSL GVR Top-K decode kernel implementation with associated test suite.
Description check ✅ Passed PR description is mostly complete with clear explanation of changes, test coverage section, and performance data, though the template structure shows author filled in substantive details post-template.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

🤖 Prompt for all review comments with 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.

Inline comments:
In
`@tensorrt_llm/_torch/cute_dsl_kernels/blackwell/top_k/test_gvr_topk_decode.py`:
- Around line 29-45: The test picks argmax_idx over the full N, which breaks the
pre_idx[...,0] invariant when the kernel only scans the first (N - next_n + 1)
columns; update _make_inputs to compute an effective_scan = N - next_n + 1 (or
accept next_n as a parameter) and compute argmax_idx = int(logits[0,
:effective_scan].argmax().item()) so pre_idx_list[0] is in-range, then build
pre_idx as before; apply the same change to the other occurrence (the second
_make_inputs usage around the later test).
- Line 21: Remove the unused "from typing import Tuple" import and replace the
two occurrences of typing.Tuple[...] return annotations in this test module with
the native Python 3.10+ generic syntax tuple[...] (e.g., change "Tuple[int,
str]" to "tuple[int, str]") in the two functions in
tensorrt_llm/_torch/cute_dsl_kernels/blackwell/top_k/test_gvr_topk_decode.py so
the file uses built-in tuple typing and no longer imports typing.Tuple.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 031d6484-acc6-4f3e-b7b3-e6defb77c52c

📥 Commits

Reviewing files that changed from the base of the PR and between c7e7fc5 and 882c767.

📒 Files selected for processing (3)
  • tensorrt_llm/_torch/cute_dsl_kernels/blackwell/top_k/__init__.py
  • tensorrt_llm/_torch/cute_dsl_kernels/blackwell/top_k/gvr_topk_decode.py
  • tensorrt_llm/_torch/cute_dsl_kernels/blackwell/top_k/test_gvr_topk_decode.py

Comment thread tensorrt_llm/_torch/cute_dsl_kernels/blackwell/top_k/test_gvr_topk_decode.py Outdated
Comment thread tensorrt_llm/_torch/cute_dsl_kernels/blackwell/top_k/test_gvr_topk_decode.py Outdated
limin2021 added 9 commits May 27, 2026 01:24
Mirrors the kFTarget=kK alignment for K=512/1024 (all dtypes) from CUDA
PR NVIDIA#14413 on the DSL GVR Top-K kernel so the DSL Phase-2 secant
behavior matches the new CUDA reference. Old pre-NVIDIA#14413 values kept as
inline comments for easy rollback.

Verified: 768/768 pytest configs pass for K=512/1024 across all dtypes,
N, next_n, use_256bit_load, num_threads_per_block, and
enable_warp_parallel_reduce.

Signed-off-by: Mindy Li <11663212+limin2021@users.noreply.github.com>
Move module-level MAX_REFINE_ITERS / FLT_MAX / NEG_FLT_MAX into
instance attributes so all kernel-wide knobs live in one place.
Inline NUM_BINS_DEFAULT (2048) directly into the GvrParams table
since it was only used in three K=2048 entries. Drop dead
MAX_CANDIDATES.

Pure refactor — values, control flow, and DSL IR are unchanged.
Also removes the previously-commented-out A/B layout/unroll dead
code in block_count_ge.

Signed-off-by: Mindy Li <11663212+limin2021@users.noreply.github.com>
Mirrors heuristicTopKDecode.cu PR NVIDIA#14219 cr-aware branch in the DSL
GVR Top-K kernel. compress_ratio=1 (default) preserves DSv3.2 behavior
exactly; compress_ratio=4 enables the DSv4 (overlap-compressor) indexer
path:
  * pre_idx_offset = 0 (vs (row % next_n) + 1 for cr=1) — in compressed-
    index space, new entries append at the end so prev-step indices
    remain valid as-is.
  * N = actual_kv_len / cr — logits/preIdx live in compressed-token-
    index space when cr > 1.

GvrParams TABLE is also keyed by (dtype, K, cr) so V3.2 and V4 use
their respectively tuned kFTarget values:
  cr=1 (V3.2): kFTarget = 384 (K=512) / 2560 (K=1024), pre-NVIDIA#14413.
  cr=4 (V4):   kFTarget = kK   = 512 (K=512) / 1024 (K=1024), PR NVIDIA#14413.
  K=2048: identical across cr (V4 doesn't natively use K=2048).

Cache key includes compress_ratio so different cr settings compile
separate kernels. assert restricts compress_ratio in {1, 4}.

Verified: 1152/1152 pytest configs pass on cr=1 default path.
Signed-off-by: Mindy Li <11663212+limin2021@users.noreply.github.com>
…3 mb

Two paired host-wrapper heuristic refinements:

1. enable_warp_parallel_reduce: bool → Optional[bool] = None, default
   auto-coupled to num_threads_per_block: enabled iff threads == 1024 (32
   warps, where serial tid==0 cost dominates). At threads == 512 (16
   warps) the warp-parallel path measured a ~2pp synth regression so it
   stays off. Cache key sees the concrete bool. Explicit True/False still
   overrides for A/B testing.

2. tier-3 (large grid + large N) min_blocks_per_mp hardcoded "= 3"
   replaced by a (T, dtype) lookup:
     T == 1024 or dtype == fp32 → mb=2
     T == 512  and dtype in (bf16, fp16) → mb=3
   Derived from BS{256,384,512} × N{16K,32K,65K} × all 9 (dtype, K) sweep
   (gvr-topk-opt/sweep_tv_mb_kineto/mb_sweep.png). Old mb=3 default
   regressed by 25-37% on (T=512 + fp32 + large N/BS) configs because
   cap=42 starves the 4-LDG-inflight ILP (fp32 vec_w=4 × unroll-4 needs
   50+ regs). bf16/fp16 keep mb=3 since cvt-to-fp32 ILP fits in 40 regs
   and the extra CTA/SM (3 vs 2) hides cvt latency.

Pure default-policy change — no behavioral effect when caller passes
explicit values. Verified: pytest smoke 4/4 on cr=1 default path.

Signed-off-by: Mindy Li <11663212+limin2021@users.noreply.github.com>
Two more host-wrapper Optional[*]=None defaults so callers no longer
need to pick threads/vec-bits per shape:

  num_threads_per_block (None default):
    1024 iff num_rows <= num_sms (1 CTA/SM bound) AND N >= 65536
    (so each of the 1024 threads has meaningful vec-loop work).
    Otherwise 512.

  use_256bit_load (None default):
    True iff dtype == fp32 AND N >= 16384.
    Half-prec (bf16/fp16) cvt-to-fp32 doubles fragment reg footprint
    and regresses 5-11% at K=512/1024; LDG already saturates at 128b
    anyway. fp32 N=8K dips 5-8% with 256b at small grid so the N
    threshold excludes that single tier.

Cache key sees concrete values; (None, X) and (None, Y) hash apart.
Explicit values still override for A/B testing.

Derivation: sweep BS{1,4,16,64,128,256,384,512} x N{4K..131K} x all 9
(dtype, K), gvr-topk-opt/sweep_tv_kineto/auto_speedup.csv. Net vs
baseline (T=512, V=128):
  - median speedup vs CUDA  1.09x -> 1.10x
  - mean   speedup vs CUDA  1.11x -> 1.15x  (+3.8pp)
  - max    speedup vs CUDA  1.45x -> 1.52x
  - 21 of 22 sp<1 configs were already sp<1 in baseline (BS=384 grid
    quirk, unrelated to this change). 1 new config introduces a 0.8pp
    sp<1 dip (within bench noise).

Pure default-policy change. Verified: 4-case auto-path smoke + pytest
smoke 4/4 on cr=1 default.

Signed-off-by: Mindy Li <11663212+limin2021@users.noreply.github.com>
Decode runs under CUDA graph, where the (T, V) heuristic baked in at
capture time is reused across all replays. The capture-time
logits.shape[1] is typically much smaller than peak runtime N, so the
captured kernel misses the large-N (T=1024, V=256) path. Add an
optional max_seq_len hint so the caller (e.g. dsa.py) can pass the
peak compressed-N for the model; the heuristic then tunes the captured
kernel for the peak.

Usage guidance baked into the docstring + inline comment:
  * CUDA Graph mode: CALLER MUST PASS max_seq_len.
  * Eager mode: leave max_seq_len=None (heuristic adapts per call).

Rules with max_seq_len:
  * T=1024 threshold becomes dtype-aware to avoid half-prec K=512/1024
    small-N replay regression (14-16% when forced T=1024 at small N):
      fp32 -> 65536 (small-N replay 1-9% loss, net win)
      half -> 131072 (only forced at very large peak)
  * V=256 still gated by fp32 + N >= 16384.

Without max_seq_len, dtype-split is NOT applied because per-call
adaptive decisions never force T=1024 onto small N — heuristic only
fires for N >= 65536 by definition — so the half-prec N=65K-128K
+4-6% T=1024 win is preserved.

Cache key sees concrete (T, V), so different max_seq_len hints compile
distinct kernels. Pure default-policy extension. Verified with 4-case
auto smoke (no hint / fp32+131K / bf16+131K / bf16+200K).

Signed-off-by: Mindy Li <11663212+limin2021@users.noreply.github.com>
The min_blocks_per_mp tier heuristic was still computing n_vec_iters
from logits.shape[1] (capture-time N). In graph mode with max_seq_len
hint, this would stick small-capture-N calls in tier-0 (mb=0) and
miss the tier-3 occupancy choice for large-N replays — same pitfall
the (T, V) heuristic was fixed against in the previous commit.

Switch to N_dec (= max_seq_len if provided, else logits.shape[1]) so
the tier classification is consistent with how T/V are picked.

Smoke 4/4.

Signed-off-by: Mindy Li <11663212+limin2021@users.noreply.github.com>
Add a wave-fit branch in the fp32 tier-3 path: when num_rows ∈ (296, 444]
(i.e. fits 1 wave at 3 CTAs/SM but needs partial 2nd wave at 2 CTAs/SM
with num_sms=148), pick mb=3 instead of mb=2. This recovers ~15% perf
on fp32 BS=384 across (K, N) — verified against CUDA which already uses
__launch_bounds__(BS, 3) for this exact reason.

Math:
  mb=2 cap → 2 CTAs/SM × 148 SMs = 296 CTAs in 1 wave.
  mb=3 cap → 3 CTAs/SM × 148 SMs = 444 CTAs in 1 wave.
For BS=384 (× next_n=1):
  mb=2: 384 / 296 = 1.30 waves → tail wave wastes ~70% SMs.
  mb=3: 384 / 444 = 0.86 waves → 1 wave fits, max SM utilization.

Verified perf gains (fp32 T=512, both V=128 and V=256 default paths):
  fp32 K=512  N=4K-32K BS=384: +11-23%
  fp32 K=1024 N=4K-32K BS=384: +16-19%
  fp32 K=2048 N=8K-32K BS=384: +5-9%

Other BS unaffected:
  BS ≤ 296 (192, 256): mb=2 already fits 1 wave → rule keeps mb=2 (no change)
  BS > 444 (512): both need >1 wave → rule keeps mb=2 (ILP > occupancy)

Half-prec heuristic unchanged (already uses mb=3 in tier-3 via the
dtype-split path from a prior commit).

Bench artifacts: gvr-topk-opt/auto_full_bench/fp32_bs384_cluster/
(mb sweep CSV + NCU reports + drivers). Smoke: pytest 4/4 + spot tests
across BS={256, 384, 512}.

Signed-off-by: Mindy Li <11663212+limin2021@users.noreply.github.com>
Functional change: limit wave-fit mb=3 branch to N <= 32768. Beyond
that threshold the kernel becomes bandwidth-bound and mb=3's 3-way L2
sharing causes contention; mb=2's lower occupancy gives each CTA more
bandwidth and wins +21-30% at fp32 K=512 N=65K BS=384.

The full wave-fit rule for fp32 tier-3 is now:
  if 2*num_sms < num_rows <= 3*num_sms and N_dec <= 32768:
      mb = 3
  else:
      mb = 2

Also cleans up file comments: remove obsolete TODO list, trim refs to
specific CUDA line numbers, simplify class docstring.

Signed-off-by: Mindy Li <11663212+limin2021@users.noreply.github.com>
@limin2021 limin2021 changed the title Cute dsl gvr topk [TRTLLM-35237][feat] Add cute dsl gvr top-k decode kernel May 28, 2026
@limin2021 limin2021 changed the title [TRTLLM-35237][feat] Add cute dsl gvr top-k decode kernel [TRTLLM-35882][feat] Add cute dsl gvr top-k decode kernel May 28, 2026
Registers torch.ops.trtllm.cute_dsl_gvr_topk_decode as the production
entry point for the cuTe DSL GVR Top-K decode kernel (Blackwell SM100).
Op writes values + indices into caller-allocated buffers (mutates_args
style), matching the existing cute_dsl_indexer_topk_decode pattern so
the DSA indexer pipeline can drop it in.

CuteDSLGvrTopKDecodeRunner takes ownership of the JIT compile cache and
the auto-heuristic for T (threads/block), V (vec-load width),
min_blocks_per_mp and enable_warp_parallel_reduce. The previous
module-level wrapper in gvr_topk_decode.py is removed; standalone
bench / A-B testing with the full tuning knob set lives in
tests/scripts/cute_dsl_kernels/top_k/run_gvr_topk.py.

Tests:
- tests/unittest/.../test_cute_dsl_gvr_topk_decode.py: production
  correctness sweep via the op (dtype x K x N x next_n x batch_size x
  compress_ratio) with vectorized tie-aware + strict sort+allclose check.
- tests/scripts/.../run_gvr_topk.py: dual-mode driver -- pytest sweep
  over T/V/wp knobs and standalone CLI for single-case verification.

Signed-off-by: Mindy Li <11663212+limin2021@users.noreply.github.com>
@limin2021

Copy link
Copy Markdown
Collaborator Author

@longcheng-nv Thanks for the thorough review. Replies per item below.


#1 — Perf table (replace PNG)

Added 3 tables to PR description:

  • Full sweep (408 configs, v5 vs CUDA prod GVR) inside <details> collapsible.
  • Cumulative v1→v5 — each row's per-class delta IS a switch-class A/B (e.g., v3→v4 +2.1pp = T+V auto-heuristic gain across all 408 shapes).
  • Spot row: fp32 K=2048 N=64K BS=64 at T-switch boundary, v3 (T=512 forced) 22.26 µs vs v4 (T=1024 auto) 19.67 µs = 1.132× from T-switch alone.

Median 0.964× → 1.152× across 408 configs; sp<1 dropped 230 → 7.


#2 — Alignment check for use_256bit_load=True

Analyzed and decided to document the contract rather than add a runtime assert:

  • logits's tensor lineage in V4 indexer pipeline is always either fresh torch.empty() or a row-slice — both preserve 32B alignment (PyTorch CUDA allocator is 256B-aligned; row_stride × elem_size is multiple of 32 for our N ≥ 1K).
  • Column slices don't match per-row top-K semantics — not a foreseeable misuse path.
  • No existing CuTe DSL op in this codebase carries runtime data_ptr alignment checks; adding one here would be inconsistent without a concrete risk model.

Added inline comment next to the use_256bit_load heuristic decision documenting the 32B contract. Happy to add the assert if you prefer stricter defense.


#3 — Clarify return_output_values policy

Done. Op-level comment now states:

Op-level hardcodes return_output_values=False — DSA indexer pipeline
only consumes indices, mirroring CUDA indexer_topk_decode.

The kernel keeps both True/False branches to support enabling value
writeback in the future if a downstream caller needs it.

Not dead code; permanent False for the production op.


#4 — Realistic preIdx hit-rate test

Done. Added preidx_hit_rate axis to _make_inputs + @parametrize:

@pytest.mark.parametrize("preidx_hit_rate", [0.0, 0.5])
  • 0.0 = current worst-case (only argmax slot meaningful).
  • 0.5 = realistic (matching V3.2 ~40% / V4 Pro ~75%); exercises Guess-phase short-circuit path.

Test matrix expanded 144 → 288; all 288 pass.


Minor 1 — _fmin_f32_inline PTX workaround

Comment already explains root cause (cute DSL exposes fmax but not fmin) and cost. The missing cute.arch.fmin is a CuTe DSL gap, not a TRT-LLM-owned issue — will surface via DSL maintainer channels. The workaround can be replaced with the native API once it lands; leaving inline note for now.


Minor 2 — GvrParams table → formula

kFTarget values are empirically fit per (dtype, K, cr) from swe-bench V3.2 / V4 captures (gives 1.5–2.2× P2-iter reduction). No clean closed form:

  • cr=1: 384 / 2560 / 3072 / 4096 — saturation-point measurements per K, not a function of K.
  • cr=4: kFTarget == K holds for K ∈ {512, 1024} but breaks at K=2048 (3072).

Future K additions need a small sweep. Keeping the hand-table as the honest source of truth.


Minor 3 — run_gvr_topk.py top-of-file comment

Done. Added:

**Not in CI** — this file imports the DSL kernel module directly
(no trtllm runtime dep), to enable knob-A/B development outside the
production op.

- Document 32B alignment caller contract when use_256bit_load=True
  (no runtime assert; matches existing DSL op convention — see PR
  reply for risk-model rationale)
- Clarify return_output_values policy: op hardcodes False (matches
  CUDA indexer_topk_decode); kernel retains True branch for future
  caller flexibility
- Add preidx_hit_rate parametrize axis to op unittest (0.0
  worst-case + 0.5 realistic, matching V3.2/V4 Pro production
  preIdx∩topK overlap); test matrix 144 → 288
- Add 'Not in CI' header docstring to standalone driver
  run_gvr_topk.py explaining the trtllm-runtime-free design
- Add --num_sms CLI to standalone driver for heuristic-edge debug

Signed-off-by: Mindy Li <11663212+limin2021@users.noreply.github.com>
@limin2021

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #51301 [ run ] triggered by Bot. Commit: e219a00 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #51301 [ run ] completed with state FAILURE. Commit: e219a00
/LLM/main/L0_MergeRequest_PR pipeline #40718 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

@limin2021

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #51310 [ run ] triggered by Bot. Commit: e219a00 Link to invocation

@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.

Follow-up review — please address the barrier race + the still-open alignment guard

Thanks @limin2021 for the round-2 updates. Re-reviewing at head e219a000… against my previous review (2026-05-29) and aligning with @yuxianq's inline note from earlier today.


📋 Status of prior asks

# Ask Status Notes
1 Replace perf PNG with numerical table ✅ Addressed PR body now has a 408-row table across (dtype × K × N × BS) + per-optimization cumulative table (v1 → v5). Strong evidence.
2 Runtime assert when use_256bit_load=True Open No data_ptr() % 32 check anywhere in the new code; still a real footgun for downstream callers (views, padded layouts).
3 Clarify return_output_values policy ✅ Addressed Op-level docstring + the comment "kernel keeps both branches to support enabling value writeback in the future" makes the intent explicit.
4 Realistic preIdx hit-rate test ✅ Addressed Unit test now parametrizes preidx_hit_rate ∈ {0.0, 0.5}; covers both worst-case and realistic Guess-phase short-circuit.

🐛 New / outstanding correctness asks

A. Confirming @yuxianq's barrier race in phase4_histogram_snap Branch B (gvr_topk_decode.py L1497–L1521)

Yuxianq's concern is correct — there is a missing cute.arch.barrier() between the "every thread recomputes block_min/block_max from staged warp slots" read and the "zero histogram" write that follows. Reproducing the sequence:

# L1488–L1492: lane==0 stages cmin/cmax bits → smem_wcnt[w] / smem_hist[w]
cute.arch.barrier()                                  # L1493 ✓

# L1498–L1511: every thread (re)reads smem_hist[w] for w ∈ [0, num_warps)
for w in cutlass.range_constexpr(self.num_warps):
    vmin_bits = smem_wcnt[w]
    vmax_bits = smem_hist[w]                         # READ smem_hist[0..NW)
    ...

# L1517–L1520: zero histogram across [0, kBins)
i6 = tidx
while i6 < cutlass.Int32(kBins):
    smem_hist[i6] = cutlass.Int32(0)                 # WRITE smem_hist[0..kBins)
    i6 = i6 + cutlass.Int32(num_threads)
cute.arch.barrier()                                  # L1521

The unrolled read loop runs in-warp-lockstep but not in-block-lockstep — warp 0's thread 0 can finish the entire read loop and start zeroing smem_hist[0] while warp 1 is still reading smem_hist[0]. Result: vmax_bits (and therefore bmax_r) is read as 0, the patched bmax_r ≤ bmin_r → bmax_r = bmin_r + 1e-6 may fire spuriously, and the histogram bin width gets squashed → all candidates land in bin 0 → wrong K-th threshold.

This is hit-rate-dependent and won't show on most synth distributions (which is why the 144-cell unit sweep passes), but I'd expect it to surface under adversarial inputs.

Fix: insert a cute.arch.barrier() after the read loop. The cleanest spot is between the bmax_r ≤ bmin_r patch and the zero loop:

if bmax_r <= bmin_r:
    bmax_r = bmin_r + cutlass.Float32(1e-6)
cute.arch.barrier()        # ← ADD: ensure all reads of smem_hist[0..NW) complete before zeroing

# Zero histogram
i6 = tidx
while i6 < cutlass.Int32(kBins):
    ...

B. Ask #2 from previous review — still open

Re-quoting for visibility; should land in this PR rather than as a follow-up since it protects the op's public contract:

if use_256bit_load:
    assert logits.data_ptr() % 32 == 0, (
        f"256-bit vec load requires 32B-aligned logits.data_ptr(), "
        f"got {logits.data_ptr()} % 32 = {logits.data_ptr() % 32}"
    )

🔍 Minor (nice-to-fix in this PR, won't block)

  1. Stale comment in phase4_histogram_snap Branch B (gvr_topk_decode.py around the bmin/bmax recompute loop):

    # Note: unrolled for 64 times.

    The constexpr unrolls num_warps times, which is 16 or 32, never 64. Looks like leftover from an earlier num_threads=2048 variant.

  2. Docstring/allocation mismatch in phase1_preidx_stats:

    s_thr,  # cute.Tensor [3] float32: [threshold, val_lo, val_hi, pmax_saved]

    Says 4-wide but allocated as (3,); pmax_saved lives in the separate s_thr_extra allocation. Cosmetic but misleading.

  3. packed = (lge << 16) | lgt in block_fused_snap_iter silently assumes lge, lgt < 2^16 = 65536. The current bound is cand_count ≤ kC ≤ 6144, so OK — but worth a static_assert-style const check / comment so a future bump of kC doesn't quietly corrupt the packing.

  4. num_sms: int = 148 hardcoded default in tests/scripts/cute_dsl_kernels/top_k/run_gvr_topk.py (gvr_topk_decode signature). The standalone driver's __main__ and pytest path both override it via torch.cuda.get_device_properties(0).multi_processor_count, so this is only a footgun for someone calling gvr_topk_decode() directly on B300 (132 SMs/die) without overriding. Either pass num_sms always or remove the default.

  5. Perf regression cells — the v5 table lists sp<1: 7/408, but specific cells worth flagging in the PR body so future maintainers know which corners the heuristic loses:

    • fp32 K=512 N=4096 BS=384: 0.777× (largest regression)
    • bf16/fp16 K=1024–2048 N=131072 BS=512: 0.912× / 0.933× / 0.957×

    The pattern (high-BS / large-N / fp32-small-K corner) suggests the min_blocks_per_mp tier-3 rule may need one more sub-bucket, but acceptable as a follow-up if the per-step impact on DSv4 indexer is bounded.


Status

Holding at COMMENT until A (barrier race) and B (alignment guard) land. Happy to flip to APPROVE once both are in. cc @Kefeng-Duan / @yuxianq for visibility.

Comment thread tensorrt_llm/_torch/custom_ops/cute_dsl_custom_ops.py
Comment thread tests/scripts/cute_dsl_kernels/top_k/run_gvr_topk.py
Comment thread tensorrt_llm/_torch/cute_dsl_kernels/blackwell/top_k/gvr_topk_decode.py Outdated
limin2021 added 2 commits June 1, 2026 05:57
Bug fixes:
- phase4_histogram_snap: add missing cute.arch.barrier() between the
  smem_hist[0..NW-1] read loop (recomputing block_min/block_max from
  warp-staged cmax slots) and the histogram-zeroing write loop. Without
  this, warp-0 thread-0 could finish the unrolled read and start
  zeroing smem_hist[0] while warp-N was still reading the staged cmax
  → squashed bmax_r → all candidates land in bin 0 → wrong K-th
  threshold. Hit-rate-dependent; covered by existing tests once they
  span realistic preIdx hit rates (NVIDIA#4 from prior review).
- Add runtime 32B alignment assert on logits.data_ptr() when the
  use_256bit_load heuristic fires (LDG.E.256 path). Catches view-with-
  unaligned-offset misuse with a clear error rather than silent
  miscompiled addresses / faults.

Code hygiene:
- Fix stale "unrolled for 64 times" comment in the block_min/max
  recompute loop; it unrolls num_warps times (16 or 32).
- Fix phase1_preidx_stats s_thr docstring: [3] [threshold, val_lo,
  val_hi]; pmax_saved lives in the separate s_thr_extra allocation.
- Document the (lge << 16) | lgt packing's <2^16 per-warp count
  precondition (currently holds via cand_count ≤ kC ≤ 6144 in
  GvrParams; future kC bump past 65536 would silently corrupt).

Signed-off-by: Mindy Li <11663212+limin2021@users.noreply.github.com>
s_thr_extra was a [1]-wide fp32 smem buffer written twice in
phase1_preidx_stats (parallel + serial paths) to "save pmax", but
never read anywhere in the kernel — leftover from an earlier design
that was refactored. The same value is already in s_thr[2] at the
point of the redundant write; subsequent secant updates of s_thr[2]
don't need the original pmax.

Drops: smem allocation, phase1_preidx_stats parameter + call-site
arg, and 2 write statements.

Reported by @yuxianq in PR review.

Signed-off-by: Mindy Li <11663212+limin2021@users.noreply.github.com>
@limin2021

limin2021 commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator Author

@longcheng-nv fixed the 2nd round review.

For Minor 4th question, please ignore this one. This script is only used for local debug/test in no-trtllm env. These are not production path.

@limin2021

Copy link
Copy Markdown
Collaborator Author

/bot run

@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.

✅ Approved — all follow-up asks landed cleanly

Verified against head 97c47dc8 (commits d23ba74a + 97c47dc8 since my COMMENT review).

Ask Status Notes
A. Barrier race fix in phase4_histogram_snap cute.arch.barrier() inserted between the smem_hist[0..NW-1] read and the zero loop, with reasoning in the comment.
B. 32-byte alignment assert for use_256bit_load=True Guards logits.data_ptr() % 32 == 0 with a clear error pointing at column-slice / stride-padded layouts as the cause.
Minor 1 — stale "unrolled for 64 times" comment Updated to reflect the actual num_warps-times unroll (16 or 32).
Minor 2 — s_thr docstring vs allocation Went beyond the doc fix: removed the dead s_thr_extra allocation entirely (was written but never read).
Minor 3 — 16-bit packing assumption Comment block documents the lge/lgt < 2^16 invariant tied to kC ≤ 6144 and warns about future kC bumps.

Delta is 5 hunks across the two files I previously reviewed, no collateral substantive changes. Race fix is the right scope (per-row CTA, smem-only).

Thanks @limin2021 for the fast turnaround, and thanks @yuxianq for catching the barrier race. Approving.

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #51321 [ run ] triggered by Bot. Commit: 97c47dc Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #51310 [ run ] completed with state ABORTED. Commit: e219a00

Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #51321 [ run ] completed with state SUCCESS. Commit: 97c47dc
/LLM/main/L0_MergeRequest_PR pipeline #40734 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

@limin2021

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #51357 [ run ] triggered by Bot. Commit: 97c47dc Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #51357 [ run ] completed with state SUCCESS. Commit: 97c47dc
/LLM/main/L0_MergeRequest_PR pipeline #40768 completed with status: 'SUCCESS'

CI Report

Link to invocation

@longcheng-nv
longcheng-nv requested a review from lfr-0531 June 2, 2026 02:01
@lfr-0531
lfr-0531 requested a review from mingyangHao June 2, 2026 03:00
@yuxianq
yuxianq merged commit ca00411 into NVIDIA:main Jun 2, 2026
7 checks passed
@limin2021

Copy link
Copy Markdown
Collaborator Author

TODO: test more N, including [K, 4096]

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.

7 participants