You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
ROCm 7.2.4. Note that llama-server --version on these images reports version: 1 (81a8712) — the fork stamps its own build number and commit, so the upstream base is identifiable only from the image tag (b10187 / b10207).
Both builds reproduce identically. b10207 is ~20 upstream builds newer than b10187, so this is not a regression introduced in that window and not specific to the gfx906 fork.
Operating systems
Linux
GGML backends
HIP
Hardware
6 × AMD Instinct MI50, gfx906:sramecc+:xnack- (0x66a1), 32 GiB each
Uniform PCIe, 2 hops, all NUMA node 0, no XGMI
Host: 8-core AMD CPU, 503 GiB RAM
Fully GPU-resident across all 6 cards. No CPU expert offload. deepseek4 is layer-split only on this backend (--split-mode tensor errors, row fails on gfx906).
Environment: HSA_OVERRIDE_GFX_VERSION=9.0.6, HIP_VISIBLE_DEVICES=0,1,2,3,4,5, ROCBLAS_USE_HIPBLASLT=0 (hipBLASLt ships no gfx906 Tensile kernels).
Problem description
Token generation is flat through depth 3072 and then collapses by 6.4× at depth 4096. Prompt processing degrades smoothly across the same range, with no corresponding discontinuity.
GGML_SCHED_DEBUG=2 with llama-server -v. Op × backend assignment across the whole graph:
189 CPU TOP_K
10 CPU GET_ROWS
This is the sched_reserve dump — the worst-case graph at full declared context, not what runs per request. Those are the only CPU-assigned nodes there. Everything else — MUL_MAT, MUL_MAT_ID, ADD, CONCAT, MUL, SCALE, CONT, RMS_NORM, GET_ROWS, SET_ROWS, ROPE, FLASH_ATTN, LIGHTNING_INDEXER, DSV4_HC_* — is on ROCm0–5.
sched_reserve: graph splits = 49 for a 6-device layer split (≈6 expected). 22 of the 49 splits are CPU splits, each with exactly one input, and it is always the lightning-indexer score tensor:
So the graph leaves the GPUs ~22 extra times per eval to run TOP_K on the host and copy indices back. lid_score_masked scales with token count (32K → 512K between the two reserve passes, tracking embd 16K → 256K), so at decode the transferred tensor scales with n_kv.
This accounts for every observed symptom:
pp survives — one round-trip amortised across a 2048-token batch
tg collapses — 22 round-trips per single token
hard threshold, then a flat floor — the op is either accepted or rejected, with no middle ground
with dmesg concurrently flooding amdgpu: init_user_pages: Failed to get user pages: -1. This is the host staging traffic for the CPU excursions, not an independent fault — there is 5–8.5 GiB free VRAM on every card and 380 GiB available host RAM at the time.
Confirmation: the op switches backend between the two depths
Per-request scheduler dumps from a single llama-server -v process (GGML_SCHED_DEBUG=2, --ctx-size 16384), log sliced by request:
prompt_n = 2501 → tg 15.15 t/s. All TOP_K nodes on GPU:
Same process, same weights, same flags — only the prompt length differs. The fallback is partial: 42 nodes stay on GPU while 105 move, consistent with a per-node ne[0] threshold rather than a whole-op rejection.
Two further checks rule out competing explanations:
The AMD_LOG_LEVEL=1 32 MiB allocation-failure count is identical at depth 3072 and 4096 (25368 each), so the host-allocation retry loop is a fixed load-time artifact and not the cause of the collapse.
Cutting --ctx-size from 204800 to 32768 does not move the threshold — it tracks actual n_kv, not declared context.
Root cause in upstream source: CUB gate excludes HIP
ggml_cuda_op_top_k is present as a defined symbol in the HIP backend:
So CUDA builds with CUDART >= 11.7 return true unconditionally and never fall back. HIP and MUSA are explicitly excluded from the CUB path and are therefore subject to the size gate. CUB is NVIDIA-only; there is no in-tree rocPRIM / hipCUB equivalent wired up for the HIP backend.
This is not gfx906-specific and not fork-specific — it affects every AMD GPU on the HIP backend, and MUSA. Any ROCm user running DeepSeek-V4-Flash (or any other architecture whose graph contains a large TOP_K) past a few thousand tokens of context will hit it.
Note one unexplained detail: master's gate is ne[0] <= 1024, but TOP_K ran entirely on GPU here at n_kv = 2501. So either ne[0] on the indexer's TOP_K input is not n_kv directly (chunking or stride in the deepseek4 graph), or the tested builds carry a different threshold than current master. The CUB-vs-no-CUB split is the operative cause either way — the op demonstrably switches backends as n_kv grows — but the exact threshold value in these builds is unconfirmed.
Related HIP-backend strings, confirming the non-CUB bitonic path is what gets used:
_Z28argsort_f32_i32_cuda_bitonicPKfPiii15ggml_sort_orderP12ihipStream_t
_Z20ggml_cuda_op_argsortR25ggml_backend_cuda_contextP11ggml_tensor
_ZL17k_argsort_f32_i32IL15ggml_sort_order0EEvPKfPiii
/build/llamacpp/ggml/src/ggml-cuda/argsort.cu
hipMemcpy2DAsync(dst_d, k * sizeof(int), tmp_dst, ncols * sizeof(int), k * sizeof(int), nrows, hipMemcpyDeviceToDevice, stream)
Suggested fix
Route the HIP backend through hipCUB / rocPRIM for GGML_OP_TOP_K and GGML_OP_ARGSORT, or extend the non-CUB bitonic path to handle larger ncols via a multi-pass / global-memory sort. Either removes the CPU excursion entirely.
Declared context size — cutting --ctx-size from 204800 to 32768 does not move the threshold; it tracks actual depth
Build version — identical on b10187 and b10207
GGML_CUDA_NO_PINNED=1 is not a workaround; the server still collapses with it set.
Impact
DeepSeek-V4-Flash is unusable past ~3K context on ROCm despite full GPU residency, on hardware that otherwise sustains ~16 t/s. There is no runtime or configuration mitigation — thread count is capped by an 8-core host, batch size is irrelevant, and no newer prebuilt image changes the behaviour.
Because the gate is #ifndef GGML_CUDA_USE_CUB rather than anything architecture-specific, the impact is backend-wide: all AMD GPUs on HIP (CDNA and RDNA alike) and all MUSA devices. CUDA is unaffected.
Name and Version
Tested on two prebuilt gfx906 images:
mixa3607/llama.cpp-gfx906:b10187-rocm-7.2.4-mxxm-9fa3fef
mixa3607/llama.cpp-gfx906:b10207-rocm-7.2.4-9fa3fef
ROCm 7.2.4. Note that llama-server --version on these images reports version: 1 (81a8712) — the fork stamps its own build number and commit, so the upstream base is identifiable only from the image tag (b10187 / b10207).
Both builds reproduce identically. b10207 is ~20 upstream builds newer than b10187, so this is not a regression introduced in that window and not specific to the gfx906 fork.
Operating systems
Linux
GGML backends
HIP
Hardware
6 × AMD Instinct MI50, gfx906:sramecc+:xnack- (0x66a1), 32 GiB each
Uniform PCIe, 2 hops, all NUMA node 0, no XGMI
Host: 8-core AMD CPU, 503 GiB RAM
Models
bartowski DeepSeek-V4-Flash-0731-MXFP4 — 4 shards, 145.63 GiB, 284.33 B params, arch deepseek4.
Fully GPU-resident across all 6 cards. No CPU expert offload. deepseek4 is layer-split only on this backend (--split-mode tensor errors, row fails on gfx906).
Problem description & steps to reproduce
Command line
Environment:
HSA_OVERRIDE_GFX_VERSION=9.0.6,HIP_VISIBLE_DEVICES=0,1,2,3,4,5,ROCBLAS_USE_HIPBLASLT=0(hipBLASLt ships no gfx906 Tensile kernels).Problem description
Token generation is flat through depth 3072 and then collapses by 6.4× at depth 4096. Prompt processing degrades smoothly across the same range, with no corresponding discontinuity.
Wider sweep (
-p 2048 -n 128 -r 3) shows the post-cliff value is a flat floor rather than continuing decay: tg128 = 16.14 (d0), 16.11 (d1024), 15.68 (d2048), 2.38 (d4096), 2.32 (d8192). pp2048 over the same range: 231.86 → 215.43 → 210.03 → 170.97 → 153.94 → 129.58 (d16384), smooth throughout.Confirmed on
llama-serveras well, not justllama-bench:Root cause
GGML_SCHED_DEBUG=2withllama-server -v. Op × backend assignment across the whole graph:This is the
sched_reservedump — the worst-case graph at full declared context, not what runs per request. Those are the only CPU-assigned nodes there. Everything else —MUL_MAT,MUL_MAT_ID,ADD,CONCAT,MUL,SCALE,CONT,RMS_NORM,GET_ROWS,SET_ROWS,ROPE,FLASH_ATTN,LIGHTNING_INDEXER,DSV4_HC_*— is on ROCm0–5.sched_reserve: graph splits = 49for a 6-device layer split (≈6 expected). 22 of the 49 splits are CPU splits, each with exactly one input, and it is always the lightning-indexer score tensor:So the graph leaves the GPUs ~22 extra times per eval to run TOP_K on the host and copy indices back.
lid_score_maskedscales with token count (32K → 512K between the two reserve passes, trackingembd16K → 256K), so at decode the transferred tensor scales withn_kv.This accounts for every observed symptom:
-b/-ub—-b 512 -ub 512reproduces identicallyROCm_Host compute buffer size = 5736.68 MiBat load, on an otherwise fully-resident modelUnder
AMD_LOG_LEVEL=1the collapsed state shows a tight retry loop (~4 ms apart) of 32 MiB host allocations:with
dmesgconcurrently floodingamdgpu: init_user_pages: Failed to get user pages: -1. This is the host staging traffic for the CPU excursions, not an independent fault — there is 5–8.5 GiB free VRAM on every card and 380 GiB available host RAM at the time.Confirmation: the op switches backend between the two depths
Per-request scheduler dumps from a single
llama-server -vprocess (GGML_SCHED_DEBUG=2,--ctx-size 16384), log sliced by request:prompt_n = 2501 → tg 15.15 t/s. All TOP_K nodes on GPU:
prompt_n = 6001 → tg 2.35 t/s. Most TOP_K nodes moved to CPU:
Same process, same weights, same flags — only the prompt length differs. The fallback is partial: 42 nodes stay on GPU while 105 move, consistent with a per-node
ne[0]threshold rather than a whole-op rejection.Two further checks rule out competing explanations:
AMD_LOG_LEVEL=132 MiB allocation-failure count is identical at depth 3072 and 4096 (25368 each), so the host-allocation retry loop is a fixed load-time artifact and not the cause of the collapse.--ctx-sizefrom 204800 to 32768 does not move the threshold — it tracks actualn_kv, not declared context.Root cause in upstream source: CUB gate excludes HIP
ggml_cuda_op_top_kis present as a defined symbol in the HIP backend:The kernel is compiled in. The fallback comes from
supports_opinggml/src/ggml-cuda/ggml-cuda.cu:and
GGML_CUDA_USE_CUBis defined inggml/src/ggml-cuda/common.cuhas:So CUDA builds with CUDART >= 11.7 return
trueunconditionally and never fall back. HIP and MUSA are explicitly excluded from the CUB path and are therefore subject to the size gate. CUB is NVIDIA-only; there is no in-tree rocPRIM / hipCUB equivalent wired up for the HIP backend.This is not gfx906-specific and not fork-specific — it affects every AMD GPU on the HIP backend, and MUSA. Any ROCm user running DeepSeek-V4-Flash (or any other architecture whose graph contains a large TOP_K) past a few thousand tokens of context will hit it.
Note one unexplained detail: master's gate is
ne[0] <= 1024, but TOP_K ran entirely on GPU here atn_kv= 2501. So eitherne[0]on the indexer's TOP_K input is notn_kvdirectly (chunking or stride in thedeepseek4graph), or the tested builds carry a different threshold than current master. The CUB-vs-no-CUB split is the operative cause either way — the op demonstrably switches backends asn_kvgrows — but the exact threshold value in these builds is unconfirmed.Related HIP-backend strings, confirming the non-CUB bitonic path is what gets used:
Suggested fix
Route the HIP backend through hipCUB / rocPRIM for
GGML_OP_TOP_KandGGML_OP_ARGSORT, or extend the non-CUB bitonic path to handle largerncolsvia a multi-pass / global-memory sort. Either removes the CPU excursion entirely.Ruled out
-b 512 -ub 512reproduces identicallyRLIMIT_MEMLOCK—--ulimit memlock=-1:-1 --cap-add IPC_LOCKchanges nothing;--privilegedalready grantsCAP_IPC_LOCKGGML_CUDA_NO_VMM=1— no effectrealloc/gallocroutput after either a short or a long requestGGML_OP_LIGHTNING_INDEXERruns on ROCm here (189 nodes, none on CPU), so cuda : CUDA GGML_OP_LIGHTNING_INDEXER implementation (generic vector kernel + wmma kernel) #25545 is present and working--ctx-sizefrom 204800 to 32768 does not move the threshold; it tracks actual depthGGML_CUDA_NO_PINNED=1is not a workaround; the server still collapses with it set.Impact
DeepSeek-V4-Flash is unusable past ~3K context on ROCm despite full GPU residency, on hardware that otherwise sustains ~16 t/s. There is no runtime or configuration mitigation — thread count is capped by an 8-core host, batch size is irrelevant, and no newer prebuilt image changes the behaviour.
Because the gate is
#ifndef GGML_CUDA_USE_CUBrather than anything architecture-specific, the impact is backend-wide: all AMD GPUs on HIP (CDNA and RDNA alike) and all MUSA devices. CUDA is unaffected.Possibly related
GGML_OP_LIGHTNING_INDEXERimplementation (present and working here)First Bad Commit
No response
Relevant log output
Logs