char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
6602dd338
char/aliceai-t5-moe
1// Flash attention via oneMKL GEMM (XMX-accelerated). 2// Uses column_major::gemm for Q*K^T and S*V matmuls 3// with an online softmax SYCL kernel. 4// 5// All GQA query heads sharing a KV head are batched into single 6// GEMM calls, amortizing MKL launch overhead across K and V reuse. 7// 8 9#include "common.hpp" 10#include "fattn-common.hpp" 11#include "fattn-buffers.hpp" 12#include "convert.hpp" 13#include "fattn.hpp" 14 15#include <oneapi/mkl.hpp> 16#include <cstdio> 17#include <chrono> 18 19#define MKL_FA_CHUNK_SIZE_KV 8192 20 21// Number of query rows processed per tile. The score buffers (KQ_f32, S_f16) 22// are sized q_tile_rows * chunk_size, so this bounds their footprint 23// regardless of batch size (n_query_rows = n_queries * gqa_ratio). A typical 24// single-ubatch prefill (e.g. ubatch 1024 * gqa 8 = 8192 rows) is exactly one 25// tile, so it runs with no extra iterations. Larger batches tile and stay 26// bounded. Override with GGML_SYCL_MKL_FA_Q_TILE. 27#define MKL_FA_Q_TILE 8192 28 29#define MKL_FA_WG_SIZE 256 30 31using oneapi::mkl::transpose ; 32using oneapi::mkl::blas::column_major::gemm ; 33 34// --------------------------------------------------------------------------- 35// Helpers 36// --------------------------------------------------------------------------- 37 38// Pack all GQA Q heads for one KV head into fp16, applying q_scale. 39// Launches one kernel per GQA group — each kernel copies exactly 40// n_queries * DKQ elements using the per-group dst offset and 41// per-head source stride. 42static void mkl_fa_pack_q_fp16 ( 43 dpct::queue_ptr stream , 44 sycl::half * __restrictdst , 45const float * __restrictq_src , 46int n_queries ,int DKQ , 47int gqa_ratio ,int kvh_base_head , 48float q_scale ,int64_t q_row_stride ,int64_t q_head_stride , 49int64_t wg_size ) { 50 51for (int iqg = 0 ;iqg < gqa_ratio ;iqg ++ ) { 52int iqh = kvh_base_head + iqg ; 53 sycl::half * dst_g = dst + (int64_t )iqg * n_queries * DKQ ; 54 55const int64_t n_elem = (int64_t )n_queries * DKQ ; 56const int64_t wg = ((n_elem + wg_size - 1 ) /wg_size )* wg_size ; 57 58stream -> submit ([& ](sycl::handler & cgh ) { 59cgh .parallel_for (sycl::nd_range < 1 > (wg ,wg_size ), 60 [= ](sycl::nd_item < 1 > item ) { 61int64_t e = item .get_global_id (0 ); 62if (e >=n_elem )return ; 63 64int64_t q = e /DKQ ; 65int64_t d = e - q * DKQ ; 66 67// Stride-aware source offset: handles permuted, 68// sliced, or contiguous Q tensor layouts. 69int64_t src_off = d 70+ q * q_row_stride 71+ (int64_t )iqh * q_head_stride ; 72 73dst_g [e ]= sycl::half ( 74q_src [src_off ]* q_scale ); 75 }); 76 }); 77 } 78} 79 80// Zero-initialize the online softmax state arrays. 81// KQ_max → -inf, KQ_sum → 0, VKQ_accum → 0. 82// Merged into one kernel to avoid per-array launch overhead. 83static void mkl_fa_init_softmax_state ( 84 dpct::queue_ptr stream , 85float * kmax ,float * ksum ,float * vacc , 86int n_query_rows ,int DV ,int64_t wg_size ) { 87 88const float neg_inf = -1e30f ; 89const int64_t n_maxsum = n_query_rows ; 90const int64_t n_vacc = (int64_t )n_query_rows * DV ; 91const int64_t total = (n_vacc > n_maxsum ) ?n_vacc :n_maxsum ; 92const int64_t wg = ((total + wg_size - 1 ) /wg_size )* wg_size ; 93 94stream -> submit ([& ](sycl::handler & cgh ) { 95cgh .parallel_for (sycl::nd_range < 1 > (wg ,wg_size ), 96 [= ](sycl::nd_item < 1 > item ) { 97int64_t i = item .get_global_id (0 ); 98if (i < n_maxsum ) { 99kmax [i ]= neg_inf ; 100ksum [i ]= 0.0f ; 101 } 102if (i < n_vacc ) { 103vacc [i ]= 0.0f ; 104 } 105 }); 106 }); 107} 108 109// Online softmax over one KV chunk for a tile of GQA query rows. 110// The tile spans absolute rows [q0, q0 + q_rows). Score buffers 111// (KQ_f32/S_f16) are indexed RELATIVE to the tile; the persistent state 112// (VKQ_accum/KQ_max/KQ_sum) and mask are indexed by ABSOLUTE row. 113// For each row: find local max → rescale previous VKQ_accum → 114// compute exp(s - max) → write S_f16 → update running max/sum. 115static void mkl_fa_online_softmax_chunk ( 116 dpct::queue_ptr stream , 117float * __restrictKQ_f32 , 118 sycl::half * __restrictS_f16 , 119float * __restrictKQ_max , 120float * __restrictKQ_sum , 121float * __restrictVKQ_accum , 122int q0 ,int q_rows ,int n_queries ,int DV , 123int chunk_size ,int chunk_start , 124int kvh_head , 125const sycl::half * mask_data ,int64_t mask_head_stride , 126int64_t mask_row_stride ,int mask_n_heads , 127float logit_softcap ,int64_t wg_size ) { 128 129const int64_t wg = ((q_rows + wg_size - 1 ) /wg_size )* wg_size ; 130 131stream -> submit ([& ](sycl::handler & cgh ) { 132cgh .parallel_for (sycl::nd_range < 1 > (wg ,wg_size ), 133 [= ](sycl::nd_item < 1 > item ) { 134int jc_rel = item .get_global_id (0 ); 135if (jc_rel >=q_rows )return ; 136int jc_abs = q0 + jc_rel ; 137 138const int gqa_group = jc_abs /n_queries ; 139const int q_row = jc_abs %n_queries ; 140 141// Score buffers are tile-local (relative index). 142const float * __restrictKQ_row = KQ_f32 143+ jc_rel * (int64_t )chunk_size ; 144// Persistent accumulator is full-sized (absolute index). 145float * __restrictvkq = VKQ_accum 146+ jc_abs * (int64_t )DV ; 147 148const sycl::half * mask_h = nullptr ; 149int64_t m_stride = 0 ; 150if (mask_data ) { 151int m_head = (mask_n_heads > 1 ) 152 ? (kvh_head + gqa_group ) :0 ; 153mask_h = mask_data + (int64_t )m_head * mask_head_stride ; 154m_stride = mask_row_stride ; 155 } 156 157// Row-wise local maximum (softcap before mask) 158float local_max = -1e30f ; 159for (int i = 0 ;i < chunk_size ;i ++ ) { 160float s = KQ_row [i ]; 161if (logit_softcap != 0.0f ) { 162s = logit_softcap * sycl::tanh (s ); 163 } 164if (mask_h ) { 165s += (float )mask_h [q_row * m_stride 166+ (chunk_start + i )]; 167 } 168if (s > local_max )local_max = s ; 169 } 170 171// Rescale previous accumulator by exp(old_max - new_max) 172float old_max = KQ_max [jc_abs ]; 173float new_max = (old_max > local_max ) ?old_max :local_max ; 174float rescale = (old_max < -1e29f ) ?1.0f 175 : sycl::native::exp (old_max - new_max ); 176 177for (int v = 0 ;v < DV ;v ++ ) { 178vkq [v ] *=rescale ; 179 } 180 181// Softmax and write S_f16 (tile-local index) 182float local_sum = 0.0f ; 183 sycl::half * __restrictS_row = S_f16 184+ jc_rel * (int64_t )chunk_size ; 185 186for (int i = 0 ;i < chunk_size ;i ++ ) { 187float s = KQ_row [i ]; 188if (logit_softcap != 0.0f ) { 189s = logit_softcap * sycl::tanh (s ); 190 } 191if (mask_h ) { 192s += (float )mask_h [q_row * m_stride 193+ (chunk_start + i )]; 194 } 195float val = sycl::native::exp (s - new_max ); 196S_row [i ]= sycl::half (val ); 197local_sum += val ; 198 } 199 200KQ_sum [jc_abs ]= KQ_sum [jc_abs ]* rescale + local_sum ; 201KQ_max [jc_abs ]= new_max ; 202 }); 203 }); 204} 205 206// Write one GQA group's normalized output to its destination head. 207static void mkl_fa_normalize_head ( 208 dpct::queue_ptr stream , 209float * __restrictdst_batch , 210const float * __restrictVKQ_accum , 211const float * __restrictKQ_sum , 212int iqh ,int n_queries ,int DV ,int n_q_heads , 213int64_t src_offset ,int64_t wg_size ) { 214 215const int64_t wg = ((n_queries + wg_size - 1 ) /wg_size )* wg_size ; 216 217stream -> submit ([& ](sycl::handler & cgh ) { 218cgh .parallel_for (sycl::nd_range < 1 > (wg ,wg_size ), 219 [= ](sycl::nd_item < 1 > item ) { 220int jc = item .get_global_id (0 ); 221if (jc >=n_queries )return ; 222 223int ksum_idx = (int )(src_offset /DV )+ jc ; 224float inv_sum = 1.0f /KQ_sum [ksum_idx ]; 225const float * __restrictsrc = VKQ_accum 226+ src_offset + jc * (int64_t )DV ; 227// Interleaved dst layout (matching TILE): 228// rows alternate between heads, then increment query. 229// offset = (query * n_q_heads + head) * DV 230float * __restrictdst_row = dst_batch 231+ ((int64_t )jc * n_q_heads + iqh )* (int64_t )DV ; 232 233for (int v = 0 ;v < DV ;v ++ ) { 234dst_row [v ]= src [v ]* inv_sum ; 235 } 236 }); 237 }); 238} 239 240// --------------------------------------------------------------------------- 241// Per-chunk dequant 242// 243// Rather than dequantizing all of K/V up front (footprint scales with 244// context), we dequant one KV-head chunk at a time into a dense 245// [this_chunk x D] fp16 buffer (row-major, lda = D). The source address of 246// element (head=ikvh, row=chunk_start+r, col=c) decomposes into independent 247// linear terms head_off(ikvh) + row_off(chunk_start) + (r,c), so slicing a 248// chunk is a clean pointer offset in every layout case. The true-Gemma- 249// interleave vs padded-seq-view distinction is resolved once when the 250// descriptor is built; slicing does not reintroduce it. 251// --------------------------------------------------------------------------- 252enum mkl_fa_kv_desc_mode { 253MKL_FA_KV_MODE_F16_DENSE = 0 , 254MKL_FA_KV_MODE_F16_INTERLEAVED = 1 , 255MKL_FA_KV_MODE_QUANT_CONTIG = 2 , 256MKL_FA_KV_MODE_QUANT_NC = 3 , 257}; 258 259struct mkl_fa_kv_desc { 260const char * data = nullptr ; 261ggml_type type = GGML_TYPE_F16 ; 262int64_t D = 0 ;// ne[0] 263int64_t nb1 = 0 ;// byte stride, seq dim 264int64_t nb2 = 0 ;// byte stride, head dim 265mkl_fa_kv_desc_mode mode = MKL_FA_KV_MODE_F16_DENSE ; 266int64_t ts = 0 ;// type size (mode 3 base offset) 267int64_t s01 = 0 ;// nc row stride in blocks (mode 3) 268int64_t s02 = 0 ;// nc head stride in blocks (mode 3) 269}; 270 271static mkl_fa_kv_desc mkl_fa_make_desc (const ggml_tensor * T ,bool interleaved ,int n_kv_heads ) { 272mkl_fa_kv_desc d ; 273d .data = (const char * )T -> data ; 274d .type = T -> type ; 275d .D = T -> ne [0 ]; 276d .nb1 = (int64_t )T -> nb [1 ]; 277d .nb2 = (int64_t )T -> nb [2 ]; 278d .ts = (int64_t )ggml_type_size (T -> type ); 279 280if (T -> type == GGML_TYPE_F16 ) { 281d .mode = interleaved ?MKL_FA_KV_MODE_F16_INTERLEAVED 282 :MKL_FA_KV_MODE_F16_DENSE ; 283 }else if (ggml_is_contiguously_allocated (T )&& !interleaved ) { 284d .mode = MKL_FA_KV_MODE_QUANT_CONTIG ; 285 }else { 286d .mode = MKL_FA_KV_MODE_QUANT_NC ; 287const int64_t bs = (int64_t )ggml_blck_size (T -> type ); 288const int64_t blk_per_row = T -> ne [0 ] /bs ; 289// True Gemma interleave packs heads within a row (nb[2] < ne[1]*nb[1]) 290// → reconstruct physical strides. Padded seq-views (nb[2] > ne[1]*nb[1]) 291// already have correct physical strides. 292const bool gemma = interleaved && 293 ((int64_t )T -> nb [2 ]< (int64_t )T -> ne [1 ]* (int64_t )T -> nb [1 ]); 294if (gemma ) { 295d .s01 = (int64_t )n_kv_heads * blk_per_row ; 296d .s02 = blk_per_row ; 297 }else { 298d .s01 = d .nb1 /d .ts ; 299d .s02 = d .nb2 /d .ts ; 300 } 301 } 302return d ; 303} 304 305// Dequant one KV-head chunk into a dense [this_chunk x D] fp16 buffer. 306static void mkl_fa_dequant_chunk ( 307 dpct::queue_ptr stream ,const mkl_fa_kv_desc & d ,ggml_tensor * dst_ctx , 308 sycl::half * out ,int ikvh ,int chunk_start ,int this_chunk ) { 309 310const int64_t D = d .D ; 311switch (d .mode ) { 312case MKL_FA_KV_MODE_F16_DENSE : { 313const char * base = d .data + (int64_t )ikvh * d .nb2 314+ (int64_t )chunk_start * d .nb1 ; 315stream -> memcpy (out ,base , (size_t )this_chunk * D * sizeof (sycl::half )); 316break ; 317 } 318case MKL_FA_KV_MODE_F16_INTERLEAVED : { 319const char * base = d .data + (int64_t )ikvh * d .nb2 320+ (int64_t )chunk_start * d .nb1 ; 321const int64_t row_halfs = d .nb1 / (int64_t )sizeof (sycl::half ); 322const sycl::half * src = (const sycl::half * )base ; 323stream -> parallel_for ( 324 sycl::range < 2 > ((size_t )this_chunk , (size_t )D ), 325 [= ](sycl::item < 2 > it ) { 326int64_t r = it .get_id (0 ); 327int64_t c = it .get_id (1 ); 328out [r * D + c ]= src [r * row_halfs + c ]; 329 }); 330break ; 331 } 332case MKL_FA_KV_MODE_QUANT_CONTIG : { 333const char * base = d .data + (int64_t )ikvh * d .nb2 334+ (int64_t )chunk_start * d .nb1 ; 335to_fp16_sycl_t to_fp16 = ggml_get_to_fp16_sycl (d .type ,dst_ctx ); 336to_fp16 (base ,out , (int64_t )this_chunk * D ,stream ); 337break ; 338 } 339default : {// MKL_FA_KV_MODE_QUANT_NC 340to_fp16_nc_sycl_t to_fp16 = ggml_get_to_fp16_nc_sycl (d .type ); 341const int64_t base_blocks = (int64_t )ikvh * d .s02 342+ (int64_t )chunk_start * d .s01 ; 343const char * base = d .data + base_blocks * d .ts ; 344// ne02 = ne03 = 1 → s02/s03 inert; head+chunk offset carried by base. 345to_fp16 (base ,out ,D ,this_chunk ,1 ,1 ,d .s01 ,d .s02 ,d .s02 ,stream ); 346break ; 347 } 348 } 349} 350 351// --------------------------------------------------------------------------- 352// MKL Flash Attention orchestrator 353// 354// Pipeline: dequantize K/V → for each KV head: 355// pack GQA Q heads → MKL GEMM KQ → online softmax → 356// MKL GEMM VKQ → accumulate → normalize → scatter to dst 357// --------------------------------------------------------------------------- 358void ggml_sycl_flash_attn_ext_mkl (ggml_backend_sycl_context & ctx ,ggml_tensor * dst ) { 359 360const ggml_tensor * Q = dst -> src [0 ]; 361const ggml_tensor * K = dst -> src [1 ]; 362const ggml_tensor * V = dst -> src [2 ]; 363const ggml_tensor * mask = dst -> src [3 ]; 364ggml_tensor * KQV = dst ; 365 366GGML_ASSERT (Q -> type == GGML_TYPE_F32 ); 367GGML_ASSERT (KQV -> type == GGML_TYPE_F32 ); 368 369// --- Op params --- 370float scale = 1.0f ,max_bias = 0.0f ,logit_softcap = 0.0f ; 371memcpy (& scale , (const float * )KQV -> op_params + 0 ,sizeof (float )); 372memcpy (& max_bias , (const float * )KQV -> op_params + 1 ,sizeof (float )); 373memcpy (& logit_softcap , (const float * )KQV -> op_params + 2 ,sizeof (float )); 374 375const float q_scale = scale ; 376 377// --- Dimensions --- 378const int DKQ = (int )K -> ne [0 ]; 379const int DV = (int )V -> ne [0 ]; 380const int n_queries = (int )Q -> ne [1 ]; 381const int n_q_heads = (int )Q -> ne [2 ]; 382const int n_kv_heads = (int )K -> ne [2 ]; 383const int n_batch = (int )Q -> ne [3 ]; 384const int n_kv = (int )K -> ne [1 ]; 385const int gqa_ratio = n_q_heads /n_kv_heads ; 386const int n_query_rows = n_queries * gqa_ratio ; 387 388GGML_ASSERT (n_q_heads %n_kv_heads == 0 ); 389GGML_ASSERT (max_bias == 0.0f );// ALiBi not supported 390GGML_ASSERT (Q -> ne [3 ]== K -> ne [3 ]|| K -> ne [3 ]== 1 ); 391 392const int chunk_size = std::min (MKL_FA_CHUNK_SIZE_KV ,n_kv ); 393 394// Query rows are processed in tiles of q_tile_rows so the score buffers 395// (KQ_f32/S_f16 = q_tile_rows * chunk_size) stay bounded regardless of 396// batch size. n_query_rows <= Q_TILE is a single tile (no extra work). 397static int q_tile_env = ggml_sycl_get_env ("GGML_SYCL_MKL_FA_Q_TILE" ,MKL_FA_Q_TILE ); 398const int q_tile_rows = std::max (1 , std::min (q_tile_env ,n_query_rows )); 399 400const int64_t wg_size = MKL_FA_WG_SIZE ; 401 402// --- Debug output (gated by GGML_SYCL_MKL_FA_DEBUG=1) --- 403static int mkl_call_count = 0 ; 404mkl_call_count ++ ; 405static int mkl_debug = ggml_sycl_get_env ("GGML_SYCL_MKL_FA_DEBUG" ,0 ); 406const bool do_print = (mkl_debug == 1 ); 407 408const int64_t q_row_stride = Q -> nb [1 ] /sizeof (float ); 409const int64_t q_head_stride = Q -> nb [2 ] /sizeof (float ); 410 411const bool V_is_K_view = V -> view_src 412&& (V -> view_src == K || (V -> view_src == K -> view_src 413&& V -> view_offs == K -> view_offs )); 414 415// Early interleaved detection for debug output. 416// True interleaved detection happens after dequant (nb12_fp16 == nb11_fp16), 417// but we can pre-detect on the original tensor strides. 418const bool k_early_interleaved = 419 ((int64_t )K -> ne [1 ]* K -> nb [1 ]!= K -> nb [2 ]); 420const bool v_early_interleaved = 421 !V_is_K_view && ((int64_t )V -> ne [1 ]* V -> nb [1 ]!= V -> nb [2 ]); 422 423if (do_print ) { 424GGML_LOG_INFO ("[MKL-FA] #%d D=%d DV=%d n_q=%d n_kv=%d " 425"n_qh=%d n_kvh=%d gqa=%d batch=%d K=%s V=%s " 426"chunk=%d buf=%.1fMB%s%s\n" , 427mkl_call_count ,DKQ ,DV ,n_queries ,n_kv , 428n_q_heads ,n_kv_heads ,gqa_ratio ,n_batch , 429ggml_type_name (K -> type ),ggml_type_name (V -> type ), 430chunk_size , 431 (double )((int64_t )n_query_rows * chunk_size * sizeof (float )) 432 / (1024.0 * 1024.0 ), 433k_early_interleaved ?" K_ILV" :"" , 434v_early_interleaved ?" V_ILV" :"" ); 435GGML_LOG_INFO ("[MKL-FA] #%d Q-nb1=%lld Q-nb2=%lld " 436"q_rs=%lld q_hs=%lld dst_rs=%lld dst_hs=%lld\n" , 437mkl_call_count , 438 (long long )Q -> nb [1 ], (long long )Q -> nb [2 ], 439 (long long )q_row_stride , (long long )q_head_stride , 440 (long long )(KQV -> nb [1 ] /sizeof (float )), 441 (long long )(KQV -> nb [2 ] /sizeof (float ))); 442 } 443 444// --- Stream and allocators --- 445 dpct::queue_ptr stream = ctx .stream (); 446 447#define MKL_TAKE_TIME (t0 ) auto t0 = std::chrono::steady_clock::now() 448#define MKL_ACCUM (acc ,t0 ) do { if (do_print) { \ 449 acc += (int64_t)std::chrono::duration_cast \ 450 <std::chrono::microseconds>(std::chrono::steady_clock::now() - (t0)).count(); \ 451} } while(0) 452 453int64_t gemm_kq_time_us = 0 ; 454int64_t gemm_vkq_time_us = 0 ; 455int64_t softmax_time_us = 0 ; 456int64_t dequant_time_us = 0 ; 457 458MKL_TAKE_TIME (t_deq ); 459 460// --- K/V dequant descriptors --- 461// Dequant is done per-chunk inside the KV loop (footprint independent of 462// context). Output is always dense row-major fp16 [this_chunk x D], lda=D. 463// Interleaved detection: ne[1]*nb[1] != nb[2] means heads are interleaved. 464const bool k_interleaved = 465 ((int64_t )K -> ne [1 ]* K -> nb [1 ]!= K -> nb [2 ])&& K -> ne [2 ]> 1 ; 466const bool v_interleaved = 467 ((int64_t )V -> ne [1 ]* V -> nb [1 ]!= V -> nb [2 ])&& V -> ne [2 ]> 1 ; 468 469const mkl_fa_kv_desc K_desc = mkl_fa_make_desc (K ,k_interleaved ,n_kv_heads ); 470const mkl_fa_kv_desc V_desc = V_is_K_view 471 ?K_desc :mkl_fa_make_desc (V ,v_interleaved ,n_kv_heads ); 472 473MKL_ACCUM (dequant_time_us ,t_deq ); 474 475// --- Resolve mask pointers --- 476int64_t mask_head_stride = 0 ; 477int64_t mask_row_stride = 0 ; 478int mask_n_heads = 0 ; 479 480if (mask ) { 481// Use actual fp16 device size (2 bytes), NOT sizeof(sycl::half) 482// which may be 4 on the host in oneAPI. 483mask_head_stride = mask -> nb [2 ] /2 ; 484mask_row_stride = mask -> nb [1 ] /2 ; 485mask_n_heads = (int )mask -> ne [2 ]; 486 } 487 488// --- Allocate intermediates from pool --- 489ggml_sycl_pool & pool = ctx .pool (); 490 491ggml_sycl_pool_alloc < float > KQ_f32 (pool );// [q_tile_rows x chunk] 492ggml_sycl_pool_alloc < sycl::half > S_f16 (pool );// [q_tile_rows x chunk] 493ggml_sycl_pool_alloc < float > VKQ_chunk (pool );// [q_tile_rows x DV] 494ggml_sycl_pool_alloc < float > VKQ_accum (pool );// [n_query_rows x DV] (full) 495ggml_sycl_pool_alloc < float > KQ_max (pool );// [n_query_rows] (full) 496ggml_sycl_pool_alloc < float > KQ_sum (pool );// [n_query_rows] (full) 497ggml_sycl_pool_alloc < sycl::half > Q_head_f16 (pool );// [n_query_rows x DKQ] (full) 498ggml_sycl_pool_alloc < sycl::half > K_chunk_f16 (pool );// [chunk x DKQ] (per-chunk dequant) 499ggml_sycl_pool_alloc < sycl::half > V_chunk_f16 (pool );// [chunk x DV] (per-chunk dequant) 500 501KQ_f32 .alloc ((size_t )q_tile_rows * chunk_size ); 502S_f16 .alloc ((size_t )q_tile_rows * chunk_size ); 503VKQ_chunk .alloc ((size_t )q_tile_rows * DV ); 504VKQ_accum .alloc ((size_t )n_query_rows * DV ); 505KQ_max .alloc (n_query_rows ); 506KQ_sum .alloc (n_query_rows ); 507Q_head_f16 .alloc ((size_t )n_query_rows * DKQ ); 508K_chunk_f16 .alloc ((size_t )chunk_size * DKQ ); 509 510 sycl::half * V_chunk_f16_ptr ; 511if (V_is_K_view ) { 512V_chunk_f16_ptr = K_chunk_f16 .ptr ;// V aliases K (DV == DKQ) 513 }else { 514V_chunk_f16 .alloc ((size_t )chunk_size * DV ); 515V_chunk_f16_ptr = V_chunk_f16 .ptr ; 516 } 517 518 sycl::half * Q_head_f16_ptr = Q_head_f16 .ptr ; 519float * KQ_f32_ptr = KQ_f32 .ptr ; 520 sycl::half * S_f16_ptr = S_f16 .ptr ; 521float * VKQ_chunk_ptr = VKQ_chunk .ptr ; 522float * VKQ_accum_ptr = VKQ_accum .ptr ; 523float * KQ_max_ptr = KQ_max .ptr ; 524float * KQ_sum_ptr = KQ_sum .ptr ; 525 sycl::half * K_chunk_f16_ptr = K_chunk_f16 .ptr ; 526 527const float alpha = 1.0f ; 528const float beta = 0.0f ; 529 530for (int ib = 0 ;ib < n_batch ;ib ++ ) { 531const float * Q_batch = (const float * )Q -> data 532+ ib * (Q -> nb [3 ] /sizeof (float )); 533float * dst_batch = (float * )KQV -> data 534+ ib * (KQV -> nb [3 ] /sizeof (float )); 535 536const sycl::half * mask_batch = nullptr ; 537if (mask ) { 538int m_batch = (mask -> ne [3 ]> 1 ) ?ib :0 ; 539mask_batch = (const sycl::half * )mask -> data 540+ m_batch * (mask -> nb [3 ] /2 );// 2 = actual fp16 device size 541 } 542 543for (int ikvh = 0 ;ikvh < n_kv_heads ;ikvh ++ ) { 544int kvh_base_head = ikvh * gqa_ratio ; 545 546// 1. Pack all GQA Q heads into fp16 (full n_query_rows) 547mkl_fa_pack_q_fp16 (stream , 548Q_head_f16_ptr ,Q_batch , 549n_queries ,DKQ , 550gqa_ratio ,kvh_base_head , 551q_scale ,q_row_stride ,q_head_stride ,wg_size ); 552 553// 2. Initialize softmax state (full n_query_rows) 554mkl_fa_init_softmax_state (stream , 555KQ_max_ptr ,KQ_sum_ptr ,VKQ_accum_ptr , 556n_query_rows ,DV ,wg_size ); 557 558// Sync before MKL GEMM (MKL may use an internal queue) 559stream -> wait (); 560 561// 3. KV chunk loop (OUTER): dequant each chunk once, then tile queries. 562for (int chunk_start = 0 ;chunk_start < n_kv ;chunk_start += chunk_size ) { 563int this_chunk = std::min (chunk_size ,n_kv - chunk_start ); 564 565// 3a. Dequant this KV chunk to dense fp16 (once per chunk) 566 { 567MKL_TAKE_TIME (t0 ); 568mkl_fa_dequant_chunk (stream ,K_desc ,KQV , 569K_chunk_f16_ptr ,ikvh ,chunk_start ,this_chunk ); 570if (!V_is_K_view ) { 571mkl_fa_dequant_chunk (stream ,V_desc ,KQV , 572V_chunk_f16_ptr ,ikvh ,chunk_start ,this_chunk ); 573 } 574stream -> wait ();// dequant must be ready before MKL GEMM 575MKL_ACCUM (dequant_time_us ,t0 ); 576 } 577 578// 3b. Query tile loop (INNER) — bounds KQ_f32/S_f16 footprint. 579for (int q0 = 0 ;q0 < n_query_rows ;q0 += q_tile_rows ) { 580int q_rows = std::min (q_tile_rows ,n_query_rows - q0 ); 581 582// GEMM: KQ = Q_tile × K_chunk^T 583 { 584MKL_TAKE_TIME (t0 ); 585 sycl::event ev = gemm (* stream , 586 transpose::trans , transpose::nontrans , 587this_chunk ,q_rows ,DKQ , 588alpha , 589K_chunk_f16_ptr ,DKQ , 590Q_head_f16_ptr + (int64_t )q0 * DKQ ,DKQ , 591beta , 592KQ_f32_ptr ,this_chunk ); 593try {ev .wait_and_throw (); }catch (sycl::exception & e ) { 594GGML_LOG_INFO ("[MKL-FA] GEMM KQ: %s\n" ,e .what ()); 595GGML_ABORT ("MKL GEMM KQ failed" ); 596 } 597MKL_ACCUM (gemm_kq_time_us ,t0 ); 598 } 599// Online softmax over this chunk for this query tile 600 { 601MKL_TAKE_TIME (t0 ); 602mkl_fa_online_softmax_chunk (stream , 603KQ_f32_ptr ,S_f16_ptr , 604KQ_max_ptr ,KQ_sum_ptr ,VKQ_accum_ptr , 605q0 ,q_rows ,n_queries ,DV , 606this_chunk ,chunk_start , 607kvh_base_head , 608mask_batch ,mask_head_stride , 609mask_row_stride ,mask_n_heads , 610logit_softcap ,wg_size ); 611stream -> wait ();// S_f16 must be ready for GEMM 612MKL_ACCUM (softmax_time_us ,t0 ); 613 } 614 615// GEMM: VKQ_chunk = S × V_chunk 616 { 617MKL_TAKE_TIME (t0 ); 618 sycl::event ev = gemm (* stream , 619 transpose::nontrans , transpose::nontrans , 620DV ,q_rows ,this_chunk , 621alpha , 622V_chunk_f16_ptr ,DV , 623S_f16_ptr ,this_chunk , 624beta , 625VKQ_chunk_ptr ,DV ); 626try {ev .wait_and_throw (); }catch (sycl::exception & e ) { 627GGML_LOG_INFO ("[MKL-FA] GEMM VKQ: %s\n" ,e .what ()); 628GGML_ABORT ("MKL GEMM VKQ failed" ); 629 } 630MKL_ACCUM (gemm_vkq_time_us ,t0 ); 631 } 632// VKQ_accum[q0..] += VKQ_chunk 633 { 634const int64_t n_total = (int64_t )q_rows * DV ; 635const int64_t wg = ((n_total + wg_size - 1 ) /wg_size ) 636* wg_size ; 637float * accum = VKQ_accum_ptr + (int64_t )q0 * DV ; 638stream -> submit ([& ](sycl::handler & cgh ) { 639cgh .parallel_for (sycl::nd_range < 1 > (wg ,wg_size ), 640 [= ](sycl::nd_item < 1 > item ) { 641int64_t i = item .get_global_id (0 ); 642if (i < n_total ) { 643accum [i ]+= VKQ_chunk_ptr [i ]; 644 } 645 }); 646 }); 647 } 648 } 649 } 650 651// 4. Normalize and scatter each GQA head to dst 652for (int iqg = 0 ;iqg < gqa_ratio ;iqg ++ ) { 653int iqh = kvh_base_head + iqg ; 654int64_t src_offset = (int64_t )iqg * n_queries * DV ; 655mkl_fa_normalize_head (stream , 656dst_batch ,VKQ_accum_ptr ,KQ_sum_ptr , 657iqh ,n_queries ,DV ,n_q_heads , 658src_offset ,wg_size ); 659 } 660 } 661 } 662 663#undef MKL_TAKE_TIME 664#undef MKL_ACCUM 665 666if (do_print ) { 667const int64_t v_chunk_elems = V_is_K_view ?0 : (int64_t )chunk_size * DV ; 668double total_mb = (double )( 669 (int64_t )q_tile_rows * chunk_size * sizeof (float )// KQ_f32 670+ (int64_t )q_tile_rows * chunk_size * sizeof (sycl::half )// S_f16 671+ (int64_t )q_tile_rows * DV * sizeof (float )// VKQ_chunk 672+ (int64_t )n_query_rows * DV * sizeof (float )// VKQ_accum 673+ (int64_t )n_query_rows * sizeof (float )// KQ_max 674+ (int64_t )n_query_rows * sizeof (float )// KQ_sum 675+ (int64_t )n_query_rows * DKQ * sizeof (sycl::half )// Q_head_f16 676+ (int64_t )chunk_size * DKQ * sizeof (sycl::half )// K_chunk_f16 677+ v_chunk_elems * (int64_t )sizeof (sycl::half )// V_chunk_f16 678 ) / (1024.0 * 1024.0 ); 679GGML_LOG_INFO ("[MKL-FA] #%d n_kv=%d n_q=%d q_tile=%d time_us: " 680"dequant=%lld GEMM_KQ=%lld softmax=%lld GEMM_VKQ=%lld " 681"buf_mb=%.1f\n" , 682mkl_call_count ,n_kv ,n_queries ,q_tile_rows , 683 (long long )dequant_time_us , 684 (long long )gemm_kq_time_us , 685 (long long )softmax_time_us , 686 (long long )gemm_vkq_time_us , 687total_mb ); 688 } 689}