Skip to content

perf(topk): skip output index sort for tie-break selection - #4295

Merged
bkryu merged 4 commits into
flashinfer-ai:mainfrom
zianglih:agent/topk-tie-break-unsorted
Aug 5, 2026
Merged

bkryu merged 4 commits into
flashinfer-ai:mainfrom
zianglih:agent/topk-tie-break-unsorted

Conversation

@zianglih

@zianglih zianglih commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

📌 Description

@HumansAnd

Follow-up to #3095.

tie_break determines which equal-valued elements are selected at the top-k boundary. Today, requesting a tie-break also promotes the public deterministic flag, which runs the index-ordering finalizer even though the default API output is unsorted.

This PR separates deterministic selection from deterministic output ordering:

  • tie-break modes still use deterministic filtered selection so SMALL/LARGE choose the intended boundary indices;
  • the shared FinalizeTopKIndicesKernel always finalizes/remaps indices, with SORT_LOCAL_INDICES as the compile-time switch for its optional local-index radix sort;
  • explicit deterministic=True retains the existing CUB index sort and repeatable output order;
  • tie-break with deterministic=False skips the finalizer entirely for plain Top-K, while page-table/ragged transforms run only the required remapping work;
  • can_use_clusters_topk now receives tie_break and rejects tie-break modes at the same early-exit boundary as dsa_graph_safe;
  • API documentation, exact-set/remapping tests, and the Top-K benchmark driver reflect that tie-breaking controls selection, not output order.

sorted=True remains unchanged and still requests descending value order.

sorted and deterministic use different sort keys

These options are independent and should not be conflated:

Option Contract Sort key/order
sorted=True Return the selected top-k elements in score order, matching torch.topk(..., sorted=True) value descending, carrying the corresponding index
deterministic=True Make selection and output ordering repeatable for a fixed input and system on the filtered path, local/original index ascending, carrying the corresponding value

The deterministic filtered-path index sort is a canonicalization step; it does not imply descending score order. If both options are enabled, FlashInfer first establishes deterministic index order and then applies a stable descending value sort. Equal values therefore retain the deterministic prior order. For page-table and ragged transforms, local indices are canonicalized before remapping, so the returned mapped IDs are not necessarily numerically sorted.

tie_break is separate from both: it controls which equal-valued elements are selected at the top-k boundary, not the final output order.

Performance

Both sides used the same final benchmark driver and ran on the same physical GPU (CUDA_VISIBLE_DEVICES=2) in the flashinfer-pr4048-cu132 devbox from the hell queue. Speedup is BEFORE / AFTER.

Environment Value
GPU NVIDIA B200 (sm100)
CUDA toolkit 13.2.1
PyTorch 2.13.0+cu132
Driver 580.126.09
CUPTI Python/native 13.2.0 / 13.2.75
FlashInfer source version 0.6.17
BEFORE upstream/main at 668a1ba1ca86432c79f6adad37ecfce8d06ec083
AFTER 89c649679af75792b2cad1020c91b41bf2262adc
Timing CUPTI activity timing, no CUDA graph replay, cold L2, 10 dry runs, 100 measured iterations, median

The shared timer supports CUPTI with and without CUDA graphs. A control sweep with CUPTI + CUDA graphs + cold-L2 reproduced the same illegal-memory-access failure on both upstream main and this branch at the first 128K page-table row, so it is not specific to this PR's kernel changes. The final benchmark follows existing FlashInfer CUPTI microbenchmarks by using enable_cupti=True with use_cuda_graph=False; no benchmark-specific environment, global, or CLI escape hatch remains.

Commands:

export CUDA_VISIBLE_DEVICES=2

python benchmarks/bench_topk.py \
  --op dsa_topk \
  --dtype bf16 \
  --dsa-input-pattern dsa_relu \
  --dsa-case all \
  --dsa-topk 2048 \
  --tie-break

python benchmarks/bench_topk.py \
  --op varlen \
  --dtype bf16 \
  --length-dist causal \
  --varlen-k 2048 \
  --varlen-q-len 128 \
  --tie-break

Summary (geometric mean across cases):

Suite Tie-break Geomean speedup Case range
DSA Top-K small 1.141x 1.123x–1.182x
DSA Top-K large 1.135x 1.071x–1.170x
Varlen page-table small 1.080x 1.034x–1.145x
Varlen page-table large 1.078x 1.035x–1.139x
Varlen ragged small 1.121x 1.061x–1.186x
Varlen ragged large 1.119x 1.061x–1.195x

DSA Top-K results (latency in µs):

Case Small BEFORE Small AFTER Speedup Large BEFORE Large AFTER Speedup
decode_b1_q1_l128k 83.17 74.08 1.123x 84.54 72.24 1.170x
decode_b8_q1_l64k 80.69 68.29 1.182x 79.04 67.86 1.165x
decode_b32_q1_l128k 90.56 80.51 1.125x 85.92 80.19 1.071x
prefill_b1_q128_l128k 91.41 80.51 1.135x 91.33 80.43 1.136x

Variable-length transform results (latency in µs):

Transform Rows Max length Small BEFORE Small AFTER Speedup Large BEFORE Large AFTER Speedup
page-table 512 16,384 49.06 42.85 1.145x 49.09 43.10 1.139x
ragged 512 16,384 47.87 40.35 1.186x 48.10 40.26 1.195x
page-table 512 65,536 86.38 79.07 1.092x 87.66 80.67 1.087x
ragged 512 65,536 84.80 75.50 1.123x 85.39 76.99 1.109x
page-table 512 131,072 99.63 92.54 1.077x 99.92 92.80 1.077x
ragged 512 131,072 98.61 89.41 1.103x 98.75 89.68 1.101x
page-table 2,048 16,384 172.29 157.33 1.095x 173.63 158.94 1.092x
ragged 2,048 16,384 170.30 144.35 1.180x 171.66 145.87 1.177x
page-table 2,048 65,536 442.69 424.46 1.043x 444.54 426.35 1.043x
ragged 2,048 65,536 437.07 405.06 1.079x 438.82 406.80 1.079x
page-table 2,048 131,072 565.89 547.06 1.034x 568.35 548.93 1.035x
ragged 2,048 131,072 558.54 526.56 1.061x 561.33 529.02 1.061x
Raw BEFORE output: DSA Top-K
====================================================================================================
dsa_topk: DeepSeek DSA-like indexer top-k workload (dtype=BF16, deterministic=False, dsa_pattern=dsa_relu, k=2048, tie_break=True)
NOTE: tie-break columns use deterministic=False; slowdowns use the non-deterministic baseline
====================================================================================================
                    case     rows    seq_len      k |   FlashInfer FlashInfer(det) DetSlowdown FlashInfer(tie-small)  TieSmallSlowdown FlashInfer(tie-large)  TieLargeSlowdown   torch.topk    Speedup
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
      decode_b1_q1_l128k        1     131072   2048 |      40.90us            n/a         n/a               83.17us             2.03x               84.54us             2.07x      75.44us      1.84x
       decode_b8_q1_l64k        8      65536   2048 |      40.75us            n/a         n/a               80.69us             1.98x               79.04us             1.94x      82.86us      2.03x
     decode_b32_q1_l128k       32     131072   2048 |      45.66us            n/a         n/a               90.56us             1.98x               85.92us             1.88x     112.77us      2.47x
   prefill_b1_q128_l128k      128     131072   2048 |      82.61us            n/a         n/a               91.41us             1.11x               91.33us             1.11x     197.28us      2.39x
Raw AFTER output: DSA Top-K
====================================================================================================
dsa_topk: DeepSeek DSA-like indexer top-k workload (dtype=BF16, deterministic=False, dsa_pattern=dsa_relu, k=2048, tie_break=True)
NOTE: tie-break columns use deterministic=False; slowdowns use the non-deterministic baseline
====================================================================================================
                    case     rows    seq_len      k |   FlashInfer FlashInfer(det) DetSlowdown FlashInfer(tie-small)  TieSmallSlowdown FlashInfer(tie-large)  TieLargeSlowdown   torch.topk    Speedup
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
      decode_b1_q1_l128k        1     131072   2048 |      41.02us            n/a         n/a               74.08us             1.81x               72.24us             1.76x      73.60us      1.79x
       decode_b8_q1_l64k        8      65536   2048 |      40.69us            n/a         n/a               68.29us             1.68x               67.86us             1.67x      82.58us      2.03x
     decode_b32_q1_l128k       32     131072   2048 |      45.89us            n/a         n/a               80.51us             1.75x               80.19us             1.75x     112.69us      2.46x
   prefill_b1_q128_l128k      128     131072   2048 |      82.59us            n/a         n/a               80.51us             0.97x               80.43us             0.97x     198.18us      2.40x
Raw BEFORE output: variable-length transforms
====================================================================================================
varlen: Variable-length segment top-k transforms (production-realistic) (dtype=BF16, length_dist=causal, k=2048, deterministic=False, tie_break=True)
NOTE: lengths model per-row valid windows; decode = independent context lengths, prefill(causal) = monotonic growth within a request (q_len=128)
NOTE: torch(mask) masks invalid positions once (outside timing) then torch.topk, isolating selection cost vs the length-aware kernel
NOTE: tie-break columns use deterministic=False; slowdowns use the non-deterministic baseline
NOTE: Clusters column omitted under deterministic/tie-break (clusters requires the non-deterministic path)
====================================================================================================
  regime       dist   transform     rows   reqs   max_len      k |  len_min  len_mean  len_max  triv% |   FlashInfer FlashInfer(det) DetSlowdown FlashInfer(tie-small)  TieSmallSlowdown FlashInfer(tie-large)  TieLargeSlowdown   torch(mask)   Speedup
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 prefill     causal  page_table      512      4     16384   2048 |      375    7109.2    12411  25.0% |      30.34us            n/a         n/a               49.06us             1.62x               49.09us             1.62x      153.26us     5.05x
 prefill     causal      ragged      512      4     16384   2048 |      375    7109.2    12411  25.0% |      27.33us            n/a         n/a               47.87us             1.75x               48.10us             1.76x      152.34us     5.57x
 prefill     causal  page_table      512      4     65536   2048 |     2039   22253.8    37156   2.0% |      65.44us            n/a         n/a               86.38us             1.32x               87.66us             1.34x      308.99us     4.72x
 prefill     causal      ragged      512      4     65536   2048 |     2039   22253.8    37156   2.0% |      60.43us            n/a         n/a               84.80us             1.40x               85.39us             1.41x      309.34us     5.12x
 prefill     causal  page_table      512      4    131072   2048 |      524   33177.8   101452  25.0% |     152.19us            n/a         n/a               99.63us             0.65x               99.92us             0.66x      592.27us     3.89x
 prefill     causal      ragged      512      4    131072   2048 |      524   33177.8   101452  25.0% |     144.70us            n/a         n/a               98.61us             0.68x               98.75us             0.68x      591.23us     4.09x
 prefill     causal  page_table     2048     16     16384   2048 |      416    7917.1    15346  12.5% |     122.30us            n/a         n/a              172.29us             1.41x              173.63us             1.42x      366.67us     3.00x
 prefill     causal      ragged     2048     16     16384   2048 |      416    7917.1    15346  12.5% |     111.49us            n/a         n/a              170.30us             1.53x              171.66us             1.54x      367.58us     3.30x
 prefill     causal  page_table     2048     16     65536   2048 |     7804   36260.2    63720   0.0% |     318.27us            n/a         n/a              442.69us             1.39x              444.54us             1.40x     1114.34us     3.50x
 prefill     causal      ragged     2048     16     65536   2048 |     7804   36260.2    63720   0.0% |     298.85us            n/a         n/a              437.07us             1.46x              438.82us             1.47x     1114.48us     3.73x
 prefill     causal  page_table     2048     16    131072   2048 |     7095   60508.9   124975   0.0% |     870.77us            n/a         n/a              565.89us             0.65x              568.35us             0.65x     2000.19us     2.30x
 prefill     causal      ragged     2048     16    131072   2048 |     7095   60508.9   124975   0.0% |     849.74us            n/a         n/a              558.54us             0.66x              561.33us             0.66x     2001.38us     2.36x
Raw AFTER output: variable-length transforms
====================================================================================================
varlen: Variable-length segment top-k transforms (production-realistic) (dtype=BF16, length_dist=causal, k=2048, deterministic=False, tie_break=True)
NOTE: lengths model per-row valid windows; decode = independent context lengths, prefill(causal) = monotonic growth within a request (q_len=128)
NOTE: torch(mask) masks invalid positions once (outside timing) then torch.topk, isolating selection cost vs the length-aware kernel
NOTE: tie-break columns use deterministic=False; slowdowns use the non-deterministic baseline
NOTE: Clusters column omitted under deterministic/tie-break (clusters requires the non-deterministic path)
====================================================================================================
  regime       dist   transform     rows   reqs   max_len      k |  len_min  len_mean  len_max  triv% |   FlashInfer FlashInfer(det) DetSlowdown FlashInfer(tie-small)  TieSmallSlowdown FlashInfer(tie-large)  TieLargeSlowdown   torch(mask)   Speedup
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 prefill     causal  page_table      512      4     16384   2048 |      375    7109.2    12411  25.0% |      30.30us            n/a         n/a               42.85us             1.41x               43.10us             1.42x      153.52us     5.07x
 prefill     causal      ragged      512      4     16384   2048 |      375    7109.2    12411  25.0% |      27.39us            n/a         n/a               40.35us             1.47x               40.26us             1.47x      152.86us     5.58x
 prefill     causal  page_table      512      4     65536   2048 |     2039   22253.8    37156   2.0% |      65.34us            n/a         n/a               79.07us             1.21x               80.67us             1.23x      309.01us     4.73x
 prefill     causal      ragged      512      4     65536   2048 |     2039   22253.8    37156   2.0% |      60.45us            n/a         n/a               75.50us             1.25x               76.99us             1.27x      310.05us     5.13x
 prefill     causal  page_table      512      4    131072   2048 |      524   33177.8   101452  25.0% |     152.22us            n/a         n/a               92.54us             0.61x               92.80us             0.61x      591.52us     3.89x
 prefill     causal      ragged      512      4    131072   2048 |      524   33177.8   101452  25.0% |     144.75us            n/a         n/a               89.41us             0.62x               89.68us             0.62x      591.46us     4.09x
 prefill     causal  page_table     2048     16     16384   2048 |      416    7917.1    15346  12.5% |     122.27us            n/a         n/a              157.33us             1.29x              158.94us             1.30x      366.85us     3.00x
 prefill     causal      ragged     2048     16     16384   2048 |      416    7917.1    15346  12.5% |     111.52us            n/a         n/a              144.35us             1.29x              145.87us             1.31x      366.70us     3.29x
 prefill     causal  page_table     2048     16     65536   2048 |     7804   36260.2    63720   0.0% |     318.19us            n/a         n/a              424.46us             1.33x              426.35us             1.34x     1113.92us     3.50x
 prefill     causal      ragged     2048     16     65536   2048 |     7804   36260.2    63720   0.0% |     298.83us            n/a         n/a              405.06us             1.36x              406.80us             1.36x     1114.29us     3.73x
 prefill     causal  page_table     2048     16    131072   2048 |     7095   60508.9   124975   0.0% |     870.80us            n/a         n/a              547.06us             0.63x              548.93us             0.63x     1999.70us     2.30x
 prefill     causal      ragged     2048     16    131072   2048 |     7095   60508.9   124975   0.0% |     849.66us            n/a         n/a              526.56us             0.62x              529.02us             0.62x     2000.72us     2.35x

🔍 Related Issues

🚀 Pull Request Checklist

Thank you for contributing to FlashInfer! Before we review your pull request, please make sure the following items are complete.

✅ Pre-commit Checks

  • I have installed pre-commit by running pip install pre-commit (or used my preferred method).
  • I have installed the hooks with pre-commit install.
  • I have run the hooks manually with pre-commit run --all-files and fixed any reported issues.

If you are unsure about how to set up pre-commit, see the pre-commit documentation.

🧪 Tests

  • Tests have been added or updated as needed.
  • All tests are passing (unittest, etc.).
python -m pytest -q tests/utils/test_topk.py
1396 passed, 2 warnings in 75.78s

pre-commit run --all-files
all hooks passed

Coverage includes:

  • unordered exact-set checks for SMALL/LARGE tie-break output;
  • an explicit clusters override that verifies tie-break modes take the filtered path;
  • page-table/ragged transforms with non-identity remapping;
  • explicit deterministic=True ordering for plain and transform APIs;
  • BF16 long-context filtered Top-K at 128K sequence length and k=2048.

Reviewer Notes

The intended contract is:

  • tie_break controls deterministic boundary selection only;
  • deterministic=True additionally requests repeatable output ordering;
  • sorted=True continues to request descending value order.

The key implementation detail is keeping DETERMINISTIC for filtered selection. The shared FinalizeTopKIndicesKernel always performs final mapping/writeback; SORT_LOCAL_INDICES independently controls its pre-writeback CUB sort. Transformed tie-break outputs retain the remap path with sorting disabled, while plain tie-break output skips the finalizer entirely.

The benchmark driver keeps CUPTI enabled and disables CUDA graph replay, matching other FlashInfer CUPTI microbenchmarks. This avoids adding a benchmark-global variable, environment setting, or CLI fallback solely for this workload.

Summary by CodeRabbit

  • Behavior Updates

    • Tie-breaking and deterministic ordering are now controlled independently.
    • Selecting a tie-break option no longer automatically enables deterministic execution.
    • Deterministic mode provides consistent index ordering; tie-breaking alone does not guarantee output order.
    • Clustered execution avoids unsupported tie-break and graph-safe combinations.
    • Page-table and ragged Top-K transformations preserve correct ordering and index mappings.
  • Benchmarking

    • Top-K benchmarks now provide more consistent timing comparisons against the nondeterministic baseline.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a028b375-47ba-4e4f-bd89-3d09b41b1ecf

📥 Commits

Reviewing files that changed from the base of the PR and between 89c649679af75792b2cad1020c91b41bf2262adc and 138b943.

📒 Files selected for processing (5)
  • benchmarks/bench_topk.py
  • csrc/topk.cu
  • flashinfer/topk.py
  • include/flashinfer/topk.cuh
  • tests/utils/test_topk.py
💤 Files with no reviewable changes (1)
  • csrc/topk.cu
🚧 Files skipped from review as they are similar to previous changes (4)
  • flashinfer/topk.py
  • tests/utils/test_topk.py
  • include/flashinfer/topk.cuh
  • benchmarks/bench_topk.py

📝 Walkthrough

Walkthrough

Top-K tie-break handling no longer implies deterministic output ordering. API dispatch preserves caller-selected modes, clustered execution rejects explicit tie-break requests, and configurable finalization handles sorting and transformed outputs. Benchmarks and tests cover the revised behavior.

Changes

Top-K tie-break behavior

Layer / File(s) Summary
API tie-break semantics
flashinfer/topk.py, csrc/topk.cu
Top-K APIs pass tie_break and deterministic independently. Clustered execution rejects explicit tie-break requests.
Configurable output finalization
include/flashinfer/topk.cuh
Filtered Top-K uses conditional index sorting before plain, page-table, or ragged output transformation.
Tie-break behavior validation
tests/utils/test_topk.py
Tests cover clusters, filtered workloads, multiple dtypes, deterministic ordering, and remapped transform outputs.
Benchmark timing and reporting
benchmarks/bench_topk.py
Benchmarks use shared median timing, preserve the requested deterministic mode, and compare against nondeterministic baselines.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant TopKAPI
  participant CUDADispatch
  participant LaunchFinalizeTopKIndices
  Caller->>TopKAPI: request top-k with deterministic and tie_break
  TopKAPI->>CUDADispatch: pass both modes independently
  CUDADispatch->>LaunchFinalizeTopKIndices: pass index-sorting mode
  LaunchFinalizeTopKIndices-->>Caller: return plain or transformed output
Loading

Possibly related PRs

Suggested reviewers: aleozlx, sricketts, dhiraj113

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.33% 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
Title check ✅ Passed The title clearly summarizes the main change: skipping output index sorting for tie-break selection.
Description check ✅ Passed The description covers the required sections, implementation details, tests, benchmarks, related issue, and reviewer notes.
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.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@zianglih
zianglih marked this pull request as ready for review July 31, 2026 10:08
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

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

It should be an important feature to have, thanks for the contribution.

Comment thread benchmarks/bench_topk.py Outdated
@@ -24,6 +24,8 @@
from flashinfer.testing.utils import bench_gpu_time
from flashinfer.utils import get_compute_capability

BENCH_ENABLE_CUPTI = True

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.

How about reading this value from environment variable?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I have dropped this interface. The instability is now avoided by using enable_cupti=True + use_cuda_graph=False, which is already a common pattern in the repo.

@yzh119

yzh119 commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

/bot run

@yzh119 yzh119 added the run-ci label Jul 31, 2026
@flashinfer-bot

Copy link
Copy Markdown
Collaborator

GitLab MR !1093 has been created, and the CI pipeline #60457495 is currently running. I'll report back once the pipeline job completes.

@flashinfer-bot

Copy link
Copy Markdown
Collaborator

[FAILED] Pipeline #60457495 — 6/18 executed test jobs passed

Compared with nightly #60449790.

Unit Tests

GPU CUDA 12.9 CUDA 13.0 Notes
5090 ⚠️ Infra ⚠️ Infra Infrastructure: CI infrastructure failure (2 jobs; CUDA 12.9, CUDA 13.0)
B300 🟡 Old 🟡 Old Old: tests.moe.test_cute_dsl_fused_moe.TestAutotuneReplayMemsetContract (4 failures; CUDA 12.9, CUDA 13.0)
Old: tests.test_artifacts (2 failures; CUDA 12.9, CUDA 13.0)
Test timeout: 1 test file timed out: tests/moe/test_trtllm_gen_fused_moe.py (2 jobs; CUDA 12.9, CUDA 13.0)
GB200 🟡 Old 🟡 Old Old: tests.moe.test_cute_dsl_fused_moe.TestAutotuneReplayMemsetContract (4 failures; CUDA 12.9, CUDA 13.0)
Old: tests.test_artifacts (2 failures; CUDA 12.9, CUDA 13.0)
GB300 🟡 Old 🟡 Old Old: tests.moe.test_cute_dsl_fused_moe.TestAutotuneReplayMemsetContract (4 failures; CUDA 12.9, CUDA 13.0)
Old: tests.test_artifacts (2 failures; CUDA 12.9, CUDA 13.0)
H100 🟡 Old 🟡 Old Old: tests.moe.test_cute_dsl_fused_moe.TestAutotuneReplayMemsetContract (4 failures; CUDA 12.9, CUDA 13.0)
Old: tests.test_artifacts (2 failures; CUDA 12.9, CUDA 13.0)
RTX Pro 6000 Blackwell 🟡 Old 🟡 Old Old: tests.moe.test_cute_dsl_fused_moe.TestAutotuneReplayMemsetContract (4 failures; CUDA 12.9, CUDA 13.0)
Old: tests.test_artifacts (2 failures; CUDA 12.9, CUDA 13.0)

✅ Pass · 🟡 Old failure · ❌ New failure · ⏱ Test timeout · ⚠️ Infrastructure · ❔ Unknown or unclassified · — Not run

Multi-GPU and Multi-Node Tests — 6/6 passed

GPU CUDA 12.9 CUDA 13.0 Notes
B300 (multi-GPU) ✅ Pass ✅ Pass
GB200 (multi-node) ✅ Pass ✅ Pass
GB300 (multi-node) ✅ Pass ✅ Pass
Failure details

Pre-existing failures

  • tests.moe.test_cute_dsl_fused_moe.TestAutotuneReplayMemsetContract — 20 failures on B300 / CUDA 12.9, B300 / CUDA 13.0, GB200 / CUDA 12.9, GB200 / CUDA 13.0, GB300 / CUDA 12.9, GB300 / CUDA 13.0, H100 / CUDA 12.9, H100 / CUDA 13.0, RTX Pro 6000 Blackwell / CUDA 12.9, RTX Pro 6000 Blackwell / CUDA 13.0
    • ValueError: Expected a cuda device, but got: cpu
  • tests.test_artifacts — 10 failures on B300 / CUDA 12.9, B300 / CUDA 13.0, GB200 / CUDA 12.9, GB200 / CUDA 13.0, GB300 / CUDA 12.9, GB300 / CUDA 13.0, H100 / CUDA 12.9, H100 / CUDA 13.0, RTX Pro 6000 Blackwell / CUDA 12.9, RTX Pro 6000 Blackwell / CUDA 13.0
    • FileNotFoundError: [Errno 2] No such file or directory: '[internal path]

Timeouts, infrastructure, or incomplete jobs

@zianglih
zianglih force-pushed the agent/topk-tie-break-unsorted branch from 89c6496 to 138b943 Compare August 2, 2026 17:59
@yyihuang

yyihuang commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

/bot run

@flashinfer-bot

Copy link
Copy Markdown
Collaborator

GitLab MR !1093 has been updated with latest changes, and the CI pipeline #60826663 is currently running. I'll report back once the pipeline job completes.

@flashinfer-bot

Copy link
Copy Markdown
Collaborator

[FAILED] Pipeline #60826663 — 15/18 executed test jobs passed

Compared with nightly #60712014.

Unit Tests

GPU CUDA 12.9 CUDA 13.0 Notes
5090 ⚠️ Infra ⚠️ Infra Infrastructure: CI infrastructure failure (2 jobs; CUDA 12.9, CUDA 13.0)
B300 ✅ Pass ✅ Pass
GB200 ✅ Pass ✅ Pass
GB300 ✅ Pass ✅ Pass
H100 ❌ New ✅ Pass New: tests.attention.test_hopper (2456 failures; CUDA 12.9)
New: tests.gdn.test_prefill_delta_rule (1 failure; CUDA 12.9)
PR-related: tests.utils.test_topk (14 failures; CUDA 12.9)
… and 159 more
RTX Pro 6000 Blackwell ✅ Pass ✅ Pass

✅ Pass · 🟡 Old failure · ❌ New failure · ⏱ Test timeout · ⚠️ Infrastructure · ❔ Unknown or unclassified · — Not run

Multi-GPU and Multi-Node Tests — 6/6 passed

GPU CUDA 12.9 CUDA 13.0 Notes
B300 (multi-GPU) ✅ Pass ✅ Pass
GB200 (multi-node) ✅ Pass ✅ Pass
GB300 (multi-node) ✅ Pass ✅ Pass
Failure details

PR-related regressions

  • tests.utils.test_topk — 14 failures on H100 / CUDA 12.9
    • RuntimeError: FlashInfer requires GPUs with sm75 or higher

New relative to nightly (attribution uncertain)

  • tests.attention.test_hopper — 2456 failures on H100 / CUDA 12.9
  • tests.gdn.test_prefill_delta_rule — 1 failure on H100 / CUDA 12.9
    • RuntimeError: CUDA unknown error - this may be due to an incorrectly set up environment, e.g. changing env variable CUDA_VISIBLE_DEVICES after program start. Setting the availab…

Pre-existing failures

  • tests.attention.test_rope — 30254 failures on H100 / CUDA 12.9
    • RuntimeError: CUDA unknown error - this may be due to an incorrectly set up environment, e.g. changing env variable CUDA_VISIBLE_DEVICES after program start. Setting the availab…
  • tests.gemm.test_sm_constraint_gemm — 27648 failures on H100 / CUDA 12.9
    • RuntimeError: CUDA unknown error - this may be due to an incorrectly set up environment, e.g. changing env variable CUDA_VISIBLE_DEVICES after program start. Setting the availab…
  • tests.gemm.test_mm_fp4 — 23958 failures on H100 / CUDA 12.9
    • RuntimeError: CUDA unknown error - this may be due to an incorrectly set up environment, e.g. changing env variable CUDA_VISIBLE_DEVICES after program start. Setting the availab…
  • tests.attention.test_trtllm_gen_attention_prefill — 14804 failures on H100 / CUDA 12.9
    • RuntimeError: CUDA unknown error - this may be due to an incorrectly set up environment, e.g. changing env variable CUDA_VISIBLE_DEVICES after program start. Setting the availab…
  • tests.attention.test_trtllm_gen_mla — 14075 failures on H100 / CUDA 12.9
    • RuntimeError: CUDA unknown error - this may be due to an incorrectly set up environment, e.g. changing env variable CUDA_VISIBLE_DEVICES after program start. Setting the availab…
  • tests.attention.test_sliding_window — 11792 failures on H100 / CUDA 12.9
    • failed on setup with "RuntimeError: CUDA unknown error - this may be due to an incorrectly set up environment, e.g. changing env variable CUDA_VISIBLE_DEVICES after program star…
  • tests.gemm.test_mm_bf16 — 7563 failures on H100 / CUDA 12.9
    • RuntimeError: CUDA unknown error - this may be due to an incorrectly set up environment, e.g. changing env variable CUDA_VISIBLE_DEVICES after program start. Setting the availab…
  • tests.moe.test_trtllm_gen_routed_fused_moe — 3709 failures on H100 / CUDA 12.9
    • RuntimeError: CUDA unknown error - this may be due to an incorrectly set up environment, e.g. changing env variable CUDA_VISIBLE_DEVICES after program start. Setting the availab…
  • tests.attention.test_hopper_fp8_attention — 3704 failures on H100 / CUDA 12.9
    • RuntimeError: CUDA unknown error - this may be due to an incorrectly set up environment, e.g. changing env variable CUDA_VISIBLE_DEVICES after program start. Setting the availab…
  • tests.gdn.test_prefill_delta_rule — 3544 failures on H100 / CUDA 12.9
    • RuntimeError: CUDA unknown error - this may be due to an incorrectly set up environment, e.g. changing env variable CUDA_VISIBLE_DEVICES after program start. Setting the availab…
  • tests.gemm.test_bmm_fp8 — 3456 failures on H100 / CUDA 12.9
    • RuntimeError: CUDA unknown error - this may be due to an incorrectly set up environment, e.g. changing env variable CUDA_VISIBLE_DEVICES after program start. Setting the availab…
  • tests.gemm.test_groupwise_scaled_gemm_mxfp4 — 3456 failures on H100 / CUDA 12.9
    • RuntimeError: CUDA unknown error - this may be due to an incorrectly set up environment, e.g. changing env variable CUDA_VISIBLE_DEVICES after program start. Setting the availab…
  • … and 147 more failing test groups

Timeouts, infrastructure, or incomplete jobs

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

Thank you @zianglih.

Key change here is that previously if we set tie_break to small or large and set deterministic=False, the outputs were still deterministic and sorted because we silently sorted. This was not a guaranteed behavior

This PR seems to make the output not sorted so the non-promised behavior is changing. Non-blocking but wanted to note in this PR. Will trigger the CI one last time to get targeted testing and approve.

@bkryu

bkryu commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

/bot run tests/utils

@flashinfer-bot

Copy link
Copy Markdown
Collaborator

GitLab MR !1093 has been created, and the CI pipeline #61240246 is currently running. I'll report back once the pipeline job completes.

@bkryu
bkryu merged commit d9c97ac into flashinfer-ai:main Aug 5, 2026
31 of 32 checks passed
@flashinfer-bot

Copy link
Copy Markdown
Collaborator

[CANCELING] Pipeline #61240246: canceled

@zianglih
zianglih deleted the agent/topk-tie-break-unsorted branch August 5, 2026 18:42
bkryu pushed a commit that referenced this pull request Aug 9, 2026
<!-- .github/pull_request_template.md -->

## 📌 Description

@HumansAnd

SGLang's DeepSeek V4 indexer uses a compact page table in which one
entry represents 64 score positions. Its score rows may also have
padding between rows, and CUDA graph capture owns the translated and
raw-index output buffers. The current `top_k_page_table_transform`
contract assumes one page-table entry per score and allocates only the
translated output, so SGLang has to split this into `top_k`, score
re-gathering, compact-page translation, and output copies.

This PR extends the existing fused page-table transform for that layout:

- Add `page_size`, defaulting to `1` for backward compatibility.
- Add optional caller-owned `out` and `out_raw_indices` buffers. Raw
indices remain positionally aligned with translated indices, including
deterministic post-sort and `-1` padding; the two buffers must be
disjoint.
- Honor the input row stride instead of requiring tightly packed rows,
while preserving last-dimension contiguity and alignment-safe
vectorization.
- Propagate the contract through the Python API, TVM FFI binding, Radix
and Filtered implementations, graph-safe dispatch, deterministic
post-sort, and the trivial `length <= k` path.
- Extend only the operation-local trace template for `page_size`. Its
core one-output definition excludes destination buffers; raw-output
calls are deliberately not emitted and use a distinct routing identity
so Trace Apply falls back to the API.

For each selected local score index `idx`, the compact transform is:

```text
physical_page = src_page_table[
    batch_idx, page_table_row_start + idx // page_size
]
output = physical_page * page_size + idx % page_size
```

`page_table_row_starts` is measured in page-table entries, while
`row_starts` is measured in score elements. When `page_size > 1` and
`row_starts` is supplied, callers must therefore provide
`page_table_row_starts` explicitly rather than relying on the existing
shared-start behavior.

The C++ path uses policy types rather than a second boolean mode. The
FFI boundary selects `DirectPageTableKernelPolicy` or
`ConfigurablePageTableKernelPolicy` once, and the kernel ABI is derived
structurally from the policy type: an empty policy contributes zero
arguments, while a stateful trivially-copyable policy contributes one
object. Host dispatch carries one typed policy value through the
selection stack; only the terminal launch forms the zero-or-one argument
pack. The direct policy therefore adds no kernel argument or device
branch, while the configurable policy owns score-row layout,
logical-to-physical translation, and the optional raw-index sink. Future
transforms that share the flat row-selection contract can extend the
configurable policy without multiplying kernel variants or changing the
selection kernels. Direct translation sites retain their original
expressions so supported-path SASS is preserved exactly.

This is a clean extension of the API introduced in #4169: `page_size=1`,
omitted output buffers, and tightly packed inputs retain the existing
behavior and cluster fast path. [SGLang
#33237](sgl-project/sglang#33237) uses this API
to replace its DeepSeek V4 unfused workaround with one graph-safe
FlashInfer call.

## ⚡ Performance

Fresh performance validation compares the exact rebase base
`29196cf437778906c72630dc5d9850de547501de` with head
`cf0319a3497e252219544c0a8b4168c6ba598f88` on the same NVIDIA B200
(driver 580.126.09), CUDA 13.2.78, and PyTorch 2.13.0+cu132. Base and
head used separate editable source trees and JIT workspaces.
`benchmarks/bench_topk.py` is byte-identical on both sides (SHA-256
`57cd1ca61b38120380cb9ea7cf81ae3ee972484724bb2bb785649ae05cc199d9`).

The script uses CUPTI (`cupti-python` 13.2.0 and `nvidia-cuda-cupti`
13.2.75), 10 dry runs, 100 measured iterations, cold L2, and the median.
After #4295 it already sets `use_cuda_graph=False`, so no temporary
benchmark-source edit was needed and the CUPTI plus CUDA graph
instability is excluded.

Exact PR-body commands:

```bash
python3 benchmarks/bench_topk.py \
  --op dsa_topk --dtype bf16 --dsa-input-pattern dsa_relu \
  --dsa-case all --dsa-topk 2048 --tie-break

python3 benchmarks/bench_topk.py \
  --op varlen --dtype bf16 --length-dist causal \
  --varlen-k 2048 --varlen-q-len 128 --tie-break
```

After #4295 these exact commands keep `deterministic=False`: they
measure the default nondeterministic path plus SMALL/LARGE tie selection
without the canonical output-order sort. For fair DSA pairing, the
confirmation runs used the same command after `torch.manual_seed(1234)`
and `torch.cuda.manual_seed_all(1234)`. Current-mode DSA ran
base/head/head/base; varlen used base/head/head/base and its built-in
per-case seeds. Canonical-output coverage repeated both commands with
`--deterministic` on base and head. All comparisons use matched per-case
medians; negative PR delta means the head is faster.

| workload / mode | default PR delta (worst) | deterministic PR delta
(worst) | tie-small PR delta (worst) | tie-large PR delta (worst) |
|---|---:|---:|---:|---:|
| DSA, current ABBA | `+0.05%` (`+0.13%`) | n/a | `-0.09%` (`+0.08%`) |
`-0.01%` (`+0.12%`) |
| page-table varlen, current ABBA | `+0.01%` (`+0.10%`) | n/a | `+0.06%`
(`+0.19%`) | `+0.05%` (`+0.24%`) |
| ragged varlen, current ABBA | `+0.02%` (`+0.08%`) | n/a | `+0.01%`
(`+0.13%`) | `-0.02%` (`+0.14%`) |
| DSA, explicit deterministic | `+0.03%` (`+0.15%`) | `-0.09%`
(`+0.10%`) | `+0.01%` (`+0.41%`) | `-0.09%` (no regressed point) |
| page-table varlen, explicit deterministic | `+0.01%` (`+0.10%`) |
`-0.18%` (`+0.03%`) | `-0.09%` (no regressed point) | `-0.05%`
(`+0.09%`) |
| ragged varlen, explicit deterministic | `-0.01%` (`+0.06%`) | `-0.10%`
(`+0.01%`) | `-0.01%` (`+0.03%`) | `-0.13%` (`+0.08%`) |

All 12 varlen rows had identical `len_min`, `len_mean`, `len_max`, and
`triv%` across paired runs. Current-mode suite geomeans are within
`+0.06%`, the largest individual delta is `+0.24%`, and deterministic
geomeans are flat or faster apart from a `+0.01%` DSA tie-small geomean.
This supports no measurable kernel-latency regression after the final
rebase.

These sweeps exercise the existing direct compatibility paths
(`page_size=1`, contiguous rows, no caller-owned outputs). The
configured V4 path has no pre-PR API equivalent. Its policy cleanup was
separately checked by an eager ABBA host audit using `page_size=64`,
padded row stride, raw output, and all three production shapes: its
configured 12-case batched end-to-end geomean was `+0.053%`, with a
worst point of `+0.380%`.

The policy design also has complementary binary evidence from the
exhaustive audit performed after #4295:

```text
direct: 354/354 affected kernels, 0 normalized SASS/resource/KPARAM mismatches
  radix=180, filtered=132, finalizer=42
configured: 116 kernels
  radix=60, filtered=44, finalizer=12
  exactly one trailing 24-byte policy object; PageTable mode only
```

The direct variants retained their upstream parameter counts and
constant-bank spans. The configured variants add one policy object
without a second policy family or boolean template axis.

## 🔍 Related Issues

- SGLang integration: sgl-project/sglang#33237
- Independent score/page-table starts:
#4169
- SGLang packed-PAGED workaround and backend-selection fix:
sgl-project/sglang#32490
- SGLang DeepSeek V4 Top-K backend integration:
sgl-project/sglang#31087

## 🚀 Pull Request Checklist

Thank you for contributing to FlashInfer! Before we review your pull
request, please make sure the following items are complete.

### ✅ Pre-commit Checks

- [x] I have installed `pre-commit` by running `pip install pre-commit`
(or used your preferred method).
- [x] I have installed the hooks with `pre-commit install`.
- [x] I have run the hooks manually with `pre-commit run --all-files`
and fixed any reported issues.

> If you are unsure about how to set up `pre-commit`, see [the
pre-commit documentation](https://pre-commit.com/).

## 🧪 Tests

- [x] Tests have been added or updated as needed.
- [x] All tests are passing (`unittest`, etc.).

Validation used an editable source build on one NVIDIA B200 with CUDA
13.2.78 and PyTorch 2.13.0+cu132. The rebased head was synced to an
isolated source tree and JIT workspace on `flashinfer-pr4366-cu132`:

```text
pre-commit run --all-files
Passed

python3 -m pytest -q \
  tests/utils/test_topk.py::test_top_k_page_table_transform_misaligned_scores_without_row_starts \
  tests/utils/test_topk.py::test_top_k_page_table_transform_compact_pages_cuda_graph_replay
18 passed, 3 warnings in 93.98s

python3 -m pytest -q \
  tests/topk_varlen/test_topk_varlen.py::test_radix_preallocated_outputs \
  tests/topk_varlen/test_topk_varlen.py::test_out_values_ignored_when_return_values_false
6 passed, 39 warnings in 4.41s

python3 -m pytest -q \
  tests/trace/test_fi_trace_template_consistency.py \
  tests/trace/test_template_init.py \
  -k top_k_page_table_transform
6 passed, 1 skipped, 972 deselected, 3 warnings in 0.27s

python3 -m pytest -q tests/utils/test_topk.py
1495 passed, 3 warnings in 7.56s
```

Final CUDA 13.2 validation was rerun on rebased head
`cf0319a3497e252219544c0a8b4168c6ba598f88`. It covers the page-table
changes plus the optional-output overlap from #3901 after conflict
resolution. `git range-diff`, `git diff --check`, and `pre-commit run
--all-files` also passed.

The Top-K matrix covers Radix multi-CTA and Filtered dispatch,
graph-safe mode, deterministic mode, optional raw output for default and
compact page sizes, independent score/page-table starts, compact and
default page sizes, padded row strides, misaligned input bases, trivial
and selected rows, and CUDA graph replay with mutated inputs. The trace
checks cover the operation-local schema and default-argument
initialization. A separate smoke check also verified that the Python
`page_size <= 2**30` validation matches the native contract.

The warnings are existing CUTLASS DSL deprecations from
`tests/conftest.py` and `flashinfer/cute_dsl/utils.py`.

## Reviewer Notes

Review focus is welcome on the positional pairing of raw and translated
outputs across deterministic post-sort, the structural zero-or-one
policy ABI, and the page-table transform in the Radix multi-CTA and
graph-safe Filtered epilogues.




<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Added support for compact page tables with configurable page sizes.
* Added optional raw-index outputs alongside translated physical
indices.
  * Added reusable output buffers for flexible result storage.
* Improved support for empty rows, padding, non-contiguous inputs, and
physical-page remapping.
* **Bug Fixes**
  * Added validation for page metadata and output compatibility.
  * Improved CUDA graph and algorithm-path support.
* **Documentation**
* Updated API and tracing documentation for page sizes, physical
indices, and raw-index outputs.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
@kahyunnam kahyunnam added the op: misc norm, activation, sampling, RoPE, quantization, etc. label Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

op: misc norm, activation, sampling, RoPE, quantization, etc. run-ci

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants