Skip to content

cuda: wave64 (CDNA2 / gfx90a) correctness for turbo KV-cache + ConvRot - #328

Merged
TheTom merged 4 commits into
TheTom:feature/turboquant-kv-cachefrom
jasstrong:fix/wave64-turbo-kernels
Aug 31, 2026
Merged

cuda: wave64 (CDNA2 / gfx90a) correctness for turbo KV-cache + ConvRot#328
TheTom merged 4 commits into
TheTom:feature/turboquant-kv-cachefrom
jasstrong:fix/wave64-turbo-kernels

Conversation

@jasstrong

Copy link
Copy Markdown

Makes the turbo KV-cache and ConvRot CR kernels correct on wave64 (CDNA / gfx90a / MI210), which previously produced garbage there. Validated on an MI210 — this brings CDNA2 up as a working turbo target.

Root cause

The turbo kernels compute a logical lane = j % WARP_SIZE (WARP_SIZE is the 32-lane tile width, not the physical wave) then call __shfl_*_sync with the width argument omitted, so on wave64 the shuffle spans the full 64-lane physical wave and reaches across the 32-lane logical-warp boundary. Fix = the explicit-width idiom already used correctly in these files (convrot.cu, fwht.cu, fattn-vec.cuh:401): pass WARP_SIZE as the shuffle width so each 32-lane subsection stays self-contained.

Commits

  1. ConvRot CR mat-vec (mmv-cr.cu) — the convrot_inverse butterflies + the cooperative-kernel reduction. Fixes the q6_cr/q8_cr MUL_MAT test-backend-ops failures on gfx90a (1694 → 1696/1697; the remaining CR fail is a pre-existing flaky on tiny random shapes, unrelated to wave size).
  2. Turbo KV write (set-rows.cu) — the qs/nibble gathers in k_set_rows_turbo2/3/4 (+tails). The __ballot_sync sign-packs are converted to the same width-32 shuffle-gather the qs packing uses (__ballot_sync returns a 64-bit mask on wave64 and was being truncated into a uint32_t).
  3. Turbo KV read (fattn-vec.cuh) — the softmax-weight broadcast to the V-accumulation lanes (turbo-V only; non-turbo V spills KQ to shared memory). This was the root cause of turbo-V garbage.
  4. Hardening — explicit WARP_SIZE width on the intra-warp XOR reductions (set-rows norm/recon + the turbo-wht butterfly). Correct today because their offsets are always < 32, so no functional change; this removes the latent wave64 fragility.

Validation (MI210 / gfx90a)

  • test-backend-ops MUL_MAT: q6_cr/q8_cr wave64 failures fixed.
  • Turbo KV coherence (1253-token needle-in-haystack, cache_type_k=turbo3 / cache_type_v=turbo4): output went from total garbage to coherent with the needle correctly recalled. Isolation (f16-K/turbo4-V vs turbo3-K/f16-V) confirmed the K and V turbo paths are each independently correct after the fixes.

jas added 4 commits August 29, 2026 21:54
The convrot_inverse butterflies and the cooperative-kernel final reduction
in mmv-cr.cu omit the width argument to __shfl_sync / __shfl_down_sync. On
wave64 (CDNA / gfx90a) the physical wave is 64 lanes, so the two logical
32-lane groups (lane = p & 31, block dim 64) are no longer separate warps:
the upper group reads shuffle sources from the lower 32 physical lanes,
corrupting the radix-4 inverse rotation, and the reduction inside `if (p < 32)`
pulls stale row_sum from lanes that never entered the branch. Pass explicit
WARP_SIZE (32) width so each 32-lane subsection is self-contained, matching
the convrot.cu / fwht.cu convention. Fixes the q6_cr/q8_cr MUL_MAT failures
in test-backend-ops on gfx90a.
k_set_rows_turbo2/3/4 pack the turbo KV cache with warp-cooperative shuffles
and __ballot_sync that assume warpSize == 32. On wave64 (CDNA / gfx90a):
  - the qs / nibble gathers use absolute srcLanes from lane = j % 32 but omit
    the shuffle width, so threads in the upper 32 physical lanes read from the
    lower 32 -> corrupt qs / nibble bytes;
  - __ballot_sync returns a 64-bit mask that truncates into uint32_t and the
    per-8-lane byte extraction only sees the low 32 lanes -> corrupt sign plane.
Pass explicit WARP_SIZE (32) width on the gathers, and replace the sign ballot
with the same width-32 shuffle-gather the qs packing uses (wave-size agnostic,
no 64-bit-ballot truncation). The intra-warp xor reductions are left as-is:
their offsets are <= 16 so each butterfly stays inside its 32-lane subgroup.
Previously the turbo KV cache was populated with garbage on the MI210.
flash_attn_ext_vec broadcasts each K-position's softmax weight KQ_reg to the
V-accumulation lanes with a width-omitted __shfl_sync, but only on the turbo-V
path (non-turbo V spills KQ to shared memory). On wave64 (CDNA / gfx90a) the
kernel packs two 32-lane logical warps into one 64-lane physical wave
(nthreads=128, nwarps=4), so lanes in the upper logical warp read the softmax
weight from the WRONG warp's K-positions -> half of every V accumulation is
multiplied by the wrong weights -> incoherent output. This is why only turbo
KV V-cache (turbo2/3/4) produced garbage on the MI210. Add the explicit
WARP_SIZE width to both broadcast sites (V_DOT2 and float paths), matching the
KQ_max reduction two lines up. Read-side dequant and the write path were
already wave64-correct.
Follow-up to the wave64 correctness fixes. The intra-warp XOR reductions in the
turbo set-rows norm/recon kernels and the turbo-wht butterfly omit the shuffle
width. They are correct on wave64 today only because their offsets are always
< 32 (each aligned 32-lane group self-reduces regardless of physical wave
width), but that is latent fragility: a future offset >= 32, or a stricter mask
interpretation, would silently break them. Add the explicit WARP_SIZE width so
the 32-lane grouping is intent, not accident. No functional change on any
current arch; removes the wave64 footgun ahead of UDNA.
@TheTom

TheTom commented Aug 31, 2026

Copy link
Copy Markdown
Owner

Thanks, @jasstrong. I reviewed the shuffle changes and the wave64 failure mode checks out. The explicit width keeps every operation inside the logical 32-lane tile, and replacing the truncated wave64 ballot with an 8-lane shuffle gather is the right fix for the sign bytes. The separate K/V isolation and real MI210 needle test give this useful hardware coverage beyond the backend cases. Merging this.

@TheTom
TheTom merged commit ef68c3c into TheTom:feature/turboquant-kv-cache Aug 31, 2026
4 of 21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants