ROCm: add radix TOP_K for long rows - #27466
Conversation
|
Hi @jadenmach2, thanks for your contribution! Per our contribution guidelines, the automated PR checker found the following issue(s) that need your attention:
Please note that maintainers reserve the right to make final decisions on PRs. If you believe there is a mistake, please comment below. |
743d1a4 to
ff7cd32
Compare
IMbackK
left a comment
There was a problem hiding this comment.
While we would like to have just one code path here, i think this is a good idea take as a stop gap until the hipCUB hipGraph interaction is fixed in rocm
| if (ncols > 1024) { | ||
| top_k_radix_cuda(pool, src0_d, dst_d, ncols, nrows, k, stream); | ||
| } else { | ||
| #endif |
There was a problem hiding this comment.
missing comment
// defined(GGML_USE_HIP)
| return true; | ||
| #else | ||
| return op->src[0]->ne[0] <= 1024; | ||
| #endif |
There was a problem hiding this comment.
missing comment
// defined(GGML_USE_HIP) || defined(GGML_CUDA_USE_CUB)
| cudaMemcpyDeviceToDevice, stream)); | ||
| #if defined(GGML_USE_HIP) | ||
| } | ||
| #endif |
There was a problem hiding this comment.
missing comment
// defined(GGML_USE_HIP)
|
I tested the performance of this on NVIDIA RTX PRO 6000 Max-Q and it's pretty good, beats CUB argsort-based and DeviceTopK-based implementations for almost all tested shapes (and it's close enough for the few slower ones). Maybe we could simply use it as the default TOP_K implementation for now? Reddish is this PR slower, blueish is this PR faster. PR 27466 vs argsort (k = 1024)
PR 27466 vs argsort (k = 64)
PR 27466 vs DeviceTopK (k = 64)
CC @ORippler |
|
Id like to also test it on RDNA/CDNA vs hipCUB before doing so, but yeah sure. |
@IMbackK Sure, it's just a vibe-coded thing that accepts two CSV files with ncols,nrows,time columns (I use time per run gathered with sed from the test-backend-ops perf output |
There was a problem hiding this comment.
Maybe we could simply use it as the default TOP_K implementation for now?
Id like to also test it on RDNA/CDNA vs hipCUB before doing so, but yeah sure.
I'd also like to see this tested
- for smaller k (typically used for backend-sampling like 20/40)
- on more SKUs (can test on what I have available)
- on more OSs (Windows may take longer to launch the 5 kernels here as opposed to Linux)
if we want to make it the default path
| top_k_radix_state * states = states_alloc.get(); | ||
| int * histograms = histograms_alloc.get(); | ||
|
|
||
| top_k_radix_init<<<(nrows + BLOCK_SIZE - 1) / BLOCK_SIZE, BLOCK_SIZE, 0, stream>>>(states, nrows, k); |
There was a problem hiding this comment.
we should really have a ceildiv function in the cuda backend 😄
|
|
||
| top_k_radix_init<<<(nrows + BLOCK_SIZE - 1) / BLOCK_SIZE, BLOCK_SIZE, 0, stream>>>(states, nrows, k); | ||
|
|
||
| const dim3 histogram_grid(blocks_per_row, nrows); |
There was a problem hiding this comment.
This is going to hit the 16bit limit of griddim.y/z (on CUDA at least). Either guard dispatch, chunk, or fold into griddim.x:
There was a problem hiding this comment.
Thank you for the review, I will push the changes
|
I want to add a testpoint with Q3.8-27B, 2xR9700, any advice on the settings I should use to trigger the paths? Or just a simple baseline with long context from 60k to 120k np 1..3 with MTP from 0..4? Current Settings Baseline
|
RDNA3.5 (gfx1151 / Strix Halo APU) datapoint — +1New SKU: Radeon 8060S iGPU (gfx1151, RDNA3.5), ROCm 7.2.1, on current master. End-to-end — Qwen3.8-Flash-Next UD-IQ4_XS (its QSA indexer runs
Long-context decode collapse gone, and the radix kernels capture into HIP graphs fine. vs hipCUB (re: @IMbackK) — I also tried a hipCUB Happy to run more shapes if useful. Nice work! |
|
2X R9700, Powercapped at 230W, Settings
Results Detailed
|
|
you can defiantly find shapes where this performs poorly against the cub path (1 row), so from a hip perspective we may want to keep it around until amd fixes the graph capture problem, or roll another own solution DetailsGFX908:
GFX1100:
GFX1201
|
IMbackK
left a comment
There was a problem hiding this comment.
Its good as is for the purposes of supporting ncols > 1024 on hip.
|
Tested this PR on Strix Halo (Ryzen AI Max+ 395 / Radeon 8060S, gfx1151, ROCm 7.1), applied onto master Correctness: HIP graph capture: safe. Three consecutive 2500-token speculative-decoding generations with graphs enabled, zero incidents. This is notable because the hipCUB alternative (#26592) aborts capture deterministically — Single-row decode (llama-bench tg64, UD-IQ4_XS 93.7 GB): parity with the hipCUB path, both a big win over master's CPU fallback:
Multi-row wide case — tuning opportunity: with speculative decoding, TOP_K arrives as ~65 rows × 24k cols during draft verification. There the hipCUB segmented sort is ~27% faster end-to-end on our workload (21.3 vs 16.1 tok/s on a file-rewrite task at 24k-token context). Possibly the Net: for graph compatibility this PR is currently the only working GPU TOP_K on ROCm, and single-row performance matches CUB. Would love to see it land. |
cant repo that either at 100k context could you provide more information? |
I'm using Unsloth's Q4_K_XL https://huggingface.co/unsloth/Qwen3.8-Flash-Next-GGUF/tree/main/UD-Q4_K_XL with Machine is Strix Halo, I unfortunately can't paste exact prompts/context for repro |
|
rocm version, and compile time options please. |
Right, sorry; |
* ROCm: add radix TOP_K for long rows
shouldn't this be -DGGML_HIP_UMA ? instead of UMAD |
Neither exists atm i guess but it doesnt matter for this, my bad in any case - ive probably copied it from one of the random gfx1151 repos floating around in the past Someone reported the same garbage output pointing at an earlier commit - #28113 |
* ROCm: add radix TOP_K for long rows
Enable the preserved 0005r1 fallback for CUB builds without DeviceTopK at 8192 columns or more. Retain the argsort control switch and existing radix kernels. Document source provenance, bounded quality evidence, toolkit eligibility, and a manual benchmark client. Related upstream work: ggml-org#28366 and ggml-org#27466. Assisted-by: Codex Astra
CUDA builds with CCCL < 3.2 have no cub::DeviceTopK, so ggml_top_k falls back to a full argsort + copy (~12 bytes of temp per cell). For sparse-attention top-k over long contexts this OOMs small cards (200K cols x 128 rows ~= 190MB+ of temp per layer). Reuse the exact radix selection from ggml-org#27466 (merged for HIP) on CUDA when CUB_TOP_K_AVAILABLE is off: O(nrows) temp, same selected set. Also gate next_power_of_2, unused on that path now.
CUDA builds with CCCL < 3.2 have no cub::DeviceTopK, so ggml_top_k falls back to a full argsort + copy (~12 bytes of temp per cell). For sparse-attention top-k over long contexts this OOMs small cards (200K cols x 128 rows ~= 190MB+ of temp per layer). Reuse the exact radix selection from ggml-org#27466 (merged for HIP) on CUDA when CUB_TOP_K_AVAILABLE is off: O(nrows) temp, same selected set. Also gate next_power_of_2, unused on that path now.
* ROCm: add radix TOP_K for long rows
* ROCm: add radix TOP_K for long rows
Keep USB4 RPC, Flash-Next NextN, DFlash LM-head gather, meta shard guards, HIP TOP_K supports_op, and AVX-512 AllReduce. Take upstream radix TOP_K (ggml-org#27466) and RPC dispatcher check (ggml-org#26500) combined with view-src tensor data offsets.
* ROCm: add radix TOP_K for long rows
With GGML_HIP_CUB=ON, GGML_CUDA_USE_CUB is defined on HIP, which hid the radix select added upstream in ggml-org#27466 behind its !GGML_CUDA_USE_CUB guard. TOP_K on wide rows then fell back to a full hipCUB segmented sort plus a copy. Radix select only scans the row, so for wide inputs it is much faster. Measured with test-backend-ops perf on an RX 7900 XT (gfx1100): ne=[151936,1], k=20/40 148/136 us -> 119/122 us (1.1-1.2x) ne=[200000,16], k=16 1061 us -> 187 us (5.7x) ne=[131072,4], k=2048 708 us -> 121 us (5.9x) Below 32k columns the fixed launch cost of the multi-pass radix kernel (~100 us) loses to the bitonic/hipCUB path, so only rows of at least 32768 columns are routed to radix. ARGSORT and the other ops keep using hipCUB, which is still what lifts their ne[0] <= 1024 limit. TOP_K: 525/525 test-backend-ops cases pass on ROCm0.




Overview
This adds a ROCm TOP_K path for rows larger than 1024 elements. The current non-CUB path uses the bitonic implementation for small rows, but larger rows are reported as unsupported.
The new path uses exact 8-bit radix selection. Small rows continue to use the existing bitonic path, and the implementation does not depend on hipCUB.
The main motivation is DeepSeek-V4 long-context decoding, where the lightning indexer produces a TOP_K row over the KV history.
Performance
I tested this with DeepSeek-V4 Flash GGUF (155 GB) on three MI250X devices using ROCm 7.2. The numbers below are median generation throughput over 10 repetitions:
Testing
test-backend-ops test -o TOP_K -b ROCm0Requirements