Skip to content

HIP: broaden MoE ncols_opt tile heuristic on RDNA3.5 architecture - #28935

Merged
IMbackK merged 1 commit into
ggml-org:masterfrom
WenqiangJia2026:wenqijia/moe-ncols-opt-rdna35
Sep 16, 2026
Merged

IMbackK merged 1 commit into
ggml-org:masterfrom
WenqiangJia2026:wenqijia/moe-ncols-opt-rdna35

Conversation

@WenqiangJia2026

Copy link
Copy Markdown
Contributor

Overview

the code is add code to broaden the support for rdna 3.5 arch,
the change for correction and performance improvement has verified on local target.

Additional information

the command for build:
cmake -S . -B build -G Ninja -DCMAKE_C_COMPILER=<rocm>/bin/clang.exe -DCMAKE_CXX_COMPILER=<rocm>/bin/clang++.exe -DCMAKE_HIP_COMPILER=<rocm>/bin/clang++.exe -DGGML_HIP=ON -DAMDGPU_TARGETS=gfx1151 -DGGML_HIP_NO_VMM=ON -DGGML_CUDA_FA_ALL_KERNELS=ON -DCMAKE_BUILD_TYPE=Release

the local target:
CPU(AMD Ryzen AI MAX+ 388,8C/16T,23.79 GB RAM)
GPU (AMD Radeon 8060S,gfx1151 (RDNA3.5),20 CU,wave32)
toolchain (ROCm 7.2,HIP 7.2.60201-38d754472,clang 21.0.0git,CMake 3.27.2,Ninja 1.11.0)

we verify the Correctness and performance result use below command:
test-backend-ops test -b ROCm0 -o MUL_MAT -p type_a=q4_K / q5_K / q4_0 / q5_0
test-backend-ops test -b ROCm0 -o MUL_MAT_ID -p type_a=q4_K / q5_K / q4_0 / q5_0
llama-bench -m <model> -p 2048 -n 128 -b 2048 -ub 512 -ngl 99 -fa 1 -lm none -r 3 -o json

the final result as below:
LFM2.5-8B-A1B-UD-Q4_K_M (Q4_K MoE) pp2048 +16.198% CI [+12.704, +19.799] 8/8
Qwen1.5-MoE-A2.7B-Q2_K (Q2_K MoE) pp2048 +6.189% CI [ +5.245, +7.141] 8/8

all pass: MUL_MAT 64/64, 29/29, 48/48, 14/14; MUL_MAT_ID 84/84, 3/3, 74/74, 3/3

This is verified on only one gfx1151 device.

Requirements

  • I have read and agree with the contributing guidelines
  • AI usage disclosure: YES — usage (AI-assisted code verify and testing). I understand the changes fully.

It's found the MoE ncols_opt tile heuristic needs to be broadened
to include the RDNA3.5 architecture.

The code change is implemented in ggml/src/ggml-cuda/mmq.cu
and just change the GGML_CUDA_CC_IS_RDNA3_0 to
GGML_CUDA_CC_IS_RDNA3 in the condition.
The dense dispatch logic remains unchanged.
The Test machine configuration we used is
AMD Radeon 8060S, gfx1151 (RDNA3.5), 20 CU, wave32
+ AMD Ryzen AI MAX+ 388, 8C/16T, 23.79 GB RAM

we complete the Correctness verification and performance evaluation as follows:
  test-backend-ops test -b ROCm0 -o MUL_MAT    -p type_a=<q4_K|q5_K|q4_0|q5_0>
  test-backend-ops test -b ROCm0 -o MUL_MAT_ID -p type_a=<q4_K|q5_K|q4_0|q5_0>
  all pass: MUL_MAT 64/64, 29/29, 48/48, 14/14;
            MUL_MAT_ID 84/84, 3/3, 74/74, 3/3

Performance result on target machine:
  LFM2.5-8B-A1B-UD-Q4_K_M  (Q4_K MoE)   +16.198%  [+12.704, +19.799]   8/8
  Qwen1.5-MoE-A2.7B-Q2_K   (Q2_K MoE)    +6.189%  [ +5.245,  +7.141]   8/8
  pooled (16 pairs)                     +11.081%  [ +7.972, +14.279]  16/16

Token generation (tg128) is unchanged on the Q4_K MoE model and +2.188%
[+0.905, +3.488] on the Q2_K one.
@WenqiangJia2026
WenqiangJia2026 requested a review from a team as a code owner September 15, 2026 06:45
@github-actions github-actions Bot added ggml changes relating to the ggml tensor library for machine learning CUDA Related to the CUDA backend labels Sep 15, 2026
@ggml-gh-bot

ggml-gh-bot Bot commented Sep 15, 2026

Copy link
Copy Markdown

Hi @WenqiangJia2026, thanks for your contribution!

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

  • 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.

@WenqiangJia2026

Copy link
Copy Markdown
Contributor Author

Hi @WenqiangJia2026, thanks for your contribution!

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

  • 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.

The actual AI usage has been disclosed, and the commit message and PR description
were written by myself. The testing result used 8 balanced AB/BA pairs, and the performance summary was paired log-ratio geometric mean.

@JohannesGaessler

Copy link
Copy Markdown
Contributor

Please benchmark the performance for all relevant combinations of physical batch sizes and model types and post the results you get with scripts/compare_llama_bench.py. For the model you can use Granite 3.0. The batch sizes should be powers of 2 up to 512. The types should cover at least iq1_s iq2_s iq2_xs iq2_xxs iq3_m iq3_s iq3_xs iq3_xxs iq4_nl iq4_xs q2_k q3_k_s q4_0 q4_1 q4_k_s q5_0 q5_1 q5_k_s q6_k q8_0.

@WenqiangJia2026

Copy link
Copy Markdown
Contributor Author

Please benchmark the performance for all relevant combinations of physical batch sizes and model types and post the results you get with scripts/compare_llama_bench.py. For the model you can use Granite 3.0. The batch sizes should be powers of 2 up to 512. The types should cover at least iq1_s iq2_s iq2_xs iq2_xxs iq3_m iq3_s iq3_xs iq3_xxs iq4_nl iq4_xs q2_k q3_k_s q4_0 q4_1 q4_k_s q5_0 q5_1 q5_k_s q6_k q8_0.

sweep-28935-data.zip

Hi I have completed the experiments you mentioned before, below are the details and results:
I have compared the two versions' performance(clean version(4c9233c) and patched version(b3ea2b2))
The model info :ibm-granite/granite-3.0-3b-a800m-instruct
considering the repo only provides 10 of the 20 requested types(which means it lacks the IQ1_S, IQ2_S, IQ2_XS, IQ2_XXS, IQ3_S, IQ3_XXS, IQ4_NL, Q4_1, Q5_0, Q5_1
),
thus I decided to quantize it to 20 types using same imatrix locally:
granite-3.0-3b-a800m-instruct-IQ1_S.gguf 0.69 GiB
granite-3.0-3b-a800m-instruct-IQ2_XXS.gguf 0.87 GiB
granite-3.0-3b-a800m-instruct-IQ2_XS.gguf 0.96 GiB
granite-3.0-3b-a800m-instruct-IQ2_S.gguf 0.99 GiB
granite-3.0-3b-a800m-instruct-Q2_K.gguf 1.18 GiB
granite-3.0-3b-a800m-instruct-IQ3_XXS.gguf 1.23 GiB
granite-3.0-3b-a800m-instruct-IQ3_XS.gguf 1.31 GiB
granite-3.0-3b-a800m-instruct-Q3_K_S.gguf 1.38 GiB
granite-3.0-3b-a800m-instruct-IQ3_S.gguf 1.38 GiB
granite-3.0-3b-a800m-instruct-IQ3_M.gguf 1.41 GiB
granite-3.0-3b-a800m-instruct-IQ4_XS.gguf 1.70 GiB
granite-3.0-3b-a800m-instruct-IQ4_NL.gguf 1.79 GiB
granite-3.0-3b-a800m-instruct-Q4_0.gguf 1.80 GiB
granite-3.0-3b-a800m-instruct-Q4_K_S.gguf 1.81 GiB
granite-3.0-3b-a800m-instruct-Q4_1.gguf 1.98 GiB
granite-3.0-3b-a800m-instruct-Q5_K_S.gguf 2.18 GiB
granite-3.0-3b-a800m-instruct-Q5_0.gguf 2.18 GiB
granite-3.0-3b-a800m-instruct-Q5_1.gguf 2.37 GiB
granite-3.0-3b-a800m-instruct-Q6_K.gguf 2.58 GiB
granite-3.0-3b-a800m-instruct-Q8_0.gguf 3.34 GiB
The HW and SW info as below:
Windows 11 Pro + ROCm 7.2+CMake 3.27.2-msvc1
AMD RYZEN AI MAX+ 388 w/ Radeon 8060S +GPU driver(32.0.31021.5001)

the build command:
cmake -S . -B build -G Ninja
-DCMAKE_C_COMPILER=/bin/clang.exe
-DCMAKE_CXX_COMPILER=/bin/clang++.exe
-DCMAKE_HIP_COMPILER=/bin/clang++.exe
-DGGML_HIP=ON -DAMDGPU_TARGETS=gfx1151 -DGGML_HIP_NO_VMM=ON
-DGGML_CUDA_FA_ALL_KERNELS=ON -DGGML_LLAMAFILE=OFF
-DGGML_CPU_REPACK=OFF -DGGML_OPENMP=OFF -DBUILD_SHARED_LIBS=OFF
-DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release --target llama-bench -j 8

the quantize part command:

curl -L -o wikitext-2-raw-v1.zip
https://huggingface.co/datasets/ggml-org/ci/resolve/main/wikitext-2-raw-v1.zip

llama-imatrix -m granite-3.0-3b-a800m-instruct-f16.gguf
-f wikitext-2-raw/wiki.train.raw
-o granite-3.0-3b-a800m-instruct.imatrix.gguf
-ngl 99 -c 512 --chunks 200

llama-quantize --imatrix granite-3.0-3b-a800m-instruct.imatrix.gguf
granite-3.0-3b-a800m-instruct-f16.gguf
granite-3.0-3b-a800m-instruct-.gguf 16

the performance command:

llama-bench -m granite-3.0-3b-a800m-instruct-.gguf
-p 512 -n 0 -b 512 -ub 1,2,4,8,16,32,64,128,256,512
-ngl 99 -fa 1 -r 5 -o jsonl

scripts/compare-llama-bench.py -t llama-bench -i sweep.jsonl -b 4c9233c -c b3ea2b2

the final result as below:
Values in below table are the Speedup column produced by compare-llama-bench.py:
avg_ts(b3ea2b2) / avg_ts(4c9233c).
A value above 1.00 means the patched build is faster.
Test is pp512; ub is the physical batch size (-ub);
each data point is the mean of 5 repetitions (-r 5).

Type ub=1 ub=2 ub=4 ub=8 ub=16 ub=32 ub=64 ub=128 ub=256 ub=512
IQ1_S 1.32 1.00 0.98 1.05 1.01 1.05 1.32 1.54 1.12 1.03
IQ2_S 0.99 1.01 1.02 1.02 1.01 0.94 1.04 1.26 1.19 1.05
IQ2_XS 1.00 1.00 0.99 0.98 1.01 0.93 1.05 1.27 1.08 1.03
IQ2_XXS 1.00 1.03 0.98 0.99 0.99 1.15 1.09 1.19 1.27 1.06
IQ3_S 0.99 0.98 1.00 1.01 0.99 1.02 1.01 1.23 1.08 1.00
IQ3_M 0.93 1.01 1.01 1.00 0.99 1.01 1.06 1.24 1.07 1.07
IQ3_XS 1.05 1.00 1.00 1.04 1.02 1.02 0.99 1.18 1.06 1.04
IQ3_XXS 1.01 1.01 1.00 0.96 0.97 1.14 1.01 1.15 1.21 1.05
IQ4_NL 0.98 1.01 1.03 1.00 0.98 1.41 1.30 1.13 1.36 1.09
IQ4_XS 1.01 0.99 0.98 1.02 0.99 1.39 1.32 1.13 1.23 1.06
Q2_K 1.00 1.01 0.99 0.99 0.99 1.09 1.16 1.23 1.06 1.06
Q3_K_S 1.02 1.03 1.02 0.99 1.01 0.94 1.03 1.46 1.15 0.99
Q4_0 0.99 0.98 1.00 1.00 0.97 1.35 1.28 1.12 1.08 0.93
Q4_1 0.99 1.01 1.00 1.00 0.97 1.03 1.32 1.53 1.14 1.00
Q4_K_S 0.99 0.99 1.04 1.00 0.97 1.00 1.33 1.51 1.22 0.99
Q5_0 0.97 1.00 1.01 1.01 1.01 1.26 1.08 1.10 1.05 0.94
Q5_1 0.99 0.99 1.01 1.01 1.02 1.11 1.27 1.47 1.25 0.95
Q5_K_S 0.99 1.00 1.01 1.02 1.02 1.06 1.27 1.46 1.15 1.00
Q6_K 1.00 0.95 1.00 1.00 1.00 1.12 1.25 1.70 1.05 0.99
Q8_0 1.02 0.99 1.00 1.01 0.97 1.26 1.28 1.24 1.20 1.08
mean 1.01 1.00 1.00 1.01 0.99 1.11 1.17 1.31 1.15 1.02

summarized the results as below:

Same data expressed as percent change, the compute details as below:
(avg_ts_patched / avg_ts_clean - 1) * 100.

* marks a significant difference, not a footnote: the two means
are separated by more than 2 standard errors, where
SE = sqrt((stddev_clean^2 + stddev_patched^2) / 5)
using the stddev_ts values llama-bench reports over its 5 repetitions.

type             1        2        4        8       16       32       64      128      256      512
IQ1_S       +32.1*    +0.1     -1.9     +5.0*    +1.1     +5.3*   +31.6*   +53.5*   +12.3*    +2.8
IQ2_S        -0.5     +1.2*    +1.8     +1.6     +1.4     -5.7*    +3.7*   +25.9*   +18.6*    +5.3*
IQ2_XS       -0.3     -0.5     -0.6     -1.6     +0.8     -7.1*    +4.6    +27.1*    +7.6     +2.8
IQ2_XXS      +0.3     +2.7     -2.0     -0.6     -0.5    +15.4*    +8.8*   +18.7*   +26.7*    +5.5
IQ3_M        -6.5*    +0.8     +0.6     -0.1     -1.2     +1.4*    +5.9*   +23.6*    +7.2*    +6.8*
IQ3_S        -1.3     -1.6     +0.0     +0.7     -1.1     +1.9*    +1.3    +23.1*    +8.4*    -0.3
IQ3_XS       +4.9*    +0.3     +0.3     +3.6     +2.4     +2.2     -1.5    +18.1*    +6.4*    +4.3
IQ3_XXS      +0.7     +0.7     +0.1     -3.7     -2.7*   +14.0*    +1.2    +15.5*   +20.9*    +5.4
IQ4_NL       -1.7     +0.8     +2.7     -0.3     -1.6    +41.1*   +30.4*   +13.5*   +35.8*    +8.5
IQ4_XS       +1.2     -1.1     -1.8     +1.7     -0.8    +38.7*   +31.8*   +12.9*   +22.6*    +5.8*
Q2_K         +0.1     +1.4     -1.5     -0.7     -1.5     +8.9*   +16.0*   +23.4*    +5.8     +6.3*
Q3_K_S       +1.5*    +3.1*    +2.2     -0.6     +0.7     -6.0*    +2.8    +45.8*   +15.3*    -0.8
Q4_0         -1.2     -2.2     -0.4     -0.0     -3.5    +35.1*   +28.1*   +11.7*    +7.9     -7.2*
Q4_1         -1.1     +0.7     -0.5     +0.3     -3.0     +3.1    +31.9*   +52.6*   +13.7*    -0.3
Q4_K_S       -0.7     -0.7     +4.2*    -0.3     -2.9     +0.4    +32.8*   +51.2*   +22.3*    -1.4
Q5_0         -2.6*    +0.1     +1.3     +0.9     +1.2    +26.0*    +7.5*   +10.0*    +5.3     -5.7
Q5_1         -0.7     -1.0     +0.6     +0.6     +2.2    +11.5*   +27.4*   +46.9*   +25.3*    -5.1
Q5_K_S       -1.2     -0.0     +0.8     +1.7     +2.1*    +6.3*   +27.4*   +45.6*   +15.1*    +0.3
Q6_K         +0.3     -5.2*    +0.3     +0.1     -0.3    +11.8*   +24.5*   +69.9*    +5.5     -1.0
Q8_0         +1.8     -0.8     +0.2     +0.9     -3.2    +25.7*   +27.8*   +24.0*   +19.7*    +8.1*

Across the 200 (type, ubatch) cells: 77 significantly faster, 8 significantly
slower, 115 not significant.

and there are 8 statistically significant slower as below:
Q4_0 ub=512 (0.93), IQ2_XS ub=32 (0.93), IQ3_M ub=1 (0.93),
Q3_K_S ub=32 (0.94), IQ2_S ub=32 (0.94), Q6_K ub=2 (0.95),
IQ3_XXS ub=16 (0.97), Q5_0 ub=1 (0.97)

the ub=32 case I found the result change significantly(IQ4_NL 1.41 vs IQ2_XS 0.93),
and because when ub=32,the clean version tile width was 32, the patched version tile width was 8.
the model was 40 experts / top-8 arch, and compute ncols_opt was 7.

the original data was attached, which help to reproduce above result.

@IMbackK
IMbackK merged commit fccf716 into ggml-org:master Sep 16, 2026
19 of 24 checks passed
@WenqiangJia2026
WenqiangJia2026 deleted the wenqijia/moe-ncols-opt-rdna35 branch September 16, 2026 07:56
quimmedes pushed a commit to quimmedes/cafe-llama.cpp that referenced this pull request Sep 16, 2026
…ml-org#28935)

It's found the MoE ncols_opt tile heuristic needs to be broadened
to include the RDNA3.5 architecture.

The code change is implemented in ggml/src/ggml-cuda/mmq.cu
and just change the GGML_CUDA_CC_IS_RDNA3_0 to
GGML_CUDA_CC_IS_RDNA3 in the condition.
The dense dispatch logic remains unchanged.
The Test machine configuration we used is
AMD Radeon 8060S, gfx1151 (RDNA3.5), 20 CU, wave32
+ AMD Ryzen AI MAX+ 388, 8C/16T, 23.79 GB RAM

we complete the Correctness verification and performance evaluation as follows:
  test-backend-ops test -b ROCm0 -o MUL_MAT    -p type_a=<q4_K|q5_K|q4_0|q5_0>
  test-backend-ops test -b ROCm0 -o MUL_MAT_ID -p type_a=<q4_K|q5_K|q4_0|q5_0>
  all pass: MUL_MAT 64/64, 29/29, 48/48, 14/14;
            MUL_MAT_ID 84/84, 3/3, 74/74, 3/3

Performance result on target machine:
  LFM2.5-8B-A1B-UD-Q4_K_M  (Q4_K MoE)   +16.198%  [+12.704, +19.799]   8/8
  Qwen1.5-MoE-A2.7B-Q2_K   (Q2_K MoE)    +6.189%  [ +5.245,  +7.141]   8/8
  pooled (16 pairs)                     +11.081%  [ +7.972, +14.279]  16/16

Token generation (tg128) is unchanged on the Q4_K MoE model and +2.188%
[+0.905, +3.488] on the Q2_K one.
tungel pushed a commit to tungel/llama.cpp that referenced this pull request Sep 18, 2026
qwen4exp (Qwen3.8-Flash-Next) support on the rdna-boosts core, squashed
from the beta/qwen4exp delta (fork qwen4exp tip dd4301fb4, re-based to
the 465e49b-era core; promoted to the delivery at the 050dde5
re-base 2026-09-07):

- QSA layers: fused indexer top-k (GGML_OP_INDEXER_TOPK, radix), sparse
  flash attention (GGML_OP_FLASH_ATTN_QSA) -- the default FA path;
  LLAMA_QSA_SPARSE_FA=0 keeps dense; CPU reference for the sparse op
- fused decode ops GGML_OP_HC_MIX / GGML_OP_HC_COMBINE (+ kernel
  geometry, rms/gamma fold, F32/Q8_0 inject fold, head-call fusion)
- managed lazy reader (llama-lazy-reader.cpp/.h) with PLE n-gram row
  loading and batched cold-page fetch (LLAMA_LAZY_IO_THREADS)
- MTP draft-head support for the Flash-Next GGUFs (--spec-type draft-mtp)
- WS4 hyperconn prefill fusions (DSV4_HC_COMB/PRE/POST); ggml sched
  alloc-fallback sync fix; QSA dense shortcut (DEFAULT ON) + LLAMA_QSA_OFF
- QSA decode campaign (2026-09-07): fused INDEXER_POOL/SCORE ops, f16
  indexer caches, incremental derived block-vector cache, two-round
  16-bit topk select, CUDA device-description gfx-id exposure
- arch policy (2026-09-07 crossover tables): dense decode below the
  per-arch depth, QSA at/above (gfx1151 default 65536);
  LLAMA_QSA_DENSE_DECODE_UNTIL overrides
- MoE weighted-reduction fusion on the Meta-TP path; concat-transposed,
  mmid-512x10, repeat-absorb hc-combine absorb; model support in
  src/models/qwen4exp.cpp

Re-based onto the 050dde5 delivery core 2026-09-07: the only
upstream-drift conflict (ggml-cuda/common.cuh) resolved by keeping both
the upstream gfx90c GCN-APU arch macros and the block's exact-SKU
GGML_CUDA_CC_IS_GFX1151 predicate.  Upstream's branchless Q4_K/Q5_K mmvq
unpack + L2 prefetch and the block's k-quant/decode additions coexist
(mmqq/vecdotq regions disjoint).  Full beta notes + validation:
beta/qwen4exp/README.md.

Amended 2026-09-08 with the compiler-warning cleanup for the
ggml-clang / ggml-clang++ (system clang 16) and ROCm 7.14 HIP builds:
drop the unused n_blocks local in ggml_indexer_fill (ggml.c); add the
GGML_OP_INDEXER_SCORE/GGML_OP_INDEXER_FILL case labels to the CPU
compute-forward switch (GPU-only fused ops; the CPU plan phase already
aborts on them); drop the unreachable break after the noreturn
GGML_ABORT in the HC_MIX/HC_COMBINE CPU type dispatches (ops.cpp);
restore the GGML_CUDA_QSA_INDEXER_CACHE env tri-state (0/1/2) so the
idx_cache == 2 debug probe is actually reachable (was narrowed to bool);
size_t loop counter in the device gfx-id sniff.  No generated-code or
runtime-behavior change in default configs.

Amended 2026-09-08 (2nd) with the qwen4exp tensor-split backend gate:
llm_arch_supports_sm_tensor(qwen4exp) is now true only on HIP builds
(#ifdef GGML_USE_HIP).  The fused QSA/HC/WS4 ops tensor-split through
the meta backend are validated on ROCm only (3x R9700); on backends
that cannot run them on-device (Vulkan, Metal, SYCL) the CPU-fallback
subgraphs leave the meta splitter unable to reconcile mirrored-vs-split
operand states (abort at ggml-backend-meta.cpp handle_generic, e.g. the
qwen4exp gated-attention MUL on Vulkan), and NVIDIA CUDA is untested,
so those builds keep upstream's unsupported (clean error / arch-test
SKIP) instead of crashing.

Amended 2026-09-10: freed-cell KV handling moved from host-side zeroing
to kernel-side masked-V elimination, and the earlier host zeroing
(seq_rm/seq_keep/clear per-free memsets, 2026-09-09 fold of the strix
aad5adb port) is REMOVED.  The llama-kv-cache files are back to the
upstream state (no zero_freed/rows_hw/sharers wiring; evicting a KV
sequence is pure host cell bookkeeping again on every backend).  In its
place block 14 now carries the three kernel fixes that make freed-cell
content unreadable in masked flash attention, so the host workaround is
unnecessary:

- HIP fattn-tile.cuh: in the packed-bf16 PV path, zero the per-warp V
  register copies of rows whose P is exactly +0.0 across the warp's
  columns (the fully-masked rows) before the bf16 dot.
- HIP fattn-mma-f16.cuh: after each V-tile slice is staged in shared
  memory, zero the rows the mask tile marks blocked (-inf) for every
  query column of the block; one extra uniform barrier on the masked
  path (ncols2 > 1 || mask_h) only; compile-time excluded for the
  V_is_K_view and NVIDIA swizzled (swz_V) paths.
- Vulkan flash_attn_cm1.comp: never read V of fully masked columns
  (per-column liveness -> V of dead columns stays +0.0); flash_attn.comp
  scalar path likewise skips the V load for dead columns.

All three are unconditional in their kernel paths (no arch/env gating):
they are correctness fixes for any masked flash-attention column whose
cell is stale/freed (batch serving, KV eviction), active by default on
every device.  They were root-caused and validated on the Strix Halo
box (single gfx1151): 16/16 identical-request determinism gates pass on
both ROCm and Vulkan with the host zeroing disabled, bit-identical
across every KV cache type each backend's FA supports (ROCm
f16/bf16/q8_0/q4_0; Vulkan also q4_1/q5_0/q5_1/iq4_nl), with
test-backend-ops FLASH_ATTN_EXT vs CPU 4591/4591 (ROCm) and 7822/7822
(Vulkan) and no measurable decode/prefill regression (tg128 within
0.05% at depth-16384; pp within single-run drift).  Full record:
wip/strix-halo/kvzero/RECORD-2026-09-09.md and
wip/kv-sign-leak/HANDOVER-2026-09-09-mma-f16.md in the delivery repo.
The 2026-09-09 amendment text below is retained as history only; the
freed-cell zeroing gate it describes no longer exists in the block.

Amended 2026-09-12 (seventh) with the MTP-export logits-purity fix: an
unmasked embeddings_nextn export (the draft-mtp target) used to defer the
last layer's output-row gather, so the last layer's ffn tail ran on the
full ubatch and the prefill's last-position logits shifted by a ULP
against --spec-type none.  The last layer now always gathers the output
rows before its tail (exactly the plain path) and builds a second
full-row tail solely for t_h_nextn when the chunk drops rows
(n_outputs < n_tokens); a decode/verify batch drops none, so nothing is
duplicated there.  Verified on gfx1151: the mstep NEXTN=1 prefill
mismatch is gone (0 mismatches; was 1 at pos = 4293), the W=4 width
probe is 0 mismatches with the W=8 position list unmoved, the default
and forced-sparse text gates are byte-identical, MTP acceptance is
bit-identical (f16 0.51678 = 77/149 pre- and post-fix), and the graph is
a no-op on every non-NEXTN path.  Instrument: wip/strix-halo/qsa-item4/.

Amended 2026-09-12 (eighth) with the decode/verify band-uniformity fix
for the QSA indexer score.  The score's matmul carries the indexer heads
in its N dimension, so its ne11 is n_idx_h * n_tps (4 * n_tps for
qwen4exp); block 08's "keep the verify batch on the decode kernel" guard
only covered ne11 <= MMVF_MAX_BATCH_SIZE (8), so from n_tps = 3 the
verify batch fell through to MMF while decode (n_tps = 1) stayed on
MMVF.  The two families accumulate the truncated dot product differently,
so the indexer score differed by a ULP and flipped a top-k near-tie: the
forward was bit-identical to decode for 101 steps and then diverged.
The guard now keeps the whole flattened band on the decode family
(ne11 <= MMVF_MAX_BATCH_SIZE_FLAT = 32) and mul_mat_vec_f is instantiated
for ncols_dst 9..32.  The guard is block 08's; block 14 extends it
because it is the block that introduces the flattened batch.  Verified
on gfx1201 (3x R9700, layer split): mstep W = 1,2,3,4,5,8 all 0
mismatches (was W >= 3 impure), the W = 1 reference Thash is unchanged
(2bd73063dd0a9524) so decode numerics are untouched, the f16 band stays
pure, and the forced-sparse q8_0 and default text gates are
byte-identical (a4cdc10dfb6c / 2e078b6966c0).  Cross-check on gfx1151
pending.  Instruments: wip/strix-halo/qsa-item4/.

Amended 2026-09-17 (re-base onto ebbb185): the pair-fusion MUL_MAT_ID arm's
ncols_opt arch gate is broadened from GGML_CUDA_CC_IS_RDNA3_0 to
GGML_CUDA_CC_IS_RDNA3, matching upstream ggml-org#28935 which broadened the standalone
ggml_cuda_mul_mat_q tile heuristic to RDNA3.5 (gfx1151).  No-op on RDNA3_0 and
RDNA4 (the predicate is unchanged there), but it keeps the fused gate+up pair's
tile selection consistent with the standalone on gfx1151 instead of falling
back to the full token count.
tungel pushed a commit to tungel/llama.cpp that referenced this pull request Sep 18, 2026
qwen4exp (Qwen3.8-Flash-Next) support on the rdna-boosts core, squashed
from the beta/qwen4exp delta (fork qwen4exp tip dd4301fb4, re-based to
the 465e49b-era core; promoted to the delivery at the 050dde5
re-base 2026-09-07):

- QSA layers: fused indexer top-k (GGML_OP_INDEXER_TOPK, radix), sparse
  flash attention (GGML_OP_FLASH_ATTN_QSA) -- the default FA path;
  LLAMA_QSA_SPARSE_FA=0 keeps dense; CPU reference for the sparse op
- fused decode ops GGML_OP_HC_MIX / GGML_OP_HC_COMBINE (+ kernel
  geometry, rms/gamma fold, F32/Q8_0 inject fold, head-call fusion)
- managed lazy reader (llama-lazy-reader.cpp/.h) with PLE n-gram row
  loading and batched cold-page fetch (LLAMA_LAZY_IO_THREADS)
- MTP draft-head support for the Flash-Next GGUFs (--spec-type draft-mtp)
- WS4 hyperconn prefill fusions (DSV4_HC_COMB/PRE/POST); ggml sched
  alloc-fallback sync fix; QSA dense shortcut (DEFAULT ON) + LLAMA_QSA_OFF
- QSA decode campaign (2026-09-07): fused INDEXER_POOL/SCORE ops, f16
  indexer caches, incremental derived block-vector cache, two-round
  16-bit topk select, CUDA device-description gfx-id exposure
- arch policy (2026-09-07 crossover tables): dense decode below the
  per-arch depth, QSA at/above (gfx1151 default 65536);
  LLAMA_QSA_DENSE_DECODE_UNTIL overrides
- MoE weighted-reduction fusion on the Meta-TP path; concat-transposed,
  mmid-512x10, repeat-absorb hc-combine absorb; model support in
  src/models/qwen4exp.cpp

Re-based onto the 050dde5 delivery core 2026-09-07: the only
upstream-drift conflict (ggml-cuda/common.cuh) resolved by keeping both
the upstream gfx90c GCN-APU arch macros and the block's exact-SKU
GGML_CUDA_CC_IS_GFX1151 predicate.  Upstream's branchless Q4_K/Q5_K mmvq
unpack + L2 prefetch and the block's k-quant/decode additions coexist
(mmqq/vecdotq regions disjoint).  Full beta notes + validation:
beta/qwen4exp/README.md.

Amended 2026-09-08 with the compiler-warning cleanup for the
ggml-clang / ggml-clang++ (system clang 16) and ROCm 7.14 HIP builds:
drop the unused n_blocks local in ggml_indexer_fill (ggml.c); add the
GGML_OP_INDEXER_SCORE/GGML_OP_INDEXER_FILL case labels to the CPU
compute-forward switch (GPU-only fused ops; the CPU plan phase already
aborts on them); drop the unreachable break after the noreturn
GGML_ABORT in the HC_MIX/HC_COMBINE CPU type dispatches (ops.cpp);
restore the GGML_CUDA_QSA_INDEXER_CACHE env tri-state (0/1/2) so the
idx_cache == 2 debug probe is actually reachable (was narrowed to bool);
size_t loop counter in the device gfx-id sniff.  No generated-code or
runtime-behavior change in default configs.

Amended 2026-09-08 (2nd) with the qwen4exp tensor-split backend gate:
llm_arch_supports_sm_tensor(qwen4exp) is now true only on HIP builds
(#ifdef GGML_USE_HIP).  The fused QSA/HC/WS4 ops tensor-split through
the meta backend are validated on ROCm only (3x R9700); on backends
that cannot run them on-device (Vulkan, Metal, SYCL) the CPU-fallback
subgraphs leave the meta splitter unable to reconcile mirrored-vs-split
operand states (abort at ggml-backend-meta.cpp handle_generic, e.g. the
qwen4exp gated-attention MUL on Vulkan), and NVIDIA CUDA is untested,
so those builds keep upstream's unsupported (clean error / arch-test
SKIP) instead of crashing.

Amended 2026-09-10: freed-cell KV handling moved from host-side zeroing
to kernel-side masked-V elimination, and the earlier host zeroing
(seq_rm/seq_keep/clear per-free memsets, 2026-09-09 fold of the strix
aad5adb port) is REMOVED.  The llama-kv-cache files are back to the
upstream state (no zero_freed/rows_hw/sharers wiring; evicting a KV
sequence is pure host cell bookkeeping again on every backend).  In its
place block 14 now carries the three kernel fixes that make freed-cell
content unreadable in masked flash attention, so the host workaround is
unnecessary:

- HIP fattn-tile.cuh: in the packed-bf16 PV path, zero the per-warp V
  register copies of rows whose P is exactly +0.0 across the warp's
  columns (the fully-masked rows) before the bf16 dot.
- HIP fattn-mma-f16.cuh: after each V-tile slice is staged in shared
  memory, zero the rows the mask tile marks blocked (-inf) for every
  query column of the block; one extra uniform barrier on the masked
  path (ncols2 > 1 || mask_h) only; compile-time excluded for the
  V_is_K_view and NVIDIA swizzled (swz_V) paths.
- Vulkan flash_attn_cm1.comp: never read V of fully masked columns
  (per-column liveness -> V of dead columns stays +0.0); flash_attn.comp
  scalar path likewise skips the V load for dead columns.

All three are unconditional in their kernel paths (no arch/env gating):
they are correctness fixes for any masked flash-attention column whose
cell is stale/freed (batch serving, KV eviction), active by default on
every device.  They were root-caused and validated on the Strix Halo
box (single gfx1151): 16/16 identical-request determinism gates pass on
both ROCm and Vulkan with the host zeroing disabled, bit-identical
across every KV cache type each backend's FA supports (ROCm
f16/bf16/q8_0/q4_0; Vulkan also q4_1/q5_0/q5_1/iq4_nl), with
test-backend-ops FLASH_ATTN_EXT vs CPU 4591/4591 (ROCm) and 7822/7822
(Vulkan) and no measurable decode/prefill regression (tg128 within
0.05% at depth-16384; pp within single-run drift).  Full record:
wip/strix-halo/kvzero/RECORD-2026-09-09.md and
wip/kv-sign-leak/HANDOVER-2026-09-09-mma-f16.md in the delivery repo.
The 2026-09-09 amendment text below is retained as history only; the
freed-cell zeroing gate it describes no longer exists in the block.

Amended 2026-09-12 (seventh) with the MTP-export logits-purity fix: an
unmasked embeddings_nextn export (the draft-mtp target) used to defer the
last layer's output-row gather, so the last layer's ffn tail ran on the
full ubatch and the prefill's last-position logits shifted by a ULP
against --spec-type none.  The last layer now always gathers the output
rows before its tail (exactly the plain path) and builds a second
full-row tail solely for t_h_nextn when the chunk drops rows
(n_outputs < n_tokens); a decode/verify batch drops none, so nothing is
duplicated there.  Verified on gfx1151: the mstep NEXTN=1 prefill
mismatch is gone (0 mismatches; was 1 at pos = 4293), the W=4 width
probe is 0 mismatches with the W=8 position list unmoved, the default
and forced-sparse text gates are byte-identical, MTP acceptance is
bit-identical (f16 0.51678 = 77/149 pre- and post-fix), and the graph is
a no-op on every non-NEXTN path.  Instrument: wip/strix-halo/qsa-item4/.

Amended 2026-09-12 (eighth) with the decode/verify band-uniformity fix
for the QSA indexer score.  The score's matmul carries the indexer heads
in its N dimension, so its ne11 is n_idx_h * n_tps (4 * n_tps for
qwen4exp); block 08's "keep the verify batch on the decode kernel" guard
only covered ne11 <= MMVF_MAX_BATCH_SIZE (8), so from n_tps = 3 the
verify batch fell through to MMF while decode (n_tps = 1) stayed on
MMVF.  The two families accumulate the truncated dot product differently,
so the indexer score differed by a ULP and flipped a top-k near-tie: the
forward was bit-identical to decode for 101 steps and then diverged.
The guard now keeps the whole flattened band on the decode family
(ne11 <= MMVF_MAX_BATCH_SIZE_FLAT = 32) and mul_mat_vec_f is instantiated
for ncols_dst 9..32.  The guard is block 08's; block 14 extends it
because it is the block that introduces the flattened batch.  Verified
on gfx1201 (3x R9700, layer split): mstep W = 1,2,3,4,5,8 all 0
mismatches (was W >= 3 impure), the W = 1 reference Thash is unchanged
(2bd73063dd0a9524) so decode numerics are untouched, the f16 band stays
pure, and the forced-sparse q8_0 and default text gates are
byte-identical (a4cdc10dfb6c / 2e078b6966c0).  Cross-check on gfx1151
pending.  Instruments: wip/strix-halo/qsa-item4/.

Amended 2026-09-17 (re-base onto ebbb185): the pair-fusion MUL_MAT_ID arm's
ncols_opt arch gate is broadened from GGML_CUDA_CC_IS_RDNA3_0 to
GGML_CUDA_CC_IS_RDNA3, matching upstream ggml-org#28935 which broadened the standalone
ggml_cuda_mul_mat_q tile heuristic to RDNA3.5 (gfx1151).  No-op on RDNA3_0 and
RDNA4 (the predicate is unchanged there), but it keeps the fused gate+up pair's
tile selection consistent with the standalone on gfx1151 instead of falling
back to the full token count.
tungel pushed a commit to tungel/llama.cpp that referenced this pull request Sep 18, 2026
qwen4exp (Qwen3.8-Flash-Next) support on the rdna-boosts core, squashed
from the beta/qwen4exp delta (fork qwen4exp tip dd4301fb4, re-based to
the 465e49b-era core; promoted to the delivery at the 050dde5
re-base 2026-09-07):

- QSA layers: fused indexer top-k (GGML_OP_INDEXER_TOPK, radix), sparse
  flash attention (GGML_OP_FLASH_ATTN_QSA) -- the default FA path;
  LLAMA_QSA_SPARSE_FA=0 keeps dense; CPU reference for the sparse op
- fused decode ops GGML_OP_HC_MIX / GGML_OP_HC_COMBINE (+ kernel
  geometry, rms/gamma fold, F32/Q8_0 inject fold, head-call fusion)
- managed lazy reader (llama-lazy-reader.cpp/.h) with PLE n-gram row
  loading and batched cold-page fetch (LLAMA_LAZY_IO_THREADS)
- MTP draft-head support for the Flash-Next GGUFs (--spec-type draft-mtp)
- WS4 hyperconn prefill fusions (DSV4_HC_COMB/PRE/POST); ggml sched
  alloc-fallback sync fix; QSA dense shortcut (DEFAULT ON) + LLAMA_QSA_OFF
- QSA decode campaign (2026-09-07): fused INDEXER_POOL/SCORE ops, f16
  indexer caches, incremental derived block-vector cache, two-round
  16-bit topk select, CUDA device-description gfx-id exposure
- arch policy (2026-09-07 crossover tables): dense decode below the
  per-arch depth, QSA at/above (gfx1151 default 65536);
  LLAMA_QSA_DENSE_DECODE_UNTIL overrides
- MoE weighted-reduction fusion on the Meta-TP path; concat-transposed,
  mmid-512x10, repeat-absorb hc-combine absorb; model support in
  src/models/qwen4exp.cpp

Re-based onto the 050dde5 delivery core 2026-09-07: the only
upstream-drift conflict (ggml-cuda/common.cuh) resolved by keeping both
the upstream gfx90c GCN-APU arch macros and the block's exact-SKU
GGML_CUDA_CC_IS_GFX1151 predicate.  Upstream's branchless Q4_K/Q5_K mmvq
unpack + L2 prefetch and the block's k-quant/decode additions coexist
(mmqq/vecdotq regions disjoint).  Full beta notes + validation:
beta/qwen4exp/README.md.

Amended 2026-09-08 with the compiler-warning cleanup for the
ggml-clang / ggml-clang++ (system clang 16) and ROCm 7.14 HIP builds:
drop the unused n_blocks local in ggml_indexer_fill (ggml.c); add the
GGML_OP_INDEXER_SCORE/GGML_OP_INDEXER_FILL case labels to the CPU
compute-forward switch (GPU-only fused ops; the CPU plan phase already
aborts on them); drop the unreachable break after the noreturn
GGML_ABORT in the HC_MIX/HC_COMBINE CPU type dispatches (ops.cpp);
restore the GGML_CUDA_QSA_INDEXER_CACHE env tri-state (0/1/2) so the
idx_cache == 2 debug probe is actually reachable (was narrowed to bool);
size_t loop counter in the device gfx-id sniff.  No generated-code or
runtime-behavior change in default configs.

Amended 2026-09-08 (2nd) with the qwen4exp tensor-split backend gate:
llm_arch_supports_sm_tensor(qwen4exp) is now true only on HIP builds
(#ifdef GGML_USE_HIP).  The fused QSA/HC/WS4 ops tensor-split through
the meta backend are validated on ROCm only (3x R9700); on backends
that cannot run them on-device (Vulkan, Metal, SYCL) the CPU-fallback
subgraphs leave the meta splitter unable to reconcile mirrored-vs-split
operand states (abort at ggml-backend-meta.cpp handle_generic, e.g. the
qwen4exp gated-attention MUL on Vulkan), and NVIDIA CUDA is untested,
so those builds keep upstream's unsupported (clean error / arch-test
SKIP) instead of crashing.

Amended 2026-09-10: freed-cell KV handling moved from host-side zeroing
to kernel-side masked-V elimination, and the earlier host zeroing
(seq_rm/seq_keep/clear per-free memsets, 2026-09-09 fold of the strix
aad5adb port) is REMOVED.  The llama-kv-cache files are back to the
upstream state (no zero_freed/rows_hw/sharers wiring; evicting a KV
sequence is pure host cell bookkeeping again on every backend).  In its
place block 14 now carries the three kernel fixes that make freed-cell
content unreadable in masked flash attention, so the host workaround is
unnecessary:

- HIP fattn-tile.cuh: in the packed-bf16 PV path, zero the per-warp V
  register copies of rows whose P is exactly +0.0 across the warp's
  columns (the fully-masked rows) before the bf16 dot.
- HIP fattn-mma-f16.cuh: after each V-tile slice is staged in shared
  memory, zero the rows the mask tile marks blocked (-inf) for every
  query column of the block; one extra uniform barrier on the masked
  path (ncols2 > 1 || mask_h) only; compile-time excluded for the
  V_is_K_view and NVIDIA swizzled (swz_V) paths.
- Vulkan flash_attn_cm1.comp: never read V of fully masked columns
  (per-column liveness -> V of dead columns stays +0.0); flash_attn.comp
  scalar path likewise skips the V load for dead columns.

All three are unconditional in their kernel paths (no arch/env gating):
they are correctness fixes for any masked flash-attention column whose
cell is stale/freed (batch serving, KV eviction), active by default on
every device.  They were root-caused and validated on the Strix Halo
box (single gfx1151): 16/16 identical-request determinism gates pass on
both ROCm and Vulkan with the host zeroing disabled, bit-identical
across every KV cache type each backend's FA supports (ROCm
f16/bf16/q8_0/q4_0; Vulkan also q4_1/q5_0/q5_1/iq4_nl), with
test-backend-ops FLASH_ATTN_EXT vs CPU 4591/4591 (ROCm) and 7822/7822
(Vulkan) and no measurable decode/prefill regression (tg128 within
0.05% at depth-16384; pp within single-run drift).  Full record:
wip/strix-halo/kvzero/RECORD-2026-09-09.md and
wip/kv-sign-leak/HANDOVER-2026-09-09-mma-f16.md in the delivery repo.
The 2026-09-09 amendment text below is retained as history only; the
freed-cell zeroing gate it describes no longer exists in the block.

Amended 2026-09-12 (seventh) with the MTP-export logits-purity fix: an
unmasked embeddings_nextn export (the draft-mtp target) used to defer the
last layer's output-row gather, so the last layer's ffn tail ran on the
full ubatch and the prefill's last-position logits shifted by a ULP
against --spec-type none.  The last layer now always gathers the output
rows before its tail (exactly the plain path) and builds a second
full-row tail solely for t_h_nextn when the chunk drops rows
(n_outputs < n_tokens); a decode/verify batch drops none, so nothing is
duplicated there.  Verified on gfx1151: the mstep NEXTN=1 prefill
mismatch is gone (0 mismatches; was 1 at pos = 4293), the W=4 width
probe is 0 mismatches with the W=8 position list unmoved, the default
and forced-sparse text gates are byte-identical, MTP acceptance is
bit-identical (f16 0.51678 = 77/149 pre- and post-fix), and the graph is
a no-op on every non-NEXTN path.  Instrument: wip/strix-halo/qsa-item4/.

Amended 2026-09-12 (eighth) with the decode/verify band-uniformity fix
for the QSA indexer score.  The score's matmul carries the indexer heads
in its N dimension, so its ne11 is n_idx_h * n_tps (4 * n_tps for
qwen4exp); block 08's "keep the verify batch on the decode kernel" guard
only covered ne11 <= MMVF_MAX_BATCH_SIZE (8), so from n_tps = 3 the
verify batch fell through to MMF while decode (n_tps = 1) stayed on
MMVF.  The two families accumulate the truncated dot product differently,
so the indexer score differed by a ULP and flipped a top-k near-tie: the
forward was bit-identical to decode for 101 steps and then diverged.
The guard now keeps the whole flattened band on the decode family
(ne11 <= MMVF_MAX_BATCH_SIZE_FLAT = 32) and mul_mat_vec_f is instantiated
for ncols_dst 9..32.  The guard is block 08's; block 14 extends it
because it is the block that introduces the flattened batch.  Verified
on gfx1201 (3x R9700, layer split): mstep W = 1,2,3,4,5,8 all 0
mismatches (was W >= 3 impure), the W = 1 reference Thash is unchanged
(2bd73063dd0a9524) so decode numerics are untouched, the f16 band stays
pure, and the forced-sparse q8_0 and default text gates are
byte-identical (a4cdc10dfb6c / 2e078b6966c0).  Cross-check on gfx1151
pending.  Instruments: wip/strix-halo/qsa-item4/.

Amended 2026-09-17 (re-base onto ebbb185): the pair-fusion MUL_MAT_ID arm's
ncols_opt arch gate is broadened from GGML_CUDA_CC_IS_RDNA3_0 to
GGML_CUDA_CC_IS_RDNA3, matching upstream ggml-org#28935 which broadened the standalone
ggml_cuda_mul_mat_q tile heuristic to RDNA3.5 (gfx1151).  No-op on RDNA3_0 and
RDNA4 (the predicate is unchanged there), but it keeps the fused gate+up pair's
tile selection consistent with the standalone on gfx1151 instead of falling
back to the full token count.
tungel pushed a commit to tungel/llama.cpp that referenced this pull request Sep 19, 2026
qwen4exp (Qwen3.8-Flash-Next) support on the rdna-boosts core, squashed
from the beta/qwen4exp delta (fork qwen4exp tip dd4301fb4, re-based to
the 465e49b-era core; promoted to the delivery at the 050dde5
re-base 2026-09-07):

- QSA layers: fused indexer top-k (GGML_OP_INDEXER_TOPK, radix), sparse
  flash attention (GGML_OP_FLASH_ATTN_QSA) -- the default FA path;
  LLAMA_QSA_SPARSE_FA=0 keeps dense; CPU reference for the sparse op
- fused decode ops GGML_OP_HC_MIX / GGML_OP_HC_COMBINE (+ kernel
  geometry, rms/gamma fold, F32/Q8_0 inject fold, head-call fusion)
- managed lazy reader (llama-lazy-reader.cpp/.h) with PLE n-gram row
  loading and batched cold-page fetch (LLAMA_LAZY_IO_THREADS)
- MTP draft-head support for the Flash-Next GGUFs (--spec-type draft-mtp)
- WS4 hyperconn prefill fusions (DSV4_HC_COMB/PRE/POST); ggml sched
  alloc-fallback sync fix; QSA dense shortcut (DEFAULT ON) + LLAMA_QSA_OFF
- QSA decode campaign (2026-09-07): fused INDEXER_POOL/SCORE ops, f16
  indexer caches, incremental derived block-vector cache, two-round
  16-bit topk select, CUDA device-description gfx-id exposure
- arch policy (2026-09-07 crossover tables): dense decode below the
  per-arch depth, QSA at/above (gfx1151 default 65536);
  LLAMA_QSA_DENSE_DECODE_UNTIL overrides
- MoE weighted-reduction fusion on the Meta-TP path; concat-transposed,
  mmid-512x10, repeat-absorb hc-combine absorb; model support in
  src/models/qwen4exp.cpp

Re-based onto the 050dde5 delivery core 2026-09-07: the only
upstream-drift conflict (ggml-cuda/common.cuh) resolved by keeping both
the upstream gfx90c GCN-APU arch macros and the block's exact-SKU
GGML_CUDA_CC_IS_GFX1151 predicate.  Upstream's branchless Q4_K/Q5_K mmvq
unpack + L2 prefetch and the block's k-quant/decode additions coexist
(mmqq/vecdotq regions disjoint).  Full beta notes + validation:
beta/qwen4exp/README.md.

Amended 2026-09-08 with the compiler-warning cleanup for the
ggml-clang / ggml-clang++ (system clang 16) and ROCm 7.14 HIP builds:
drop the unused n_blocks local in ggml_indexer_fill (ggml.c); add the
GGML_OP_INDEXER_SCORE/GGML_OP_INDEXER_FILL case labels to the CPU
compute-forward switch (GPU-only fused ops; the CPU plan phase already
aborts on them); drop the unreachable break after the noreturn
GGML_ABORT in the HC_MIX/HC_COMBINE CPU type dispatches (ops.cpp);
restore the GGML_CUDA_QSA_INDEXER_CACHE env tri-state (0/1/2) so the
idx_cache == 2 debug probe is actually reachable (was narrowed to bool);
size_t loop counter in the device gfx-id sniff.  No generated-code or
runtime-behavior change in default configs.

Amended 2026-09-08 (2nd) with the qwen4exp tensor-split backend gate:
llm_arch_supports_sm_tensor(qwen4exp) is now true only on HIP builds
(#ifdef GGML_USE_HIP).  The fused QSA/HC/WS4 ops tensor-split through
the meta backend are validated on ROCm only (3x R9700); on backends
that cannot run them on-device (Vulkan, Metal, SYCL) the CPU-fallback
subgraphs leave the meta splitter unable to reconcile mirrored-vs-split
operand states (abort at ggml-backend-meta.cpp handle_generic, e.g. the
qwen4exp gated-attention MUL on Vulkan), and NVIDIA CUDA is untested,
so those builds keep upstream's unsupported (clean error / arch-test
SKIP) instead of crashing.

Amended 2026-09-10: freed-cell KV handling moved from host-side zeroing
to kernel-side masked-V elimination, and the earlier host zeroing
(seq_rm/seq_keep/clear per-free memsets, 2026-09-09 fold of the strix
aad5adb port) is REMOVED.  The llama-kv-cache files are back to the
upstream state (no zero_freed/rows_hw/sharers wiring; evicting a KV
sequence is pure host cell bookkeeping again on every backend).  In its
place block 14 now carries the three kernel fixes that make freed-cell
content unreadable in masked flash attention, so the host workaround is
unnecessary:

- HIP fattn-tile.cuh: in the packed-bf16 PV path, zero the per-warp V
  register copies of rows whose P is exactly +0.0 across the warp's
  columns (the fully-masked rows) before the bf16 dot.
- HIP fattn-mma-f16.cuh: after each V-tile slice is staged in shared
  memory, zero the rows the mask tile marks blocked (-inf) for every
  query column of the block; one extra uniform barrier on the masked
  path (ncols2 > 1 || mask_h) only; compile-time excluded for the
  V_is_K_view and NVIDIA swizzled (swz_V) paths.
- Vulkan flash_attn_cm1.comp: never read V of fully masked columns
  (per-column liveness -> V of dead columns stays +0.0); flash_attn.comp
  scalar path likewise skips the V load for dead columns.

All three are unconditional in their kernel paths (no arch/env gating):
they are correctness fixes for any masked flash-attention column whose
cell is stale/freed (batch serving, KV eviction), active by default on
every device.  They were root-caused and validated on the Strix Halo
box (single gfx1151): 16/16 identical-request determinism gates pass on
both ROCm and Vulkan with the host zeroing disabled, bit-identical
across every KV cache type each backend's FA supports (ROCm
f16/bf16/q8_0/q4_0; Vulkan also q4_1/q5_0/q5_1/iq4_nl), with
test-backend-ops FLASH_ATTN_EXT vs CPU 4591/4591 (ROCm) and 7822/7822
(Vulkan) and no measurable decode/prefill regression (tg128 within
0.05% at depth-16384; pp within single-run drift).  Full record:
wip/strix-halo/kvzero/RECORD-2026-09-09.md and
wip/kv-sign-leak/HANDOVER-2026-09-09-mma-f16.md in the delivery repo.
The 2026-09-09 amendment text below is retained as history only; the
freed-cell zeroing gate it describes no longer exists in the block.

Amended 2026-09-12 (seventh) with the MTP-export logits-purity fix: an
unmasked embeddings_nextn export (the draft-mtp target) used to defer the
last layer's output-row gather, so the last layer's ffn tail ran on the
full ubatch and the prefill's last-position logits shifted by a ULP
against --spec-type none.  The last layer now always gathers the output
rows before its tail (exactly the plain path) and builds a second
full-row tail solely for t_h_nextn when the chunk drops rows
(n_outputs < n_tokens); a decode/verify batch drops none, so nothing is
duplicated there.  Verified on gfx1151: the mstep NEXTN=1 prefill
mismatch is gone (0 mismatches; was 1 at pos = 4293), the W=4 width
probe is 0 mismatches with the W=8 position list unmoved, the default
and forced-sparse text gates are byte-identical, MTP acceptance is
bit-identical (f16 0.51678 = 77/149 pre- and post-fix), and the graph is
a no-op on every non-NEXTN path.  Instrument: wip/strix-halo/qsa-item4/.

Amended 2026-09-12 (eighth) with the decode/verify band-uniformity fix
for the QSA indexer score.  The score's matmul carries the indexer heads
in its N dimension, so its ne11 is n_idx_h * n_tps (4 * n_tps for
qwen4exp); block 08's "keep the verify batch on the decode kernel" guard
only covered ne11 <= MMVF_MAX_BATCH_SIZE (8), so from n_tps = 3 the
verify batch fell through to MMF while decode (n_tps = 1) stayed on
MMVF.  The two families accumulate the truncated dot product differently,
so the indexer score differed by a ULP and flipped a top-k near-tie: the
forward was bit-identical to decode for 101 steps and then diverged.
The guard now keeps the whole flattened band on the decode family
(ne11 <= MMVF_MAX_BATCH_SIZE_FLAT = 32) and mul_mat_vec_f is instantiated
for ncols_dst 9..32.  The guard is block 08's; block 14 extends it
because it is the block that introduces the flattened batch.  Verified
on gfx1201 (3x R9700, layer split): mstep W = 1,2,3,4,5,8 all 0
mismatches (was W >= 3 impure), the W = 1 reference Thash is unchanged
(2bd73063dd0a9524) so decode numerics are untouched, the f16 band stays
pure, and the forced-sparse q8_0 and default text gates are
byte-identical (a4cdc10dfb6c / 2e078b6966c0).  Cross-check on gfx1151
pending.  Instruments: wip/strix-halo/qsa-item4/.

Amended 2026-09-17 (re-base onto ebbb185): the pair-fusion MUL_MAT_ID arm's
ncols_opt arch gate is broadened from GGML_CUDA_CC_IS_RDNA3_0 to
GGML_CUDA_CC_IS_RDNA3, matching upstream ggml-org#28935 which broadened the standalone
ggml_cuda_mul_mat_q tile heuristic to RDNA3.5 (gfx1151).  No-op on RDNA3_0 and
RDNA4 (the predicate is unchanged there), but it keeps the fused gate+up pair's
tile selection consistent with the standalone on gfx1151 instead of falling
back to the full token count.
mbednarek360 pushed a commit to mbednarek360/llama.cpp that referenced this pull request Sep 22, 2026
qwen4exp (Qwen3.8-Flash-Next) support on the rdna-boosts core, squashed
from the beta/qwen4exp delta (fork qwen4exp tip dd4301fb4, re-based to
the 465e49b-era core; promoted to the delivery at the 050dde5
re-base 2026-09-07):

- QSA layers: fused indexer top-k (GGML_OP_INDEXER_TOPK, radix), sparse
  flash attention (GGML_OP_FLASH_ATTN_QSA) -- the default FA path;
  LLAMA_QSA_SPARSE_FA=0 keeps dense; CPU reference for the sparse op
- fused decode ops GGML_OP_HC_MIX / GGML_OP_HC_COMBINE (+ kernel
  geometry, rms/gamma fold, F32/Q8_0 inject fold, head-call fusion)
- managed lazy reader (llama-lazy-reader.cpp/.h) with PLE n-gram row
  loading and batched cold-page fetch (LLAMA_LAZY_IO_THREADS)
- MTP draft-head support for the Flash-Next GGUFs (--spec-type draft-mtp)
- WS4 hyperconn prefill fusions (DSV4_HC_COMB/PRE/POST); ggml sched
  alloc-fallback sync fix; QSA dense shortcut (DEFAULT ON) + LLAMA_QSA_OFF
- QSA decode campaign (2026-09-07): fused INDEXER_POOL/SCORE ops, f16
  indexer caches, incremental derived block-vector cache, two-round
  16-bit topk select, CUDA device-description gfx-id exposure
- arch policy (2026-09-07 crossover tables): dense decode below the
  per-arch depth, QSA at/above (gfx1151 default 65536);
  LLAMA_QSA_DENSE_DECODE_UNTIL overrides
- MoE weighted-reduction fusion on the Meta-TP path; concat-transposed,
  mmid-512x10, repeat-absorb hc-combine absorb; model support in
  src/models/qwen4exp.cpp

Re-based onto the 050dde5 delivery core 2026-09-07: the only
upstream-drift conflict (ggml-cuda/common.cuh) resolved by keeping both
the upstream gfx90c GCN-APU arch macros and the block's exact-SKU
GGML_CUDA_CC_IS_GFX1151 predicate.  Upstream's branchless Q4_K/Q5_K mmvq
unpack + L2 prefetch and the block's k-quant/decode additions coexist
(mmqq/vecdotq regions disjoint).  Full beta notes + validation:
beta/qwen4exp/README.md.

Amended 2026-09-08 with the compiler-warning cleanup for the
ggml-clang / ggml-clang++ (system clang 16) and ROCm 7.14 HIP builds:
drop the unused n_blocks local in ggml_indexer_fill (ggml.c); add the
GGML_OP_INDEXER_SCORE/GGML_OP_INDEXER_FILL case labels to the CPU
compute-forward switch (GPU-only fused ops; the CPU plan phase already
aborts on them); drop the unreachable break after the noreturn
GGML_ABORT in the HC_MIX/HC_COMBINE CPU type dispatches (ops.cpp);
restore the GGML_CUDA_QSA_INDEXER_CACHE env tri-state (0/1/2) so the
idx_cache == 2 debug probe is actually reachable (was narrowed to bool);
size_t loop counter in the device gfx-id sniff.  No generated-code or
runtime-behavior change in default configs.

Amended 2026-09-08 (2nd) with the qwen4exp tensor-split backend gate:
llm_arch_supports_sm_tensor(qwen4exp) is now true only on HIP builds
(#ifdef GGML_USE_HIP).  The fused QSA/HC/WS4 ops tensor-split through
the meta backend are validated on ROCm only (3x R9700); on backends
that cannot run them on-device (Vulkan, Metal, SYCL) the CPU-fallback
subgraphs leave the meta splitter unable to reconcile mirrored-vs-split
operand states (abort at ggml-backend-meta.cpp handle_generic, e.g. the
qwen4exp gated-attention MUL on Vulkan), and NVIDIA CUDA is untested,
so those builds keep upstream's unsupported (clean error / arch-test
SKIP) instead of crashing.

Amended 2026-09-10: freed-cell KV handling moved from host-side zeroing
to kernel-side masked-V elimination, and the earlier host zeroing
(seq_rm/seq_keep/clear per-free memsets, 2026-09-09 fold of the strix
aad5adb port) is REMOVED.  The llama-kv-cache files are back to the
upstream state (no zero_freed/rows_hw/sharers wiring; evicting a KV
sequence is pure host cell bookkeeping again on every backend).  In its
place block 14 now carries the three kernel fixes that make freed-cell
content unreadable in masked flash attention, so the host workaround is
unnecessary:

- HIP fattn-tile.cuh: in the packed-bf16 PV path, zero the per-warp V
  register copies of rows whose P is exactly +0.0 across the warp's
  columns (the fully-masked rows) before the bf16 dot.
- HIP fattn-mma-f16.cuh: after each V-tile slice is staged in shared
  memory, zero the rows the mask tile marks blocked (-inf) for every
  query column of the block; one extra uniform barrier on the masked
  path (ncols2 > 1 || mask_h) only; compile-time excluded for the
  V_is_K_view and NVIDIA swizzled (swz_V) paths.
- Vulkan flash_attn_cm1.comp: never read V of fully masked columns
  (per-column liveness -> V of dead columns stays +0.0); flash_attn.comp
  scalar path likewise skips the V load for dead columns.

All three are unconditional in their kernel paths (no arch/env gating):
they are correctness fixes for any masked flash-attention column whose
cell is stale/freed (batch serving, KV eviction), active by default on
every device.  They were root-caused and validated on the Strix Halo
box (single gfx1151): 16/16 identical-request determinism gates pass on
both ROCm and Vulkan with the host zeroing disabled, bit-identical
across every KV cache type each backend's FA supports (ROCm
f16/bf16/q8_0/q4_0; Vulkan also q4_1/q5_0/q5_1/iq4_nl), with
test-backend-ops FLASH_ATTN_EXT vs CPU 4591/4591 (ROCm) and 7822/7822
(Vulkan) and no measurable decode/prefill regression (tg128 within
0.05% at depth-16384; pp within single-run drift).  Full record:
wip/strix-halo/kvzero/RECORD-2026-09-09.md and
wip/kv-sign-leak/HANDOVER-2026-09-09-mma-f16.md in the delivery repo.
The 2026-09-09 amendment text below is retained as history only; the
freed-cell zeroing gate it describes no longer exists in the block.

Amended 2026-09-12 (seventh) with the MTP-export logits-purity fix: an
unmasked embeddings_nextn export (the draft-mtp target) used to defer the
last layer's output-row gather, so the last layer's ffn tail ran on the
full ubatch and the prefill's last-position logits shifted by a ULP
against --spec-type none.  The last layer now always gathers the output
rows before its tail (exactly the plain path) and builds a second
full-row tail solely for t_h_nextn when the chunk drops rows
(n_outputs < n_tokens); a decode/verify batch drops none, so nothing is
duplicated there.  Verified on gfx1151: the mstep NEXTN=1 prefill
mismatch is gone (0 mismatches; was 1 at pos = 4293), the W=4 width
probe is 0 mismatches with the W=8 position list unmoved, the default
and forced-sparse text gates are byte-identical, MTP acceptance is
bit-identical (f16 0.51678 = 77/149 pre- and post-fix), and the graph is
a no-op on every non-NEXTN path.  Instrument: wip/strix-halo/qsa-item4/.

Amended 2026-09-12 (eighth) with the decode/verify band-uniformity fix
for the QSA indexer score.  The score's matmul carries the indexer heads
in its N dimension, so its ne11 is n_idx_h * n_tps (4 * n_tps for
qwen4exp); block 08's "keep the verify batch on the decode kernel" guard
only covered ne11 <= MMVF_MAX_BATCH_SIZE (8), so from n_tps = 3 the
verify batch fell through to MMF while decode (n_tps = 1) stayed on
MMVF.  The two families accumulate the truncated dot product differently,
so the indexer score differed by a ULP and flipped a top-k near-tie: the
forward was bit-identical to decode for 101 steps and then diverged.
The guard now keeps the whole flattened band on the decode family
(ne11 <= MMVF_MAX_BATCH_SIZE_FLAT = 32) and mul_mat_vec_f is instantiated
for ncols_dst 9..32.  The guard is block 08's; block 14 extends it
because it is the block that introduces the flattened batch.  Verified
on gfx1201 (3x R9700, layer split): mstep W = 1,2,3,4,5,8 all 0
mismatches (was W >= 3 impure), the W = 1 reference Thash is unchanged
(2bd73063dd0a9524) so decode numerics are untouched, the f16 band stays
pure, and the forced-sparse q8_0 and default text gates are
byte-identical (a4cdc10dfb6c / 2e078b6966c0).  Cross-check on gfx1151
pending.  Instruments: wip/strix-halo/qsa-item4/.

Amended 2026-09-17 (re-base onto ebbb185): the pair-fusion MUL_MAT_ID arm's
ncols_opt arch gate is broadened from GGML_CUDA_CC_IS_RDNA3_0 to
GGML_CUDA_CC_IS_RDNA3, matching upstream ggml-org#28935 which broadened the standalone
ggml_cuda_mul_mat_q tile heuristic to RDNA3.5 (gfx1151).  No-op on RDNA3_0 and
RDNA4 (the predicate is unchanged there), but it keeps the fused gate+up pair's
tile selection consistent with the standalone on gfx1151 instead of falling
back to the full token count.
@BrewTestBot BrewTestBot mentioned this pull request Sep 23, 2026
1 task done
justinappler added a commit to justinappler/llama.cpp-strix-halo that referenced this pull request Sep 26, 2026
…NA3.5

Upstream ggml-org#28552/ggml-org#28935 land the routed-MoE ncols picker on RDNA3.5, which
Finding ggml-org#10 measured flat against our static J=48 cap, so the cap goes.
Upstream ggml-org#28102 routes WMMA to MMA_F16 at D=256 and bypasses our tile
config at prefill; guard it off on RDNA3.5 pending the G/N A/B.

Assisted-by: Claude Opus 5.5
tungel pushed a commit to tungel/llama.cpp that referenced this pull request Sep 27, 2026
qwen4exp (Qwen3.8-Flash-Next) support on the rdna-boosts core, squashed
from the beta/qwen4exp delta (fork qwen4exp tip dd4301fb4, re-based to
the 465e49b-era core; promoted to the delivery at the 050dde5
re-base 2026-09-07):

- QSA layers: fused indexer top-k (GGML_OP_INDEXER_TOPK, radix), sparse
  flash attention (GGML_OP_FLASH_ATTN_QSA) -- the default FA path;
  LLAMA_QSA_SPARSE_FA=0 keeps dense; CPU reference for the sparse op
- fused decode ops GGML_OP_HC_MIX / GGML_OP_HC_COMBINE (+ kernel
  geometry, rms/gamma fold, F32/Q8_0 inject fold, head-call fusion)
- managed lazy reader (llama-lazy-reader.cpp/.h) with PLE n-gram row
  loading and batched cold-page fetch (LLAMA_LAZY_IO_THREADS)
- MTP draft-head support for the Flash-Next GGUFs (--spec-type draft-mtp)
- WS4 hyperconn prefill fusions (DSV4_HC_COMB/PRE/POST); ggml sched
  alloc-fallback sync fix; QSA dense shortcut (DEFAULT ON) + LLAMA_QSA_OFF
- QSA decode campaign (2026-09-07): fused INDEXER_POOL/SCORE ops, f16
  indexer caches, incremental derived block-vector cache, two-round
  16-bit topk select, CUDA device-description gfx-id exposure
- arch policy (2026-09-07 crossover tables): dense decode below the
  per-arch depth, QSA at/above (gfx1151 default 65536);
  LLAMA_QSA_DENSE_DECODE_UNTIL overrides
- MoE weighted-reduction fusion on the Meta-TP path; concat-transposed,
  mmid-512x10, repeat-absorb hc-combine absorb; model support in
  src/models/qwen4exp.cpp

Re-based onto the 050dde5 delivery core 2026-09-07: the only
upstream-drift conflict (ggml-cuda/common.cuh) resolved by keeping both
the upstream gfx90c GCN-APU arch macros and the block's exact-SKU
GGML_CUDA_CC_IS_GFX1151 predicate.  Upstream's branchless Q4_K/Q5_K mmvq
unpack + L2 prefetch and the block's k-quant/decode additions coexist
(mmqq/vecdotq regions disjoint).  Full beta notes + validation:
beta/qwen4exp/README.md.

Amended 2026-09-08 with the compiler-warning cleanup for the
ggml-clang / ggml-clang++ (system clang 16) and ROCm 7.14 HIP builds:
drop the unused n_blocks local in ggml_indexer_fill (ggml.c); add the
GGML_OP_INDEXER_SCORE/GGML_OP_INDEXER_FILL case labels to the CPU
compute-forward switch (GPU-only fused ops; the CPU plan phase already
aborts on them); drop the unreachable break after the noreturn
GGML_ABORT in the HC_MIX/HC_COMBINE CPU type dispatches (ops.cpp);
restore the GGML_CUDA_QSA_INDEXER_CACHE env tri-state (0/1/2) so the
idx_cache == 2 debug probe is actually reachable (was narrowed to bool);
size_t loop counter in the device gfx-id sniff.  No generated-code or
runtime-behavior change in default configs.

Amended 2026-09-08 (2nd) with the qwen4exp tensor-split backend gate:
llm_arch_supports_sm_tensor(qwen4exp) is now true only on HIP builds
(#ifdef GGML_USE_HIP).  The fused QSA/HC/WS4 ops tensor-split through
the meta backend are validated on ROCm only (3x R9700); on backends
that cannot run them on-device (Vulkan, Metal, SYCL) the CPU-fallback
subgraphs leave the meta splitter unable to reconcile mirrored-vs-split
operand states (abort at ggml-backend-meta.cpp handle_generic, e.g. the
qwen4exp gated-attention MUL on Vulkan), and NVIDIA CUDA is untested,
so those builds keep upstream's unsupported (clean error / arch-test
SKIP) instead of crashing.

Amended 2026-09-10: freed-cell KV handling moved from host-side zeroing
to kernel-side masked-V elimination, and the earlier host zeroing
(seq_rm/seq_keep/clear per-free memsets, 2026-09-09 fold of the strix
aad5adb port) is REMOVED.  The llama-kv-cache files are back to the
upstream state (no zero_freed/rows_hw/sharers wiring; evicting a KV
sequence is pure host cell bookkeeping again on every backend).  In its
place block 14 now carries the three kernel fixes that make freed-cell
content unreadable in masked flash attention, so the host workaround is
unnecessary:

- HIP fattn-tile.cuh: in the packed-bf16 PV path, zero the per-warp V
  register copies of rows whose P is exactly +0.0 across the warp's
  columns (the fully-masked rows) before the bf16 dot.
- HIP fattn-mma-f16.cuh: after each V-tile slice is staged in shared
  memory, zero the rows the mask tile marks blocked (-inf) for every
  query column of the block; one extra uniform barrier on the masked
  path (ncols2 > 1 || mask_h) only; compile-time excluded for the
  V_is_K_view and NVIDIA swizzled (swz_V) paths.
- Vulkan flash_attn_cm1.comp: never read V of fully masked columns
  (per-column liveness -> V of dead columns stays +0.0); flash_attn.comp
  scalar path likewise skips the V load for dead columns.

All three are unconditional in their kernel paths (no arch/env gating):
they are correctness fixes for any masked flash-attention column whose
cell is stale/freed (batch serving, KV eviction), active by default on
every device.  They were root-caused and validated on the Strix Halo
box (single gfx1151): 16/16 identical-request determinism gates pass on
both ROCm and Vulkan with the host zeroing disabled, bit-identical
across every KV cache type each backend's FA supports (ROCm
f16/bf16/q8_0/q4_0; Vulkan also q4_1/q5_0/q5_1/iq4_nl), with
test-backend-ops FLASH_ATTN_EXT vs CPU 4591/4591 (ROCm) and 7822/7822
(Vulkan) and no measurable decode/prefill regression (tg128 within
0.05% at depth-16384; pp within single-run drift).  Full record:
wip/strix-halo/kvzero/RECORD-2026-09-09.md and
wip/kv-sign-leak/HANDOVER-2026-09-09-mma-f16.md in the delivery repo.
The 2026-09-09 amendment text below is retained as history only; the
freed-cell zeroing gate it describes no longer exists in the block.

Amended 2026-09-12 (seventh) with the MTP-export logits-purity fix: an
unmasked embeddings_nextn export (the draft-mtp target) used to defer the
last layer's output-row gather, so the last layer's ffn tail ran on the
full ubatch and the prefill's last-position logits shifted by a ULP
against --spec-type none.  The last layer now always gathers the output
rows before its tail (exactly the plain path) and builds a second
full-row tail solely for t_h_nextn when the chunk drops rows
(n_outputs < n_tokens); a decode/verify batch drops none, so nothing is
duplicated there.  Verified on gfx1151: the mstep NEXTN=1 prefill
mismatch is gone (0 mismatches; was 1 at pos = 4293), the W=4 width
probe is 0 mismatches with the W=8 position list unmoved, the default
and forced-sparse text gates are byte-identical, MTP acceptance is
bit-identical (f16 0.51678 = 77/149 pre- and post-fix), and the graph is
a no-op on every non-NEXTN path.  Instrument: wip/strix-halo/qsa-item4/.

Amended 2026-09-12 (eighth) with the decode/verify band-uniformity fix
for the QSA indexer score.  The score's matmul carries the indexer heads
in its N dimension, so its ne11 is n_idx_h * n_tps (4 * n_tps for
qwen4exp); block 08's "keep the verify batch on the decode kernel" guard
only covered ne11 <= MMVF_MAX_BATCH_SIZE (8), so from n_tps = 3 the
verify batch fell through to MMF while decode (n_tps = 1) stayed on
MMVF.  The two families accumulate the truncated dot product differently,
so the indexer score differed by a ULP and flipped a top-k near-tie: the
forward was bit-identical to decode for 101 steps and then diverged.
The guard now keeps the whole flattened band on the decode family
(ne11 <= MMVF_MAX_BATCH_SIZE_FLAT = 32) and mul_mat_vec_f is instantiated
for ncols_dst 9..32.  The guard is block 08's; block 14 extends it
because it is the block that introduces the flattened batch.  Verified
on gfx1201 (3x R9700, layer split): mstep W = 1,2,3,4,5,8 all 0
mismatches (was W >= 3 impure), the W = 1 reference Thash is unchanged
(2bd73063dd0a9524) so decode numerics are untouched, the f16 band stays
pure, and the forced-sparse q8_0 and default text gates are
byte-identical (a4cdc10dfb6c / 2e078b6966c0).  Cross-check on gfx1151
pending.  Instruments: wip/strix-halo/qsa-item4/.

Amended 2026-09-17 (re-base onto ebbb185): the pair-fusion MUL_MAT_ID arm's
ncols_opt arch gate is broadened from GGML_CUDA_CC_IS_RDNA3_0 to
GGML_CUDA_CC_IS_RDNA3, matching upstream ggml-org#28935 which broadened the standalone
ggml_cuda_mul_mat_q tile heuristic to RDNA3.5 (gfx1151).  No-op on RDNA3_0 and
RDNA4 (the predicate is unchanged there), but it keeps the fused gate+up pair's
tile selection consistent with the standalone on gfx1151 instead of falling
back to the full token count.
[fold target for the qwen4exp/HC/QSA/indexer beta group — see the follow-up commit folds]
Folds beta/mmb-general: the deferred half of 0001 (MMB stand-down of the pair fusion) and 0027
(MMVQ routed band W=16 + gfx1151 dense odd-row band). The qwen4exp/HC/QSA/indexer group folds
into block 15 (it needs block 15's context, which the beta series was authored against).
tebruno99 pushed a commit to tebruno99/llama.cpp-rocm10-patches that referenced this pull request Oct 7, 2026
qwen4exp (Qwen3.8-Flash-Next) support on the rdna-boosts core, squashed
from the beta/qwen4exp delta (fork qwen4exp tip dd4301fb4, re-based to
the 465e49b-era core; promoted to the delivery at the 050dde5
re-base 2026-09-07):

- QSA layers: fused indexer top-k (GGML_OP_INDEXER_TOPK, radix), sparse
  flash attention (GGML_OP_FLASH_ATTN_QSA) -- the default FA path;
  LLAMA_QSA_SPARSE_FA=0 keeps dense; CPU reference for the sparse op
- fused decode ops GGML_OP_HC_MIX / GGML_OP_HC_COMBINE (+ kernel
  geometry, rms/gamma fold, F32/Q8_0 inject fold, head-call fusion)
- managed lazy reader (llama-lazy-reader.cpp/.h) with PLE n-gram row
  loading and batched cold-page fetch (LLAMA_LAZY_IO_THREADS)
- MTP draft-head support for the Flash-Next GGUFs (--spec-type draft-mtp)
- WS4 hyperconn prefill fusions (DSV4_HC_COMB/PRE/POST); ggml sched
  alloc-fallback sync fix; QSA dense shortcut (DEFAULT ON) + LLAMA_QSA_OFF
- QSA decode campaign (2026-09-07): fused INDEXER_POOL/SCORE ops, f16
  indexer caches, incremental derived block-vector cache, two-round
  16-bit topk select, CUDA device-description gfx-id exposure
- arch policy (2026-09-07 crossover tables): dense decode below the
  per-arch depth, QSA at/above (gfx1151 default 65536);
  LLAMA_QSA_DENSE_DECODE_UNTIL overrides
- MoE weighted-reduction fusion on the Meta-TP path; concat-transposed,
  mmid-512x10, repeat-absorb hc-combine absorb; model support in
  src/models/qwen4exp.cpp

Re-based onto the 050dde5 delivery core 2026-09-07: the only
upstream-drift conflict (ggml-cuda/common.cuh) resolved by keeping both
the upstream gfx90c GCN-APU arch macros and the block's exact-SKU
GGML_CUDA_CC_IS_GFX1151 predicate.  Upstream's branchless Q4_K/Q5_K mmvq
unpack + L2 prefetch and the block's k-quant/decode additions coexist
(mmqq/vecdotq regions disjoint).  Full beta notes + validation:
beta/qwen4exp/README.md.

Amended 2026-09-08 with the compiler-warning cleanup for the
ggml-clang / ggml-clang++ (system clang 16) and ROCm 7.14 HIP builds:
drop the unused n_blocks local in ggml_indexer_fill (ggml.c); add the
GGML_OP_INDEXER_SCORE/GGML_OP_INDEXER_FILL case labels to the CPU
compute-forward switch (GPU-only fused ops; the CPU plan phase already
aborts on them); drop the unreachable break after the noreturn
GGML_ABORT in the HC_MIX/HC_COMBINE CPU type dispatches (ops.cpp);
restore the GGML_CUDA_QSA_INDEXER_CACHE env tri-state (0/1/2) so the
idx_cache == 2 debug probe is actually reachable (was narrowed to bool);
size_t loop counter in the device gfx-id sniff.  No generated-code or
runtime-behavior change in default configs.

Amended 2026-09-08 (2nd) with the qwen4exp tensor-split backend gate:
llm_arch_supports_sm_tensor(qwen4exp) is now true only on HIP builds
(#ifdef GGML_USE_HIP).  The fused QSA/HC/WS4 ops tensor-split through
the meta backend are validated on ROCm only (3x R9700); on backends
that cannot run them on-device (Vulkan, Metal, SYCL) the CPU-fallback
subgraphs leave the meta splitter unable to reconcile mirrored-vs-split
operand states (abort at ggml-backend-meta.cpp handle_generic, e.g. the
qwen4exp gated-attention MUL on Vulkan), and NVIDIA CUDA is untested,
so those builds keep upstream's unsupported (clean error / arch-test
SKIP) instead of crashing.

Amended 2026-09-10: freed-cell KV handling moved from host-side zeroing
to kernel-side masked-V elimination, and the earlier host zeroing
(seq_rm/seq_keep/clear per-free memsets, 2026-09-09 fold of the strix
aad5adb port) is REMOVED.  The llama-kv-cache files are back to the
upstream state (no zero_freed/rows_hw/sharers wiring; evicting a KV
sequence is pure host cell bookkeeping again on every backend).  In its
place block 14 now carries the three kernel fixes that make freed-cell
content unreadable in masked flash attention, so the host workaround is
unnecessary:

- HIP fattn-tile.cuh: in the packed-bf16 PV path, zero the per-warp V
  register copies of rows whose P is exactly +0.0 across the warp's
  columns (the fully-masked rows) before the bf16 dot.
- HIP fattn-mma-f16.cuh: after each V-tile slice is staged in shared
  memory, zero the rows the mask tile marks blocked (-inf) for every
  query column of the block; one extra uniform barrier on the masked
  path (ncols2 > 1 || mask_h) only; compile-time excluded for the
  V_is_K_view and NVIDIA swizzled (swz_V) paths.
- Vulkan flash_attn_cm1.comp: never read V of fully masked columns
  (per-column liveness -> V of dead columns stays +0.0); flash_attn.comp
  scalar path likewise skips the V load for dead columns.

All three are unconditional in their kernel paths (no arch/env gating):
they are correctness fixes for any masked flash-attention column whose
cell is stale/freed (batch serving, KV eviction), active by default on
every device.  They were root-caused and validated on the Strix Halo
box (single gfx1151): 16/16 identical-request determinism gates pass on
both ROCm and Vulkan with the host zeroing disabled, bit-identical
across every KV cache type each backend's FA supports (ROCm
f16/bf16/q8_0/q4_0; Vulkan also q4_1/q5_0/q5_1/iq4_nl), with
test-backend-ops FLASH_ATTN_EXT vs CPU 4591/4591 (ROCm) and 7822/7822
(Vulkan) and no measurable decode/prefill regression (tg128 within
0.05% at depth-16384; pp within single-run drift).  Full record:
wip/strix-halo/kvzero/RECORD-2026-09-09.md and
wip/kv-sign-leak/HANDOVER-2026-09-09-mma-f16.md in the delivery repo.
The 2026-09-09 amendment text below is retained as history only; the
freed-cell zeroing gate it describes no longer exists in the block.

Amended 2026-09-12 (seventh) with the MTP-export logits-purity fix: an
unmasked embeddings_nextn export (the draft-mtp target) used to defer the
last layer's output-row gather, so the last layer's ffn tail ran on the
full ubatch and the prefill's last-position logits shifted by a ULP
against --spec-type none.  The last layer now always gathers the output
rows before its tail (exactly the plain path) and builds a second
full-row tail solely for t_h_nextn when the chunk drops rows
(n_outputs < n_tokens); a decode/verify batch drops none, so nothing is
duplicated there.  Verified on gfx1151: the mstep NEXTN=1 prefill
mismatch is gone (0 mismatches; was 1 at pos = 4293), the W=4 width
probe is 0 mismatches with the W=8 position list unmoved, the default
and forced-sparse text gates are byte-identical, MTP acceptance is
bit-identical (f16 0.51678 = 77/149 pre- and post-fix), and the graph is
a no-op on every non-NEXTN path.  Instrument: wip/strix-halo/qsa-item4/.

Amended 2026-09-12 (eighth) with the decode/verify band-uniformity fix
for the QSA indexer score.  The score's matmul carries the indexer heads
in its N dimension, so its ne11 is n_idx_h * n_tps (4 * n_tps for
qwen4exp); block 08's "keep the verify batch on the decode kernel" guard
only covered ne11 <= MMVF_MAX_BATCH_SIZE (8), so from n_tps = 3 the
verify batch fell through to MMF while decode (n_tps = 1) stayed on
MMVF.  The two families accumulate the truncated dot product differently,
so the indexer score differed by a ULP and flipped a top-k near-tie: the
forward was bit-identical to decode for 101 steps and then diverged.
The guard now keeps the whole flattened band on the decode family
(ne11 <= MMVF_MAX_BATCH_SIZE_FLAT = 32) and mul_mat_vec_f is instantiated
for ncols_dst 9..32.  The guard is block 08's; block 14 extends it
because it is the block that introduces the flattened batch.  Verified
on gfx1201 (3x R9700, layer split): mstep W = 1,2,3,4,5,8 all 0
mismatches (was W >= 3 impure), the W = 1 reference Thash is unchanged
(2bd73063dd0a9524) so decode numerics are untouched, the f16 band stays
pure, and the forced-sparse q8_0 and default text gates are
byte-identical (a4cdc10dfb6c / 2e078b6966c0).  Cross-check on gfx1151
pending.  Instruments: wip/strix-halo/qsa-item4/.

Amended 2026-09-17 (re-base onto ebbb185): the pair-fusion MUL_MAT_ID arm's
ncols_opt arch gate is broadened from GGML_CUDA_CC_IS_RDNA3_0 to
GGML_CUDA_CC_IS_RDNA3, matching upstream ggml-org#28935 which broadened the standalone
ggml_cuda_mul_mat_q tile heuristic to RDNA3.5 (gfx1151).  No-op on RDNA3_0 and
RDNA4 (the predicate is unchanged there), but it keeps the fused gate+up pair's
tile selection consistent with the standalone on gfx1151 instead of falling
back to the full token count.
[fold target for the qwen4exp/HC/QSA/indexer beta group — see the follow-up commit folds]
Folds beta/mmb-general: the deferred half of 0001 (MMB stand-down of the pair fusion) and 0027
(MMVQ routed band W=16 + gfx1151 dense odd-row band). The qwen4exp/HC/QSA/indexer group folds
into block 15 (it needs block 15's context, which the beta series was authored against).

Campaign fold (wip/moe-expert-cache, beta): `llm_arch_supports_sm_tensor()`
rejects `LLM_ARCH_GEMMA4` until the segmented host-resident-expert async upload
is finished -- gemma4's fused expert tensor (`ffn_gate_up_exps`) has a segmented
split layout and the MMQ tail pad makes the range non-row-aligned, so the
per-ubatch `-ncmoe` upload has no correct path.  All offload routes are covered
at the arch level; `-sm layer` is unaffected.

r6 (2026-10-05, issue ggml-org#95): publish `GGML_USE_HIP` on the `ggml` target so the
qwen4exp tensor-split gate sees it in dynamic-backend builds.  `ggml_add_backend()`
only adds `GGML_USE_<backend>` to `ggml` when `GGML_BACKEND_DL` is OFF, and the
published containers build with `-DGGML_BACKEND_DL=ON` (needed for
`GGML_CPU_ALL_VARIANTS`), so `src/llama-arch.cpp`'s `#ifdef GGML_USE_HIP` compiled
to the HIP-absent branch and `-sm tensor` for qwen4exp failed with "not implemented
for architecture 'qwen4exp'" even though the HIP backend was built.  The static
build already got the macro from `ggml_add_backend(HIP)`; publishing it in
`ggml/src/ggml-hip/CMakeLists.txt` covers both.  Issue ggml-org#95 (@ethanjjjjjjj).

Folded 2026-10-07 (PR ggml-org#106, @briansp2020; issue ggml-org#105): `LLAMA_KV_N_PAD_MIN` raises the n_kv
padding floor (opt-in; the default 256 is unchanged), keeping the per-device tensor-split graphs
stable for longer; and the conv-state tail is copied straight into each rollback slot (one 2D
memcpy on the strided view, about 144 fewer kernels per verify graph;
`LLAMA_CONV_TAIL_CONT=1` restores the cont+copy).
stew675 added a commit to stew675/llama.cpp that referenced this pull request Oct 8, 2026
qwen4exp (Qwen3.8-Flash-Next) support on the rdna-boosts core, squashed
from the beta/qwen4exp delta (fork qwen4exp tip dd4301fb4, re-based to
the 465e49b-era core; promoted to the delivery at the 050dde5
re-base 2026-09-07):

- QSA layers: fused indexer top-k (GGML_OP_INDEXER_TOPK, radix), sparse
  flash attention (GGML_OP_FLASH_ATTN_QSA) -- the default FA path;
  LLAMA_QSA_SPARSE_FA=0 keeps dense; CPU reference for the sparse op
- fused decode ops GGML_OP_HC_MIX / GGML_OP_HC_COMBINE (+ kernel
  geometry, rms/gamma fold, F32/Q8_0 inject fold, head-call fusion)
- managed lazy reader (llama-lazy-reader.cpp/.h) with PLE n-gram row
  loading and batched cold-page fetch (LLAMA_LAZY_IO_THREADS)
- MTP draft-head support for the Flash-Next GGUFs (--spec-type draft-mtp)
- WS4 hyperconn prefill fusions (DSV4_HC_COMB/PRE/POST); ggml sched
  alloc-fallback sync fix; QSA dense shortcut (DEFAULT ON) + LLAMA_QSA_OFF
- QSA decode campaign (2026-09-07): fused INDEXER_POOL/SCORE ops, f16
  indexer caches, incremental derived block-vector cache, two-round
  16-bit topk select, CUDA device-description gfx-id exposure
- arch policy (2026-09-07 crossover tables): dense decode below the
  per-arch depth, QSA at/above (gfx1151 default 65536);
  LLAMA_QSA_DENSE_DECODE_UNTIL overrides
- MoE weighted-reduction fusion on the Meta-TP path; concat-transposed,
  mmid-512x10, repeat-absorb hc-combine absorb; model support in
  src/models/qwen4exp.cpp

Re-based onto the 050dde5 delivery core 2026-09-07: the only
upstream-drift conflict (ggml-cuda/common.cuh) resolved by keeping both
the upstream gfx90c GCN-APU arch macros and the block's exact-SKU
GGML_CUDA_CC_IS_GFX1151 predicate.  Upstream's branchless Q4_K/Q5_K mmvq
unpack + L2 prefetch and the block's k-quant/decode additions coexist
(mmqq/vecdotq regions disjoint).  Full beta notes + validation:
beta/qwen4exp/README.md.

Amended 2026-09-08 with the compiler-warning cleanup for the
ggml-clang / ggml-clang++ (system clang 16) and ROCm 7.14 HIP builds:
drop the unused n_blocks local in ggml_indexer_fill (ggml.c); add the
GGML_OP_INDEXER_SCORE/GGML_OP_INDEXER_FILL case labels to the CPU
compute-forward switch (GPU-only fused ops; the CPU plan phase already
aborts on them); drop the unreachable break after the noreturn
GGML_ABORT in the HC_MIX/HC_COMBINE CPU type dispatches (ops.cpp);
restore the GGML_CUDA_QSA_INDEXER_CACHE env tri-state (0/1/2) so the
idx_cache == 2 debug probe is actually reachable (was narrowed to bool);
size_t loop counter in the device gfx-id sniff.  No generated-code or
runtime-behavior change in default configs.

Amended 2026-09-08 (2nd) with the qwen4exp tensor-split backend gate:
llm_arch_supports_sm_tensor(qwen4exp) is now true only on HIP builds
(#ifdef GGML_USE_HIP).  The fused QSA/HC/WS4 ops tensor-split through
the meta backend are validated on ROCm only (3x R9700); on backends
that cannot run them on-device (Vulkan, Metal, SYCL) the CPU-fallback
subgraphs leave the meta splitter unable to reconcile mirrored-vs-split
operand states (abort at ggml-backend-meta.cpp handle_generic, e.g. the
qwen4exp gated-attention MUL on Vulkan), and NVIDIA CUDA is untested,
so those builds keep upstream's unsupported (clean error / arch-test
SKIP) instead of crashing.

Amended 2026-09-10: freed-cell KV handling moved from host-side zeroing
to kernel-side masked-V elimination, and the earlier host zeroing
(seq_rm/seq_keep/clear per-free memsets, 2026-09-09 fold of the strix
aad5adb port) is REMOVED.  The llama-kv-cache files are back to the
upstream state (no zero_freed/rows_hw/sharers wiring; evicting a KV
sequence is pure host cell bookkeeping again on every backend).  In its
place block 14 now carries the three kernel fixes that make freed-cell
content unreadable in masked flash attention, so the host workaround is
unnecessary:

- HIP fattn-tile.cuh: in the packed-bf16 PV path, zero the per-warp V
  register copies of rows whose P is exactly +0.0 across the warp's
  columns (the fully-masked rows) before the bf16 dot.
- HIP fattn-mma-f16.cuh: after each V-tile slice is staged in shared
  memory, zero the rows the mask tile marks blocked (-inf) for every
  query column of the block; one extra uniform barrier on the masked
  path (ncols2 > 1 || mask_h) only; compile-time excluded for the
  V_is_K_view and NVIDIA swizzled (swz_V) paths.
- Vulkan flash_attn_cm1.comp: never read V of fully masked columns
  (per-column liveness -> V of dead columns stays +0.0); flash_attn.comp
  scalar path likewise skips the V load for dead columns.

All three are unconditional in their kernel paths (no arch/env gating):
they are correctness fixes for any masked flash-attention column whose
cell is stale/freed (batch serving, KV eviction), active by default on
every device.  They were root-caused and validated on the Strix Halo
box (single gfx1151): 16/16 identical-request determinism gates pass on
both ROCm and Vulkan with the host zeroing disabled, bit-identical
across every KV cache type each backend's FA supports (ROCm
f16/bf16/q8_0/q4_0; Vulkan also q4_1/q5_0/q5_1/iq4_nl), with
test-backend-ops FLASH_ATTN_EXT vs CPU 4591/4591 (ROCm) and 7822/7822
(Vulkan) and no measurable decode/prefill regression (tg128 within
0.05% at depth-16384; pp within single-run drift).  Full record:
wip/strix-halo/kvzero/RECORD-2026-09-09.md and
wip/kv-sign-leak/HANDOVER-2026-09-09-mma-f16.md in the delivery repo.
The 2026-09-09 amendment text below is retained as history only; the
freed-cell zeroing gate it describes no longer exists in the block.

Amended 2026-09-12 (seventh) with the MTP-export logits-purity fix: an
unmasked embeddings_nextn export (the draft-mtp target) used to defer the
last layer's output-row gather, so the last layer's ffn tail ran on the
full ubatch and the prefill's last-position logits shifted by a ULP
against --spec-type none.  The last layer now always gathers the output
rows before its tail (exactly the plain path) and builds a second
full-row tail solely for t_h_nextn when the chunk drops rows
(n_outputs < n_tokens); a decode/verify batch drops none, so nothing is
duplicated there.  Verified on gfx1151: the mstep NEXTN=1 prefill
mismatch is gone (0 mismatches; was 1 at pos = 4293), the W=4 width
probe is 0 mismatches with the W=8 position list unmoved, the default
and forced-sparse text gates are byte-identical, MTP acceptance is
bit-identical (f16 0.51678 = 77/149 pre- and post-fix), and the graph is
a no-op on every non-NEXTN path.  Instrument: wip/strix-halo/qsa-item4/.

Amended 2026-09-12 (eighth) with the decode/verify band-uniformity fix
for the QSA indexer score.  The score's matmul carries the indexer heads
in its N dimension, so its ne11 is n_idx_h * n_tps (4 * n_tps for
qwen4exp); block 08's "keep the verify batch on the decode kernel" guard
only covered ne11 <= MMVF_MAX_BATCH_SIZE (8), so from n_tps = 3 the
verify batch fell through to MMF while decode (n_tps = 1) stayed on
MMVF.  The two families accumulate the truncated dot product differently,
so the indexer score differed by a ULP and flipped a top-k near-tie: the
forward was bit-identical to decode for 101 steps and then diverged.
The guard now keeps the whole flattened band on the decode family
(ne11 <= MMVF_MAX_BATCH_SIZE_FLAT = 32) and mul_mat_vec_f is instantiated
for ncols_dst 9..32.  The guard is block 08's; block 14 extends it
because it is the block that introduces the flattened batch.  Verified
on gfx1201 (3x R9700, layer split): mstep W = 1,2,3,4,5,8 all 0
mismatches (was W >= 3 impure), the W = 1 reference Thash is unchanged
(2bd73063dd0a9524) so decode numerics are untouched, the f16 band stays
pure, and the forced-sparse q8_0 and default text gates are
byte-identical (a4cdc10dfb6c / 2e078b6966c0).  Cross-check on gfx1151
pending.  Instruments: wip/strix-halo/qsa-item4/.

Amended 2026-09-17 (re-base onto ebbb185): the pair-fusion MUL_MAT_ID arm's
ncols_opt arch gate is broadened from GGML_CUDA_CC_IS_RDNA3_0 to
GGML_CUDA_CC_IS_RDNA3, matching upstream ggml-org#28935 which broadened the standalone
ggml_cuda_mul_mat_q tile heuristic to RDNA3.5 (gfx1151).  No-op on RDNA3_0 and
RDNA4 (the predicate is unchanged there), but it keeps the fused gate+up pair's
tile selection consistent with the standalone on gfx1151 instead of falling
back to the full token count.
[fold target for the qwen4exp/HC/QSA/indexer beta group — see the follow-up commit folds]
Folds beta/mmb-general: the deferred half of 0001 (MMB stand-down of the pair fusion) and 0027
(MMVQ routed band W=16 + gfx1151 dense odd-row band). The qwen4exp/HC/QSA/indexer group folds
into block 15 (it needs block 15's context, which the beta series was authored against).

Campaign fold (wip/moe-expert-cache, beta): `llm_arch_supports_sm_tensor()`
rejects `LLM_ARCH_GEMMA4` until the segmented host-resident-expert async upload
is finished -- gemma4's fused expert tensor (`ffn_gate_up_exps`) has a segmented
split layout and the MMQ tail pad makes the range non-row-aligned, so the
per-ubatch `-ncmoe` upload has no correct path.  All offload routes are covered
at the arch level; `-sm layer` is unaffected.

r6 (2026-10-05, issue ggml-org#95): publish `GGML_USE_HIP` on the `ggml` target so the
qwen4exp tensor-split gate sees it in dynamic-backend builds.  `ggml_add_backend()`
only adds `GGML_USE_<backend>` to `ggml` when `GGML_BACKEND_DL` is OFF, and the
published containers build with `-DGGML_BACKEND_DL=ON` (needed for
`GGML_CPU_ALL_VARIANTS`), so `src/llama-arch.cpp`'s `#ifdef GGML_USE_HIP` compiled
to the HIP-absent branch and `-sm tensor` for qwen4exp failed with "not implemented
for architecture 'qwen4exp'" even though the HIP backend was built.  The static
build already got the macro from `ggml_add_backend(HIP)`; publishing it in
`ggml/src/ggml-hip/CMakeLists.txt` covers both.  Issue ggml-org#95 (@ethanjjjjjjj).

Folded 2026-10-07 (PR ggml-org#106, @briansp2020; issue ggml-org#105): `LLAMA_KV_N_PAD_MIN` raises the n_kv
padding floor (opt-in; the default 256 is unchanged), keeping the per-device tensor-split graphs
stable for longer; and the conv-state tail is copied straight into each rollback slot (one 2D
memcpy on the strided view, about 144 fewer kernels per verify graph;
`LLAMA_CONV_TAIL_CONT=1` restores the cont+copy).
sjaccaud added a commit to sjaccaud/llama.cpp that referenced this pull request Oct 27, 2026
qwen4exp (Qwen3.8-Flash-Next) support on the rdna-boosts core, squashed
from the beta/qwen4exp delta (fork qwen4exp tip dd4301fb4, re-based to
the 465e49b-era core; promoted to the delivery at the 050dde5
re-base 2026-09-07):

- QSA layers: fused indexer top-k (GGML_OP_INDEXER_TOPK, radix), sparse
  flash attention (GGML_OP_FLASH_ATTN_QSA) -- the default FA path;
  LLAMA_QSA_SPARSE_FA=0 keeps dense; CPU reference for the sparse op
- fused decode ops GGML_OP_HC_MIX / GGML_OP_HC_COMBINE (+ kernel
  geometry, rms/gamma fold, F32/Q8_0 inject fold, head-call fusion)
- managed lazy reader (llama-lazy-reader.cpp/.h) with PLE n-gram row
  loading and batched cold-page fetch (LLAMA_LAZY_IO_THREADS)
- MTP draft-head support for the Flash-Next GGUFs (--spec-type draft-mtp)
- WS4 hyperconn prefill fusions (DSV4_HC_COMB/PRE/POST); ggml sched
  alloc-fallback sync fix; QSA dense shortcut (DEFAULT ON) + LLAMA_QSA_OFF
- QSA decode campaign (2026-09-07): fused INDEXER_POOL/SCORE ops, f16
  indexer caches, incremental derived block-vector cache, two-round
  16-bit topk select, CUDA device-description gfx-id exposure
- arch policy (2026-09-07 crossover tables): dense decode below the
  per-arch depth, QSA at/above (gfx1151 default 65536);
  LLAMA_QSA_DENSE_DECODE_UNTIL overrides
- MoE weighted-reduction fusion on the Meta-TP path; concat-transposed,
  mmid-512x10, repeat-absorb hc-combine absorb; model support in
  src/models/qwen4exp.cpp

Re-based onto the 050dde5 delivery core 2026-09-07: the only
upstream-drift conflict (ggml-cuda/common.cuh) resolved by keeping both
the upstream gfx90c GCN-APU arch macros and the block's exact-SKU
GGML_CUDA_CC_IS_GFX1151 predicate.  Upstream's branchless Q4_K/Q5_K mmvq
unpack + L2 prefetch and the block's k-quant/decode additions coexist
(mmqq/vecdotq regions disjoint).  Full beta notes + validation:
beta/qwen4exp/README.md.

Amended 2026-09-08 with the compiler-warning cleanup for the
ggml-clang / ggml-clang++ (system clang 16) and ROCm 7.14 HIP builds:
drop the unused n_blocks local in ggml_indexer_fill (ggml.c); add the
GGML_OP_INDEXER_SCORE/GGML_OP_INDEXER_FILL case labels to the CPU
compute-forward switch (GPU-only fused ops; the CPU plan phase already
aborts on them); drop the unreachable break after the noreturn
GGML_ABORT in the HC_MIX/HC_COMBINE CPU type dispatches (ops.cpp);
restore the GGML_CUDA_QSA_INDEXER_CACHE env tri-state (0/1/2) so the
idx_cache == 2 debug probe is actually reachable (was narrowed to bool);
size_t loop counter in the device gfx-id sniff.  No generated-code or
runtime-behavior change in default configs.

Amended 2026-09-08 (2nd) with the qwen4exp tensor-split backend gate:
llm_arch_supports_sm_tensor(qwen4exp) is now true only on HIP builds
(#ifdef GGML_USE_HIP).  The fused QSA/HC/WS4 ops tensor-split through
the meta backend are validated on ROCm only (3x R9700); on backends
that cannot run them on-device (Vulkan, Metal, SYCL) the CPU-fallback
subgraphs leave the meta splitter unable to reconcile mirrored-vs-split
operand states (abort at ggml-backend-meta.cpp handle_generic, e.g. the
qwen4exp gated-attention MUL on Vulkan), and NVIDIA CUDA is untested,
so those builds keep upstream's unsupported (clean error / arch-test
SKIP) instead of crashing.

Amended 2026-09-10: freed-cell KV handling moved from host-side zeroing
to kernel-side masked-V elimination, and the earlier host zeroing
(seq_rm/seq_keep/clear per-free memsets, 2026-09-09 fold of the strix
aad5adb port) is REMOVED.  The llama-kv-cache files are back to the
upstream state (no zero_freed/rows_hw/sharers wiring; evicting a KV
sequence is pure host cell bookkeeping again on every backend).  In its
place block 14 now carries the three kernel fixes that make freed-cell
content unreadable in masked flash attention, so the host workaround is
unnecessary:

- HIP fattn-tile.cuh: in the packed-bf16 PV path, zero the per-warp V
  register copies of rows whose P is exactly +0.0 across the warp's
  columns (the fully-masked rows) before the bf16 dot.
- HIP fattn-mma-f16.cuh: after each V-tile slice is staged in shared
  memory, zero the rows the mask tile marks blocked (-inf) for every
  query column of the block; one extra uniform barrier on the masked
  path (ncols2 > 1 || mask_h) only; compile-time excluded for the
  V_is_K_view and NVIDIA swizzled (swz_V) paths.
- Vulkan flash_attn_cm1.comp: never read V of fully masked columns
  (per-column liveness -> V of dead columns stays +0.0); flash_attn.comp
  scalar path likewise skips the V load for dead columns.

All three are unconditional in their kernel paths (no arch/env gating):
they are correctness fixes for any masked flash-attention column whose
cell is stale/freed (batch serving, KV eviction), active by default on
every device.  They were root-caused and validated on the Strix Halo
box (single gfx1151): 16/16 identical-request determinism gates pass on
both ROCm and Vulkan with the host zeroing disabled, bit-identical
across every KV cache type each backend's FA supports (ROCm
f16/bf16/q8_0/q4_0; Vulkan also q4_1/q5_0/q5_1/iq4_nl), with
test-backend-ops FLASH_ATTN_EXT vs CPU 4591/4591 (ROCm) and 7822/7822
(Vulkan) and no measurable decode/prefill regression (tg128 within
0.05% at depth-16384; pp within single-run drift).  Full record:
wip/strix-halo/kvzero/RECORD-2026-09-09.md and
wip/kv-sign-leak/HANDOVER-2026-09-09-mma-f16.md in the delivery repo.
The 2026-09-09 amendment text below is retained as history only; the
freed-cell zeroing gate it describes no longer exists in the block.

Amended 2026-09-12 (seventh) with the MTP-export logits-purity fix: an
unmasked embeddings_nextn export (the draft-mtp target) used to defer the
last layer's output-row gather, so the last layer's ffn tail ran on the
full ubatch and the prefill's last-position logits shifted by a ULP
against --spec-type none.  The last layer now always gathers the output
rows before its tail (exactly the plain path) and builds a second
full-row tail solely for t_h_nextn when the chunk drops rows
(n_outputs < n_tokens); a decode/verify batch drops none, so nothing is
duplicated there.  Verified on gfx1151: the mstep NEXTN=1 prefill
mismatch is gone (0 mismatches; was 1 at pos = 4293), the W=4 width
probe is 0 mismatches with the W=8 position list unmoved, the default
and forced-sparse text gates are byte-identical, MTP acceptance is
bit-identical (f16 0.51678 = 77/149 pre- and post-fix), and the graph is
a no-op on every non-NEXTN path.  Instrument: wip/strix-halo/qsa-item4/.

Amended 2026-09-12 (eighth) with the decode/verify band-uniformity fix
for the QSA indexer score.  The score's matmul carries the indexer heads
in its N dimension, so its ne11 is n_idx_h * n_tps (4 * n_tps for
qwen4exp); block 08's "keep the verify batch on the decode kernel" guard
only covered ne11 <= MMVF_MAX_BATCH_SIZE (8), so from n_tps = 3 the
verify batch fell through to MMF while decode (n_tps = 1) stayed on
MMVF.  The two families accumulate the truncated dot product differently,
so the indexer score differed by a ULP and flipped a top-k near-tie: the
forward was bit-identical to decode for 101 steps and then diverged.
The guard now keeps the whole flattened band on the decode family
(ne11 <= MMVF_MAX_BATCH_SIZE_FLAT = 32) and mul_mat_vec_f is instantiated
for ncols_dst 9..32.  The guard is block 08's; block 14 extends it
because it is the block that introduces the flattened batch.  Verified
on gfx1201 (3x R9700, layer split): mstep W = 1,2,3,4,5,8 all 0
mismatches (was W >= 3 impure), the W = 1 reference Thash is unchanged
(2bd73063dd0a9524) so decode numerics are untouched, the f16 band stays
pure, and the forced-sparse q8_0 and default text gates are
byte-identical (a4cdc10dfb6c / 2e078b6966c0).  Cross-check on gfx1151
pending.  Instruments: wip/strix-halo/qsa-item4/.

Amended 2026-09-17 (re-base onto ebbb185): the pair-fusion MUL_MAT_ID arm's
ncols_opt arch gate is broadened from GGML_CUDA_CC_IS_RDNA3_0 to
GGML_CUDA_CC_IS_RDNA3, matching upstream ggml-org#28935 which broadened the standalone
ggml_cuda_mul_mat_q tile heuristic to RDNA3.5 (gfx1151).  No-op on RDNA3_0 and
RDNA4 (the predicate is unchanged there), but it keeps the fused gate+up pair's
tile selection consistent with the standalone on gfx1151 instead of falling
back to the full token count.
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.

3 participants