Repository navigation
Conversation
FLARE_CANDIDATE_MERGE_FUSED=1 (read when a merge is prepared; set it identically for export and serving) replaces the _threshold launch + _counts launch of the DSA candidate merge with one _threshold_counts launch: every (row, tile) program takes the cutoff (minimum of the K largest scores) itself, tile 0 publishes it and writes the -1 fill that _select overwrites. 6 -> 5 launches per merge. The fused variant is a separate native contract (flare.candidate_merge_fused), so the default query, compile payload and cache keys are unchanged. Integer-identical output (selected IDs and order): 800 random cases (rows 1..16, width 8192/16384, valid 0..width, distinct and tied scores, poisoned scratch) and 10,000 graph replays per case at rows 1/4/8, 0 differences; candidate-merge tests 6 passed off and on + new test_candidate_merge_fused_cuda.py (3). CUDA graph us/merge (TP8 width 16384, RTX PRO 6000, 3 runs): valid 1500 6.30 -> 5.59 (1 row), 6.62 -> 5.86 (4), 6.95 -> 6.21 (8); valid 16384 20.00 -> 19.18, 21.49 -> 20.70, 21.99 -> 21.24. A single-program finalize (threshold+counts+select) was exact but 12 us slower whenever more than 2048 candidates are valid and is not used.
FLARE_CANDIDATE_MERGE_RADIX=1 (default off): one Triton program per row finds the K-th largest order key of the compacted candidates exactly (8-bit MSD radix passes with tl.histogram, early exit once the digit holding the K-th key holds exactly the keys still needed), takes the cutoff with the same tl.min the threshold kernel applies to the top-k values, and writes the per-tile greater/tie counts and -1 fill for the unchanged _select. Rows with at most K candidates copy their compacted ids as the row top-k does. This replaces the b12x row top-k, the threshold and the cutoff-count kernels: 6 -> 4 launches per merge. Own contract flare.candidate_merge_radix; default query, contract and cache keys are unchanged. Takes precedence over FLARE_CANDIDATE_MERGE_FUSED. Merges wider than 16384 keep the default contract. Output (selected ids and their order) is identical to the default merge, including ties, -inf, signed zeros and NaN scores. TP8 width 16384, top-k 2048, CUDA graph on SM120: 20.0 -> 12.3 us (1 row), 21.8 -> 13.7 us (8 rows) when more than 2048 candidates are valid; 6.2 -> 4.0 us at short context.
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.
📌 Description
FLARE_CANDIDATE_MERGE_RADIX=1(default off). It is read when a merge is prepared, so set it identically for export and serving. The DSA candidate merge uses the b12x row top-k (run_row_topk, a 1024-thread single-CTA radix over 10-bit FP16 coarse bins) for only two things: the cutoff (tl.minof the top-K values, when more than K candidates are valid) and the compacted ids when at most K are valid. This PR replaces the row top-k, the threshold kernel and the cutoff-count kernel with one new Triton kernel,_radix_counts:tl.histogrampasses. Keys are FP32 bits mapped to the same unsigned total order the b12x radix uses, and the passes exit early once the K-th key's digit bin holds exactly the keys still needed.tl.min(Tritonminnumf) over that top-K set, with NaN padding neutral. It writes the per-512-tile greater/tie counts and the -1 fill._selectis unchanged, so the selected ids and their order are identical to the default. Rows with at most K valid candidates copy their compacted ids, as the row top-k emits them._count,_compact,_radix_counts,_select. It has its own native contract,flare.candidate_merge_radix. Default query, contract and cache keys are unchanged. Merges wider than 16,384 (TP8 x top-2048; 16-bit count fields, row in registers) silently keep the default contract, which is covered by a test.Relation to #10 (
FLARE_CANDIDATE_MERGE_FUSED): #10 folds the threshold kernel into the counts kernel (6 -> 5 launches, about -0.75 us). It keeps the row top-k, which is the expensive part at long context. This PR removes the row top-k as well, so it supersedes #10 when both are set: RADIX takes precedence over FUSED. #10 remains useful on its own, for example above the 16,384 width limit. #10's CUDA test unsetsFLARE_CANDIDATE_MERGE_RADIXfor isolation; that is a one-line change.CUDA graph, us per merge (TP8 width 8 x 2048, top-k 2048, RTX PRO 6000 Max-Q, 200 calls per graph, median of 30, 3-run medians):
At 4,096 valid candidates the gain is smaller: 10.7 -> 10.5 (normal) to 15.0 -> 13.3 (ties) at 1 row. Profiler split at 1 row, long context: the row top-k is 13.7 / 15-32 / 18-33 us (normal / band / ties), the threshold 1.5 and the counts 1.3.
_radix_countsis 8.5 / 9.8 / 10.7 us.🧪 Tests
On this rebased branch (single RTX PRO 6000 Max-Q):
test_candidate_merge_radix_cuda.py(new: 5 shapes including width 3000 / top-k 1024 / top-k 512, specials, poisoned scratch, CUDA graph, off vs on bitwise),test_candidate_merge_fused_cuda.pyandtest_candidate_merge_cuda.pywere run withFLARE_RUN_CUDA_TESTS=1: 15 passed with the switch off, 15 passed with it on.tests/recipes/test_kimi3*,test_kimi31*,tests/components/test_candidate_merge*,test_collective*): 5 F / 148 P on Optionally fold the DSA candidate-merge threshold into the cutoff counts #10 and on this branch, with an identical failure set (pre-existing).On our pre-rebase tree (release branch + these patches), the same gates also passed, plus 5,280 top-2048 cases at width 16384, and the switch has been in our deployment since then.
Topology assumptions
Generic (single GPU, any TP). The width limit 16,384 matches TP8 x top-2048. Wider merges keep the default path.
Reviewer Notes
tl.minsemantics were chosen to reproduce the row top-k's result bit for bit, including NaN and signed zero. If the row top-k's tie order ever changes, this contract has to follow it; the CUDA test compares off vs on bitwise.