Conversation
… transform fast_topk_cuda_tl_impl in kernels/jit/csrc/dsa/kpool_topk_transform.cuh stashes the stage-1 threshold bin in a 4096-entry shared buffer. When a bin held more candidates (tied or tightly clustered score rows), the stash kept the first 4096 by atomic arrival order and dropped the rest, so the refine rounds ranked a subset that did not contain every true top-K member (286 of 512 groups differed from torch.topk on a row with 9407 candidates in one bin). The stash counters and the threshold state were also reset only inside the threshold-finder branches, and the index[pos] / index[K - pos] stores had no bound. Every reset is now unconditional, the selection slots start at -1, and every store is guarded. Changes: - When the stage-1 threshold bin exceeds the stash, descend the remaining key bytes along the chosen byte path until the bin fits; a clip is only applied at full 32-bit key equality, where every remaining candidate is tied. - Partition every stage on bytes of the FP32 monotone key; remove the now-unused FP16 key helper. (The base's FP16 stage-1 key was monotone and not itself a defect; the re-keying gives the descent one key.) - Reset s_threshold_bin_id, s_counter and the stash counters before every finder, independently of it; initialize the selection slots to -1 and emit a -1 pad column for an unfilled slot instead of indexing the page table with it; bound every index[] store; static_assert that K fits one stash round. - Scores must be finite; NaN ordering is unspecified (unchanged from the base's sign-dependent placement). Motivation: a GLM-5.3-Flash W4A16 TP2 deployment on RTX PRO 6000 Blackwell hit Xid 31 with a coredump naming this kernel and a shared store at stash position 4101 (reported in ormandj/sglang-glm53-flash-sm120#3 and sgl-project#4). How the base reaches that position from a valid input is not established; a sanitizer reproducer on that architecture is still to be provided. Adds test/registered/kernels/test_dsa_kpool_topk_transform.py: clustered rows beyond the stash, all-equal rows, clusters separating at each key byte, the exact-fill boundary, length == K + 1, uniform batches, and page-table / offset expansion, each compared against torch.topk by value multiset. The clustered-row cases fail at the base. Not measured here: the added barriers and the descent's full-row passes against the base on representative score distributions. Co-authored-by: Benjamin Oldenburg <benjamin.oldenburg@ordis.co.th>
ormandj
requested review from
BBuf,
DarkSharpness,
HaiShaw,
HydraQYH,
celve and
yuan-luo
as code owners
September 2, 2026 04:37
This was referenced Sep 2, 2026
Merged
3 tasks
Contributor
Author
Open
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Base commit: cf0f761.
Motivation
kpool_topk_transform_kernel<K>(python/sglang/kernels/jit/csrc/dsa/kpool_topk_transform.cuh) selects the topKpool groups of a score row with a two-stage radix select and expands them to token indices for the DSA kpool indexer.Reported production failure: on a GLM-5.3-Flash W4A16 TP2 deployment on two RTX PRO 6000 Blackwell GPUs, a long-prefill workload produced Xid 31 on both ranks in the same second; the driver coredump named
kpool_topk_transform_kernel<512>with a shared-memory store at offset 39,204 against a 39,184-byte window, i.e. stash position 4101 of the second stash buffer. Reported by @bold84 in ormandj/sglang-glm53-flash-sm120 issue #3 and PR #4, and reproduced on the reporter's deployment and on a second W4A16 TP2 RTX PRO 6000 deployment. How the base kernel reaches that position from a valid input is not established: under the source-level invariant every threshold finder fires, so every stash counter is reset each round and every stash position is bounded by the stash count. Acompute-sanitizerreproducer on the affected architecture is still to be provided; this PR does not claim to have reproduced the fault.Selection defect verified from the base source: rows whose stage-1 threshold bin holds more than 4096 candidates (tied or tightly clustered scores) select the wrong groups. Author report from the investigation: on the diverging input (9407 candidates in one bin), 286 of the 512 selected groups differed from
torch.topk.Modifications
atomicAddkept the first 4096 by arrival and the guarded store dropped the rest, so the refine rounds ranked a subset. Now, if the stage-1 threshold bin holds more than the stash, the kernel fills the definite members above the bin, then walks key bytes 16, 8 and 0 restricted to the chosen byte path, recomputing histogram, cumsum and threshold at each level and filling that level's above-threshold members, until the remaining bin fits the stash or the last byte is reached, where the clip drops exact ties only.index[pos]store is guarded bypos < K(the round-3 fill bypos > 0 && pos <= K), stash stores bypos < SMEM_INPUT_SIZE;static_assert(K < SMEM_INPUT_SIZE).index[]is initialized to -1 before selection, and the expansion writes -1 and skips the page-table lookup forgroup_id < 0.deepseek_v4/topk_v1.cuhdo). The indexer produces FP32 relu-weighted FP8 dot products with-infonly as padding outside the row.Author report: the fix was contributed by @bold84 in ormandj/sglang-glm53-flash-sm120#4.
Accuracy Tests
test/registered/kernels/test_dsa_kpool_topk_transform.py(register_cuda_ci,base-b-kernel-unit,1-gpu-large,pool_size = 4, group top-k 512 and 256). Each row is compared withtorch.topkby sorted value multiset (any subset of groups tied at the K-th value is accepted); expanded columns are checked to begroup_id * pool_size + slot, distinct, belowlength, and never the -1 pad; rows are padded with+infbeyondlengths. Cases: clusters of 9407, 17802 and 4097 entries in one stage-1 bin, which exercise the clipped-selection path; a 17802-entry row batched with aK + 1row; 4097 and 8193 identical values; 4097/4097 clusters first differing at key bit 16, 8 or 0; exactly K distinct values above a 65,536-way tie;length == K + 1; uniform rows of several lengths; expansion through a biased page table and throughtopk_indices_offset. The test proves the clipped-selection defect; it does not exercise the reported out-of-bounds store.Author report, one RTX PRO 6000 Blackwell (SM120, fresh JIT cache): on this branch the file passes (8 passed, 28 subtests); with the base kernel (cf0f761) substituted, 10 of 16 cases fail on the clustered rows (wrong groups selected), the rest pass.
compute-sanitizer --tool memcheckontest_stage1_bin_exceeds_stashreports 0 errors for both the base and this branch, so the reported out-of-bounds store is not reproduced by these inputs on this device. The following checks were also run:clang-format20.1.7 with the JIT.clang-format,black,isort,ruff,codespell,scripts/lint/check_registered_tests.py; the constructed rows were checked on CPU to land in a single stage-1 bin; the header compiles as a CUDA translation unit with the JIT's flags for SM120 for bothKvalues (nvcc -c, no execution).Contributor-reported on ormandj/sglang-glm53-flash-sm120#4, not re-run here: exact value-set agreement with
torch.topkover 12,000 randomized structured rows;compute-sanitizer --tool memcheckwith 0 errors on tied, clustered, boundary and page-table cases including the issue-#3 reproducer; the previously faulting workload (four 150k to 170k-token prefills followed by a 173,504-token restore with concurrent decode, TP2) passed 3/3.Speed Tests and Profiling
The radix path executes six additional block-wide barriers per row. Rows whose stage-1 bin exceeds the stash run a histogram pass, a fill pass and a stash pass per descent level instead of clipping the bin. Using FP32 key byte 24 for stage 1 creates coarser initial bins than the base's FP16 top byte: for uniformly distributed
[0, 1)scores about half the values share the[0.5, 1)bin and exceed the 4096-entry stash at row lengths above about 8192.Author report, microbenchmark on one RTX PRO 6000 Blackwell (8 rows per call,
pool_size4,topk2048, fp32 scores, 50 timed calls after warmup), base cf0f761 against this branch:Uniform scores take the overflow descent from 8192 entries on; normally distributed scores mostly do not. Real indexer logits were not benchmarked.
Checklist
Prepared with AI assistance.