hipfire
/learn · deck 01 · RDNA / WMMA

RDNA & WMMA — A Distil

Companion to AMD Distil — CDNA / MFMA. This deck zooms in on the other AMD compute family: RDNA (consumer / pro / APU), the WMMA matrix path that arrived with RDNA3, and historical performance findings retained by the hipfire project across real RDNA silicon (gfx1010 / gfx1030 / gfx1100 / gfx1151 / gfx12xx).

Where the CDNA deck spends its budget on data-center matrix throughput (MFMA, HBM, XCDs), this deck spends its budget on the consumer-silicon realities: wave32, GDDR6 / system-RAM, 16×16×16 matrix tiles via WMMA, and a dispatch story that has to span five generations from RDNA1 (no matrix unit at all) up to RDNA4 (gfx12 WMMA layout with half8 packing on the same 16×16×16 tile).


01 The RDNA family at a glance

hipfire targets the entire RDNA family with one Rust binary — the matrix-path feature gating is the load-bearing piece of the dispatch layer.

interactive · matrix-path generations · what each arch adds
matrix unit arrives gfx12 layout + half8 pack RDNA1 gfx1010 scalar RDNA2 gfx1030 scalar dp4a (i8) RDNA3 gfx1100 scalar dp4a dot2 fp16 v_wmma_* 16×16 tile RDNA3.5 gfx1151 scalar dp4a dot2 fp16 v_wmma_* APU / iGPU RDNA4 gfx1201 scalar dp4a dot2 fp16 v_wmma_* 16×16 · half8 pack
RDNA3 (gfx1100) First gen with the WMMA matrix unit. 16×16×16 fp16 tile per issue, 4096 FMA (8192 FLOPs); ~123 TFLOPs theoretical peak on the 7900 XTX (model, not measured).
arch LLVM target wave VGPRs / SIMD waves / SIMD LDS (max WG / WGP) matrix path
Navi 10 (5700 XT)gfx101032102420≤64 KB WG · 128 KB WGPscalar (no packed DOT, no WMMA)
Navi 21 (6900 XT)gfx103032102416≤64 KB WG · 128 KB WGPv_dot4 / v_dot2 (packed DOT, no WMMA)
Navi 31 (7900 XT/XTX)gfx110032153616≤64 KB WG · 128 KB WGPWMMA (16×16×16, half16)
Navi 32 / 33gfx1101 / gfx110232102416≤64 KB WG · 128 KB WGPWMMA (16×16×16, half16)
Strix Halo APUgfx1150 / gfx115132102416≤64 KB WG · 128 KB WGPWMMA (16×16×16, half16)
Navi 48 / Navi 44 (RX 9070 family / R9700)gfx1201 / gfx120032153616≤64 KB WG · 128 KB WGPWMMA (16×16×16, half8 gfx12 layout)

Two structural breaks worth memorizing:

  1. RDNA1/2 → RDNA3 is where the matrix unit appears. Before gfx1100, the fastest f16-mac path is v_dot2_f32_f16 (a 2-wide packed-FMA) or v_dot4_i32_i8 (dp4a). After gfx1100, you have v_wmma_* with a 16×16 accumulator per wave.
  2. RDNA3 → RDNA4 keeps the wave32 WMMA shape and the 16×16×16 tile, but changes the layout: A/B packing halves from half16_t to half8_t per lane, the builtin gains a _w32_gfx12 suffix, and the C-map moves. LDS stays ≤64 KB per workgroup (≤128 KB per WGP) on both gens. Most kernels still need their own .gfx12.hip variant — for the incompatible builtin/layout, not a wider tile.

02 CDNA vs RDNA — the divergence summary

CDNA — Instinct
MFMA path, data center
RDNA — Radeon / Ryzen AI
WMMA path, consumer
Wavefront64 threads32 threads
Matrix unitMFMA (v_mfma_*) — 16×16×16 fp16, 32×32×8 bf16, FP8 on CDNA 3WMMA (v_wmma_*) — 16×16×16 fp16 on RDNA3 and RDNA4; gfx12 halves A/B packing to half8 with a _w32_gfx12 builtin
MemoryHBM3 / HBM3E / HBM4 (192–288 GB)GDDR6 (8–24 GB) or shared system RAM (Strix Halo)
CacheLarge L2, compute-optimizedSmaller L1/L2 + Infinity Cache (up to 96 MB on 7900 XTX; 64 MB on R9700)
GraphicsNone (no rasterizer, no RT, no display)Full graphics + RT pipeline
Transistor budgetMore CUs, bigger matrix engines, more HBM controllersDisplay engines, rasterizer, RT cores compete with compute
Typical portgfx908 / gfx90a / gfx942 / gfx950gfx1010 → gfx1201

Why this matters for code: the same .hip file compiles for either family — but a CDNA kernel ported to RDNA hits three landmines: wavefront width (64 → 32 halves the lanes seen by __shfl_*), matrix intrinsic (v_mfma_* doesn't exist on RDNA — you must call v_wmma_*), and memory-tier sizing (96 MB Infinity Cache on the 7900 XTX changes your tiling math vs HBM-direct).

03 WMMA — one instruction, one 16×16 tile

The WMMA family is RDNA3+'s answer to CDNA's MFMA. Same shape — a fused D = A × B + C over a 16×16 tile — but reachable from wave32 (half the lanes) and from a fundamentally consumer-silicon caches.

The canonical RDNA3 builtin (used in hipfire's gemm_f16_wmma.hip):

// WMMA layout (gfx11 RDNA3): __builtin_amdgcn_wmma_f32_16x16x16_f16_w32(a, b, c)
// computes  D = A x B + C over one 16x16 tile (ISA-defined lane layout).
//
// A is half16_t (16 fp16 values per lane)
// B is half16_t
// C / D are float8_t (8 fp32 accumulators per lane on wave32)
// (gfx12 RDNA4 keeps 16x16x16 but packs A/B as half8_t per lane, uses a
//  _w32_gfx12 builtin, and remaps C — see “What gfx12 WMMA changes” below.)

typedef _Float16 __attribute__((ext_vector_type(16))) half16_t;
typedef float    __attribute__((ext_vector_type(8)))  float8_t;

__launch_bounds__(32, 2)
extern "C" __global__ void gemm_f16_wmma(
    const _Float16* __restrict__ W, const float* __restrict__ X,
    float*          __restrict__ Y, int M, int K, int N)
{
    // Grid: [ceil(M/16), ceil(N/16)],  Block: [32]
    float8_t acc = {0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f};
    for (int k0 = 0; k0 < K; k0 += 16) {
        half16_t a_reg, b_reg;
        // ...load 16x16 tiles into a_reg and b_reg...
        acc = __builtin_amdgcn_wmma_f32_16x16x16_f16_w32(a_reg, b_reg, acc);
    }
    // ...store acc back to Y...
}
interactive · v_wmma_f32_NxNxN_f16 · one issue, one tile
A · fp16 · 16×16
×
B · fp16 · 16×16
+
C · fp32 · accumulator
=
D · fp32 · result
wave32 lanes contributing:
1 issue · 4096 FMA (8192 FLOPs) per issue — ~123 TFLOPs theoretical peak, 7900 XTX (model)
tile shape
M=16, N=16, K=16
inputs · output
fp16 × fp16 → fp32 accumulator
per issue
4096 FMA (8192 FLOPs) · 16-element dot product per output cell
theoretical peak
~123 TFLOPs · 7900 XTX (gfx1100) theoretical model, not measured
notes
Training-class precision. Default WMMA path on RDNA3.

What WMMA gives you (RDNA3, gfx1100 / 1151):

What gfx12 WMMA (RDNA4, gfx1200+) changes:

04 The roofline — three matrix paths, one chip

Because RDNA3 ships both v_wmma_* and v_dot2_f32_f16 (and the scalar fallback), it has three different compute ceilings on the same chip. Which one bounds you is decided by your kernel's matrix path, not by the GPU's nameplate TFLOPs.

interactive · roofline · 7900 XTX · illustrative theoretical model (nameplate roofs, not measured) · AI = 4·B pedagogical, 0.960 TB/s memory slope · drag the batch-size slider
arithmetic intensity (FLOPs / byte) → performance (TFLOPs/s) → GDDR6 roof · 0.960 TB/s dot2 roof · 41 TF (model) WMMA roof · 123 TF (model) B=1 · decode GEMV
B=1
arithmetic intensity ~4.0 FLOPs/byte
currently bound by GDDR bandwidth
model ceiling (not measured) ~4 TFLOPs/s

This is the regime split the fixture-scoped snapshots in § 07 illustrate: batch size and arithmetic intensity decide which model roof binds, not the card's nameplate alone. Nothing on this chart is a measured hipfire throughput.

05 Why CDNA “throws away” graphics and RDNA doesn't

Mirror of slide 40 from the CDNA deck, but inverted:

RDNA spends transistors on ( + )
  • Rasterizers, ROPs, display engines, video encode/decode.
  • Ray-tracing accelerators (since RDNA2).
  • Mesh shaders, primitive shaders.
  • Smaller, latency-optimized L0 / L1.
What it gives up vs CDNA ( − )
  • Fewer CUs (96 on 7900 XTX vs 304 on MI300X — about 3×).
  • No HBM (24 GB GDDR6 vs 192 GB HBM3 — about 8×).
  • No FP8 / FP6 / FP4 MFMA — RDNA3 WMMA is fp16 / bf16 / i8 / i4 only; RDNA4 WMMA adds FP8 mixes.
  • No XCD-level multi-die packaging — RDNA3 chiplets are MCDs (memory) not compute.

The result for an LLM workload: an RDNA3 board costs ≈ 1/10 of an MI300X. The matrix-unit throughput per dollar is similar if your kernel actually saturates WMMA. The headline divergence is that one of them runs a display.

06 The wave32 occupancy table

WMMA-fused GEMMs are register-hungry. The 8-float accumulator alone costs 8 VGPRs per thread (lane); with tile-staging and A/B register tiles you quickly land in the 80–120 VGPR range. That has to fit the wave32 occupancy curve — drag the slider:

VGPR-capacity model for gfx1100 and gfx1201: 1536 VGPRs per SIMD, wave32 allocation granule 24, cap 16 waves/SIMD — min(16, floor(1536 / (ceil(vgprs/24) × 24))). LDS, thread/workgroup, and barrier limits are explicitly excluded.

interactive · wave32 occupancy explorer
96
gfx1100 · 7900 XTX 1536 VGPRs / SIMD · granule 24
0481216
16 waves / SIMD
gfx1201 · R9700 1536 VGPRs / SIMD · granule 24
0481216
16 waves / SIMD
Typical WMMA fused GEMM (~96 VGPRs per thread): full VGPR-capacity occupancy — 16/16 waves/SIMD on both gfx1100 and gfx1201 (LDS/workgroup limits excluded).
full lookup table
VGPRs / thread (.vgpr_count) gfx1100 waves / SIMD
1536 pool · granule 24 · cap 16
gfx1201 waves / SIMD
1536 pool · granule 24 · cap 16
≤ 96 1616
97–120 1212
121–1441010
145–1689 9
169–1928 8
193–2167 7
217–2406 6
241–2566 6

Two rules of thumb that show up repeatedly in the hipfire kernel notes:

Reading those numbers out of a compiled .hsaco (from the gfx-kernel-metadata skill):

ARCH=gfx1100
# 1. Unbundle the offload container into a real ELF
/opt/rocm/llvm/bin/clang-offload-bundler --type=o --unbundle \
    --input=kernel.hsaco --output=/tmp/kernel.elf \
    --targets=hipv4-amdgcn-amd-amdhsa--$ARCH
# 2. Read AMDGPU notes
/opt/rocm/llvm/bin/llvm-readelf --notes /tmp/kernel.elf
# → .vgpr_count, .group_segment_fixed_size, .private_segment_fixed_size,
#   .wavefront_size: 32 (RDNA) / 64 (CDNA)

07 Measured snapshots — fixture-scoped beta figures

Shared fixtures from src/data/performance.ts (beta branch). Optimization on the hipfire beta branch is continuous. Every figure below is fixture-scoped, and a date is shown only when its source carries a measurement date. These source-published snapshots are not immutable or lasting claims. Numbers update frequently. /docs/benchmarks is the build-time live ledger.

Rows are heterogeneous fixtures. Compare cells only when model, quant/mode, prompt, method, and backend match. No cross-hardware ratio below is presented as an isolated WMMA gain — rows differ in CU count, memory tier, clocks, and backend, not just matrix path.

Qwen3.6 35B-A3B MQ4R · AR · Q8 KV · Ordinary autoregressive decode, single GPU; no MTP, DFlash, reduced-output bench, or manual clock pinning. TG128 = three-run medians. Multi-turn columns from clean eight-turn serving runs. Source: beta README · Qwen3.6 35B-A3B MQ4R (README fixture, undated).

fig · AR decode tok/s, TG128 medians · Qwen3.6 35B-A3B MQ4R · bars count to rounded values, table carries exact medians
gfx1100 · 7900 XTX
0
gfx1151 · Strix Halo
0
gfx1201 · R9700
0
card arch TG128 AR tok/s eight-turn avg tok/s final turn
Radeon RX 7900 XTXgfx1100 253.3 191 160.3 @ 18.2K
Radeon 8060S / Strix Halogfx1151 115.1 92.2 82.5 @ 21.3K
Radeon AI PRO R9700gfx1201 203.9 169.5 146.7 @ 22.2K

Qwen3.8-27B MQ4V2 product ladder · hiptrx · gfx1201 · Radeon AI PRO R9700 · dated 2026-08-20 · Product-ladder checkpoint on the hiptrx gfx1201 / Radeon AI PRO R9700 fixture. AR decode, prefill, DFlash decode, mean accepted draft length (τ), and bits-per-weight. Source: beta · 2026-08-20 Qwen3.8 MQ-V2 product ladder.

tier AR tok/s prefill tok/s DFlash tok/s τ bits/weight
XT 35.3 490.7 251.6 11.7 4.456
Base 33.2 479 263.3 13.11 4.659
Pro 31.8 473.8 258.2 13.11 4.897

Featured checkpoint figure: Base-tier DFlash 263.3 tok/s (Qwen3.8-27B MQ4V2 Base · gfx1201 R9700 · 2026-08-20). The ladder's AR/prefill/DFlash columns are separate protocols on one dated fixture — the spread across tiers motivates batch/AI regime thinking from § 04, not an isolated WMMA speedup.

08 Memory hierarchy — RDNA edition

Same layered story as CDNA, with consumer-silicon numbers. The absolute bandwidths are smaller across the board, but Infinity Cache (96 MB on the 7900 XTX; 64 MB on the R9700) is a uniquely RDNA tier — it sits between L2 and DRAM and dramatically widens the effective LLM-friendly working set.

fig · memory hierarchy, 7900 XTX class
 tier             scope         cap          BW          lat
 ────             ─────         ───          ──          ───
 VGPRs            per CU        8 KB/wave    ~100 TB/s   0 c
 LDS              per WGP       ≤64 KB/WG   ~10 TB/s    ~20 c
 L1               per CU        32 KB        ~5 TB/s     ~50 c
 L2               per array     6 MB         ~3 TB/s     ~150 c
 Infinity Cache   device-wide   64–96 MB     ~17 TB/s    ~200 c   ← RDNA-only
 GDDR / sys RAM   device-wide   8–96+ GB     0.5–1.0     ~400 c
        

The standard CDNA-deck rule still holds: every step down the ladder is at least an order of magnitude slower, so a kernel that touches the same byte twice should get the second touch from LDS/L1, not DRAM. The RDNA twist is that Infinity Cache makes the device-wide working set ~96 MB instead of ~6 MB of L2 on the 7900 XTX — a short-context KV slice can live entirely in IC, which helps gfx1100 decode punch above its raw GDDR bandwidth tier.

09 Dispatch — fast paths first, baseline last

rdna-compute::dispatch is the kernel-selection hot path in hipfire. Every GEMM / GEMV / norm / fused op routes through here. The shape is always the same: arch-feature predicates, fast paths first, baseline (fp16-packed, RDNA1) last.

interactive · dispatch tree · click an arch to see which kernel branch wins
gemm_qkv_hfq4g256(...)
has_wmma_w32_gfx12 → WMMA path (gfx12 layout) gfx1200 / 1201
has_wmma_w32 → WMMA path (RDNA3) gfx1100 / 1101 / 1102 / 1150 / 1151
has_dot2_f32_f16 → dot2 path gfx1011 / 1012 / 1030…
(default) → fp16 packed baseline gfx1010 / 1013 (no packed DOT)
gfx1100 takes the WMMA path (RDNA3) — ~123 TFLOPs theoretical ceiling (model, not measured).
// Simplified WMMA-capability cascade for gemm_qkv_hfq4g256.
// Production may select an MMQ arm first when batch/alignment allow —
// see crates/rdna-compute/src/gemm.rs. Predicate names match arch_caps.rs.
pub fn gemm_qkv_hfq4g256(&self, ...) -> HipResult<()> {
    // if self.arch_caps.has_hfq4_mmq() && dims aligned { mmq(...) }  // may win first
    if self.arch_caps.has_wmma_w32_gfx12() {          // RDNA4: gfx1200/1201
        return self.gemm_qkv_hfq4g256_wmma_gfx12(...);
    }
    if self.arch_caps.has_wmma_w32() {                // RDNA3: gfx11xx, gfx1150/51
        return self.gemm_qkv_hfq4g256_wmma(...);
    }
    if self.arch_caps.has_dot2_f32_f16() {            // RDNA1.1+/RDNA2; false on gfx1010
        return self.gemm_qkv_hfq4g256_dot2(...);
    }
    self.gemm_qkv_hfq4g256_fp16(...)                  // gfx1010/1013 packed-fp16 baseline
}

Two contributor rules from the architecture doc:

  1. Predicates are arch-feature checks, not inline arch.starts_with(...) chains. New silicon = update one predicate, not 40 call sites.
  2. No unreachable branches. When a new arch absorbs a check that was matched by an older || starts_with("gfxN") clause, drop the redundant clause in the same diff.

The per-arch kernel-file naming convention follows the dispatch:

kernels/src/gemm_qkv_hfq4g256_wmma.hip            # default WMMA (RDNA3)
kernels/src/gemm_qkv_hfq4g256_wmma.gfx12.hip      # gfx12xx override (gfx12 WMMA layout)
kernels/src/gemv_hfq4g256.gfx1030.v4.hip          # chip-specific versioned
kernels/src/gemv_hfq6g256_residual_wave64.hip     # wave64 (CDNA fall-back)

scripts/compile-kernels.sh resolves chip → family → default in that order; .gfx12.hip covers both gfx1200 and gfx1201 with one file.

10 Historical hipfire snapshots (superseded)

The figures in this section are retained for architectural context, not as current throughput claims. For dated, fixture-scoped beta figures see § 07 (src/data/performance.ts).

Lifecycle: historical. These fixture-bound observations do not carry the complete measurement date and binary/model identity required for a current measured claim. See beta docs/BENCHMARKS.md.

Historical 7900 XTX (gfx1100) snapshot, then-default asym3 KV and FlashAttention auto (undated multiplier column removed):

model decode tok/s peak prefill tok/s
Qwen 3.5 0.8B3917383
Qwen 3.5 4B 1802487
Qwen 3.5 9B 1321663
Qwen 3.5 27B 47 478

The historical beta table also retained superseded-method DFlash observations: up to 218.6 tok/s on 27B HumanEval/53 and 372.9 tok/s on 9B HumanEval/0. They used asym3 KV and max_tokens=120; results were genre-conditional and are not current baselines. See the historical source table.

Historical 27B AR snapshot across the RDNA family — same model, prompt sweep, asym3 KV, and --no-chatml:

card arch 27B AR decode tok/s prefill tok/s TTFT
7900 XTXgfx1100 44.9 462 328 ms
R9700gfx1201 35.1 — —
Strix Halo iGPUgfx1151 14.95 161 950 ms

Historical HumanEval N=33 fixture. The source does not carry the full per-row measurement date and binary/model identity now required for a measured claim; the observed 7900 XTX-to-Strix spread was consistent with their different memory-bandwidth tiers under that fixture.

Retained historical APU snapshot — Strix Halo (gfx1151), 27B MQ4 with the 27B-DFlash sidecar and the then-canonical code prompt:

modetok/svs AR
AR baseline (no draft)14.95—
hipfire DFlash82.05.5×
lucebox DFlash · llama.cpp fork, same model27.41.8×

Historical row: 3-cell median, τ=10.0, accept rate 0.67. This deck predates the current complete identity/date manifest requirement, so the comparison remains educational context rather than a current performance claim.

That historical snapshot illustrates the verify-batch opportunity on a system-RAM APU; current performance belongs in a dated, protocol-complete checkpoint.

11 Summary — what to remember

12 Primary sources


Source repository: github.com/warpfront/hipfire · Companion: CDNA / MFMA deck · Further reading: /docs/architecture, /docs/benchmarks, /docs/quantization.