Skip to content

ggml-hrx: route Q4_K/Q5_K/IQ4_XS prompt matmuls to the q8_1 x4 kernel (pp512 99 -> 341 on Qwen3.8-27B) - #55

Merged
bong-water-water-bong merged 2 commits into
1bit/hrx-vulkan-patchedfrom
1bit/hrx-q8-prefill-policy
Oct 1, 2026
Merged

bong-water-water-bong merged 2 commits into
1bit/hrx-vulkan-patchedfrom
1bit/hrx-q8-prefill-policy

Conversation

@bong-water-water-bong

@bong-water-water-bong bong-water-water-bong commented Oct 1, 2026 •

Copy link
Copy Markdown

On Qwen3.8-27B UD-Q4_K_XL, 94% of HRX prompt time went to the generic f32 WMMA matmul kernels, which work in 32-token tiles. An iree-profile dispatch trace of a 1.8K-token prompt:

Kernel Share
mul_mat_swiglu 51%
mul_mat_add 25%
mul_mat 18%

This model's projections are mostly Q5_K and IQ4_XS. Two routing rules kept them away from the q8_1 x4 prefill kernel:

  1. 89078ac: the q8_1 x4 matcher took Q5_K and IQ4_XS only when q8_1 activations already existed, and never for a matmul feeding a GLU. Q4_K was allowed both. Q5_K and IQ4_XS now get the Q4_K policy.
  2. 5085ae2: even then, the FFN gate/up projections went to the generic fused mul_mat_swiglu. It is registered at priority 290, above the q8_1 x4 matcher (285), and claims gate, up and the GLU together; that was 59% of prompt time. For prompt chunks of 256-2048 tokens in multiples of 256, whose gate and up are Q4_K, Q5_K or IQ4_XS, it now declines. Each projection then takes the q8_1 x4 kernel and the GLU runs as its own op.

GGML_HRX_Q8_PREFILL_RELAX=0 restores the old routing for both.

gfx1151, llama-bench -b 512 -ub 512 before after
pp512 99.7 340.7
pp2048 99.0 317.7
KLD vs BF16 logits (wikitext-2, 20 x 512) 0.00717 0.00712
same top token 96.13% 96.27%

test-backend-ops -o MUL_MAT -b HRX0: 265/265. Decode is untouched, since both rules apply only to chunks of at least 256 tokens.

🤖 Generated with Claude Code

The q8_1 x4 prefill matcher accepted Q5_K and IQ4_XS weights only when an
earlier dispatch had already produced q8_1 activations, and never when the
matmul feeds a GLU; Q4_K was allowed both. On Qwen3.8-27B UD-Q4_K_XL, whose
projections are mostly Q5_K and IQ4_XS, that left 94% of prompt time in the
generic mul_mat_f32_f32_wmma (an iree-profile dispatch trace of a 1.8K-token
prompt: mul_mat_swiglu 51%, mul_mat_add 25%, mul_mat 18%).

Q5_K and IQ4_XS now follow the Q4_K policy (standalone activation quantize,
GLU consumers allowed). GGML_HRX_Q8_PREFILL_RELAX=0 restores the old policy.

gfx1151, llama-bench -b 512 -ub 512, old -> new: pp512 99.1 -> 143.9,
pp2048 99.3 -> 138.6 tok/s. KLD against the BF16 logits (wikitext-2, 20 x 512):
0.00717 -> 0.00720, same top token 96.13% -> 96.24%. test-backend-ops MUL_MAT
on HRX0: 265/265.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the ggml label Oct 1, 2026
… kernel

With Q5_K/IQ4_XS on the q8_1 x4 prefill kernel, the FFN gate/up projections
still went to the generic f32 WMMA mul_mat_swiglu: it is registered at
priority 290, above the q8_1 x4 matcher (285), and claims gate, up and the GLU
together. It was 59% of prompt time on Qwen3.8-27B UD-Q4_K_XL.

For prompt chunks of 256-2048 tokens (multiple of 256) whose gate and up
weights are Q4_K, Q5_K or IQ4_XS, the generic fused matcher now declines, so
each projection takes the q8_1 x4 kernel and the GLU runs as its own op. It
follows the same switch: GGML_HRX_Q8_PREFILL_RELAX=0 restores the old routing.
The policy helper moves to dispatch-mul-mat-common.h as
common_q8_prefill_relaxed().

gfx1151, llama-bench -b 512 -ub 512, GGML_HRX_Q8_PREFILL_RELAX=0 -> default
(both commits of this branch): pp512 99.7 -> 340.7, pp2048 99.0 -> 317.7 tok/s.
KLD against the BF16 logits (wikitext-2, 20 x 512): 0.00717 -> 0.00712, same
top token 96.13% -> 96.27%. test-backend-ops MUL_MAT on HRX0: 265/265.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@bong-water-water-bong bong-water-water-bong changed the title ggml-hrx: Q5_K/IQ4_XS prefill matmuls take the q8_1 x4 kernel (pp512 99 -> 144 on Qwen3.8-27B) ggml-hrx: route Q4_K/Q5_K/IQ4_XS prompt matmuls to the q8_1 x4 kernel (pp512 99 -> 341 on Qwen3.8-27B) Oct 1, 2026

@bong-water-water-bong bong-water-water-bong left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review (PR-Agent duty). A large showcase win (Qwen3.8-27B HRX pp512 99.7 -> 340.7, pp2048 99.0 -> 317.7) with KLD vs BF16 unchanged (0.00717 -> 0.00712) and MUL_MAT ops 265/265. The approach is right.

Checked:

  • Rule 1 (Q5_K / IQ4_XS take the Q4_K q8_1 x4 policy): standalone q8_1 quantize, GLU consumers allowed. It's gated by common_q8_prefill_relaxed(), read once, default on, with =0 restoring the old routing.
  • Rule 2 (mul_mat_swiglu declines for 256..2048-token chunks in multiples of 256, when both gate and up are Q4_K/Q5_K/IQ4_XS): each projection then takes q8_1 x4 and the GLU runs as its own op. If the q8_1 x4 matcher declines some shape, the node falls back to the generic per-op path, which is correct, just slower. So the decline can't strand a node.
  • Decode untouched: both rules need at least 256 tokens.

Before merge:

  1. The two Windows Test failures (x64-openblas, x64-vulkan). HRX isn't built on Windows, so this change likely isn't the cause, but #54's Windows jobs passed. Please re-run them, or show the failing test is pre-existing. Their logs weren't available when I looked.
  2. The numbers in the code comments (pp512 143 -> 363, 99 -> 141, KLD 0.00720) are from the intermediate builds and differ from the PR table. Fine as history, but say which step each refers to, or use the final numbers, so nobody reads 363 as current.

Non-blocking: this is policy logic in AMD's files (dispatch-mul-mat.cpp, dispatch-gated-mul-mat.cpp, dispatch-mul-mat-common.h), about 35 lines. It's acceptable as small hooks, but if it grows, move common_q8_prefill_relaxed() and the format predicate into a headed file of ours.

Mergeable once (1) is cleared.

@bong-water-water-bong
bong-water-water-bong merged commit 6e42b51 into 1bit/hrx-vulkan-patched Oct 1, 2026
8 of 24 checks passed
bong-water-water-bong added a commit that referenced this pull request Oct 2, 2026
…_q4_0) through llama-hadamard (#58)

The engine's tools/hadamard_q4_0.py writes Q4_0 files whose matmul weights are
rotated per 32-element block along the input dimension by the normalized
Sylvester Walsh-Hadamard matrix, without signs, and stamps
onebit.hadamard_q4_0 = 32. That is the prism.hadamard transform with
block_size 32 and sign_mode identity, so llama-hadamard now reads the stamp too:
the rotated weights are the file's Q4_0 tensors on a verified matmul path (a
Q4_0 lookup table such as token_embd is written in the plain basis). The
activation rotation is an ordinary MUL_MAT against H, so the files run on HRX
and every other backend, where before only the engine's ROCm build (which
rotates inside its quantizers) could run them.

- ssm_alpha / ssm_beta join the verified kinds: qwen35 and qwen35moe build both
  through build_lora_mm, and the engine's files rotate them.
- A stamped weight the model does not load (the MTP layer, blk.64, without
  --mtp) is skipped; prism.hadamard's named weights stay strict.
- verify_graph still refuses a folded weight that reaches a matmul without its
  transform.

Qwen3.8-27B-Q4_0-H32 on HRX0, llama-perplexity, wikitext-2 40 x 512 against the
BF16 logits: PPL 6.021, KLD 0.0292, same top token 92.5% - the ROCm build's
exact int8 path gives 6.008 / 0.0284 / 92.5% (without the rotation HRX gave PPL
550,134). llama-bench on HRX0 (before fork #55): pp512 63.5, pp2048 62.4,
tg64 11.65 tok/s. Q4_0 prompt matmuls still take HRX's generic path; #55
routes the K-quants and IQ4_XS only.

Co-authored-by: bong-water-water-bong <bong-water-water-bong@1bit.gg>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant