char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
cc83d7b48
char/aliceai-t5-moe
1#include <array> 2#include <cstdint> 3#include <cstdio> 4#include <cstring> 5#include <string> 6#include <optional> 7#include <unordered_map> 8#include <vector> 9 10#include "fattn-onednn.hpp" 11#include "fattn-tile.hpp" 12#include "convert.hpp" 13 14// set minimum query length to treat as prefill (32) 15#define GGML_SYCL_FA_ONEDNN_MIN_Q 32 16 17bool ggml_sycl_fattn_onednn_binds_kv (const ggml_tensor * K ,const ggml_tensor * V ) { 18if (K -> type != GGML_TYPE_F16 || V -> type != GGML_TYPE_F16 ) { 19return false; 20 } 21auto bindable = [](const ggml_tensor * t ) { 22return t -> nb [0 ]== sizeof (sycl::half )&& t -> nb [1 ] %sizeof (sycl::half )== 0 && 23t -> nb [2 ] %sizeof (sycl::half )== 0 && t -> nb [3 ] %sizeof (sycl::half )== 0 ; 24 }; 25return bindable (K )&& bindable (V ); 26} 27 28bool ggml_sycl_flash_attn_ext_onednn_supported (const ggml_tensor * dst ,bool use_shape_limit ) { 29#if !GGML_SYCL_DNNL 30GGML_UNUSED (dst ); 31GGML_UNUSED (use_shape_limit ); 32return false; 33#else 34if (!g_ggml_sycl_fa_onednn ) { 35return false; 36 } 37const ggml_tensor * Q = dst -> src [0 ]; 38const ggml_tensor * K = dst -> src [1 ]; 39const ggml_tensor * V = dst -> src [2 ]; 40const ggml_tensor * mask = dst -> src [3 ]; 41const ggml_tensor * sinks = dst -> src [4 ]; 42 43// F16 KV: native SDPA at any KV length. 44// Non-F16: dequant to F16 then SDPA at prefill lengths. Only the 45// standard quantized KV cache types (Q4_0-Q8_0) and F32 are accepted 46// because their to_fp16_sycl conversion is verified. BF16 and IQ* 47// are excluded: BF16 needs a strided conversion kernel that does not 48// exist yet; IQ types are model-weight-only quants with no dequant 49// registration and are never used as KV caches. 50if (K -> type != GGML_TYPE_F16 || V -> type != GGML_TYPE_F16 ) { 51auto kt = K -> type ,vt = V -> type ; 52bool k_ok = kt == GGML_TYPE_F32 || kt == GGML_TYPE_Q4_0 || kt == GGML_TYPE_Q4_1 || 53kt == GGML_TYPE_Q5_0 || kt == GGML_TYPE_Q5_1 || kt == GGML_TYPE_Q8_0 ; 54bool v_ok = vt == GGML_TYPE_F32 || vt == GGML_TYPE_Q4_0 || vt == GGML_TYPE_Q4_1 || 55vt == GGML_TYPE_Q5_0 || vt == GGML_TYPE_Q5_1 || vt == GGML_TYPE_Q8_0 ; 56if (!k_ok || !v_ok ) { 57return false; 58 } 59if (use_shape_limit && (Q -> ne [1 ]< 32 || K -> ne [1 ]< 1024 )) { 60return false; 61 } 62for (const ggml_tensor * t : {K ,V }) { 63if (t -> type == GGML_TYPE_F16 && t -> nb [1 ] % (t -> ne [0 ]* 2 )!= 0 ) { 64return false; 65 } 66 } 67 } 68// This is the improved SPDA gate. Rather than gating Alchemist GPUs from all SPDA features, we instead target only the failing shapes. 69// If the GPU being assessed isn't in the grouping below, it has full access to all SPDA shapes. Otherwise, if it's an Alchemist GPU, we block only the shapes with head sizes that fail. 70// It is much easier to compare the device to a small list of failing cases than to define all the passing ones. 71const gpu_arch arch = ggml_sycl_info ().devices [ggml_sycl_get_device ()].hw_info .arch ; 72bool support_spda = !(arch == gpu_arch::intel_gpu_dg2_g10 || 73arch == gpu_arch::intel_gpu_dg2_g11 || 74arch == gpu_arch::intel_gpu_dg2_g12 ); 75 76if (!support_spda && K -> ne [0 ]== 64 ) { 77return false; 78 } 79// Optional KV-length ceiling (GGML_SYCL_FA_ONEDNN_MAX_KV, 0 = unlimited). Escape hatch: 80// very long sequences make the fused SDPA slow enough to risk the xe driver watchdog on 81// some stacks; past the cap we fall back to the native FA kernel instead. 82if (g_ggml_sycl_fa_onednn_max_kv > 0 && K -> ne [1 ]> g_ggml_sycl_fa_onednn_max_kv ) { 83return false; 84 } 85// gate for the following cases 86// 1. if the oneDNN graph Add node has no input --> skip 87// 2. types other than f16 need different logical_tensor declaration 88// 3. the mask must be shape [1, 1, q, seq] 89// 4. sinks: excludes attention sink (Xiao et al., 2024) that can't be modeled by oneDNN graph 90if (!mask || mask -> type != GGML_TYPE_F16 || mask -> ne [2 ]!= 1 || mask -> ne [3 ]!= 1 || sinks ) { 91return false; 92 } 93float max_bias = 0.0f ,logit_softcap = 0.0f ; 94memcpy (& max_bias , (const float * )dst -> op_params + 1 ,sizeof (float )); 95memcpy (& logit_softcap , (const float * )dst -> op_params + 2 ,sizeof (float )); 96if (max_bias != 0.0f || logit_softcap != 0.0f ) { 97return false; 98 } 99// K and V must share head_dim: the SDPA graph uses a single `d` for both. 100const int64_t d = K -> ne [0 ]; 101if (V -> ne [0 ]!= d || Q -> ne [3 ]!= 1 ) { 102return false; 103 } 104// GQA must divide evenly. 105if (K -> ne [2 ]== 0 || Q -> ne [2 ] %K -> ne [2 ]!= 0 ) { 106return false; 107 } 108// Prefill only. 109if (use_shape_limit && Q -> ne [1 ]< GGML_SYCL_FA_ONEDNN_MIN_Q ) { 110return false; 111 } 112return true; 113#endif 114} 115 116#if GGML_SYCL_DNNL 117 118#include "dnnl.hpp" 119#include "dnnl_sycl.hpp" 120#include "oneapi/dnnl/dnnl_graph.hpp" // graph API lives only under oneapi/dnnl/, not at the include root 121 122using namespace dnnl ; 123using namespace dnnl::graph ; 124 125// strided src (f16 or f32) -> contiguous f16 [ne0,ne1,ne2,ne3] (ne0 innermost). nb* are BYTE strides. 126template < typename src_t > 127static void cont_to_f16_sycl (const char * src , sycl::half * dst , 128int64_t ne0 ,int64_t ne1 ,int64_t ne2 ,int64_t ne3 , 129size_t nb1 ,size_t nb2 ,size_t nb3 , dpct::queue_ptr stream ) { 130const int64_t n = ne0 * ne1 * ne2 * ne3 ; 131stream -> parallel_for (sycl::range < 1 > (n ), [= ](sycl::id < 1 > ix ) { 132const int64_t gid = ix [0 ]; 133int64_t i = gid ; 134const int64_t i0 = i %ne0 ;i /=ne0 ; 135const int64_t i1 = i %ne1 ;i /=ne1 ; 136const int64_t i2 = i %ne2 ;const int64_t i3 = i /ne2 ; 137const src_t * p = (const src_t * ) (src + i1 * nb1 + i2 * nb2 + i3 * nb3 )+ i0 ; 138dst [gid ]= (sycl::half ) (* p ); 139 }); 140} 141 142// oneDNN SDPA out (f16 contiguous [mb,H,q,d]) -> ggml dst (f32 [head_dim,H,n_tok,mb], contiguous). 143static void permute_sdpa_out_sycl (const sycl::half * out ,float * dst , 144int64_t mb ,int64_t H ,int64_t q ,int64_t d , dpct::queue_ptr stream ) { 145const int64_t n = mb * H * q * d ; 146stream -> parallel_for (sycl::range < 1 > (n ), [= ](sycl::id < 1 > ix ) { 147const int64_t gid = ix [0 ]; 148int64_t i = gid ; 149const int64_t e = i %d ;i /=d ; 150const int64_t t = i %q ;i /=q ; 151const int64_t h = i %H ;const int64_t b = i /H ; 152dst [e + h * d + t * d * H + b * d * H * q ]= (float )out [gid ]; 153 }); 154} 155 156struct sdpa_partition { 157compiled_partition cp ; 158 std::vector < logical_tensor > ins ; 159logical_tensor out ; 160size_t id_q = 0 ,id_k = 0 ,id_v = 0 ,id_scale = 0 ,id_mask = 0 ; 161bool ok = false; 162}; 163 164// Build + compile the contiguous-input GQA SDPA graph (MatMul->Divide->Add->SoftMax->MatMul), f32 out. 165// Mirrors the hardware-verified scratch/onednn_sdpa_probe.cpp build_gqa (partitions=1, sdp_primitive_kernel_t). 166static sdpa_partition build_sdpa (const engine & eng ,int H ,int Hkv ,int q ,int seq ,int d , 167const std::array < int64_t ,5 > & k_str ,const std::array < int64_t ,5 > & v_str )try { 168using ltype = logical_tensor::layout_type ; 169using dt = logical_tensor::data_type ; 170using ldims = logical_tensor::dims ; 171const dt fi = dt::f32 ,t = dt::f16 ; 172const int rep = H /Hkv ; 173const ldims q_sz = {1 ,Hkv ,rep ,q ,d },kv_sz = {1 ,Hkv ,1 ,seq ,d },s_sz = {1 ,Hkv ,rep ,q ,seq }, 174sc = {1 ,1 ,1 ,1 ,1 },msk = {1 ,1 ,1 ,q ,seq },o_sz = {1 ,Hkv ,rep ,q ,d }; 175const ldims k_st (k_str .begin (),k_str .end ()),v_st (v_str .begin (),v_str .end ()); 176int64_t id = 0 ; 177sdpa_partition E ; 178 179auto query = logical_tensor (id ++ ,t ,q_sz , ltype::strided ); 180auto key = logical_tensor (id ++ ,t ,kv_sz ,k_st ); 181auto score = logical_tensor (id ++ ,fi ,s_sz , ltype::strided ); 182auto bmm1 = op (id ++ , op::kind::MatMul ,"bmm1" ); 183bmm1 .set_attr < bool > (op::attr::transpose_b , true);// key is [.., seq, d] 184bmm1 .add_inputs ({query ,key });bmm1 .add_outputs ({score }); 185 186auto scale = logical_tensor (id ++ ,t ,sc , ltype::strided ); 187auto scaled = logical_tensor (id ++ ,fi ,s_sz , ltype::strided ); 188auto sdiv = op (id ++ , op::kind::Divide ,"scale_div" );// score / (1/kq_scale) == score * kq_scale 189sdiv .add_inputs ({score ,scale });sdiv .add_outputs ({scaled }); 190 191auto mask = logical_tensor (id ++ ,t ,msk , ltype::strided ); 192auto masked = logical_tensor (id ++ ,fi ,s_sz , ltype::strided ); 193auto madd = op (id ++ , op::kind::Add ,"mask_add" ); 194madd .add_inputs ({scaled ,mask });madd .add_outputs ({masked }); 195 196auto probs = logical_tensor (id ++ ,t ,s_sz , ltype::strided ); 197auto smax = op (id ++ , op::kind::SoftMax ,"softmax" ); 198smax .set_attr < int64_t > (op::attr::axis ,-1 ); 199smax .set_attr < std::string > (op::attr::mode ,"inf_as_zero" ); 200smax .add_inputs ({masked });smax .add_outputs ({probs }); 201 202auto value = logical_tensor (id ++ ,t ,kv_sz ,v_st ); 203// f16 output is REQUIRED to hit sdp_primitive_kernel_t (the systolic micro-kernel); an f32 output 204// falls to larger_partition_kernel_t which materializes N^2 (confirmed: scratch/onednn_sdpa_kernel_probe.cpp). 205// converted to the f32 ggml dst in the permute below. 206auto output = logical_tensor (id ++ ,t ,o_sz , ltype::strided );// f16 contiguous [mb,Hkv,rep,q,d] 207auto bmm2 = op (id ++ , op::kind::MatMul ,"bmm2" ); 208bmm2 .add_inputs ({probs ,value });bmm2 .add_outputs ({output }); 209 210 dnnl::graph::graph g (eng .get_kind ()); 211g .add_op (bmm1 );g .add_op (sdiv );g .add_op (madd );g .add_op (smax );g .add_op (bmm2 ); 212g .finalize (); 213 214auto parts = g .get_partitions (); 215if (parts .size ()!= 1 || !parts [0 ].is_supported ()) { 216GGML_LOG_WARN ("%s: oneDNN did not fuse the SDPA graph; falling back to TILE kernel\n" ,__func__ ); 217return E ;// ok stays false -> caller falls back to TILE 218 } 219E .ins = parts [0 ].get_input_ports (); 220E .out = parts [0 ].get_output_ports ()[0 ]; 221E .cp = parts [0 ].compile (E .ins , {E .out },eng ); 222E .out = E .cp .query_logical_tensor (E .out .get_id ()); 223E .id_q = query .get_id ();E .id_k = key .get_id ();E .id_v = value .get_id (); 224E .id_scale = scale .get_id ();E .id_mask = mask .get_id (); 225E .ok = true; 226return E ; 227} 228catch (const std::exception & e ) { 229// compile() can reject a stride set the partitioner never inspects; memoise the failure so the 230// fallback costs one build rather than one per call. 231GGML_LOG_WARN ("%s: oneDNN SDPA partition build failed (%s); falling back to TILE kernel\n" ,__func__ ,e .what ()); 232return {}; 233} 234 235void ggml_sycl_flash_attn_ext_onednn (ggml_backend_sycl_context & ctx ,ggml_tensor * dst )try { 236const ggml_tensor * Q = dst -> src [0 ]; 237const ggml_tensor * K = dst -> src [1 ]; 238const ggml_tensor * V = dst -> src [2 ]; 239const ggml_tensor * mask = dst -> src [3 ]; 240 241const int64_t d = K -> ne [0 ];// head_dim 242const int64_t seq = K -> ne [1 ];// n_kv 243const int64_t Hkv = K -> ne [2 ];// n_head_kv 244const int64_t H = Q -> ne [2 ];// n_head 245const int64_t q = Q -> ne [1 ];// n_tok 246const int64_t mb = Q -> ne [3 ];// batch (== 1, gated) 247 248float kq_scale = 1.0f ; 249memcpy (& kq_scale , (const float * )dst -> op_params + 0 ,sizeof (float )); 250 251 dpct::queue_ptr stream = ctx .stream (); 252 dnnl::engine eng = ctx .engine_dnnl (stream ); 253 dnnl::stream strm = ctx .stream_dnnl (stream ); 254 255const ggml_sycl_fattn_extra extra = ggml_sycl_fattn_get_extra (dst ); 256 257// Q: always f32 -- copy to dense f16. 258 std::optional < ggml_sycl_pool_alloc < sycl::half >> Qf_pool ; 259 sycl::half * Qf_ptr = (sycl::half * )extra .Q_buffer_ptr ; 260if (!Qf_ptr ) { 261Qf_pool .emplace (ctx .pool (), (size_t )H * q * d ); 262Qf_ptr = Qf_pool -> get (); 263 } 264cont_to_f16_sycl < float > ((const char * )Q -> data ,Qf_ptr ,d ,q ,H ,mb ,Q -> nb [1 ],Q -> nb [2 ],Q -> nb [3 ],stream ); 265 266// K/V: bind the f16 cache in place. llama.cpp permutes it to [token][head][dim], so its head 267// plane is strided rather than dense, which is what an explicit stride vector expresses. 268// Quantized and f32 KV still stage a dense copy -- the layout the k_str/v_str defaults describe. 269 sycl::half * K_ptr = nullptr ; 270 sycl::half * V_ptr = nullptr ; 271 std::array < int64_t ,5 > k_str {Hkv * seq * d ,seq * d ,seq * d ,d ,1 }; 272 std::array < int64_t ,5 > v_str = k_str ; 273 std::optional < ggml_sycl_pool_alloc < sycl::half >> Kf_pool ; 274 std::optional < ggml_sycl_pool_alloc < sycl::half >> Vf_pool ; 275// Helper: hand out reserved space, or fall back to the pool. 276auto stage_k = [& ](size_t n ) {if (extra .K_buffer_ptr ) {return (sycl::half * )extra .K_buffer_ptr ; } 277Kf_pool .emplace (ctx .pool (),n );return Kf_pool -> get (); }; 278auto stage_v = [& ](size_t n ) {if (extra .V_buffer_ptr ) {return (sycl::half * )extra .V_buffer_ptr ; } 279Vf_pool .emplace (ctx .pool (),n );return Vf_pool -> get (); }; 280 281auto elem_strides = [](const ggml_tensor * t ) { 282const int64_t s1 = (int64_t ) (t -> nb [1 ] /t -> nb [0 ]); 283const int64_t s2 = (int64_t ) (t -> nb [2 ] /t -> nb [0 ]); 284const int64_t s3 = (int64_t ) (t -> nb [3 ] /t -> nb [0 ]); 285// dims are {mb=1, Hkv, rep=1, seq, d}; the size-1 dims at 0 and 2 never advance an address. 286return std::array < int64_t ,5 > {s3 ,s2 ,s2 ,s1 ,1 }; 287 }; 288 289if (ggml_sycl_fattn_onednn_binds_kv (K ,V )) { 290K_ptr = (sycl::half * )K -> data ; 291V_ptr = (sycl::half * )V -> data ; 292k_str = elem_strides (K ); 293v_str = elem_strides (V ); 294 }else if (K -> type == GGML_TYPE_F16 && V -> type == GGML_TYPE_F16 ) { 295K_ptr = stage_k ((size_t )Hkv * seq * d ); 296V_ptr = stage_v ((size_t )Hkv * seq * d ); 297cont_to_f16_sycl < sycl::half > ((const char * )K -> data ,K_ptr ,d ,seq ,Hkv ,mb ,K -> nb [1 ],K -> nb [2 ],K -> nb [3 ],stream ); 298cont_to_f16_sycl < sycl::half > ((const char * )V -> data ,V_ptr ,d ,seq ,Hkv ,mb ,V -> nb [1 ],V -> nb [2 ],V -> nb [3 ],stream ); 299 }else if (ggml_is_quantized (K -> type )) { 300// Quantized K/V: dequant to dense F16 using pool, same lifetime as F16 path. 301K_ptr = stage_k ((size_t )ggml_nelements (K )); 302 { 303const char * K_data = (const char * )K -> data ; 304const bool k_non_dense = ((int64_t )K -> ne [1 ]* K -> nb [1 ]!= K -> nb [2 ])&& K -> ne [2 ]> 1 ; 305const bool k_gemma = k_non_dense && 306 ((int64_t )K -> nb [2 ]< (int64_t )K -> ne [1 ]* (int64_t )K -> nb [1 ]); 307if (ggml_is_contiguously_allocated (K )&& !k_non_dense ) { 308to_fp16_sycl_t to_fp16 = ggml_get_to_fp16_sycl (K -> type ,dst ); 309to_fp16 (K_data ,K_ptr ,ggml_nelements (K ),stream ); 310 }else { 311const size_t bs = ggml_blck_size (K -> type ); 312const size_t ts = ggml_type_size (K -> type ); 313to_fp16_nc_sycl_t to_fp16 = ggml_get_to_fp16_nc_sycl (K -> type ); 314int64_t s01 ,s02 ,s03 ; 315if (k_gemma ) { 316const int64_t blk_per_row = (int64_t )K -> ne [0 ] /bs ; 317s01 = (int64_t )Hkv * blk_per_row ; 318s02 = blk_per_row ; 319s03 = (int64_t )K -> ne [1 ]* s01 ; 320 }else { 321s01 = (int64_t )K -> nb [1 ] /ts ; 322s02 = (int64_t )K -> nb [2 ] /ts ; 323s03 = (int64_t )K -> nb [3 ] /ts ; 324 } 325to_fp16 (K_data ,K_ptr , 326K -> ne [0 ],K -> ne [1 ],K -> ne [2 ],K -> ne [3 ], 327s01 ,s02 ,s03 ,stream ); 328 } 329 } 330// Quantized V: always dequant separately. Even when K and V share 331// the same underlying allocation (V is a view of K with the same 332// data pointer), their logical values differ because the quantized 333// elements at different positions/offsets represent different K/V 334// data. Master's F16 path also never aliases K and V. 335V_ptr = stage_v ((size_t )ggml_nelements (V )); 336 { 337const char * V_data = (const char * )V -> data ; 338const bool v_non_dense = ((int64_t )V -> ne [1 ]* V -> nb [1 ]!= V -> nb [2 ])&& V -> ne [2 ]> 1 ; 339const bool v_gemma = v_non_dense && 340 ((int64_t )V -> nb [2 ]< (int64_t )V -> ne [1 ]* (int64_t )V -> nb [1 ]); 341if (ggml_is_contiguously_allocated (V )&& !v_non_dense ) { 342to_fp16_sycl_t to_fp16 = ggml_get_to_fp16_sycl (V -> type ,dst ); 343to_fp16 (V_data ,V_ptr ,ggml_nelements (V ),stream ); 344 }else { 345const size_t bs = ggml_blck_size (V -> type ); 346const size_t ts = ggml_type_size (V -> type ); 347to_fp16_nc_sycl_t to_fp16 = ggml_get_to_fp16_nc_sycl (V -> type ); 348int64_t s01 ,s02 ,s03 ; 349if (v_gemma ) { 350const int64_t blk_per_row = (int64_t )V -> ne [0 ] /bs ; 351s01 = (int64_t )V -> ne [2 ]* blk_per_row ; 352s02 = blk_per_row ; 353s03 = (int64_t )V -> ne [1 ]* s01 ; 354 }else { 355s01 = (int64_t )V -> nb [1 ] /ts ; 356s02 = (int64_t )V -> nb [2 ] /ts ; 357s03 = (int64_t )V -> nb [3 ] /ts ; 358 } 359to_fp16 (V_data ,V_ptr , 360V -> ne [0 ],V -> ne [1 ],V -> ne [2 ],V -> ne [3 ], 361s01 ,s02 ,s03 ,stream ); 362 } 363 } 364 }else { 365// F32: strided copy to dense F16 via cont_to_f16_sycl<float>. 366K_ptr = stage_k ((size_t )ggml_nelements (K )); 367cont_to_f16_sycl < float > ((const char * )K -> data ,K_ptr ,K -> ne [0 ],K -> ne [1 ],K -> ne [2 ],K -> ne [3 ], 368K -> nb [1 ],K -> nb [2 ],K -> nb [3 ],stream ); 369V_ptr = stage_v ((size_t )ggml_nelements (V )); 370cont_to_f16_sycl < float > ((const char * )V -> data ,V_ptr ,V -> ne [0 ],V -> ne [1 ],V -> ne [2 ],V -> ne [3 ], 371V -> nb [1 ],V -> nb [2 ],V -> nb [3 ],stream ); 372 } 373 374// divide-by-(1/scale) reproduces ggml's score *= kq_scale on the proven probe graph. 375// 376// The scale must not be uploaded with an async memcpy from a stack local: on the in-order 377// queue that copy waits behind the K/V staging kernels, and once those take long enough 378// (n_kv >= ~26k on B70) the host frame is recycled before the copy runs, feeding the SDPA a 379// garbage scale (output collapses to a repeated token). Write the scalar from a kernel 380// instead -- the value is captured into the command, so no host memory has to outlive the 381// call, and the enqueue stays async. 382const sycl::half scale_h = (sycl::half ) (1.0f /kq_scale ); 383 std::optional < ggml_sycl_pool_alloc < sycl::half >> scbuf ; 384 sycl::half * scale_dev = (sycl::half * )extra .scale_buffer_ptr ; 385if (!scale_dev ) { 386scbuf .emplace (ctx .pool (),1 ); 387scale_dev = scbuf -> get (); 388 } 389stream -> single_task ([= ]() {* scale_dev = scale_h ; }); 390 391// f16 contiguous SDPA out [mb,H,q,d] 392 std::optional < ggml_sycl_pool_alloc < sycl::half >> outf_pool ; 393 sycl::half * outf_ptr = (sycl::half * )extra .out_buffer_ptr ; 394if (!outf_ptr ) { 395outf_pool .emplace (ctx .pool (), (size_t )H * q * d ); 396outf_ptr = outf_pool -> get (); 397 } 398 399// compile once per (device, shape, KV strides), reuse across layers/calls. Stride 2 always 400// repeats stride 1 and stride 4 is always 1, so the key covers every entry that can differ. 401static std::unordered_map < std::string ,sdpa_partition > cache ; 402char keyb [256 ]; 403snprintf (keyb ,sizeof (keyb ),"%d:%lld:%lld:%lld:%lld:%lld:%lld:%lld:%lld:%lld:%lld:%lld" ,ggml_sycl_get_device (), 404 (long long )H , (long long )Hkv , (long long )q , (long long )seq , (long long )d , 405 (long long )k_str [0 ], (long long )k_str [1 ], (long long )k_str [3 ], 406 (long long )v_str [0 ], (long long )v_str [1 ], (long long )v_str [3 ]); 407auto it = cache .find (keyb ); 408if (it == cache .end ()) { 409it = cache .emplace (keyb ,build_sdpa (eng , (int )H , (int )Hkv , (int )q , (int )seq , (int )d ,k_str ,v_str )).first ; 410 } 411sdpa_partition & E = it -> second ; 412if (!E .ok ) { 413// oneDNN can decline a shape or a stride set that _supported() never sees; build_sdpa warns per key. 414ggml_sycl_flash_attn_ext_tile (ctx ,dst ); 415return ; 416 } 417 418auto id2ptr = [& ](size_t r )-> void * { 419if (r == E .id_q )return Qf_ptr ; 420if (r == E .id_k )return K_ptr ; 421if (r == E .id_v )return V_ptr ; 422if (r == E .id_scale )return scale_dev ; 423if (r == E .id_mask )return (void * )mask -> data ; 424return nullptr ; 425 }; 426 std::vector < tensor > ti ; 427ti .reserve (E .ins .size ()); 428for (auto & lt :E .ins ) { 429ti .emplace_back (lt ,eng ,id2ptr (lt .get_id ())); 430 } 431tensor to (E .out ,eng ,outf_ptr ); 432E .cp .execute (strm ,ti , {to }); 433 434permute_sdpa_out_sycl (outf_ptr , (float * )dst -> data ,mb ,H ,q ,d ,stream ); 435// Single device needs no sync: the dnnl stream wraps this same in-order queue, so the SDPA 436// serializes with the staging kernels before it and the permute/pool reuse after it. The 437// garbage output formerly blamed on the missing sync here was the scale use-after-return 438// fixed above. Keep the conservative wait for multi-GPU, where other devices' streams can 439// race the pool: 440if (ggml_sycl_info ().device_count > 1 ) { 441stream -> wait_and_throw (); 442 } 443} 444catch (const std::exception & e ) { 445// any oneDNN/SYCL failure is non-fatal: fall back to the existing kernel (strictly additive). 446GGML_LOG_WARN ("%s: oneDNN SDPA failed (%s); falling back to TILE kernel\n" ,__func__ ,e .what ()); 447ggml_sycl_flash_attn_ext_tile (ctx ,dst ); 448} 449 450#endif // GGML_SYCL_DNNL