mirror of
https://github.com/Comfy-Org/comfy-kitchen.git
synced 2026-11-04 23:37:42 +00:00
[PR #94] [CLOSED] HIP: fix RDNA4 GEMM tile selection, vectorize fp8 elementwise kernels #140
Labels
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Comfy-Org/comfy-kitchen#140
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/Comfy-Org/comfy-kitchen/pull/94
Author: @crashingalexsan
Created: 8/5/2026
Status: ❌ Closed
Base:
main← Head:rdna4-wmma-tuning📝 Commits (10+)
e177160HIP: fix RDNA4 GEMM tile selection and vectorize fp8 elementwise kernelsa9ee312HIP: make the device CU cache atomicd1e673cRename device_cu_count to device_wgp_count, cover the BKB=128 tile paths35df5ecRoute skinny shapes (M or N <= 64) to the 64x64 tilebfb2c0bTrigger CIcfb2a59Tighten selector and test-shape comments90d9a97Reword tuning provenance comment to RDNA4a887137Cover w4x4 deep-K on 40-48 WGP parts, unify float conversion helpers4e3778fNote byte units on depth thresholds, cover the scalar elementwise fallback690e9b4Make misaligned-view tests compare real allocations and run the hip backend📊 Changes
6 files changed (+295 additions, -50 deletions)
View changed files
📝
comfy_kitchen/backends/hip/gemm_wmma.h(+93 -0)📝
comfy_kitchen/backends/hip/ops/gemm_fp8.hip(+1 -20)📝
comfy_kitchen/backends/hip/ops/gemm_int8.hip(+1 -16)📝
comfy_kitchen/backends/hip/ops/per_tensor_fp8.hip(+138 -12)📝
tests/test_hip_wmma.py(+12 -2)📝
tests/test_qdq.py(+50 -0)📄 Description
HIP: fix RDNA4 GEMM tile selection, vectorize fp8 elementwise kernels
What this fixes
The WMMA GEMM launcher chose its tile from
M/N/Kthresholds that, on twocommon shape classes, select the worst configuration available to it. Both are
pure selection bugs — the kernel itself is fine, it was just being handed the
wrong template parameters.
1. The
BM=256"deep-K" tile was gated onK > N, which is exactly where itperforms worst. On
4096x2048x8192it runs at 152 TFLOPS where the same kernelat
128x128withBKB=128reaches 188. Its 16 accumulator fragments per warpcost more occupancy than the taller tile buys back. Removed entirely.
2. The
M >= 96 && N >= 96floor sent narrow shapes to the128x128tile evenwhen that yields fewer blocks than the device has CUs, leaving most of the GPU
idle.
2048x128x2048ran at 18 TFLOPS; the64x64tile does 70.What replaces them
A single
launch_gemm_wmma()ingemm_wmma.h, shared by the fp8 and int8launchers, selecting on three measured axes:
64x64when the128x128grid can't fill the deviceBKB=128fromK>=4096__syncthreadspairs per K elementK>=4096,w4x4/t2x2while blocks/CU <= 4, elsew4x2/t2x4The K threshold is set by int8, not fp8: taking
BKB=128lower costs int8 0.81xat
K=640, where fp8 only breaks even.The warp-grid crossover, consistent across both policies:
w4x4/t2x2vsw4x2/t2x4Results
Old policy vs new, same kernel, A/B interleaved with min-of-3 on an idle
gfx1201 (RX 9070 XT), pseudorandom operands:
Shapes where both policies select the same config land within ±4% (noise).
Measured through torch against hipBLASLt on the same shapes, the fp8 GEMM goes
from 1.21x slower than hipBLASLt to 1.11x — it closes 46% of the gap. Output
is bit-identical to hipBLASLt on every shape tested.
Elementwise kernels
per_tensor_fp8quantize/dequantize moved one element per thread, so a wavewrote 32 contiguous bytes. Added a 16-elements-per-thread path moving whole
16-byte chunks, gated on a 16-byte alignment check of both pointers — a
contiguous tensor can still be a mid-buffer view, whose base carries the offset.
Per-element arithmetic is unchanged, so both paths are bit-identical (verified on
raw bit patterns; note
torch.equalreports False on fp8-derived data purelybecause e4m3
0x7f/0xffdecode to NaN).Testing
test_hip_wmma,test_hip_dispatch,test_qdq,test_int8—338 passed, 0 failed (stable over repeated runs).
gfx1100,gfx1151,gfx1201,gfx1030.NVFP4/MXFP8 (no HIP kernels), and HIP graph capture all fail identically on
unmodified v0.2.26.
Limitations / for reviewers
All tuning was done on gfx12 (gfx1201). I have no gfx11 hardware. The
grid-coverage rule adapts itself via a runtime CU query and should carry over;
the K and warp-grid thresholds are gfx12 numbers.
gfx11 fp8 spills registers badly and this PR does not fix it. With no fp8
WMMA there,
MmaFp8::Fragis av16bf(8 VGPRs) rather than gfx12'sv2i(2), and compiler resource usage on gfx1100 shows every
128x128fp8 tilespilling:
w4x2/t2x4308 VGPRs spilled,256x128834, while64x64x128and128x128 w4x4/t2x2spill none. This PR helps gfx11 incidentally — it deletesthe 834-spill tile and routes narrow shapes to the spill-free
64x64— but asquare deep-K shape moves from 308 to 369. Using
w4x4/t2x2throughout wouldeliminate the spilling, but it is the slower grid on gfx12 once blocks are
plentiful, so a proper fix needs an arch-conditional grid and someone with an
RDNA3 card to validate it. Worth noting register pressure alone does not
predict throughput here.
Benchmarking caveat: operand data matters a lot. A cycling byte fill
switches far fewer transistors than realistic randn-derived fp8, so the GPU
holds a ~20% higher sustained clock — 218 vs 180 TFLOPS on the same kernel and
shape. All numbers here use pseudorandom operands. Timing two policies in
back-to-back blocks rather than interleaved also makes the second look ~30%
slower even when both select the identical config.
Where the win concentrates (thanks @0xDELUXA): sweeping K on a fixed
2048x2048, the speedup over the old selector is concentrated where K is a
multiple of 4096 (1.35x at K=4096, 1.98x at K=8192, vs ~1.03x at 3072/6144).
The likely mechanism is the deleted
256x128tile hitting cache aliasing whenthe row stride is a multiple of 4096 bytes. If you benchmark this PR at, say,
K=3072 only, it will look like a no-op — that is expected.
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.