char-fork/llama.cpp

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

Aman Guptaggml: add SWIGLU_CLAMP (#27930)0190529ec

char/aliceai-t5-moe
138.2 KiB3457 linesraw
1#ifndef GGML_WEBGPU_SHADER_LIB_HPP
2#define GGML_WEBGPU_SHADER_LIB_HPP
3
4#include "ggml-impl.h"
5#include "ggml-wgsl-shaders.hpp"
6#include "ggml.h"
7#include "pre_wgsl.hpp"
8
9#include <webgpu/webgpu_cpp.h>
10
11#include <algorithm>
12#include <memory>
13#include <string>
14#include <unordered_map>
15#include <vector>
16
17#define GGML_WEBGPU_F16_SIZE_BYTES                   2
18#define GGML_WEBGPU_F32_SIZE_BYTES                   4
19#define GGML_WEBGPU_I32_SIZE_BYTES                   4
20#define GGML_WEBGPU_FLASH_ATTN_PREFERRED_KV_SG_TILES 8u
21#define GGML_WEBGPU_FLASH_ATTN_VEC_MAX_SEQ_LEN       20u
22#define GGML_WEBGPU_FLASH_ATTN_VEC_MAX_KV_TILE       32u
23#define GGML_WEBGPU_FLASH_ATTN_TILE_MAX_KV_TILE      64u
24#define GGML_WEBGPU_FLASH_ATTN_PREFERRED_WG_SIZE     128u
25// Matches GGML_PAD(..., 256) in src/llama-context.cpp for KV cache sizing.
26#define GGML_WEBGPU_KV_SEQ_PAD                       256u
27
28#define GGML_WEBGPU_ARGSORT_MERGE_MAX_WG_SIZE 512u
29
30// Matrix multiplication parameters
31
32// Register tiling parameters
33#define WEBGPU_MUL_MAT_TILE_M           4
34#define WEBGPU_MUL_MAT_TILE_N           4
35#define WEBGPU_MUL_MAT_WG_SIZE_M        8
36#define WEBGPU_MUL_MAT_WG_SIZE_N        8
37#define WEBGPU_MUL_MAT_REG_TILE_K_FLOAT 8
38#define WEBGPU_MUL_MAT_REG_TILE_K_QUANT 32
39
40// Subgroup matrix parameters
41// The number of subgroups in the M dimension
42#define WEBGPU_MUL_MAT_SUBGROUP_M            2
43// The number of subgroups in the N dimension
44#define WEBGPU_MUL_MAT_SUBGROUP_N            4
45// The number of subgroup matrices each subgroup accumulates over
46#define WEBGPU_MUL_MAT_SUBGROUP_MATRIX_M     4
47#define WEBGPU_MUL_MAT_SUBGROUP_MATRIX_N     2
48#define WEBGPU_MUL_MAT_SUBGROUP_TILE_K_FLOAT 32
49#define WEBGPU_MUL_MAT_SUBGROUP_TILE_K_QUANT 32
50
51// Matrix-vector multiplication parameters
52#define WEBGPU_MUL_MAT_VEC_WG_SIZE 256
53
54#define WEBGPU_MUL_MAT_VEC_FLOAT_OUTPUTS_PER_WG    4
55#define WEBGPU_MUL_MAT_VEC_LEGACY_Q_OUTPUTS_PER_WG 4
56#define WEBGPU_MUL_MAT_VEC_K_Q_OUTPUTS_PER_WG      4
57
58// default size for reg-tile matrix multiplication
59#define WEBGPU_MUL_MAT_WG_SIZE 256
60
61// Same hash combine function as in boost
62template <typename T> inline void ggml_webgpu_hash_combine(size_t & seed, const T & value) {
63    seed ^= std::hash<T>{}(value) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
64}
65
66// Calculates base address of a tensor ignoring the fake base pointer
67inline uintptr_t ggml_webgpu_tensor_addr(const ggml_tensor * tensor) {
68    const ggml_tensor * base_tensor = tensor->view_src ? tensor->view_src : tensor;
69    return (uintptr_t) base_tensor->data + tensor->view_offs;
70}
71
72inline bool ggml_webgpu_tensor_equal(const ggml_tensor * a, const ggml_tensor * b) {
73    return a->buffer == b->buffer && ggml_webgpu_tensor_addr(a) == ggml_webgpu_tensor_addr(b);
74}
75
76struct ggml_webgpu_shader_lib_context {
77    ggml_tensor * src0;
78    ggml_tensor * src1;
79    ggml_tensor * src2;
80    ggml_tensor * src3;
81    ggml_tensor * src4;
82    ggml_tensor * src5;
83    ggml_tensor * dst;
84
85    uint32_t    max_wg_size;
86    size_t      wg_mem_limit_bytes       = 0;
87    bool        supports_subgroups       = false;
88    bool        supports_subgroup_matrix = false;
89    uint32_t    sg_mat_m                 = 0;
90    uint32_t    sg_mat_n                 = 0;
91    uint32_t    sg_mat_k                 = 0;
92    uint32_t    min_subgroup_size        = 0;
93    uint32_t    max_subgroup_size        = 0;
94    bool        supports_dot_product     = false;
95    std::string vendor;
96};
97
98struct webgpu_pipeline {
99    wgpu::ComputePipeline pipeline;
100    std::string           name;
101    std::shared_ptr<void> context = nullptr;
102};
103
104struct ggml_webgpu_generic_shader_decisions {
105    uint32_t wg_size = 0;
106    bool     inplace = false;
107};
108
109struct ggml_webgpu_binary_shader_decisions {
110    uint32_t wg_size     = 0;
111    bool     inplace     = false;
112    bool     overlap     = false;
113    bool     src_overlap = false;
114};
115
116struct ggml_webgpu_glu_shader_decisions {
117    uint32_t wg_size     = 0;
118    bool     src_overlap = false;
119};
120
121struct ggml_webgpu_processed_shader {
122    std::string           wgsl;
123    std::string           variant;
124    std::shared_ptr<void> decisions;
125};
126
127struct ggml_webgpu_ssm_conv_shader_decisions {
128    uint32_t block_size;
129    uint32_t tokens_per_wg;
130};
131
132struct ggml_webgpu_ssm_scan_pipeline_key {
133    int  type;
134    int  d_state;
135    bool xbc_overlap;
136    bool a_overlap;
137    bool ids_overlap;
138
139    bool operator==(const ggml_webgpu_ssm_scan_pipeline_key & other) const {
140        return type == other.type && d_state == other.d_state && xbc_overlap == other.xbc_overlap &&
141               a_overlap == other.a_overlap && ids_overlap == other.ids_overlap;
142    }
143};
144
145struct ggml_webgpu_ssm_scan_pipeline_key_hash {
146    size_t operator()(const ggml_webgpu_ssm_scan_pipeline_key & key) const {
147        size_t seed = 0;
148        ggml_webgpu_hash_combine(seed, key.type);
149        ggml_webgpu_hash_combine(seed, key.d_state);
150        ggml_webgpu_hash_combine(seed, key.xbc_overlap);
151        ggml_webgpu_hash_combine(seed, key.a_overlap);
152        ggml_webgpu_hash_combine(seed, key.ids_overlap);
153        return seed;
154    }
155};
156
157struct ggml_webgpu_ssm_scan_shader_decisions {
158    uint32_t wg_size;
159    uint32_t tokens_per_tile;
160    bool     xbc_overlap = false;
161    bool     a_overlap   = false;
162    bool     ids_overlap = false;
163};
164
165/** Argsort **/
166
167struct ggml_webgpu_argsort_shader_lib_context {
168    uint32_t max_wg_size;
169    size_t   wg_mem_limit_bytes;
170    int32_t  order;
171};
172
173/** Set Rows **/
174
175struct ggml_webgpu_set_rows_pipeline_key {
176    int dst_type;
177    int vec4;
178    int i64_idx;
179    int pair_blocks;
180
181    bool operator==(const ggml_webgpu_set_rows_pipeline_key & other) const {
182        return dst_type == other.dst_type && vec4 == other.vec4 && i64_idx == other.i64_idx &&
183               pair_blocks == other.pair_blocks;
184    }
185};
186
187struct ggml_webgpu_set_rows_pipeline_key_hash {
188    size_t operator()(const ggml_webgpu_set_rows_pipeline_key & key) const {
189        size_t seed = 0;
190        ggml_webgpu_hash_combine(seed, key.dst_type);
191        ggml_webgpu_hash_combine(seed, key.vec4);
192        ggml_webgpu_hash_combine(seed, key.i64_idx);
193        ggml_webgpu_hash_combine(seed, key.pair_blocks);
194        return seed;
195    }
196};
197
198struct ggml_webgpu_set_rows_shader_decisions {
199    bool     vec4;
200    bool     i64_idx;
201    bool     pair_blocks;
202    uint32_t wg_size;
203};
204
205/** Set **/
206
207struct ggml_webgpu_set_pipeline_key {
208    ggml_type type;
209    bool      inplace;
210
211    bool operator==(const ggml_webgpu_set_pipeline_key & other) const {
212        return type == other.type && inplace == other.inplace;
213    }
214};
215
216struct ggml_webgpu_set_pipeline_key_hash {
217    size_t operator()(const ggml_webgpu_set_pipeline_key & key) const {
218        size_t seed = 0;
219        ggml_webgpu_hash_combine(seed, key.type);
220        ggml_webgpu_hash_combine(seed, key.inplace);
221        return seed;
222    }
223};
224
225/** Get Rows **/
226
227struct ggml_webgpu_get_rows_pipeline_key {
228    ggml_type src_type;
229    int       vectorized;
230
231    bool operator==(const ggml_webgpu_get_rows_pipeline_key & other) const {
232        return src_type == other.src_type && vectorized == other.vectorized;
233    }
234};
235
236struct ggml_webgpu_get_rows_pipeline_key_hash {
237    size_t operator()(const ggml_webgpu_get_rows_pipeline_key & key) const {
238        size_t seed = 0;
239        ggml_webgpu_hash_combine(seed, key.src_type);
240        ggml_webgpu_hash_combine(seed, key.vectorized);
241        return seed;
242    }
243};
244
245/** Row Norm **/
246
247struct ggml_webgpu_row_norm_pipeline_key {
248    ggml_op   op;
249    ggml_type src_type;
250    ggml_type dst_type;
251    bool      inplace;
252
253    bool operator==(const ggml_webgpu_row_norm_pipeline_key & other) const {
254        return op == other.op && src_type == other.src_type && dst_type == other.dst_type && inplace == other.inplace;
255    }
256};
257
258struct ggml_webgpu_row_norm_pipeline_key_hash {
259    size_t operator()(const ggml_webgpu_row_norm_pipeline_key & key) const {
260        size_t seed = 0;
261        ggml_webgpu_hash_combine(seed, key.op);
262        ggml_webgpu_hash_combine(seed, key.src_type);
263        ggml_webgpu_hash_combine(seed, key.dst_type);
264        ggml_webgpu_hash_combine(seed, key.inplace);
265        return seed;
266    }
267};
268
269/** RMS_NORM + MUL **/
270
271struct ggml_webgpu_rms_norm_mul_pipeline_key {
272    bool inplace;      // rn_src == dst
273    bool overlap;      // mul_src == dst
274    bool src_overlap;  // rn_src binding overlaps mul_src binding
275
276    bool operator==(const ggml_webgpu_rms_norm_mul_pipeline_key & other) const {
277        return inplace == other.inplace && overlap == other.overlap && src_overlap == other.src_overlap;
278    }
279};
280
281struct ggml_webgpu_rms_norm_mul_pipeline_key_hash {
282    size_t operator()(const ggml_webgpu_rms_norm_mul_pipeline_key & key) const {
283        size_t seed = 0;
284        ggml_webgpu_hash_combine(seed, key.inplace);
285        ggml_webgpu_hash_combine(seed, key.overlap);
286        ggml_webgpu_hash_combine(seed, key.src_overlap);
287        return seed;
288    }
289};
290
291struct ggml_webgpu_rms_norm_mul_shader_decisions {
292    uint32_t wg_size     = 0;
293    bool     inplace     = false;
294    bool     overlap     = false;
295    bool     src_overlap = false;
296};
297
298/** Pad **/
299struct ggml_webgpu_pad_pipeline_key {
300    bool circular;
301
302    bool operator==(const ggml_webgpu_pad_pipeline_key & other) const { return circular == other.circular; }
303};
304
305struct ggml_webgpu_pad_pipeline_key_hash {
306    size_t operator()(const ggml_webgpu_pad_pipeline_key & key) const {
307        size_t seed = 0;
308        ggml_webgpu_hash_combine(seed, key.circular);
309        return seed;
310    }
311};
312
313/** Solve Tri **/
314struct ggml_webgpu_solve_tri_pipeline_key {
315    int type;
316    int n;
317    int k;
318
319    bool operator==(const ggml_webgpu_solve_tri_pipeline_key & other) const {
320        return type == other.type && n == other.n && k == other.k;
321    }
322};
323
324struct ggml_webgpu_solve_tri_pipeline_key_hash {
325    size_t operator()(const ggml_webgpu_solve_tri_pipeline_key & key) const {
326        size_t seed = 0;
327        ggml_webgpu_hash_combine(seed, key.type);
328        ggml_webgpu_hash_combine(seed, key.n);
329        ggml_webgpu_hash_combine(seed, key.k);
330        return seed;
331    }
332};
333
334/** SSM Conv **/
335struct ggml_webgpu_ssm_conv_pipeline_key {
336    int type;
337    int vectorized;
338
339    bool operator==(const ggml_webgpu_ssm_conv_pipeline_key & other) const {
340        return type == other.type && vectorized == other.vectorized;
341    }
342};
343
344/** CONV 2D */
345struct ggml_webgpu_conv2d_pipeline_key {
346    ggml_type weight_type;
347    ggml_type input_type;
348    ggml_type output_type;
349
350    bool operator==(const ggml_webgpu_conv2d_pipeline_key & other) const {
351        return weight_type == other.weight_type && input_type == other.input_type && output_type == other.output_type;
352    }
353};
354
355struct ggml_webgpu_conv2d_pipeline_key_hash {
356    size_t operator()(const ggml_webgpu_conv2d_pipeline_key & key) const {
357        size_t seed = 0;
358        ggml_webgpu_hash_combine(seed, key.weight_type);
359        ggml_webgpu_hash_combine(seed, key.input_type);
360        ggml_webgpu_hash_combine(seed, key.output_type);
361        return seed;
362    }
363};
364
365// Same type fields as conv2d plus the input layout (WHCN vs CWHN).
366struct ggml_webgpu_conv2d_dw_pipeline_key {
367    ggml_type weight_type;
368    ggml_type input_type;
369    ggml_type output_type;
370    bool      whcn;
371
372    bool operator==(const ggml_webgpu_conv2d_dw_pipeline_key & other) const {
373        return weight_type == other.weight_type && input_type == other.input_type && output_type == other.output_type &&
374               whcn == other.whcn;
375    }
376};
377
378struct ggml_webgpu_conv2d_dw_pipeline_key_hash {
379    size_t operator()(const ggml_webgpu_conv2d_dw_pipeline_key & key) const {
380        size_t seed = 0;
381        ggml_webgpu_hash_combine(seed, key.weight_type);
382        ggml_webgpu_hash_combine(seed, key.input_type);
383        ggml_webgpu_hash_combine(seed, key.output_type);
384        ggml_webgpu_hash_combine(seed, key.whcn);
385        return seed;
386    }
387};
388
389/** Im2Col **/
390struct ggml_webgpu_im2col_pipeline_key {
391    ggml_type input_type;
392    ggml_type output_type;
393
394    bool operator==(const ggml_webgpu_im2col_pipeline_key & other) const {
395        return input_type == other.input_type && output_type == other.output_type;
396    }
397};
398
399struct ggml_webgpu_im2col_pipeline_key_hash {
400    size_t operator()(const ggml_webgpu_im2col_pipeline_key & key) const {
401        size_t seed = 0;
402        ggml_webgpu_hash_combine(seed, key.input_type);
403        ggml_webgpu_hash_combine(seed, key.output_type);
404        return seed;
405    }
406};
407
408/** Gated Delta Net **/
409struct ggml_webgpu_gated_delta_net_pipeline_key {
410    int type;
411    int s_v;
412    int kda;
413
414    bool operator==(const ggml_webgpu_gated_delta_net_pipeline_key & other) const {
415        return type == other.type && s_v == other.s_v && kda == other.kda;
416    }
417};
418
419struct ggml_webgpu_gated_delta_net_pipeline_key_hash {
420    size_t operator()(const ggml_webgpu_gated_delta_net_pipeline_key & key) const {
421        size_t seed = 0;
422        ggml_webgpu_hash_combine(seed, key.type);
423        ggml_webgpu_hash_combine(seed, key.s_v);
424        ggml_webgpu_hash_combine(seed, key.kda);
425        return seed;
426    }
427};
428
429struct ggml_webgpu_ssm_conv_pipeline_key_hash {
430    size_t operator()(const ggml_webgpu_ssm_conv_pipeline_key & key) const {
431        size_t seed = 0;
432        ggml_webgpu_hash_combine(seed, key.type);
433        ggml_webgpu_hash_combine(seed, key.vectorized);
434        return seed;
435    }
436};
437
438/** Scale **/
439
440struct ggml_webgpu_scale_pipeline_key {
441    int inplace;
442
443    bool operator==(const ggml_webgpu_scale_pipeline_key & other) const { return inplace == other.inplace; }
444};
445
446struct ggml_webgpu_scale_pipeline_key_hash {
447    size_t operator()(const ggml_webgpu_scale_pipeline_key & key) const {
448        size_t seed = 0;
449        ggml_webgpu_hash_combine(seed, key.inplace);
450        return seed;
451    }
452};
453
454/** Upscale **/
455
456struct ggml_webgpu_upscale_pipeline_key {
457    ggml_type input_type;
458    ggml_type output_type;
459    uint32_t  base_mode;
460    bool      antialias;
461
462    bool operator==(const ggml_webgpu_upscale_pipeline_key & other) const {
463        return input_type == other.input_type && output_type == other.output_type && base_mode == other.base_mode &&
464               antialias == other.antialias;
465    }
466};
467
468struct ggml_webgpu_upscale_pipeline_key_hash {
469    size_t operator()(const ggml_webgpu_upscale_pipeline_key & key) const {
470        size_t seed = 0;
471        ggml_webgpu_hash_combine(seed, key.input_type);
472        ggml_webgpu_hash_combine(seed, key.output_type);
473        ggml_webgpu_hash_combine(seed, key.base_mode);
474        ggml_webgpu_hash_combine(seed, key.antialias);
475        return seed;
476    }
477};
478
479/** Concat **/
480
481struct ggml_webgpu_concat_pipeline_key {
482    int  type;
483    bool src_overlap;
484
485    bool operator==(const ggml_webgpu_concat_pipeline_key & other) const {
486        return type == other.type && src_overlap == other.src_overlap;
487    }
488};
489
490struct ggml_webgpu_concat_pipeline_key_hash {
491    size_t operator()(const ggml_webgpu_concat_pipeline_key & key) const {
492        size_t seed = 0;
493        ggml_webgpu_hash_combine(seed, key.type);
494        ggml_webgpu_hash_combine(seed, key.src_overlap);
495        return seed;
496    }
497};
498
499/** Repeat **/
500
501struct ggml_webgpu_repeat_pipeline_key {
502    int type;
503
504    bool operator==(const ggml_webgpu_repeat_pipeline_key & other) const { return type == other.type; }
505};
506
507struct ggml_webgpu_repeat_pipeline_key_hash {
508    size_t operator()(const ggml_webgpu_repeat_pipeline_key & key) const {
509        size_t seed = 0;
510        ggml_webgpu_hash_combine(seed, key.type);
511        return seed;
512    }
513};
514
515/** Binary **/
516
517struct ggml_webgpu_binary_pipeline_key {
518    int  type;
519    int  op;
520    bool inplace;
521    bool overlap;
522    bool src_overlap;
523
524    bool operator==(const ggml_webgpu_binary_pipeline_key & other) const {
525        return type == other.type && op == other.op && inplace == other.inplace && overlap == other.overlap &&
526               src_overlap == other.src_overlap;
527    }
528};
529
530struct ggml_webgpu_binary_pipeline_key_hash {
531    size_t operator()(const ggml_webgpu_binary_pipeline_key & key) const {
532        size_t seed = 0;
533        ggml_webgpu_hash_combine(seed, key.type);
534        ggml_webgpu_hash_combine(seed, key.op);
535        ggml_webgpu_hash_combine(seed, key.inplace);
536        ggml_webgpu_hash_combine(seed, key.overlap);
537        ggml_webgpu_hash_combine(seed, key.src_overlap);
538        return seed;
539    }
540};
541
542/* Add_Id */
543
544struct ggml_webgpu_add_id_pipeline_key {
545    bool inplace;
546
547    bool operator==(const ggml_webgpu_add_id_pipeline_key & other) const { return inplace == other.inplace; }
548};
549
550struct ggml_webgpu_add_id_pipeline_key_hash {
551    size_t operator()(const ggml_webgpu_add_id_pipeline_key & key) const {
552        size_t seed = 0;
553        ggml_webgpu_hash_combine(seed, key.inplace);
554        return seed;
555    }
556};
557
558/** Unary **/
559
560struct ggml_webgpu_unary_pipeline_key {
561    int           type;
562    int           op;
563    bool          is_unary;  // many unary operators fall under the GGML_OP_UNARY umbrella
564    bool          inplace;
565    ggml_tri_type ttype;     // only used for GGML_OP_TRI
566
567    bool operator==(const ggml_webgpu_unary_pipeline_key & other) const {
568        return type == other.type && op == other.op && is_unary == other.is_unary && inplace == other.inplace &&
569               ttype == other.ttype;
570    }
571};
572
573struct ggml_webgpu_unary_pipeline_key_hash {
574    size_t operator()(const ggml_webgpu_unary_pipeline_key & key) const {
575        size_t seed = 0;
576        ggml_webgpu_hash_combine(seed, key.type);
577        ggml_webgpu_hash_combine(seed, key.op);
578        ggml_webgpu_hash_combine(seed, key.is_unary);
579        ggml_webgpu_hash_combine(seed, key.inplace);
580        ggml_webgpu_hash_combine(seed, key.ttype);
581        return seed;
582    }
583};
584
585/** FlashAttention */
586
587struct ggml_webgpu_flash_attn_common_pipeline_key {
588    ggml_type q_type;
589    ggml_type k_type;
590    ggml_type v_type;
591    ggml_type dst_type;
592    uint32_t  head_dim_qk;
593    uint32_t  head_dim_v;
594    bool      k_direct;
595    bool      v_direct;
596    bool      kv_overlap;
597    bool      has_mask;
598    bool      has_sinks;
599    bool      uses_logit_softcap;
600
601    bool operator==(const ggml_webgpu_flash_attn_common_pipeline_key & other) const {
602        return q_type == other.q_type && k_type == other.k_type && v_type == other.v_type &&
603               dst_type == other.dst_type && head_dim_qk == other.head_dim_qk && head_dim_v == other.head_dim_v &&
604               k_direct == other.k_direct && v_direct == other.v_direct && kv_overlap == other.kv_overlap &&
605               has_mask == other.has_mask && has_sinks == other.has_sinks &&
606               uses_logit_softcap == other.uses_logit_softcap;
607    }
608};
609
610inline void ggml_webgpu_flash_attn_hash_common_pipeline_key(size_t &                                           seed,
611                                                            const ggml_webgpu_flash_attn_common_pipeline_key & key) {
612    ggml_webgpu_hash_combine(seed, key.q_type);
613    ggml_webgpu_hash_combine(seed, key.k_type);
614    ggml_webgpu_hash_combine(seed, key.v_type);
615    ggml_webgpu_hash_combine(seed, key.dst_type);
616    ggml_webgpu_hash_combine(seed, key.head_dim_qk);
617    ggml_webgpu_hash_combine(seed, key.head_dim_v);
618    ggml_webgpu_hash_combine(seed, key.k_direct);
619    ggml_webgpu_hash_combine(seed, key.v_direct);
620    ggml_webgpu_hash_combine(seed, key.kv_overlap);
621    ggml_webgpu_hash_combine(seed, key.has_mask);
622    ggml_webgpu_hash_combine(seed, key.has_sinks);
623    ggml_webgpu_hash_combine(seed, key.uses_logit_softcap);
624}
625
626struct ggml_webgpu_flash_attn_vec_pipeline_key {
627    ggml_webgpu_flash_attn_common_pipeline_key common;
628
629    bool operator==(const ggml_webgpu_flash_attn_vec_pipeline_key & other) const { return common == other.common; }
630};
631
632struct ggml_webgpu_flash_attn_vec_pipeline_key_hash {
633    size_t operator()(const ggml_webgpu_flash_attn_vec_pipeline_key & key) const {
634        size_t seed = 0;
635        ggml_webgpu_flash_attn_hash_common_pipeline_key(seed, key.common);
636        return seed;
637    }
638};
639
640struct ggml_webgpu_flash_attn_pipeline_key {
641    ggml_webgpu_flash_attn_common_pipeline_key common;
642    bool                                       use_sg_matrix;
643
644    bool operator==(const ggml_webgpu_flash_attn_pipeline_key & other) const {
645        return common == other.common && use_sg_matrix == other.use_sg_matrix;
646    }
647};
648
649struct ggml_webgpu_flash_attn_pipeline_key_hash {
650    size_t operator()(const ggml_webgpu_flash_attn_pipeline_key & key) const {
651        size_t seed = 0;
652        ggml_webgpu_flash_attn_hash_common_pipeline_key(seed, key.common);
653        ggml_webgpu_hash_combine(seed, key.use_sg_matrix);
654        return seed;
655    }
656};
657
658struct ggml_webgpu_flash_attn_vec_decisions {
659    uint32_t kv_tile = 0;
660    uint32_t wg_size = 0;
661};
662
663struct ggml_webgpu_flash_attn_decisions {
664    bool     use_sg_matrix = false;
665    uint32_t q_tile        = 0;
666    uint32_t kv_tile       = 0;
667    uint32_t wg_size       = 0;
668};
669
670inline constexpr uint32_t GGML_WEBGPU_FLASH_ATTN_TILE_KV_VEC_WIDTH = 4u;
671inline constexpr uint32_t GGML_WEBGPU_FLASH_ATTN_TILE_Q_TILE       = 4u;
672
673inline size_t ggml_webgpu_flash_attn_tensor_offset(const ggml_tensor * tensor) {
674    constexpr uintptr_t ptr_base_addr = 0x1000u;
675    const ggml_tensor * base          = tensor->view_src != nullptr ? tensor->view_src : tensor;
676    return reinterpret_cast<uintptr_t>(base->data) - ptr_base_addr + tensor->view_offs;
677}
678
679inline bool ggml_webgpu_flash_attn_float_vec4_aligned(const ggml_tensor * K, size_t storage_offset_alignment) {
680    const uint32_t offset_elems =
681        (uint32_t) ((ggml_webgpu_flash_attn_tensor_offset(K) & (storage_offset_alignment - 1)) /
682                    ggml_type_size(K->type));
683    return offset_elems % GGML_WEBGPU_FLASH_ATTN_TILE_KV_VEC_WIDTH == 0u;
684}
685
686inline bool ggml_webgpu_flash_attn_float_vec4_aligned(const ggml_tensor * K,
687                                                      const ggml_tensor * V,
688                                                      size_t              storage_offset_alignment) {
689    return ggml_webgpu_flash_attn_float_vec4_aligned(K, storage_offset_alignment) &&
690           ggml_webgpu_flash_attn_float_vec4_aligned(V, storage_offset_alignment);
691}
692
693inline bool ggml_webgpu_flash_attn_k_direct(const ggml_tensor * Q, const ggml_tensor * K, uint32_t kv_direct_align) {
694    return (K->type == GGML_TYPE_F16 || K->type == GGML_TYPE_Q8_0 || K->type == GGML_TYPE_Q4_0) &&
695           (Q->ne[0] % kv_direct_align == 0) && (K->ne[1] % GGML_WEBGPU_KV_SEQ_PAD == 0);
696}
697
698inline bool ggml_webgpu_flash_attn_v_direct(const ggml_tensor * Q, const ggml_tensor * V, uint32_t kv_direct_align) {
699    return ggml_webgpu_flash_attn_k_direct(Q, V, kv_direct_align);
700}
701
702inline ggml_webgpu_flash_attn_common_pipeline_key ggml_webgpu_flash_attn_make_common_pipeline_key(
703    const ggml_webgpu_shader_lib_context & context,
704    uint32_t                               kv_direct_align,
705    bool                                   kv_overlap) {
706    ggml_webgpu_flash_attn_common_pipeline_key key = {};
707    key.q_type                                     = context.src0->type;
708    key.k_type                                     = context.src1->type;
709    key.v_type                                     = context.src2->type;
710    key.dst_type                                   = context.dst->type;
711    key.head_dim_qk                                = (uint32_t) context.src0->ne[0];
712    key.head_dim_v                                 = (uint32_t) context.src2->ne[0];
713    key.k_direct           = ggml_webgpu_flash_attn_k_direct(context.src0, context.src1, kv_direct_align);
714    key.v_direct           = ggml_webgpu_flash_attn_v_direct(context.src0, context.src2, kv_direct_align);
715    key.kv_overlap         = kv_overlap;
716    key.has_mask           = context.src3 != nullptr;
717    key.has_sinks          = context.src4 != nullptr;
718    key.uses_logit_softcap = ggml_get_op_params_f32(context.dst, 2) != 0.0f;
719    return key;
720}
721
722inline std::vector<std::string> ggml_webgpu_flash_attn_common_defines(
723    const ggml_webgpu_flash_attn_common_pipeline_key & key,
724    std::string &                                      variant,
725    uint32_t                                           q_tile,
726    uint32_t                                           kv_tile,
727    uint32_t                                           wg_size) {
728    std::vector<std::string> defines;
729
730    switch (key.k_type) {
731        case GGML_TYPE_F32:
732            defines.push_back("K_F32");
733            break;
734        case GGML_TYPE_F16:
735            defines.push_back("K_F16");
736            break;
737        case GGML_TYPE_Q4_0:
738            defines.push_back("K_Q4_0");
739            break;
740        case GGML_TYPE_Q8_0:
741            defines.push_back("K_Q8_0");
742            break;
743        default:
744            GGML_ABORT("Unsupported K type for flash attention shader");
745    }
746    variant += std::string("_k") + ggml_type_name(key.k_type);
747
748    switch (key.v_type) {
749        case GGML_TYPE_F32:
750            defines.push_back("V_F32");
751            break;
752        case GGML_TYPE_F16:
753            defines.push_back("V_F16");
754            break;
755        case GGML_TYPE_Q4_0:
756            defines.push_back("V_Q4_0");
757            break;
758        case GGML_TYPE_Q8_0:
759            defines.push_back("V_Q8_0");
760            break;
761        default:
762            GGML_ABORT("Unsupported V type for flash attention shader");
763    }
764    variant += std::string("_v") + ggml_type_name(key.v_type);
765
766    switch (key.q_type) {
767        case GGML_TYPE_F32:
768            defines.push_back("Q_F32");
769            break;
770        case GGML_TYPE_F16:
771            defines.push_back("Q_F16");
772            break;
773        default:
774            GGML_ABORT("Unsupported Q type for flash attention shader");
775    }
776    variant += std::string("_q") + ggml_type_name(key.q_type);
777
778    switch (key.dst_type) {
779        case GGML_TYPE_F32:
780            defines.push_back("DST_F32");
781            break;
782        case GGML_TYPE_F16:
783            defines.push_back("DST_F16");
784            break;
785        default:
786            GGML_ABORT("Unsupported dst type for flash attention shader");
787    }
788    variant += std::string("_dst") + ggml_type_name(key.dst_type);
789
790    if (key.has_mask) {
791        defines.push_back("MASK");
792        variant += "_mask";
793    }
794    if (key.has_sinks) {
795        defines.push_back("SINKS");
796        variant += "_sinks";
797    }
798    if (key.uses_logit_softcap) {
799        defines.push_back("LOGIT_SOFTCAP");
800        variant += "_lgsc";
801    }
802    if (key.k_direct) {
803        defines.push_back("K_DIRECT");
804        variant += "_k_direct";
805    }
806    if (key.v_direct) {
807        defines.push_back("V_DIRECT");
808        variant += "_v_direct";
809    }
810    if (key.kv_overlap) {
811        defines.push_back("KV_OVERLAP");
812        variant += "_kv_overlap";
813    }
814
815    defines.push_back(std::string("HEAD_DIM_QK=") + std::to_string(key.head_dim_qk));
816    variant += std::string("_hsqk") + std::to_string(key.head_dim_qk);
817
818    defines.push_back(std::string("HEAD_DIM_V=") + std::to_string(key.head_dim_v));
819    variant += std::string("_hsv") + std::to_string(key.head_dim_v);
820
821    defines.push_back(std::string("Q_TILE=") + std::to_string(q_tile));
822    defines.push_back(std::string("KV_TILE=") + std::to_string(kv_tile));
823    defines.push_back(std::string("WG_SIZE=") + std::to_string(wg_size));
824
825    if (ggml_is_quantized(key.k_type) || ggml_is_quantized(key.v_type)) {
826        defines.push_back("U32_DEQUANT_HELPERS");
827        if (ggml_is_quantized(key.k_type)) {
828            defines.push_back("LOADERS_QUANTIZED_K");
829        }
830        if (ggml_is_quantized(key.v_type)) {
831            defines.push_back("LOADERS_QUANTIZED_V");
832        }
833    }
834
835    return defines;
836}
837
838struct ggml_webgpu_flash_attn_vec_reduce_pipeline_key {
839    uint32_t  head_dim_v;
840    uint32_t  wg_size;
841    ggml_type dst_type;
842};
843
844struct ggml_webgpu_flash_attn_vec_reduce_pipeline_key_hash {
845    size_t operator()(const ggml_webgpu_flash_attn_vec_reduce_pipeline_key & key) const {
846        size_t seed = 0;
847        ggml_webgpu_hash_combine(seed, key.head_dim_v);
848        ggml_webgpu_hash_combine(seed, key.wg_size);
849        ggml_webgpu_hash_combine(seed, key.dst_type);
850        return seed;
851    }
852};
853
854inline bool operator==(const ggml_webgpu_flash_attn_vec_reduce_pipeline_key & lhs,
855                       const ggml_webgpu_flash_attn_vec_reduce_pipeline_key & rhs) {
856    return lhs.head_dim_v == rhs.head_dim_v && lhs.wg_size == rhs.wg_size && lhs.dst_type == rhs.dst_type;
857}
858
859struct ggml_webgpu_flash_attn_blk_pipeline_key {
860    uint32_t kv_tile;
861
862    bool operator==(const ggml_webgpu_flash_attn_blk_pipeline_key & other) const { return kv_tile == other.kv_tile; }
863};
864
865struct ggml_webgpu_flash_attn_blk_pipeline_key_hash {
866    size_t operator()(const ggml_webgpu_flash_attn_blk_pipeline_key & key) const {
867        size_t seed = 0;
868        ggml_webgpu_hash_combine(seed, key.kv_tile);
869        return seed;
870    }
871};
872
873// Note: this will slightly overestimate memory usage for vec path
874// since row_max and exp_sum shmem are not needed.
875inline size_t ggml_webgpu_flash_attn_wg_mem_bytes(uint32_t q_tile,
876                                                  uint32_t kv_tile,
877                                                  uint32_t head_dim_qk,
878                                                  uint32_t head_dim_v,
879                                                  bool     has_mask,
880                                                  bool     kv_direct) {
881    const uint32_t max_head_dim = std::max(head_dim_qk, head_dim_v);
882    size_t         f16_elems    = 0;
883    size_t         f32_elems    = 0;
884
885    f32_elems += q_tile * head_dim_qk;        // q_shmem
886    if (!kv_direct) {
887        f32_elems += kv_tile * max_head_dim;  // kv_shmem
888    }
889    f32_elems += q_tile * head_dim_v;         // o_shmem
890    if (has_mask) {
891        f32_elems += q_tile * kv_tile;        // mask_shmem
892    }
893    f32_elems += q_tile * kv_tile;            // inter_shmem
894    f32_elems += q_tile;                      // row_max_shmem
895    f32_elems += q_tile;                      // exp_sum_shmem
896    return f16_elems * GGML_WEBGPU_F16_SIZE_BYTES + f32_elems * GGML_WEBGPU_F32_SIZE_BYTES;
897}
898
899inline uint32_t ggml_webgpu_flash_attn_max_kv_tile(size_t   limit_bytes,
900                                                   uint32_t q_tile,
901                                                   uint32_t kv_granularity,
902                                                   uint32_t head_dim_qk,
903                                                   uint32_t head_dim_v,
904                                                   bool     has_mask,
905                                                   bool     kv_direct) {
906    const size_t base_q_bytes =
907        ggml_webgpu_flash_attn_wg_mem_bytes(q_tile, 0, head_dim_qk, head_dim_v, has_mask, kv_direct);
908    if (limit_bytes <= base_q_bytes) {
909        return 0;
910    }
911    const size_t one_kv_bytes =
912        ggml_webgpu_flash_attn_wg_mem_bytes(q_tile, 1, head_dim_qk, head_dim_v, has_mask, kv_direct);
913    const size_t bytes_per_kv = one_kv_bytes - base_q_bytes;
914    if (bytes_per_kv == 0) {
915        return 0;
916    }
917    const size_t max_kv_tile = (limit_bytes - base_q_bytes) / bytes_per_kv;
918    return (uint32_t) ((max_kv_tile / kv_granularity) * kv_granularity);
919}
920
921inline uint32_t ggml_webgpu_flash_attn_get_vec_kv_tile(size_t   wg_mem_limit_bytes,
922                                                       uint32_t head_dim_qk,
923                                                       uint32_t head_dim_v,
924                                                       bool     has_mask,
925                                                       bool     kv_direct) {
926    const uint32_t max_kv_tile =
927        ggml_webgpu_flash_attn_max_kv_tile(wg_mem_limit_bytes, 1u, 1u, head_dim_qk, head_dim_v, has_mask, kv_direct);
928    GGML_ASSERT(max_kv_tile > 0);
929
930    uint32_t kv_tile = std::min(GGML_WEBGPU_FLASH_ATTN_VEC_MAX_KV_TILE, max_kv_tile);
931    if (kv_direct) {
932        kv_tile = std::min(kv_tile, GGML_WEBGPU_KV_SEQ_PAD);
933        while (GGML_WEBGPU_KV_SEQ_PAD % kv_tile != 0) {
934            kv_tile -= 1u;
935        }
936    }
937
938    return kv_tile;
939}
940
941inline bool ggml_webgpu_flash_attn_can_use_subgroup_matrix_path(bool                supports_subgroup_matrix,
942                                                                uint32_t            sg_mat_k,
943                                                                uint32_t            sg_mat_n,
944                                                                const ggml_tensor * Q,
945                                                                const ggml_tensor * V) {
946    return supports_subgroup_matrix && Q->ne[0] % sg_mat_k == 0 && V->ne[0] % sg_mat_n == 0;
947}
948
949/** Matrix Multiplication **/
950
951struct ggml_webgpu_mul_mat_vec_pipeline_key {
952    ggml_type src0_type;
953    ggml_type src1_type;
954    int       vectorized;
955    uint32_t  num_cols;
956    bool      use_mmvq;
957    bool      src_overlap;
958
959    bool operator==(const ggml_webgpu_mul_mat_vec_pipeline_key & other) const {
960        return src0_type == other.src0_type && src1_type == other.src1_type && vectorized == other.vectorized &&
961               num_cols == other.num_cols && use_mmvq == other.use_mmvq && src_overlap == other.src_overlap;
962    }
963};
964
965struct ggml_webgpu_mul_mat_vec_pipeline_key_hash {
966    size_t operator()(const ggml_webgpu_mul_mat_vec_pipeline_key & key) const {
967        size_t seed = 0;
968        ggml_webgpu_hash_combine(seed, key.src0_type);
969        ggml_webgpu_hash_combine(seed, key.src1_type);
970        ggml_webgpu_hash_combine(seed, key.vectorized);
971        ggml_webgpu_hash_combine(seed, key.num_cols);
972        ggml_webgpu_hash_combine(seed, key.use_mmvq);
973        ggml_webgpu_hash_combine(seed, key.src_overlap);
974        return seed;
975    }
976};
977
978struct ggml_webgpu_mul_mat_vec_shader_decisions {
979    uint32_t wg_size;
980    uint32_t outputs_per_wg;
981    uint32_t vec_size;
982    bool     src_overlap = false;
983};
984
985struct ggml_webgpu_quantize_q8_pipeline_key {
986    ggml_type src0_type;
987
988    bool operator==(const ggml_webgpu_quantize_q8_pipeline_key & other) const { return src0_type == other.src0_type; }
989};
990
991struct ggml_webgpu_quantize_q8_pipeline_key_hash {
992    size_t operator()(const ggml_webgpu_quantize_q8_pipeline_key & key) const {
993        size_t seed = 0;
994        ggml_webgpu_hash_combine(seed, key.src0_type);
995        return seed;
996    }
997};
998
999struct ggml_webgpu_mul_mat_pipeline_key {
1000    ggml_type src0_type;
1001    ggml_type src1_type;
1002    int       vectorized;
1003    int       use_subgroup_matrix;
1004    bool      src_overlap;
1005
1006    bool operator==(const ggml_webgpu_mul_mat_pipeline_key & other) const {
1007        return src0_type == other.src0_type && src1_type == other.src1_type && vectorized == other.vectorized &&
1008               use_subgroup_matrix == other.use_subgroup_matrix && src_overlap == other.src_overlap;
1009    }
1010};
1011
1012struct ggml_webgpu_mul_mat_pipeline_key_hash {
1013    size_t operator()(const ggml_webgpu_mul_mat_pipeline_key & key) const {
1014        size_t seed = 0;
1015        ggml_webgpu_hash_combine(seed, key.src0_type);
1016        ggml_webgpu_hash_combine(seed, key.src1_type);
1017        ggml_webgpu_hash_combine(seed, key.vectorized);
1018        ggml_webgpu_hash_combine(seed, key.use_subgroup_matrix);
1019        ggml_webgpu_hash_combine(seed, key.src_overlap);
1020        return seed;
1021    }
1022};
1023
1024struct ggml_webgpu_mul_mat_shader_decisions {
1025    uint32_t tile_k;
1026    uint32_t wg_size_m;
1027    uint32_t wg_size_n;
1028    uint32_t wg_size;
1029    uint32_t outputs_per_wg;
1030    int      use_subgroup_matrix;
1031
1032    uint32_t tile_m;
1033    uint32_t tile_n;
1034
1035    // Subgroup matrix parameters
1036    uint32_t subgroup_m;
1037    uint32_t subgroup_n;
1038    uint32_t subgroup_matrix_m;
1039    uint32_t subgroup_matrix_n;
1040
1041    uint32_t mul_mat_wg_size;
1042    bool     src_overlap = false;
1043};
1044
1045/** MUL_MAT_ID **/
1046
1047struct ggml_webgpu_mul_mat_id_pipeline_key {
1048    ggml_type src0_type;
1049    ggml_type src1_type;
1050    uint32_t  n_experts;
1051    uint32_t  num_cols;
1052    int       vectorized;
1053
1054    bool operator==(const ggml_webgpu_mul_mat_id_pipeline_key & other) const {
1055        return src0_type == other.src0_type && src1_type == other.src1_type && n_experts == other.n_experts &&
1056               num_cols == other.num_cols && vectorized == other.vectorized;
1057    }
1058};
1059
1060struct ggml_webgpu_mul_mat_id_pipeline_key_hash {
1061    size_t operator()(const ggml_webgpu_mul_mat_id_pipeline_key & key) const {
1062        size_t seed = 0;
1063        ggml_webgpu_hash_combine(seed, key.src0_type);
1064        ggml_webgpu_hash_combine(seed, key.src1_type);
1065        ggml_webgpu_hash_combine(seed, key.n_experts);
1066        ggml_webgpu_hash_combine(seed, key.num_cols);
1067        ggml_webgpu_hash_combine(seed, key.vectorized);
1068        return seed;
1069    }
1070};
1071
1072/** Cpy **/
1073
1074struct ggml_webgpu_cpy_pipeline_key {
1075    ggml_type src_type;
1076    ggml_type dst_type;
1077
1078    bool operator==(const ggml_webgpu_cpy_pipeline_key & other) const {
1079        return src_type == other.src_type && dst_type == other.dst_type;
1080    }
1081};
1082
1083struct ggml_webgpu_cpy_pipeline_key_hash {
1084    size_t operator()(const ggml_webgpu_cpy_pipeline_key & key) const {
1085        size_t seed = 0;
1086        ggml_webgpu_hash_combine(seed, key.src_type);
1087        ggml_webgpu_hash_combine(seed, key.dst_type);
1088        return seed;
1089    }
1090};
1091
1092/** Glu **/
1093
1094struct ggml_webgpu_glu_pipeline_key {
1095    ggml_glu_op glu_op;
1096    ggml_type   type;
1097    bool        split;
1098    bool        src_overlap;
1099
1100    bool operator==(const ggml_webgpu_glu_pipeline_key & other) const {
1101        return glu_op == other.glu_op && type == other.type && split == other.split && src_overlap == other.src_overlap;
1102    }
1103};
1104
1105struct ggml_webgpu_glu_pipeline_key_hash {
1106    size_t operator()(const ggml_webgpu_glu_pipeline_key & key) const {
1107        size_t seed = 0;
1108        ggml_webgpu_hash_combine(seed, key.glu_op);
1109        ggml_webgpu_hash_combine(seed, key.type);
1110        ggml_webgpu_hash_combine(seed, key.split);
1111        ggml_webgpu_hash_combine(seed, key.src_overlap);
1112        return seed;
1113    }
1114};
1115
1116/** Rope **/
1117
1118struct ggml_webgpu_rope_pipeline_key {
1119    ggml_type type;
1120    bool      inplace;
1121    bool      has_ff;
1122
1123    bool operator==(const ggml_webgpu_rope_pipeline_key & other) const {
1124        return type == other.type && inplace == other.inplace && has_ff == other.has_ff;
1125    }
1126};
1127
1128struct ggml_webgpu_rope_pipeline_key_hash {
1129    size_t operator()(const ggml_webgpu_rope_pipeline_key & key) const {
1130        size_t seed = 0;
1131        ggml_webgpu_hash_combine(seed, key.type);
1132        ggml_webgpu_hash_combine(seed, key.inplace);
1133        ggml_webgpu_hash_combine(seed, key.has_ff);
1134        return seed;
1135    }
1136};
1137
1138/** SoftMax **/
1139
1140struct ggml_webgpu_soft_max_pipeline_key {
1141    ggml_type mask_type;
1142    bool      has_mask;
1143    bool      has_sink;
1144    bool      inplace;
1145
1146    bool operator==(const ggml_webgpu_soft_max_pipeline_key & other) const {
1147        return mask_type == other.mask_type && has_mask == other.has_mask && has_sink == other.has_sink &&
1148               inplace == other.inplace;
1149    }
1150};
1151
1152struct ggml_webgpu_soft_max_pipeline_key_hash {
1153    size_t operator()(const ggml_webgpu_soft_max_pipeline_key & key) const {
1154        size_t seed = 0;
1155        ggml_webgpu_hash_combine(seed, key.mask_type);
1156        ggml_webgpu_hash_combine(seed, key.has_mask);
1157        ggml_webgpu_hash_combine(seed, key.has_sink);
1158        ggml_webgpu_hash_combine(seed, key.inplace);
1159        return seed;
1160    }
1161};
1162
1163/** MMVQ **/
1164
1165inline bool ggml_webgpu_can_use_mmvq(const ggml_tensor * src0,
1166                                     const ggml_tensor * src1,
1167                                     bool                supports_dot_product,
1168                                     const std::string & vendor) {
1169    if (src1->ne[1] <= 4) {
1170        bool supports_dp4a = vendor == "amd" || vendor == "intel" || vendor == "nvidia";
1171        if (supports_dp4a && supports_dot_product) {
1172            switch (src1->type) {
1173                case GGML_TYPE_F32:
1174                    switch (src0->type) {
1175                        case GGML_TYPE_Q4_0:
1176                        case GGML_TYPE_Q4_1:
1177                        case GGML_TYPE_Q8_0:
1178                        case GGML_TYPE_Q2_K:
1179                        case GGML_TYPE_Q4_K:
1180                            return src0->ne[0] % 4 == 0;
1181                        default:
1182                            break;
1183                    }
1184                    break;
1185                default:
1186                    break;
1187            }
1188        }
1189    }
1190    return false;
1191}
1192
1193class ggml_webgpu_shader_lib {
1194    wgpu::Device           device;
1195    pre_wgsl::Preprocessor preprocessor;
1196
1197    std::unordered_map<int, webgpu_pipeline> sum_rows_pipelines;       // key is fixed, no variants yet
1198    std::unordered_map<int, webgpu_pipeline> argmax_pipelines;         // key is vec4
1199    std::unordered_map<int, webgpu_pipeline> argsort_pipelines;        // key is order
1200    std::unordered_map<int, webgpu_pipeline> argsort_merge_pipelines;  // key is order
1201    std::unordered_map<int, webgpu_pipeline> cumsum_pipelines;         // key is fixed, no variants yet
1202    std::unordered_map<ggml_webgpu_row_norm_pipeline_key, webgpu_pipeline, ggml_webgpu_row_norm_pipeline_key_hash>
1203        row_norm_pipelines;                                            // op/inplace
1204
1205    std::unordered_map<ggml_webgpu_get_rows_pipeline_key, webgpu_pipeline, ggml_webgpu_get_rows_pipeline_key_hash>
1206        get_rows_pipelines;   // src_type, vectorized
1207    std::unordered_map<ggml_webgpu_unary_pipeline_key, webgpu_pipeline, ggml_webgpu_unary_pipeline_key_hash>
1208        unary_pipelines;      // type/op/inplace
1209    std::unordered_map<ggml_webgpu_scale_pipeline_key, webgpu_pipeline, ggml_webgpu_scale_pipeline_key_hash>
1210        scale_pipelines;      // inplace
1211    std::unordered_map<ggml_webgpu_solve_tri_pipeline_key, webgpu_pipeline, ggml_webgpu_solve_tri_pipeline_key_hash>
1212        solve_tri_pipelines;  // type
1213    std::unordered_map<ggml_webgpu_ssm_conv_pipeline_key, webgpu_pipeline, ggml_webgpu_ssm_conv_pipeline_key_hash>
1214        ssm_conv_pipelines;   // type/vectorized
1215    std::unordered_map<ggml_webgpu_ssm_scan_pipeline_key, webgpu_pipeline, ggml_webgpu_ssm_scan_pipeline_key_hash>
1216        ssm_scan_pipelines;   // type/d_state
1217    std::unordered_map<ggml_webgpu_gated_delta_net_pipeline_key,
1218                       webgpu_pipeline,
1219                       ggml_webgpu_gated_delta_net_pipeline_key_hash>
1220        gated_delta_net_pipelines;  // type/S_v/kda
1221    std::unordered_map<ggml_webgpu_pad_pipeline_key, webgpu_pipeline, ggml_webgpu_pad_pipeline_key_hash>
1222        pad_pipelines;              // circular/non-circular
1223    std::unordered_map<ggml_webgpu_binary_pipeline_key, webgpu_pipeline, ggml_webgpu_binary_pipeline_key_hash>
1224        binary_pipelines;           // type/op/inplace/overlap/src_overlap
1225    std::unordered_map<ggml_webgpu_add_id_pipeline_key, webgpu_pipeline, ggml_webgpu_add_id_pipeline_key_hash>
1226        add_id_pipelines;           // inplace
1227    std::unordered_map<ggml_webgpu_concat_pipeline_key, webgpu_pipeline, ggml_webgpu_concat_pipeline_key_hash>
1228        concat_pipelines;           // type
1229    std::unordered_map<ggml_webgpu_repeat_pipeline_key, webgpu_pipeline, ggml_webgpu_repeat_pipeline_key_hash>
1230        repeat_pipelines;           // type
1231    std::unordered_map<ggml_webgpu_flash_attn_vec_pipeline_key,
1232                       webgpu_pipeline,
1233                       ggml_webgpu_flash_attn_vec_pipeline_key_hash>
1234        flash_attn_vec_pipelines;
1235    std::unordered_map<ggml_webgpu_flash_attn_pipeline_key, webgpu_pipeline, ggml_webgpu_flash_attn_pipeline_key_hash>
1236        flash_attn_pipelines;
1237    std::unordered_map<ggml_webgpu_flash_attn_vec_reduce_pipeline_key,
1238                       webgpu_pipeline,
1239                       ggml_webgpu_flash_attn_vec_reduce_pipeline_key_hash>
1240        flash_attn_vec_reduce_pipelines;
1241    std::unordered_map<ggml_webgpu_flash_attn_blk_pipeline_key,
1242                       webgpu_pipeline,
1243                       ggml_webgpu_flash_attn_blk_pipeline_key_hash>
1244        flash_attn_blk_pipelines;
1245    std::unordered_map<ggml_webgpu_mul_mat_vec_pipeline_key, webgpu_pipeline, ggml_webgpu_mul_mat_vec_pipeline_key_hash>
1246        mul_mat_vec_pipelines;   // fast mat-vec (n==1)
1247    std::unordered_map<ggml_webgpu_mul_mat_pipeline_key, webgpu_pipeline, ggml_webgpu_mul_mat_pipeline_key_hash>
1248        mul_mat_fast_pipelines;  // fast mat-mat (reg-tile or subgroup)
1249    std::unordered_map<ggml_webgpu_quantize_q8_pipeline_key, webgpu_pipeline, ggml_webgpu_quantize_q8_pipeline_key_hash>
1250                                             quantize_q8_pipelines;
1251    std::unordered_map<int, webgpu_pipeline> mul_mat_id_gather_pipelines;  // key is fixed
1252    std::unordered_map<ggml_webgpu_mul_mat_id_pipeline_key, webgpu_pipeline, ggml_webgpu_mul_mat_id_pipeline_key_hash>
1253        mul_mat_id_pipelines;                                              // src0_type/src1_type
1254    std::unordered_map<ggml_webgpu_mul_mat_id_pipeline_key, webgpu_pipeline, ggml_webgpu_mul_mat_id_pipeline_key_hash>
1255        mul_mat_id_vec_pipelines;                                          // src0_type/src1_type
1256
1257    std::unordered_map<ggml_webgpu_set_rows_pipeline_key, webgpu_pipeline, ggml_webgpu_set_rows_pipeline_key_hash>
1258        set_rows_pipelines;
1259    std::unordered_map<ggml_webgpu_set_pipeline_key, webgpu_pipeline, ggml_webgpu_set_pipeline_key_hash> set_pipelines;
1260    std::unordered_map<ggml_webgpu_cpy_pipeline_key, webgpu_pipeline, ggml_webgpu_cpy_pipeline_key_hash> cpy_pipelines;
1261    std::unordered_map<ggml_webgpu_glu_pipeline_key, webgpu_pipeline, ggml_webgpu_glu_pipeline_key_hash> glu_pipelines;
1262    std::unordered_map<ggml_webgpu_rope_pipeline_key, webgpu_pipeline, ggml_webgpu_rope_pipeline_key_hash>
1263        rope_pipelines;
1264    std::unordered_map<ggml_webgpu_soft_max_pipeline_key, webgpu_pipeline, ggml_webgpu_soft_max_pipeline_key_hash>
1265        soft_max_pipelines;
1266    std::unordered_map<ggml_webgpu_conv2d_pipeline_key, webgpu_pipeline, ggml_webgpu_conv2d_pipeline_key_hash>
1267        conv2d_pipelines;
1268    std::unordered_map<ggml_webgpu_conv2d_dw_pipeline_key, webgpu_pipeline, ggml_webgpu_conv2d_dw_pipeline_key_hash>
1269        conv2d_dw_pipelines;
1270    std::unordered_map<ggml_webgpu_im2col_pipeline_key, webgpu_pipeline, ggml_webgpu_im2col_pipeline_key_hash>
1271        im2col_pipelines;
1272
1273    std::unordered_map<ggml_webgpu_rms_norm_mul_pipeline_key,
1274                       webgpu_pipeline,
1275                       ggml_webgpu_rms_norm_mul_pipeline_key_hash>
1276        rms_norm_mul_pipelines;
1277    std::unordered_map<ggml_webgpu_upscale_pipeline_key, webgpu_pipeline, ggml_webgpu_upscale_pipeline_key_hash>
1278        upscale_pipelines;
1279
1280  public:
1281    ggml_webgpu_shader_lib(wgpu::Device device) { this->device = device; }
1282
1283    webgpu_pipeline get_sum_rows_pipeline(const ggml_webgpu_shader_lib_context & context) {
1284        auto it = sum_rows_pipelines.find(1);
1285        if (it != sum_rows_pipelines.end()) {
1286            return it->second;
1287        }
1288        std::vector<std::string> defines;
1289        defines.push_back(std::string("WG_SIZE=") + std::to_string(context.max_wg_size));
1290
1291        auto processed        = preprocessor.preprocess(wgsl_sum_rows, defines);
1292        sum_rows_pipelines[1] = ggml_webgpu_create_pipeline(device, processed, "sum_rows");
1293        return sum_rows_pipelines[1];
1294    }
1295
1296    webgpu_pipeline get_row_norm_pipeline(const ggml_webgpu_shader_lib_context & context) {
1297        ggml_webgpu_row_norm_pipeline_key key = {};
1298        key.op                                = context.dst->op;
1299        key.src_type                          = context.src0->type;
1300        key.dst_type                          = context.dst->type;
1301        key.inplace                           = ggml_webgpu_tensor_equal(context.src0, context.dst);
1302
1303        auto it = row_norm_pipelines.find(key);
1304        if (it != row_norm_pipelines.end()) {
1305            return it->second;
1306        }
1307        std::vector<std::string> defines;
1308        std::string              variant;
1309
1310        switch (key.op) {
1311            case GGML_OP_RMS_NORM:
1312                defines.push_back("RMS_NORM");
1313                variant = "rms_norm";
1314                break;
1315            case GGML_OP_NORM:
1316                defines.push_back("NORM");
1317                variant = "norm";
1318                break;
1319            case GGML_OP_L2_NORM:
1320                defines.push_back("L2_NORM");
1321                variant = "l2_norm";
1322                break;
1323            default:
1324                GGML_ABORT("Unsupported op for row_norm shader");
1325        }
1326
1327        if (key.inplace) {
1328            defines.push_back("INPLACE");
1329            variant += "_inplace";
1330        }
1331
1332        if (key.src_type == GGML_TYPE_F32) {
1333            defines.push_back("SRC_F32");
1334            variant += "_src_f32";
1335        } else if (key.src_type == GGML_TYPE_F16) {
1336            defines.push_back("SRC_F16");
1337            variant += "_src_f16";
1338        }
1339
1340        if (key.dst_type == GGML_TYPE_F32) {
1341            defines.push_back("DST_F32");
1342            variant += "_dst_f32";
1343        } else if (key.dst_type == GGML_TYPE_F16) {
1344            defines.push_back("DST_F16");
1345            variant += "_dst_f16";
1346        }
1347
1348        const uint32_t row_norm_wg_size = 128u;
1349        uint32_t       wg_size          = std::min(context.max_wg_size, row_norm_wg_size);
1350        defines.push_back(std::string("WG_SIZE=") + std::to_string(wg_size));
1351        auto processed                  = preprocessor.preprocess(wgsl_row_norm, defines);
1352        auto decisions                  = std::make_shared<ggml_webgpu_generic_shader_decisions>();
1353        decisions->wg_size              = wg_size;
1354        decisions->inplace              = key.inplace;
1355        row_norm_pipelines[key]         = ggml_webgpu_create_pipeline(device, processed, variant);
1356        row_norm_pipelines[key].context = decisions;
1357        return row_norm_pipelines[key];
1358    }
1359
1360    webgpu_pipeline get_argmax_pipeline(const ggml_webgpu_shader_lib_context & context) {
1361        bool vec4 = context.src0->ne[0] % 4 == 0;
1362
1363        auto it = argmax_pipelines.find(vec4);
1364        if (it != argmax_pipelines.end()) {
1365            return it->second;
1366        }
1367        std::string              variant = "argmax";
1368        std::vector<std::string> defines;
1369        defines.push_back(std::string("WG_SIZE=") + std::to_string(context.max_wg_size));
1370        if (vec4) {
1371            defines.push_back("VEC4");
1372            variant += "_vec4";
1373        }
1374
1375        auto processed         = preprocessor.preprocess(wgsl_argmax, defines);
1376        argmax_pipelines[vec4] = ggml_webgpu_create_pipeline(device, processed, variant);
1377        return argmax_pipelines.at(vec4);
1378    }
1379
1380    webgpu_pipeline get_set_rows_pipeline(const ggml_webgpu_shader_lib_context & context) {
1381        const bool                        quantized = ggml_is_quantized(context.dst->type);
1382        ggml_webgpu_set_rows_pipeline_key key       = {};
1383        key.dst_type                                = context.dst->type;
1384        key.vec4 =
1385            (context.dst->type == GGML_TYPE_F32 || context.dst->type == GGML_TYPE_F16) && context.src0->ne[0] % 4 == 0;
1386        key.i64_idx     = context.src1->type == GGML_TYPE_I64;
1387        key.pair_blocks = quantized && ((context.src0->ne[0] / ggml_blck_size(context.dst->type)) % 2 == 0);
1388
1389        auto it = set_rows_pipelines.find(key);
1390        if (it != set_rows_pipelines.end()) {
1391            return it->second;
1392        }
1393
1394        std::vector<std::string> defines;
1395        std::string              variant = "set_rows";
1396
1397        switch (context.dst->type) {
1398            case GGML_TYPE_F32:
1399                defines.push_back("DST_F32");
1400                variant += "_dstf32";
1401                break;
1402            case GGML_TYPE_F16:
1403                defines.push_back("DST_F16");
1404                variant += "_dstf16";
1405                break;
1406            case GGML_TYPE_Q8_0:
1407                defines.push_back("DST_Q8_0");
1408                variant += "_dstq8_0";
1409                break;
1410            case GGML_TYPE_Q4_0:
1411                defines.push_back("DST_Q4_0");
1412                variant += "_dstq4_0";
1413                break;
1414            default:
1415                GGML_ABORT("Unsupported dst type for set_rows shader");
1416        }
1417
1418        if (key.vec4) {
1419            defines.push_back("VEC4");
1420            variant += "_vec4";
1421        }
1422        if (key.i64_idx) {
1423            defines.push_back("I64_IDX");
1424            variant += "_i64idx";
1425        }
1426        if (key.pair_blocks) {
1427            defines.push_back("PAIR_BLOCKS");
1428            variant += "_pair_blocks";
1429        }
1430
1431        defines.push_back(std::string("WG_SIZE=") + std::to_string(context.max_wg_size));
1432
1433        const auto & shader_source      = quantized ? wgsl_set_rows_quant : wgsl_set_rows;
1434        auto         processed          = preprocessor.preprocess(shader_source, defines);
1435        auto         decisions          = std::make_shared<ggml_webgpu_set_rows_shader_decisions>();
1436        decisions->vec4                 = key.vec4;
1437        decisions->i64_idx              = key.i64_idx;
1438        decisions->pair_blocks          = key.pair_blocks;
1439        decisions->wg_size              = context.max_wg_size;
1440        set_rows_pipelines[key]         = ggml_webgpu_create_pipeline(device, processed, variant);
1441        set_rows_pipelines[key].context = decisions;
1442        return set_rows_pipelines[key];
1443    }
1444
1445    webgpu_pipeline get_set_pipeline(const ggml_webgpu_shader_lib_context & context) {
1446        ggml_webgpu_set_pipeline_key key = {};
1447        key.type                         = context.dst->type;
1448        key.inplace                      = ggml_webgpu_tensor_equal(context.src0, context.dst);
1449
1450        auto it = set_pipelines.find(key);
1451        if (it != set_pipelines.end()) {
1452            return it->second;
1453        }
1454
1455        std::vector<std::string> defines;
1456        std::string              variant = "set";
1457
1458        switch (key.type) {
1459            case GGML_TYPE_F32:
1460                defines.push_back("TYPE_F32");
1461                variant += "_f32";
1462                break;
1463            case GGML_TYPE_I32:
1464                defines.push_back("TYPE_I32");
1465                variant += "_i32";
1466                break;
1467            default:
1468                GGML_ABORT("Unsupported type for set shader");
1469        }
1470
1471        if (key.inplace) {
1472            defines.push_back("INPLACE");
1473            variant += "_inplace";
1474        }
1475
1476        defines.push_back(std::string("WG_SIZE=") + std::to_string(context.max_wg_size));
1477
1478        auto processed           = preprocessor.preprocess(wgsl_set, defines);
1479        auto decisions           = std::make_shared<ggml_webgpu_generic_shader_decisions>();
1480        decisions->wg_size       = context.max_wg_size;
1481        decisions->inplace       = key.inplace;
1482        webgpu_pipeline pipeline = ggml_webgpu_create_pipeline(device, processed, variant);
1483        pipeline.context         = decisions;
1484        set_pipelines[key]       = pipeline;
1485        return set_pipelines[key];
1486    }
1487
1488    webgpu_pipeline get_cumsum_pipeline(const ggml_webgpu_shader_lib_context & context) {
1489        auto it = cumsum_pipelines.find(1);
1490        if (it != cumsum_pipelines.end()) {
1491            return it->second;
1492        }
1493
1494        std::vector<std::string> defines;
1495        defines.push_back(std::string("WG_SIZE=") + std::to_string(context.max_wg_size));
1496
1497        auto processed      = preprocessor.preprocess(wgsl_cumsum, defines);
1498        cumsum_pipelines[1] = ggml_webgpu_create_pipeline(device, processed, "cumsum");
1499        return cumsum_pipelines[1];
1500    }
1501
1502    webgpu_pipeline get_argsort_pipeline(const ggml_webgpu_shader_lib_context & context) {
1503        bool          is_top_k = context.dst->op == GGML_OP_TOP_K;
1504        // ascending order is 0, descending order is 1
1505        const int32_t order =
1506            is_top_k ? (int32_t) GGML_SORT_ORDER_DESC : (int32_t) ggml_get_op_params_i32(context.dst, 0);
1507
1508        auto it = argsort_pipelines.find(order);
1509        if (it != argsort_pipelines.end()) {
1510            return it->second;
1511        }
1512
1513        std::vector<std::string> defines;
1514        std::string              variant = "argsort";
1515        defines.push_back(std::string("ORDER=") + std::to_string(order));
1516        variant += std::string("_order") + std::to_string(order);
1517        uint32_t wg_size = 1;
1518        while (wg_size * 2 <= context.max_wg_size &&
1519               wg_size * GGML_WEBGPU_I32_SIZE_BYTES <= context.wg_mem_limit_bytes / 2) {
1520            wg_size *= 2;
1521        }
1522        defines.push_back(std::string("WG_SIZE=") + std::to_string(wg_size));
1523        auto processed                   = preprocessor.preprocess(wgsl_argsort, defines);
1524        auto decisions                   = std::make_shared<ggml_webgpu_generic_shader_decisions>();
1525        decisions->wg_size               = wg_size;
1526        argsort_pipelines[order]         = ggml_webgpu_create_pipeline(device, processed, variant);
1527        argsort_pipelines[order].context = decisions;
1528        return argsort_pipelines[order];
1529    }
1530
1531    webgpu_pipeline get_argsort_merge_pipeline(const ggml_webgpu_shader_lib_context & context) {
1532        bool          is_top_k = context.dst->op == GGML_OP_TOP_K;
1533        // ascending order is 0, descending order is 1
1534        const int32_t order =
1535            is_top_k ? (int32_t) GGML_SORT_ORDER_DESC : (int32_t) ggml_get_op_params_i32(context.dst, 0);
1536
1537        auto it = argsort_merge_pipelines.find(order);
1538        if (it != argsort_merge_pipelines.end()) {
1539            return it->second;
1540        }
1541
1542        std::vector<std::string> defines;
1543        std::string              variant = "argsort_merge";
1544        defines.push_back(std::string("ORDER=") + std::to_string(order));
1545        variant += std::string("_order") + std::to_string(order);
1546        uint32_t wg_size = std::min(GGML_WEBGPU_ARGSORT_MERGE_MAX_WG_SIZE, context.max_wg_size);
1547        defines.push_back(std::string("WG_SIZE=") + std::to_string(wg_size));
1548
1549        auto processed                 = preprocessor.preprocess(wgsl_argsort_merge, defines);
1550        argsort_merge_pipelines[order] = ggml_webgpu_create_pipeline(device, processed, variant);
1551        return argsort_merge_pipelines[order];
1552    }
1553
1554    webgpu_pipeline get_get_rows_pipeline(const ggml_webgpu_shader_lib_context & context) {
1555        const bool vectorized                 = context.src0->type == GGML_TYPE_F32 && context.dst->ne[0] % 4 == 0;
1556        ggml_webgpu_get_rows_pipeline_key key = {};
1557        key.src_type                          = context.src0->type;
1558        key.vectorized                        = (int) vectorized;
1559
1560        auto it = get_rows_pipelines.find(key);
1561        if (it != get_rows_pipelines.end()) {
1562            return it->second;
1563        }
1564
1565        std::vector<std::string> defines;
1566        std::string              variant = "get_rows";
1567
1568        const struct ggml_type_traits * type_traits = ggml_get_type_traits(key.src_type);
1569        const char *                    type_str    = type_traits->type_name;
1570
1571        switch (key.src_type) {
1572            case GGML_TYPE_F32:
1573                defines.push_back("FLOAT_PARALLEL");
1574                if (key.vectorized) {
1575                    defines.push_back("F32_VEC");
1576                    defines.push_back("SRC_TYPE=vec4<f32>");
1577                    defines.push_back("DST_TYPE=vec4<f32>");
1578                    defines.push_back("BLOCK_SIZE=4u");
1579                } else {
1580                    defines.push_back("F32");
1581                    defines.push_back("SRC_TYPE=f32");
1582                    defines.push_back("DST_TYPE=f32");
1583                    defines.push_back("BLOCK_SIZE=1u");
1584                }
1585                variant += "_f32";
1586                break;
1587            case GGML_TYPE_F16:
1588                defines.push_back("FLOAT_PARALLEL");
1589                defines.push_back("F16");
1590                defines.push_back("SRC_TYPE=f16");
1591                defines.push_back("DST_TYPE=f32");
1592                defines.push_back("BLOCK_SIZE=1u");
1593                variant += "_f16";
1594                break;
1595            case GGML_TYPE_I32:
1596                defines.push_back("FLOAT_PARALLEL");
1597                defines.push_back("I32");
1598                defines.push_back("SRC_TYPE=i32");
1599                defines.push_back("DST_TYPE=i32");
1600                defines.push_back("BLOCK_SIZE=1u");
1601                variant += "_i32";
1602                break;
1603            default:
1604                {
1605                    std::string type_upper = type_str;
1606                    std::transform(type_upper.begin(), type_upper.end(), type_upper.begin(), ::toupper);
1607
1608                    switch (key.src_type) {
1609                        case GGML_TYPE_Q1_0:
1610                        case GGML_TYPE_Q4_0:
1611                        case GGML_TYPE_Q5_0:
1612                        case GGML_TYPE_Q8_0:
1613                        case GGML_TYPE_Q3_K:
1614                        case GGML_TYPE_Q6_K:
1615                        case GGML_TYPE_IQ2_XXS:
1616                        case GGML_TYPE_IQ2_XS:
1617                        case GGML_TYPE_IQ2_S:
1618                        case GGML_TYPE_IQ3_XXS:
1619                        case GGML_TYPE_IQ3_S:
1620                        case GGML_TYPE_IQ1_S:
1621                        case GGML_TYPE_IQ4_NL:
1622                        case GGML_TYPE_MXFP4:
1623                        case GGML_TYPE_NVFP4:
1624                            {
1625                                // Quantized types using u32 buffers for portability.
1626                                defines.push_back("SRC_TYPE=u32");
1627                                defines.push_back("U32_DEQUANT_HELPERS");
1628                                break;
1629                            }
1630                        default:
1631                            {
1632                                defines.push_back(std::string("SRC_TYPE=") + type_str);
1633                            }
1634                    }
1635
1636                    defines.push_back("BYTE_HELPERS");
1637                    defines.push_back(type_upper + "_T");
1638                    defines.push_back(type_upper);
1639                    defines.push_back(type_upper + "_SCALE_MIN");
1640                    defines.push_back(type_upper + "_TABLES");
1641                    defines.push_back(type_upper + "_GRID");
1642                    defines.push_back(type_upper + "_LUT");
1643
1644                    variant += "_";
1645                    variant += type_str;
1646
1647                    defines.push_back("DST_TYPE=f32");
1648
1649                    if (key.src_type == GGML_TYPE_Q1_0) {
1650                        defines.push_back("BLOCK_SIZE=128u");
1651                    } else if ((key.src_type >= GGML_TYPE_Q4_0 && key.src_type <= GGML_TYPE_Q8_1) ||
1652                               key.src_type == GGML_TYPE_IQ4_NL || key.src_type == GGML_TYPE_MXFP4) {
1653                        defines.push_back("BLOCK_SIZE=32u");
1654                    } else if (key.src_type == GGML_TYPE_NVFP4) {
1655                        defines.push_back("BLOCK_SIZE=64u");
1656                    } else if (key.src_type >= GGML_TYPE_Q2_K) {
1657                        defines.push_back("BLOCK_SIZE=256u");
1658                    } else {
1659                        defines.push_back("BLOCK_SIZE=1u");
1660                    }
1661                    break;
1662                }
1663        }
1664
1665        if (key.vectorized) {
1666            variant += "_vec";
1667        }
1668
1669        defines.push_back("WG_SIZE=" + std::to_string(context.max_wg_size));
1670
1671        auto processed           = preprocessor.preprocess(wgsl_get_rows, defines);
1672        auto decisions           = std::make_shared<ggml_webgpu_generic_shader_decisions>();
1673        decisions->wg_size       = context.max_wg_size;
1674        webgpu_pipeline pipeline = ggml_webgpu_create_pipeline(device, processed, variant);
1675        pipeline.context         = decisions;
1676        get_rows_pipelines[key]  = pipeline;
1677        return get_rows_pipelines[key];
1678    }
1679
1680    webgpu_pipeline get_scale_pipeline(const ggml_webgpu_shader_lib_context & context) {
1681        ggml_webgpu_scale_pipeline_key key = {};
1682        key.inplace                        = ggml_webgpu_tensor_equal(context.src0, context.dst);
1683
1684        auto it = scale_pipelines.find(key);
1685        if (it != scale_pipelines.end()) {
1686            return it->second;
1687        }
1688
1689        std::vector<std::string> defines;
1690        std::string              variant = "scale";
1691
1692        if (key.inplace) {
1693            defines.push_back("INPLACE");
1694            variant += "_inplace";
1695        }
1696
1697        defines.push_back(std::string("WG_SIZE=") + std::to_string(context.max_wg_size));
1698
1699        auto processed           = preprocessor.preprocess(wgsl_scale, defines);
1700        auto decisions           = std::make_shared<ggml_webgpu_generic_shader_decisions>();
1701        decisions->wg_size       = context.max_wg_size;
1702        decisions->inplace       = key.inplace;
1703        webgpu_pipeline pipeline = ggml_webgpu_create_pipeline(device, processed, variant);
1704        pipeline.context         = decisions;
1705        scale_pipelines[key]     = pipeline;
1706        return scale_pipelines[key];
1707    }
1708
1709    webgpu_pipeline get_solve_tri_pipeline(const ggml_webgpu_shader_lib_context & context) {
1710        ggml_webgpu_solve_tri_pipeline_key key = {};
1711        key.type                               = context.dst->type;
1712        key.n                                  = (int) context.src0->ne[0];
1713        key.k                                  = (int) context.src1->ne[0];
1714
1715        auto it = solve_tri_pipelines.find(key);
1716        if (it != solve_tri_pipelines.end()) {
1717            return it->second;
1718        }
1719
1720        std::vector<std::string> defines;
1721        std::string              variant = "solve_tri";
1722
1723        switch (key.type) {
1724            case GGML_TYPE_F32:
1725                variant += "_f32";
1726                break;
1727            default:
1728                GGML_ABORT("Unsupported type for solve_tri shader");
1729        }
1730
1731        const uint32_t wg_size       = std::min((uint32_t) key.n, context.max_wg_size);
1732        const uint32_t k_tile        = wg_size;
1733        const uint32_t bytes_per_row = ((uint32_t) key.n + wg_size) * GGML_WEBGPU_F32_SIZE_BYTES;
1734        const uint32_t batch_n       = (uint32_t) (context.wg_mem_limit_bytes / bytes_per_row);
1735
1736        defines.push_back(std::string("N=") + std::to_string(key.n));
1737        defines.push_back(std::string("WG_SIZE=") + std::to_string(wg_size));
1738        defines.push_back(std::string("K_TILE=") + std::to_string(k_tile));
1739        defines.push_back(std::string("BATCH_N=") + std::to_string(batch_n));
1740
1741        auto processed           = preprocessor.preprocess(wgsl_solve_tri, defines);
1742        auto decisions           = std::make_shared<ggml_webgpu_generic_shader_decisions>();
1743        decisions->wg_size       = wg_size;
1744        webgpu_pipeline pipeline = ggml_webgpu_create_pipeline(device, processed, variant);
1745        pipeline.context         = decisions;
1746        solve_tri_pipelines[key] = pipeline;
1747        return solve_tri_pipelines[key];
1748    }
1749
1750    webgpu_pipeline get_ssm_conv_pipeline(const ggml_webgpu_shader_lib_context & context) {
1751        ggml_webgpu_ssm_conv_pipeline_key key = {};
1752        key.type                              = context.dst->type;
1753        key.vectorized                        = context.src1->ne[0] == 4;
1754
1755        auto it = ssm_conv_pipelines.find(key);
1756        if (it != ssm_conv_pipelines.end()) {
1757            return it->second;
1758        }
1759
1760        std::vector<std::string> defines;
1761        std::string              variant = "ssm_conv";
1762
1763        switch (key.type) {
1764            case GGML_TYPE_F32:
1765                variant += "_f32";
1766                break;
1767            default:
1768                GGML_ABORT("Unsupported type for ssm_conv shader");
1769        }
1770
1771        if (key.vectorized) {
1772            defines.push_back("VECTORIZED");
1773            variant += "_vec4";
1774        }
1775
1776        constexpr uint32_t block_size    = 32u;
1777        constexpr uint32_t tokens_per_wg = 8u;
1778
1779        defines.push_back("BLOCK_SIZE=" + std::to_string(block_size) + "u");
1780        defines.push_back("TOKENS_PER_WG=" + std::to_string(tokens_per_wg) + "u");
1781
1782        auto processed           = preprocessor.preprocess(wgsl_ssm_conv, defines);
1783        auto decisions           = std::make_shared<ggml_webgpu_ssm_conv_shader_decisions>();
1784        decisions->block_size    = block_size;
1785        decisions->tokens_per_wg = tokens_per_wg;
1786        webgpu_pipeline pipeline = ggml_webgpu_create_pipeline(device, processed, variant);
1787        pipeline.context         = decisions;
1788        ssm_conv_pipelines[key]  = pipeline;
1789        return ssm_conv_pipelines[key];
1790    }
1791
1792    webgpu_pipeline get_ssm_scan_pipeline(const ggml_webgpu_shader_lib_context & context,
1793                                          bool                                   xbc_overlap,
1794                                          bool                                   a_overlap,
1795                                          bool                                   ids_overlap) {
1796        ggml_webgpu_ssm_scan_pipeline_key key = {};
1797        key.type                              = context.dst->type;
1798        key.d_state                           = (int) context.src0->ne[0];
1799        key.xbc_overlap                       = xbc_overlap;
1800        key.a_overlap                         = a_overlap;
1801        key.ids_overlap                       = ids_overlap;
1802
1803        auto it = ssm_scan_pipelines.find(key);
1804        if (it != ssm_scan_pipelines.end()) {
1805            return it->second;
1806        }
1807
1808        std::vector<std::string> defines;
1809        std::string              variant = "ssm_scan";
1810
1811        switch (key.type) {
1812            case GGML_TYPE_F32:
1813                variant += "_f32";
1814                break;
1815            default:
1816                GGML_ABORT("Unsupported type for ssm_scan shader");
1817        }
1818
1819        const uint32_t wg_size = (uint32_t) key.d_state;
1820
1821        constexpr uint32_t tokens_per_tile = 4u;
1822
1823        defines.push_back("WG_SIZE=" + std::to_string(wg_size) + "u");
1824        defines.push_back("TOKENS_PER_TILE=" + std::to_string(tokens_per_tile) + "u");
1825
1826        if (context.supports_subgroups) {
1827            defines.push_back("USE_SUBGROUP_REDUCTION");
1828            variant += "_sg_reduce";
1829        } else {
1830            variant += "_wg_reduce";
1831        }
1832
1833        if (key.xbc_overlap) {
1834            defines.push_back("XBC_OVERLAP");
1835        }
1836        if (key.a_overlap) {
1837            defines.push_back("A_OVERLAP");
1838        }
1839        if (key.ids_overlap) {
1840            defines.push_back("IDS_OVERLAP");
1841        }
1842        variant += "_d" + std::to_string(key.d_state);
1843
1844        auto processed             = preprocessor.preprocess(wgsl_ssm_scan, defines);
1845        auto decisions             = std::make_shared<ggml_webgpu_ssm_scan_shader_decisions>();
1846        decisions->wg_size         = wg_size;
1847        decisions->tokens_per_tile = tokens_per_tile;
1848        decisions->xbc_overlap     = key.xbc_overlap;
1849        decisions->a_overlap       = key.a_overlap;
1850        decisions->ids_overlap     = key.ids_overlap;
1851        webgpu_pipeline pipeline   = ggml_webgpu_create_pipeline(device, processed, variant);
1852        pipeline.context           = decisions;
1853        ssm_scan_pipelines[key]    = pipeline;
1854        return ssm_scan_pipelines[key];
1855    }
1856
1857    webgpu_pipeline get_gated_delta_net_pipeline(const ggml_webgpu_shader_lib_context & context) {
1858        ggml_webgpu_gated_delta_net_pipeline_key key = {};
1859        key.type                                     = context.dst->type;
1860        key.s_v                                      = (int) context.src2->ne[0];
1861        key.kda                                      = context.src3->ne[0] == context.src2->ne[0];
1862
1863        auto it = gated_delta_net_pipelines.find(key);
1864        if (it != gated_delta_net_pipelines.end()) {
1865            return it->second;
1866        }
1867
1868        std::vector<std::string> defines;
1869        std::string              variant = "gated_delta_net";
1870
1871        switch (key.type) {
1872            case GGML_TYPE_F32:
1873                variant += "_f32";
1874                break;
1875            default:
1876                GGML_ABORT("Unsupported type for gated_delta_net shader");
1877        }
1878
1879        if (key.kda) {
1880            defines.push_back("KDA");
1881            variant += "_kda";
1882        }
1883
1884        defines.push_back("S_V=" + std::to_string(key.s_v) + "u");
1885        defines.push_back("WG_SIZE=" + std::to_string(key.s_v) + "u");
1886
1887        auto            processed      = preprocessor.preprocess(wgsl_gated_delta_net, defines);
1888        webgpu_pipeline pipeline       = ggml_webgpu_create_pipeline(device, processed, variant);
1889        gated_delta_net_pipelines[key] = pipeline;
1890        return gated_delta_net_pipelines[key];
1891    }
1892
1893    webgpu_pipeline get_pad_pipeline(const ggml_webgpu_shader_lib_context & context) {
1894        ggml_webgpu_pad_pipeline_key key = {};
1895        key.circular                     = ggml_get_op_params_i32(context.dst, 8) != 0;
1896
1897        auto it = pad_pipelines.find(key);
1898        if (it != pad_pipelines.end()) {
1899            return it->second;
1900        }
1901
1902        std::vector<std::string> defines;
1903        std::string              variant = "pad";
1904
1905        if (key.circular) {
1906            defines.push_back("CIRCULAR");
1907            variant += "_circular";
1908        }
1909
1910        defines.push_back(std::string("WG_SIZE=") + std::to_string(context.max_wg_size));
1911
1912        auto processed           = preprocessor.preprocess(wgsl_pad, defines);
1913        auto decisions           = std::make_shared<ggml_webgpu_generic_shader_decisions>();
1914        decisions->wg_size       = context.max_wg_size;
1915        webgpu_pipeline pipeline = ggml_webgpu_create_pipeline(device, processed, variant);
1916        pipeline.context         = decisions;
1917        pad_pipelines[key]       = pipeline;
1918        return pad_pipelines[key];
1919    }
1920
1921    webgpu_pipeline get_quantize_q8_pipeline(const ggml_webgpu_shader_lib_context & context) {
1922        ggml_webgpu_quantize_q8_pipeline_key key = {};
1923        key.src0_type                            = context.src0->type;
1924
1925        auto it = quantize_q8_pipelines.find(key);
1926        if (it != quantize_q8_pipelines.end()) {
1927            return it->second;
1928        }
1929        const char *             shader_src = wgsl_quantize_q8;
1930        std::vector<std::string> defines;
1931        std::string              variant = "quantize_q8";
1932
1933        uint32_t wg_size = WEBGPU_MUL_MAT_VEC_WG_SIZE;
1934
1935        defines.push_back("SRC1_INNER_TYPE=f32");
1936        defines.push_back(std::string("WG_SIZE=") + std::to_string(wg_size));
1937
1938        const struct ggml_type_traits * src0_traits = ggml_get_type_traits(context.src0->type);
1939        std::string                     src0_name   = src0_traits->type_name;
1940        std::string                     type_upper  = src0_name;
1941        variant += "_" + src0_name;
1942        std::transform(type_upper.begin(), type_upper.end(), type_upper.begin(), ::toupper);
1943
1944        defines.push_back("MUL_ACC_" + type_upper);
1945        defines.push_back("Q8_1_T");
1946
1947        defines.push_back(context.supports_subgroups ? "USE_SUBGROUP_REDUCTION" : "USE_WORKGROUP_REDUCTION");
1948        variant += context.supports_subgroups ? "_sg_reduce" : "_wg_reduce";
1949
1950        auto processed             = preprocessor.preprocess(shader_src, defines);
1951        auto decisions             = std::make_shared<ggml_webgpu_generic_shader_decisions>();
1952        decisions->wg_size         = wg_size;
1953        webgpu_pipeline pipeline   = ggml_webgpu_create_pipeline(device, processed, variant);
1954        pipeline.context           = decisions;
1955        quantize_q8_pipelines[key] = pipeline;
1956        return quantize_q8_pipelines[key];
1957    }
1958
1959    webgpu_pipeline get_mul_mat_vec_pipeline(const ggml_webgpu_shader_lib_context & context, bool src_overlap) {
1960        ggml_webgpu_mul_mat_vec_pipeline_key key = {};
1961        key.src0_type                            = context.src0->type;
1962        key.src1_type                            = context.src1->type;
1963        key.vectorized = (context.src0->ne[0] % 4 == 0 &&
1964                          (context.src0->type == GGML_TYPE_F32 || context.src0->type == GGML_TYPE_F16)) ?
1965                             1 :
1966                             0;
1967        key.num_cols   = context.dst->ne[1];
1968        key.use_mmvq =
1969            ggml_webgpu_can_use_mmvq(context.src0, context.src1, context.supports_dot_product, context.vendor);
1970        key.src_overlap = src_overlap;
1971
1972        auto it = mul_mat_vec_pipelines.find(key);
1973        if (it != mul_mat_vec_pipelines.end()) {
1974            return it->second;
1975        }
1976
1977        std::vector<std::string> defines;
1978        std::string              variant    = "mul_mat_vec";
1979        const char *             shader_src = wgsl_mul_mat_vec;
1980
1981        // src0 type (matrix row)
1982        switch (context.src0->type) {
1983            case GGML_TYPE_F32:
1984                defines.push_back("SRC0_INNER_TYPE=f32");
1985                defines.push_back("MUL_ACC_FLOAT");
1986                variant += "_f32";
1987                break;
1988            case GGML_TYPE_F16:
1989                defines.push_back("SRC0_INNER_TYPE=f16");
1990                defines.push_back("MUL_ACC_FLOAT");
1991                variant += "_f16";
1992                break;
1993            default:
1994                {
1995                    // Quantized types: use helpers but accumulate in f16
1996                    const struct ggml_type_traits * src0_traits = ggml_get_type_traits(context.src0->type);
1997                    std::string                     src0_name   = src0_traits->type_name;
1998                    std::string                     type_upper  = src0_name;
1999                    variant += "_" + src0_name;
2000                    std::transform(type_upper.begin(), type_upper.end(), type_upper.begin(), ::toupper);
2001
2002                    defines.push_back("BYTE_HELPERS");
2003                    defines.push_back("MUL_ACC_" + type_upper);
2004                    defines.push_back("U32_DEQUANT_HELPERS");
2005                    defines.push_back("SRC0_INNER_TYPE=u32");
2006                    switch (context.src0->type) {
2007                        case GGML_TYPE_Q8_0:
2008                        case GGML_TYPE_Q4_0:
2009                        case GGML_TYPE_Q4_1:
2010                            if (key.use_mmvq) {
2011                                defines.push_back("LEGACY_QUANTS");
2012                            }
2013                            break;
2014                        case GGML_TYPE_Q2_K:
2015                        case GGML_TYPE_Q4_K:
2016                            if (key.use_mmvq) {
2017                                defines.push_back("K_QUANTS");
2018                            }
2019                            break;
2020                        case GGML_TYPE_IQ1_S:
2021                        case GGML_TYPE_IQ1_M:
2022                        case GGML_TYPE_IQ2_S:
2023                        case GGML_TYPE_IQ3_S:
2024                        case GGML_TYPE_IQ4_NL:
2025                        case GGML_TYPE_IQ4_XS:
2026                            defines.push_back(type_upper + "_GRID");
2027                            break;
2028                        case GGML_TYPE_IQ2_XXS:
2029                        case GGML_TYPE_IQ2_XS:
2030                        case GGML_TYPE_IQ3_XXS:
2031                            defines.push_back(type_upper + "_GRID");
2032                            defines.push_back(type_upper + "_TABLES");
2033                            break;
2034                        case GGML_TYPE_MXFP4:
2035                        case GGML_TYPE_NVFP4:
2036                            defines.push_back(type_upper + "_LUT");
2037                            break;
2038                        default:
2039                            break;
2040                    }
2041                    break;
2042                }
2043        }
2044
2045        // src1 type (vector)
2046        switch (context.src1->type) {
2047            case GGML_TYPE_F32:
2048                defines.push_back("SRC1_INNER_TYPE=f32");
2049                variant += "_f32";
2050                break;
2051            case GGML_TYPE_F16:
2052                defines.push_back("SRC1_INNER_TYPE=f16");
2053                variant += "_f16";
2054                break;
2055            default:
2056                GGML_ABORT("Unsupported src1 type for mul_mat_vec shader");
2057        }
2058
2059        // VEC/SCALAR controls
2060        defines.push_back(key.vectorized ? "VEC" : "SCALAR");
2061
2062        uint32_t wg_size        = WEBGPU_MUL_MAT_VEC_WG_SIZE;
2063        uint32_t outputs_per_wg = WEBGPU_MUL_MAT_VEC_FLOAT_OUTPUTS_PER_WG;
2064
2065        if (key.src0_type == GGML_TYPE_Q1_0) {
2066            outputs_per_wg = WEBGPU_MUL_MAT_VEC_LEGACY_Q_OUTPUTS_PER_WG;
2067        } else if (key.src0_type >= GGML_TYPE_Q2_K) {
2068            outputs_per_wg = WEBGPU_MUL_MAT_VEC_K_Q_OUTPUTS_PER_WG;
2069        } else if (key.src0_type >= GGML_TYPE_Q4_0) {
2070            outputs_per_wg = WEBGPU_MUL_MAT_VEC_LEGACY_Q_OUTPUTS_PER_WG;
2071        }
2072
2073        if (key.use_mmvq) {
2074            defines.push_back("MMVQ");
2075            defines.push_back("Q8_1_T");
2076        }
2077
2078        if (key.src_overlap) {
2079            defines.push_back("SRC_OVERLAP");
2080            variant += "_src_overlap";
2081        }
2082
2083        defines.push_back(std::string("WG_SIZE=") + std::to_string(wg_size));
2084        defines.push_back(std::string("OUTPUTS_PER_WG=") + std::to_string(outputs_per_wg));
2085        defines.push_back(context.supports_subgroups ? "USE_SUBGROUP_REDUCTION" : "USE_WORKGROUP_REDUCTION");
2086        variant += context.supports_subgroups ? "_sg_reduce" : "_wg_reduce";
2087        if (key.vectorized) {
2088            variant += "_vectorized";
2089        }
2090        defines.push_back(std::string("NUM_COLS=") + std::to_string(key.num_cols));
2091
2092        auto processed            = preprocessor.preprocess(shader_src, defines);
2093        auto decisions            = std::make_shared<ggml_webgpu_mul_mat_vec_shader_decisions>();
2094        decisions->wg_size        = wg_size;
2095        decisions->outputs_per_wg = outputs_per_wg;
2096        decisions->vec_size       = key.vectorized ? 4 : 1;
2097
2098        webgpu_pipeline pipeline   = ggml_webgpu_create_pipeline(device, processed, variant);
2099        pipeline.context           = decisions;
2100        mul_mat_vec_pipelines[key] = pipeline;
2101        return mul_mat_vec_pipelines[key];
2102    }
2103
2104    webgpu_pipeline get_mul_mat_fast_pipeline(const ggml_webgpu_shader_lib_context & context, bool src_overlap) {
2105        ggml_webgpu_mul_mat_pipeline_key key = {};
2106        key.src0_type                        = context.src0->type;
2107        key.src1_type                        = context.src1->type;
2108        key.vectorized          = (context.src0->ne[0] % 4 == 0 && context.dst->ne[0] % 4 == 0 &&
2109                                   (context.src0->type == GGML_TYPE_F32 || context.src0->type == GGML_TYPE_F16)) ?
2110                                      1 :
2111                                      0;
2112        key.use_subgroup_matrix = context.supports_subgroup_matrix;
2113        key.src_overlap         = src_overlap;
2114
2115        auto it = mul_mat_fast_pipelines.find(key);
2116        if (it != mul_mat_fast_pipelines.end()) {
2117            return it->second;
2118        }
2119
2120        const char * shader_src = key.use_subgroup_matrix ? wgsl_mul_mat_subgroup_matrix : wgsl_mul_mat_reg_tile;
2121        std::vector<std::string> defines;
2122        std::string              variant = key.use_subgroup_matrix ? "mul_mat_subgroup_matrix" : "mul_mat_reg_tile";
2123
2124        // src1 type
2125        switch (context.src1->type) {
2126            case GGML_TYPE_F32:
2127                defines.push_back("SRC1_INNER_TYPE=f32");
2128                break;
2129            case GGML_TYPE_F16:
2130                defines.push_back("SRC1_INNER_TYPE=f16");
2131                break;
2132            default:
2133                GGML_ABORT("Unsupported src1 type for mul_mat fast shader");
2134        }
2135
2136        // src0 type
2137        const struct ggml_type_traits * src0_traits = ggml_get_type_traits(context.src0->type);
2138        const char *                    src0_name   = src0_traits->type_name;
2139
2140        switch (context.src0->type) {
2141            case GGML_TYPE_F32:
2142                defines.push_back("SRC0_INNER_TYPE=f32");
2143                defines.push_back("FLOAT");
2144                defines.push_back("MUL_ACC_FLOAT");
2145                defines.push_back("INIT_SRC0_SHMEM_FLOAT");
2146                defines.push_back("INIT_SRC1_SHMEM_FLOAT");
2147                variant += "_f32";
2148                break;
2149            case GGML_TYPE_F16:
2150                defines.push_back("SRC0_INNER_TYPE=f16");
2151                defines.push_back("FLOAT");
2152                defines.push_back("MUL_ACC_FLOAT");
2153                defines.push_back("INIT_SRC0_SHMEM_FLOAT");
2154                defines.push_back("INIT_SRC1_SHMEM_FLOAT");
2155                variant += "_f16";
2156                break;
2157            default:
2158                {
2159                    std::string type_upper = src0_name;
2160                    std::transform(type_upper.begin(), type_upper.end(), type_upper.begin(), ::toupper);
2161
2162                    defines.push_back("BYTE_HELPERS");
2163                    defines.push_back("MUL_ACC_" + type_upper);
2164                    defines.push_back("INIT_SRC0_SHMEM_" + type_upper);
2165                    defines.push_back("INIT_SRC1_SHMEM_FLOAT");
2166                    defines.push_back("U32_DEQUANT_HELPERS");
2167                    defines.push_back("SRC0_INNER_TYPE=u32");
2168
2169                    switch (context.src0->type) {
2170                        case GGML_TYPE_IQ1_S:
2171                        case GGML_TYPE_IQ1_M:
2172                        case GGML_TYPE_IQ4_NL:
2173                        case GGML_TYPE_IQ4_XS:
2174                            defines.push_back(type_upper + "_GRID");
2175                            break;
2176                        case GGML_TYPE_IQ2_XXS:
2177                        case GGML_TYPE_IQ2_XS:
2178                        case GGML_TYPE_IQ2_S:
2179                        case GGML_TYPE_IQ3_XXS:
2180                        case GGML_TYPE_IQ3_S:
2181                            defines.push_back(type_upper + "_GRID");
2182                            defines.push_back(type_upper + "_TABLES");
2183                            break;
2184                        case GGML_TYPE_MXFP4:
2185                        case GGML_TYPE_NVFP4:
2186                            defines.push_back(type_upper + "_LUT");
2187                            break;
2188                        default:
2189                            break;
2190                    }
2191
2192                    variant += std::string("_") + src0_name;
2193                    break;
2194                }
2195        }
2196
2197        // VEC/SCALAR controls
2198        defines.push_back(key.vectorized ? "VEC" : "SCALAR");
2199
2200        const bool is_quant = ggml_is_quantized(context.src0->type);
2201
2202        uint32_t tile_k;
2203        if (key.use_subgroup_matrix) {
2204            tile_k = is_quant ? WEBGPU_MUL_MAT_SUBGROUP_TILE_K_QUANT : WEBGPU_MUL_MAT_SUBGROUP_TILE_K_FLOAT;
2205        } else {
2206            tile_k = is_quant ? WEBGPU_MUL_MAT_REG_TILE_K_QUANT : WEBGPU_MUL_MAT_REG_TILE_K_FLOAT;
2207        }
2208
2209        // Tiles
2210        defines.push_back("TILE_M=" + std::to_string(WEBGPU_MUL_MAT_TILE_M) + "u");
2211        defines.push_back("TILE_N=" + std::to_string(WEBGPU_MUL_MAT_TILE_N) + "u");
2212
2213        // Subgroup matrix specifics
2214        if (key.use_subgroup_matrix) {
2215            defines.push_back("TILE_K=" + std::to_string(tile_k) + "u");
2216            defines.push_back("MAX_SUBGROUP_SIZE=" + std::to_string(context.max_subgroup_size) + "u");
2217            defines.push_back("SUBGROUP_M=" + std::to_string(WEBGPU_MUL_MAT_SUBGROUP_M) + "u");
2218            defines.push_back("SUBGROUP_N=" + std::to_string(WEBGPU_MUL_MAT_SUBGROUP_N) + "u");
2219            defines.push_back("SUBGROUP_MATRIX_M=" + std::to_string(WEBGPU_MUL_MAT_SUBGROUP_MATRIX_M) + "u");
2220            defines.push_back("SUBGROUP_MATRIX_N=" + std::to_string(WEBGPU_MUL_MAT_SUBGROUP_MATRIX_N) + "u");
2221            defines.push_back("SUBGROUP_MATRIX_M_SIZE=" + std::to_string(context.sg_mat_m) + "u");
2222            defines.push_back("SUBGROUP_MATRIX_N_SIZE=" + std::to_string(context.sg_mat_n) + "u");
2223            defines.push_back("SUBGROUP_MATRIX_K_SIZE=" + std::to_string(context.sg_mat_k) + "u");
2224        }
2225
2226        // variant suffix for src1 type
2227        variant += std::string("_") + (context.src1->type == GGML_TYPE_F32 ? "f32" : "f16");
2228        if (key.vectorized) {
2229            variant += "_vectorized";
2230        }
2231
2232        if (key.src_overlap) {
2233            defines.push_back("SRC_OVERLAP");
2234            variant += "_src_overlap";
2235        }
2236
2237        if (!key.use_subgroup_matrix) {
2238            defines.push_back("WORKGROUP_SIZE_M=" + std::to_string(WEBGPU_MUL_MAT_WG_SIZE_M) + "u");
2239            defines.push_back("WORKGROUP_SIZE_N=" + std::to_string(WEBGPU_MUL_MAT_WG_SIZE_N) + "u");
2240            defines.push_back("TILE_K=" + std::to_string(tile_k) + "u");
2241        }
2242
2243        auto processed = preprocessor.preprocess(shader_src, defines);
2244
2245        auto decisions                 = std::make_shared<ggml_webgpu_mul_mat_shader_decisions>();
2246        decisions->tile_k              = tile_k;
2247        decisions->tile_m              = WEBGPU_MUL_MAT_TILE_M;
2248        decisions->tile_n              = WEBGPU_MUL_MAT_TILE_N;
2249        decisions->use_subgroup_matrix = key.use_subgroup_matrix;
2250        if (key.use_subgroup_matrix) {
2251            decisions->subgroup_m        = WEBGPU_MUL_MAT_SUBGROUP_M;
2252            decisions->subgroup_n        = WEBGPU_MUL_MAT_SUBGROUP_N;
2253            decisions->subgroup_matrix_m = WEBGPU_MUL_MAT_SUBGROUP_MATRIX_M;
2254            decisions->subgroup_matrix_n = WEBGPU_MUL_MAT_SUBGROUP_MATRIX_N;
2255            decisions->wg_size           = context.max_subgroup_size;
2256        } else {
2257            decisions->wg_size_m       = WEBGPU_MUL_MAT_WG_SIZE_M;
2258            decisions->wg_size_n       = WEBGPU_MUL_MAT_WG_SIZE_N;
2259            decisions->wg_size         = WEBGPU_MUL_MAT_WG_SIZE_M * WEBGPU_MUL_MAT_WG_SIZE_N;
2260            decisions->mul_mat_wg_size = WEBGPU_MUL_MAT_WG_SIZE;
2261        }
2262
2263        webgpu_pipeline pipeline    = ggml_webgpu_create_pipeline(device, processed, variant);
2264        pipeline.context            = decisions;
2265        mul_mat_fast_pipelines[key] = pipeline;
2266        return mul_mat_fast_pipelines[key];
2267    }
2268
2269    webgpu_pipeline get_mul_mat_id_gather_pipeline(const ggml_webgpu_shader_lib_context & context) {
2270        auto it = mul_mat_id_gather_pipelines.find(1);
2271        if (it != mul_mat_id_gather_pipelines.end()) {
2272            return it->second;
2273        }
2274        std::vector<std::string> defines;
2275        defines.push_back(std::string("WG_SIZE=") + std::to_string(context.max_wg_size));
2276
2277        auto processed     = preprocessor.preprocess(wgsl_mul_mat_id_gather, defines);
2278        auto decisions     = std::make_shared<ggml_webgpu_generic_shader_decisions>();
2279        decisions->wg_size = context.max_wg_size;
2280
2281        webgpu_pipeline pipeline       = ggml_webgpu_create_pipeline(device, processed, "mul_mat_id_gather");
2282        pipeline.context               = decisions;
2283        mul_mat_id_gather_pipelines[1] = pipeline;
2284        return pipeline;
2285    }
2286
2287    webgpu_pipeline get_mul_mat_id_pipeline(const ggml_webgpu_shader_lib_context & context) {
2288        ggml_webgpu_mul_mat_id_pipeline_key key = {};
2289        key.src0_type                           = context.src0->type;
2290        key.src1_type                           = context.src1->type;
2291        key.n_experts                           = context.src0->ne[2];
2292        key.vectorized = (context.src0->ne[0] % 4 == 0 && context.src0->ne[1] % 4 == 0 &&
2293                          (context.src0->type == GGML_TYPE_F32 || context.src0->type == GGML_TYPE_F16)) ?
2294                             1 :
2295                             0;
2296
2297        auto it = mul_mat_id_pipelines.find(key);
2298        if (it != mul_mat_id_pipelines.end()) {
2299            return it->second;
2300        }
2301
2302        std::vector<std::string> defines;
2303        std::string              variant = "mul_mat_id";
2304        defines.push_back("MUL_MAT_ID");
2305
2306        // src1 type
2307        switch (context.src1->type) {
2308            case GGML_TYPE_F32:
2309                defines.push_back("SRC1_INNER_TYPE=f32");
2310                break;
2311            case GGML_TYPE_F16:
2312                defines.push_back("SRC1_INNER_TYPE=f16");
2313                break;
2314            default:
2315                GGML_ABORT("Unsupported src1 type for mul_mat fast shader");
2316        }
2317
2318        // src0 type
2319        const struct ggml_type_traits * src0_traits = ggml_get_type_traits(context.src0->type);
2320        const char *                    src0_name   = src0_traits->type_name;
2321
2322        switch (context.src0->type) {
2323            case GGML_TYPE_F32:
2324                defines.push_back("SRC0_INNER_TYPE=f32");
2325                defines.push_back("INIT_SRC0_SHMEM_FLOAT");
2326                defines.push_back("INIT_SRC1_SHMEM_FLOAT");
2327                variant += "_f32";
2328                break;
2329            case GGML_TYPE_F16:
2330                defines.push_back("SRC0_INNER_TYPE=f16");
2331                defines.push_back("INIT_SRC0_SHMEM_FLOAT");
2332                defines.push_back("INIT_SRC1_SHMEM_FLOAT");
2333                variant += "_f16";
2334                break;
2335            default:
2336                {
2337                    std::string type_upper = src0_name;
2338                    std::transform(type_upper.begin(), type_upper.end(), type_upper.begin(), ::toupper);
2339
2340                    defines.push_back("BYTE_HELPERS");
2341                    defines.push_back("INIT_SRC0_SHMEM_" + type_upper);
2342                    defines.push_back("INIT_SRC1_SHMEM_FLOAT");
2343                    defines.push_back("U32_DEQUANT_HELPERS");
2344                    defines.push_back("SRC0_INNER_TYPE=u32");
2345
2346                    switch (context.src0->type) {
2347                        case GGML_TYPE_IQ1_S:
2348                        case GGML_TYPE_IQ1_M:
2349                        case GGML_TYPE_IQ4_NL:
2350                        case GGML_TYPE_IQ4_XS:
2351                            defines.push_back(type_upper + "_GRID");
2352                            break;
2353                        case GGML_TYPE_IQ2_XXS:
2354                        case GGML_TYPE_IQ2_XS:
2355                        case GGML_TYPE_IQ2_S:
2356                        case GGML_TYPE_IQ3_XXS:
2357                        case GGML_TYPE_IQ3_S:
2358                            defines.push_back(type_upper + "_GRID");
2359                            defines.push_back(type_upper + "_TABLES");
2360                            break;
2361                        case GGML_TYPE_MXFP4:
2362                        case GGML_TYPE_NVFP4:
2363                            defines.push_back(type_upper + "_LUT");
2364                            break;
2365                        default:
2366                            break;
2367                    }
2368
2369                    variant += std::string("_") + src0_name;
2370                    break;
2371                }
2372        }
2373
2374        // VEC/SCALAR controls
2375        defines.push_back(key.vectorized ? "VEC" : "SCALAR");
2376
2377        // mul_mat_id is register-tile only.
2378        const uint32_t tile_k =
2379            ggml_is_quantized(context.src0->type) ? WEBGPU_MUL_MAT_REG_TILE_K_QUANT : WEBGPU_MUL_MAT_REG_TILE_K_FLOAT;
2380
2381        // Tiles
2382        defines.push_back("TILE_M=" + std::to_string(WEBGPU_MUL_MAT_TILE_M) + "u");
2383        defines.push_back("TILE_N=" + std::to_string(WEBGPU_MUL_MAT_TILE_N) + "u");
2384        defines.push_back("TILE_K=" + std::to_string(tile_k) + "u");
2385
2386        defines.push_back("WORKGROUP_SIZE_M=" + std::to_string(WEBGPU_MUL_MAT_WG_SIZE_M) + "u");
2387        defines.push_back("WORKGROUP_SIZE_N=" + std::to_string(WEBGPU_MUL_MAT_WG_SIZE_N) + "u");
2388
2389        // variant suffix for src1 type
2390        variant += std::string("_") + (context.src1->type == GGML_TYPE_F32 ? "f32" : "f16");
2391        if (key.vectorized) {
2392            variant += "_vectorized";
2393        }
2394
2395        auto processed = preprocessor.preprocess(wgsl_mul_mat_id, defines);
2396
2397        auto decisions       = std::make_shared<ggml_webgpu_mul_mat_shader_decisions>();
2398        decisions->tile_k    = tile_k;
2399        decisions->tile_m    = WEBGPU_MUL_MAT_TILE_M;
2400        decisions->tile_n    = WEBGPU_MUL_MAT_TILE_N;
2401        decisions->wg_size_m = WEBGPU_MUL_MAT_WG_SIZE_M;
2402        decisions->wg_size_n = WEBGPU_MUL_MAT_WG_SIZE_N;
2403        decisions->wg_size   = WEBGPU_MUL_MAT_WG_SIZE_M * WEBGPU_MUL_MAT_WG_SIZE_N;
2404
2405        webgpu_pipeline pipeline  = ggml_webgpu_create_pipeline(device, processed, variant);
2406        pipeline.context          = decisions;
2407        mul_mat_id_pipelines[key] = pipeline;
2408        return mul_mat_id_pipelines[key];
2409    }
2410
2411    webgpu_pipeline get_mul_mat_id_vec_pipeline(const ggml_webgpu_shader_lib_context & context) {
2412        ggml_webgpu_mul_mat_id_pipeline_key key = {};
2413        key.src0_type                           = context.src0->type;
2414        key.src1_type                           = context.src1->type;
2415        key.n_experts                           = context.src0->ne[2];
2416        key.vectorized = (context.src0->ne[0] % 4 == 0 &&
2417                          (context.src0->type == GGML_TYPE_F32 || context.src0->type == GGML_TYPE_F16)) ?
2418                             1 :
2419                             0;
2420
2421        auto it = mul_mat_id_vec_pipelines.find(key);
2422        if (it != mul_mat_id_vec_pipelines.end()) {
2423            return it->second;
2424        }
2425
2426        std::vector<std::string> defines;
2427        std::string              variant    = "mul_mat_id_vec";
2428        const char *             shader_src = wgsl_mul_mat_id_vec;
2429
2430        // src1 type
2431        switch (context.src1->type) {
2432            case GGML_TYPE_F32:
2433                defines.push_back("SRC1_INNER_TYPE=f32");
2434                break;
2435            case GGML_TYPE_F16:
2436                defines.push_back("SRC1_INNER_TYPE=f16");
2437                break;
2438            default:
2439                GGML_ABORT("Unsupported src1 type for mul_mat fast shader");
2440        }
2441
2442        // src0 type
2443        switch (context.src0->type) {
2444            case GGML_TYPE_F32:
2445                defines.push_back("SRC0_INNER_TYPE=f32");
2446                defines.push_back("MUL_ACC_FLOAT");
2447                variant += "_f32";
2448                break;
2449            case GGML_TYPE_F16:
2450                defines.push_back("SRC0_INNER_TYPE=f16");
2451                defines.push_back("MUL_ACC_FLOAT");
2452                variant += "_f16";
2453                break;
2454            default:
2455                {
2456                    // Quantized types: use helpers but accumulate in f16
2457                    const struct ggml_type_traits * src0_traits = ggml_get_type_traits(context.src0->type);
2458                    std::string                     src0_name   = src0_traits->type_name;
2459                    std::string                     type_upper  = src0_name;
2460                    variant += "_" + src0_name;
2461                    std::transform(type_upper.begin(), type_upper.end(), type_upper.begin(), ::toupper);
2462
2463                    defines.push_back("BYTE_HELPERS");
2464                    defines.push_back("MUL_ACC_" + type_upper);
2465                    defines.push_back("U32_DEQUANT_HELPERS");
2466                    defines.push_back("SRC0_INNER_TYPE=u32");
2467                    switch (context.src0->type) {
2468                        case GGML_TYPE_IQ1_S:
2469                        case GGML_TYPE_IQ1_M:
2470                        case GGML_TYPE_IQ2_S:
2471                        case GGML_TYPE_IQ3_S:
2472                        case GGML_TYPE_IQ4_NL:
2473                        case GGML_TYPE_IQ4_XS:
2474                            defines.push_back(type_upper + "_GRID");
2475                            break;
2476                        case GGML_TYPE_IQ2_XXS:
2477                        case GGML_TYPE_IQ2_XS:
2478                        case GGML_TYPE_IQ3_XXS:
2479                            defines.push_back(type_upper + "_GRID");
2480                            defines.push_back(type_upper + "_TABLES");
2481                            break;
2482                        case GGML_TYPE_MXFP4:
2483                        case GGML_TYPE_NVFP4:
2484                            defines.push_back(type_upper + "_LUT");
2485                            break;
2486                        default:
2487                            break;
2488                    }
2489                    break;
2490                }
2491        }
2492
2493        // VEC/SCALAR controls
2494        defines.push_back(key.vectorized ? "VEC" : "SCALAR");
2495
2496        uint32_t wg_size        = WEBGPU_MUL_MAT_VEC_WG_SIZE;
2497        uint32_t outputs_per_wg = WEBGPU_MUL_MAT_VEC_FLOAT_OUTPUTS_PER_WG;
2498
2499        if (key.src0_type == GGML_TYPE_Q1_0) {
2500            outputs_per_wg = WEBGPU_MUL_MAT_VEC_LEGACY_Q_OUTPUTS_PER_WG;
2501        } else if (key.src0_type >= GGML_TYPE_Q2_K) {
2502            outputs_per_wg = WEBGPU_MUL_MAT_VEC_K_Q_OUTPUTS_PER_WG;
2503        } else if (key.src0_type >= GGML_TYPE_Q4_0) {
2504            outputs_per_wg = WEBGPU_MUL_MAT_VEC_LEGACY_Q_OUTPUTS_PER_WG;
2505        }
2506
2507        // variant suffix for src1 type
2508        variant += std::string("_") + (context.src1->type == GGML_TYPE_F32 ? "f32" : "f16");
2509
2510        defines.push_back(std::string("WG_SIZE=") + std::to_string(wg_size));
2511        defines.push_back(std::string("OUTPUTS_PER_WG=") + std::to_string(outputs_per_wg));
2512        defines.push_back(context.supports_subgroups ? "USE_SUBGROUP_REDUCTION" : "USE_WORKGROUP_REDUCTION");
2513        variant += context.supports_subgroups ? "_sg_reduce" : "_wg_reduce";
2514        if (key.vectorized) {
2515            variant += "_vectorized";
2516        }
2517        defines.push_back(std::string("NUM_COLS=1"));
2518
2519        defines.push_back(std::string("N_EXPERTS=") + std::to_string(key.n_experts));
2520
2521        auto processed = preprocessor.preprocess(shader_src, defines);
2522
2523        auto decisions            = std::make_shared<ggml_webgpu_mul_mat_vec_shader_decisions>();
2524        decisions->wg_size        = wg_size;
2525        decisions->outputs_per_wg = outputs_per_wg;
2526
2527        webgpu_pipeline pipeline      = ggml_webgpu_create_pipeline(device, processed, variant);
2528        pipeline.context              = decisions;
2529        mul_mat_id_vec_pipelines[key] = pipeline;
2530        return mul_mat_id_vec_pipelines[key];
2531    }
2532
2533    webgpu_pipeline get_unary_pipeline(const ggml_webgpu_shader_lib_context & context) {
2534        const bool                     is_unary = context.dst->op == GGML_OP_UNARY;
2535        const int                      op       = is_unary ? (int) ggml_get_unary_op(context.dst) : context.dst->op;
2536        ggml_webgpu_unary_pipeline_key key      = {};
2537        key.type                                = context.dst->type;
2538        key.op                                  = op;
2539        key.is_unary                            = is_unary;
2540        key.inplace = ggml_webgpu_tensor_equal(context.src0, context.dst) || context.dst->op == GGML_OP_FILL;
2541        key.ttype   = (ggml_tri_type) ggml_get_op_params_i32(context.dst, 0);
2542
2543        auto it = unary_pipelines.find(key);
2544        if (it != unary_pipelines.end()) {
2545            return it->second;
2546        }
2547
2548        std::vector<std::string> defines;
2549        std::string              variant =
2550            key.is_unary ? ggml_unary_op_name((ggml_unary_op) key.op) : ggml_op_name((ggml_op) key.op);
2551        defines.push_back(variant);
2552
2553        switch (key.type) {
2554            case GGML_TYPE_F32:
2555                defines.push_back("TYPE_F32");
2556                variant += "_f32";
2557                break;
2558            case GGML_TYPE_F16:
2559                defines.push_back("TYPE_F16");
2560                variant += "_f16";
2561                break;
2562            default:
2563                GGML_ABORT("Unsupported type for unary shader");
2564        }
2565
2566        if (key.inplace) {
2567            defines.push_back("INPLACE");
2568            variant += "_inplace";
2569        }
2570
2571        if (op == GGML_OP_TRI) {
2572            switch (key.ttype) {
2573                case GGML_TRI_TYPE_LOWER:
2574                    defines.push_back("TRI_TYPE_LOWER");
2575                    variant += "_tri_type_lower";
2576                    break;
2577                case GGML_TRI_TYPE_LOWER_DIAG:
2578                    defines.push_back("TRI_TYPE_LOWER_DIAG");
2579                    variant += "_tri_type_lower_diag";
2580                    break;
2581                case GGML_TRI_TYPE_UPPER:
2582                    defines.push_back("TRI_TYPE_UPPER");
2583                    variant += "_tri_type_upper";
2584                    break;
2585                case GGML_TRI_TYPE_UPPER_DIAG:
2586                    defines.push_back("TRI_TYPE_UPPER_DIAG");
2587                    variant += "_tri_upper_diag";
2588                    break;
2589                default:
2590                    GGML_ABORT("Unsupported ggml_tri_type for unary shader");
2591            }
2592        }
2593
2594        defines.push_back(std::string("WG_SIZE=") + std::to_string(context.max_wg_size));
2595
2596        auto processed           = preprocessor.preprocess(wgsl_unary, defines);
2597        auto decisions           = std::make_shared<ggml_webgpu_generic_shader_decisions>();
2598        decisions->wg_size       = context.max_wg_size;
2599        decisions->inplace       = key.inplace;
2600        webgpu_pipeline pipeline = ggml_webgpu_create_pipeline(device, processed, variant);
2601        pipeline.context         = decisions;
2602        unary_pipelines[key]     = pipeline;
2603        return unary_pipelines[key];
2604    }
2605
2606    webgpu_pipeline get_rms_norm_mul_pipeline(const ggml_webgpu_shader_lib_context & context, bool src_overlap) {
2607        ggml_webgpu_rms_norm_mul_pipeline_key key = {};
2608        key.inplace                               = ggml_webgpu_tensor_equal(context.src0, context.dst);
2609        key.overlap                               = ggml_webgpu_tensor_equal(context.src1, context.dst);
2610        key.src_overlap                           = src_overlap;
2611
2612        auto it = rms_norm_mul_pipelines.find(key);
2613        if (it != rms_norm_mul_pipelines.end()) {
2614            return it->second;
2615        }
2616
2617        std::vector<std::string> defines;
2618        std::string              op_name = "RMS_NORM_MUL";
2619        std::string              variant = op_name;
2620
2621        if (key.inplace) {
2622            defines.push_back("INPLACE");
2623            variant += "_inplace";
2624        } else if (key.overlap) {
2625            defines.push_back("OVERLAP");
2626            variant += "_overlap";
2627        } else if (key.src_overlap) {
2628            defines.push_back("SRC_OVERLAP");
2629            variant += "_src_overlap";
2630        }
2631
2632        defines.push_back(std::string("WG_SIZE=") + std::to_string(context.max_wg_size));
2633
2634        auto processed                  = preprocessor.preprocess(wgsl_rms_norm_mul, defines);
2635        auto pipeline_decisions         = std::make_shared<ggml_webgpu_rms_norm_mul_shader_decisions>();
2636        pipeline_decisions->wg_size     = context.max_wg_size;
2637        pipeline_decisions->inplace     = key.inplace;
2638        pipeline_decisions->overlap     = key.overlap;
2639        pipeline_decisions->src_overlap = key.src_overlap;
2640        webgpu_pipeline pipeline        = ggml_webgpu_create_pipeline(device, processed, variant);
2641        pipeline.context                = pipeline_decisions;
2642        rms_norm_mul_pipelines[key]     = pipeline;
2643        return rms_norm_mul_pipelines[key];
2644    }
2645
2646    webgpu_pipeline get_binary_pipeline(const ggml_webgpu_shader_lib_context & context, bool src_overlap) {
2647        ggml_webgpu_binary_pipeline_key key = {};
2648        key.type                            = context.dst->type;
2649        key.op                              = context.dst->op;
2650        key.inplace                         = ggml_webgpu_tensor_equal(context.src0, context.dst);
2651        key.overlap                         = ggml_webgpu_tensor_equal(context.src1, context.dst);
2652        key.src_overlap                     = src_overlap;
2653
2654        auto it = binary_pipelines.find(key);
2655        if (it != binary_pipelines.end()) {
2656            return it->second;
2657        }
2658
2659        std::vector<std::string> defines;
2660        std::string              op_name = ggml_op_name((ggml_op) key.op);
2661        std::string              variant = op_name;
2662
2663        defines.push_back(std::string("OP_") + op_name);
2664
2665        switch (key.type) {
2666            case GGML_TYPE_F32:
2667                defines.push_back("TYPE_F32");
2668                variant += "_f32";
2669                break;
2670            case GGML_TYPE_F16:
2671                defines.push_back("TYPE_F16");
2672                variant += "_f16";
2673                break;
2674            default:
2675                GGML_ABORT("Unsupported type for binary shader");
2676        }
2677
2678        if (key.inplace) {
2679            defines.push_back("INPLACE");
2680            variant += "_inplace";
2681        } else if (key.overlap) {
2682            defines.push_back("OVERLAP");
2683            variant += "_overlap";
2684        } else if (key.src_overlap) {
2685            defines.push_back("SRC_OVERLAP");
2686            variant += "_src_overlap";
2687        }
2688
2689        defines.push_back(std::string("WG_SIZE=") + std::to_string(context.max_wg_size));
2690
2691        auto processed                  = preprocessor.preprocess(wgsl_binary, defines);
2692        auto pipeline_decisions         = std::make_shared<ggml_webgpu_binary_shader_decisions>();
2693        pipeline_decisions->wg_size     = context.max_wg_size;
2694        pipeline_decisions->inplace     = key.inplace;
2695        pipeline_decisions->overlap     = key.overlap;
2696        pipeline_decisions->src_overlap = key.src_overlap;
2697
2698        webgpu_pipeline pipeline = ggml_webgpu_create_pipeline(device, processed, variant);
2699        pipeline.context         = pipeline_decisions;
2700        binary_pipelines[key]    = pipeline;
2701        return binary_pipelines[key];
2702    }
2703
2704    webgpu_pipeline get_add_id_pipeline(const ggml_webgpu_shader_lib_context & context) {
2705        ggml_webgpu_add_id_pipeline_key key = {};
2706        key.inplace                         = ggml_webgpu_tensor_equal(context.src0, context.dst);
2707
2708        auto it = add_id_pipelines.find(key);
2709        if (it != add_id_pipelines.end()) {
2710            return it->second;
2711        }
2712
2713        std::vector<std::string> defines;
2714        std::string              variant    = "add_id";
2715        const char *             shader_src = wgsl_add_id;
2716
2717        if (key.inplace) {
2718            defines.push_back("INPLACE");
2719            variant += "_inplace";
2720        }
2721
2722        defines.push_back(std::string("WG_SIZE=") + std::to_string(context.max_wg_size));
2723
2724        auto processed              = preprocessor.preprocess(shader_src, defines);
2725        auto pipeline_decisions     = std::make_shared<ggml_webgpu_generic_shader_decisions>();
2726        pipeline_decisions->wg_size = context.max_wg_size;
2727        pipeline_decisions->inplace = key.inplace;
2728
2729        webgpu_pipeline pipeline = ggml_webgpu_create_pipeline(device, processed, variant);
2730        pipeline.context         = pipeline_decisions;
2731        add_id_pipelines[key]    = pipeline;
2732        return pipeline;
2733    }
2734
2735    webgpu_pipeline get_concat_pipeline(const ggml_webgpu_shader_lib_context & context, bool src_overlap) {
2736        ggml_webgpu_concat_pipeline_key key = {};
2737        key.type                            = context.dst->type;
2738        key.src_overlap                     = src_overlap;
2739
2740        auto it = concat_pipelines.find(key);
2741        if (it != concat_pipelines.end()) {
2742            return it->second;
2743        }
2744
2745        std::vector<std::string> defines;
2746        std::string              variant = "concat";
2747
2748        switch (key.type) {
2749            case GGML_TYPE_F32:
2750                defines.push_back("TYPE_F32");
2751                variant += "_f32";
2752                break;
2753            case GGML_TYPE_I32:
2754                defines.push_back("TYPE_I32");
2755                variant += "_i32";
2756                break;
2757            default:
2758                GGML_ABORT("Unsupported type for concat shader");
2759        }
2760
2761        if (key.src_overlap) {
2762            defines.push_back("SRC_OVERLAP");
2763            variant += "_src_overlap";
2764        }
2765
2766        defines.push_back(std::string("WG_SIZE=") + std::to_string(context.max_wg_size));
2767
2768        auto processed           = preprocessor.preprocess(wgsl_concat, defines);
2769        auto decisions           = std::make_shared<ggml_webgpu_binary_shader_decisions>();
2770        decisions->wg_size       = context.max_wg_size;
2771        decisions->src_overlap   = key.src_overlap;
2772        webgpu_pipeline pipeline = ggml_webgpu_create_pipeline(device, processed, variant);
2773        pipeline.context         = decisions;
2774        concat_pipelines[key]    = pipeline;
2775        return concat_pipelines[key];
2776    }
2777
2778    webgpu_pipeline get_repeat_pipeline(const ggml_webgpu_shader_lib_context & context) {
2779        ggml_webgpu_repeat_pipeline_key key = {};
2780        key.type                            = context.dst->type;
2781
2782        auto it = repeat_pipelines.find(key);
2783        if (it != repeat_pipelines.end()) {
2784            return it->second;
2785        }
2786
2787        std::vector<std::string> defines;
2788        std::string              variant = "repeat";
2789
2790        switch (key.type) {
2791            case GGML_TYPE_F32:
2792                defines.push_back("TYPE_F32");
2793                variant += "_f32";
2794                break;
2795            case GGML_TYPE_F16:
2796                defines.push_back("TYPE_F16");
2797                variant += "_f16";
2798                break;
2799            case GGML_TYPE_I32:
2800                defines.push_back("TYPE_I32");
2801                variant += "_i32";
2802                break;
2803            case GGML_TYPE_I16:
2804                defines.push_back("TYPE_I16");
2805                variant += "_i16";
2806                break;
2807            default:
2808                GGML_ABORT("Unsupported type for repeat shader");
2809        }
2810
2811        defines.push_back(std::string("WG_SIZE=") + std::to_string(context.max_wg_size));
2812
2813        auto processed           = preprocessor.preprocess(wgsl_repeat, defines);
2814        auto decisions           = std::make_shared<ggml_webgpu_generic_shader_decisions>();
2815        decisions->wg_size       = context.max_wg_size;
2816        webgpu_pipeline pipeline = ggml_webgpu_create_pipeline(device, processed, variant);
2817        pipeline.context         = decisions;
2818        repeat_pipelines[key]    = pipeline;
2819        return repeat_pipelines[key];
2820    }
2821
2822    webgpu_pipeline get_flash_attn_pipeline(const ggml_webgpu_shader_lib_context & context, bool kv_overlap) {
2823        const bool can_use_subgroup_matrix = ggml_webgpu_flash_attn_can_use_subgroup_matrix_path(
2824            context.supports_subgroup_matrix, context.sg_mat_k, context.sg_mat_n, context.src0, context.src2);
2825        ggml_webgpu_flash_attn_decisions decisions = {};
2826        decisions.use_sg_matrix                    = can_use_subgroup_matrix;
2827        decisions.q_tile = decisions.use_sg_matrix ? context.sg_mat_m : GGML_WEBGPU_FLASH_ATTN_TILE_Q_TILE;
2828
2829        ggml_webgpu_flash_attn_pipeline_key key = {};
2830        key.common                              = ggml_webgpu_flash_attn_make_common_pipeline_key(
2831            context, decisions.use_sg_matrix ? context.sg_mat_k : 1u, kv_overlap);
2832        key.common.k_direct &= decisions.use_sg_matrix && key.common.k_type == GGML_TYPE_F16;
2833        key.common.v_direct &= decisions.use_sg_matrix && key.common.v_type == GGML_TYPE_F16;
2834        key.use_sg_matrix = decisions.use_sg_matrix;
2835
2836        uint32_t max_kv_tile = ggml_webgpu_flash_attn_max_kv_tile(
2837            context.wg_mem_limit_bytes, decisions.q_tile, decisions.use_sg_matrix ? context.sg_mat_n : 1u,
2838            key.common.head_dim_qk, key.common.head_dim_v, key.common.has_mask,
2839            key.common.k_direct || key.common.v_direct);
2840
2841        // WorkGroup storage size isn't enough for some params with subgroup matrices path (ref. https://github.com/ggml-org/llama.cpp/pull/26566)
2842        if (max_kv_tile == 0) {
2843            GGML_ASSERT(decisions.use_sg_matrix);
2844            // switch to flash_attn_reg_tile path
2845            decisions.use_sg_matrix = false;
2846            decisions.q_tile        = GGML_WEBGPU_FLASH_ATTN_TILE_Q_TILE;
2847            key.common.k_direct     = false;
2848            key.common.v_direct     = false;
2849            key.use_sg_matrix       = false;
2850            max_kv_tile             = ggml_webgpu_flash_attn_max_kv_tile(
2851                context.wg_mem_limit_bytes, decisions.q_tile, 1u, key.common.head_dim_qk, key.common.head_dim_v,
2852                key.common.has_mask, key.common.k_direct || key.common.v_direct);
2853            GGML_ASSERT(max_kv_tile > 0);
2854        }
2855
2856        decisions.kv_tile = decisions.use_sg_matrix ?
2857                                std::min(max_kv_tile, context.sg_mat_n * GGML_WEBGPU_FLASH_ATTN_PREFERRED_KV_SG_TILES) :
2858                                std::min(GGML_WEBGPU_FLASH_ATTN_TILE_MAX_KV_TILE, max_kv_tile);
2859        decisions.wg_size =
2860            decisions.use_sg_matrix ?
2861                std::max(context.max_subgroup_size, GGML_WEBGPU_FLASH_ATTN_PREFERRED_WG_SIZE) :
2862                std::min(context.max_wg_size, std::max(GGML_WEBGPU_FLASH_ATTN_PREFERRED_WG_SIZE,
2863                                                       GGML_WEBGPU_FLASH_ATTN_TILE_Q_TILE * context.max_subgroup_size));
2864
2865        if (key.common.k_direct || key.common.v_direct) {
2866            decisions.kv_tile = std::min(decisions.kv_tile, GGML_WEBGPU_KV_SEQ_PAD);
2867            while (GGML_WEBGPU_KV_SEQ_PAD % decisions.kv_tile != 0) {
2868                decisions.kv_tile -= decisions.use_sg_matrix ? context.sg_mat_n : context.min_subgroup_size;
2869            }
2870        }
2871
2872        auto it = flash_attn_pipelines.find(key);
2873        if (it != flash_attn_pipelines.end()) {
2874            return it->second;
2875        }
2876
2877        std::string              variant = decisions.use_sg_matrix ? "flash_attn" : "flash_attn_tile";
2878        std::vector<std::string> defines = ggml_webgpu_flash_attn_common_defines(key.common, variant, decisions.q_tile,
2879                                                                                 decisions.kv_tile, decisions.wg_size);
2880        const char *             shader_src = nullptr;
2881        if (!key.use_sg_matrix) {
2882            shader_src = wgsl_flash_attn_tile;
2883            defines.push_back("MIN_SUBGROUP_SIZE=" + std::to_string(context.min_subgroup_size) + "u");
2884            defines.push_back("MAX_SUBGROUP_SIZE=" + std::to_string(context.max_subgroup_size) + "u");
2885            variant += "_tile_sg" + std::to_string(context.min_subgroup_size) + "_" +
2886                       std::to_string(context.max_subgroup_size);
2887        } else {
2888            shader_src = wgsl_flash_attn;
2889            defines.push_back(std::string("SG_MAT_M=") + std::to_string(context.sg_mat_m));
2890            defines.push_back(std::string("SG_MAT_N=") + std::to_string(context.sg_mat_n));
2891            defines.push_back(std::string("SG_MAT_K=") + std::to_string(context.sg_mat_k));
2892        }
2893        auto            pipeline_decisions = std::make_shared<ggml_webgpu_flash_attn_decisions>(decisions);
2894        webgpu_pipeline pipeline =
2895            ggml_webgpu_create_pipeline(device, preprocessor.preprocess(shader_src, defines), variant);
2896        pipeline.context          = pipeline_decisions;
2897        flash_attn_pipelines[key] = pipeline;
2898        return flash_attn_pipelines[key];
2899    }
2900
2901    webgpu_pipeline get_flash_attn_vec_pipeline(const ggml_webgpu_shader_lib_context & context, bool kv_overlap) {
2902        ggml_webgpu_flash_attn_vec_pipeline_key key = {};
2903        key.common = ggml_webgpu_flash_attn_make_common_pipeline_key(context, GGML_WEBGPU_FLASH_ATTN_TILE_KV_VEC_WIDTH,
2904                                                                     kv_overlap);
2905
2906        auto it = flash_attn_vec_pipelines.find(key);
2907        if (it != flash_attn_vec_pipelines.end()) {
2908            return it->second;
2909        }
2910
2911        ggml_webgpu_flash_attn_vec_decisions decisions = {};
2912        decisions.kv_tile = ggml_webgpu_flash_attn_get_vec_kv_tile(context.wg_mem_limit_bytes, key.common.head_dim_qk,
2913                                                                   key.common.head_dim_v, key.common.has_mask,
2914                                                                   key.common.k_direct || key.common.v_direct);
2915        decisions.wg_size = context.max_subgroup_size;
2916
2917        std::string              variant = "flash_attn_vec";
2918        std::vector<std::string> defines =
2919            ggml_webgpu_flash_attn_common_defines(key.common, variant, 1u, decisions.kv_tile, decisions.wg_size);
2920        if (key.common.has_mask) {
2921            defines.push_back("BLK");
2922            variant.resize(variant.size() - (sizeof("_mask") - 1));
2923            variant += "_mask_blk";
2924        }
2925
2926        uint32_t       d_split = context.min_subgroup_size;
2927        const uint32_t D       = key.common.head_dim_qk | key.common.head_dim_v;
2928        const uint32_t D_lsb   = D & (~(D - 1u));
2929        d_split                = std::min(std::min(context.min_subgroup_size, 4u), std::max(D_lsb / 4u, 1u));
2930
2931        defines.push_back(std::string("D_SPLIT=") + std::to_string(d_split));
2932        variant += "_dsplit" + std::to_string(d_split);
2933
2934        auto            pipeline_decisions = std::make_shared<ggml_webgpu_flash_attn_vec_decisions>(decisions);
2935        webgpu_pipeline pipeline =
2936            ggml_webgpu_create_pipeline(device, preprocessor.preprocess(wgsl_flash_attn_vec_split, defines), variant);
2937        pipeline.context              = pipeline_decisions;
2938        flash_attn_vec_pipelines[key] = pipeline;
2939        return flash_attn_vec_pipelines[key];
2940    }
2941
2942    webgpu_pipeline get_flash_attn_blk_pipeline(const ggml_webgpu_shader_lib_context & context, uint32_t kv_tile) {
2943        ggml_webgpu_flash_attn_blk_pipeline_key key = {};
2944        key.kv_tile                                 = kv_tile;
2945        auto it                                     = flash_attn_blk_pipelines.find(key);
2946        if (it != flash_attn_blk_pipelines.end()) {
2947            return it->second;
2948        }
2949
2950        std::vector<std::string> defines;
2951        std::string              variant = "flash_attn_vec_blk";
2952
2953        defines.push_back(std::string("KV_TILE=") + std::to_string(key.kv_tile));
2954        variant += std::string("_kvt") + std::to_string(key.kv_tile);
2955
2956        uint32_t wg_size = 1;
2957        while ((wg_size << 1) <= context.max_wg_size) {
2958            wg_size <<= 1;
2959        }
2960        defines.push_back(std::string("WG_SIZE=") + std::to_string(wg_size));
2961        variant += std::string("_wg") + std::to_string(wg_size);
2962
2963        webgpu_pipeline pipeline =
2964            ggml_webgpu_create_pipeline(device, preprocessor.preprocess(wgsl_flash_attn_vec_blk, defines), variant);
2965        flash_attn_blk_pipelines[key] = pipeline;
2966        return flash_attn_blk_pipelines[key];
2967    }
2968
2969    webgpu_pipeline get_flash_attn_vec_reduce_pipeline(const ggml_webgpu_shader_lib_context & context) {
2970        ggml_webgpu_flash_attn_vec_reduce_pipeline_key key = {};
2971        key.head_dim_v                                     = (uint32_t) context.src2->ne[0];
2972        key.dst_type                                       = context.dst->type;
2973        key.wg_size                                        = context.max_wg_size;
2974        auto it                                            = flash_attn_vec_reduce_pipelines.find(key);
2975        if (it != flash_attn_vec_reduce_pipelines.end()) {
2976            return it->second;
2977        }
2978
2979        std::vector<std::string> defines;
2980        std::string              variant = "flash_attn_vec_reduce";
2981
2982        switch (key.dst_type) {
2983            case GGML_TYPE_F32:
2984                defines.push_back("DST_F32");
2985                break;
2986            case GGML_TYPE_F16:
2987                defines.push_back("DST_F16");
2988                break;
2989            default:
2990                GGML_ABORT("Unsupported dst type for flash attention vec reduce shader");
2991        }
2992        variant += std::string("_dst") + ggml_type_name(key.dst_type);
2993
2994        defines.push_back(std::string("HEAD_DIM_V=") + std::to_string(key.head_dim_v));
2995        variant += std::string("_hsv") + std::to_string(key.head_dim_v);
2996
2997        defines.push_back(std::string("WG_SIZE=") + std::to_string(context.max_wg_size));
2998        variant += std::string("_wg") + std::to_string(context.max_wg_size);
2999
3000        webgpu_pipeline pipeline =
3001            ggml_webgpu_create_pipeline(device, preprocessor.preprocess(wgsl_flash_attn_vec_reduce, defines), variant);
3002        flash_attn_vec_reduce_pipelines[key] = pipeline;
3003        return flash_attn_vec_reduce_pipelines[key];
3004    }
3005
3006    webgpu_pipeline get_cpy_pipeline(const ggml_webgpu_shader_lib_context & context) {
3007        ggml_webgpu_cpy_pipeline_key key = {};
3008        key.src_type                     = context.src0->type;
3009        key.dst_type                     = context.dst->type;
3010
3011        auto it = cpy_pipelines.find(key);
3012        if (it != cpy_pipelines.end()) {
3013            return it->second;
3014        }
3015
3016        std::vector<std::string> defines;
3017        std::string              variant = "cpy";
3018
3019        switch (key.src_type) {
3020            case GGML_TYPE_F32:
3021                defines.push_back("SRC_F32");
3022                variant += "_f32";
3023                break;
3024            case GGML_TYPE_F16:
3025                defines.push_back("SRC_F16");
3026                variant += "_f16";
3027                break;
3028            case GGML_TYPE_I32:
3029                defines.push_back("SRC_I32");
3030                variant += "_i32";
3031                break;
3032            default:
3033                GGML_ABORT("Unsupported src type for cpy shader");
3034        }
3035
3036        switch (key.dst_type) {
3037            case GGML_TYPE_F32:
3038                defines.push_back("DST_F32");
3039                variant += "_f32";
3040                break;
3041            case GGML_TYPE_F16:
3042                defines.push_back("DST_F16");
3043                variant += "_f16";
3044                break;
3045            case GGML_TYPE_I32:
3046                defines.push_back("DST_I32");
3047                variant += "_i32";
3048                break;
3049            default:
3050                GGML_ABORT("Unsupported dst type for cpy shader");
3051        }
3052
3053        defines.push_back(std::string("WG_SIZE=") + std::to_string(context.max_wg_size));
3054
3055        auto processed           = preprocessor.preprocess(wgsl_cpy, defines);
3056        auto decisions           = std::make_shared<ggml_webgpu_generic_shader_decisions>();
3057        decisions->wg_size       = context.max_wg_size;
3058        webgpu_pipeline pipeline = ggml_webgpu_create_pipeline(device, processed, variant);
3059        pipeline.context         = decisions;
3060        cpy_pipelines[key]       = pipeline;
3061        return cpy_pipelines[key];
3062    }
3063
3064    webgpu_pipeline get_glu_pipeline(const ggml_webgpu_shader_lib_context & context, bool src_overlap) {
3065        ggml_webgpu_glu_pipeline_key key = {};
3066        key.glu_op                       = ggml_get_glu_op(context.dst);
3067        key.type                         = context.dst->type;
3068        key.split                        = (context.src1 != nullptr);
3069        key.src_overlap                  = src_overlap;
3070
3071        auto it = glu_pipelines.find(key);
3072        if (it != glu_pipelines.end()) {
3073            return it->second;
3074        }
3075
3076        std::vector<std::string> defines;
3077        std::string              variant = "glu";
3078
3079        switch (key.glu_op) {
3080            case GGML_GLU_OP_REGLU:
3081                defines.push_back("OP_REGLU");
3082                variant += "_reglu";
3083                break;
3084            case GGML_GLU_OP_GEGLU:
3085                defines.push_back("OP_GEGLU");
3086                variant += "_geglu";
3087                break;
3088            case GGML_GLU_OP_SWIGLU:
3089                defines.push_back("OP_SWIGLU");
3090                variant += "_swiglu";
3091                break;
3092            case GGML_GLU_OP_SWIGLU_OAI:
3093                defines.push_back("OP_SWIGLU_OAI");
3094                variant += "_swiglu_oai";
3095                break;
3096            case GGML_GLU_OP_GEGLU_ERF:
3097                defines.push_back("OP_GEGLU_ERF");
3098                variant += "_geglu_erf";
3099                break;
3100            case GGML_GLU_OP_GEGLU_QUICK:
3101                defines.push_back("OP_GEGLU_QUICK");
3102                variant += "_geglu_quick";
3103                break;
3104            case GGML_GLU_OP_SWIGLU_CLAMP:
3105                defines.push_back("OP_SWIGLU_CLAMP");
3106                variant += "_swiglu_clamp";
3107                break;
3108            default:
3109                GGML_ABORT("Unsupported GLU op");
3110        }
3111        switch (key.type) {
3112            case GGML_TYPE_F32:
3113                defines.push_back("TYPE_F32");
3114                variant += "_f32";
3115                break;
3116            case GGML_TYPE_F16:
3117                defines.push_back("TYPE_F16");
3118                variant += "_f16";
3119                break;
3120            default:
3121                GGML_ABORT("Unsupported type for GLU shader");
3122        }
3123
3124        if (key.src_overlap) {
3125            defines.push_back("SRC_OVERLAP");
3126            variant += "_src_overlap";
3127        } else if (key.split) {
3128            variant += "_split";
3129        } else {
3130            defines.push_back("NO_SPLIT");
3131        }
3132
3133        defines.push_back(std::string("WG_SIZE=") + std::to_string(context.max_wg_size));
3134
3135        auto processed           = preprocessor.preprocess(wgsl_glu, defines);
3136        auto decisions           = std::make_shared<ggml_webgpu_glu_shader_decisions>();
3137        decisions->wg_size       = context.max_wg_size;
3138        decisions->src_overlap   = key.src_overlap;
3139        webgpu_pipeline pipeline = ggml_webgpu_create_pipeline(device, processed, variant);
3140        pipeline.context         = decisions;
3141        glu_pipelines[key]       = pipeline;
3142        return glu_pipelines[key];
3143    }
3144
3145    webgpu_pipeline get_rope_pipeline(const ggml_webgpu_shader_lib_context & context) {
3146        ggml_webgpu_rope_pipeline_key key = {};
3147        key.type                          = context.dst->type;
3148        key.inplace                       = ggml_webgpu_tensor_equal(context.src0, context.dst);
3149        key.has_ff                        = (context.src2 != nullptr);
3150
3151        auto it = rope_pipelines.find(key);
3152        if (it != rope_pipelines.end()) {
3153            return it->second;
3154        }
3155
3156        std::vector<std::string> defines;
3157        std::string              variant = "rope";
3158
3159        switch (key.type) {
3160            case GGML_TYPE_F32:
3161                defines.push_back("TYPE_F32");
3162                variant += "_f32";
3163                break;
3164            case GGML_TYPE_F16:
3165                defines.push_back("TYPE_F16");
3166                variant += "_f16";
3167                break;
3168            default:
3169                GGML_ABORT("Unsupported type for ROPE shader");
3170        }
3171
3172        if (key.inplace) {
3173            defines.push_back("INPLACE");
3174            variant += "_inplace";
3175        }
3176
3177        if (key.has_ff) {
3178            defines.push_back("FF_FUNC");
3179            variant += "_ff";
3180        }
3181
3182        defines.push_back(std::string("WG_SIZE=") + std::to_string(context.max_wg_size));
3183
3184        auto processed           = preprocessor.preprocess(wgsl_rope, defines);
3185        auto decisions           = std::make_shared<ggml_webgpu_generic_shader_decisions>();
3186        decisions->wg_size       = context.max_wg_size;
3187        decisions->inplace       = key.inplace;
3188        webgpu_pipeline pipeline = ggml_webgpu_create_pipeline(device, processed, variant);
3189        pipeline.context         = decisions;
3190        rope_pipelines[key]      = pipeline;
3191        return rope_pipelines[key];
3192    }
3193
3194    webgpu_pipeline get_soft_max_pipeline(const ggml_webgpu_shader_lib_context & context) {
3195        ggml_webgpu_soft_max_pipeline_key key = {};
3196        key.mask_type                         = context.src1 ? context.src1->type : GGML_TYPE_F32;
3197        key.has_mask                          = (context.src1 != nullptr);
3198        key.has_sink                          = (context.src2 != nullptr);
3199        key.inplace                           = ggml_webgpu_tensor_equal(context.src0, context.dst);
3200
3201        auto it = soft_max_pipelines.find(key);
3202        if (it != soft_max_pipelines.end()) {
3203            return it->second;
3204        }
3205
3206        std::vector<std::string> defines;
3207        std::string              variant = "soft_max";
3208
3209        if (key.has_mask) {
3210            defines.push_back("HAS_MASK");
3211            switch (key.mask_type) {
3212                case GGML_TYPE_F32:
3213                    defines.push_back("MASK_F32");
3214                    variant += "_mask_f32";
3215                    break;
3216                case GGML_TYPE_F16:
3217                    defines.push_back("MASK_F16");
3218                    variant += "_mask_f16";
3219                    break;
3220                default:
3221                    GGML_ABORT("Unsupported type for SOFT_MAX shader");
3222            }
3223        }
3224
3225        if (key.has_sink) {
3226            defines.push_back("HAS_SINK");
3227            variant += "_sink";
3228        }
3229
3230        if (key.inplace) {
3231            defines.push_back("INPLACE");
3232            variant += "_inplace";
3233        }
3234
3235        defines.push_back(std::string("WG_SIZE=") + std::to_string(context.max_wg_size));
3236
3237        auto processed           = preprocessor.preprocess(wgsl_soft_max, defines);
3238        auto decisions           = std::make_shared<ggml_webgpu_generic_shader_decisions>();
3239        decisions->wg_size       = context.max_wg_size;
3240        decisions->inplace       = key.inplace;
3241        webgpu_pipeline pipeline = ggml_webgpu_create_pipeline(device, processed, variant);
3242        pipeline.context         = decisions;
3243        soft_max_pipelines[key]  = pipeline;
3244        return soft_max_pipelines[key];
3245    }
3246
3247    webgpu_pipeline get_conv2d_pipeline(const ggml_webgpu_shader_lib_context & context) {
3248        ggml_webgpu_conv2d_pipeline_key key = {};
3249        key.weight_type                     = context.src0->type;
3250        key.input_type                      = context.src1->type;
3251        key.output_type                     = context.dst->type;
3252
3253        auto it = conv2d_pipelines.find(key);
3254        if (it != conv2d_pipelines.end()) {
3255            return it->second;
3256        }
3257
3258        std::vector<std::string> defines;
3259        std::string              variant = "conv_2d";
3260
3261        auto push_type_defines = [&](const char * prefix, ggml_type type) {
3262            std::string s_prefix = prefix;
3263            if (type == GGML_TYPE_F32) {
3264                defines.push_back(s_prefix + "=f32");
3265            } else if (type == GGML_TYPE_F16) {
3266                defines.push_back(s_prefix + "=f16");
3267            } else {
3268                GGML_ABORT("Unsupported type for CONV_2D shader");
3269            }
3270        };
3271
3272        push_type_defines("WEIGHT_TYPE", key.weight_type);
3273        push_type_defines("INPUT_TYPE", key.input_type);
3274        push_type_defines("OUTPUT_TYPE", key.output_type);
3275
3276        defines.push_back(std::string("WG_SIZE=") + std::to_string(context.max_wg_size));
3277
3278        auto processed           = preprocessor.preprocess(wgsl_conv2d, defines);
3279        auto decisions           = std::make_shared<ggml_webgpu_generic_shader_decisions>();
3280        decisions->wg_size       = context.max_wg_size;
3281        webgpu_pipeline pipeline = ggml_webgpu_create_pipeline(device, processed, variant);
3282        pipeline.context         = decisions;
3283        conv2d_pipelines[key]    = pipeline;
3284        return conv2d_pipelines[key];
3285    }
3286
3287    // whcn selects the input layout: contiguous WHCN vs contiguous-channels CWHN
3288    webgpu_pipeline get_conv2d_dw_pipeline(const ggml_webgpu_shader_lib_context & context, bool whcn) {
3289        ggml_webgpu_conv2d_dw_pipeline_key key = {};
3290        key.weight_type                        = context.src0->type;
3291        key.input_type                         = context.src1->type;
3292        key.output_type                        = context.dst->type;
3293        key.whcn                               = whcn;
3294
3295        auto it = conv2d_dw_pipelines.find(key);
3296        if (it != conv2d_dw_pipelines.end()) {
3297            return it->second;
3298        }
3299
3300        std::vector<std::string> defines;
3301        std::string              variant = whcn ? "conv_2d_dw_whcn" : "conv_2d_dw_cwhn";
3302
3303        auto push_type_defines = [&](const char * prefix, ggml_type type) {
3304            std::string s_prefix = prefix;
3305            if (type == GGML_TYPE_F32) {
3306                defines.push_back(s_prefix + "=f32");
3307            } else if (type == GGML_TYPE_F16) {
3308                defines.push_back(s_prefix + "=f16");
3309            } else {
3310                GGML_ABORT("Unsupported type for CONV_2D shader");
3311            }
3312        };
3313
3314        push_type_defines("WEIGHT_TYPE", key.weight_type);
3315        push_type_defines("INPUT_TYPE", key.input_type);
3316        push_type_defines("OUTPUT_TYPE", key.output_type);
3317
3318        if (whcn) {
3319            defines.push_back("WHCN");
3320        }
3321        defines.push_back(std::string("WG_SIZE=") + std::to_string(context.max_wg_size));
3322
3323        auto processed           = preprocessor.preprocess(wgsl_conv2d_dw, defines);
3324        auto decisions           = std::make_shared<ggml_webgpu_generic_shader_decisions>();
3325        decisions->wg_size       = context.max_wg_size;
3326        webgpu_pipeline pipeline = ggml_webgpu_create_pipeline(device, processed, variant);
3327        pipeline.context         = decisions;
3328        conv2d_dw_pipelines[key] = pipeline;
3329        return conv2d_dw_pipelines[key];
3330    }
3331
3332    webgpu_pipeline get_im2col_pipeline(const ggml_webgpu_shader_lib_context & context) {
3333        ggml_webgpu_im2col_pipeline_key key = {};
3334        key.input_type                      = context.src1->type;
3335        key.output_type                     = context.dst->type;
3336
3337        auto it = im2col_pipelines.find(key);
3338        if (it != im2col_pipelines.end()) {
3339            return it->second;
3340        }
3341
3342        std::vector<std::string> defines;
3343        std::string              variant = "im2col";
3344
3345        auto push_type_defines = [&](const char * prefix, ggml_type type) {
3346            std::string s_prefix = prefix;
3347            if (type == GGML_TYPE_F32) {
3348                defines.push_back(s_prefix + "=f32");
3349            } else if (type == GGML_TYPE_F16) {
3350                defines.push_back(s_prefix + "=f16");
3351            } else {
3352                GGML_ABORT("Unsupported type for IM2COL shader");
3353            }
3354        };
3355
3356        push_type_defines("INPUT_TYPE", key.input_type);
3357        push_type_defines("OUTPUT_TYPE", key.output_type);
3358
3359        defines.push_back(std::string("WG_SIZE=") + std::to_string(context.max_wg_size));
3360
3361        auto processed           = preprocessor.preprocess(wgsl_im2col, defines);
3362        auto decisions           = std::make_shared<ggml_webgpu_generic_shader_decisions>();
3363        decisions->wg_size       = context.max_wg_size;
3364        webgpu_pipeline pipeline = ggml_webgpu_create_pipeline(device, processed, variant);
3365        pipeline.context         = decisions;
3366        im2col_pipelines[key]    = pipeline;
3367        return im2col_pipelines[key];
3368    }
3369
3370    webgpu_pipeline get_upscale_pipeline(const ggml_webgpu_shader_lib_context & context) {
3371        const uint32_t mode_flags = (uint32_t) ggml_get_op_params_i32(context.dst, 0);
3372        const uint32_t base_mode  = mode_flags & 0xFFu;
3373        const bool     antialias  = (mode_flags & GGML_SCALE_FLAG_ANTIALIAS) != 0u;
3374
3375        ggml_webgpu_upscale_pipeline_key key = {};
3376        key.input_type                       = context.src0->type;
3377        key.output_type                      = context.dst->type;
3378        key.base_mode                        = base_mode;
3379        key.antialias                        = antialias;
3380
3381        auto it = upscale_pipelines.find(key);
3382        if (it != upscale_pipelines.end()) {
3383            return it->second;
3384        }
3385
3386        std::vector<std::string> defines;
3387        std::string              variant = "upscale";
3388
3389        if (key.input_type == GGML_TYPE_F16) {
3390            defines.push_back("SRC_F16");
3391            variant += "_src_f16";
3392        } else {
3393            variant += "_src_f32";
3394        }
3395
3396        if (key.output_type == GGML_TYPE_F16) {
3397            defines.push_back("DST_F16");
3398            variant += "_dst_f16";
3399        } else {
3400            variant += "_dst_f32";
3401        }
3402
3403        switch (base_mode) {
3404            case GGML_SCALE_MODE_NEAREST:
3405                defines.push_back("NEAREST");
3406                variant += "_nearest";
3407                break;
3408            case GGML_SCALE_MODE_BILINEAR:
3409                defines.push_back("BILINEAR");
3410                variant += "_bilinear";
3411                break;
3412            case GGML_SCALE_MODE_BICUBIC:
3413                defines.push_back("BICUBIC");
3414                variant += "_bicubic";
3415                break;
3416            default:
3417                GGML_ABORT("Unsupported upscale mode");
3418        }
3419
3420        if (antialias) {
3421            defines.push_back("ANTIALIAS");
3422            variant += "_aa";
3423        }
3424
3425        defines.push_back(std::string("WG_SIZE=") + std::to_string(context.max_wg_size));
3426
3427        auto processed           = preprocessor.preprocess(wgsl_upscale, defines);
3428        auto decisions           = std::make_shared<ggml_webgpu_generic_shader_decisions>();
3429        decisions->wg_size       = context.max_wg_size;
3430        webgpu_pipeline pipeline = ggml_webgpu_create_pipeline(device, processed, variant);
3431        pipeline.context         = decisions;
3432        upscale_pipelines[key]   = pipeline;
3433        return upscale_pipelines[key];
3434    }
3435
3436  private:
3437    static webgpu_pipeline ggml_webgpu_create_pipeline(wgpu::Device & device,
3438                                                       std::string    shader_code,
3439                                                       std::string    label) {
3440        wgpu::ShaderSourceWGSL shader_source;
3441        shader_source.code = shader_code.c_str();
3442
3443        wgpu::ShaderModuleDescriptor shader_desc;
3444        shader_desc.nextInChain = &shader_source;
3445
3446        wgpu::ShaderModule shader_module = device.CreateShaderModule(&shader_desc);
3447
3448        wgpu::ComputePipelineDescriptor pipeline_desc;
3449        pipeline_desc.label              = label.c_str();
3450        pipeline_desc.compute.module     = shader_module;
3451        pipeline_desc.compute.entryPoint = "main";   // Entry point in the WGSL code
3452        pipeline_desc.layout             = nullptr;  // nullptr means auto layout
3453        return { device.CreateComputePipeline(&pipeline_desc), label };
3454    }
3455};
3456
3457#endif  // GGML_WEBGPU_SHADER_LIB_HPP