Skip to content

fix(test): compare qsa_block_topk by score multiset, not indices (tie-invariant) - #694

Closed
zhangnju wants to merge 1 commit into
FlashML-org:mainfrom
zhangnju:fix-qsa-block-topk-ties-test
Closed

zhangnju wants to merge 1 commit into
FlashML-org:mainfrom
zhangnju:fix-qsa-block-topk-ties-test

Conversation

@zhangnju

@zhangnju zhangnju commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Problem (ROCm-specific)

tests/models/qwen4_exp/test_qsa_kernels.py::test_block_topk_matches_torch_topk[ties-4-4096-512] fails on ROCm (gfx1201 / R9700) with a mismatch count that varies run to run (e.g. 41/2048, then 80/2048 — so it is also flaky). On CUDA it passes, which is why NV CI never caught it.

Measured, torch 2.14 / Triton 3.8, same test:

Backend index comparison (old) score comparison (this PR)
R9700 / ROCm 7.14 (gfx1201) FAIL, flaky (41–80 / 2048, varies run-to-run) PASS, deterministic (3/3)
RTX 4090 / CUDA 13 PASS (0 / 2048, every run) PASS

Root cause

The test compared the top-k selection by index against torch.topk. In ties mode the scores are randint(0, 3), so the top-512-of-4096 boundary is heavily tied. qsa_block_topk breaks ties deterministically by lowest column id — its documented design (qsa/topk.py: "candidates stay in ascending column order throughout, so ties still go to the lowest column").

torch.topk's tie-breaking is implementation-defined (the API does not guarantee which of several equal elements are returned), and the two backends differ:

  • CUDA resolves ties toward the lowest index, deterministically, so it coincidentally matches the kernel and the index comparison passes.
  • ROCm resolves ties differently and nondeterministically run to run, so the returned index set differs from the kernel's and the mismatch count varies.

It is the same torch version on both (2.14.0, +cu130 vs +rocm7.14), so this is a backend-kernel difference, not a version issue. Both backends return a valid top-k; the test was relying on torch.topk's unspecified, backend-specific tie order happening to match the kernel's lowest-column convention, which only holds on CUDA.

Fix

Top-k with ties is a set of scores, not of indices: the winning score multiset is unique even when which tied column fills a given slot is not. The test now compares the sorted selected-score multiset (empty -1 slots counted as -inf) instead of the index set — platform-independent, and no longer dependent on torch.topk's tie order. The kernel is unchanged; the structural assertions (live count, front-packing, ascending order) are kept.

Validation

See the table: green on both backends, and deterministic on ROCm where the old index comparison was flaky.

…-invariant)

test_block_topk_matches_torch_topk compared the top-k selection by index against torch.topk.
In "ties" mode the selection boundary is heavily tied, and the kernel breaks ties deterministically
by lowest column id (its documented design) while torch.topk on GPU breaks ties by its own order --
not lowest-index, and not deterministic run-to-run. Both pick a valid top-k, but their index sets
differ on the tied boundary, so the index comparison fails with a run-to-run-varying mismatch count.

Compare the winning score multiset instead (-1 slots counted as -inf), which every valid top-k
shares. The kernel is unchanged; the structural assertions (live count, front-packing, ascending
order) are kept.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant