Xe2 (Lunar Lake/LNL, Battlemage/BMG) ESIMD Flash Attention — HD=256 (S^T Architecture)
Specialized knowledge for GQA flash attention with head_dim=256 on Intel Xe2 (Lunar Lake/LNL, Battlemage/BMG).
Current best: 88 TFLOPS (64.9% of 135 TFLOPS peak) at 16Kx16K, 32Q/2KV heads.
Reference files hold detailed history; this file holds critical rules and architecture.
Quick-Reference Rules (must follow every time)
HD=256 Architecture Differences (vs HD=128)
- S^T approach: QK GEMM produces scores in transposed layout
[KV_PER_SG=16][Q_ROWS*Q_GRPS=32] per thread. This is because K is loaded in reversed/transposed format for DPAS efficiency.
- Register transpose required: Before VS GEMM, S must be transposed from
[KV×Q] to [Q×KV] and written to SLM as fp16. This is the #1 overhead source (~20% of kernel time).
- Larger SLM footprint: Q_SLM=64KB + S_SLM=32KB + MAX_SLM=4KB + SUM_SLM=4KB = ~104KB (vs ~16-32KB for HD=128).
- 32 threads/WG (8 sg_i × 4 sg_j) — matches oneDNN decomposition. doubleGRF required.
Kernel Constants
HD = 256; // Head dimension
WG_Q_ROWS = 128; // Q rows processed per WG
KV_CHUNK = 128; // KV rows per outer iteration (8 sg_i × 16 KV_PER_SG)
KV_PER_SG = 16; // KV rows per subgroup per iteration
Q_ROWS = 8; // Q rows per subgroup tile
Q_PAIRS = 2; // Q pairs per sg_j (2 × 16Q = 32Q per sg_j)
Q_GRPS = 4; // Q groups per subgroup (4 × 8 = 32 Q rows per SG)
D_BLKS_PER_SG = 2; // D blocks per SG (2 × 16D = 32D per sg_i)
Thread Mapping
sg_i = tid & 7; // 0-7: KV/D dimension
sg_j = tid >> 3; // 0-3: Q dimension
// sg_i handles: KV rows [sg_i*16 : sg_i*16+16], D cols [sg_i*32 : sg_i*32+32] (VS phase)
// sg_j handles: Q rows [sg_j*32 : sg_j*32+32] (4 groups × 8 rows)
SLM Layout
0x00000 - 0x0FFFF: Q_SLM (64 KB) — Q tile [128 Q × 256 D] fp16
0x10000 - 0x17FFF: S_SLM (32 KB) — Scores [128 Q × 128 KV] fp16 (after transpose)
0x18000 - 0x18FFF: MAX_SLM (4 KB) — Cross-SG max reduction
0x19000 - 0x19FFF: SUM_SLM (4 KB) — Cross-SG sum reduction
Build Commands
# Full build (correctness + perf benchmark):
icpx test_rev256_onednn_v2.cpp -o test_rev256_onednn_v2.exe `
-fsycl -fsycl-targets=spir64_gen `
-Xs "-device bmg -options -doubleGRF" -O2 -w
# Mini build (perf only, faster compile):
icpx rev256_onednn_v2_83tflops_mad_fusion.cpp -o test.exe `
-fsycl -fsycl-targets=spir64_gen `
-Xs "-device bmg -options -doubleGRF" -O2 -w
# ISA dump build:
icpx test_rev256_onednn_v2.cpp -o test.exe `
-fsycl -fsycl-targets=spir64_gen `
-Xs "-device bmg -options -doubleGRF" -O2 -w -save-temps
# Then: ocloc disasm -file <spv> -device bmg -dump dump_v2_new
S Transpose: Solved via lsc_slm_scatter
The register transpose from [KV=16][Q=16] fp32 → [Q=8][KV=16] fp16 row-major used to generate ~270 mov (4|M0) instructions in ISA. Now eliminated using lsc_slm_scatter<uint32_t, 2, u32, 16>:
Working approach (Approach 2 — s_scatter, +1.7 TFLOPS):
- Pack adjacent KV rows into u32 via shift+OR:
packed = low_kv | (high_kv << 16)
lsc_slm_scatter<uint32_t, 2, u32, 16> writes 2 u32 per Q address → 4 KV values per call
- 4 scatter calls per qp (kv groups 0..3, 4..7, 8..11, 12..15), 8 total per thread
- SLM layout after scatter is Q-major KV-fast — same as baseline after register transpose
- Block read unchanged (slm_block_load)
CRITICAL: Element-major data layout. lsc_slm_scatter with NElts > 1 expects element-major layout:
// CORRECT: data[0..15] = elem0 for all addrs, data[16..31] = elem1 for all addrs
data.select<16, 1>(0) = packed0;
data.select<16, 1>(16) = packed1;
// WRONG: address-major interleave (produces ~0.12 rel_rms systematic error)
data.select<16, 2>(0) = packed0; // DO NOT USE
data.select<16, 2>(1) = packed1; // DO NOT USE
Failed alternatives:
- Approach 1 (s_gather — block_store + gather read): 73 TFLOPS (-12.9) — SLM gathers too expensive
- SLM scatter NElts=1 (64 D16 messages): 69 TFLOPS — scatter bandwidth too low with single-element writes
- VNNI interleave (stride-2 uint32 pairs + 8×8 transpose): neutral (82.7 TFLOPS) — compiler decomposed stride-8 uint32 gather into even more mov(4|M0)
Performance Testing
- Correctness: 9 test configs (128×128 to 1K×1K, GQA + MHA, multiple data ranges)
- Benchmark sizes: 8K×8K, 8K×16K, 16K×16K (compute-bound regime)
- Warmup: 5 iterations, then 100 timed iterations
- FLOPS formula:
4 * q_len * kv_len * HD * num_heads_q + 2 * q_len * kv_len * num_heads_q
- Peak: 135 TFLOPS (BMG Xe2 XMX fp16)
- Correctness threshold: max_diff < 0.1, check NaN count first
Backup Convention
All backups in SDP/backup/ with naming: rev256_onednn_v2_<TFLOPS>tflops_<technique>.{h,cpp,exe}
Performance Ladder (current)
| Version |
TFLOPS |
Roofline% |
Technique |
Useful? |
| vnni |
64 |
47% |
VNNI-based QK approach |
NO — slower than reversed DPAS |
| xprefetch |
68 |
50% |
Extended prefetch hints |
NO — marginal gain, added complexity |
| deferred_comp_70 |
70 |
52% |
Deferred compensation, cross-phase prefetch |
YES — foundation for all later work |
| nbarrier_71 |
71 |
53% |
Named barriers instead of regular barriers |
YES — small but free gain |
| best_72 |
72 |
53% |
Optimal baseline configuration |
YES — stable reference point |
| s_preload_72 |
72 |
53% |
S-preload in VS phase |
NEUTRAL — same perf, more code complexity |
| fp16_transpose_82 |
82 |
61% |
Separate F32→FP16 from strided gather |
YES — major breakthrough (+14%) |
| simd_inv_83 |
82.5 |
61% |
SIMD16 reciprocal instead of scalar div |
YES — small gain |
| mad_fusion_83 |
82.7 |
61% |
fp32_sum mad fusion (mul+add → mad) |
YES — incremental gain |
| pipelined_qk_84 |
84 |
62% |
32-thread WG (8×4), pipelined K loads, split barrier B |
YES — previous best |
| s_scatter_88 |
88 |
65% |
Eliminate S register transpose via lsc_slm_scatter<u32,2> |
YES — current best |
| s_gather (reverted) |
73 |
54% |
block_store + lsc_slm_gather<u32,4> read |
NO — gather reads too expensive (-12.9 TFLOPS) |
| SLM scatter NElts=1 (reverted) |
69 |
51% |
Replace transpose with 64 SLM scatter D16 ops |
NO — major regression, scatter BW too low |
| VNNI interleave (reverted) |
82.7 |
61% |
Stride-2 interleave + 8×8 uint32 transpose |
NO — neutral, compiler generated more movs |
| Early V loads (reverted) |
82.7 |
61% |
V loads before S transpose |
NO — increased register live range hurt scheduling |
| Double-buffered S (reverted) |
83.8 |
62% |
Cross-iteration pipeline with 2 S buffers |
NO — neutral, barrier_A already syncs S writes (see below) |
| S_VNNI + transposed acc (abandoned) |
— |
— |
Eliminate S transpose via DPAS operand swap |
NO — hw constraints: fp16 transpose max width=4, store height max=8 |
Key Optimization Insights
What Worked
- lsc_slm_scatter S transpose elimination (+1.7 TFLOPS, 84→88): Replace ~270 register transpose
mov instructions with lsc_slm_scatter<uint32_t, 2, u32, 16>. Pack adjacent KV rows into u32 via shift+OR, scatter directly to transposed SLM positions. 8 scatter calls per thread (2 qp × 4 kv_groups). Key discovery: NElts > 1 data must be in element-major layout.
- Separate F32→FP16 conversion from strided gather (+14%): Convert entire ST_tile to fp16 first, then do strided gather on fp16 data. Halves the register traffic for the transpose.
- mad instruction fusion:
fp32_sum = fp32_sum * delta + local_sum generates SIMD32 mad instruction with 2× throughput on BMG XVE.
- SIMD16 reciprocal:
simd<float,16> inv = 1.0f / sum instead of scalar 1.0f / sum[i] in a loop.
- Split barrier B (arrive/wait): Named barrier arrive after S SLM writes → V loads + K prefetch + compensation (all independent work) → named barrier wait before VS reads. The arrive/wait window spans V loads + K prefetch + deferred compensation = 100+ cycles of useful work between arrive and wait. See
xe2-nbarrier-pipelining skill for the pattern.
- Deferred compensation: Apply
A_tile *= delta before VS GEMM, not interleaved inside VS loop — cleaner DPAS pipeline.
- Cross-phase prefetch: Prefetch next K tile during VS phase, prefetch V during QK phase.
- 32-thread WG + pipelined K loads (+1.3 TFLOPS): Expanded from 8×2=16 to 8×4=32 threads matching oneDNN decomposition. K loads pipelined: load K[d+1] after DPAS[d], hiding K load latency behind compute.
What Didn't Work
- SLM gather S read (Approach 1):
slm_block_store of un-transposed data + lsc_slm_gather<uint32_t, 4, u32, 16> to read transposed. 73 TFLOPS (-12.9 from baseline). SLM gather reads are much more expensive than block loads — 4 gathers per S tile vs 1 block load each, and each gather has higher message overhead.
- SLM scatter NElts=1: 64
lsc_slm_scatter<u16, 1, u16, 8> messages (one per KV × qh). 69 TFLOPS. Per-element scatter bandwidth is severely limited.
- VNNI interleave transpose: Pairing adjacent KV into uint32 VNNI pairs with stride-2 dest writes — Xe2 compiler decomposes
select<8,8>() stride-8 uint32 gather into mov(4|M0), producing even more instructions.
- Extended prefetch (xprefetch): Adding more prefetch hints beyond the basic cross-phase pattern gave marginal improvement.
- S-preload optimization: Pre-loading S tiles from SLM before VS GEMM — no measurable improvement.
- Early V loads: Moving V loads before S transpose to overlap V latency with transpose ALU. Increased register live range, hurt compiler scheduling → 82.7 vs 84.1 baseline.
- Double-buffered S SLM (83.8 vs 84.1 TFLOPS — neutral): Cross-iteration pipeline with 2 S buffers (S_A + S_B, 32KB each). Pipeline: QK[k+1] → softmax[k+1] → S_store[k+1]→buf_new → compensation[k] → VS[k]→buf_old. Why it failed:
- barrier_A (full barrier for softmax max reduction) already synchronizes ALL SLM writes including S — the named barrier for S sync was already free (never blocks)
- Q SLM reduced from 16→15 d_blks (SLM budget: 60KB Q + 32KB S_A + 32KB S_B + 4KB shared = 128KB max) → 16th d_blk loaded from global (extra LSC sends)
- Extra registers:
delta_prev, local_sum_prev carried across iterations
- Code bloat from prologue/epilogue (3-phase structure duplicates compensation+VS)
- Key lesson: The split barrier B arrive/wait window (S writes → V loads + K prefetch + compensation → VS reads) already hides S synchronization latency. Double-buffering removes a barrier that was never blocking.
- S_VNNI + transposed accumulator (abandoned at build): Swap DPAS operands so S_vnni is src1 and V is src2, eliminating the S register transpose entirely. Accumulator becomes [8D×16Q] instead of [8Q×16D], requiring one-time output transpose. Build constraints discovered:
lsc_load_2d fp16 transpose: max width=4 (need 2 loads per 8D tile → 8 loads/kv_blk vs 2)
lsc_store_2d fp16: max height=8 (can't do height=16 for transposed output store)
- These are hardware/ESIMD API constraints, not software limitations
Remaining Gap vs oneDNN (90+ TFLOPS)
- DPAS density: oneDNN achieves 4 consecutive QK DPAS and 8 consecutive VS DPAS with
{Atomic} tags. Ours has 2 consecutive DPAS with intervening sends/movs.
- V from global: oneDNN loads V via 2D block load from global memory. Ours loads V cooperatively to SLM first.
- S transpose is now solved: ~270 register transpose movs eliminated via lsc_slm_scatter. Gap narrowed from ~6 to ~3 TFLOPS vs oneDNN.
Architecture: oneDNN vs Ours
| Aspect |
oneDNN SDPA |
Our rev256_onednn_v2 |
| Threads/WG |
32 (8×4) |
32 (8×4) |
| DPAS/iter |
144 (48 QK + 96 VS) |
128 (64 QK + 64 VS) |
| QK DPAS density |
4 consecutive |
4 consecutive (pipelined K) |
| VS DPAS density |
8 consecutive |
8 consecutive |
| Q storage |
SLM (shared by all SGs) |
SLM (shared by all SGs) |
| K access |
2D block load |
2D block load (pipelined) |
| V access |
2D block load from global |
2D block load with VNNI |
| S storage |
SLM (VNNI packed) |
SLM (row-major fp16, via lsc_slm_scatter) |
| Softmax |
Cross-SG atomic max |
Cross-SG SLM max + barrier |
| S sync |
Split barrier (arrive/wait) |
Named barrier (arrive/wait) |
| Peak measured |
~90+ TFLOPS |
88 TFLOPS |
See analysis/onednn_vs_ours_analysis.md and analysis/kernel_redesign_plan.md for full WG redesign plan.
Assets (ready to compile)
icpx <file>.cpp -o <file>.exe -fsycl -fsycl-targets=spir64_gen -Xs "-device bmg -options -doubleGRF" -O2 -w
| Asset |
Purpose |
Perf |
assets/rev256_onednn_v2_88tflops_s_scatter.h |
Current best — lsc_slm_scatter S transpose elimination. 32-thread WG, pipelined K, split barrier B, all Phase 1-4 optimizations. Primary code reference. |
88 TFLOPS (65%) |
assets/rev256_onednn_v2_88tflops_s_scatter.cpp |
Test harness for 88T — correctness (9 tests) + 3 perf benchmarks. Reproducible. |
run directly |
assets/rev256_onednn_v2_86tflops_qk_pipeline.h |
Pre-scatter — pipelined K loads, 32-thread WG, split barrier B. Diff with 88T to see exactly what s_scatter changed. |
86 TFLOPS (64%) |
assets/rev256_onednn_v2_86tflops_qk_pipeline.cpp |
Test harness for 86T — reproducible perf benchmark. |
run directly |
assets/rev256_onednn_v2_84tflops_pipelined_qk.h |
Pre-pipeline — 32-thread WG with pipelined K loads. Diff with 86T to see K pipeline refinements. |
84 TFLOPS (62%) |
assets/rev256_onednn_v2_84tflops_pipelined_qk.cpp |
Test harness for 84T — reproducible perf benchmark. |
run directly |
assets/rev256_onednn_v2_83tflops_mad_fusion.h |
Pre-32-thread — 16-thread WG with mad fusion, simd_inv, fp16 transpose. Shows the Phase 2 architecture before WG expansion. |
83 TFLOPS (61%) |
assets/rev256_onednn_v2_83tflops_mad_fusion.cpp |
Mini benchmark for 83T — perf-only test harness. |
run directly |
assets/rev256_onednn_v2_82tflops_fp16_transpose.h |
fp16 transpose breakthrough — the single optimization that gave +14.7%. Compare with 83T to see simd_inv + mad fusion changes. |
82 TFLOPS (61%) |
assets/test_rev256_onednn_v2.cpp |
Full test harness — 9 correctness configs (GQA+MHA, multiple ranges/seeds) + 3 perf benchmarks (8K-16K). |
run directly |
assets/flash.attn.b.mha256.fp16.opt.h |
Baseline kernel — original HD=256 opt kernel (49 TFLOPS). Non-S^T architecture for contrast. |
49 TFLOPS (36%) |
Key code patterns to study (diff across versions):
- 88T vs 86T: lsc_slm_scatter S transpose elimination (the only diff — S write section)
- 86T vs 84T: Pipelined K loads refinement, split barrier B optimization
- 84T vs 83T: 16→32 thread WG expansion, K load pipelining
- 83T vs 82T: simd_inv (SIMD16 reciprocal) + mad fusion (fused multiply-add)
- 82T: The fp16 transpose breakthrough — separate F32→FP16 from strided gather
Workflow
- Edit kernel in
rev256_onednn_v2.h following rules above
- Build with
do_build_full.ps1 (correctness+perf) or do_build.ps1 (perf only)
- Run exe — check 9/9 correctness PASS, then check TFLOPS at 8K×8K, 16K×16K
- If improvement: backup to
backup/rev256_onednn_v2_<TFLOPS>tflops_<technique>.{h,cpp,exe}
- If regression: restore from latest backup in
backup/
- ISA analysis: build with
-save-temps, disassemble with ocloc, compare mov/DPAS counts
Key Files
| File |
Purpose |
rev256_onednn_v2_s_scatter.h |
Current best HD=256 kernel (88 TFLOPS, s_scatter) |
test_rev256_onednn_v2.cpp |
Full test harness (correctness + perf benchmark) |
rev256_onednn_v2_83tflops_mad_fusion.cpp |
Mini benchmark (perf only, baseline comparison) |
flash.attn.b.mha256.fp16.opt.h |
HD=256 baseline kernel for perf comparison |
do_build_full.ps1 |
PowerShell build script (full test) |
do_build.ps1 |
PowerShell build script (mini/perf only) |
do_isa_new.ps1 |
PowerShell ISA dump + disassemble script |
analysis/onednn_vs_ours_analysis.md |
Detailed architecture comparison with oneDNN |
analysis/kernel_redesign_plan.md |
WG redesign plan (8×4 = 32 threads) |
analysis/oneDNN_16kx16k.csv |
oneDNN ISA dump for reference |
Backup Files
| Backup |
TFLOPS |
Description |
rev256_onednn_v2_vnni_64tflops.h |
64 |
Initial VNNI approach |
rev256_onednn_v2_xprefetch_68tflops.h |
68 |
Extended prefetch experiment |
rev256_onednn_v2_deferred_comp_70tflops.h |
70 |
Deferred compensation baseline |
rev256_onednn_v2_70tflops_best.h |
70 |
Cross-phase prefetch optimized |
rev256_onednn_v2_71tflops_nbarrier.h |
71 |
Named barriers |
rev256_onednn_v2_72tflops_best.h |
72 |
Best pre-transpose-opt config |
rev256_onednn_v2_72tflops_s_preload.h |
72 |
S-preload experiment |
rev256_onednn_v2_82tflops_fp16_transpose.h |
82 |
F32→FP16 before transpose (breakthrough) |
rev256_onednn_v2_83tflops_simd_inv.h |
82.5 |
SIMD16 reciprocal |
rev256_onednn_v2_83tflops_mad_fusion.h |
82.7 |
mad fusion |
rev256_onednn_v2_84tflops_pipelined_qk.h |
84 |
32-thread WG, pipelined K loads, split barrier B |
rev256_onednn_v2_88tflops_s_scatter.h |
88 |
lsc_slm_scatter S transpose elimination (current best) |
rev256_onednn_v2_double_buffer.h |
83.8 |
Double-buffered S cross-iteration pipeline (reverted) |
Related Skills
| Skill |
When to use |
xe2-nbarrier-pipelining |
Named barrier arrive/wait patterns for pipelining independent GPU operations |
xe2-sdp-kernels |
HD=128 flash attention — different architecture, higher roofline% |
xe2-sdp-bf16 |
BF16 variants — bf16 ALU limits, V conversion interleaving |
xe2-gtpin-profiling |
GTPin setup and ISA analysis methodology |
intel-gpu-vtune-profiling |
VTune GPU Compute/Media Hotspots — capture and analyze HW metrics (XMX%, stalls, L3, occupancy) |
intel-gpu-kernel-opt |
General Xe2 optimization patterns |
sycl-esimd-build |
Compilation flags, doubleGRF, spill detection |
xe2-dpas-patterns |
DPAS tiling, VNNI packing, systolic pipeline |
Reference Files
| File |
Contents |
references/optimization-history.md |
Full chronological journey from 64→88 TFLOPS with root cause analysis |
references/vtune-analysis.md |
VTune GPU hotspot analysis across all 13 versions — HW metrics, stall breakdown, phase-by-phase explanation |
references/vtune-summary.csv |
Raw VTune metrics CSV (13 rows × 30 columns) for programmatic analysis |
assets/rev256_onednn_v2_88tflops_s_scatter.h |
Current best kernel (88T) — lsc_slm_scatter S transpose elimination |
assets/rev256_onednn_v2_88tflops_s_scatter.cpp |
Test harness for 88T — reproducible |
assets/rev256_onednn_v2_86tflops_qk_pipeline.h |
Pre-scatter kernel (86T) — pipelined K, split barrier B |
assets/rev256_onednn_v2_86tflops_qk_pipeline.cpp |
Test harness for 86T — reproducible |
assets/rev256_onednn_v2_84tflops_pipelined_qk.h |
Pipelined QK kernel (84T) — 32-thread WG |
assets/rev256_onednn_v2_84tflops_pipelined_qk.cpp |
Test harness for 84T — reproducible |
assets/rev256_onednn_v2_83tflops_mad_fusion.h |
MAD fusion kernel (83T) — pre-32-thread |
assets/rev256_onednn_v2_83tflops_mad_fusion.cpp |
Mini benchmark for 83T |
assets/rev256_onednn_v2_82tflops_fp16_transpose.h |
fp16 transpose breakthrough (82T) |
assets/test_rev256_onednn_v2.cpp |
Full correctness + perf test harness |
assets/flash.attn.b.mha256.fp16.opt.h |
Baseline kernel (49T) for comparison |
1---2name: xe2-sdp-hd2563description: Use this skill when writing, optimizing, benchmarking, or debugging Flash Attention SDP kernels with head dimension 256 (HD=256) targeting Intel Xe2 (Lunar Lake/LNL, Battlemage/BMG) GPU using SYCL ESIMD. Xe2 is the GPU architecture; LNL and BMG are product names. Covers the S^T (transposed scores) architecture, oneDNN-inspired v2 kernel design, GQA support, softmax optimization, lsc_slm_scatter S transpose elimination, ISA-level analysis, and the complete optimization journey from 64 to 88 TFLOPS. Use whenever the user mentions HD=256 SDP, head_dim=256 attention, rev256, onednn_v2 kernel, S transpose, s_scatter, s_gather, lsc_slm_scatter, lsc_slm_gather, or large head dimension flash attention on Intel GPU.4---56# Xe2 (Lunar Lake/LNL, Battlemage/BMG) ESIMD Flash Attention — HD=256 (S^T Architecture)78Specialized knowledge for GQA flash attention with head_dim=256 on Intel Xe2 (Lunar Lake/LNL, Battlemage/BMG).9Current best: **88 TFLOPS** (64.9% of 135 TFLOPS peak) at 16Kx16K, 32Q/2KV heads.10Reference files hold detailed history; this file holds critical rules and architecture.1112---1314## Quick-Reference Rules (must follow every time)1516### HD=256 Architecture Differences (vs HD=128)17- **S^T approach**: QK GEMM produces scores in transposed layout `[KV_PER_SG=16][Q_ROWS*Q_GRPS=32]` per thread. This is because K is loaded in reversed/transposed format for DPAS efficiency.18- **Register transpose required**: Before VS GEMM, S must be transposed from `[KV×Q]` to `[Q×KV]` and written to SLM as fp16. This is the **#1 overhead source** (~20% of kernel time).19- **Larger SLM footprint**: Q_SLM=64KB + S_SLM=32KB + MAX_SLM=4KB + SUM_SLM=4KB = ~104KB (vs ~16-32KB for HD=128).20- **32 threads/WG** (8 sg_i × 4 sg_j) — matches oneDNN decomposition. doubleGRF required.2122### Kernel Constants23```cpp24HD = 256; // Head dimension25WG_Q_ROWS = 128; // Q rows processed per WG26KV_CHUNK = 128; // KV rows per outer iteration (8 sg_i × 16 KV_PER_SG)27KV_PER_SG = 16; // KV rows per subgroup per iteration28Q_ROWS = 8; // Q rows per subgroup tile29Q_PAIRS = 2; // Q pairs per sg_j (2 × 16Q = 32Q per sg_j)30Q_GRPS = 4; // Q groups per subgroup (4 × 8 = 32 Q rows per SG)31D_BLKS_PER_SG = 2; // D blocks per SG (2 × 16D = 32D per sg_i)32```3334### Thread Mapping35```cpp36sg_i = tid & 7; // 0-7: KV/D dimension37sg_j = tid >> 3; // 0-3: Q dimension38// sg_i handles: KV rows [sg_i*16 : sg_i*16+16], D cols [sg_i*32 : sg_i*32+32] (VS phase)39// sg_j handles: Q rows [sg_j*32 : sg_j*32+32] (4 groups × 8 rows)40```4142### SLM Layout43```440x00000 - 0x0FFFF: Q_SLM (64 KB) — Q tile [128 Q × 256 D] fp16450x10000 - 0x17FFF: S_SLM (32 KB) — Scores [128 Q × 128 KV] fp16 (after transpose)460x18000 - 0x18FFF: MAX_SLM (4 KB) — Cross-SG max reduction470x19000 - 0x19FFF: SUM_SLM (4 KB) — Cross-SG sum reduction48```4950### Build Commands51```powershell52# Full build (correctness + perf benchmark):53icpx test_rev256_onednn_v2.cpp -o test_rev256_onednn_v2.exe `54 -fsycl -fsycl-targets=spir64_gen `55 -Xs "-device bmg -options -doubleGRF" -O2 -w5657# Mini build (perf only, faster compile):58icpx rev256_onednn_v2_83tflops_mad_fusion.cpp -o test.exe `59 -fsycl -fsycl-targets=spir64_gen `60 -Xs "-device bmg -options -doubleGRF" -O2 -w6162# ISA dump build:63icpx test_rev256_onednn_v2.cpp -o test.exe `64 -fsycl -fsycl-targets=spir64_gen `65 -Xs "-device bmg -options -doubleGRF" -O2 -w -save-temps66# Then: ocloc disasm -file <spv> -device bmg -dump dump_v2_new67```6869### S Transpose: Solved via lsc_slm_scatter70The register transpose from `[KV=16][Q=16]` fp32 → `[Q=8][KV=16]` fp16 row-major used to generate ~270 `mov (4|M0)` instructions in ISA. **Now eliminated** using `lsc_slm_scatter<uint32_t, 2, u32, 16>`:7172**Working approach (Approach 2 — s_scatter, +1.7 TFLOPS):**731. Pack adjacent KV rows into u32 via shift+OR: `packed = low_kv | (high_kv << 16)`742. `lsc_slm_scatter<uint32_t, 2, u32, 16>` writes 2 u32 per Q address → 4 KV values per call753. 4 scatter calls per qp (kv groups 0..3, 4..7, 8..11, 12..15), 8 total per thread764. SLM layout after scatter is Q-major KV-fast — same as baseline after register transpose775. Block read unchanged (slm_block_load)7879**CRITICAL: Element-major data layout.** `lsc_slm_scatter` with NElts > 1 expects element-major layout:80```cpp81// CORRECT: data[0..15] = elem0 for all addrs, data[16..31] = elem1 for all addrs82data.select<16, 1>(0) = packed0;83data.select<16, 1>(16) = packed1;8485// WRONG: address-major interleave (produces ~0.12 rel_rms systematic error)86data.select<16, 2>(0) = packed0; // DO NOT USE87data.select<16, 2>(1) = packed1; // DO NOT USE88```8990**Failed alternatives:**91- Approach 1 (s_gather — block_store + gather read): **73 TFLOPS (-12.9)** — SLM gathers too expensive92- SLM scatter NElts=1 (64 D16 messages): **69 TFLOPS** — scatter bandwidth too low with single-element writes93- VNNI interleave (stride-2 uint32 pairs + 8×8 transpose): **neutral** (82.7 TFLOPS) — compiler decomposed stride-8 uint32 gather into even more mov(4|M0)9495### Performance Testing96- **Correctness**: 9 test configs (128×128 to 1K×1K, GQA + MHA, multiple data ranges)97- **Benchmark sizes**: 8K×8K, 8K×16K, 16K×16K (compute-bound regime)98- **Warmup**: 5 iterations, then 100 timed iterations99- **FLOPS formula**: `4 * q_len * kv_len * HD * num_heads_q + 2 * q_len * kv_len * num_heads_q`100- **Peak**: 135 TFLOPS (BMG Xe2 XMX fp16)101- **Correctness threshold**: max_diff < 0.1, check NaN count first102103### Backup Convention104All backups in `SDP/backup/` with naming: `rev256_onednn_v2_<TFLOPS>tflops_<technique>.{h,cpp,exe}`105106---107108## Performance Ladder (current)109110| Version | TFLOPS | Roofline% | Technique | Useful? |111|---------|--------|-----------|-----------|---------|112| vnni | 64 | 47% | VNNI-based QK approach | NO — slower than reversed DPAS |113| xprefetch | 68 | 50% | Extended prefetch hints | NO — marginal gain, added complexity |114| deferred_comp_70 | 70 | 52% | Deferred compensation, cross-phase prefetch | YES — foundation for all later work |115| nbarrier_71 | 71 | 53% | Named barriers instead of regular barriers | YES — small but free gain |116| best_72 | 72 | 53% | Optimal baseline configuration | YES — stable reference point |117| s_preload_72 | 72 | 53% | S-preload in VS phase | NEUTRAL — same perf, more code complexity |118| **fp16_transpose_82** | **82** | **61%** | Separate F32→FP16 from strided gather | **YES** — major breakthrough (+14%) |119| simd_inv_83 | 82.5 | 61% | SIMD16 reciprocal instead of scalar div | YES — small gain |120| mad_fusion_83 | 82.7 | 61% | fp32_sum mad fusion (mul+add → mad) | YES — incremental gain |121| pipelined_qk_84 | 84 | 62% | 32-thread WG (8×4), pipelined K loads, split barrier B | YES — previous best |122| **s_scatter_88** | **88** | **65%** | Eliminate S register transpose via lsc_slm_scatter<u32,2> | **YES** — current best |123| s_gather (reverted) | 73 | 54% | block_store + lsc_slm_gather<u32,4> read | **NO** — gather reads too expensive (-12.9 TFLOPS) |124| SLM scatter NElts=1 (reverted) | 69 | 51% | Replace transpose with 64 SLM scatter D16 ops | **NO** — major regression, scatter BW too low |125| VNNI interleave (reverted) | 82.7 | 61% | Stride-2 interleave + 8×8 uint32 transpose | **NO** — neutral, compiler generated more movs |126| Early V loads (reverted) | 82.7 | 61% | V loads before S transpose | **NO** — increased register live range hurt scheduling |127| Double-buffered S (reverted) | 83.8 | 62% | Cross-iteration pipeline with 2 S buffers | **NO** — neutral, barrier_A already syncs S writes (see below) |128| S_VNNI + transposed acc (abandoned) | — | — | Eliminate S transpose via DPAS operand swap | **NO** — hw constraints: fp16 transpose max width=4, store height max=8 |129130---131132## Key Optimization Insights133134### What Worked1351. **lsc_slm_scatter S transpose elimination** (+1.7 TFLOPS, 84→88): Replace ~270 register transpose `mov` instructions with `lsc_slm_scatter<uint32_t, 2, u32, 16>`. Pack adjacent KV rows into u32 via shift+OR, scatter directly to transposed SLM positions. 8 scatter calls per thread (2 qp × 4 kv_groups). Key discovery: NElts > 1 data must be in **element-major** layout.1362. **Separate F32→FP16 conversion from strided gather** (+14%): Convert entire ST_tile to fp16 first, then do strided gather on fp16 data. Halves the register traffic for the transpose.1373. **mad instruction fusion**: `fp32_sum = fp32_sum * delta + local_sum` generates SIMD32 `mad` instruction with 2× throughput on BMG XVE.1384. **SIMD16 reciprocal**: `simd<float,16> inv = 1.0f / sum` instead of scalar `1.0f / sum[i]` in a loop.1395. **Split barrier B (arrive/wait)**: Named barrier arrive after S SLM writes → V loads + K prefetch + compensation (all independent work) → named barrier wait before VS reads. The arrive/wait window spans V loads + K prefetch + deferred compensation = 100+ cycles of useful work between arrive and wait. See `xe2-nbarrier-pipelining` skill for the pattern.1406. **Deferred compensation**: Apply `A_tile *= delta` before VS GEMM, not interleaved inside VS loop — cleaner DPAS pipeline.1417. **Cross-phase prefetch**: Prefetch next K tile during VS phase, prefetch V during QK phase.1428. **32-thread WG + pipelined K loads** (+1.3 TFLOPS): Expanded from 8×2=16 to 8×4=32 threads matching oneDNN decomposition. K loads pipelined: load K[d+1] after DPAS[d], hiding K load latency behind compute.143144### What Didn't Work1451. **SLM gather S read (Approach 1)**: `slm_block_store` of un-transposed data + `lsc_slm_gather<uint32_t, 4, u32, 16>` to read transposed. 73 TFLOPS (-12.9 from baseline). SLM gather reads are much more expensive than block loads — 4 gathers per S tile vs 1 block load each, and each gather has higher message overhead.1462. **SLM scatter NElts=1**: 64 `lsc_slm_scatter<u16, 1, u16, 8>` messages (one per KV × qh). 69 TFLOPS. Per-element scatter bandwidth is severely limited.1473. **VNNI interleave transpose**: Pairing adjacent KV into uint32 VNNI pairs with stride-2 dest writes — Xe2 compiler decomposes `select<8,8>()` stride-8 uint32 gather into `mov(4|M0)`, producing even more instructions.1483. **Extended prefetch (xprefetch)**: Adding more prefetch hints beyond the basic cross-phase pattern gave marginal improvement.1494. **S-preload optimization**: Pre-loading S tiles from SLM before VS GEMM — no measurable improvement.1505. **Early V loads**: Moving V loads before S transpose to overlap V latency with transpose ALU. Increased register live range, hurt compiler scheduling → 82.7 vs 84.1 baseline.1516. **Double-buffered S SLM** (83.8 vs 84.1 TFLOPS — neutral): Cross-iteration pipeline with 2 S buffers (S_A + S_B, 32KB each). Pipeline: QK[k+1] → softmax[k+1] → S_store[k+1]→buf_new → compensation[k] → VS[k]→buf_old. **Why it failed:**152 - barrier_A (full barrier for softmax max reduction) already synchronizes ALL SLM writes including S — the named barrier for S sync was already free (never blocks)153 - Q SLM reduced from 16→15 d_blks (SLM budget: 60KB Q + 32KB S_A + 32KB S_B + 4KB shared = 128KB max) → 16th d_blk loaded from global (extra LSC sends)154 - Extra registers: `delta_prev`, `local_sum_prev` carried across iterations155 - Code bloat from prologue/epilogue (3-phase structure duplicates compensation+VS)156 - **Key lesson**: The split barrier B arrive/wait window (S writes → V loads + K prefetch + compensation → VS reads) already hides S synchronization latency. Double-buffering removes a barrier that was never blocking.1577. **S_VNNI + transposed accumulator** (abandoned at build): Swap DPAS operands so S_vnni is src1 and V is src2, eliminating the S register transpose entirely. Accumulator becomes [8D×16Q] instead of [8Q×16D], requiring one-time output transpose. **Build constraints discovered:**158 - `lsc_load_2d` fp16 transpose: max width=4 (need 2 loads per 8D tile → 8 loads/kv_blk vs 2)159 - `lsc_store_2d` fp16: max height=8 (can't do height=16 for transposed output store)160 - These are hardware/ESIMD API constraints, not software limitations161162### Remaining Gap vs oneDNN (90+ TFLOPS)1631. **DPAS density**: oneDNN achieves 4 consecutive QK DPAS and 8 consecutive VS DPAS with `{Atomic}` tags. Ours has 2 consecutive DPAS with intervening sends/movs.1642. **V from global**: oneDNN loads V via 2D block load from global memory. Ours loads V cooperatively to SLM first.1653. **S transpose is now solved**: ~270 register transpose movs eliminated via lsc_slm_scatter. Gap narrowed from ~6 to ~3 TFLOPS vs oneDNN.166167---168169## Architecture: oneDNN vs Ours170171| Aspect | oneDNN SDPA | Our rev256_onednn_v2 |172|--------|-------------|----------------------|173| Threads/WG | 32 (8×4) | 32 (8×4) |174| DPAS/iter | 144 (48 QK + 96 VS) | 128 (64 QK + 64 VS) |175| QK DPAS density | 4 consecutive | 4 consecutive (pipelined K) |176| VS DPAS density | 8 consecutive | 8 consecutive |177| Q storage | SLM (shared by all SGs) | SLM (shared by all SGs) |178| K access | 2D block load | 2D block load (pipelined) |179| V access | 2D block load from global | 2D block load with VNNI |180| S storage | SLM (VNNI packed) | SLM (row-major fp16, via lsc_slm_scatter) |181| Softmax | Cross-SG atomic max | Cross-SG SLM max + barrier |182| S sync | Split barrier (arrive/wait) | Named barrier (arrive/wait) |183| Peak measured | ~90+ TFLOPS | 88 TFLOPS |184185See `analysis/onednn_vs_ours_analysis.md` and `analysis/kernel_redesign_plan.md` for full WG redesign plan.186187---188189## Assets (ready to compile)190191```powershell192icpx <file>.cpp -o <file>.exe -fsycl -fsycl-targets=spir64_gen -Xs "-device bmg -options -doubleGRF" -O2 -w193```194195| Asset | Purpose | Perf |196|-------|---------|------|197| `assets/rev256_onednn_v2_88tflops_s_scatter.h` | **Current best** — lsc_slm_scatter S transpose elimination. 32-thread WG, pipelined K, split barrier B, all Phase 1-4 optimizations. Primary code reference. | **88 TFLOPS (65%)** |198| `assets/rev256_onednn_v2_88tflops_s_scatter.cpp` | **Test harness for 88T** — correctness (9 tests) + 3 perf benchmarks. Reproducible. | run directly |199| `assets/rev256_onednn_v2_86tflops_qk_pipeline.h` | **Pre-scatter** — pipelined K loads, 32-thread WG, split barrier B. Diff with 88T to see exactly what s_scatter changed. | 86 TFLOPS (64%) |200| `assets/rev256_onednn_v2_86tflops_qk_pipeline.cpp` | **Test harness for 86T** — reproducible perf benchmark. | run directly |201| `assets/rev256_onednn_v2_84tflops_pipelined_qk.h` | **Pre-pipeline** — 32-thread WG with pipelined K loads. Diff with 86T to see K pipeline refinements. | 84 TFLOPS (62%) |202| `assets/rev256_onednn_v2_84tflops_pipelined_qk.cpp` | **Test harness for 84T** — reproducible perf benchmark. | run directly |203| `assets/rev256_onednn_v2_83tflops_mad_fusion.h` | **Pre-32-thread** — 16-thread WG with mad fusion, simd_inv, fp16 transpose. Shows the Phase 2 architecture before WG expansion. | 83 TFLOPS (61%) |204| `assets/rev256_onednn_v2_83tflops_mad_fusion.cpp` | **Mini benchmark for 83T** — perf-only test harness. | run directly |205| `assets/rev256_onednn_v2_82tflops_fp16_transpose.h` | **fp16 transpose breakthrough** — the single optimization that gave +14.7%. Compare with 83T to see simd_inv + mad fusion changes. | 82 TFLOPS (61%) |206| `assets/test_rev256_onednn_v2.cpp` | **Full test harness** — 9 correctness configs (GQA+MHA, multiple ranges/seeds) + 3 perf benchmarks (8K-16K). | run directly |207| `assets/flash.attn.b.mha256.fp16.opt.h` | **Baseline kernel** — original HD=256 opt kernel (49 TFLOPS). Non-S^T architecture for contrast. | 49 TFLOPS (36%) |208209**Key code patterns to study (diff across versions):**210- **88T vs 86T**: lsc_slm_scatter S transpose elimination (the only diff — S write section)211- **86T vs 84T**: Pipelined K loads refinement, split barrier B optimization212- **84T vs 83T**: 16→32 thread WG expansion, K load pipelining213- **83T vs 82T**: simd_inv (SIMD16 reciprocal) + mad fusion (fused multiply-add)214- **82T**: The fp16 transpose breakthrough — separate F32→FP16 from strided gather215216---217218## Workflow2192201. **Edit kernel** in `rev256_onednn_v2.h` following rules above2212. **Build** with `do_build_full.ps1` (correctness+perf) or `do_build.ps1` (perf only)2223. **Run exe** — check 9/9 correctness PASS, then check TFLOPS at 8K×8K, 16K×16K2234. **If improvement**: backup to `backup/rev256_onednn_v2_<TFLOPS>tflops_<technique>.{h,cpp,exe}`2245. **If regression**: restore from latest backup in `backup/`2256. **ISA analysis**: build with `-save-temps`, disassemble with `ocloc`, compare mov/DPAS counts226227---228229## Key Files230231| File | Purpose |232|------|---------|233| `rev256_onednn_v2_s_scatter.h` | Current best HD=256 kernel (88 TFLOPS, s_scatter) |234| `test_rev256_onednn_v2.cpp` | Full test harness (correctness + perf benchmark) |235| `rev256_onednn_v2_83tflops_mad_fusion.cpp` | Mini benchmark (perf only, baseline comparison) |236| `flash.attn.b.mha256.fp16.opt.h` | HD=256 baseline kernel for perf comparison |237| `do_build_full.ps1` | PowerShell build script (full test) |238| `do_build.ps1` | PowerShell build script (mini/perf only) |239| `do_isa_new.ps1` | PowerShell ISA dump + disassemble script |240| `analysis/onednn_vs_ours_analysis.md` | Detailed architecture comparison with oneDNN |241| `analysis/kernel_redesign_plan.md` | WG redesign plan (8×4 = 32 threads) |242| `analysis/oneDNN_16kx16k.csv` | oneDNN ISA dump for reference |243244---245246## Backup Files247248| Backup | TFLOPS | Description |249|--------|--------|-------------|250| `rev256_onednn_v2_vnni_64tflops.h` | 64 | Initial VNNI approach |251| `rev256_onednn_v2_xprefetch_68tflops.h` | 68 | Extended prefetch experiment |252| `rev256_onednn_v2_deferred_comp_70tflops.h` | 70 | Deferred compensation baseline |253| `rev256_onednn_v2_70tflops_best.h` | 70 | Cross-phase prefetch optimized |254| `rev256_onednn_v2_71tflops_nbarrier.h` | 71 | Named barriers |255| `rev256_onednn_v2_72tflops_best.h` | 72 | Best pre-transpose-opt config |256| `rev256_onednn_v2_72tflops_s_preload.h` | 72 | S-preload experiment |257| `rev256_onednn_v2_82tflops_fp16_transpose.h` | 82 | F32→FP16 before transpose (breakthrough) |258| `rev256_onednn_v2_83tflops_simd_inv.h` | 82.5 | SIMD16 reciprocal |259| `rev256_onednn_v2_83tflops_mad_fusion.h` | 82.7 | mad fusion |260| `rev256_onednn_v2_84tflops_pipelined_qk.h` | 84 | 32-thread WG, pipelined K loads, split barrier B |261| **`rev256_onednn_v2_88tflops_s_scatter.h`** | **88** | **lsc_slm_scatter S transpose elimination (current best)** |262| `rev256_onednn_v2_double_buffer.h` | 83.8 | Double-buffered S cross-iteration pipeline (reverted) |263264---265266## Related Skills267268| Skill | When to use |269|-------|------------|270| `xe2-nbarrier-pipelining` | Named barrier arrive/wait patterns for pipelining independent GPU operations |271| `xe2-sdp-kernels` | HD=128 flash attention — different architecture, higher roofline% |272| `xe2-sdp-bf16` | BF16 variants — bf16 ALU limits, V conversion interleaving |273| `xe2-gtpin-profiling` | GTPin setup and ISA analysis methodology |274| `intel-gpu-vtune-profiling` | VTune GPU Compute/Media Hotspots — capture and analyze HW metrics (XMX%, stalls, L3, occupancy) |275| `intel-gpu-kernel-opt` | General Xe2 optimization patterns |276| `sycl-esimd-build` | Compilation flags, doubleGRF, spill detection |277| `xe2-dpas-patterns` | DPAS tiling, VNNI packing, systolic pipeline |278279---280281## Reference Files282283| File | Contents |284|------|----------|285| `references/optimization-history.md` | Full chronological journey from 64→88 TFLOPS with root cause analysis |286| `references/vtune-analysis.md` | VTune GPU hotspot analysis across all 13 versions — HW metrics, stall breakdown, phase-by-phase explanation |287| `references/vtune-summary.csv` | Raw VTune metrics CSV (13 rows × 30 columns) for programmatic analysis |288| `assets/rev256_onednn_v2_88tflops_s_scatter.h` | **Current best** kernel (88T) — lsc_slm_scatter S transpose elimination |289| `assets/rev256_onednn_v2_88tflops_s_scatter.cpp` | Test harness for 88T — reproducible |290| `assets/rev256_onednn_v2_86tflops_qk_pipeline.h` | Pre-scatter kernel (86T) — pipelined K, split barrier B |291| `assets/rev256_onednn_v2_86tflops_qk_pipeline.cpp` | Test harness for 86T — reproducible |292| `assets/rev256_onednn_v2_84tflops_pipelined_qk.h` | Pipelined QK kernel (84T) — 32-thread WG |293| `assets/rev256_onednn_v2_84tflops_pipelined_qk.cpp` | Test harness for 84T — reproducible |294| `assets/rev256_onednn_v2_83tflops_mad_fusion.h` | MAD fusion kernel (83T) — pre-32-thread |295| `assets/rev256_onednn_v2_83tflops_mad_fusion.cpp` | Mini benchmark for 83T |296| `assets/rev256_onednn_v2_82tflops_fp16_transpose.h` | fp16 transpose breakthrough (82T) |297| `assets/test_rev256_onednn_v2.cpp` | Full correctness + perf test harness |298| `assets/flash.attn.b.mha256.fp16.opt.h` | Baseline kernel (49T) for comparison |