perf(ggml/hip): optimize ROCMFPX fp3/fp2 MMVQ on RDNA (perm-LUT + VDR=4 + fp2)#21
perf(ggml/hip): optimize ROCMFPX fp3/fp2 MMVQ on RDNA (perm-LUT + VDR=4 + fp2)#21DeanoC wants to merge 2 commits into
Conversation
…=4 + fp2)
Optimize the ROCMFPX single-token MMVQ path (batch-1 decode + DSpark spec seed)
for the DeepSeek-V4-Flash expert projections on RDNA/gfx1151.
fp3 (Q3_0_ROCMFPX):
- perm-LUT decode - 4 three-bit codes -> one v_perm_b32
(rocmfpx_pack4_fp3_bits12_vec_cuda); 12-bit field extraction via a dword-splice
instead of a per-bit OR loop.
- VDR=4 half-block-aligned vec_dot - iqs in {0,4} covers exactly one 16-weight
half-block (single scale, no straddle branch); the 48-bit window is spliced out
in one 64-bit read and fed through two independent dp4a chains. Doubles
work-per-lane, halves lanes-per-block and the K-loop trip count.
fp2 (Q2_0_ROCMFP2):
- two independent dp4a chains (byte-identical) and the ds_swizzle warp reduction
extended from fp3 to fp2 (shared helper warp_reduce_sum_rocmfpx_dsswizzle).
Cumulative DSpark spec decode (lucebox3, gfx1151 Strix Halo, ROCm 7.2.4;
--verify-width 4, mclk pinned 1000 MHz, matched-clock, 5-7 reps):
stage spec tok/s step correctness
b9e5804 (base, Luce-Org#496+Luce-Org#513) 21.7 -- byte-identical to stock
+ perm-LUT / fold / ds_swizzle 24.3 +12% byte-identical (SHA match)
+ fp3 VDR=4 + fp2 26.6 +9% verified correct (*)
TOTAL vs base 26.6 +22.6%
fp3-isolated kernel time, type-104 vs the unchanged type-101 fp4-fast control
(clock-independent ratio, rocprofv3): -18.5%.
(*) The VDR=4 fp3 rewrite is NOT byte-identical: regrouping the per-lane
accumulation reassociates the cross-lane float reduction (<=~1 ULP per block, far
below the argmax margin). Everything else - perm-LUT, epilogue fold, ds_swizzle,
and the entire fp2 change - IS byte-identical. Validated by: greedy math (=630)
and 3-pet logic correct, structured prompts (JSON / planet facts / number
spelling) byte-identical to base, spec accept_rate preserved (0.78 -> 0.79), and
an 8-prompt diverse greedy quality suite with no regressions.
HIP-only, wave32-gated (width==32); non-HIP and wave64 (CDNA) fall back to the
generic kernel. No effect on other quant types or backends.
Add kernel-decode correctness tests to ggml/rocmfpx/test_rocmfpx.c and wire it as
a ctest target (rocmfpx_unit) built and run in the hosted CI job:
- decode tables: fp3 {0,1,2,4,0,-1,-2,-4}, fp2 {-1,0,1,2} - the mapping the device
perm-LUT encodes, tied to it by the compile-time static_assert in vecdotq.cuh.
- decode contract: reconstruct each weight from unpacked code -> value * half-block
scale using the kernels' bit unpacking, assert bit-exact vs rocmfpx_dequantize_row
(pins bit order, code table, and the e[i>=QK/2] half-block scale split).
- dot structure: db * sum_half(e_half * sum_i code*q8) == float reference dot - the
VDR=4 fp3 / two-chain fp2 accumulation - rel err < 1e-5.
Host-only (compiles rocmfpx.c directly, no GPU), so it runs on the GitHub-hosted CI
job; added to the ci.yml build target list and the ctest -R filter.
|
Thanks @pramodith — addressed both: Kernel-level correctness tests (ec48639): added host unit tests in
It's host-only (compiles One note: a full on-GPU GPU-vs-CPU-reference check via E2E correctness commands: added a Reproducing the correctness checks (e2e) section to the PR description — build base + this PR, run greedy (temp 0) DSpark spec decode on both, SHA-256 compare the byte-identical prompts, and verify the VDR=4 set stays correct (arithmetic → 630, logic puzzle solved) with |
pramodith
left a comment
There was a problem hiding this comment.
Nice wins with bit arithmetic!
Summary
Single squashed PR (supersedes #19 + #20 — combined for easier review) optimizing the ROCMFPX single-token MMVQ path (batch-1 decode + DSpark spec seed) for the DeepSeek-V4-Flash expert projections on RDNA/gfx1151. All changes are HIP-only and gated to the ROCMFPX fp3/fp2 types; all other quant types and non-HIP builds are untouched.
Depends on (and measured against)
Stacks on two open upstream PRs — both prerequisites and the benchmark base:
df6f907, an exact ancestor of the base).ad73cb8, an exact ancestor of the base).The PR base branch
base/ds4-mmq-spec-b9e5804is exactly#496merged with#513; only the kernel changes below differ from it.Changes
fp3 (
Q3_0_ROCMFPX)vecdotq.cuh) — decode 4 fp3 codes with a singlev_perm_b32over an 8-byte LUT (rocmfpx_pack4_fp3_bits12_vec_cuda) instead of 4 scalar decodes +make_char4; extract the code window with one dword-splice instead of a per-bit OR loop. Bit-identical.vec_dot— atVDR=4the MMVQ launcher dispatchesiqs ∈ {0,4}, so eachvec_dotcovers exactly one 16-weight half-block (single scale, no straddle branch); the 48-bit weight window is spliced out in one 64-bit read and fed through two independent dp4a chains. Doubles work-per-lane, halves lanes-per-block and the K-loop trip count. This is the one non-bit-identical change (see Correctness).ds_swizzlewarp reduction (mmvq.cu) — reduce the fp3 partial sums with a bitmask-modeds_swizzlebutterfly instead of theds_bpermutelowering HIP emits forwarp_reduce_sum; identical{16,8,4,2,1}order and float adds. Bit-identical.fp2 (
Q2_0_ROCMFP2)4. Two independent dp4a chains (integer accumulation is associative → bit-identical) and the
ds_swizzlereduction extended from fp3 to fp2 (shared helperwarp_reduce_sum_rocmfpx_dsswizzle). Bit-identical.Benchmark
--verify-width 4), greedy (temperature 0), fixed 160-token prompt.rocm-smi --setmclk 2) — the BW-dominant clock, effectively fixed. sclk is not lockable on this APU (the SMU firmware ignores perf-determinism, DPM-mask, and sclk-ceiling controls; it floats 600↔2800 MHz under load). So each config runs 2 full-length warm-ups to reach steady clock, then 7 measured reps, and we report the achieved sclk per config so every comparison is matched-clock (approximate fixed clock ~1850 MHz). Rejected any rep whose sclk dipped (contention).#496merged with#513(base/ds4-mmq-spec-b9e5804, this PR's parent).Cumulative ladder — each adjacent pair was A/B'd in the same session at matched clock:
b9e5804(Luce-Org#496+Luce-Org#513)fp3-isolated kernel time — type-104 (fp3) vs the unchanged type-101 (fp4-fast) as a same-run, clock-independent control (rocprofv3
--kernel-trace): −18.5% (ratio 0.379 → 0.309).Correctness
Everything except the VDR=4 step is byte-identical to base — perm-LUT decode, epilogue fold, ds_swizzle (fp3 & fp2), and the entire fp2 change — verified by SHA-256 match on greedy output.
The VDR=4 fp3 rewrite is not byte-identical: regrouping the per-lane accumulation reassociates the cross-lane float reduction (≤ ~1 ULP per block, far below the argmax margin; the integer dp4a sums stay exact). Validated by:
accept_ratepreserved (0.78 → 0.79);If a byte-identical guarantee is preferred, the VDR=4 hunk is the only non-byte-identical part and can be dropped (keeps the +12% byte-identical rung); happy to split on request.
Reproducing the correctness checks (e2e)
Build the base and this PR (gfx1151), then run the same GGUF greedy on both and compare.
Expected:
accept_ratefrom theDSpark decode: … accept_rate=server log line is preserved (0.78 → 0.79) — a degraded kernel would drop draft acceptance.We ran this over an 8-prompt suite (code, algorithms, exact arithmetic, logic, long-form, JSON, factual recall, repetition); the byte-identical set matched and the VDR=4 set stayed correct with accept_rate held.
Scope
HIP +
GGML_TYPE_Q3_0_ROCMFPX/GGML_TYPE_Q2_0_ROCMFP2only, gated by#if defined(GGML_USE_HIP)plusconstexprtype checks and awidth == 32wave32 guard (non-HIP, CUDA, and wave64/CDNA fall back to the generic kernel). Apache-2.0. For upstreaming toLuce-Org/luceboxthis depends on Luce-Org#496 + Luce-Org#513 landing there first.