Skip to content

RDNA4 phase 3: scalar FlashAttention, FP8/int8 KV, decode and softmax on HIP - #396

Open
morousg wants to merge 7 commits into
mainfrom
rdna4/phase-3-hip-attention
Open

morousg wants to merge 7 commits into
mainfrom
rdna4/phase-3-hip-attention

Conversation

@morousg

@morousg morousg commented Oct 18, 2026

Copy link
Copy Markdown
Member

Phase 3 of the RDNA4 port: the scalar attention DPPs and the FP8 KV cache run on HIP (RX 9060, gfx1200).

Stacked on #394 and #395: this branch contains their commits. Review those first; once they merge, this diff shrinks to the last commit (7c78320).

Changes

  • Backends: softmax.h, flash_attention.h and flash_decode.h now build their DPPs under __NVCC__ || __HIPCC__. HIP launches use getHIPStream()/hipGetLastError(), the CUDA launch lines are unchanged in #else, and HIP kernels call attnRequireWave32(). flash_attention_mma.h stays CUDA-only and is still harmless to include on HIP.
  • Wave size: the 2 shuffle sites in these files now use attnShflXor, and the 32s that mean "lanes per wave" use ATTN_WAVE_SIZE. Every remaining 32 is a tile size (BLOCK_N) and is untouched. The 5 shuffle sites in the MMA file are unchanged.
  • FP8 (OCP e4m3 on both backends): HIP gets its own branch first (__HIPCC__ && __has_include(<hip/hip_fp8.h>) → __hip_fp8_e4m3, not the FNUZ __hip_fp8_e4m3_fnuz of gfx942); the CUDA condition is still exactly __has_include(<cuda_fp8.h>). The type is fk::attn_fp8_e4m3, decoded by a single attnFp8ToF32() used by Fp8TokenDequantRead, flash decode and the tests. attnFp8IsOcpE4M3() checks known bytes (0x7E = 448, 0x38 = 1, 0x80 = -0). On this ROCm (10.1), device and host decoding and encoding give OCP values, and encoding saturates at 448 like CUDA's satfinite.
  • bf16 and IOp traits: Bf16AttentionRead, its makeAttentionRead overload and the isRawBf16Read/isFp8KVRead/isInt8KVRead traits moved from flash_attention_mma.h to flash_attention.h, so decode doesn't need the CUDA-only header. They have the same names and code, with bf16 spelled attn_bf16/attn_bf162 (__nv_* under nvcc). flash_decode.h still includes the MMA header.
  • Tests: utest_flash_attention, utest_softmax, utest_flash_decode and utest_attention_wave now use Ptr instead of raw cudaMalloc/cudaMemcpy[2D], and are marked with the new __ONLY_GPU__.
    • cmake/tests/discover_tests.cmake: an ONLY_GPU substring suppresses only the _cpp target. No existing file contains that substring.
    • AGENTS.md documents the marker and the HIP status of attention/.
    • utest_flash_attention adds an fp8 format check (testFp8Format).
    • Test cases and tolerances are unchanged; HIP uses the same tolerances as CUDA.

CUDA-visible changes (same types and behavior; all compiled with nvcc 13.4)

They don't change behavior, but they aren't token-identical:

  1. The entry points' stream parameter is spelled Stream_<defaultParArch>&, which is Stream_<ParArch::GPU_NVIDIA>& under nvcc.
  2. low_precision::attnToF32/attnFromF32 are now FK_HOST_DEVICE_CNST instead of FK_DEVICE_CNST. The host packing helpers (quantizeKVCacheHost, quantizeKVCacheFp8Host) already called them from host code. nvcc accepts that for a constexpr __device__ function, but HIP's clang doesn't.
  3. Fp8TokenDequantRead::exec decodes through attnFp8ToF32, which sets __x and converts. The old device branch reinterpreted the byte as __nv_fp8_e4m3; the old host branch already used the new code.
  4. The wave-size spellings (% ATTN_WAVE_SIZE instead of & 31, and so on) and attnShflXor, which is __shfl_xor_sync(0xffffffffu, …) on CUDA.
  5. The moved bf16 read and traits (see above).

Verified on this machine (RX 9060, gfx1200, ROCm 10.1)

  • Full HIP suite with default arches (gfx1200;gfx1201): 51/51, the 47 on main plus utest_attention_wave_hip, utest_softmax_hip, utest_flash_attention_hip and utest_flash_decode_hip. Same unique warnings as main.

  • Measured max errors on HIP:

    Test Max error Tolerance
    Scalar FA (dense, int8 KV, fp8 KV, prologue/epilogue) 6e-8 to 7e-7 5e-6 / 1e-6
    Softmax ≤ 8e-8 1e-6
    Decode, every case ≤ 5e-8 1e-5

    Decode covers fp32, bf16 (including d32/d256), int8 and fp8, with empty splits and repeated calls.

  • Mutation checks: making the shuffle reduction stop one step early fails every dense case (max error 0.05 to 0.28). Decoding fp8 at twice the value fails the format check. The KV cases can't catch that one, because the oracle decodes with the same function, which is why the format check exists.

  • nvcc 13.4 + g++-13, sm_86 (PyPI wheels, no GPU): full build with 0 failures and 0 warnings; the 49 _cpp tests pass, and the _cpp list is the same as RDNA4 phase 2: CUDA tests for FP8 KV, flash decode and the mma kernel (CUDA-side, unverified locally) #395's.

Needs CUDA CI

Running utest_flash_attention_cu, utest_softmax_cu, utest_flash_decode_cu, utest_flash_attention_mma_cu and utest_attention_wave_cu on the A2000, after the changes above.

🤖 Generated with Claude Code

https://claude.ai/code/session_014d2rht2WMSopuodhtXYZzx

albertandaluz and others added 5 commits October 18, 2026 12:30
HIP_ARCH was never used: enable_language(HIP) sets CMAKE_HIP_ARCHITECTURES
to the GPU of the build machine before archs.cmake reads it, and hip::device
compiled the C++ sources for GPU_TARGETS, also detected. HIP_ARCH now seeds
CMAKE_HIP_ARCHITECTURES (an explicit value still wins), GPU_TARGETS follows
it, and the default is gfx1200;gfx1201 (Navi 44 and Navi 48 can't load each
other's code objects).

attention_wave.h adds ATTN_WAVE_SIZE, attnShflXor and attnRequireWave32 for
porting the attention DPPs to HIP. ROCm's clang no longer exposes the wave
size at compile time, so a wave64 build traps at run time instead of failing
the build, unless the compiler still defines __AMDGCN_WAVEFRONT_SIZE__.
Nothing uses them yet; utest_attention_wave tests them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014d2rht2WMSopuodhtXYZzx
utest_flash_attention gets testFp8KV (fp8 e4m3 per-token K/V through
makeFp8KVRead, oracle on the dequantized values, like testInt8KV). New
utest_flash_decode covers executeFlashDecode for every loadRow path (fp32,
raw bf16, int8 and fp8 caches, auto and forced splits with empty ones, two
calls on one workspace) and new utest_flash_attention_mma covers
executeFlashAttentionMma with raw bf16 Q/K/V, including the split-KV
forward. The oracle and the Ptr1D buffer helpers move to
attention_test_common.h.

Writing them found three bugs in flash_decode.h, fixed here:
- readElem called attnToF32 without low_precision::, so executeFlashDecode
  didn't compile with any generic prologue (fp32 reads, fused chains);
- the packed int8/fp8 loads only handle 2 or 4 bytes per lane: HEAD_DIM 32
  made a misaligned 4-byte load per lane, 256 shifted a 32-bit value by 32+;
- the bf16 pair loads need an even count per lane: HEAD_DIM 32 loaded
  misaligned pairs and wrote past out[].
Those widths now fall back to the per-element read; 64 and 128 are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014d2rht2WMSopuodhtXYZzx
softmax.h, flash_attention.h and flash_decode.h now build their DPPs for
HIP too (flash_attention_mma.h stays CUDA-only). The entry points take
Stream_<defaultParArch>& (the same type as before under nvcc) and launch on
getHIPStream() under HIP. Wave-size 32s and shuffles go through
attention_wave.h; tile sizes are unchanged.

FP8 is OCP e4m3 on both backends: attn_fp8_e4m3 is __hip_fp8_e4m3 on HIP
(not the FNUZ type of gfx942) and __nv_fp8_e4m3 on CUDA, decoded by one
attnFp8ToF32 used by Fp8TokenDequantRead and flash decode.
attnFp8IsOcpE4M3() checks the format on known bytes. bf16 is attn_bf16.
Bf16AttentionRead and the isRawBf16Read/isFp8KVRead/isInt8KVRead traits
move from flash_attention_mma.h to flash_attention.h, so decode doesn't need
the CUDA-only header. low_precision::attnToF32/attnFromF32 become host and
device: the host KV packing helpers call them, which clang rejects for a
__device__ function.

The attention tests use the Ptr helpers instead of raw cudaMalloc and the
new __ONLY_GPU__ marker (cmake/tests/discover_tests.cmake: no _cpp target,
_cu and _hip ones), so they also run as _hip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014d2rht2WMSopuodhtXYZzx
Copilot AI balanced review requested due to automatic review settings October 18, 2026 15:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The MMA test can break older CUDA architecture builds, and numerical and workspace-reuse checks can falsely pass.

3 open issues
What changed in this PR

Ports scalar attention kernels to HIP/RDNA4 while preserving CUDA support, including the stacked architecture and test changes from #394 and #395.

Changes:

  • Adds HIP attention launches, wave32 helpers, and shared low-precision types.
  • Expands attention tests and replaces CUDA-specific buffer handling.
  • Updates HIP architecture selection, GPU-only test discovery, and documentation.
File Description
utests/​algorithm/​attention/​utest_softmax.h Uses portable buffers and enables HIP testing.
utests/​algorithm/​attention/​utest_flash_decode.h Adds decode format and workspace-reuse tests.
utests/​algorithm/​attention/​utest_flash_attention.h Adds FP8 coverage and portable buffers.
utests/​algorithm/​attention/​utest_flash_attention_mma.h Adds CUDA bf16 MMA tests.
utests/​algorithm/​attention/​utest_attention_wave.h Tests wave reductions and exchanges.
utests/​algorithm/​attention/​attention_test_common.h Shares reference calculations and buffer helpers.
include/​fused_kernel/​algorithms/​attention/​softmax.h Adds HIP launches and host-callable conversions.
include/​fused_kernel/​algorithms/​attention/​flash_decode.h Enables HIP and fixes unsupported packed-load widths.
include/​fused_kernel/​algorithms/​attention/​flash_attention.h Adds HIP support and shared FP8/bf16 types.
include/​fused_kernel/​algorithms/​attention/​flash_attention_mma.h Moves shared reads and traits out.
include/​fused_kernel/​algorithms/​attention/​attention_wave.h Adds wave32 helpers and enforcement.
cmake/​tests/​discover_tests.cmake Supports GPU-only test markers.
cmake/​libs/​hip/​hip.cmake Aligns HIP package architecture targets.
cmake/​libs/​hip/​hip_init.cmake Seeds architectures before HIP initialization.
cmake/​libs/​hip/​archs.cmake Applies the selected HIP architectures.
AGENTS.md Documents HIP attention and test configuration.

Review effort: Balanced

Comment thread utests/algorithm/attention/utest_flash_attention_mma.h
inline double maxAbsErr(const std::vector<T>& got, const std::vector<double>& ref) {
double maxErr = 0.0;
for (size_t i = 0; i < ref.size(); ++i) {
maxErr = std::max(maxErr, std::abs(static_cast<double>(got[i]) - ref[i]));
Comment on lines +50 to +51
executeFlashDecode<HEAD_DIM>(q, k, v, o.ptr().data, workspace.ptr().data, bh, seqK, stream,
-1.f, splitsOverride);
albertandaluz and others added 2 commits October 18, 2026 19:01
…rst configure

Follows 1cf881b: the comment in hip_init.cmake and AGENTS.md still said
HIP_ARCH defaulted to gfx1200;gfx1201 and was re-applied on reconfigure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014d2rht2WMSopuodhtXYZzx
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants