cuda: Q4_0_ROCMFP4 + Q4_0_ROCMFP4_FAST support and MMQ tiles on HIP (gfx1151) - #40
Conversation
04c216b to
95eecae
Compare
There was a problem hiding this comment.
Validation: the HIP gfx1151 build succeeded. Q4_0_ROCMFP4_FAST MUL_MAT passed 45/45 targeted ROCm correctness cases. test-quantize-fns and test-rocmfpx also completed successfully.
Benchmark reproduced for the stated kernel shape: against the dispatch-only parent (ede2bbe), test-backend-ops perf at m=4096, n=512, k=14336 measured 5,540.70 us/run before the MMQ tiles and 2,919.23 us/run with this PR: 1.90x faster. The PR's full-model prefill result remains unverified here because the ROCmFP4 model is not local.
Scope: the stated target is gfx1151, yet the PR changes shared MMQ configuration for Ampere, CDNA, RDNA2/3/4 too. Please split or tightly gate the Strix portion and separately validate every architecture whose policy changes.
comment generated by my clanker Codex
|
Thanks for the review. Three clarifications on scope, plus where we'll comply:
On indexing safety: MMQ entry for this type is gated by the same |
|
Disclosure: written by a Claude agent (Anthropic's Claude Code) for the owner of this account. We did an independent port of the same ROCmFP4 HIP paths (dequant/convert, get_rows, MMVQ, and a Q4_0_ROCMFP4_FAST MMQ path) on a gfx1151 box, and compared it with this PR at the source level. The shared core agrees with ours: same codebook, same UE4M3 decode, same MMQ tile geometry. Three things we noticed in this PR:
For reference, our port passes |
Review finding (halo-box#40): three files added in the first commit are compiled by nothing and referenced nowhere: - ggml/rocmfp4/rocmfp4_hip.cu - two extern "C" dequant kernels; not in any CMake source list (ggml-hip globs only ../ggml-cuda/*.cu and the template instances) and no callers, so unreachable even from tests. - ggml/rocmfp4/rocmfp4_hip_codebook.cuh - included by nothing; live MMVQ/MMQ paths use get_int_from_table_16 + kvalues_rocmfp4 from ggml-common.h, which already lowers to permb on HIP. The single-nibble perm variant was for an FA K/V decode path not present in this PR. - ggml/rocmfpx/rocmfpx_hip_codebook.cuh - same, included by nothing. The live UE4M3 scale decoder is rocmfp4_hip_scale.cuh (included via common.cuh) and stays. No behavior change: verified zero references to these files/symbols in the rest of the tree.
|
Thanks — all three confirmed on our side, cleanup pushed in 0ba39db:
And yes please — run |
Loads Q4_0_ROCMFP4, Q4_0_ROCMFP4_FAST and the Q2/Q3/Q6/Q8_0_ROCMFPX GGUF tensor types on ggml-cuda/HIP (previously CPU+Vulkan only). Adds dequant, convert, get_rows support and MMVQ dispatch; restores the *_hip_* helper headers (scale LUT + codebooks) that the format import pruned. Part 1/2 of the FP4-on-HIP work; part 2 adds the MMQ tile path.
Step 2 of the Strix Halo HIP plan: give the production FP4 format an MMQ path so prefill batches (>8, incl. spec-verify at wide ubatch and MoE routing) stop falling through to dequant+hipBLAS purely because the type was absent from the MMQ switches. Mirrors MXFP4 exactly - same block size (32), one UE4M3 scale per 32 values, SRAM_LAYOUT_Q8_1 tiles, q8_0_q8_1 vec_dot kernels (dp4a and MMA data layout), D4 y-side quantizer. Differences from MXFP4: kvalues_rocmfp4 codebook (Codebook10, max level 10 not 12) and the UE4M3 scale decoder without the e8m0 *0.5 factor. Tile rows mirror this master's current MXFP4 config set, incl. the 128/64/128 row. Not added to Blackwell configs on purpose: use_native_fp4 is false for this type there, so it falls through to the Ampere config like NVFP4-generic does. Dual-scale Q4_0_ROCMFP4 (2 scales/block) deferred to a follow-up slice (NVFP4-style per-16 convention). Validated: test-backend-ops vs CPU ref on RTX 4090 CUDA (default build -p rocmfp4: 288 OK / 0 FAIL; GGML_CUDA_FORCE_MMQ=ON -o MUL_MAT -p rocmfp4: 42 OK / 0 FAIL) and on gfx1151 HIP (-o MUL_MAT -b ROCm0 -p rocmfp4: 78 OK / 0 FAIL). Measured on Ryzen AI Max+ 395 against a rebuilt merge-base in the same session: pp512 +6.1%, pp2048 +5.4% (palindrome x2, zero overlap); MUL_MAT m=4096,n=512 kernel 5389 -> 3503 us (x1.54).
Review finding (halo-box#40): three files added in the first commit are compiled by nothing and referenced nowhere: - ggml/rocmfp4/rocmfp4_hip.cu - two extern "C" dequant kernels; not in any CMake source list (ggml-hip globs only ../ggml-cuda/*.cu and the template instances) and no callers, so unreachable even from tests. - ggml/rocmfp4/rocmfp4_hip_codebook.cuh - included by nothing; live MMVQ/MMQ paths use get_int_from_table_16 + kvalues_rocmfp4 from ggml-common.h, which already lowers to permb on HIP. The single-nibble perm variant was for an FA K/V decode path not present in this PR. - ggml/rocmfpx/rocmfpx_hip_codebook.cuh - same, included by nothing. The live UE4M3 scale decoder is rocmfp4_hip_scale.cuh (included via common.cuh) and stays. No behavior change: verified zero references to these files/symbols in the rest of the tree.
Review scope reduction (halo-box#40 review): keep the MMQ tile rows only where the path is actually validated - RDNA3.5 (gfx1151, test-backend-ops 78/0) and the Ampere config table (RTX 4090 sm_89). Drop the pascal-dp4a / pascal-older / rdna2 / rdna3 / cdna / rdna4 rows: no hardware to measure there, so those architectures keep today's dequant + BLAS fallback (zero behavior change) instead of an unvalidated tile policy. should_use_mmq now returns false for this type outside the gated set (including Blackwell, which has no rows - native FP4 MMQ is MXFP4/NVFP4-only there), so a missing config row can never reach ggml_cuda_mmq_get_config's abort.
0ba39db to
d3a8ab5
Compare
|
Scope reduction done and branch rebased onto current master ( What changed in response to the review:
Re-validation on the rebased head (RTX 4090, sm_89 — the NVIDIA arm kept):
gfx1151 numbers from your validation (45/45 targeted cases, and our earlier 78/0) should carry over — the rebase resolves One heads-up unrelated to this PR: |
dzannotti
left a comment
There was a problem hiding this comment.
Verified on Strix Halo (gfx1151, HIP). Qwen3-1.7B was quantized to Q4_0_ROCMFP4_FAST and Q4_0_ROCMFP4 with this branch's llama-quantize; runs are ABBA n=4.
merge-base ec01a7dfc (CPU fallback) |
this PR | |
|---|---|---|
| ROCMFP4_FAST pp512 / pp2048 / tg128 | 125.1 / 120.3 / 46.5 | 5907.5 / 5966.8 / 176.4 |
| ROCMFP4 pp512 / pp2048 / tg128 | 94.6 / 93.2 / 32.3 | 3321.6 / 3384.6 / 164.1 |
| gpt-oss-20b MXFP4 (regression check) | 1921.0 / 1981.7 / 73.6 | 1921.0 / 1982.8 / 73.7 |
MMQ tiles vs this branch's own dispatch-only commit 2fd4c2a77 (-ub 512):
| dispatch-only | MMQ | Δ | |
|---|---|---|---|
| pp512 | 3373.6 | 5894.0 | +74.7% |
| pp2048 | 3403.4 | 5970.6 | +75.4% |
| tg128 | 176.7 | 176.4 | – |
kernel MUL_MAT m=4096 n=512 k=14336 |
5482.6 µs | 2922.0 µs | 1.88× |
test-backend-ops -b ROCm0: MUL_MAT 45/45 (FAST) and 90/90, MUL_MAT_ID 73/73 and 146/146, GET_ROWS 8/8 and 16/16.
Please fix before merge:
- Description: it says Q3/Q6/Q8_0_ROCMFPX are supported, but on HIP they are still
not supported(MUL_MAT 0/0, 78 cases skipped each) and fall back to CPU. - Scale decode:
rocmfp4_hip_scale.cuhdecodes scale bytes0x7f–0xffdifferently from the CPU decoder (rocmfp4.c), which returns 0 for them. The comment says those bytes are validated first, but validation only runs with--check-tensors, so a crafted GGUF gives different results on GPU and CPU. It's not an out-of-bounds read.if (x > 0x7e) return 0.0f;would align the two.
Comment generated via automated review of clanker Claude Code.
The arithmetic UE4M3 half-scale decode (non-LUT branch) accepted bytes 0x7f-0xff and produced garbage scales, while the CPU decoder (rocmfp4.c) returns 0 for them. Loader validation of those bytes only runs with --check-tensors, so a crafted GGUF decoded differently on GPU and CPU. Guard x > 0x7e -> 0 in both branches to match CPU. Verified bit-identical to the CPU LUT decoder for all 256 scale byte values; nvcc sm_89 compile clean. No behavior change for valid bytes, so existing test-backend-ops results stand. Assisted-by: Hermes Agent (Qwen3.8-Flash-Next)
|
Both points addressed in 40cc375. Description: corrected. The PR now states that only Scale decode: adopted the suggested guard in Verification for the decode change:
Note the guard also covers the three call sites that feed MMQ tile loads and MMVQ ( |
dzannotti
left a comment
There was a problem hiding this comment.
Re-verified on Strix Halo (gfx1151, HIP) at 40cc375d8.
| check | result |
|---|---|
| test-backend-ops MUL_MAT / MUL_MAT_ID / GET_ROWS, q4_0_rocmfp4 | 90/90, 146/146, 16/16 |
| same, q4_0_rocmfp4_fast | 45/45, 73/73, 8/8 |
| llama-bench Qwen3-1.7B ROCmFP4_FAST, previous head vs this head (ABBA) | pp512 +1.3%, pp2048 +0.4%, tg128 ±0%: no cost from the guard |
The scale-decode guard now matches the CPU decoder for invalid bytes, and the description matches the HIP dispatch. Approving.
Comment generated via automated review of clanker Claude Code.
Keep ROCmFP4 FAST and MXFP4/NVFP4 W4A4 MMQ declarations. Keep explicit rollback registrations, including Qwen4exp, without duplicate directory runs. Combine the status/all-model harness with scaled-weight seq_cp/graph-reuse coverage. Assisted-by: Codex
One conflict, ggml/src/ggml-cuda/dequantize.cuh: master added the ROCmFP4 dequantizers (halo-box#40) where this branch added the turbo2/3/4 ones. Kept both. Turbo type IDs (109-111) stay clear of master's ROCmFPX range (100-107). Stamp: Kurumi#Opus5.5H@fresh-io Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017fbUwN2eaP1kh7wNDYofQk
What
Adds ggml-cuda/HIP support for the two ROCmFP4 types this tree ships for CPU+Vulkan:
Q4_0_ROCMFP4,Q4_0_ROCMFP4_FAST. The Q2/Q3/Q6/Q8_0_ROCMFPX reference layouts stayCPU+Vulkan-only; on HIP they remain not supported and fall back to CPU (no kernels added here).
get_rows, MMVQ template instances, backend glue, so these GGUFs load and run on HIP at all. Alsorestores the
*_hip_*helper headers (scale LUT + codebooks) that the format import pruned whilecommon.cuhstill includes them.Q4_0_ROCMFP4_FAST- mirrors the existing MXFP4 tiles:SRAM_LAYOUT_Q8_1loads,
q8_0_q8_1dp4a/WMMA vec_dot, D4 y-layout, UE4M3 scale decoder without the e8m0 x0.5factor, kvalues_rocmfp4 codebook (max level 10, not NVFP4's 12). Config rows are added only for
RDNA3.5 (gfx1151) and Ampere - the two architectures validated below;
should_use_mmqgates thetype to exactly those, so every other architecture keeps today's dequant + BLAS fallback with
zero behavior change (per review: no unvalidated policy changes on pascal/rdna2/rdna3/cdna/rdna4/blackwell).
Why
Without this, HIP builds run FP4 matmuls through dequant+hipBLAS. On Radeon 8060S that fallback is
the prefill bottleneck for the published Strix Halo ROCmFP4 GGUFs; HIP is half the userbase's default
backend. Tile rows mirror this master's current MXFP4 config set (incl. the 128/64/128 row), so the
gain sits on top of today's MXFP4 tuning.
Correctness
test-backend-opsvs CPU reference, re-verified on the rebased head:-o MUL_MAT -p rocmfp4: 78 OK / 0 FAIL and 81 OK / 0 FAIL on two consecutive runs (exit 0; the case list randomizes broadcast shapes per run). On sm_89should_use_mmqreturns true for this type by default (turing_mma_availableshort-circuit), so these runs exercise the Ampere tile rows, not a BLAS fallback.-o GET_ROWS -p rocmfp4: 14 OK / 0 FAIL (exit 0)-DGGML_HIP=ON -DGPU_TARGETS=gfx1151ROCm 7.x:test-backend-ops test -o MUL_MAT -b ROCm0 -p rocmfp4: 78 OK / 0 FAIL (pre-rebase head; re-run offered below)cuda: return 0 for invalid ROCmFP4 scale bytes in the HIP decoder): thenon-LUT
rocmfp4_ue4m3_to_fp32_half_finitebranch decoded scale bytes0x7f-0xffinstead ofreturning 0 like the CPU decoder, so a crafted GGUF diverged GPU vs CPU (validation only runs
with
--check-tensors). Now guarded on both branches; verified bit-identical to the CPU LUTdecoder for all 256 scale byte values, and nvcc/sm_89 compile clean. No behavior change for
valid bytes, so the test-backend-ops results above stand.
Performance (Radeon 8060S / gfx1151, 128 GB)
Baseline rebuilt from the merge-base of this PR in the same session on the same machine
(
ab-base-dispatch= master + commit 1 of this PR, without the MMQ tiles), so the delta isolatesexactly the tile path. Method per CONTRIBUTING: warmup discarded; palindrome arm order
base/new/new/base x2 (8 runs per cell);
-fa on; ubatch 512.MUL_MAT m=4096,n=512(test-backend-ops perf, ROCm0): 5389.5 -> 3503.0 us (x1.54).<=8 stays on MMVQ by design).
Deferred / notes
Q4_0_ROCMFP4(non-FAST) MMQ deferred: needs the NVFP4-style per-16 scale convention.HIP_LAUNCH_BLOCKINGunset), not CI posture; numbers are notcomparable to blocking-mode runs.
the whole block, measured x1.67 at n=8 on gfx1151). Happy to split it into its own PR if wanted.
Written by Hermes Agent (Nous Research); benchmarks run on a Ryzen AI Max+ 395.
Cleanup after review: dropped unreferenced
ggml/rocmfp4/rocmfp4_hip.cu,rocmfp4_hip_codebook.cuhandrocmfpx/rocmfpx_hip_codebook.cuh(never in any CMake source list, no callers; live UE4M3 scale decoder isrocmfp4_hip_scale.cuhviacommon.cuh).Rebased onto current master (
ec01a7dfc) and reduced scope per review: MMQ config rows now exist only for RDNA3.5 + Ampere, with an explicitshould_use_mmqgate so unvalidated architectures (incl. Blackwell/Rubin) fall back to dequant + BLAS instead of inheriting a tile policy nobody measured.