char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
c1c766da5
char/aliceai-t5-moe
1diagnostic(off, chromium.subgroup_matrix_uniformity); 2diagnostic(off, subgroup_uniformity); 3enable f16; 4enable subgroups; 5enable chromium_experimental_subgroup_matrix; 6 7#define BYTE_HELPERS 8#define FLASH_ATTN_SCALAR_KV 9#include "flash_attn_decls.tmpl" 10#include "common_decls.tmpl" 11 12// Default values 13// The actual values are defined in shader-lib. 14#define HEAD_DIM_QK 64 15#define HEAD_DIM_V 64 16// The number of rows/columns/k in a subgroup matrix. MxK * KxN = MxN 17// Note that the "K" here does not correspond to the K in attention's Q/K/V, it's just the common dimension. 18#define SG_MAT_M 8 19#define SG_MAT_N 8 20#define SG_MAT_K 8 21// Each workgroup processes one subgroup matrix of Q rows 22#define Q_TILE SG_MAT_M 23#define KV_TILE 16 24#define WG_SIZE 64 25 26// Number of subgroup-matrix-width blocks that span the KV tile. SG_MAT_N must divide KV_TILE. 27#define KV_BLOCKS (KV_TILE / SG_MAT_N) 28 29// The number of Q rows processed per workgroup 30var<workgroup> q_shmem: array<f16, Q_TILE * HEAD_DIM_QK>; 31 32#if !defined(K_DIRECT) || !defined(V_DIRECT) 33#define STAGING_SHMEM kv_shmem 34#define STAGING_OUT_TYPE f16 35#include "flash_attn_staging.tmpl" 36const kv_shmem_size = KV_TILE * max(HEAD_DIM_QK, HEAD_DIM_V); 37// we can reuse the same shmem for K and V since we only need one at a time 38var<workgroup> kv_shmem: array<f16, kv_shmem_size>; 39#endif 40 41var<workgroup> o_shmem: array<f16, Q_TILE * HEAD_DIM_V>; // output shmem 42 43#ifdef MASK 44// storage for mask values 45var<workgroup> mask_shmem: array<f16, Q_TILE * KV_TILE>; 46#endif 47 48// storage for output of Q*K^T scores for online softmax (S matrix from paper) 49// also storage for diagonal matrix during online softmax (P matrix from paper) 50// note that we reuse the same storage for both since we only need one at a time 51var<workgroup> inter_shmem: array<f16, Q_TILE * KV_TILE>; 52 53// Storage for row max and exp sum during online softmax 54var<workgroup> row_max_shmem: array<f32, Q_TILE>; 55var<workgroup> exp_sum_shmem: array<f32, Q_TILE>; 56 57fn calc_softmax_term(kv_idx: u32, q_tile_row: u32, slope: f32) -> f32 { 58 var v = select(FLOAT_MIN, 59 f32(inter_shmem[kv_idx + q_tile_row * KV_TILE]) * params.scale, 60 kv_idx < KV_TILE); 61#ifdef LOGIT_SOFTCAP 62 v = params.logit_softcap * tanh(v); 63#endif 64#ifdef MASK 65 let mask_val = select(0.0, f32(mask_shmem[q_tile_row * KV_TILE + kv_idx]), kv_idx < KV_TILE); 66 let mask_term = slope * mask_val; 67 v += mask_term; 68#endif 69 return v; 70} 71 72@compute @workgroup_size(WG_SIZE) 73fn main(@builtin(workgroup_id) wg_id: vec3<u32>, 74 @builtin(local_invocation_id) local_id: vec3<u32>, 75 @builtin(subgroup_id) subgroup_id: u32, 76 @builtin(subgroup_size) subgroup_size: u32, 77 @builtin(num_subgroups) num_subgroups: u32, 78 @builtin(subgroup_invocation_id) sg_inv_id: u32) { 79 80 // initialize row max for online softmax 81 for (var i = local_id.x; i < Q_TILE; i += WG_SIZE) { 82 row_max_shmem[i] = FLOAT_MIN; 83 exp_sum_shmem[i] = 0.0; 84 } 85 86 for (var i = local_id.x; i < Q_TILE * HEAD_DIM_V; i += WG_SIZE) { 87 o_shmem[i] = 0.0; 88 } 89 90 // workgroups per head/batch 91 let wg_per_head = (params.seq_len_q + Q_TILE - 1u) / Q_TILE; 92 let wg_per_batch = wg_per_head * params.n_heads; 93 94 let dst2_stride = HEAD_DIM_V * params.n_heads; 95 let dst3_stride = dst2_stride * params.seq_len_q; 96 97 // batch index 98 let batch_idx = wg_id.x / wg_per_batch; 99 let q_batch_offset = params.offset_q + batch_idx * params.stride_q3; 100 let k_batch_offset = params.offset_k + batch_idx * params.stride_k3; 101 let v_batch_offset = params.offset_v + batch_idx * params.stride_v3; 102 let dst_batch_offset = params.offset_dst + batch_idx * dst3_stride; 103 let wg_in_batch = wg_id.x % wg_per_batch; 104 105 // head index 106 let head_idx = wg_in_batch / wg_per_head; 107 let q_head_offset = q_batch_offset + head_idx * params.stride_q2; 108 let k_head_idx = head_idx / params.q_per_kv; 109 let v_head_idx = k_head_idx; 110 let k_head_offset = k_batch_offset + k_head_idx * params.stride_k2; 111 let v_head_offset = v_batch_offset + v_head_idx * params.stride_v2; 112 113 // starting Q row for this workgroup 114 let wg_in_head = wg_in_batch % wg_per_head; 115 let q_row_start = wg_in_head * Q_TILE; 116 117#ifdef MASK 118 // mask offset 119 let mask_global_offset = params.offset_mask + batch_idx * params.stride_mask3 + q_row_start * params.seq_len_kv; 120#endif 121 122 // note that the output is permuted, the layout is [head_dim_v, n_heads, seq_len_q, batch_size] 123 let dst_global_offset = dst_batch_offset + q_row_start * dst2_stride + head_idx * HEAD_DIM_V; 124 125 let head = f32(head_idx); 126 let slope = select(1.0, select(pow(params.m1, 2.0 * (head - params.n_head_log2) + 1.0), pow(params.m0, head + 1.0), head < params.n_head_log2), params.max_bias > 0); 127 128 // load q tile into shared memory 129 for (var elem_idx = local_id.x; elem_idx < Q_TILE * HEAD_DIM_QK; elem_idx += WG_SIZE) { 130 let q_row = elem_idx / HEAD_DIM_QK; 131 let q_col = elem_idx % HEAD_DIM_QK; 132 let head_q_row = q_row_start + q_row; 133 let global_q_row_offset = q_head_offset + head_q_row * params.stride_q1; 134 q_shmem[elem_idx] = f16(select( 135 0.0, 136 Q[global_q_row_offset + q_col], 137 head_q_row < params.seq_len_q && q_col < HEAD_DIM_QK)); 138 } 139 140 for (var kv_tile = 0u; kv_tile < params.seq_len_kv; kv_tile += KV_TILE) { 141 let kv_count = min(KV_TILE, params.seq_len_kv - kv_tile); 142 // clear inter_shmem to ensure zero-initialized accumulators 143 for (var elem_idx = local_id.x; elem_idx < Q_TILE * KV_TILE; elem_idx += WG_SIZE) { 144 inter_shmem[elem_idx] = 0.0; 145 } 146 147 // load k tile into shared memory 148#ifndef K_DIRECT 149 load_k_tile_block(local_id.x, kv_count, kv_tile, k_head_offset); 150#endif 151 152 workgroupBarrier(); 153 154 // accumulate q block * k block into registers across the entire KV tile 155 // TODO: this loop seems to be the current largest bottleneck 156 // this bracket exists to scope the lifetime of variables, reducing register pressure 157 { 158#ifdef K_DIRECT 159 let k_block_row = kv_tile + subgroup_id * SG_MAT_N; 160 var k_global_offset = k_head_offset + k_block_row * params.stride_k1; 161#else 162 var k_block_offset = subgroup_id * SG_MAT_N * HEAD_DIM_QK; 163#endif 164 for (var kv_block = subgroup_id; kv_block < KV_BLOCKS; kv_block += num_subgroups) { 165 let inter_offset = kv_block * SG_MAT_N; 166 var acc: subgroup_matrix_result<f16, SG_MAT_N, SG_MAT_M> = subgroupMatrixLoad<subgroup_matrix_result<f16, SG_MAT_N, SG_MAT_M>>(&inter_shmem, inter_offset, false, KV_TILE); 167 168 var q_cur = subgroupMatrixLoad<subgroup_matrix_left<f16, SG_MAT_K, SG_MAT_M>>(&q_shmem, 0u, false, HEAD_DIM_QK); 169 170#ifdef K_DIRECT 171 var k_cur = subgroupMatrixLoad<subgroup_matrix_right<f16, SG_MAT_N, SG_MAT_K>>(&K, k_global_offset + 0u, true, params.stride_k1); 172#else 173 var k_cur = subgroupMatrixLoad<subgroup_matrix_right<f16, SG_MAT_N, SG_MAT_K>>(&kv_shmem, k_block_offset + 0u, true, HEAD_DIM_QK); 174#endif 175 176 var t: u32 = 1u; 177 for (; t + 1u < HEAD_DIM_QK / SG_MAT_K; t += 2u) { 178 let h0 = t * SG_MAT_K; 179 var q0 = subgroupMatrixLoad<subgroup_matrix_left<f16, SG_MAT_K, SG_MAT_M>>(&q_shmem, h0, false, HEAD_DIM_QK); 180#ifdef K_DIRECT 181 var k0 = subgroupMatrixLoad<subgroup_matrix_right<f16, SG_MAT_N, SG_MAT_K>>(&K, k_global_offset + h0, true, params.stride_k1); 182#else 183 var k0 = subgroupMatrixLoad<subgroup_matrix_right<f16, SG_MAT_N, SG_MAT_K>>(&kv_shmem, k_block_offset + h0, true, HEAD_DIM_QK); 184#endif 185 acc = subgroupMatrixMultiplyAccumulate(q_cur, k_cur, acc); 186 q_cur = q0; 187 k_cur = k0; 188 189 let h1 = (t + 1u) * SG_MAT_K; 190 var q1g = subgroupMatrixLoad<subgroup_matrix_left<f16, SG_MAT_K, SG_MAT_M>>(&q_shmem, h1, false, HEAD_DIM_QK); 191#ifdef K_DIRECT 192 var k1g = subgroupMatrixLoad<subgroup_matrix_right<f16, SG_MAT_N, SG_MAT_K>>(&K, k_global_offset + h1, true, params.stride_k1); 193#else 194 var k1g = subgroupMatrixLoad<subgroup_matrix_right<f16, SG_MAT_N, SG_MAT_K>>(&kv_shmem, k_block_offset + h1, true, HEAD_DIM_QK); 195#endif 196 acc = subgroupMatrixMultiplyAccumulate(q_cur, k_cur, acc); 197 q_cur = q1g; 198 k_cur = k1g; 199 } 200 201 // handle odd tail 202 if (t < HEAD_DIM_QK / SG_MAT_K) { 203 let h = t * SG_MAT_K; 204 var qn = subgroupMatrixLoad<subgroup_matrix_left<f16, SG_MAT_K, SG_MAT_M>>(&q_shmem, h, false, HEAD_DIM_QK); 205#ifdef K_DIRECT 206 var kn = subgroupMatrixLoad<subgroup_matrix_right<f16, SG_MAT_N, SG_MAT_K>>(&K, k_global_offset + h, true, params.stride_k1); 207#else 208 var kn = subgroupMatrixLoad<subgroup_matrix_right<f16, SG_MAT_N, SG_MAT_K>>(&kv_shmem, k_block_offset + h, true, HEAD_DIM_QK); 209#endif 210 acc = subgroupMatrixMultiplyAccumulate(q_cur, k_cur, acc); 211 q_cur = qn; 212 k_cur = kn; 213 } 214 215 acc = subgroupMatrixMultiplyAccumulate(q_cur, k_cur, acc); 216 217#ifdef K_DIRECT 218 k_global_offset += num_subgroups * SG_MAT_N * params.stride_k1; 219#else 220 k_block_offset += num_subgroups * SG_MAT_N * HEAD_DIM_QK; 221#endif 222 subgroupMatrixStore(&inter_shmem, inter_offset, acc, false, KV_TILE); 223 } 224 } 225 226 227#ifdef MASK 228 // load mask tile into shared memory for this KV block 229 // TODO: optimize and skip if mask is -INF for the entire tile 230 for (var elem_idx = local_id.x; elem_idx < Q_TILE * KV_TILE; elem_idx += WG_SIZE) { 231 let mask_row = elem_idx / KV_TILE; 232 let mask_col = elem_idx % KV_TILE; 233 let global_q_row = q_row_start + mask_row; 234 let global_k_col = kv_tile + mask_col; 235 let mask_in_bounds = global_q_row < params.seq_len_q && global_k_col < params.seq_len_kv; 236 let mask_idx = mask_global_offset + mask_row * params.seq_len_kv + global_k_col; 237 mask_shmem[elem_idx] = select(0.0, mask[mask_idx], mask_in_bounds); 238 } 239#endif 240 241 workgroupBarrier(); 242 243 // online softmax 244 for (var q_tile_row = subgroup_id; q_tile_row < Q_TILE; q_tile_row += num_subgroups) { 245 let global_q_row = q_row_start + q_tile_row; 246 if (global_q_row >= params.seq_len_q) { 247 break; 248 } 249 250 // initialize running max for this row 251 var prev_max = row_max_shmem[q_tile_row]; 252 var final_max = prev_max; 253 // pass 1: compute final max across the full KV tile in chunks 254 for (var kv_offset = 0u; kv_offset < KV_TILE; kv_offset += subgroup_size) { 255 let kv_idx = kv_offset + sg_inv_id; 256 let softmax_term = calc_softmax_term(kv_idx, q_tile_row, slope); 257 final_max = subgroupMax(max(final_max, softmax_term)); 258 } 259 260 var total_exp_term: f32 = 0.0; 261 // pass 2: compute exp sum and write P using final_max 262 for (var kv_offset = 0u; kv_offset < KV_TILE; kv_offset += subgroup_size) { 263 let kv_idx = kv_offset + sg_inv_id; 264 let softmax_term = calc_softmax_term(kv_idx, q_tile_row, slope); 265 let cur_p = select(0.0, 266 exp(softmax_term - final_max), 267 kv_tile + kv_idx < params.seq_len_kv && kv_idx < KV_TILE); 268 total_exp_term += subgroupAdd(cur_p); 269 if (kv_idx < KV_TILE) { 270 inter_shmem[kv_idx + q_tile_row * KV_TILE] = f16(cur_p); 271 } 272 } 273 274 let cur_exp = exp(prev_max - final_max); 275 276 if (sg_inv_id == 0) { 277 row_max_shmem[q_tile_row] = final_max; 278 exp_sum_shmem[q_tile_row] = exp_sum_shmem[q_tile_row] * cur_exp + total_exp_term; 279 } 280 281 for (var elem_idx = sg_inv_id; elem_idx < HEAD_DIM_V; elem_idx += subgroup_size) { 282 let idx = q_tile_row * HEAD_DIM_V + elem_idx; 283 o_shmem[idx] = f16(f32(o_shmem[idx]) * cur_exp); 284 } 285 } 286 287 // load v tile into shared memory 288#ifndef V_DIRECT 289 load_v_tile_block(local_id.x, kv_count, kv_tile, v_head_offset); 290#endif 291 292 workgroupBarrier(); 293 294 // we have P (Q_TILE x KV_TILE) in inter_shmem and V (KV_TILE x head_dim_v) in kv_shmem 295 // we want to compute O += P * V across the full KV tile 296 for (var head_dim_block = subgroup_id * SG_MAT_N; 297 head_dim_block < HEAD_DIM_V; 298 head_dim_block += num_subgroups * SG_MAT_N) { 299 // load O submatrix from shared memory 300 var o_sg_mat: subgroup_matrix_result<f16, SG_MAT_N, SG_MAT_M> = subgroupMatrixLoad<subgroup_matrix_result<f16, SG_MAT_N, SG_MAT_M>>( 301 &o_shmem, 302 head_dim_block, 303 false, 304 HEAD_DIM_V 305 ); 306 for (var kv_block = 0u; kv_block < KV_BLOCKS; kv_block++) { 307 let p_offset = kv_block * SG_MAT_N; 308 var p_sg_mat: subgroup_matrix_left<f16, SG_MAT_K, SG_MAT_M> = subgroupMatrixLoad<subgroup_matrix_left<f16, SG_MAT_K, SG_MAT_M>>( 309 &inter_shmem, 310 p_offset, 311 false, 312 KV_TILE 313 ); 314 315 // load V submatrix from global or shared memory 316#ifdef V_DIRECT 317 let v_block_row = kv_tile + kv_block * SG_MAT_N; 318 let v_global_offset = v_head_offset + v_block_row * params.stride_v1 + head_dim_block; 319 var v_sg_mat: subgroup_matrix_right<f16, SG_MAT_N, SG_MAT_K> = subgroupMatrixLoad<subgroup_matrix_right<f16, SG_MAT_N, SG_MAT_K>>( 320 &V, 321 v_global_offset, 322 false, 323 params.stride_v1 324 ); 325#else 326 let v_block_offset = kv_block * SG_MAT_N * HEAD_DIM_V; 327 var v_sg_mat: subgroup_matrix_right<f16, SG_MAT_N, SG_MAT_K> = subgroupMatrixLoad<subgroup_matrix_right<f16, SG_MAT_N, SG_MAT_K>>( 328 &kv_shmem, 329 v_block_offset + head_dim_block, 330 false, 331 HEAD_DIM_V 332 ); 333#endif 334 // O += P * V 335 o_sg_mat = subgroupMatrixMultiplyAccumulate(p_sg_mat, v_sg_mat, o_sg_mat); 336 } 337 // store O back to shared memory 338 subgroupMatrixStore(&o_shmem, head_dim_block, o_sg_mat, false, HEAD_DIM_V); 339 } 340 workgroupBarrier(); 341 } 342 343#ifdef SINKS 344 // add sinks (applied once after processing all KV tiles) 345 for (var q_tile_row = subgroup_id; 346 q_tile_row < Q_TILE; 347 q_tile_row += num_subgroups) { 348 // no need to process rows beyond seq_len_q 349 let global_q_row = q_row_start + q_tile_row; 350 if (global_q_row >= params.seq_len_q) { 351 break; 352 } 353 354 var prev_max = row_max_shmem[q_tile_row]; 355 356 // for non-sink threads, exp(FLOAT_MIN) effectively zeroes out their contribution to the sum 357 let sink_val = select(FLOAT_MIN, sinks[params.offset_sinks + head_idx], sg_inv_id == 0); 358 let new_max = subgroupMax(max(prev_max, sink_val)); 359 let max_exp = exp(prev_max - new_max); 360 let sink_exp = exp(sink_val - new_max); 361 362 let sink_exp_sum = subgroupAdd(sink_exp); 363 364 if (sg_inv_id == 0) { 365 exp_sum_shmem[q_tile_row] = exp_sum_shmem[q_tile_row] * max_exp + sink_exp_sum; 366 } 367 368 for (var elem_idx = sg_inv_id; elem_idx < HEAD_DIM_V; elem_idx += subgroup_size) { 369 let idx = q_tile_row * HEAD_DIM_V + elem_idx; 370 let val = f32(o_shmem[idx]) * max_exp; 371 o_shmem[idx] = f16(val); 372 } 373 } 374 workgroupBarrier(); 375#endif 376 for (var q_tile_row = subgroup_id; 377 q_tile_row < Q_TILE; 378 q_tile_row += num_subgroups) { 379 380 let global_q_row = q_row_start + q_tile_row; 381 if (global_q_row >= params.seq_len_q) { break; } 382 383 let exp_sum = exp_sum_shmem[q_tile_row]; 384 let scale = select(0.0, 1.0 / exp_sum, exp_sum != 0.0); 385 386 let row_base: u32 = dst_global_offset + q_tile_row * dst2_stride; 387 388 for (var elem_base = sg_inv_id * 4u; 389 elem_base < HEAD_DIM_V; 390 elem_base += subgroup_size * 4u) { 391 392 let i0 = q_tile_row * HEAD_DIM_V + (elem_base + 0u); 393 let i1 = q_tile_row * HEAD_DIM_V + (elem_base + 1u); 394 let i2 = q_tile_row * HEAD_DIM_V + (elem_base + 2u); 395 let i3 = q_tile_row * HEAD_DIM_V + (elem_base + 3u); 396 397 let v = vec4<f32>( 398 f32(o_shmem[i0]) * scale, 399 f32(o_shmem[i1]) * scale, 400 f32(o_shmem[i2]) * scale, 401 f32(o_shmem[i3]) * scale 402 ); 403 404 let dst_vec_index: u32 = (row_base + elem_base) >> 2u; 405 dst[dst_vec_index] = v; 406 } 407 } 408}