Skip to content

prefill: the dense GGUF projections through MMQ on HIP (opt-in STRATA_DENSE_MMQ=1) - #820

Closed
xendak wants to merge 1 commit into
Niko1221:mainfrom
xendak:mmq-gfx1030-main
Closed

xendak wants to merge 1 commit into
Niko1221:mainfrom
xendak:mmq-gfx1030-main

Conversation

@xendak

@xendak xendak commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

Notes

This repository is being on the move constantly so its hard to catch up to the
current branch, but i'll be trying to keep this up to date.. i don't have an
ai subscription though so sometimes the update speed is too demanding :')

What

The prompt path's dense GGUF projections run dequantize-to-FP16 + cuBLAS. On the
AMD cards (checked with rocprof on an RX 6900 XT) that is 68% of the prefill GPU
time at ~25% of the card's fp32-accumulate rate, while the same shapes through
llama.cpp's int8 MMQ run ~7x faster. This PR wires them through the MMQ path the
MoE experts already take, opt-in:

  • Gemm::native's beta == 0 products with a covered type, a K that is a
    multiple of 256 values, and a matrix whose tile fits the card's shared memory
    run mmq::quantize + one mul_mat_q per 1024-row chunk (the FP16 activations
    are widened to FP32 in the dequant scratch, which the old path only fills
    afterwards). STRATA_DENSE_MMQ=1 opts in; unset, nothing changes. The
    first dense call prints prefill gemm: dense MMQ on.
  • STRATA_MMQ_KQUANTS now also compiles the q4_k q5_k q5_1 q6_k instances and
    defines STRATA_MMQ_KQUANTS on the HIP branch (the mixed-quant packs' dense
    tensors are K-quants: this model's attention/PLE matrices are
    IQ4_XS/IQ3_S/Q4_K/Q5_K/Q6_K), and adds q6_k to the CUDA list (the usual
    attention weight of plain i-quant GGUFs).
  • moe_mmq grows two small helpers: f16_to_f32 and a {0, rows} bounds
    writer.
  • hip_prefill_mmq_parity gains synthetic Q4_K/Q5_K/Q6_K/Q5_1 passes (skipped
    when the build does not have the instances).

Why K must be a full 256-value multiple

llama.cpp's MMQ loads the weights in 256-value K chunks; the chunk past a
partial row reads bytes past the row - the next row's, or, for the last weight
row, past the whole tensor. The MoE path is safe because its expert gather
buffers end in a zeroed tail; a dense GGUF tensor is passed as-is, so what
follows it is the next tensor's blocks in the mapping. On swift-1.5-iq3_xxs the
640-value IQ4_NL shared-expert down picked a NaN scale out of those bytes and
produced a whole NaN output column, which zeroed the model output - found and
minimized with a type-bisect env knob and an operand-capture replay harness. The
odd shapes stay on the dequantize + cuBLAS path.

Testing (RX 6900 XT, gfx1030, ROCm 7.2.3, i7-13700KF)

-DSTRATA_MMQ_KQUANTS=ON -DSTRATA_ENABLE_HIP=ON, swift-1.5-iq3_xxs, the user's
knobs
(--pool-workers 15 --adapt-every 1 --prefill 8192 --kv-resident 32768 --vram-reserve-mib 1024),
bench_prefill.py fresh medians of two ~4K and two ~8K trials:

4K fresh 8K fresh 8K followup decode
STRATA_DENSE_MMQ unset 436.1 tok/s 447.7 tok/s 163.9 tok/s 40.6 tok/s
STRATA_DENSE_MMQ=1 757.2 tok/s 773.9 tok/s 206.1 tok/s 40.3 tok/s

+73-74% fresh prefill; decode within noise. Greedy output is bit-identical for
the first 200 tokens of a short prompt; longer greedy runs diverge around token
~26 (the q8_1 activation rounding) and stay coherent. hip_prefill_mmq_parity
passes with the new K-quant passes (rel_l2 ≤ 0.005 vs ggml's own dequantizer on
the host).

Not tested: CUDA builds (the instance list change is mechanical), other AMD
archs, the q6_k CUDA instance end to end.

…_DENSE_MMQ=1), the K-quant instances on the HIP build

The prompt path's dense projections (attention qkv/gate, the PLE key, the shared
experts, the router, the head) run dequantize-to-FP16 + cuBLAS: on the RDNA2
cards that is 68% of the prefill GPU time at ~25% of the card's fp32-accumulate
rate, while the same shapes through llama.cpp's int8 MMQ run ~7x faster
(measured with bench_dense_gemm).  This wires Gemm::native's beta = 0 products
through the MMQ path the MoE experts already take: quantize the FP16 activations
to q8_1 (they are converted to FP32 in the dequant scratch, which the old path
only fills afterwards) and run one mul_mat_q product per 1024-row chunk.  It is
opt-in (STRATA_DENSE_MMQ=1; unset, nothing changes) and falls back silently to
the old path on any shape MMQ does not take.

- STRATA_MMQ_KQUANTS also compiles the Q4_K / Q5_K / Q5_1 / Q6_K instances on
  the HIP branch (the mixed-quant packs' dense tensors are K-quants: this
  model's attention/PLE matrices are IQ4_XS/IQ3_S/Q4_K/Q5_K/Q6_K), adds Q6_K to
  the CUDA list, and defines STRATA_MMQ_KQUANTS for the HIP target.
- Gemm::native_mmq: one MMQ context, xq/ident/bounds buffer (~9.5 MB) and a
  failed-flag per Gemm; fp16->fp32 and the {0, rows} bounds helper join
  moe_mmq's kernel set.
- K must be a full 256-value multiple.  MMQ loads the weights in 256-value K
  chunks, and past a partial row the chunk reads bytes past the row - the next
  row's, or for the last weight row bytes past the tensor, which no caller
  guarantees to be zeros (the MoE path is safe because its gather buffers end
  in a zeroed tail).  On this pack the 640-value IQ4_NL shared-expert down
  picked up a NaN scale from the next tensor's blocks and zeroed the output;
  the odd shapes stay on the dequantize + cuBLAS path.

Measured on the RX 6900 XT (gfx1030), swift-1.5-iq3_xxs, WORKERS=15 ADAPT=1
PREFILL=8192, bench_prefill.py fresh medians: 4K prefill 436.1 -> 757.2 tok/s,
8K fresh 447.7 -> 773.9 tok/s, 8K followup 163.9 -> 206.1 tok/s; decode within
noise (40.6 vs 40.3 tok/s at --pcie-frac 0, 52.7 vs 49.9 at the 0.55 default).
Greedy output: bit-identical for the first 200 tokens of a short prompt; a 4K
prompt's greedy text diverges at token ~26 (the q8_1 activation rounding) and
stays coherent.
@Theaninova

Copy link
Copy Markdown

Holy moly I'm getting probably like 4x-5x the prefill on my RX6800, ~600tok/s

Niko1221 added a commit that referenced this pull request Oct 6, 2026
…CUDA builds (#820)

Gemm::native_mmq, its call, the f16_to_f32/set_bounds kernels and the Q6_K case are under __HIPCC__; the CUDA type list loses q6_k. The K % 256 != 0 out-of-bounds read is already refused in native_mmq (kept). Still opt-in: STRATA_DENSE_MMQ=1 on a STRATA_MMQ_KQUANTS=ON HIP build.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Niko1221

Niko1221 commented Oct 6, 2026

Copy link
Copy Markdown
Owner

Taken for 0.1.40 as opt-in (STRATA_DENSE_MMQ=1, needs a HIP build with STRATA_MMQ_KQUANTS=ON), HIP only. The CUDA build gets no q6_k instance and the K % 256 read is refused in native_mmq. We could not reproduce the gain on gfx1151 (UD-Q4_K_XL, 8K prompt, 10 interleaved pairs): median prefill 806 vs 797 tok/s, and the output differs (KL against the default path 0.008-0.09 on 6 prompts, mean 0.03). So it stays opt-in. On gfx1030 without a hipBLASLt table it may still win, as you measured.

@Niko1221 Niko1221 closed this Oct 6, 2026
Niko1221 added a commit that referenced this pull request Oct 6, 2026
…CUDA builds (#820)

Gemm::native_mmq, its call, the f16_to_f32/set_bounds kernels and the Q6_K case are under __HIPCC__; the CUDA type list loses q6_k. The K % 256 != 0 out-of-bounds read is already refused in native_mmq (kept). Still opt-in: STRATA_DENSE_MMQ=1 on a STRATA_MMQ_KQUANTS=ON HIP build.
anon761 pushed a commit to anon761/Strata-upstream that referenced this pull request Oct 6, 2026
…CUDA builds (Niko1221#820)

Gemm::native_mmq, its call, the f16_to_f32/set_bounds kernels and the Q6_K case are under __HIPCC__; the CUDA type list loses q6_k. The K % 256 != 0 out-of-bounds read is already refused in native_mmq (kept). Still opt-in: STRATA_DENSE_MMQ=1 on a STRATA_MMQ_KQUANTS=ON HIP build.
Niko1221 pushed a commit that referenced this pull request Oct 7, 2026
…5 (PR #917)

Interactive Q2_0 and UD-IQ4_XS chats against llama.cpp Vulkan, then controlled A-E runs of the gfx1151 hipBLASLt tuning table
(250.9 to 528.9 prompt tok/s), dense MMQ (+0.2%) and the shared-expert decode stream (+2.5%, 8 requests). The build is
0.1.39 plus then-unmerged PRs (#895, #820, a tuning table adapted from #755), not the 0.1.40 release; the index line in
COMMUNITY_BENCHMARKS.md says so and notes that 0.1.40 turned the shared-expert stream off on HIP (#826).
sskver pushed a commit to sskver/Strata that referenced this pull request Oct 7, 2026
…CUDA builds (Niko1221#820)

Gemm::native_mmq, its call, the f16_to_f32/set_bounds kernels and the Q6_K case are under __HIPCC__; the CUDA type list loses q6_k. The K % 256 != 0 out-of-bounds read is already refused in native_mmq (kept). Still opt-in: STRATA_DENSE_MMQ=1 on a STRATA_MMQ_KQUANTS=ON HIP build.
0xrjman pushed a commit to 0xrjman/Strata that referenced this pull request Oct 7, 2026
…5 (PR Niko1221#917)

Interactive Q2_0 and UD-IQ4_XS chats against llama.cpp Vulkan, then controlled A-E runs of the gfx1151 hipBLASLt tuning table
(250.9 to 528.9 prompt tok/s), dense MMQ (+0.2%) and the shared-expert decode stream (+2.5%, 8 requests). The build is
0.1.39 plus then-unmerged PRs (Niko1221#895, Niko1221#820, a tuning table adapted from Niko1221#755), not the 0.1.40 release; the index line in
COMMUNITY_BENCHMARKS.md says so and notes that 0.1.40 turned the shared-expert stream off on HIP (Niko1221#826).
svgop added a commit to svgop/Strata that referenced this pull request Oct 17, 2026
…__CUDACC__ (STRATA_PREFILL_MMQ is already PUBLIC on strata_prefill in the CUDA branch - CMake:1517), moe_mmq f16_to_f32/set_bounds + kernels un-gated (Niko1221#820 note updated), STRATA_DENSE_MMQ_MAXT T-gate added (MMQ = bandwidth regime small-T, cuBLAS FP16 tensor cores = compute regime large-T). Build reconfigured STRATA_MMQ_KQUANTS=ON (libstrata_kq.so 14.5MB deployed to engine/). ENGAGEMENT confirmed (K-quant kernels opened + dense MMQ on). Paired probes BOTH boot orders: 21k-tok -2% SLOWER (real, warm-bias favored arm), 2.7k-tok +3.1% FASTER, 1k +0.45% - the a1444e3 curve shape, crossover ~4k. Gated arm (MAXT=4096) 3v3 + MMLU 2x chain running
samnolak added a commit to samnolak/Strata that referenced this pull request Nov 2, 2026
…pt projections as int8 MMQ

Gemm::native_mmq was HIP-only. On a GeForce Ada card FP16 x FP16 -> FP32 runs at half the FP16 rate (RTX 4080:
97.5 TFLOPS) while int8 runs at 389.9 TOPS, and the FP16 path dequantizes every dense weight again for every chunk.
UD-Q6_K_XL keeps its dense projections in Q8_0 (attn_qkv / attn_gate / ssm_out, attn_q / k / v / output, the shared
expert's gate / up): ~2.9 G weights, ~55 % of a prompt token's GEMM work, while the routed experts already run on MMQ.

- gemm.cu: the native_mmq guards are STRATA_PREFILL_MMQ only (CUDA builds define it with the native experts);
  STRATA_DENSE_MMQ=1 as on HIP, plus STRATA_DENSE_MMQ_MIN_T=N (only products of >= N rows); not with
  STRATA_DEQUANT_SIDE (its side dequant into the scratch would not wait for this product's FP32 rows there).
- moe_mmq.cu: f16_to_f32 / set_bounds on CUDA too (Niko1221#820 had kept them HIP-only).
- dense_mmq_rules.hpp + dense_mmq_rules_test (CPU): the switch parse, the K rule (K % 256 == 0, K <= 8192: the
  640-wide shared-expert down stays on cuBLAS), the row passes.

Not bit-identical to the FP16 path (W8A8, q8_1 activations, as the routed experts and decode's MMVQ): off by default,
KL gate before use. Unset: p55/final's paths, unchanged bits.

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants