|
| 1 | +# Handoff: turbo4_0 multi-warp WMMA prefill-tile corruption on RDNA4 (Qwen3.6-27B, hd256) |
| 2 | + |
| 3 | +**Date:** 2026-07-11 · **Machine:** mad-lab-main, R9700 / gfx1201 (RDNA4), `build-hip` |
| 4 | +**Repo:** master @ `90bdfa9c5` (gate just removed — the bug is now exposed on the ungated mw path) |
| 5 | +**Status:** root-caused to a specific kernel; **not fixed**. This is a handoff for Codex. |
| 6 | + |
| 7 | +--- |
| 8 | + |
| 9 | +## 0. Scope — what this IS and is NOT |
| 10 | + |
| 11 | +**IS:** the **turbo4_0** (128-elem blocks, head_dim **256**) **multi-warp WMMA _prefill_ tile kernel** corrupting attention output on RDNA4/gfx1201. Repro model: **Qwen3.6-27B-Q8_0** (arch `qwen35`, hd256, n_head=24, **n_head_kv=4**, 65 blocks). Output = pure `////` garbage (HTTP 200, no crash). |
| 12 | + |
| 13 | +**IS NOT:** the turbo4_**64** (head_dim-64, LFM2.5) flash-**decode** centroid-table bug. That was a *separate* bug (decode used the N128 table instead of N64) — **already fixed** by Codex in `e591a6747` (`mt_pagedattn_decode.cu:318`). It is head_dim-64-only and does **not** touch this hd256 turbo4_0 path. Do not conflate them. |
| 14 | + |
| 15 | +--- |
| 16 | + |
| 17 | +## 1. Symptom & reliable isolation |
| 18 | + |
| 19 | +Qwen3.6-27B + `--cache-type-k/v turbo4` + `--flash-attn on` + `--kv-tiered 90,10,0 --kv-tier-paged-blocks`, on a prompt long enough that the tile fires (`avg_q_len ≥ 16`) → reasoning content is a solid run of `/`. |
| 20 | + |
| 21 | +**Runtime A/B (env toggles, one binary, no rebuild). The load-bearing signal is the MULTIWARP toggle:** |
| 22 | + |
| 23 | +| Env | Prefill path | Result | |
| 24 | +|---|---|---| |
| 25 | +| default | multi-warp WMMA tile | **GARBAGE** | |
| 26 | +| `GGML_PAGED_TILE_MULTIWARP=0` | **single-warp** WMMA tile | **coherent** | |
| 27 | +| `GGML_PAGED_TILE=0` | scalar prefill | coherent | |
| 28 | +| `GGML_PAGED_DECODE=0` (tile still on) | mw tile prefill | **GARBAGE** (⇒ prefill, not decode, is the corruptor) | |
| 29 | + |
| 30 | +So: **only the multi-warp variant of the prefill tile corrupts.** Single-warp tile, scalar prefill, split-K decode, and the turbo4 scatter are all correct. |
| 31 | + |
| 32 | +> ⚠️ Two confounds to ignore: (a) `--cache-type q8_0` "coherent" is meaningless — q8_0 isn't admitted to the tile gate at all. (b) Some of claude's 2026-07-11 *morning* A/B calls were later shown unreliable (e.g. it wrongly concluded the RDNA4 dispatch gate was a "no-op" — it was actually effective; see §5). Trust the MULTIWARP toggle above, which is consistent across sessions. |
| 33 | +
|
| 34 | +**Discriminators (signature):** breaks only for **turbo4_0 × head_dim 256 × paged × multi-warp**. |
| 35 | +- hd128 (llama/orpheus) turbo4-paged: **coherent**. |
| 36 | +- hd256 **MoE** (ornith, `qwen35moe`) turbo4-paged: **coherent** — *even though it also hits the mw tile.* ornith has **n_head_kv=2**; Qwen3.6-27B has **n_head_kv=4**. This n_head_kv=2-vs-4 difference is **unexplained and is a prime lead** — the cooperative tile likely has a per-(kv_head) grid/smem index that only mis-behaves at n_kv_heads=4. |
| 37 | + |
| 38 | +--- |
| 39 | + |
| 40 | +## 2. It is a REGRESSION (07-06 upstream sync) |
| 41 | + |
| 42 | +Bisected on the R9700, same repro: |
| 43 | +- **GOOD** `66eec7f54` (2026-07-01, pre-sync) → coherent. |
| 44 | +- **BAD** `676b87554` (2026-07-09) → garbage. |
| 45 | +- Only commit in range touching the paged/turbo path = the **07-06 upstream sync merge `06a3da0e6`**. |
| 46 | + |
| 47 | +**But the mw-kernel source barely changed:** |
| 48 | +- The only sync change to `mt_pagedattn_tile.cu` is `9e8310ecb` ("size mw tile Q_TILES to device shared-mem limit"), and **on AMD it is a no-op** — AMD takes the `FULL` config (`Q_TILES=6`, 64 KiB), byte-identical to pre-change; only the NVIDIA `NARROW` variant and a `NARROW_SMEM=false` template arg were added. |
| 49 | +- `mma.cuh` was **not** changed by the sync. |
| 50 | + |
| 51 | +⇒ The trigger is a **changed shared dependency the mw TU `#include`s**, almost certainly **`turbo-quant.cuh` (+142)**: the sync added `TURBO_CENTROIDS_4BIT_N64` + `TURBO4_64_OL*` `__constant__` tables and a `__device__ int g_turbo4_64_ol_use_n64_table`. **Leading theory: those added `__constant__`/`__device__` symbols shifted the mw kernel's register/occupancy/codegen and tipped a _latent_ cooperative-kernel bug that was accidentally-correct before.** (turbo4_0's own `TURBO_CENTROIDS_4BIT` N128 table is unchanged, and `coop_stage_turbo4_tile` uses it correctly — so this is NOT a centroid-table mismatch like the turbo4_64 decode bug was.) |
| 52 | + |
| 53 | +--- |
| 54 | + |
| 55 | +## 3. Static analysis (2 passes: Codex + claude) — no readable logic defect |
| 56 | + |
| 57 | +`coop_stage_turbo4_tile` (`mt_pagedattn_tile.cu:215-278`, the mw-only turbo4_0 staging, the sole source-level delta vs the correct single-warp path) reads **correct**: |
| 58 | +- warp→qblock round-robin `qb=warp_id; qb+=N_WARPS` over 32 qblocks / 6 warps covers every qblock exactly once (no gap/overlap). |
| 59 | +- `blk` depends on `qb` (not `lane_id`), so all lanes in a warp agree; lane-0 norm broadcast via `__shfl_sync(...,0,WARP_SIZE)` is consistent. |
| 60 | +- `blk->qs + 2*lane_id` for lanes 0-31 covers `qs[0..63]` exactly (in-bounds); smem write offsets in range. |
| 61 | +- **RDNA4 is wave32**, so the wave64 shuffle-width hazard class (the old gfx803 bug) does **not** apply here. |
| 62 | +- `__syncthreads()` present after each cooperative stage (Q/K/V) in both kernel bodies. |
| 63 | + |
| 64 | +Kernel bodies: WMMA path `#if defined(AMD_WMMA_AVAILABLE)` (~817-1147) and FMA `#else` (~1148-1394); both call `coop_stage_turbo4_tile`. No incorrect index / missing barrier / OOB / race was found by reading. ⇒ most likely a **compiler/resource-sensitivity**, not a fixable-by-reading line. |
| 65 | + |
| 66 | +**Strongest (unproven) suspect — register/occupancy pressure in the HS=256 mw specialization:** `__launch_bounds__((NARROW?..:6)*32, 2)` = **(192, 2)** forces ≥2 blocks/SM → tight VGPR cap → possible spill/miscompile on RDNA4. Live state is unusually heavy: 16 Q-frags + 16 output-frags + ping-pong K-frags + 2 score accumulators + 6 cooperative warps doing divergent constant-table gathers. |
| 67 | + |
| 68 | +--- |
| 69 | + |
| 70 | +## 4. What claude already tried |
| 71 | + |
| 72 | +- **`__launch_bounds__` relaxation** (dropped the `,2` min-blocks → let the compiler use more registers / less spill): **BUILT but NEVER TESTED** (interrupted, then reverted). **This is the #1 untested lead — Codex should test it first.** Line `mt_pagedattn_tile.cu:803-804`. |
| 73 | +- **RDNA4→single-warp dispatch gate** (`mw_on = ... && !(IS_RDNA4(cc) && HS==256)`): worked (correct bypass, single-warp is still WMMA) but is a bypass, not a fix — **removed** in `90bdfa9c5` per kmbandy, who wants the real kernel fix. |
| 74 | +- Differential smem dump: planned, not executed. |
| 75 | + |
| 76 | +--- |
| 77 | + |
| 78 | +## 5. Corrected fact (don't repeat claude's error) |
| 79 | + |
| 80 | +gfx1201 cc parses (via `ggml_cuda_parse_id`, `ggml-cuda.cu:156`) to `GGML_CUDA_CC_OFFSET_AMD + 0x1201`, so `GGML_CUDA_CC_IS_RDNA4(cc)` is **true**. The dispatch gate was therefore **effective** (claude's morning "no-op" claim was wrong). Relevant only because it explains why the R9700 was coherent while the gate was in — the mw bug was bypassed, not gone. |
| 81 | + |
| 82 | +--- |
| 83 | + |
| 84 | +## 6. Suggested experiments (in priority order) |
| 85 | + |
| 86 | +Brick note: gfx1201 drives the desktop. The bug is **wrong-result, not a fault**, so *running* the mw kernel is safe; a bad kernel *edit* could fault → use crash-survivable iteration, and prefer iterating on a non-display card if one is available. |
| 87 | + |
| 88 | +1. **Test the launch_bounds relaxation** (`(192,2)` → drop the `,2`, or try `(192,1)`), rebuild, repro. If mw goes coherent → it's register/occupancy pressure; the fix is a proper launch-bounds / register-budget adjustment. |
| 89 | +2. **Confirm the second-order-compile mechanism:** on GOOD commit `66eec7f54`, add ONLY the sync's new `__constant__` tables + `__device__` global to `turbo-quant.cuh` (no call sites), rebuild, repro. Garbage ⇒ mechanism confirmed (codegen shift tips a latent bug) → then hunt the latent bug with instrumentation. |
| 90 | +3. **Differential dump** (env-gated): dump `smem_k`/`smem_v` after staging, then QK scores, softmax max/sum, and PV output for block(0,0,0) in BOTH the mw and single-warp kernels on a fixed tiny input; first divergence localizes it (staging vs QK vs softmax vs PV). |
| 91 | +4. **Chase the n_head_kv=4 discriminator:** instrument the cooperative tile's per-(kv_head) grid/smem indexing; ornith (n_kv=2) clean vs Qwen (n_kv=4) garbage should point at the faulty index. |
| 92 | +5. **Compiler resource report (RGA / `--save-temps`)** for `mt_paged_attention_tile_mw_kernel<256,16,TURBO4_0,false>` on gfx1201 at GOOD vs BAD (or ±the turbo-quant.cuh constants); a VGPR/spill/occupancy delta confirms §3's theory. |
| 93 | + |
| 94 | +--- |
| 95 | + |
| 96 | +## 7. Key references |
| 97 | + |
| 98 | +- **mw kernel:** `ggml/src/ggml-cuda/mt_pagedattn_tile.cu` — `mt_paged_attention_tile_mw_kernel` ~805; `__launch_bounds__` ~803-804; `coop_stage_turbo4_tile` 215-278; WMMA body ~817-1147; FMA `#else` ~1148-1394. |
| 99 | +- **dispatch:** `ggml/src/ggml-cuda/mt_pagedattn.cu` — `mw_on` select ~1720 (gate removed); tile gate (`HS 128/256 & F16/TURBO4_0/TURBO3_0 & avg_q_len≥16`) ~1685; env toggles + `MAD_PAGEDATTN_PROBE` ~55-90 / ~1700. |
| 100 | +- **suspected trigger:** `ggml/src/ggml-cuda/turbo-quant.cuh` — added `TURBO_CENTROIDS_4BIT_N64` etc. (~L352-500). turbo4_0's `TURBO_CENTROIDS_4BIT` (N128) unchanged. |
| 101 | +- **cc parse:** `ggml/src/ggml-cuda/ggml-cuda.cu:156` `ggml_cuda_parse_id`. |
| 102 | +- **older/companion writeup:** `docs/dev/2026-07-10-turbo4-paged-mw-wmma-rdna4-corruption.md` (has the full A/B matrix + bisect detail; note its §4.2 gate discussion is superseded by §5 above). |
| 103 | + |
| 104 | +## 8. Repro command |
| 105 | + |
| 106 | +``` |
| 107 | +<ENV> ./build-hip/bin/llama-server \ |
| 108 | + --model ~/models/Qwen3.6-27B-Q8_0.gguf --n-gpu-layers 999 --ctx-size 131072 \ |
| 109 | + --parallel 1 --cache-type-k turbo4 --cache-type-v turbo4 --flash-attn on \ |
| 110 | + --kv-tiered 90,10,0 --kv-tier-ssd-path <dir> --kv-tier-paged-blocks \ |
| 111 | + --no-mmap --no-warmup --jinja --host 127.0.0.1 --port 8091 --device ROCm0 |
| 112 | +``` |
| 113 | +Send a demanding, multi-paragraph reasoning prompt (short prompts take the scalar path and hide the bug). Env toggles: `GGML_PAGED_TILE_MULTIWARP`, `GGML_PAGED_TILE`, `GGML_PAGED_DECODE`, `MAD_PAGEDATTN_PROBE=verbose` (logs `[probe-tile] ... wmma=1` when the tile fires). The live R9700 service is `llama-server-qwen36-27b-swatm-r9700` (:8090); its binary is the old *gated* build (still coherent) until `build-hip` is rebuilt from `90bdfa9c5`. |
| 114 | +``` |
| 115 | +
|
| 116 | +--- |
| 117 | +
|
| 118 | +## 9. Codex update (2026-07-11, before handing back to Opus) |
| 119 | +
|
| 120 | +Codex ran the investigation directly on the R9700. The systemd service on |
| 121 | +`:8090` and the manual test server on `:8091` are both stopped at handoff. |
| 122 | +The checkout is intentionally dirty with diagnostic edits described below. |
| 123 | +Do not treat the current source as a proposed fix. |
| 124 | +
|
| 125 | +### 9.1 Compiler metadata findings |
| 126 | +
|
| 127 | +The gfx1201 code object for the failing specialization was extracted from |
| 128 | +`build-hip/bin/libggml-hip.so.0.15.3` and inspected with `llvm-readelf`: |
| 129 | +
|
| 130 | +| Specialization | VGPR | VGPR spills | private scratch/thread | |
| 131 | +|---|---:|---:|---:| |
| 132 | +| HS256 TURBO4_0 FULL (6 warps) | 256 | 21 | 88 B | |
| 133 | +| HS256 TURBO4_0 NARROW (3 warps) | 248 | 0 | 0 B | |
| 134 | +| HS256 F16 FULL | 247 | 0 | 0 B | |
| 135 | +
|
| 136 | +This initially looked like a strong lead, but two follow-up compilations |
| 137 | +disproved it as the regression mechanism: |
| 138 | +
|
| 139 | +1. Removing only the second `__launch_bounds__` argument (`..., 2`) produced |
| 140 | + exactly the same 256 VGPR / 21 spill / 88 B metadata. |
| 141 | +2. Recompiling known-good commit `66eec7f54` with the current compiler also |
| 142 | + produced exactly 256 / 21 / 88 for HS256 TURBO4_0, with the same kernel |
| 143 | + code size (`0x3278`). |
| 144 | +
|
| 145 | +Therefore the spills are real but are not new in the bad source, and the |
| 146 | +new turbo4_64 centroid constants did not shift this kernel's resource |
| 147 | +allocation under the current compiler. The original incremental bisect |
| 148 | +should eventually be revalidated with clean build directories in case a |
| 149 | +stale HIP object affected it. |
| 150 | +
|
| 151 | +### 9.2 Runtime experiment matrix |
| 152 | +
|
| 153 | +All requests used the same 153-token planning prompt, temperature 0, and |
| 154 | +`MAD_PAGEDATTN_PROBE=verbose`. Every bad run logged 16 calls like: |
| 155 | +
|
| 156 | +``` |
| 157 | +[probe-tile] avg_q_len=149 max_ctx=131072 wmma=1 total_q=149 |
| 158 | +``` |
| 159 | +
|
| 160 | +The failure was deterministic: the response contained only `/` in |
| 161 | +`reasoning_content` (128 or 256 tokens). |
| 162 | +
|
| 163 | +| Experiment | Result | Conclusion | |
| 164 | +|---|---|---| |
| 165 | +| Force existing NARROW kernel on RDNA4 HS256 TURBO4_0 (3 warps, 40 KiB LDS, zero spills) | GARBAGE | Not caused by 64 KiB LDS or reported spills | |
| 166 | +| Replace `coop_stage_turbo4_tile` for both K and V with generic per-element `ops::k_load` / `ops::v_load` staging | GARBAGE | Not the cooperative turbo4 staging, centroid gather, or its n_kv-head indexing | |
| 167 | +| Replace multi-warp N_ACC=2 QK accumulation and K/V ping-pong prefetch with the coherent single-warp kernel's direct load-then-WMMA loops | GARBAGE | Not the N_ACC or ping-pong optimization | |
| 168 | +| Set HS256 NARROW `Q_TILES_PER_BLOCK=1` while still using the multi-warp kernel body | GARBAGE | Does not require multiple warps or inter-warp interference | |
| 169 | +| Carry FP32 scores across the V-load barrier and pack to half2 after `__syncthreads()`, matching the single-warp kernel | GARBAGE | Not the packed `scores_h` lifetime across the barrier | |
| 170 | +| Remove the complete `__launch_bounds__` attribute | GARBAGE | Not the launch-bounds attribute | |
| 171 | +
|
| 172 | +These results substantially narrow the bug: even with one warp, generic K/V |
| 173 | +staging, direct WMMA loops, post-barrier packing, and no launch bounds, the |
| 174 | +multi-warp kernel body still corrupts while dispatching to the separate |
| 175 | +single-warp kernel remains coherent. |
| 176 | +
|
| 177 | +### 9.3 Current untested build and dirty source state |
| 178 | +
|
| 179 | +The final diagnostic change added the extra 1 KiB FP32 score scratch that |
| 180 | +the coherent single-warp launcher allocates unconditionally: |
| 181 | +
|
| 182 | +``` |
| 183 | +smem_bytes = mw_tile_smem_bytes(...) + 16 * 16 * sizeof(float) |
| 184 | +``` |
| 185 | +
|
| 186 | +This build completed successfully but was **not runtime-tested** before the |
| 187 | +handoff. Test this first. It is the last obvious structural difference in |
| 188 | +the reduced one-warp experiment. |
| 189 | +
|
| 190 | +Current diagnostic edits in `mt_pagedattn_tile.cu` are cumulative: |
| 191 | +
|
| 192 | +- RDNA4 + HS256 + TURBO4_0 forces `NARROW_SMEM=true`. |
| 193 | +- HS256 narrow Q tiles changed from 3 to 1. |
| 194 | +- TURBO4_0 uses generic per-element K/V staging in the multi-warp body. |
| 195 | +- N_ACC=2 and K/V ping-pong prefetch were replaced by direct WMMA loops. |
| 196 | +- FP32 scores, rather than packed half2 scores, cross the V-load barrier. |
| 197 | +- The multi-warp kernel has no `__launch_bounds__` attribute. |
| 198 | +- The launcher adds 1 KiB unused dynamic-smem padding (built, untested). |
| 199 | +
|
| 200 | +Because the edits are cumulative diagnostics, revert them selectively or |
| 201 | +restore the file from `90bdfa9c5` before constructing a final fix. |
| 202 | +
|
| 203 | +### 9.4 Recommended next steps |
| 204 | +
|
| 205 | +1. Runtime-test the already-built 1 KiB smem-padding candidate first. |
| 206 | +2. If it still fails, stop source-toggle experiments and add a differential |
| 207 | + numeric dump for block `(head=0, seq=0, q-group=0)` in both kernel entry |
| 208 | + points. Capture, in order: staged Q, staged K/V, first QK accumulator, |
| 209 | + masked/scaled scores, softmax max/sum, first PV accumulator, final output. |
| 210 | +3. Because the reduced multi-warp body is now one warp and nearly mirrors the |
| 211 | + coherent kernel, the first differing checkpoint should expose either a |
| 212 | + launch/grid/shared-memory-layout difference or a compiler codegen issue. |
| 213 | +4. Re-run GOOD/BAD with fresh build directories before relying further on |
| 214 | + the July 6 source bisect. |
| 215 | +
|
| 216 | +--- |
| 217 | +
|
| 218 | +## 10. Grok resolution (2026-07-11 evening) |
| 219 | +
|
| 220 | +### 10.1 Root-cause reframe (supersedes §0–§3 “mw-only” framing) |
| 221 | +
|
| 222 | +Codex’s reductions were valuable but aimed at the wrong layer. Runtime |
| 223 | +isolation on clean `90bdfa9c5` shows: |
| 224 | +
|
| 225 | +| Path | Simple math (`17+25`, ~20 tok) | Plan-style prompt (~38–105 tok) | |
| 226 | +|---|---|---| |
| 227 | +| Scalar prefill (`GGML_PAGED_TILE=0`) | coherent | coherent | |
| 228 | +| **WMMA** single-warp tile | often coherent | **pure `/` garbage** | |
| 229 | +| **WMMA** multi-warp tile | **usually garbage** | **pure `/` garbage** | |
| 230 | +| **FMA** single-warp tile | coherent | coherent | |
| 231 | +| **FMA** multi-warp tile | coherent | coherent | |
| 232 | +
|
| 233 | +So: |
| 234 | +
|
| 235 | +1. **Not multi-warp-specific.** Cooperative staging, N_ACC, launch_bounds, |
| 236 | + smem padding, and Q_TILES are all red herrings once WMMA is removed. |
| 237 | +2. **WMMA tile path is numerically wrong on RDNA4.** Multi-warp looked like |
| 238 | + “the” bug because it fails even easy prompts; single-warp *masks* the |
| 239 | + bug on easy prompts (template math) and fails on demanding ones. |
| 240 | +3. Prior “MULTIWARP=0 coherent” A/Bs were confounded by using easy math |
| 241 | + canaries (or short prompts). The handoff’s own plan prompt **fails on |
| 242 | + single-warp WMMA** too. |
| 243 | +4. FMA tile multi-warp working proves the cooperative algorithm, GQA |
| 244 | + indexing, turbo4 staging, and softmax/mask logic are fine. |
| 245 | +
|
| 246 | +### 10.2 1 KiB smem-padding candidate |
| 247 | +
|
| 248 | +Tested (Codex’s untested build): still garbage under WMMA. Closed. |
| 249 | +
|
| 250 | +### 10.3 Layout probe result (WMMA matmul is fine) |
| 251 | +
|
| 252 | +Standalone HIP probes on gfx1201 (`/tmp/rdna4_wmma_{tile,pv,ninner}_probe.hip`): |
| 253 | +
|
| 254 | +- QK `mma(D,K,Q)` + `get_i`/`get_j` matches FMA host ref (max |diff| ~1e-4) |
| 255 | +- PV `load_ldmatrix_trans` + `mma(D,V,scores_h)` matches FMA host ref (exact) |
| 256 | +- HS=256 multi-chunk `N_INNER=16` preload-all-Q matches FMA host ref |
| 257 | +
|
| 258 | +**Operand order / C-lane map are not the bug.** |
| 259 | +
|
| 260 | +### 10.4 Actual root cause + fix |
| 261 | +
|
| 262 | +`mt_pagedattn_tile.cu` is built with **`-ffast-math`** (`-ffinite-math-only`). |
| 263 | +Under that, IEEE **`-INFINITY` is UB**. The compiler may delete inf |
| 264 | +materialization and `x == -INFINITY` tests → causal mask / softmax breaks |
| 265 | +in the WMMA specialization (FMA path was less sensitive / “accidentally |
| 266 | +OK” on easy prompts). |
| 267 | +
|
| 268 | +**Fix (working tree):** replace all `-INFINITY` in this TU with a finite |
| 269 | +sentinel: |
| 270 | +
|
| 271 | +```cpp |
| 272 | +static constexpr float SOFTMAX_MASK_VAL = -1.0e30f; |
| 273 | +``` |
| 274 | + |
| 275 | +Used for mask fill, `running_max` init, comparisons, and masked-exp zeroing. |
| 276 | +No FMA force, no multiwarp gate. WMMA multi-warp stays on. |
| 277 | + |
| 278 | +### 10.5 Verified (R9700, default multiwarp ON, turbo4+paged) |
| 279 | + |
| 280 | +| Check | Result | |
| 281 | +|---|---| |
| 282 | +| math | coherent | |
| 283 | +| full plan handoff prompt | coherent (real plan text, not `/`) | |
| 284 | +| PP ~200 tok | ~539 t/s | |
| 285 | +| PP ~800 tok | ~895 t/s | |
| 286 | +| PP ~2k tok | ~988 t/s | |
| 287 | +| PP ~4k tok | **~1015 t/s** (matches pre-fix WMMA ~1048; FMA was ~667) | |
| 288 | + |
| 289 | +Perf hit ≈ **0** vs broken WMMA baseline; **not** the FMA crutch. |
| 290 | + |
| 291 | +### 10.6 Follow-ups |
| 292 | + |
| 293 | +- Same `-INFINITY` pattern still exists in `mt_pagedattn_decode.cu` and |
| 294 | + scalar `mt_pagedattn.cu` — latent under `-ffast-math`. Apply the same |
| 295 | + sentinel when those paths misbehave. |
| 296 | +- Prefer a TU-wide ban on IEEE inf in HIP sources built with fast-math, |
| 297 | + or drop `-ffinite-math-only` for attention TUs. |
0 commit comments