Commit 996cea1
authored
Add flash attention for non-quantized CPU GroupQueryAttention (#28962)
## Summary
Adds an FP32 flash attention path for the CPU
`com.microsoft.GroupQueryAttention` (GQA) contrib op, mirroring the
existing quantized-KV flash attention path. The new tiled,
online-softmax kernel avoids materializing the full `[S, T]` attention
score matrix. It is restricted to prefill / chunked-prefill
(`sequence_length > 1`); single-token decode falls back to the naive
path. With causal early-termination it is faster than the naive path
across all measured prefill lengths while using a fraction of the
memory.
## Key changes
- **New MLAS kernel** `onnxruntime/core/mlas/lib/flashattn_gqa.cpp`
(`MlasFlashAttentionGQA`):
- Tiled QK / softmax / SV with online-softmax (running max/sum
rescaling).
- GQA head grouping (`num_heads % kv_num_heads == 0`), causal masking,
local window, additive attention bias, and packed-QKV input.
- **Causal early-termination**: during prefill, KV blocks that fall
entirely in the causally masked upper triangle are skipped (`break` once
`ir >= past_seqlen + q_idx + row_size_q`), avoiding the wasted QK/SV
GEMMs over roughly half of the square prefill attention matrix.
- Per-batch invocation for ragged / shared-buffer `seqlens_k`.
- **MLAS API** `onnxruntime/core/mlas/inc/mlas.h`: new
`MlasFlashAttentionGQAArgs` struct and `MlasFlashAttentionGQA`
declaration.
- **Dispatch** `onnxruntime/contrib_ops/cpu/bert/gqa_attention_base.h`:
new `ApplyAttentionFlash` that concatenates new K/V into the FP32
present cache and invokes the kernel. The per-thread scratch buffer size
is computed with `SafeInt<size_t>` to guard against `size_t` overflow on
large/malformed shapes before allocation.
- **Wiring**
`onnxruntime/contrib_ops/cpu/bert/group_query_attention.cc`: float-only
flash dispatch, active only for prefill (`sequence_length > 1`) and when
`softcap == 0`, no smooth softmax, no head sink, no QK output; falls
back to the naive path otherwise. The existing
`ORT_GQA_DISABLE_FLASH_ATTENTION` env var disables it.
- **CMake** `cmake/onnxruntime_mlas.cmake`: register the new source
file.
- **Docs** `docs/contrib_ops/cpu/gqa.md`: document the non-quantized
flash attention path, activation conditions, causal early-termination,
file list, and FP32 flash-vs-naive benchmark results.
- **Benchmark**
`onnxruntime/test/python/transformers/benchmark_gqa_cpu_flash.py`: add
an FP32 (non-quantized) mode (`--fp32`) for operator-level
flash-vs-naive comparison.
### Why prefill-only (`sequence_length > 1`)
Single-token decode (`sequence_length == 1`) produces only a `[1,
total_sequence_length]` score row per head, so there is nothing to tile
away and the extra online-softmax bookkeeping makes the flash kernel
slower and noisier than naive in practice. Restricting the flash path to
prefill keeps the consistent prefill win without regressing decode.
Because decode is excluded, the two-phase flash-decoding kernels are
unreachable and have been removed for a smaller, simpler implementation.
`float16` continues to use the naive path (the kernel is float-only,
matching the quantized flash constraint).
## Performance
Operator-level, AMD EPYC 7763 (16 physical cores), threads=8, FP32 KV
cache, `B=1, num_heads=16, kv_num_heads=8, head_size=128`. Flash is
faster than naive across all measured prefill lengths (and
single-threaded as well, 1.4-1.8x), confirming the gain is algorithmic -
the causal early-termination removes the wasted upper-triangle work that
previously made flash slower than naive at short sequences.
| Prefill Seq Length | Naive (ms) | Flash (ms) | Speedup |
|---:|---:|---:|---:|
| 512 | 5.8-8.4 | 4.2-5.3 | 1.4-1.6x |
| 1024 | 25-29 | 13-18 | 1.6-2.0x |
| 2048 | 87-118 | 52-65 | 1.5-2.0x |
| 4096 | 365-380 | 213-234 | 1.6-1.7x |
The flash path's primary structural benefit is memory: it never
allocates the full O(N x S x T) attention matrix (~1 GB at S=4096, N=16)
and instead uses an O(S x Bc) per-thread tile.
## Testing
- **C++ op tests**: `onnxruntime_provider_test
--gtest_filter="GroupQueryAttentionTest.*"` - 38 passed (12 GPU/WebGPU
skipped) with flash on (default) and with
`ORT_GQA_DISABLE_FLASH_ATTENTION=1`.
- **Flash vs. naive parity** (FP32): output of the flash path matches
the naive path (max abs diff ~1e-7) across prefill (block-aligned and
non-aligned `S`), MHA and GQA head ratios, and local window. Decode now
uses the naive path on both sides (diff 0).
- **Python parity** (`test_gqa_cpu.py`, flash vs. naive reference):
focused FP32 sweep of 600 prompt configurations covering all head sizes
(32-256), GQA ratios `(6,6)/(6,3)/(9,9)/(9,3)`, batches `1/3/5`,
causal/local window, attention bias, position ids, packed QKV, and
with/without KV buffer - all passed. The official `test_gqa_cpu.py`
suite passes.
Two correctness bugs were found and fixed via the parity sweep while
developing this path:
1. Attention-bias batch stride ignored head broadcasting for `[batch, 1,
S, T]` bias.
2. Query batch stride was hardcoded to `num_heads * S * H`, which is
incorrect for packed-QKV input (correct stride is `(num_heads + 2 *
kv_num_heads) * S * H`).1 parent eade9ea commit 996cea1
7 files changed
Lines changed: 911 additions & 58 deletions
File tree
- cmake
- docs/contrib_ops/cpu
- onnxruntime
- contrib_ops/cpu/bert
- core/mlas
- inc
- lib
- test/python/transformers
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
56 | 56 | | |
57 | 57 | | |
58 | 58 | | |
| 59 | + | |
59 | 60 | | |
60 | 61 | | |
61 | 62 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
20 | | - | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
21 | 26 | | |
22 | 27 | | |
23 | 28 | | |
| |||
48 | 53 | | |
49 | 54 | | |
50 | 55 | | |
51 | | - | |
| 56 | + | |
52 | 57 | | |
53 | 58 | | |
54 | 59 | | |
55 | 60 | | |
56 | | - | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
57 | 64 | | |
58 | 65 | | |
59 | 66 | | |
| |||
144 | 151 | | |
145 | 152 | | |
146 | 153 | | |
147 | | - | |
| 154 | + | |
148 | 155 | | |
149 | | - | |
| 156 | + | |
150 | 157 | | |
151 | 158 | | |
152 | 159 | | |
| |||
204 | 211 | | |
205 | 212 | | |
206 | 213 | | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
207 | 266 | | |
208 | 267 | | |
209 | 268 | | |
| |||
428 | 487 | | |
429 | 488 | | |
430 | 489 | | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
431 | 494 | | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
432 | 523 | | |
433 | 524 | | |
434 | 525 | | |
| |||
0 commit comments