[Metal] Add Flash Attention VJP for training - #2995
Conversation
|
Notes: I'm working on https://github.com/mlx-node/mlx-node and trying to port some features in trl. |
568ff36 to
26b5857
Compare
dd8daf1 to
5c78507
Compare
zcbenz
left a comment
There was a problem hiding this comment.
Can you share some benchmarking numbers?
5c78507 to
9008d2e
Compare
|
Benchmark Summary for PR Key observations:
|
|
I ran the benchmark script on a M4 air and it seems that fused VJP is slower for most cases: $ python benchmarks/python/sdpa_vjp_bench.py
SDPA VJP Benchmark - dtype=float16
=====================================================================================
[Forward + Backward (VJP)]
B H_q H_kv L D | unfused fused speedup path
-------------------------------------------------------------------------------------
2 8 8 1 64 | 0.07ms 0.07ms 1.09x vector
2 8 8 4 64 | 0.07ms 0.07ms 1.03x vector
2 8 8 8 64 | 0.07ms 0.07ms 1.05x vector
2 8 8 8 128 | 0.08ms 0.08ms 0.99x vector
2 8 8 32 64 | 0.10ms 0.11ms 0.89x STEEL
2 8 8 64 64 | 0.18ms 0.21ms 0.84x STEEL
2 8 8 128 64 | 0.45ms 0.54ms 0.82x STEEL
2 8 8 128 128 | 0.59ms 0.69ms 0.85x STEEL
2 8 8 256 128 | 1.93ms 2.32ms 0.83x STEEL
1 32 8 512 64 | 11.45ms 14.91ms 0.77x STEEL
1 32 8 512 128 | 14.26ms 17.73ms 0.80x STEEL
1 32 8 1024 64 | 50.49ms 64.39ms 0.78x STEEL
1 32 8 1024 128 | 56.06ms 69.96ms 0.80x STEEL
1 32 8 2048 128 | 244.84ms 299.90ms 0.82x STEEL
2 32 8 256 64 | 6.16ms 8.06ms 0.76x STEEL
2 32 4 256 64 | 6.23ms 8.02ms 0.78x STEEL |
|
Interesting, slight difference in the results between the M4 Air and my MacBook Pro 16 with the M3 Max and 128GB of RAM.
The primary benefit of Flash Attention VJP is memory, not speed:
For long sequences (4K+), memory savings can be critical even if speed is similar or slightly slower. |
5bba7f2 to
21caa99
Compare
|
I tested on a M3 Max and the results are still mixed: $ python benchmarks/python/sdpa_vjp_bench.py
SDPA VJP Benchmark - dtype=float16
=====================================================================================
[Forward + Backward (VJP)]
B H_q H_kv L D | unfused fused speedup path
-------------------------------------------------------------------------------------
2 8 8 1 64 | 0.30ms 0.26ms 1.17x vector
2 8 8 4 64 | 0.34ms 0.24ms 1.43x vector
2 8 8 8 64 | 0.28ms 0.25ms 1.14x vector
2 8 8 8 128 | 0.29ms 0.33ms 0.88x vector
2 8 8 32 64 | 0.33ms 0.36ms 0.92x STEEL
2 8 8 64 64 | 0.43ms 0.28ms 1.55x STEEL
2 8 8 128 64 | 0.29ms 0.33ms 0.86x STEEL
2 8 8 128 128 | 0.31ms 0.31ms 0.98x STEEL
2 8 8 256 128 | 0.39ms 0.40ms 0.97x STEEL
1 32 8 512 64 | 1.02ms 1.01ms 1.02x STEEL
1 32 8 512 128 | 1.30ms 1.34ms 0.97x STEEL
1 32 8 1024 64 | 3.75ms 3.75ms 1.00x STEEL
1 32 8 1024 128 | 4.81ms 4.81ms 1.00x STEEL
1 32 8 2048 128 | 16.65ms 16.66ms 1.00x STEEL
2 32 8 256 64 | 0.63ms 0.66ms 0.95x STEEL
2 32 4 256 64 | 0.65ms 0.67ms 0.97x STEELI'm not really familiar with how VJP works for flash attention but generally speaking reduced memory usage should make op faster when it is memory-bound, which is usually the case for mac, so I think fused op being slower likely means something is off. But anyway the result on vector path looks promising, maybe separate that part into an independent PR first? It would also make reviewing much easier. |
21caa99 to
4875a9d
Compare
|
Done, I only preserve the |
zcbenz
left a comment
There was a problem hiding this comment.
I'm not familiar with the Metal implementation so we will need other maintainers to review the code.
43bc7cb to
d226a28
Compare
|
@awni, can you take a look at this PR? |
d226a28 to
434c946
Compare
434c946 to
9db03f9
Compare
b704e4f to
229d510
Compare
…rt dispatch
Add fused backward (VJP) kernels for scaled dot-product attention on
Metal GPU, implementing Flash Attention's memory-efficient backward pass
that avoids materializing the O(L²) attention matrix.
Two-kernel architecture:
- steel_attention_vjp_dq: computes dQ gradients
- steel_attention_vjp_dkv: computes dK and dV gradients
Both kernels recompute the attention matrix tile-by-tile (BQ=32, BK=32)
using shared memory staging, eliminating the need to store the full
[B, H, L, L] intermediate for backward.
Smart dispatch policy (MLX_SDPA_VJP_MODE env var):
- "auto" (default): uses fast NAX-optimized unfused backward for
typical shapes; switches to fused when sequence length >= 8192
(MLX_SDPA_VJP_LONG_L_THRESHOLD) or when the estimated attention
matrix exceeds 1 GB (MLX_SDPA_VJP_ATTENTION_BYTES_THRESHOLD).
- "unfused": always uses unfused backward (fastest on Apple Silicon).
- "fused": always uses fused backward (memory-efficient).
On Apple Silicon with NAX-optimized matmul kernels, unfused backward is
faster at all sequence lengths due to large-tile MMA efficiency (10.7
TFLOPS vs 1.9 TFLOPS for BQ=32 tiles). However, the fused path provides
substantial memory savings by avoiding the quadratic attention matrix:
- L=512: 67% savings
- L=1024: 85% savings
- L=2048: 93% savings
- L=4096: 96% savings
This makes fused backward essential for long-context training where the
attention matrix would exceed available GPU memory.
Also includes:
- Vector VJP kernel (sdpa_vector_vjp) for short query sequences (L<=8)
supporting D=64/96/128/256 with two-stage tiling for D=256.
- Sequence padding to block boundaries for unaligned lengths.
- GQA (grouped query attention) support in both kernel paths.
- Concurrent dQ and dKV kernel dispatch for reduced latency.
- Benchmark with interleaved thermal-fair measurement (P50/P90).
- Long-sequence tests (L=8192, L=16384) verifying memory savings.
- Documentation (docs/attention_backward.md) covering dispatch policy,
env var controls, and Xcode GPU profiling guidance.
Eligible configurations: D=64, float16/bfloat16, no mask, no sinks.
Unsupported configurations fall back to unfused backward automatically.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Completely rewritten; it is now closed and will reopen as a new one. |
Summary
Implements fused backward pass (VJP) for
scaled_dot_product_attentionon Metal GPU. This enables efficient gradient computation during training without falling back to unfused (decomposed) attention operations.Changes
New Files
mlx/backend/metal/kernels/sdpa_vector_vjp.h- Vector VJP kernel for short sequences (L ≤ 8)mlx/backend/metal/kernels/steel/attn/kernels/steel_attention_vjp_dq.h- STEEL dQ gradient kernelmlx/backend/metal/kernels/steel/attn/kernels/steel_attention_vjp_dkv.h- STEEL dK/dV gradient kernelModified Files
mlx/backend/metal/scaled_dot_product_attention.cpp- VJP dispatch logic (+840 lines)mlx/fast.cpp/mlx/fast_primitives.h- Logsumexp caching, VJP routingpython/tests/test_fast_sdpa.py- Comprehensive VJP tests (+220 lines)Implementation Notes
Uses a two-kernel approach to avoid atomic operations:
dQ kernel (
steel_attention_vjp_dq.h):dK/dV kernel (
steel_attention_vjp_dkv.h):Vector VJP (
sdpa_vector_vjp.h):Key Features
Limitations
Test Plan
test_sdpa_gradpassestest_sdpa_grad_vector_path- short sequences (L=1,4,7,8)test_sdpa_grad_steel_path- longer sequences (L=16,32,128,256)test_sdpa_grad_head_dims- head dimensions (D=32,64,96,128)test_sdpa_grad_gqa- GQA configurations (4:1, 8:1, 16:1, MHA)test_sdpa_grad_dtypes- float16, bfloat16, float32test_sdpa_grad_edge_cases- L=1, non-power-of-2, large batch, qL≠kvLAll 21 SDPA tests pass (1 skipped for unrelated disabled feature).