char-fork/llama.cpp

git clone https://git.t4t.associates/char-fork/llama.cpp

fairydreamingwebgpu : reorder includes since V that appears in common_decls.tmpl may be defined as K in flash_attn_decls.tmpl if KV_OVERLAP (#27545)c1c766da5

char/aliceai-t5-moe
16.8 KiB408 linesraw
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}