Extended SYCL oneDNN SDPA to non-FP16 KV caches (Q4_0-Q8_0 and FP32)#25312
Extended SYCL oneDNN SDPA to non-FP16 KV caches (Q4_0-Q8_0 and FP32)#25312johnkarlhill wants to merge 11 commits into
Conversation
- Fix mkl_fa_normalize_head: use interleaved dst layout ((query * n_q_heads + head) * DV) matching TILE's flash_attn_combine_results. Previously used dense head-major layout which wrote head outputs to wrong addresses, corrupting attention for all models except Qwen3.6-27B (where GQA=6 heads were sparse enough to avoid visible overlap). - Remove 7 redundant stream->wait() calls — SYCL in-order queue already serializes pure SYCL kernel dependencies. Retain only the 4 MKL GEMM ↔ SYCL handshake barriers (oneMKL GEMM uses its own internal queue that does not respect SYCL in-order). - Remove unused dst_row_stride, diagnostic clutter, and dead K/V hex dump (fa_diag block in fattn-mkl.cpp). - Add MKL_FA_DISABLE=1 env var for A/B testing. - Add FA-DISP watchdog (MKL_FA_DEBUG=1) and FA-DIAG output fingerprint (MKL_FA_DIAG=1) in fattn.cpp. Tested: Gemma-4-26B, Gemma-4-31B, Qwen3.6-27B, Qwen3.6-35B-A3B Perf (B70/Battlemage, 32K, q8_0 KV): Gemma-4-26B: 1473 t/s MKL vs 746 TILE (1.97x) Qwen3.6-27B: 609 t/s MKL vs 330 TILE (1.85x) Co-Authored-By: Claude Code on DeepSeek-v4-Pro
…, document in SYCL.md Completed the following: - Rename MKL_FA_DISABLE → GGML_SYCL_ENABLE_MKL_FA (inverted: 0 to disable) - Rename MKL_FA_DEBUG → GGML_SYCL_MKL_FA_DEBUG - Rename MKL_FA_DIAG → GGML_SYCL_MKL_FA_DIAG - Replace fprintf(stderr, ...) / fflush(stderr) with GGML_LOG_INFO() macro - Document all three env vars in docs/backend/SYCL.md under Runtime - Add comment explaining MKL FA activation trigger (flash-attn + quantized KV cache + batch-size >= 1024 + n_kv >= 1024) Resolves review feedback from arthw. Again, thank you!!! Co-Authored-By: Claude Code on DeepSeek-v4-Pro
…ove dup waits, gate perf macros - Replace raw getenv() with ggml_sycl_get_env() in all 4 env-var checks (fattn.cpp: GGML_SYCL_ENABLE_MKL_FA, GGML_SYCL_MKL_FA_DEBUG, GGML_SYCL_MKL_FA_DIAG; fattn-mkl.cpp: GGML_SYCL_MKL_FA_DEBUG) - Remove duplicated stream->wait() before ev.wait_and_throw() in GEMM KQ and GEMM VKQ — ev.wait_and_throw() already waits for completion - Gate MKL_ACCUM macro behind do_print so timing accumulators are no-ops in normal operation - Remove redundant MIT/Intel copyright header from fattn-mkl.cpp - Remove unused #include <cfloat> - Expand SYCL.md MKL FA docs with step-by-step activation trigger and example llama-cli command Again, thank you!!! Co-Authored-By: Claude Code on DeepSeek-v4-Pro
Remove the quantized-only restriction on MKL activation — the MKL kernel converts any non-F16 K/V to F16 via to_fp16_sycl before GEMM, so F16 (default), BF16, and F32 caches all benefit from XMX hardware acceleration. The type restriction was an unnecessary gate. Before (F16/BF16 default cache + FA on at 32K prefill): ~356 t/s (TILE path) After: ~670 t/s (MKL path, matching quantized-cache baseline) Minimal change: two conditions removed, one comment updated in fattn.cpp. No kernel or conversion code changes — the dequant pipeline already covers all types.
Three changes: 1. Remove quantized-only restriction - MKL FA activates for all KV cache types (F16 default, BF16, F32, quantized). The MKL kernel converts non-F16 K/V via to_fp16_sycl before GEMM. 2. Rename mkl_disable -> mkl_enable to match env var (GGML_SYCL_ENABLE_MKL_FA). 3. Replace batch-size threshold with Q->ne[1] >= 32 gate. Keeps TG (Q=1) and MTP drafts (Q=3-8) on VEC path where fused kernel beats MKL launch overhead. Routes all multi-token prefill through XMX-accelerated GEMM. Production data confirms Q patterns: 1-8 TG, 32-127 cache reuse, 128+ full reprocess. At 32K F16/BF16 FA-on: 356 -> 670 t/s.
Two changes: 1. Always copy F16 K/V to dense row-major buffers before MKL GEMM. Previously F16 was read in-place with raw tensor strides. During multi-turn conversations, the accumulated KV cache had different stride properties than a fresh prefill, producing corrupted outputs. Now dense F16 gets a fast memcpy; interleaved (Gemma) gets a strided copy kernel. This matches what the quantized paths already did through to_fp16_sycl. 2. Gate MKL FA on unsupported op params (max_bias, logit_softcap, batch dim mismatch) and pathological F16 strides (nb[1] not a multiple of ne[0]*2). These conditions would previously crash inside the MKL kernel. Pathological strides (test-only) and ALiBi/softcap fall through to TILE/VEC which handle them correctly. The stride check uses modulo rather than equality, so both dense (nb1 == ne0*2) and interleaved (nb1 == H * ne0*2) pass — all real models use these layouts. Only test cases with overlapping rows (nb1=32 or nb1=75 for ne0=40) are blocked. Thanks to hmscider for the oneDNN FA PR (ggml-org#25222) which surfaced the same insight: always normalize inputs to contiguous F16 before GEMM. Co-Authored-By: Claude Code using DeepSeek-V4-Pro <noreply@anthropic.com>
|
Hi @johnkarlhill, thanks for your contribution! Per our contribution guidelines, the automated PR checker found the following issue(s) that need your attention:
Please note that maintainers reserve the right to make final decisions on PRs. If you believe there is a mistake, please comment below. |
|
Hey, my PR is still ongoing with some bugfix, and I would like to take full responsibility for it. Currently, I'm testing one of the reviewer's suggestion, which is causing some unexpected behavior I'm sorting out. Why don't you close this one and reopen separately, without merging my work to yours? I'd really like to avoid hasteful integration like this, without meticulous testing, since taking scope of each PR narrow enough is much better for scrutinizing reviews (e.g. for identifying bugs). Since my PR is about flash attention on xmx engine and kv f16, I'm fine for you to work on quantized kv speed up, independently. But please don't integrate my PR. And for future PRs, splitting the PR to quant blocks would be a better idea ( Q4, vs Q8), followed by full |
Add four-tier XMX-accelerated flash attention dispatch: 1. ONEDNN: native F16 oneDNN SDPA (from PR ggml-org#25222 by @hmscider) 2. HYBRID: dequant K/V to F16, then oneDNN SDPA (new) 3. MKL: oneMKL GEMM fallback for SDPA-incompatible shapes 4. TILE/VEC: existing scalar fallback for decode Fixes: permute formula (h/t swap), pool-alloc stream sync, MKL gate guards (mask, sinks, head_dim >= 64). Co-Authored-By: Claude Code on DeepSeek-v4-Pro
|
@hmscider — first, thank you again. Your SDPA kernel is the engine that makes this whole thing work, and nothing in this PR claims credit for it. I completely understand wanting to keep PRs scoped and your work separate. Here's what I propose: I'll remove For testing: the 32K-context On llama-perplexity: agreed — I'll run it for the HYBRID path vs TILE baseline and post the results. |
|
@SleepinDevil On F16 performance: I'm on an Arc Pro B70 as well. Quick note — Also, a quick check: does |
…hybrid - Remove fattn-onednn.cpp/hpp (reserved for PR ggml-org#25222) - Move sdpa_partition + build_sdpa() into fattn-hybrid.cpp/hpp - Strip FA-DISP diagnostics from fattn.cpp - Comment out ONEDNN enum/dispatch (uncomment when ggml-org#25222 merges) - Update gate comments for HYBRID-only dispatch order - HYBRID now ships build_sdpa() independently — no dependency on ONEDNN files Build: cmake --preset x64-windows-sycl-release -DGGML_SYCL_DNN=ON -DGGML_SYCL_F16=ON Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
e0b84bd to
9e9524e
Compare
|
@johnkarlhill Thanks for the reply back. I am very new to this and might be doing something wrong. I will very much appreciate it if you spot a silly thing I am doing below that is leading to this PR not working right on my system 😅. I built your PR with the following commands on Windows: I've built the other PR #25222 and #25025 and see my performance is around what I expected based on the writeup in those. I am just struggling with this one for some reason and now I wonder whether it is because I have both 2026.1 and 2026.0 of the Intel DLE product below 🤔. Because I installed that recently after I had previously tested #25025 and #25222. What I am testing is with this:
My BAT script to run the benchmark is as follows, I added the test-backend-ops just now to get that output: My console output is below for the above. You can see the pp @ d65k is lower compared to my benches on 25222: #25222 (comment) |
|
@SleepinDevil After running countless numbers of benchmarks, I've stopped adding the ARCH flag. I am using "cmake --preset x64-windows-sycl-release -DGGML_SYCL_F16=ON -DGGML_SYCL_DNN=ON" and found it to be almost as performant. I am running this PR as my production build so I know the performance is there, especially with 27B. I just need to clean my build directory after finishing up the MKL bugs found today... Many of those bug fixes need to be added to this PR. Give me a couple days and I'll perform a fresh build to see if the performance holds. |
|
@johnkarlhill will keep an eye out for that :). Yes, I don't know why the FA backend ops test shows failures in mine too. I also have no idea how I managed to have two versions of the oneAPI Deep Learning Essentials installed, I need to do a cleanup of my environment first I think and then try again 😅. I'll try without the arch flag too. Thanks for your efforts though, looking forward to more performance being unlocked from the Intel B70s, I cannot believe how good they are genuinely. |
|
This seems to be bugged with Alchemist based cards. I tried merging and running this on my A770 and ran the test-backend-ops and got the following outputs that failed. I know it is still a WIP but just letting you know this is the case. |
|
@simonlui It was recently discovered that the oneDNN releases right now are slightly broken for Alchemist cards. I highly suggest sticking with PR25025 for prefill performance increases as MKL does not use oneDNN. I plan on acquiring an Alchemeist card for testing in the future so, hopefully, something can be done to get the extra SPDA performance for Alchemist. |
Based on investigation in uxlfoundation/oneDNN#5510 there's a bug in latest version of the GPU driver for Alchemist that causes the failure. Using previous version works as expected. |
|
@johnkarlhill I have an unused Alchemist card I can send you if you are interested. Its a Sparkle A770-16GB card. Let me know if you are interested and we can arrange shipping. |
|
Tested this PR on Battlemage workstation hardware. The oneDNN SDPA path delivers a large prefill win with quantized KV. I initially hit GPU hangs, but after a long bisection they turned out to be unrelated to this PR: they reproduce on stock master and are caused by an interaction between the Level Zero v2 adapter and the GGML SYCL VMM pool. Details and workarounds below since anyone benching this PR with quantized KV will probably hit them. Setup
Performance (llama-bench, t/s, r=3, all with the VMM workaround below)
Prefill at depth with quantized KV is about 2.6x master and clearly worth it. Tier isolation with your env switches confirmed the gain is the HYBRID oneDNN path: 3230 t/s hybrid vs 1667 MKL vs 1330 tile at pp512 d4096. Under a sustained concurrency test (llama-server, 8 slots, 32 concurrent clients, mixed prompt lengths, 3 minutes) the PR sustains the load fine once the environment issue below is worked around: zero timeouts, overload recovery in under 100 ms, flat throughput over a soak. Environment issue you may hit while testing: quantized KV FA hangs the ccs engine (also on master) Symptoms: llama-bench with Bisection results:
So it looks like a Level Zero v2 immediate-command-list adapter issue with allocations from the GGML VMM pool (reserve/map) being touched by in-flight kernels, not a llama.cpp logic bug. I will file it against oneAPI/UR separately. Until then, anyone testing this PR with quantized KV on Battlemage probably wants Happy to rerun with patches or provide the xe devcoredump and full bisection logs. |
|
Correction to my comment above: So the failure interval moved from minutes to tens of minutes, which is consistent with a low probability race rather than a fixed allocation pattern. Treat the VMM setting as mitigation, not a fix. I am currently soaking the legacy adapter path ( |
|
Follow-up as promised: the legacy adapter path ( So the current picture for Battlemage (B70, xe, NEO 26.22.38646.6, Level Zero 1.15.38646):
The perf numbers in my first comment stand (measured between hangs, and prefill-only runs are stable). Happy to run instrumented builds, bisect driver versions, or ship the xe devcoredumps to whoever picks this up. |
|
Platform follow-up on the hangs discussed in my comments above (filed as #25692): I moved the same B70 card to a different host — Ubuntu 26.04, kernel 7.0.0-27 (newer xe), card in an external OCuLink dock (link trained Gen3 x4), NEO 26.22 — and could not reproduce any xe instability under sustained load. Three clean-boot 30-minute server soaks (different serving stack: vLLM-XPU 0.25.1 with an AWQ int4 model, continuous batching, including one run pinned at full prefill saturation for the entire 30 minutes) completed with zero engine resets and zero devcoredumps. On the original host (kernel 6.17.0-1028-oem, direct PCIe slot) this same card reproducibly reset within minutes — including under workloads unrelated to this PR. Caveats: this is not a rerun of the FA + quantized-KV config from this PR, and two variables changed at once (kernel 6.17→7.0 xe, and slot → OCuLink x4 link path), so it does not identify the failing layer. But the same silicon running heavy sustained compute cleanly on a newer kernel makes a kernel-xe/platform contribution to the reset family look more likely than a card defect. Will report back if I isolate kernel vs link path. |
That is awesome of you to suggest but I would feel horrible if if something happened to it or, if for some reason, I couldn't get the performance out of it that I'm hoping for. Thank you but I must decline. |
|
@KellenRenshaw I'm not giving up on Alchemist. I think PR25025 will give you a nice performance jump over stock but it's not merged yet. As soon as I get the funds to purchase an Alchemist card, I'll dig in to see what needs to happen. |
|
@hmscider @simonlui @SleepinDevil @KellenRenshaw @NateHag @vpirogov Closing this PR to cut a cleaner version. The original "hybrid" approach depended on #25025 (MKL GEMM), which hasnt merged yet. The new PR strips the MKL dependency entirely and feeds dequantized K/V directly into oneDNN SDPA: All feedback from this thread informed the rewrite. Thanks for the input. |
This PR extends the SYCL oneDNN SDPA implementation (#25222) to handle non-FP16 KV caches. Previously, that implementation only worked with native FP16 K/V. The existing implementation gives great XMX SDPA speedup during prefill, but users were stuck with FP16 caches and the higher VRAM overhead that comes with them.
The approach is direct: before passing the K/V matrices into the oneDNN SDPA graph, we dequantize or convert them to dense FP16 on-device. From there, the SDPA executes the same fused systolic kernel as the standard FP16 pipeline.
Supported KV Types:
Q4_0, Q4_1, Q5_0, Q5_1, Q8_0: Dequantized using to_fp16_sycl / to_fp16_nc_sycl, covering both contiguous and non-contiguous layouts (like Gemma's interleaved structure).
FP32: Handled via a strided copy to FP16 using cont_to_f16_sycl.
BF16 and IQ types are skipped for now.
Gating & Thresholds:
Non-FP16 KV: Triggers during prefill only, requiring at least 1024 KV tokens and 32 query tokens.
FP16 KV: Runs at any length, preserving the logic from #25222.
Inherits the BMG-only architecture gate for the underlying oneDNN kernel.
Synchronization Fix:
Added a critical stream->wait_and_throw() call right after the SDPA compute step, even for single-device setups. Without this explicit sync, the pool allocator reuses device buffers while the GPU is actively running the SDPA graph, leading to KV cache corruption on subsequent turns. (This bug was also caught and fixed independently by @malsbat in #25741).
Testing:
test-backend-ops: Passed 3961/3961 in the FLASH_ATTN_EXT suite.
Multi-turn coherence: Tested with Gemma 26B (q8_0 KV) and Qwen 27B (q8_0 KV) across long prompt sequences. Outputs are completely clean, and draft acceptance held steady around 90%.
Performance: Qwen 27B reached 900+ t/s peak prefill, averaged 800+ t/s at 32K context, and achieved ~20 t/s text generation with MTP.
Changes from earlier iterations:
Removed MKL dependency: The initial "hybrid" design routed dequantized K/V into oneMKL GEMM, adding a hard dependency on the unmerged #25025. Stripped out MKL entirely; the dequantized data now hooks directly into oneDNN SDPA.
Consolidated files: Removed the separate fattn-hybrid.* files and folded the logic into fattn-onednn.cpp. The oneDNN path now unifies both FP16 and non-FP16 handling under a single function and gate.
Swapped allocator: Replaced the temporary 16MB-chunk fbuf allocator with ctx.pool() for K/V dequant buffers, matching the lifetime architecture used in the pure FP16 path.
Separate dequant for K and V: Fixed an edge case where K and V shared the same quantized buffer. Reusing K's dequantized buffer for V caused SDPA to read identical data for both keys and values. They are now explicitly dequantized into separate buffers.
Cleaned up: Stripped out the FA-DISP, FA-TIME, and FA-TRACE logging macros used during development.