Skip to content

ggml-cuda: enable CUB via hipCUB on HIP for device-wide primitives - #26388

Closed
singulared wants to merge 1 commit into
ggml-org:masterfrom
singulared:hipcub-port
Closed

ggml-cuda: enable CUB via hipCUB on HIP for device-wide primitives#26388
singulared wants to merge 1 commit into
ggml-org:masterfrom
singulared:hipcub-port

Conversation

@singulared

Copy link
Copy Markdown

Overview

Enable the CUB code paths on HIP by using hipCUB (the CUB-compatible API that ships with rocPRIM), so AMD builds stop falling back to the slow/CPU paths for device-wide primitives.

Today GGML_CUDA_USE_CUB is only defined for CUDA (CUDART_VERSION >= 11070), so on HIP:

  • argsort / top_k with ncols > 1024 exceed the single-block bitonic path and fall back to the CPU backend. For DeepSeek-Sparse-Attention models (DeepSeek-V4, DeepSeek-3.2, GLM-DSA, MiniMax-M3) the per-token indexer top_k runs over the whole context, so this fallback is hit on every token, with cost growing as context grows.

  • sum, mean, cumsum and ssm_scan use their inefficient fallbacks. ggml/src/ggml-cuda/sum.cu already notes this:

    // For AMD there is rocPRIM which could be used as a drop-in replacement via hipcub but this would require...

Implementation

common.cuh enables the existing CUB paths when hipCUB is available:

#elif defined(GGML_USE_HIP) && !defined(GGML_HIP_NO_HIPCUB) && __has_include(<hipcub/hipcub.hpp>)
#    define GGML_CUDA_USE_CUB
#    define GGML_HIP_USE_HIPCUB
  • Feature-detected, so ROCm installs without hipcub-dev keep building exactly as before.
  • Opt-out via -DGGML_HIP_NO_HIPCUB.
  • Call sites are unchanged — each translation unit does namespace cub = hipcub; and pulls in the specific symbols it uses, rather than dumping the namespace globally.
  • vendors/hip.h gains the cudaStreamIsCapturing / cudaStreamCaptureStatus mappings the CUB paths need.

8 files changed, 53 insertions(+), 7 deletions(-).

Correctness

test-backend-ops test passes on the ROCm backend for every affected op: ARGSORT, TOP_K, SUM, MEAN, CUMSUM, SSM_SCAN.

Performance

Hardware: Radeon 8060S iGPU (gfx1151, Strix Halo), 128 GB unified memory, ROCm 7.2.4, Debian.

Both arms built from the same commit, differing only by this patch. llama-bench -p 0 -n 64 -d <depth> -fa 1 -mmp 0, ROCm backend.

DeepSeek-V4-Flash (284B-A13B, IQ2_XXS, KV q4_0, -b/-ub 2048), tg t/s:

depth master this PR delta
0 12.60 ± 1.15 12.65 ± 0.09 +0.4%
8192 10.54 ± 0.40 11.95 ± 0.79 +13%
32768 8.27 ± 0.17 11.36 ± 0.35 +37%
65536 7.54 ± 0.14 9.81 ± 0.21 +30%
131072 6.40 ± 0.08 8.02 ± 0.15 +25%

Depth 0 is the natural control: with no context there is no indexer top_k work and the two builds are identical. The gain appears as soon as context exists and stays ≥25% out to 128K — consistent with removing a fallback whose cost scales with depth.

Models that do not use these ops are unaffected, as expected:

model class tg @32k master → this PR
Qwen3.6-35B-A3B Q4_K_M MoE (routing uses the fused topk-moe path, ≤256 cols) 47.72 → 46.62
Gemma-4-31B Q4_K_XL dense 8.40 → 8.46

Those two are within run-to-run noise (arms were run sequentially, so the second arm sees slightly warmer hardware).

Requirements

The device-wide CUB primitives (DeviceRadixSort / DeviceSegmentedRadixSort /
DeviceSegmentedSort / DeviceReduce / DeviceScan) were compiled only for CUDA
(GGML_CUDA_USE_CUB gated behind !defined(GGML_USE_HIP)). On HIP this forced
argsort / top_k over rows > 1024 onto the CPU backend, and sum / mean / cumsum /
ssm-scan onto their fallback paths.

hipCUB exposes a CUB-compatible interface over rocPRIM, so the same code compiles
on HIP with a namespace alias. This enables the GPU path via
__has_include(<hipcub/hipcub.hpp>) (opt-out with GGML_HIP_NO_HIPCUB).

Motivation: DeepSeek sparse-attention (DSA / lightning indexer, DeepSeek-V4) runs
a top_k every decode step; on HIP that fell back to the CPU with cost growing with
context depth. Measured on gfx1151 (Strix Halo), deep-context decode improves and
the per-token CPU round-trip is removed.

Requires rocPRIM/hipCUB >= 7.2: 6.4.3 fails on RDNA (gfx1151, wave32) with a
'WarpSize > 32 without DPP broadcasts' static assert because it defaults
__AMDGCN_WAVEFRONT_SIZE to 64 under LLVM that no longer predefines it.

Validated: test-backend-ops -o ARGSORT/TOP_K/SUM/MEAN/CUMSUM pass on ROCm0
(shapes to 1M columns, exercising the CUB path). Stream-capture guard in argsort
mirrored via hipStreamIsCapturing mappings in vendors/hip.h.
@singulared
singulared requested review from a team and IMbackK as code owners July 31, 2026 21:04
@github-actions github-actions Bot added ggml changes relating to the ggml tensor library for machine learning CUDA Related to the CUDA backend labels Jul 31, 2026
@ggml-gh-bot

ggml-gh-bot Bot commented Jul 31, 2026

Copy link
Copy Markdown

Hi @singulared, thanks for your contribution!

Per our contribution guidelines, the automated PR checker found the following issue(s) that need your attention:

  • PR Template not respected: Please respect the template when creating a new pull request. Make sure to fill out all required sections.

  • AI-generated content: While code is allowed to be generated by AI, please write the PR description and commit messages on your own without the help of AI.


Please note that maintainers reserve the right to make final decisions on PRs. If you believe there is a mistake, please comment below.

@singulared
singulared marked this pull request as draft July 31, 2026 21:09
@singulared

Copy link
Copy Markdown
Author

Oops, my mistake. I use AI generated PR body. Will recreate it latter.

@singulared singulared closed this Jul 31, 2026
@matt23654

matt23654 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

I tested this PR to solve #26399 (missing topk on ROCm). On latest ROCm nightly it does not compile without modification because llama.cpp header files define a __trap macro and ROCm hipcub header files define a __trap function, so there is a conflict. There is probably a better way to solve it, but I got it to compile by guarding the include like this:

#        pragma push_macro("__trap")
#        undef __trap
#        include <hipcub/hipcub.hpp>
#        pragma pop_macro("__trap")

Once it compiled, this PR also fixed the missing topk problem for ROCm for me on RDNA 3.5/Strix Halo.

@singulared

Copy link
Copy Markdown
Author

Thanks for your review. I will take a look at this today, slightly later.

Aristo94 added a commit to Aristo94/EngramHalo.cpp that referenced this pull request Aug 27, 2026
ggml_top_k/argsort had no HIP path for ne0 > 1024, so sparse-attention
indexers (qwen4exp QSA: 12 calls per decoded token) fell back to the CPU,
collapsing long-context decode. Adds a wide selection kernel tuned for
wave32/RDNA 3.5. Earlier hipCUB-based attempts: ggml-org#26592, ggml-org#26388.
Aristo94 added a commit to Aristo94/EngramHalo.cpp that referenced this pull request Aug 28, 2026
ggml_top_k/argsort had no HIP path for ne0 > 1024, so sparse-attention
indexers (qwen4exp QSA: 12 calls per decoded token) fell back to the CPU,
collapsing long-context decode. Adds a wide selection kernel tuned for
wave32/RDNA 3.5. Earlier hipCUB-based attempts: ggml-org#26592, ggml-org#26388.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CUDA Related to the CUDA backend ggml changes relating to the ggml tensor library for machine learning

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants