Skip to content

cpu decode: route f16 SDPA through FlashSdpa, fuse GQA broadcast through cache-read cast/rope#2453

Open
czoli1976 wants to merge 1 commit into
sonos:mainfrom
czoli1976:perf/flash-sdpa-f16-gqa
Open

cpu decode: route f16 SDPA through FlashSdpa, fuse GQA broadcast through cache-read cast/rope#2453
czoli1976 wants to merge 1 commit into
sonos:mainfrom
czoli1976:perf/flash-sdpa-f16-gqa

Conversation

@czoli1976

Copy link
Copy Markdown
Contributor

Routes f16-accumulator SDPA through FlashSdpaOp on CPU and lets the GQA broadcast fusion look through cache-read Cast/ApplyRope ops, so torch-style f16 GQA exports stop paying a materialized head broadcast plus full-KV-cache repacking on every decode step.

Profiling an OpenELM-270M q40ef16 decode step at P=1000 showed the real GEMV work (~14 ms) buried under per-token recompute over unchanged data: 13.1 ms OptMatMulPack re-packing the KV cache, 8.2 ms MultiBroadcastTo materializing the repeat-interleave, 4.1 ms ApplyRope re-roping the whole window. Two routing gaps kept the existing flash path out of reach: the codegen gate only admitted f32 accumulators (the kernel itself takes any float and accumulates in f32), and the fusion matcher required the DynKeyValueCache directly under the repeat chain, which torch exports interpose with cache-read casts and rope.

Measured on Apple M-series, 1 thread, interleaved A/B against main via llm_decode_bench (medians):

context main ms/tok this PR speedup
100 15.1 13.9 1.09×
1000 43.0 30.5 1.41×
2000 75.3 48.1 1.57×

llm-bench: PP512 262 → 338 tok/s (+29%). One trade-off to be aware of: TG128 is −3% (75.1 → 72.7 tok/s) — at very shallow context the decomposed path still competes, and TRACT_FLASH_SDPA_MIN_SEQ_LEN can't gate it because kv_len is symbolic at codegen time. A runtime-side dispatch could recover it if you think it's worth the complexity.

Tests: f16-eval-vs-f32 parity on the op, and a model-level test replicating the torch GQA idiom (cache → cast → rope → repeat-interleave → SDPA) asserting the broadcasts fuse away and the fused model matches the unfused reference through real cache state. transformers/onnx-core/core/unit/linalg suites green.

Follow-ups I'd like to explore next, in order: folding the f16→f32 cast per-block into the flash streaming loop, folding rope-at-read into the kernel, and letting InPlaceKvSdpa match through the same cast/rope chain (which also removes the concat copy).

🍍

🤖 Generated with Claude Code

…A broadcast fusion look through cache-read Cast/ApplyRope ops. The flash kernel already accepts any float input (accumulating in f32) and handles grouped heads natively, but the codegen gate only admitted f32 accumulators and the fusion matcher required the KV cache directly under the repeat-interleave chain, so torch-style f16 GQA exports paid a materialized head broadcast plus full-cache repacking on every decode step.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant