perf(gqa): fused v5 prefill for sinks and sliding windows, at 44% fewer instructions per KV tile - #652
Draft
BoarQing wants to merge 1 commit into
Draft
perf(gqa): fused v5 prefill for sinks and sliding windows, at 44% fewer instructions per KV tile#652BoarQing wants to merge 1 commit into
BoarQing wants to merge 1 commit into
Conversation
|
Thanks for opening a PR! This project follows LLVM's incremental-development and AI-tool-use Before requesting review, please check that:
Reviewers are assigned through |
BoarQing
force-pushed
the
perf/gqa-v5-prefill-sinks-windows
branch
from
August 4, 2026 07:00
280ef60 to
55cd17e
Compare
L2 Accuracy Results (EP vs CPU)
Threshold: 0.01 | Run: 3632 - Commit: |
MorphiZen EP Performance Results
EPContext Export Performance
EPContext Import Performance
OGA Benchmark Results
OGA Wheel Smoke (Python benchmark_e2e.py)
Run: 3632 - Commit: |
… -5% TTFT) The v5 warp-private prefill loop was bound by instruction issue, not by occupancy, bandwidth or the matrix units: it issued 1734 instructions per KV tile to retire 32 WMMA, so the matrix units saw 2% of the stream. Occupancy had already been measured and rejected as a lever twice, from both directions. Three changes cut that to 967 instructions on the 99.6% of tiles that need no causal mask: - Native exp2 in the softmax. exp2f lowers through OCML to a denormal-safe compare/select/bias/ldexp wrapper around each v_exp_f32, six instructions where one would do, 96 of them per tile. Both call sites take arguments that are provably <= 0, so the only value the denormal path protects is one below 2^-126, a key scoring 126 log2-units under its row max. Flushing it is the correct answer, not an approximation. Same idiom HIP itself uses for __expf. - Defer the softmax denominator's cross-lane reduction out of the KV loop into the epilogue, paying it once per row instead of once per tile: 64 fewer ds_bpermute per tile. Sound because the correction factor is lane-uniform, so the reduction commutes with the online-softmax recursion. - Split the KV loop into a mask-free bulk and a masked tail via a tag-dispatched tile body. At deep KV nearly every tile is wholly below the causal diagonal yet still evaluated a per-element mask; the mask-free instantiation drops it entirely. This also removes what the restructure would otherwise cost: it takes in-loop scratch traffic from 52 ops to 6, because the compiler can keep a tile's live values in registers once the two tile shapes are distinct. Measured on gpt_oss-sink sq=512 past=8192, interleaved against the parent commit: exp2 alone -0.5%, the deferred reduction alone -8.9%, both -12.4%, all three -21.4%. The first is free on its own because the reduction chain it sits behind is what the loop waits on. In an RGP capture of a 16k-token gpt-oss-20b prefill the v5 full-attention dispatch mean falls 5989.7 -> 5207.7 us and the kernel drops from 25.3% to 23.0% of the capture window; TTFT falls 9653 -> 9172 ms, -5.0%. Across all 24 timed harness cases, 21 faster, 1 tie, no regressions; worst relL2 unchanged at 5.74e-4, so the reordered summation is not observable at harness tolerance. D=128 routes to v7 and is unaffected.
BoarQing
force-pushed
the
perf/gqa-v5-prefill-sinks-windows
branch
from
August 4, 2026 07:36
55cd17e to
9813372
Compare
BoarQing
force-pushed
the
perf/gqa-v5-prefill-sinks-windows
branch
from
August 4, 2026 12:39
76576f4 to
9813372
Compare
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two commits against the GQA prefill path, both on
gqa_flash_prefill_v5(thed==64warp-private kernel):perf(gqa): fused flash prefill for attention sinks and sliding windows— teaches v5 attention sinks, smooth softmax and sliding windows so gpt-oss no longer falls back to the decomposed path for its 12 sliding layers and 24 sink layers.HAS_WINDOWis a template parameter rather than a runtime test because the kernel runs at the VGPR ceiling: carrying the window's per-row state live in the full-attention instantiation pushedM_TILES=2from 120 to 180 bytes/lane of scratch and cost that path 21% at deep KV.perf(gqa): cut v5 prefill KV-loop instruction count 44%— the loop was bound by instruction issue, not occupancy, bandwidth or the matrix units: 1,734 instructions per KV tile to retire 32 WMMA, so the matrix units saw 2% of the stream. Three changes take that to 967 on the 99.6% of tiles that need no causal mask: nativeexp2in the softmax, the softmax denominator's cross-lane reduction deferred out of the loop into the epilogue, and a mask-free/masked tile split via a tag-dispatched tile body.Measured
Interleaved A/B on
gpt_oss-sink sq=512 past=8192, each arm a separate build, 3 rounds:exp2pays nothing on its own because the reduction chain it sits behind is what the loop waits on; it only becomes visible once that chain is gone. The tile split is likewise not separable from the restructure it needs — the same lambda without the split costs 32% on everyd=64full-attention case, because the split is what lets the compiler keep a tile's live values in registers (in-loop scratch traffic 52 ops → 6).In-model, 16k-token gpt-oss-20b prefill, RGP capture:
Correctness
test_gqa_prefillcovers 26 cases against an fp32 CPU reference: all four sink/smooth/both/none modes, windows at 1/100/128/4096,pastat 0/512/1000/8192,dat 64 and 128, and the twod=128cases that assert v5 declines so the runtime falls back rather than silently ignoring a sink or a window. All 26 pass, worstrelL25.74e-4, unchanged by commit 2 — the reordered summation in the deferred reduction is not observable at this tolerance.Across the 24 cases the suite also times: 21 faster, 1 tie, no regressions.
d=128routes to v7 and is unaffected (1.00x, 1.01x).The new mask-free instantiation is exercised by every full-attention case with
past > 0, and by the interior q-tiles of thepast = 0cases.Notes for review
__hip_cuidsymbol, which is a hash of the translation unit and moves when a comment does.ablatebitmask and thehip_gqa_flash_prefill_v*_set_ablateexports visible in this file are pre-existing onmainfrom [feat]:add opt for normal GQA #438; these commits neither add to them nor remove them. Twogetenvreaders I had used to drive them during development were taken back out before review, and the device image is byte-identical either way, since they were host-side only.What is deliberately left on the table
Re-profiling after the fact says the phase order inverted: the value GEMM is now the largest phase at 36.5% (was 19.8%) and softmax has fallen to 24.2% (was 39.1%). The loop is now only 28-41% issue-bound, so instruction count has largely stopped being the lever. Two candidates remain, both aimed at the LDS pipe and both sized from the ISA but not built: storing V transposed in LDS to turn 128
ds_load_u16into 16ds_load_b128(worth -5.4% by instruction count, 3.6-7.1% by an LDS-pipe model, against an unquantified bank-conflict risk), and DPProw_xmaskfor the remaining 64ds_bpermute(verified available on gfx1151, but it does not fold into the consumingv_max, so it is instruction-neutral and worth 0% or 4.6-9.1% depending on which LDS throughput assumption holds). Together roughly 2% of prefill at the optimistic end.Test plan
test_gqa_prefill— 26/26 pass, fp32 reference, worstrelL25.74e-4test_gqa_prefilltiming sweep, 24 cases, 20 and 40 iters, no regressions