Skip to content

perf(rocm): defer SDPA accumulator rescale, stage Q once per block - #2306

Merged
inureyes merged 3 commits into
mainfrom
perf/issue-2251-flash-sdpa-prefill
Oct 14, 2026
Merged

inureyes merged 3 commits into
mainfrom
perf/issue-2251-flash-sdpa-prefill

Conversation

@inureyes

Copy link
Copy Markdown
Member

Summary

Defer the WMMA flash-attention O-accumulator rescale so it runs only when a row's block max outgrows its softmax frame by more than a 10-point margin, and stage Q once per block instead of on every K block (issue #2251). The 16K-key chunked-prefill call on gfx1151 measured 540.1 ms/call (0.95 TFLOP/s) before the change and 429.2 ms/call (1.20 TFLOP/s) after, 1.26x, with 1.28x at 2K keys and 1.30x at 8K; the non-WMMA fallback arm is another 3.5 to 5.7x slower.

What changed

  • src/lib/mlx-cpp/patches-rocm/mlx/backend/rocm/flash_attention_wmma.hip: the O tiles and l of a row stay in the softmax frame the row was last rescaled to, so the LDS store/scale/reload of all accumulator tiles (3 barriers per tile, 24 barriers per K block at D=128) runs only when a row's block max outgrows its frame by more than kFaWmmaRescaleMargin (10.0), never in the steady state of a chunked prefill; P = exp(S - f) stays bounded by exp(10) = 22,026, inside the bf16 score cap. Q is staged once per block when the head dim fits one DW-wide slice (D <= 128).
  • tests/rocm_sdpa_flash_wmma_prefill.rs: causal correctness against an explicit f32 reference at the 2K and 16K key shapes for f16 and bf16, a score ramp that drives rows past the rescale margin, and a throughput gate that requires the 16K-key f16 call to clear 1.1 TFLOP/s, set between the measured before (0.95) and after (1.20 to 1.31).
  • src/lib/mlxcel-core/examples/sdpa_prefill_probe.rs: probe timing the maskless causal call at the three key lengths against a dense f16 GEMM bound; docs/benchmark_results/sdpa-flash-prefill-gfx1151-2026-10-13.md records the numbers.

Measurements

  • Both arms are the probe's internal median of 5 timed calls after 2 warmups, run back to back on an idle gfx1151 (rocm-smi --showpids empty and load under 0.1 before and after each arm). Before: 24.4 / 161.7 / 540.1 ms (1.41 / 1.49 / 0.95 TFLOP/s); after: 19.0 / 124.5 / 429.2 ms (1.81 / 1.93 / 1.20 TFLOP/s) at 2K / 8K / 16K keys.
  • MLX_SDPA_NO_WMMA=1 fallback arm measured 103.2 / 709.0 / 1516.8 ms (0.33 to 0.34 TFLOP/s). A gate run under scripts/rocm_gpu_guard.sh --hold measured the fixed 16K call at 392.6 ms/call (1.31 TFLOP/s).

Test plan

  • make verify-rocm-held on the gfx1151 host: 12,316 tests passed, 0 failed, 404 ignored, [verify-rocm] OK
  • cargo test --profile test-fast --features rocm --test rocm_sdpa_flash_wmma_prefill -- --test-threads=1: all three tests pass
  • cargo clippy --workspace --all-targets --features rocm -- -D warnings clean, cargo fmt clean

Closes #2251

The fused WMMA flash attention kernel rescaled every O accumulator tile through LDS on every K block: a store, scale and reload of all D_TILES f32 tiles, 3 barriers per tile, 24 barriers per K block at D=128, and re-staged Q from global memory on every K block. At the chunked-prefill shapes (2,048-token query chunks against 2K to 16K keys, GQA 32/8, head dim 128) that per-block scalar overhead dominated: the kernel ran at 1.1 to 1.7 TFLOP/s while the same device's f16 GEMM path delivers about 14 TFLOP/s at the same shape.

The O entries and l of a row now stay in the softmax frame the row was last rescaled to, so the rescale runs only when a row's block max outgrows that frame by more than a 10-point margin (kFaWmmaRescaleMargin), never in the steady state of a chunked prefill where the block max moves a few points per block; P = exp(S - f) stays bounded by exp(10) = 22,026, well inside the bf16 score cap. Q is staged once per block when the head dim fits one DW-wide slice (D <= 128).

Refs #2251
Three tests on the fused ROCm SDPA prefill path at the chunked-prefill shapes: causal correctness against an explicit f32 matmul + softmax + matmul reference at the 2K and 16K key shapes for f16 and bf16, a score ramp that drives a row's block max past the rescale margin so the deferred branch runs, and a throughput gate that requires the 16K-key f16 call to clear 1.1 TFLOP/s, set between the measured before (0.95) and after (1.20 to 1.31) on an idle gfx1151. The probe example times the maskless causal call at the three key lengths against a dense f16 GEMM bound; it builds only under the rocm feature.

Refs #2251
Benchmark session page for the flash SDPA prefill record on Radeon 8060S (gfx1151) with the measurement harness scripts (session, bench round, server). The kernel-level table is filled from the probe's idle-GPU A/B (before 540.1 ms, after 429.2 ms at 16K keys, 1.26x, 3.5x over the non-WMMA fallback); the model-level session (bench-decode, batch-4 server) is marked not measured yet because the shared host kept the guard lock busy.

Refs #2251
@inureyes
inureyes force-pushed the perf/issue-2251-flash-sdpa-prefill branch from 818144f to 661dc19 Compare October 14, 2026 04:30
@inureyes
inureyes merged commit e642d56 into main Oct 14, 2026
27 checks passed
@inureyes
inureyes deleted the perf/issue-2251-flash-sdpa-prefill branch October 14, 2026 04:33
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.

perf(rocm): flash SDPA dominates long-context chunked prefill on gfx1151

1 participant