char-fork/llama.cpp

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

Pascalwebgpu: align tensor bindings to the type block size (#28382)3f5e94d7c

char/aliceai-t5-moe
236.0 KiB4809 linesraw
1/*
2    WebGPU backend implementation.
3    Note: Use ClangFormat to format this file.
4*/
5
6#include "ggml-webgpu.h"
7
8#include "ggml-backend-impl.h"
9#include "ggml-impl.h"
10#include "ggml-webgpu-shader-lib.hpp"
11#include "ggml.h"
12
13#ifdef __EMSCRIPTEN__
14#    include <emscripten/emscripten.h>
15#endif
16
17#include <webgpu/webgpu_cpp.h>
18
19#include <atomic>
20#include <cstdint>
21#include <cstring>
22#ifdef GGML_WEBGPU_GPU_PROFILE
23#    include <iomanip>
24#endif
25#if defined(GGML_WEBGPU_DEBUG) || defined(GGML_WEBGPU_CPU_PROFILE) || defined(GGML_WEBGPU_GPU_PROFILE)
26#    include <iostream>
27#endif
28#include <memory>
29#include <mutex>
30#include <optional>
31#include <string>
32#include <utility>
33#include <vector>
34
35#define ROUNDUP_POW2(x, pow2) (((x) + ((pow2) - 1)) & ~((pow2) - 1))
36#define CEIL_DIV(M, N)        (((M) + (N) - 1) / (N))
37
38// Return a rectangular grid of workgroups with minimal over-provisioned workgroups.
39// Assumes that the total number of workgroups does not exceed max_per_dim^2.
40static inline void compute_2d_workgroups(uint32_t total_wg, uint32_t max_per_dim, uint32_t & wg_x, uint32_t & wg_y) {
41    wg_y = std::max(1u, CEIL_DIV(total_wg, max_per_dim));
42    wg_x = CEIL_DIV(total_wg, wg_y);
43}
44
45static inline uint32_t ggml_webgpu_u32_from_f32(float value) {
46    uint32_t bits;
47    memcpy(&bits, &value, sizeof(bits));
48    return bits;
49}
50
51#ifdef GGML_WEBGPU_DEBUG
52#    define WEBGPU_LOG_DEBUG(msg)  std::cout << msg << std::endl
53#    define WEBGPU_DEBUG_BUF_ELEMS 512
54#else
55#    define WEBGPU_LOG_DEBUG(msg) ((void) 0)
56#endif  // GGML_WEBGPU_DEBUG
57
58#ifdef GGML_WEBGPU_CPU_PROFILE
59// total timing (aggregated)
60#    define WEBGPU_CPU_PROFILE_TOTAL_START(id) auto cpu_total_start_##id = std::chrono::high_resolution_clock::now();
61
62#    define WEBGPU_CPU_PROFILE_TOTAL_END(id, ctx)                                                         \
63        auto   cpu_total_end_##id = std::chrono::high_resolution_clock::now();                            \
64        double cpu_total_time_##id =                                                                      \
65            std::chrono::duration<double, std::milli>(cpu_total_end_##id - cpu_total_start_##id).count(); \
66        (ctx)->cpu_time_ms[#id] += cpu_total_time_##id;
67// fine-grained timing (not included in totals)
68#    define WEBGPU_CPU_PROFILE_DETAIL_START(id) auto cpu_detail_start_##id = std::chrono::high_resolution_clock::now();
69
70#    define WEBGPU_CPU_PROFILE_DETAIL_END(id, ctx)                                                          \
71        auto   cpu_detail_end_##id = std::chrono::high_resolution_clock::now();                             \
72        double cpu_detail_time_##id =                                                                       \
73            std::chrono::duration<double, std::milli>(cpu_detail_end_##id - cpu_detail_start_##id).count(); \
74        (ctx)->cpu_detail_ms[#id] += cpu_detail_time_##id;
75#else
76#    define WEBGPU_CPU_PROFILE_TOTAL_START(id)
77#    define WEBGPU_CPU_PROFILE_TOTAL_END(id, ctx)
78#    define WEBGPU_CPU_PROFILE_DETAIL_START(id)
79#    define WEBGPU_CPU_PROFILE_DETAIL_END(id, ctx)
80#endif  // GGML_WEBGPU_CPU_PROFILE
81
82#ifdef GGML_WEBGPU_GPU_PROFILE
83#    define WEBGPU_MAX_PROFILE_QUERY_COUNT        4096u
84#    define WEBGPU_TIMESTAMP_QUERY_BUF_SIZE_BYTES (WEBGPU_MAX_PROFILE_QUERY_COUNT * sizeof(uint64_t))
85#endif
86
87/* Constants */
88
89#define WEBGPU_DEFAULT_COMMAND_SUBMIT_BATCH_SIZE 64u
90#define WEBGPU_NUM_PARAM_SLOT_SAFETY_MARGIN      10u
91#define WEBGPU_RUNTIME_WAIT_TIMEOUT_MS           30000u
92#define WEBGPU_RUNTIME_WAIT_TIMEOUT_NS           (WEBGPU_RUNTIME_WAIT_TIMEOUT_MS * 1e6)
93#define WEBGPU_PARAMS_BUF_SIZE_BYTES             128  // enough for 32 parameters
94#define WEBGPU_SET_ROWS_ERROR_BUF_SIZE_BYTES     4
95#define WEBGPU_STORAGE_BUF_BINDING_MULT          4    // a storage buffer binding size must be a multiple of 4
96
97/* End Constants */
98
99// This is a "fake" base pointer, since WebGPU buffers do not have pointers to
100// their locations.
101static void * const webgpu_ptr_base = (void *) (uintptr_t) 0x1000;  // NOLINT
102
103static size_t ggml_webgpu_tensor_offset(const ggml_tensor * tensor) {
104    const ggml_tensor * base_tensor = tensor->view_src ? tensor->view_src : tensor;
105    return (size_t) ((uintptr_t) base_tensor->data - (uintptr_t) webgpu_ptr_base) + tensor->view_offs;
106}
107
108/* Struct definitions */
109
110// Forward reference
111static void ggml_webgpu_create_buffer(wgpu::Device &    device,
112                                      wgpu::Buffer &    buffer,
113                                      size_t            size,
114                                      wgpu::BufferUsage usage,
115                                      const char *      label);
116
117// Slot-based parameter arena for compute graph encoding. Each encoded kernel
118// gets a unique uniform-buffer slice within the current batch, and the slot
119// cursor is reset immediately after that batch is submitted.
120struct webgpu_param_arena {
121    wgpu::Buffer buffer;
122    size_t       slot_stride = 0;
123    size_t       slot_size   = 0;
124    uint32_t     slot_count  = 0;
125    uint32_t     next_slot   = 0;
126
127    void init(wgpu::Device device, size_t slot_size, uint32_t slot_count, size_t alignment) {
128        this->slot_stride = ROUNDUP_POW2(slot_size, alignment);
129        this->slot_size   = slot_size;
130        this->slot_count  = slot_count;
131        this->next_slot   = 0;
132
133        ggml_webgpu_create_buffer(device, buffer, this->slot_stride * slot_count,
134                                  wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Uniform, "ggml_webgpu_param_arena");
135    }
136
137    size_t alloc_slot(size_t size) {
138        GGML_ASSERT(size <= slot_size);
139        if (next_slot >= slot_count) {
140            GGML_ABORT("ggml_webgpu: parameter arena exhausted while encoding a batch");
141        }
142
143        return slot_stride * next_slot++;
144    }
145
146    void reset() { next_slot = 0; }
147
148    void cleanup() {
149        if (buffer) {
150            buffer.Destroy();
151            buffer = nullptr;
152        }
153    }
154
155    ~webgpu_param_arena() { this->cleanup(); }
156};
157
158struct webgpu_encoded_op {
159    uint32_t num_kernels = 0;
160#ifdef GGML_WEBGPU_GPU_PROFILE
161    std::vector<std::string> pipeline_names;
162#endif
163};
164
165struct webgpu_dispatch_desc {
166    webgpu_pipeline                   pipeline;
167    std::vector<uint32_t>             params;
168    std::vector<wgpu::BindGroupEntry> bind_group_entries;
169    std::pair<uint32_t, uint32_t>     workgroups = { 1, 1 };
170};
171
172struct webgpu_capabilities {
173    wgpu::Limits limits;
174    bool         supports_subgroups       = false;
175    bool         supports_subgroup_matrix = false;
176    bool         supports_dot_product     = false;
177
178    uint32_t sg_mat_m = 0;
179    uint32_t sg_mat_n = 0;
180    uint32_t sg_mat_k = 0;
181
182    uint32_t subgroup_size     = 0;
183    uint32_t min_subgroup_size = 0;
184    uint32_t max_subgroup_size = 0;
185    size_t   memset_bytes_per_thread;
186};
187
188// Stores global webgpu members
189struct webgpu_global_context_struct {
190    wgpu::Instance instance;
191    wgpu::Adapter  adapter;
192    wgpu::Device   device;
193    wgpu::Queue    queue;
194    uint32_t       command_submit_batch_size = WEBGPU_DEFAULT_COMMAND_SUBMIT_BATCH_SIZE;
195    uint32_t       max_inflight_batches      = UINT32_MAX;
196
197    webgpu_capabilities  capabilities;
198    // Shared buffer to move data from device to host
199    wgpu::Buffer         get_tensor_staging_buf;
200    // Global mutex for get_tensor
201    std::recursive_mutex mutex;
202
203    wgpu::Buffer    memset_params_buf;
204    webgpu_pipeline memset_pipeline;
205
206    std::string vendor;
207
208    // TODO: We should rework the CPU profiling time handling to make it more useful. ref: https://github.com/ggml-org/llama.cpp/pull/22050
209#ifdef GGML_WEBGPU_CPU_PROFILE
210    // Profiling: labeled CPU time in ms (total)
211    std::unordered_map<std::string, double> cpu_time_ms;
212    // Profiling: detailed CPU time in ms
213    std::unordered_map<std::string, double> cpu_detail_ms;
214#endif
215
216#ifdef GGML_WEBGPU_DEBUG
217    wgpu::Buffer debug_host_buf;
218    wgpu::Buffer debug_dev_buf;
219#endif
220
221    ~webgpu_global_context_struct() {
222        if (this->get_tensor_staging_buf) {
223            this->get_tensor_staging_buf.Destroy();
224            this->get_tensor_staging_buf = nullptr;
225        }
226        if (this->memset_params_buf) {
227            this->memset_params_buf.Destroy();
228            this->memset_params_buf = nullptr;
229        }
230#ifdef GGML_WEBGPU_DEBUG
231        if (this->debug_host_buf) {
232            this->debug_host_buf.Destroy();
233            this->debug_host_buf = nullptr;
234        }
235        if (this->debug_dev_buf) {
236            this->debug_dev_buf.Destroy();
237            this->debug_dev_buf = nullptr;
238        }
239#endif
240    }
241};
242
243typedef std::shared_ptr<webgpu_global_context_struct> webgpu_global_context;
244
245// All the base objects needed to run operations on a WebGPU device
246struct webgpu_context_struct {
247    // Points to global instances owned by ggml_backend_webgpu_reg_context
248    webgpu_global_context global_ctx;
249
250    std::unique_ptr<ggml_webgpu_shader_lib> shader_lib;
251
252    webgpu_param_arena       param_arena;
253    wgpu::Buffer             set_rows_dev_error_buf;
254    wgpu::Buffer             set_rows_host_error_buf;
255    wgpu::CommandEncoder     active_command_encoder;
256    wgpu::ComputePassEncoder active_compute_pass;
257    bool                     batch_compute_passes = true;
258
259    size_t memset_bytes_per_thread;
260
261#ifdef GGML_WEBGPU_GPU_PROFILE
262    // Profiling: per-shader GPU time in ms
263    std::unordered_map<std::string, double> shader_gpu_time_ms;
264    wgpu::Buffer                            profile_timestamp_dev_buf;
265    wgpu::Buffer                            profile_timestamp_host_buf;
266    wgpu::QuerySet                          profile_timestamp_query_set;
267    uint32_t                                profile_timestamp_query_count = 0;
268#endif
269
270    ~webgpu_context_struct() {
271#ifdef GGML_WEBGPU_GPU_PROFILE
272        if (this->profile_timestamp_host_buf) {
273            this->profile_timestamp_host_buf.Destroy();
274            this->profile_timestamp_host_buf = nullptr;
275        }
276        if (this->profile_timestamp_dev_buf) {
277            this->profile_timestamp_dev_buf.Destroy();
278            this->profile_timestamp_dev_buf = nullptr;
279        }
280        if (this->profile_timestamp_query_set) {
281            this->profile_timestamp_query_set.Destroy();
282            this->profile_timestamp_query_set = nullptr;
283        }
284#endif
285        if (this->set_rows_host_error_buf) {
286            this->set_rows_host_error_buf.Destroy();
287            this->set_rows_host_error_buf = nullptr;
288        }
289        if (this->set_rows_dev_error_buf) {
290            this->set_rows_dev_error_buf.Destroy();
291            this->set_rows_dev_error_buf = nullptr;
292        }
293    }
294};
295
296typedef std::shared_ptr<webgpu_context_struct> webgpu_context;
297
298// Metadata required for the ggml backend registration/discovery interface
299struct ggml_backend_webgpu_reg_context {
300    // Since the Instance is a global entrypoint into the WebGPU API, it lives here
301    webgpu_global_context webgpu_global_ctx;
302    size_t                device_count;
303    const char *          name;
304};
305
306// Per-device struct for the global logical device interface
307struct ggml_backend_webgpu_device_context {
308    webgpu_global_context webgpu_global_ctx;
309    std::string           device_name;
310    std::string           device_desc;
311};
312
313// Per-thread data required to actually run WebGPU operations in a backend instance
314struct ggml_backend_webgpu_context {
315    webgpu_context webgpu_ctx;
316    std::string    name;
317};
318
319// Per-thread data related to buffers
320struct ggml_backend_webgpu_buffer_context {
321    wgpu::Buffer          buffer;
322    std::string           label;
323    webgpu_global_context global_ctx;
324
325    ggml_backend_webgpu_buffer_context(wgpu::Buffer buf, std::string lbl, webgpu_global_context global_ctx_) :
326        buffer(std::move(buf)),
327        label(std::move(lbl)),
328        global_ctx(std::move(global_ctx_)) {}
329};
330
331/* WebGPU object initializations */
332
333static webgpu_pipeline ggml_webgpu_create_pipeline(wgpu::Device &                           device,
334                                                   const char *                             shader_code,
335                                                   const char *                             label,
336                                                   const std::vector<wgpu::ConstantEntry> & constants = {}) {
337    wgpu::ShaderSourceWGSL shader_source;
338    shader_source.code = shader_code;
339
340    wgpu::ShaderModuleDescriptor shader_desc;
341    shader_desc.nextInChain = &shader_source;
342
343    wgpu::ShaderModule shader_module = device.CreateShaderModule(&shader_desc);
344
345    wgpu::ComputePipelineDescriptor pipeline_desc;
346    pipeline_desc.label              = label;
347    pipeline_desc.compute.module     = shader_module;
348    pipeline_desc.compute.entryPoint = "main";   // Entry point in the WGSL code
349    pipeline_desc.layout             = nullptr;  // nullptr means auto layout
350    if (constants.size() > 0) {
351        pipeline_desc.compute.constants     = constants.data();
352        pipeline_desc.compute.constantCount = constants.size();
353    }
354    return { device.CreateComputePipeline(&pipeline_desc), label };
355}
356
357static void ggml_webgpu_create_buffer(wgpu::Device &    device,
358                                      wgpu::Buffer &    buffer,
359                                      size_t            size,
360                                      wgpu::BufferUsage usage,
361                                      const char *      label) {
362    wgpu::BufferDescriptor buffer_desc;
363    buffer_desc.size             = size;
364    buffer_desc.usage            = usage;
365    buffer_desc.label            = label;
366    buffer_desc.mappedAtCreation = false;
367
368    // TODO: error handling
369    buffer = device.CreateBuffer(&buffer_desc);
370}
371
372static wgpu::Buffer ggml_webgpu_tensor_buf(const ggml_tensor * tensor) {
373    ggml_backend_webgpu_buffer_context * ctx = (ggml_backend_webgpu_buffer_context *) tensor->buffer->context;
374    return ctx->buffer;
375}
376
377// Binding offset for a tensor: the largest aligned offset at or before the tensor whose
378// distance to the tensor is a whole number of type blocks, so shaders can index the
379// misalignment in elements even for block quantized types.
380static size_t ggml_webgpu_tensor_align_offset(const ggml_tensor * t, size_t alignment) {
381    const size_t offset    = ggml_webgpu_tensor_offset(t);
382    const size_t type_size = ggml_type_size(t->type);
383    size_t       aligned   = offset & ~(alignment - 1);
384    while ((offset - aligned) % type_size != 0) {
385        GGML_ASSERT(aligned >= alignment);
386        aligned -= alignment;
387    }
388    return aligned;
389}
390
391static size_t ggml_webgpu_tensor_misalignment(const ggml_tensor * t, size_t alignment) {
392    return ggml_webgpu_tensor_offset(t) - ggml_webgpu_tensor_align_offset(t, alignment);
393}
394
395static size_t ggml_webgpu_tensor_misalignment(webgpu_context & ctx, const ggml_tensor * t) {
396    return ggml_webgpu_tensor_misalignment(t, ctx->global_ctx->capabilities.limits.minStorageBufferOffsetAlignment);
397}
398
399static size_t ggml_webgpu_tensor_align_offset(webgpu_context & ctx, const ggml_tensor * t) {
400    return ggml_webgpu_tensor_align_offset(t, ctx->global_ctx->capabilities.limits.minStorageBufferOffsetAlignment);
401}
402
403static size_t ggml_webgpu_tensor_binding_size(const ggml_tensor * t, size_t alignment) {
404    return ROUNDUP_POW2(ggml_nbytes(t) + ggml_webgpu_tensor_misalignment(t, alignment),
405                        WEBGPU_STORAGE_BUF_BINDING_MULT);
406}
407
408static size_t ggml_webgpu_tensor_binding_size(webgpu_context & ctx, const ggml_tensor * t) {
409    return ggml_webgpu_tensor_binding_size(t, ctx->global_ctx->capabilities.limits.minStorageBufferOffsetAlignment);
410}
411
412static bool ggml_webgpu_tensor_binding_overlap(const webgpu_global_context & global_ctx,
413                                               const ggml_tensor *           a,
414                                               const ggml_tensor *           b) {
415    if (a->buffer != b->buffer) {
416        return false;
417    }
418
419    const size_t alignment = global_ctx->capabilities.limits.minStorageBufferOffsetAlignment;
420    const size_t a_offset  = ggml_webgpu_tensor_align_offset(a, alignment);
421    const size_t b_offset  = ggml_webgpu_tensor_align_offset(b, alignment);
422    return a_offset < b_offset + ggml_webgpu_tensor_binding_size(b, alignment) &&
423           b_offset < a_offset + ggml_webgpu_tensor_binding_size(a, alignment);
424}
425
426static bool ggml_webgpu_tensor_binding_overlap_range(const webgpu_global_context & global_ctx,
427                                                     ggml_tensor *                 tensor,
428                                                     ggml_backend_buffer_t         buffer,
429                                                     size_t                        offset,
430                                                     size_t                        size) {
431    if (tensor->buffer != buffer) {
432        return false;
433    }
434
435    const size_t alignment     = global_ctx->capabilities.limits.minStorageBufferOffsetAlignment;
436    const size_t tensor_offset = ggml_webgpu_tensor_align_offset(tensor, alignment);
437    return tensor_offset < offset + size && offset < tensor_offset + ggml_webgpu_tensor_binding_size(tensor, alignment);
438}
439
440struct ggml_webgpu_merged_binding_range {
441    size_t offset;
442    size_t size;
443};
444
445static ggml_webgpu_merged_binding_range ggml_webgpu_tensor_merged_binding_range(
446    webgpu_context &                     ctx,
447    std::initializer_list<ggml_tensor *> tensors) {
448    size_t merged_offset = SIZE_MAX;
449    size_t merged_end    = 0;
450
451    for (ggml_tensor * tensor : tensors) {
452        const size_t bind_offset = ggml_webgpu_tensor_align_offset(ctx, tensor);
453        const size_t bind_end    = bind_offset + ggml_webgpu_tensor_binding_size(ctx, tensor);
454
455        merged_offset = std::min(merged_offset, bind_offset);
456        merged_end    = std::max(merged_end, bind_end);
457    }
458
459    return { merged_offset, merged_end - merged_offset };
460}
461
462static uint32_t ggml_webgpu_tensor_merged_element_offset(const ggml_tensor *                      tensor,
463                                                         const ggml_webgpu_merged_binding_range & merged_range) {
464    return (uint32_t) ((ggml_webgpu_tensor_offset(tensor) - merged_range.offset) / ggml_type_size(tensor->type));
465}
466
467static wgpu::BindGroupEntry ggml_webgpu_make_bind_group_entry(uint32_t     binding,
468                                                              wgpu::Buffer buffer,
469                                                              uint64_t     offset,
470                                                              uint64_t     size) {
471    wgpu::BindGroupEntry entry = {};
472    entry.binding              = binding;
473    entry.buffer               = std::move(buffer);
474    entry.offset               = offset;
475    entry.size                 = size;
476    return entry;
477}
478
479static wgpu::BindGroupEntry ggml_webgpu_make_tensor_bind_group_entry(webgpu_context & ctx,
480                                                                     uint32_t         binding,
481                                                                     ggml_tensor *    tensor) {
482    return ggml_webgpu_make_bind_group_entry(binding, ggml_webgpu_tensor_buf(tensor),
483                                             ggml_webgpu_tensor_align_offset(ctx, tensor),
484                                             ggml_webgpu_tensor_binding_size(ctx, tensor));
485}
486
487/** End WebGPU object initializations */
488
489/** WebGPU Actions */
490
491template <typename T>
492static void ggml_backend_webgpu_check_wait_status(wgpu::WaitStatus wait_status,
493                                                  T                callback_status,
494                                                  T                success_status,
495                                                  const char *     wait_name,
496                                                  const char *     failure_name,
497                                                  const char *     callback_message) {
498    if (wait_status == wgpu::WaitStatus::TimedOut) {
499        GGML_ABORT("ggml_webgpu: %s timed out after %u ms\n", wait_name, WEBGPU_RUNTIME_WAIT_TIMEOUT_MS);
500    }
501    if (wait_status == wgpu::WaitStatus::Error) {
502        GGML_ABORT("ggml_webgpu: %s failed\n", wait_name);
503    }
504    if (callback_status != success_status) {
505        GGML_ABORT("ggml_webgpu: %s failed with status %d: %s\n", failure_name, static_cast<int>(callback_status),
506                   callback_message);
507    }
508}
509
510// TODO: these next two functions may want tuning across different platforms and workloads,
511static uint32_t ggml_backend_webgpu_get_max_inflight_batches() {
512    return UINT32_MAX;
513}
514
515static uint32_t ggml_backend_webgpu_get_command_submit_batch_size() {
516    return WEBGPU_DEFAULT_COMMAND_SUBMIT_BATCH_SIZE;
517}
518
519static void ggml_backend_webgpu_wait_queue(webgpu_global_context & ctx) {
520    wgpu::QueueWorkDoneStatus callback_status = wgpu::QueueWorkDoneStatus::Error;
521    std::string               callback_message;
522
523    const wgpu::WaitStatus wait_status = ctx->instance.WaitAny(
524        ctx->queue.OnSubmittedWorkDone(
525            wgpu::CallbackMode::AllowSpontaneous,
526            [&callback_status, &callback_message](wgpu::QueueWorkDoneStatus status, wgpu::StringView message) {
527                callback_status  = status;
528                callback_message = std::string(message);
529            }),
530        WEBGPU_RUNTIME_WAIT_TIMEOUT_NS);
531
532    ggml_backend_webgpu_check_wait_status(wait_status, callback_status, wgpu::QueueWorkDoneStatus::Success,
533                                          "Queue wait", "Queue work", callback_message.c_str());
534}
535
536static void ggml_backend_webgpu_map_buffer(webgpu_global_context & ctx,
537                                           wgpu::Buffer &          buffer,
538                                           wgpu::MapMode           mode,
539                                           size_t                  offset,
540                                           size_t                  size) {
541    wgpu::MapAsyncStatus callback_status = wgpu::MapAsyncStatus::Error;
542    std::string          callback_message;
543
544    const wgpu::WaitStatus wait_status = ctx->instance.WaitAny(
545        buffer.MapAsync(mode, offset, size, wgpu::CallbackMode::AllowSpontaneous,
546                        [&callback_status, &callback_message](wgpu::MapAsyncStatus status, wgpu::StringView message) {
547                            callback_status  = status;
548                            callback_message = std::string(message);
549                        }),
550        WEBGPU_RUNTIME_WAIT_TIMEOUT_NS);
551
552    ggml_backend_webgpu_check_wait_status(wait_status, callback_status, wgpu::MapAsyncStatus::Success,
553                                          "Buffer map wait", "Buffer map", callback_message.c_str());
554}
555
556static void ggml_backend_webgpu_submit_commands(webgpu_context &          ctx,
557                                                const wgpu::CommandBuffer commands,
558                                                uint32_t &                num_inflight_batches) {
559    if (num_inflight_batches >= ctx->global_ctx->max_inflight_batches) {
560        ggml_backend_webgpu_wait_queue(ctx->global_ctx);
561        num_inflight_batches = 0;
562    }
563
564    ctx->global_ctx->queue.Submit(1, &commands);
565    num_inflight_batches++;
566}
567
568#ifdef GGML_WEBGPU_DEBUG
569// This function adds debugging information to shaders, as WebGPU does not support printing directly.
570// To use, add a bind group entry to the setup for the shader you are debugging, add the buffer and
571// debug statements in the shader, and then call this function after encoding the commands and submitting them.
572static void ggml_backend_webgpu_debug(webgpu_global_context & ctx) {
573    wgpu::CommandEncoder encoder = ctx->device.CreateCommandEncoder();
574    encoder.CopyBufferToBuffer(ctx->debug_dev_buf, 0, ctx->debug_host_buf, 0, ctx->debug_host_buf.GetSize());
575    wgpu::CommandBuffer commands = encoder.Finish();
576    ctx->queue.Submit(1, &commands);
577    ggml_backend_webgpu_map_buffer(ctx, ctx->debug_host_buf, wgpu::MapMode::Read, 0, ctx->debug_host_buf.GetSize());
578    const float * debug_data = (const float *) ctx->debug_host_buf.GetConstMappedRange();
579    std::cout << "debug[0]: " << debug_data[0] << "\n";
580    ctx->debug_host_buf.Unmap();
581}
582#endif
583
584static webgpu_encoded_op ggml_backend_webgpu_build_multi(webgpu_context &                          ctx,
585                                                         const std::vector<webgpu_dispatch_desc> & dispatches) {
586    webgpu_encoded_op            result = {};
587    std::vector<wgpu::BindGroup> bind_groups;
588    std::vector<size_t>          param_offsets;
589    result.num_kernels = dispatches.size();
590
591    for (size_t i = 0; i < dispatches.size(); i++) {
592        const webgpu_dispatch_desc & dispatch     = dispatches[i];
593        const size_t                 param_size   = dispatch.params.size() * sizeof(uint32_t);
594        const size_t                 param_offset = ctx->param_arena.alloc_slot(param_size);
595
596        std::vector<wgpu::BindGroupEntry> entries            = dispatch.bind_group_entries;
597        uint32_t                          params_binding_num = entries.size();
598        entries.push_back(ggml_webgpu_make_bind_group_entry(params_binding_num, ctx->param_arena.buffer, param_offset,
599                                                            ctx->param_arena.slot_size));
600
601        wgpu::BindGroupDescriptor bind_group_desc;
602        bind_group_desc.layout     = dispatch.pipeline.pipeline.GetBindGroupLayout(0);
603        bind_group_desc.entryCount = entries.size();
604        bind_group_desc.entries    = entries.data();
605        bind_group_desc.label      = dispatch.pipeline.name.c_str();
606        bind_groups.push_back(ctx->global_ctx->device.CreateBindGroup(&bind_group_desc));
607        param_offsets.push_back(param_offset);
608    }
609
610    for (size_t i = 0; i < param_offsets.size(); i++) {
611        ctx->global_ctx->queue.WriteBuffer(ctx->param_arena.buffer, param_offsets[i], dispatches[i].params.data(),
612                                           dispatches[i].params.size() * sizeof(uint32_t));
613    }
614
615#ifdef GGML_WEBGPU_GPU_PROFILE
616    for (size_t i = 0; i < dispatches.size(); i++) {
617        GGML_ASSERT(ctx->profile_timestamp_query_count + 2 <= WEBGPU_MAX_PROFILE_QUERY_COUNT);
618        const uint32_t query_begin = ctx->profile_timestamp_query_count++;
619        const uint32_t query_end   = ctx->profile_timestamp_query_count++;
620
621        wgpu::PassTimestampWrites ts_writes   = {};
622        ts_writes.querySet                    = ctx->profile_timestamp_query_set;
623        ts_writes.beginningOfPassWriteIndex   = query_begin;
624        ts_writes.endOfPassWriteIndex         = query_end;
625        wgpu::ComputePassDescriptor pass_desc = {};
626        pass_desc.timestampWrites             = &ts_writes;
627
628        wgpu::ComputePassEncoder pass = ctx->active_command_encoder.BeginComputePass(&pass_desc);
629
630        pass.SetPipeline(dispatches[i].pipeline.pipeline);
631        pass.SetBindGroup(0, bind_groups[i]);
632        pass.DispatchWorkgroups(dispatches[i].workgroups.first, dispatches[i].workgroups.second, 1);
633        pass.End();
634        result.pipeline_names.push_back(dispatches[i].pipeline.name);
635    }
636#else
637    for (size_t i = 0; i < dispatches.size(); i++) {
638        if (ctx->batch_compute_passes) {
639            ctx->active_compute_pass.SetPipeline(dispatches[i].pipeline.pipeline);
640            ctx->active_compute_pass.SetBindGroup(0, bind_groups[i]);
641            ctx->active_compute_pass.DispatchWorkgroups(dispatches[i].workgroups.first, dispatches[i].workgroups.second,
642                                                        1);
643        } else {
644            wgpu::ComputePassEncoder pass = ctx->active_command_encoder.BeginComputePass();
645            pass.SetPipeline(dispatches[i].pipeline.pipeline);
646            pass.SetBindGroup(0, bind_groups[i]);
647            pass.DispatchWorkgroups(dispatches[i].workgroups.first, dispatches[i].workgroups.second, 1);
648            pass.End();
649        }
650    }
651#endif
652
653    return result;
654}
655
656static webgpu_encoded_op ggml_backend_webgpu_build(webgpu_context &                  ctx,
657                                                   webgpu_pipeline &                 pipeline,
658                                                   std::vector<uint32_t>             params,
659                                                   std::vector<wgpu::BindGroupEntry> bind_group_entries,
660                                                   uint32_t                          wg_x,
661                                                   uint32_t                          wg_y = 1) {
662    return ggml_backend_webgpu_build_multi(
663        ctx, {
664                 { pipeline, std::move(params), std::move(bind_group_entries), { wg_x, wg_y } },
665    });
666}
667
668static void ggml_backend_webgpu_buffer_memset(webgpu_global_context & ctx,
669                                              wgpu::Buffer &          buf,
670                                              uint32_t                value,
671                                              size_t                  offset,
672                                              size_t                  size) {
673    std::vector<uint32_t>             params  = { (uint32_t) offset, (uint32_t) size, value };
674    std::vector<wgpu::BindGroupEntry> entries = { ggml_webgpu_make_bind_group_entry(0, buf, 0, buf.GetSize()) };
675    size_t                            bytes_per_wg =
676        ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup * ctx->capabilities.memset_bytes_per_thread;
677    uint32_t wg_x = CEIL_DIV(size + 3, bytes_per_wg);
678
679    ctx->queue.WriteBuffer(ctx->memset_params_buf, 0, params.data(), params.size() * sizeof(uint32_t));
680
681    wgpu::BindGroupEntry params_entry = {};
682    params_entry.binding              = 1;
683    params_entry.buffer               = ctx->memset_params_buf;
684    params_entry.offset               = 0;
685    params_entry.size                 = WEBGPU_PARAMS_BUF_SIZE_BYTES;
686    entries.push_back(params_entry);
687
688    wgpu::BindGroupDescriptor bind_group_desc;
689    bind_group_desc.layout     = ctx->memset_pipeline.pipeline.GetBindGroupLayout(0);
690    bind_group_desc.entryCount = entries.size();
691    bind_group_desc.entries    = entries.data();
692    bind_group_desc.label      = ctx->memset_pipeline.name.c_str();
693    wgpu::BindGroup bind_group = ctx->device.CreateBindGroup(&bind_group_desc);
694
695    wgpu::CommandEncoder     encoder = ctx->device.CreateCommandEncoder();
696    wgpu::ComputePassEncoder pass    = encoder.BeginComputePass();
697    pass.SetPipeline(ctx->memset_pipeline.pipeline);
698    pass.SetBindGroup(0, bind_group);
699    pass.DispatchWorkgroups(wg_x, 1, 1);
700    pass.End();
701
702    wgpu::CommandBuffer              command  = encoder.Finish();
703    std::vector<wgpu::CommandBuffer> commands = { command };
704    ctx->queue.Submit(commands.size(), commands.data());
705}
706
707/** End WebGPU Actions */
708
709/** GGML Backend Interface */
710
711static const char * ggml_backend_webgpu_name(ggml_backend_t backend) {
712    ggml_backend_webgpu_context * ctx = (ggml_backend_webgpu_context *) backend->context;
713    return ctx->name.c_str();
714}
715
716static void ggml_backend_webgpu_free(ggml_backend_t backend) {
717    ggml_backend_webgpu_context * ctx = (ggml_backend_webgpu_context *) backend->context;
718    WEBGPU_LOG_DEBUG("ggml_backend_webgpu_free(" << ctx->name << ")");
719
720#ifdef GGML_WEBGPU_CPU_PROFILE
721    std::cout << "\n[ggml_webgpu cpu profiling summary]\n";
722    double total_cpu = 0.0;
723    for (const auto & kv : ctx->webgpu_ctx->global_ctx->cpu_time_ms) {
724        total_cpu += kv.second;
725    }
726    std::cout << "ggml_webgpu: total cpu time: " << total_cpu << " ms\n";
727    std::cout << "ggml_webgpu: cpu breakdown:\n";
728    for (const auto & kv : ctx->webgpu_ctx->global_ctx->cpu_time_ms) {
729        double pct = (total_cpu > 0.0) ? (kv.second / total_cpu * 100.0) : 0.0;
730        std::cout << "ggml_webgpu:  " << kv.first << ": " << kv.second << " ms (" << pct << "%)\n";
731    }
732    if (ctx->webgpu_ctx->global_ctx->cpu_detail_ms.size() > 0) {
733        std::cout << "ggml_webgpu: cpu detailed breakdown:\n";
734    }
735    for (const auto & kv : ctx->webgpu_ctx->global_ctx->cpu_detail_ms) {
736        double pct = (total_cpu > 0.0) ? (kv.second / total_cpu * 100.0) : 0.0;
737        std::cout << "ggml_webgpu:  " << kv.first << ": " << kv.second << " ms (" << pct << "%)\n";
738    }
739#endif
740
741#ifdef GGML_WEBGPU_GPU_PROFILE
742    std::cout << "\n[ggml_webgpu gpu profiling summary]\n";
743    double total_gpu = 0.0;
744    for (const auto & kv : ctx->webgpu_ctx->shader_gpu_time_ms) {
745        total_gpu += kv.second;
746    }
747    std::cout << "ggml_webgpu: total gpu time (all shaders): " << total_gpu << " ms\n";
748    std::cout << "\nggml_webgpu: gpu breakdown:\n";
749    for (const auto & kv : ctx->webgpu_ctx->shader_gpu_time_ms) {
750        double pct = (total_gpu > 0.0) ? (kv.second / total_gpu * 100.0) : 0.0;
751        std::cout << "ggml_webgpu:  " << kv.first << ": " << kv.second << " ms (" << std::fixed << std::setprecision(2)
752                  << pct << "%)\n";
753    }
754#endif
755
756#if defined(GGML_WEBGPU_CPU_PROFILE) && defined(GGML_WEBGPU_GPU_PROFILE)
757    std::cout << "ggml_webgpu: gpu/cpu ratio: " << (total_cpu > 0.0 ? total_gpu / total_cpu : 0.0) << "\n";
758#endif
759
760    delete ctx;
761    delete backend;
762}
763
764static webgpu_encoded_op ggml_webgpu_cpy(webgpu_context & ctx, ggml_tensor * src, ggml_tensor * dst) {
765    ggml_webgpu_shader_lib_context shader_lib_ctx = {};
766    shader_lib_ctx.src0                           = src;
767    shader_lib_ctx.dst                            = dst;
768    shader_lib_ctx.max_wg_size = ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
769
770    webgpu_pipeline pipeline = ctx->shader_lib->get_cpy_pipeline(shader_lib_ctx);
771
772    auto * decisions = static_cast<ggml_webgpu_generic_shader_decisions *>(pipeline.context.get());
773
774    uint32_t ne = (uint32_t) ggml_nelements(dst);
775
776    std::vector<uint32_t> params = {
777        ne, (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src) / ggml_type_size(src->type)),
778        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, dst) / ggml_type_size(dst->type)),
779        // Convert byte-strides to element-strides
780        (uint32_t) (src->nb[0] / ggml_type_size(src->type)), (uint32_t) (src->nb[1] / ggml_type_size(src->type)),
781        (uint32_t) (src->nb[2] / ggml_type_size(src->type)), (uint32_t) (src->nb[3] / ggml_type_size(src->type)),
782        (uint32_t) (dst->nb[0] / ggml_type_size(dst->type)), (uint32_t) (dst->nb[1] / ggml_type_size(dst->type)),
783        (uint32_t) (dst->nb[2] / ggml_type_size(dst->type)), (uint32_t) (dst->nb[3] / ggml_type_size(dst->type)),
784        // Logical shapes
785        (uint32_t) src->ne[0], (uint32_t) src->ne[1], (uint32_t) src->ne[2], (uint32_t) dst->ne[0],
786        (uint32_t) dst->ne[1], (uint32_t) dst->ne[2]
787    };
788
789    std::vector<wgpu::BindGroupEntry> entries = {
790        ggml_webgpu_make_tensor_bind_group_entry(ctx, 0, src),
791        ggml_webgpu_make_tensor_bind_group_entry(ctx, 1, dst),
792    };
793
794    uint32_t wg_x;
795    uint32_t wg_y;
796    uint32_t total_wg = CEIL_DIV(ne, decisions->wg_size);
797    compute_2d_workgroups(total_wg, ctx->global_ctx->capabilities.limits.maxComputeWorkgroupsPerDimension, wg_x, wg_y);
798    return ggml_backend_webgpu_build(ctx, pipeline, params, entries, wg_x, wg_y);
799}
800
801static webgpu_encoded_op ggml_webgpu_set(webgpu_context & ctx,
802                                         ggml_tensor *    src0,
803                                         ggml_tensor *    src1,
804                                         ggml_tensor *    dst) {
805    ggml_webgpu_shader_lib_context shader_lib_ctx = {};
806    shader_lib_ctx.src0                           = src0;
807    shader_lib_ctx.src1                           = src1;
808    shader_lib_ctx.dst                            = dst;
809    shader_lib_ctx.max_wg_size = ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
810
811    webgpu_pipeline pipeline = ctx->shader_lib->get_set_pipeline(shader_lib_ctx);
812
813    auto *     decisions = static_cast<ggml_webgpu_generic_shader_decisions *>(pipeline.context.get());
814    const bool inplace   = decisions->inplace;
815
816    const uint32_t ne            = inplace ? (uint32_t) ggml_nelements(src1) : (uint32_t) ggml_nelements(dst);
817    const uint32_t dst_type_size = (uint32_t) ggml_type_size(dst->type);
818
819    std::vector<uint32_t> params = {
820        ne,
821        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src0) / ggml_type_size(src0->type)),
822        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src1) / ggml_type_size(src1->type)),
823        (uint32_t) (((const int32_t *) dst->op_params)[3] / dst_type_size),
824
825        (uint32_t) (src1->nb[0] / ggml_type_size(src1->type)),
826        (uint32_t) (src1->nb[1] / ggml_type_size(src1->type)),
827        (uint32_t) (src1->nb[2] / ggml_type_size(src1->type)),
828        (uint32_t) (src1->nb[3] / ggml_type_size(src1->type)),
829
830        1u,
831        (uint32_t) (((const int32_t *) dst->op_params)[0] / dst_type_size),
832        (uint32_t) (((const int32_t *) dst->op_params)[1] / dst_type_size),
833        (uint32_t) (((const int32_t *) dst->op_params)[2] / dst_type_size),
834
835        (uint32_t) src1->ne[0],
836        (uint32_t) src1->ne[1],
837        (uint32_t) src1->ne[2],
838        (uint32_t) src1->ne[3],
839    };
840
841    std::vector<wgpu::BindGroupEntry> entries;
842    uint32_t                          binding_index = 0;
843    if (!inplace) {
844        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 0, src0));
845        binding_index++;
846    }
847    entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, binding_index, src1));
848    entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, binding_index + 1, dst));
849
850    uint32_t wg_x = CEIL_DIV(ne, decisions->wg_size);
851    return ggml_backend_webgpu_build(ctx, pipeline, params, entries, wg_x);
852}
853
854static webgpu_encoded_op ggml_webgpu_pad(webgpu_context & ctx, ggml_tensor * src, ggml_tensor * dst) {
855    ggml_webgpu_shader_lib_context shader_lib_ctx = {};
856    shader_lib_ctx.src0                           = src;
857    shader_lib_ctx.dst                            = dst;
858    shader_lib_ctx.max_wg_size = ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
859
860    webgpu_pipeline pipeline = ctx->shader_lib->get_pad_pipeline(shader_lib_ctx);
861
862    auto * decisions = static_cast<ggml_webgpu_generic_shader_decisions *>(pipeline.context.get());
863
864    const uint32_t ne = (uint32_t) ggml_nelements(dst);
865
866    std::vector<uint32_t> params = {
867        ne,
868        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src) / ggml_type_size(src->type)),
869        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, dst) / ggml_type_size(dst->type)),
870        // Strides (in elements)
871        (uint32_t) (src->nb[0] / ggml_type_size(src->type)),
872        (uint32_t) (src->nb[1] / ggml_type_size(src->type)),
873        (uint32_t) (src->nb[2] / ggml_type_size(src->type)),
874        (uint32_t) (src->nb[3] / ggml_type_size(src->type)),
875        // Shapes
876        (uint32_t) src->ne[0],
877        (uint32_t) src->ne[1],
878        (uint32_t) src->ne[2],
879        (uint32_t) src->ne[3],
880        (uint32_t) dst->ne[0],
881        (uint32_t) dst->ne[1],
882        (uint32_t) dst->ne[2],
883        (uint32_t) dst->ne[3],
884        // Pad sizes
885        (uint32_t) ggml_get_op_params_i32(dst, 0),
886        (uint32_t) ggml_get_op_params_i32(dst, 1),
887        (uint32_t) ggml_get_op_params_i32(dst, 2),
888        (uint32_t) ggml_get_op_params_i32(dst, 3),
889        (uint32_t) ggml_get_op_params_i32(dst, 4),
890        (uint32_t) ggml_get_op_params_i32(dst, 5),
891        (uint32_t) ggml_get_op_params_i32(dst, 6),
892        (uint32_t) ggml_get_op_params_i32(dst, 7),
893    };
894
895    std::vector<wgpu::BindGroupEntry> entries = {
896        ggml_webgpu_make_tensor_bind_group_entry(ctx, 0, src),
897        ggml_webgpu_make_tensor_bind_group_entry(ctx, 1, dst),
898    };
899
900    uint32_t wg_x = CEIL_DIV(ne, decisions->wg_size);
901    return ggml_backend_webgpu_build(ctx, pipeline, params, entries, wg_x);
902}
903
904static webgpu_encoded_op ggml_webgpu_solve_tri(webgpu_context & ctx,
905                                               ggml_tensor *    src0,
906                                               ggml_tensor *    src1,
907                                               ggml_tensor *    dst) {
908    ggml_webgpu_shader_lib_context shader_lib_ctx = {};
909    shader_lib_ctx.src0                           = src0;
910    shader_lib_ctx.src1                           = src1;
911    shader_lib_ctx.dst                            = dst;
912    shader_lib_ctx.max_wg_size        = ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
913    shader_lib_ctx.wg_mem_limit_bytes = ctx->global_ctx->capabilities.limits.maxComputeWorkgroupStorageSize;
914
915    webgpu_pipeline pipeline = ctx->shader_lib->get_solve_tri_pipeline(shader_lib_ctx);
916
917    auto * decisions = static_cast<ggml_webgpu_generic_shader_decisions *>(pipeline.context.get());
918
919    std::vector<uint32_t> params = {
920        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src0) / ggml_type_size(src0->type)),
921        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src1) / ggml_type_size(src1->type)),
922        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, dst) / ggml_type_size(dst->type)),
923
924        (uint32_t) (src0->nb[0] / ggml_type_size(src0->type)),
925        (uint32_t) (src0->nb[1] / ggml_type_size(src0->type)),
926        (uint32_t) (src0->nb[2] / ggml_type_size(src0->type)),
927        (uint32_t) (src0->nb[3] / ggml_type_size(src0->type)),
928
929        (uint32_t) (src1->nb[0] / ggml_type_size(src1->type)),
930        (uint32_t) (src1->nb[1] / ggml_type_size(src1->type)),
931        (uint32_t) (src1->nb[2] / ggml_type_size(src1->type)),
932        (uint32_t) (src1->nb[3] / ggml_type_size(src1->type)),
933
934        (uint32_t) (dst->nb[0] / ggml_type_size(dst->type)),
935        (uint32_t) (dst->nb[1] / ggml_type_size(dst->type)),
936        (uint32_t) (dst->nb[2] / ggml_type_size(dst->type)),
937        (uint32_t) (dst->nb[3] / ggml_type_size(dst->type)),
938
939        (uint32_t) src1->ne[0],
940        (uint32_t) dst->ne[2],
941    };
942
943    std::vector<wgpu::BindGroupEntry> entries = {
944        ggml_webgpu_make_tensor_bind_group_entry(ctx, 0, src0),
945        ggml_webgpu_make_tensor_bind_group_entry(ctx, 1, src1),
946        ggml_webgpu_make_tensor_bind_group_entry(ctx, 2, dst),
947    };
948
949    const uint32_t wg_x = CEIL_DIV((uint32_t) src1->ne[0], decisions->wg_size);
950    const uint32_t wg_y = (uint32_t) (dst->ne[2] * dst->ne[3]);
951    return ggml_backend_webgpu_build(ctx, pipeline, params, entries, wg_x, wg_y);
952}
953
954static webgpu_encoded_op ggml_webgpu_conv_2d(webgpu_context & ctx,
955                                             ggml_tensor *    src0,
956                                             ggml_tensor *    src1,
957                                             ggml_tensor *    dst) {
958    const int32_t s0 = ggml_get_op_params_i32(dst, 0);
959    const int32_t s1 = ggml_get_op_params_i32(dst, 1);
960    const int32_t p0 = ggml_get_op_params_i32(dst, 2);
961    const int32_t p1 = ggml_get_op_params_i32(dst, 3);
962    const int32_t d0 = ggml_get_op_params_i32(dst, 4);
963    const int32_t d1 = ggml_get_op_params_i32(dst, 5);
964
965    std::vector<uint32_t> params = {
966        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src0) / ggml_type_size(src0->type)),
967        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src1) / ggml_type_size(src1->type)),
968        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, dst) / ggml_type_size(dst->type)),
969
970        (uint32_t) (src0->nb[0] / ggml_type_size(src0->type)),
971        (uint32_t) (src0->nb[1] / ggml_type_size(src0->type)),
972        (uint32_t) (src0->nb[2] / ggml_type_size(src0->type)),
973        (uint32_t) (src0->nb[3] / ggml_type_size(src0->type)),
974
975        (uint32_t) (src1->nb[0] / ggml_type_size(src1->type)),
976        (uint32_t) (src1->nb[1] / ggml_type_size(src1->type)),
977        (uint32_t) (src1->nb[2] / ggml_type_size(src1->type)),
978        (uint32_t) (src1->nb[3] / ggml_type_size(src1->type)),
979
980        (uint32_t) (dst->nb[0] / ggml_type_size(dst->type)),
981        (uint32_t) (dst->nb[1] / ggml_type_size(dst->type)),
982        (uint32_t) (dst->nb[2] / ggml_type_size(dst->type)),
983        (uint32_t) (dst->nb[3] / ggml_type_size(dst->type)),
984
985        (uint32_t) src0->ne[0],
986        (uint32_t) src0->ne[1],
987        (uint32_t) src0->ne[2],
988
989        (uint32_t) src1->ne[0],
990        (uint32_t) src1->ne[1],
991
992        (uint32_t) dst->ne[0],
993        (uint32_t) dst->ne[1],
994        (uint32_t) dst->ne[2],
995        (uint32_t) dst->ne[3],
996
997        (uint32_t) s0,
998        (uint32_t) s1,
999        (uint32_t) p0,
1000        (uint32_t) p1,
1001        (uint32_t) d0,
1002        (uint32_t) d1,
1003    };
1004
1005    std::vector<wgpu::BindGroupEntry> entries = {
1006        ggml_webgpu_make_tensor_bind_group_entry(ctx, 0, src0),
1007        ggml_webgpu_make_tensor_bind_group_entry(ctx, 1, src1),
1008        ggml_webgpu_make_tensor_bind_group_entry(ctx, 2, dst),
1009    };
1010
1011    ggml_webgpu_shader_lib_context shader_lib_ctx = {};
1012    shader_lib_ctx.src0                           = src0;
1013    shader_lib_ctx.src1                           = src1;
1014    shader_lib_ctx.dst                            = dst;
1015    shader_lib_ctx.max_wg_size = ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
1016
1017    webgpu_pipeline pipeline = ctx->shader_lib->get_conv2d_pipeline(shader_lib_ctx);
1018
1019    auto * decisions = static_cast<ggml_webgpu_generic_shader_decisions *>(pipeline.context.get());
1020
1021    uint32_t wg_x;
1022    uint32_t wg_y;
1023    uint32_t total_wg = CEIL_DIV((uint32_t) ggml_nelements(dst), decisions->wg_size);
1024    compute_2d_workgroups(total_wg, ctx->global_ctx->capabilities.limits.maxComputeWorkgroupsPerDimension, wg_x, wg_y);
1025
1026    return ggml_backend_webgpu_build(ctx, pipeline, params, entries, wg_x, wg_y);
1027}
1028
1029// Same param/binding layout as conv_2d; the shader differs
1030static webgpu_encoded_op ggml_webgpu_conv_2d_dw(webgpu_context & ctx,
1031                                                ggml_tensor *    src0,
1032                                                ggml_tensor *    src1,
1033                                                ggml_tensor *    dst) {
1034    const int32_t s0 = ggml_get_op_params_i32(dst, 0);
1035    const int32_t s1 = ggml_get_op_params_i32(dst, 1);
1036    const int32_t p0 = ggml_get_op_params_i32(dst, 2);
1037    const int32_t p1 = ggml_get_op_params_i32(dst, 3);
1038    const int32_t d0 = ggml_get_op_params_i32(dst, 4);
1039    const int32_t d1 = ggml_get_op_params_i32(dst, 5);
1040
1041    // Scalar params matching conv2d_dw.wgsl (weight src0 [KW,KH,1,C], input src1, output dst).
1042    std::vector<uint32_t> params = {
1043        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src0) / ggml_type_size(src0->type)),
1044        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src1) / ggml_type_size(src1->type)),
1045        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, dst) / ggml_type_size(dst->type)),
1046
1047        (uint32_t) ggml_nelements(dst),
1048        (uint32_t) dst->ne[2],
1049        (uint32_t) dst->ne[0],
1050        (uint32_t) dst->ne[1],
1051        (uint32_t) src1->ne[0],
1052        (uint32_t) src1->ne[1],
1053        (uint32_t) src0->ne[0],
1054        (uint32_t) src0->ne[1],
1055
1056        (uint32_t) s0,
1057        (uint32_t) s1,
1058        (uint32_t) p0,
1059        (uint32_t) p1,
1060        (uint32_t) d0,
1061        (uint32_t) d1,
1062    };
1063
1064    std::vector<wgpu::BindGroupEntry> entries = {
1065        ggml_webgpu_make_tensor_bind_group_entry(ctx, 0, src0),
1066        ggml_webgpu_make_tensor_bind_group_entry(ctx, 1, src1),
1067        ggml_webgpu_make_tensor_bind_group_entry(ctx, 2, dst),
1068    };
1069
1070    ggml_webgpu_shader_lib_context shader_lib_ctx = {};
1071    shader_lib_ctx.src0                           = src0;
1072    shader_lib_ctx.src1                           = src1;
1073    shader_lib_ctx.dst                            = dst;
1074    shader_lib_ctx.max_wg_size = ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
1075
1076    // Input layout: contiguous -> WHCN, contiguous-channels -> CWHN
1077    const bool      whcn      = ggml_is_contiguous(src1);
1078    webgpu_pipeline pipeline  = ctx->shader_lib->get_conv2d_dw_pipeline(shader_lib_ctx, whcn);
1079    auto *          decisions = static_cast<ggml_webgpu_generic_shader_decisions *>(pipeline.context.get());
1080
1081    uint32_t wg_x;
1082    uint32_t wg_y;
1083    uint32_t total_wg = CEIL_DIV((uint32_t) ggml_nelements(dst), decisions->wg_size);
1084    compute_2d_workgroups(total_wg, ctx->global_ctx->capabilities.limits.maxComputeWorkgroupsPerDimension, wg_x, wg_y);
1085
1086    return ggml_backend_webgpu_build(ctx, pipeline, params, entries, wg_x, wg_y);
1087}
1088
1089static webgpu_encoded_op ggml_webgpu_im2col(webgpu_context & ctx,
1090                                            ggml_tensor *    src0,
1091                                            ggml_tensor *    src1,
1092                                            ggml_tensor *    dst) {
1093    const int32_t s0    = ggml_get_op_params_i32(dst, 0);
1094    const int32_t s1    = ggml_get_op_params_i32(dst, 1);
1095    const int32_t p0    = ggml_get_op_params_i32(dst, 2);
1096    const int32_t p1    = ggml_get_op_params_i32(dst, 3);
1097    const int32_t d0    = ggml_get_op_params_i32(dst, 4);
1098    const int32_t d1    = ggml_get_op_params_i32(dst, 5);
1099    const bool    is_2D = ggml_get_op_params_i32(dst, 6) == 1;
1100
1101    const uint32_t KW = src0->ne[0];
1102    const uint32_t KH = is_2D ? src0->ne[1] : 1;
1103    const uint32_t IC = is_2D ? src0->ne[2] : src0->ne[1];
1104
1105    const uint32_t IW = src1->ne[0];
1106    const uint32_t IH = is_2D ? src1->ne[1] : 1;
1107    const uint32_t N  = is_2D ? src1->ne[3] : src1->ne[2];
1108
1109    const uint32_t OW = dst->ne[1];
1110    const uint32_t OH = is_2D ? dst->ne[2] : 1;
1111
1112    const uint32_t si0 = (uint32_t) (src1->nb[0] / ggml_type_size(src1->type));
1113    const uint32_t si1 = is_2D ? (uint32_t) (src1->nb[1] / ggml_type_size(src1->type)) : 0;
1114    const uint32_t si2 = is_2D ? (uint32_t) (src1->nb[2] / ggml_type_size(src1->type)) :
1115                                 (uint32_t) (src1->nb[1] / ggml_type_size(src1->type));
1116    const uint32_t si3 = is_2D ? (uint32_t) (src1->nb[3] / ggml_type_size(src1->type)) :
1117                                 (uint32_t) (src1->nb[2] / ggml_type_size(src1->type));
1118
1119    const uint32_t so0 = (uint32_t) (dst->nb[0] / ggml_type_size(dst->type));
1120    const uint32_t so1 = (uint32_t) (dst->nb[1] / ggml_type_size(dst->type));
1121    const uint32_t so2 = is_2D ? (uint32_t) (dst->nb[2] / ggml_type_size(dst->type)) : 0;
1122    const uint32_t so3 = is_2D ? (uint32_t) (dst->nb[3] / ggml_type_size(dst->type)) :
1123                                 (uint32_t) (dst->nb[2] / ggml_type_size(dst->type));
1124
1125    std::vector<uint32_t> params = {
1126        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src1) / ggml_type_size(src1->type)),
1127        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, dst) / ggml_type_size(dst->type)),
1128
1129        si0,
1130        si1,
1131        si2,
1132        si3,
1133        so0,
1134        so1,
1135        so2,
1136        so3,
1137
1138        KW,
1139        KH,
1140        IC,
1141
1142        IW,
1143        IH,
1144        N,
1145
1146        OW,
1147        OH,
1148
1149        (uint32_t) s0,
1150        (uint32_t) s1,
1151        (uint32_t) p0,
1152        (uint32_t) p1,
1153        (uint32_t) d0,
1154        (uint32_t) d1,
1155    };
1156
1157    std::vector<wgpu::BindGroupEntry> entries = {
1158        ggml_webgpu_make_tensor_bind_group_entry(ctx, 0, src1),
1159        ggml_webgpu_make_tensor_bind_group_entry(ctx, 1, dst),
1160    };
1161
1162    ggml_webgpu_shader_lib_context shader_lib_ctx = {};
1163    shader_lib_ctx.src0                           = src0;
1164    shader_lib_ctx.src1                           = src1;
1165    shader_lib_ctx.dst                            = dst;
1166    shader_lib_ctx.max_wg_size = ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
1167
1168    webgpu_pipeline pipeline = ctx->shader_lib->get_im2col_pipeline(shader_lib_ctx);
1169
1170    auto * decisions = static_cast<ggml_webgpu_generic_shader_decisions *>(pipeline.context.get());
1171
1172    uint32_t wg_x;
1173    uint32_t wg_y;
1174    uint32_t total_wg = CEIL_DIV((uint32_t) ggml_nelements(dst), decisions->wg_size);
1175    compute_2d_workgroups(total_wg, ctx->global_ctx->capabilities.limits.maxComputeWorkgroupsPerDimension, wg_x, wg_y);
1176
1177    return ggml_backend_webgpu_build(ctx, pipeline, params, entries, wg_x, wg_y);
1178}
1179
1180static webgpu_encoded_op ggml_webgpu_ssm_conv(webgpu_context & ctx,
1181                                              ggml_tensor *    src0,
1182                                              ggml_tensor *    src1,
1183                                              ggml_tensor *    dst) {
1184    ggml_webgpu_shader_lib_context shader_lib_ctx = {};
1185    shader_lib_ctx.src0                           = src0;
1186    shader_lib_ctx.src1                           = src1;
1187    shader_lib_ctx.dst                            = dst;
1188    shader_lib_ctx.max_wg_size = ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
1189
1190    webgpu_pipeline pipeline  = ctx->shader_lib->get_ssm_conv_pipeline(shader_lib_ctx);
1191    auto *          decisions = static_cast<ggml_webgpu_ssm_conv_shader_decisions *>(pipeline.context.get());
1192
1193    const uint32_t token_tiles = CEIL_DIV((uint32_t) dst->ne[1], decisions->tokens_per_wg);
1194
1195    std::vector<uint32_t> params = {
1196        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src0) / ggml_type_size(src0->type)),
1197        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src1) / ggml_type_size(src1->type)),
1198        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, dst) / ggml_type_size(dst->type)),
1199
1200        (uint32_t) (src0->nb[1] / ggml_type_size(src0->type)),
1201        (uint32_t) (src0->nb[2] / ggml_type_size(src0->type)),
1202        (uint32_t) (src1->nb[1] / ggml_type_size(src1->type)),
1203
1204        (uint32_t) (dst->nb[0] / ggml_type_size(dst->type)),
1205        (uint32_t) (dst->nb[1] / ggml_type_size(dst->type)),
1206        (uint32_t) (dst->nb[2] / ggml_type_size(dst->type)),
1207
1208        (uint32_t) src1->ne[0],
1209        (uint32_t) src0->ne[1],
1210        (uint32_t) dst->ne[1],
1211        (uint32_t) dst->ne[2],
1212        token_tiles,
1213    };
1214
1215    std::vector<wgpu::BindGroupEntry> entries = {
1216        ggml_webgpu_make_tensor_bind_group_entry(ctx, 0, src0),
1217        ggml_webgpu_make_tensor_bind_group_entry(ctx, 1, src1),
1218        ggml_webgpu_make_tensor_bind_group_entry(ctx, 2, dst),
1219    };
1220
1221    const uint32_t wg_x = CEIL_DIV((uint32_t) src0->ne[1], decisions->block_size);
1222    const uint32_t wg_y = token_tiles * (uint32_t) dst->ne[2];
1223    return ggml_backend_webgpu_build(ctx, pipeline, params, entries, wg_x, wg_y);
1224}
1225
1226static webgpu_encoded_op ggml_webgpu_ssm_scan(webgpu_context & ctx,
1227                                              ggml_tensor *    src0,
1228                                              ggml_tensor *    src1,
1229                                              ggml_tensor *    src2,
1230                                              ggml_tensor *    src3,
1231                                              ggml_tensor *    src4,
1232                                              ggml_tensor *    src5,
1233                                              ggml_tensor *    src6,
1234                                              ggml_tensor *    dst) {
1235    ggml_webgpu_shader_lib_context shader_lib_ctx = {};
1236    shader_lib_ctx.src0                           = src0;
1237    shader_lib_ctx.src1                           = src1;
1238    shader_lib_ctx.src2                           = src2;
1239    shader_lib_ctx.src3                           = src3;
1240    shader_lib_ctx.src4                           = src4;
1241    shader_lib_ctx.src5                           = src5;
1242    shader_lib_ctx.dst                            = dst;
1243    shader_lib_ctx.max_wg_size        = ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
1244    shader_lib_ctx.supports_subgroups = ctx->global_ctx->capabilities.supports_subgroups;
1245    bool                             xbc_overlap = ggml_webgpu_tensor_binding_overlap(ctx->global_ctx, src1, src2) ||
1246                                                   ggml_webgpu_tensor_binding_overlap(ctx->global_ctx, src1, src4) ||
1247                                                   ggml_webgpu_tensor_binding_overlap(ctx->global_ctx, src1, src5) ||
1248                                                   ggml_webgpu_tensor_binding_overlap(ctx->global_ctx, src2, src4) ||
1249                                                   ggml_webgpu_tensor_binding_overlap(ctx->global_ctx, src2, src5) ||
1250                                                   ggml_webgpu_tensor_binding_overlap(ctx->global_ctx, src4, src5);
1251    bool                             a_overlap   = false;
1252    bool                             ids_overlap = false;
1253    ggml_webgpu_merged_binding_range xbc_merged_range = {};
1254    if (xbc_overlap) {
1255        xbc_merged_range = ggml_webgpu_tensor_merged_binding_range(ctx, { src1, src2, src4, src5 });
1256        a_overlap        = ggml_webgpu_tensor_binding_overlap_range(ctx->global_ctx, src3, src1->buffer,
1257                                                                    xbc_merged_range.offset, xbc_merged_range.size);
1258        if (a_overlap) {
1259            xbc_merged_range = ggml_webgpu_tensor_merged_binding_range(ctx, { src1, src2, src3, src4, src5 });
1260        }
1261        ids_overlap = ggml_webgpu_tensor_binding_overlap_range(ctx->global_ctx, src6, src1->buffer,
1262                                                               xbc_merged_range.offset, xbc_merged_range.size);
1263        if (ids_overlap) {
1264            xbc_merged_range =
1265                a_overlap ? ggml_webgpu_tensor_merged_binding_range(ctx, { src1, src2, src3, src4, src5, src6 }) :
1266                            ggml_webgpu_tensor_merged_binding_range(ctx, { src1, src2, src4, src5, src6 });
1267        }
1268    }
1269
1270    webgpu_pipeline pipeline =
1271        ctx->shader_lib->get_ssm_scan_pipeline(shader_lib_ctx, xbc_overlap, a_overlap, ids_overlap);
1272    auto * decisions = static_cast<ggml_webgpu_ssm_scan_shader_decisions *>(pipeline.context.get());
1273    xbc_overlap      = decisions->xbc_overlap;
1274    a_overlap        = decisions->a_overlap;
1275    ids_overlap      = decisions->ids_overlap;
1276
1277    uint32_t offset_x        = (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src1) / ggml_type_size(src1->type));
1278    uint32_t offset_dt       = (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src2) / ggml_type_size(src2->type));
1279    uint32_t offset_A        = (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src3) / ggml_type_size(src3->type));
1280    uint32_t offset_B        = (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src4) / ggml_type_size(src4->type));
1281    uint32_t offset_C        = (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src5) / ggml_type_size(src5->type));
1282    uint32_t offset_ids      = (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src6) / ggml_type_size(src6->type));
1283    size_t   xbc_bind_offset = 0;
1284    size_t   xbc_bind_size   = 0;
1285    if (xbc_overlap) {
1286        xbc_bind_offset = xbc_merged_range.offset;
1287        xbc_bind_size   = xbc_merged_range.size;
1288        offset_x        = ggml_webgpu_tensor_merged_element_offset(src1, xbc_merged_range);
1289        offset_dt       = ggml_webgpu_tensor_merged_element_offset(src2, xbc_merged_range);
1290        if (a_overlap) {
1291            offset_A = ggml_webgpu_tensor_merged_element_offset(src3, xbc_merged_range);
1292        }
1293        offset_B = ggml_webgpu_tensor_merged_element_offset(src4, xbc_merged_range);
1294        offset_C = ggml_webgpu_tensor_merged_element_offset(src5, xbc_merged_range);
1295        if (ids_overlap) {
1296            offset_ids = ggml_webgpu_tensor_merged_element_offset(src6, xbc_merged_range);
1297        }
1298    }
1299
1300    std::vector<uint32_t> params = {
1301        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src0) / ggml_type_size(src0->type)),
1302        offset_x,
1303        offset_dt,
1304        offset_A,
1305        offset_B,
1306        offset_C,
1307        offset_ids,
1308        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, dst) / ggml_type_size(dst->type)),
1309
1310        (uint32_t) (src0->nb[1] / ggml_type_size(src0->type)),
1311        (uint32_t) (src0->nb[2] / ggml_type_size(src0->type)),
1312        (uint32_t) (src0->nb[3] / ggml_type_size(src0->type)),
1313
1314        (uint32_t) (src1->nb[1] / ggml_type_size(src1->type)),
1315        (uint32_t) (src1->nb[2] / ggml_type_size(src1->type)),
1316        (uint32_t) (src1->nb[3] / ggml_type_size(src1->type)),
1317
1318        (uint32_t) (src2->nb[1] / ggml_type_size(src2->type)),
1319        (uint32_t) (src2->nb[2] / ggml_type_size(src2->type)),
1320
1321        (uint32_t) src3->ne[0],
1322        (uint32_t) (src3->nb[1] / ggml_type_size(src3->type)),
1323
1324        (uint32_t) (src4->nb[1] / ggml_type_size(src4->type)),
1325        (uint32_t) (src4->nb[2] / ggml_type_size(src4->type)),
1326        (uint32_t) (src4->nb[3] / ggml_type_size(src4->type)),
1327
1328        (uint32_t) (src5->nb[1] / ggml_type_size(src5->type)),
1329        (uint32_t) (src5->nb[2] / ggml_type_size(src5->type)),
1330        (uint32_t) (src5->nb[3] / ggml_type_size(src5->type)),
1331
1332        (uint32_t) src0->ne[0],
1333        (uint32_t) src0->ne[1],
1334        (uint32_t) src0->ne[2],
1335        (uint32_t) src4->ne[1],
1336        (uint32_t) src1->ne[2],
1337        (uint32_t) ggml_nelements(src1),
1338        (uint32_t) ggml_get_op_params_i32(dst, 0),
1339    };
1340
1341    std::vector<wgpu::BindGroupEntry> entries = {
1342        ggml_webgpu_make_tensor_bind_group_entry(ctx, 0, src0),
1343    };
1344    if (xbc_overlap) {
1345        entries.push_back(
1346            ggml_webgpu_make_bind_group_entry(1, ggml_webgpu_tensor_buf(src1), xbc_bind_offset, xbc_bind_size));
1347        if (ids_overlap) {
1348            if (!a_overlap) {
1349                entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 2, src3));
1350            }
1351            entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, a_overlap ? 2 : 3, dst));
1352        } else if (a_overlap) {
1353            entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 2, src6));
1354            entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 3, dst));
1355        } else {
1356            entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 2, src3));
1357            entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 3, src6));
1358            entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 4, dst));
1359        }
1360    } else {
1361        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 1, src1));
1362        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 2, src2));
1363        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 3, src3));
1364        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 4, src4));
1365        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 5, src5));
1366        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 6, src6));
1367        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 7, dst));
1368    }
1369
1370    const uint32_t total_wg       = (uint32_t) (src0->ne[1] * src0->ne[2] * src1->ne[3]);
1371    const uint32_t max_wg_per_dim = ctx->global_ctx->capabilities.limits.maxComputeWorkgroupsPerDimension;
1372    uint32_t       wg_x;
1373    uint32_t       wg_y;
1374    compute_2d_workgroups(total_wg, max_wg_per_dim, wg_x, wg_y);
1375
1376    return ggml_backend_webgpu_build(ctx, pipeline, params, entries, wg_x, wg_y);
1377}
1378
1379static webgpu_encoded_op ggml_webgpu_gated_delta_net(webgpu_context & ctx,
1380                                                     ggml_tensor *    src0,
1381                                                     ggml_tensor *    src1,
1382                                                     ggml_tensor *    src2,
1383                                                     ggml_tensor *    src3,
1384                                                     ggml_tensor *    src4,
1385                                                     ggml_tensor *    src5,
1386                                                     ggml_tensor *    dst) {
1387    ggml_webgpu_shader_lib_context shader_lib_ctx = {};
1388    shader_lib_ctx.src0                           = src0;
1389    shader_lib_ctx.src1                           = src1;
1390    shader_lib_ctx.src2                           = src2;
1391    shader_lib_ctx.src3                           = src3;
1392    shader_lib_ctx.src4                           = src4;
1393    shader_lib_ctx.dst                            = dst;
1394    shader_lib_ctx.max_wg_size = ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
1395
1396    webgpu_pipeline pipeline = ctx->shader_lib->get_gated_delta_net_pipeline(shader_lib_ctx);
1397
1398    const uint32_t s_v      = (uint32_t) src2->ne[0];
1399    const uint32_t h        = (uint32_t) src2->ne[1];
1400    const uint32_t n_tokens = (uint32_t) src2->ne[2];
1401    const uint32_t n_seqs   = (uint32_t) src2->ne[3];
1402    const uint32_t K        = (uint32_t) ggml_get_op_params_i32(dst, 0);
1403    const float    scale    = 1.0f / sqrtf((float) s_v);
1404    uint32_t       scale_u32;
1405    memcpy(&scale_u32, &scale, sizeof(scale_u32));
1406
1407    std::vector<uint32_t> params = {
1408        h,
1409        n_tokens,
1410        n_seqs,
1411        s_v * h * n_tokens * n_seqs,
1412
1413        (uint32_t) (src0->nb[1] / ggml_type_size(src0->type)),
1414        (uint32_t) (src0->nb[2] / ggml_type_size(src0->type)),
1415        (uint32_t) (src0->nb[3] / ggml_type_size(src0->type)),
1416
1417        (uint32_t) (src2->nb[1] / ggml_type_size(src2->type)),
1418        (uint32_t) (src2->nb[2] / ggml_type_size(src2->type)),
1419        (uint32_t) (src2->nb[3] / ggml_type_size(src2->type)),
1420
1421        (uint32_t) (src4->nb[1] / ggml_type_size(src4->type)),
1422        (uint32_t) (src4->nb[2] / ggml_type_size(src4->type)),
1423        (uint32_t) (src4->nb[3] / ggml_type_size(src4->type)),
1424
1425        (uint32_t) src0->ne[1],
1426        (uint32_t) (src2->ne[3] / src0->ne[3]),
1427        K,
1428        scale_u32,
1429    };
1430
1431    std::vector<wgpu::BindGroupEntry> entries = {
1432        ggml_webgpu_make_tensor_bind_group_entry(ctx, 0, src0), ggml_webgpu_make_tensor_bind_group_entry(ctx, 1, src1),
1433        ggml_webgpu_make_tensor_bind_group_entry(ctx, 2, src2), ggml_webgpu_make_tensor_bind_group_entry(ctx, 3, src3),
1434        ggml_webgpu_make_tensor_bind_group_entry(ctx, 4, src4), ggml_webgpu_make_tensor_bind_group_entry(ctx, 5, src5),
1435        ggml_webgpu_make_tensor_bind_group_entry(ctx, 6, dst),
1436    };
1437
1438    return ggml_backend_webgpu_build(ctx, pipeline, params, entries, h, n_seqs);
1439}
1440
1441static std::optional<webgpu_encoded_op> ggml_webgpu_set_rows(webgpu_context & ctx,
1442                                                             ggml_tensor *    src,
1443                                                             ggml_tensor *    idx,
1444                                                             ggml_tensor *    dst) {
1445    // For set rows specifically, we need to check if src and idx are empty
1446    // tensors.
1447    if (ggml_is_empty(src) || ggml_is_empty(idx)) {
1448        return std::nullopt;
1449    }
1450
1451    ggml_webgpu_shader_lib_context shader_lib_ctx = {};
1452    shader_lib_ctx.src0                           = src;
1453    shader_lib_ctx.src1                           = idx;
1454    shader_lib_ctx.dst                            = dst;
1455    shader_lib_ctx.max_wg_size = ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
1456
1457    webgpu_pipeline pipeline = ctx->shader_lib->get_set_rows_pipeline(shader_lib_ctx);
1458
1459    auto * decisions = static_cast<ggml_webgpu_set_rows_shader_decisions *>(pipeline.context.get());
1460
1461    std::vector<uint32_t> params = {
1462        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src) / ggml_type_size(src->type)),
1463        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, idx) / ggml_type_size(idx->type)),
1464        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, dst) / ggml_type_size(dst->type)),
1465        // Convert byte-strides to element-strides
1466        (uint32_t) (src->nb[1] / ggml_type_size(src->type)), (uint32_t) (src->nb[2] / ggml_type_size(src->type)),
1467        (uint32_t) (src->nb[3] / ggml_type_size(src->type)), (uint32_t) (idx->nb[0] / ggml_type_size(idx->type)),
1468        (uint32_t) (idx->nb[1] / ggml_type_size(idx->type)), (uint32_t) (idx->nb[2] / ggml_type_size(idx->type)),
1469        (uint32_t) (dst->nb[1] / ggml_type_size(dst->type)), (uint32_t) (dst->nb[2] / ggml_type_size(dst->type)),
1470        (uint32_t) (dst->nb[3] / ggml_type_size(dst->type)),
1471        // Shape of src
1472        (uint32_t) src->ne[0], (uint32_t) src->ne[1], (uint32_t) src->ne[2], (uint32_t) src->ne[3],
1473        // Shape of idx
1474        (uint32_t) (idx->ne[1]), (uint32_t) (idx->ne[2])
1475    };
1476
1477    std::vector<wgpu::BindGroupEntry> entries;
1478    entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 0, src));
1479    entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 1, idx));
1480    entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 2, dst));
1481
1482    if (decisions->i64_idx) {
1483        entries.push_back(ggml_webgpu_make_bind_group_entry(3, ctx->set_rows_dev_error_buf, 0,
1484                                                            ctx->set_rows_dev_error_buf.GetSize()));
1485    }
1486
1487    uint32_t threads;
1488    if (ggml_is_quantized(dst->type)) {
1489        const uint32_t blocks_per_row = src->ne[0] / ggml_blck_size(dst->type);
1490        threads =
1491            (src->ne[1] * src->ne[2] * src->ne[3]) * (decisions->pair_blocks ? (blocks_per_row / 2) : blocks_per_row);
1492    } else if (decisions->vec4) {
1493        threads = (src->ne[1] * src->ne[2] * src->ne[3]) * (src->ne[0] / 4);
1494    } else {
1495        threads = src->ne[0] * src->ne[1] * src->ne[2] * src->ne[3];
1496    }
1497    uint32_t wg_x = CEIL_DIV(threads, decisions->wg_size);
1498    return ggml_backend_webgpu_build(ctx, pipeline, params, entries, wg_x, 1);
1499}
1500
1501// Workgroup size is a common constant
1502static std::vector<wgpu::ConstantEntry> ggml_webgpu_wg_size_entry(uint32_t wg_size) {
1503    std::vector<wgpu::ConstantEntry> constants(1);
1504    constants[0].key   = "wg_size";
1505    constants[0].value = wg_size;
1506    return constants;
1507}
1508
1509static webgpu_encoded_op ggml_webgpu_get_rows(webgpu_context & ctx,
1510                                              ggml_tensor *    src,
1511                                              ggml_tensor *    idx,
1512                                              ggml_tensor *    dst) {
1513    const bool float_parallel = src->type == GGML_TYPE_F32 || src->type == GGML_TYPE_F16 || src->type == GGML_TYPE_I32;
1514
1515    ggml_webgpu_shader_lib_context shader_lib_ctx = {};
1516    shader_lib_ctx.src0                           = src;
1517    shader_lib_ctx.src1                           = nullptr;
1518    shader_lib_ctx.dst                            = dst;
1519    shader_lib_ctx.max_wg_size = ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
1520
1521    webgpu_pipeline pipeline  = ctx->shader_lib->get_get_rows_pipeline(shader_lib_ctx);
1522    auto *          decisions = static_cast<ggml_webgpu_generic_shader_decisions *>(pipeline.context.get());
1523
1524    std::vector<uint32_t> params = { (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src) / ggml_type_size(src->type)),
1525                                     (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, idx) / ggml_type_size(idx->type)),
1526                                     (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, dst) / ggml_type_size(dst->type)),
1527                                     (uint32_t) (src->nb[1] / ggml_type_size(src->type)),
1528                                     (uint32_t) (src->nb[2] / ggml_type_size(src->type)),
1529                                     (uint32_t) (src->nb[3] / ggml_type_size(src->type)),
1530                                     (uint32_t) (idx->nb[0] / ggml_type_size(idx->type)),
1531                                     (uint32_t) (idx->nb[1] / ggml_type_size(idx->type)),
1532                                     (uint32_t) (idx->nb[2] / ggml_type_size(idx->type)),
1533                                     (uint32_t) (dst->nb[1] / ggml_type_size(dst->type)),
1534                                     (uint32_t) (dst->nb[2] / ggml_type_size(dst->type)),
1535                                     (uint32_t) (dst->nb[3] / ggml_type_size(dst->type)),
1536                                     (uint32_t) dst->ne[0],
1537                                     (uint32_t) dst->ne[1],
1538                                     (uint32_t) dst->ne[2],
1539                                     (uint32_t) dst->ne[3],
1540                                     (uint32_t) (idx->ne[1]),
1541                                     (uint32_t) (idx->ne[2]) };
1542
1543    std::vector<wgpu::BindGroupEntry> entries = { ggml_webgpu_make_tensor_bind_group_entry(ctx, 0, src),
1544                                                  ggml_webgpu_make_tensor_bind_group_entry(ctx, 1, idx),
1545                                                  ggml_webgpu_make_tensor_bind_group_entry(ctx, 2, dst) };
1546
1547    uint32_t blocks_per_row = (uint32_t) (dst->ne[0] / (src->type == GGML_TYPE_F32 && dst->ne[0] % 4 == 0 ? 4 : 1));
1548    uint32_t total_rows     = (uint32_t) (dst->ne[1] * dst->ne[2] * dst->ne[3]);
1549    uint32_t total_threads  = float_parallel ? blocks_per_row * total_rows : total_rows;
1550    uint32_t wg_x           = CEIL_DIV(total_threads, decisions->wg_size);
1551
1552    return ggml_backend_webgpu_build(ctx, pipeline, params, entries, wg_x);
1553}
1554
1555static void ggml_webgpu_quantize_q8_dispatch(webgpu_context &                    ctx,
1556                                             ggml_tensor *                       src0,
1557                                             ggml_tensor *                       src1,
1558                                             ggml_tensor *                       dst,
1559                                             std::vector<webgpu_dispatch_desc> & dispatches) {
1560    ggml_webgpu_shader_lib_context shader_lib_ctx = {};
1561
1562    shader_lib_ctx.src0               = src0;
1563    shader_lib_ctx.src1               = src1;
1564    shader_lib_ctx.dst                = dst;
1565    shader_lib_ctx.max_wg_size        = ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
1566    shader_lib_ctx.supports_subgroups = ctx->global_ctx->capabilities.supports_subgroups;
1567
1568    webgpu_pipeline qq8_pipeline = ctx->shader_lib->get_quantize_q8_pipeline(shader_lib_ctx);
1569
1570    // quantize_q8 pipeline
1571    const size_t dst_offset           = ggml_webgpu_tensor_offset(dst);
1572    const size_t q8_src1_align_offset = ROUNDUP_POW2(
1573        dst_offset + ggml_nbytes(dst), ctx->global_ctx->capabilities.limits.minStorageBufferOffsetAlignment);
1574    const size_t q8_src1_binding_size = ROUNDUP_POW2(
1575        src1->ne[3] * src1->ne[2] * src1->ne[1] * (36 /* sizeof(q8_1) */ * (src1->ne[0] / /* block_size */ 32)),
1576        WEBGPU_STORAGE_BUF_BINDING_MULT);
1577
1578    std::vector<uint32_t> q8_params = {
1579        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src1) / ggml_type_size(src1->type)),
1580        (uint32_t) (src1->nb[1] / ggml_type_size(src1->type)),
1581        (uint32_t) (src1->nb[2] / ggml_type_size(src1->type)),
1582        (uint32_t) (src1->nb[3] / ggml_type_size(src1->type)),
1583        (uint32_t) src1->ne[0],
1584        (uint32_t) src1->ne[1],
1585        (uint32_t) src1->ne[2],
1586        (uint32_t) src1->ne[3],
1587    };
1588
1589    std::vector<wgpu::BindGroupEntry> q8_entries = {
1590        ggml_webgpu_make_tensor_bind_group_entry(ctx, 0, src1),
1591        ggml_webgpu_make_bind_group_entry(1, ggml_webgpu_tensor_buf(dst), q8_src1_align_offset, q8_src1_binding_size)
1592    };
1593
1594    auto q8_decisions = static_cast<ggml_webgpu_generic_shader_decisions *>(qq8_pipeline.context.get());
1595
1596    uint32_t       q8_wg_size     = q8_decisions->wg_size;
1597    uint32_t       q8_wg_x        = 1;
1598    uint32_t       q8_wg_y        = 1;
1599    const uint32_t wg_per_vec     = (src0->ne[0] / 4 + (q8_wg_size - 1)) / q8_wg_size;
1600    const uint32_t q8_total_wg    = src1->ne[1] * src1->ne[2] * src1->ne[3] * wg_per_vec;
1601    const uint32_t max_wg_per_dim = ctx->global_ctx->capabilities.limits.maxComputeWorkgroupsPerDimension;
1602    compute_2d_workgroups(q8_total_wg, max_wg_per_dim, q8_wg_x, q8_wg_y);
1603
1604    dispatches.push_back({
1605        qq8_pipeline, std::move(q8_params), std::move(q8_entries), { q8_wg_x, q8_wg_y }
1606    });
1607}
1608
1609static webgpu_encoded_op ggml_webgpu_mul_mat(webgpu_context & ctx,
1610                                             ggml_tensor *    src0,
1611                                             ggml_tensor *    src1,
1612                                             ggml_tensor *    dst) {
1613    // Determine if this is a mat-vec operation
1614    bool use_mat_vec = (dst->ne[1] <= 4);
1615
1616    // use MMVQ path for mat-vec
1617    bool use_mmvq = ggml_webgpu_can_use_mmvq(src0, src1, ctx->global_ctx->capabilities.supports_dot_product,
1618                                             ctx->global_ctx->vendor);
1619
1620    ggml_webgpu_shader_lib_context shader_lib_ctx = {};
1621
1622    shader_lib_ctx.src0                     = src0;
1623    shader_lib_ctx.src1                     = src1;
1624    shader_lib_ctx.dst                      = dst;
1625    shader_lib_ctx.max_wg_size              = ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
1626    shader_lib_ctx.supports_subgroups       = ctx->global_ctx->capabilities.supports_subgroups;
1627    shader_lib_ctx.supports_subgroup_matrix = ctx->global_ctx->capabilities.supports_subgroup_matrix;
1628    shader_lib_ctx.sg_mat_m                 = ctx->global_ctx->capabilities.sg_mat_m;
1629    shader_lib_ctx.sg_mat_n                 = ctx->global_ctx->capabilities.sg_mat_n;
1630    shader_lib_ctx.sg_mat_k                 = ctx->global_ctx->capabilities.sg_mat_k;
1631    shader_lib_ctx.min_subgroup_size        = ctx->global_ctx->capabilities.min_subgroup_size;
1632    shader_lib_ctx.max_subgroup_size        = ctx->global_ctx->capabilities.max_subgroup_size;
1633    shader_lib_ctx.supports_dot_product     = ctx->global_ctx->capabilities.supports_dot_product;
1634    shader_lib_ctx.vendor                   = ctx->global_ctx->vendor;
1635
1636    // Get or create pipeline
1637    webgpu_pipeline                   pipeline;
1638    std::vector<webgpu_dispatch_desc> dispatches;
1639    const bool src_overlap = ggml_webgpu_tensor_binding_overlap(ctx->global_ctx, src0, src1) && !use_mmvq;
1640
1641    if (use_mat_vec) {
1642        if (use_mmvq) {
1643            ggml_webgpu_quantize_q8_dispatch(ctx, src0, src1, dst, dispatches);
1644        }
1645        pipeline = ctx->shader_lib->get_mul_mat_vec_pipeline(shader_lib_ctx, src_overlap);
1646    } else {
1647        pipeline = ctx->shader_lib->get_mul_mat_fast_pipeline(shader_lib_ctx, src_overlap);
1648    }
1649
1650    uint32_t offset_src0   = (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src0) / ggml_type_size(src0->type));
1651    uint32_t offset_src1   = (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src1) / ggml_type_size(src1->type));
1652    size_t   merged_offset = 0;
1653    size_t   merged_size   = 0;
1654    if (src_overlap) {
1655        const ggml_webgpu_merged_binding_range merged_range =
1656            ggml_webgpu_tensor_merged_binding_range(ctx, { src0, src1 });
1657        merged_offset = merged_range.offset;
1658        merged_size   = merged_range.size;
1659        offset_src0   = ggml_webgpu_tensor_merged_element_offset(src0, merged_range);
1660        offset_src1   = ggml_webgpu_tensor_merged_element_offset(src1, merged_range);
1661    }
1662
1663    // Build params
1664    std::vector<uint32_t> params = { offset_src0,
1665                                     offset_src1,
1666                                     (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, dst) / ggml_type_size(dst->type)),
1667                                     (uint32_t) dst->ne[0],
1668                                     (uint32_t) dst->ne[1],
1669                                     (uint32_t) src0->ne[0],
1670                                     (uint32_t) (src0->nb[1] / ggml_type_size(src0->type)),
1671                                     (uint32_t) (src1->nb[1] / ggml_type_size(src1->type)),
1672                                     (uint32_t) (src0->nb[2] / ggml_type_size(src0->type)),
1673                                     (uint32_t) (src1->nb[2] / ggml_type_size(src1->type)),
1674                                     (uint32_t) (src0->nb[3] / ggml_type_size(src0->type)),
1675                                     (uint32_t) (src1->nb[3] / ggml_type_size(src1->type)),
1676                                     (uint32_t) src0->ne[2],
1677                                     (uint32_t) src0->ne[3],
1678                                     (uint32_t) (src1->ne[2] / src0->ne[2]),
1679                                     (uint32_t) (src1->ne[3] / src0->ne[3]) };
1680
1681    // Build bind group entries
1682    std::vector<wgpu::BindGroupEntry> entries = {};
1683    if (use_mmvq) {
1684        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 0, src0));
1685        auto & mmvq_qq8_entry = dispatches[0].bind_group_entries[1];
1686        entries.push_back(ggml_webgpu_make_bind_group_entry(1, ggml_webgpu_tensor_buf(dst), mmvq_qq8_entry.offset,
1687                                                            mmvq_qq8_entry.size));
1688        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 2, dst));
1689    } else if (src_overlap) {
1690        entries.push_back(
1691            ggml_webgpu_make_bind_group_entry(0, ggml_webgpu_tensor_buf(src0), merged_offset, merged_size));
1692        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 1, dst));
1693    } else {
1694        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 0, src0));
1695        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 1, src1));
1696        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 2, dst));
1697    }
1698
1699    // Calculate workgroup dimensions
1700    uint32_t       wg_x           = 1;
1701    uint32_t       wg_y           = 1;
1702    const uint32_t max_wg_per_dim = ctx->global_ctx->capabilities.limits.maxComputeWorkgroupsPerDimension;
1703
1704    if (use_mat_vec) {
1705        auto * decisions = static_cast<ggml_webgpu_mul_mat_vec_shader_decisions *>(pipeline.context.get());
1706
1707        uint32_t batches       = dst->ne[2] * dst->ne[3];
1708        uint32_t output_groups = CEIL_DIV(dst->ne[0], decisions->outputs_per_wg);
1709        uint32_t total_wg      = output_groups * batches;
1710        compute_2d_workgroups(total_wg, max_wg_per_dim, wg_x, wg_y);
1711    } else {
1712        auto * decisions = static_cast<ggml_webgpu_mul_mat_shader_decisions *>(pipeline.context.get());
1713
1714        // Fast-path tiled/subgroup calculations
1715        uint32_t wg_m;
1716        uint32_t wg_n;
1717        if (decisions->use_subgroup_matrix) {
1718            uint32_t wg_m_sg_tile =
1719                decisions->subgroup_m * decisions->subgroup_matrix_m * ctx->global_ctx->capabilities.sg_mat_m;
1720            wg_m = CEIL_DIV(dst->ne[0], wg_m_sg_tile);
1721            uint32_t wg_n_sg_tile =
1722                decisions->subgroup_n * decisions->subgroup_matrix_n * ctx->global_ctx->capabilities.sg_mat_n;
1723            wg_n = CEIL_DIV(dst->ne[1], wg_n_sg_tile);
1724        } else {
1725            uint32_t tile_m_s = decisions->tile_m * decisions->wg_size_m;
1726            uint32_t tile_n_s = decisions->tile_n * decisions->wg_size_n;
1727            wg_m              = CEIL_DIV(dst->ne[0], tile_m_s);
1728            wg_n              = CEIL_DIV(dst->ne[1], tile_n_s);
1729        }
1730        uint32_t total_wg = wg_m * wg_n * dst->ne[2] * dst->ne[3];
1731        compute_2d_workgroups(total_wg, max_wg_per_dim, wg_x, wg_y);
1732    }
1733
1734    dispatches.push_back({
1735        pipeline, std::move(params), std::move(entries), { wg_x, wg_y }
1736    });
1737
1738    return ggml_backend_webgpu_build_multi(ctx, dispatches);
1739}
1740
1741static webgpu_encoded_op ggml_webgpu_mul_mat_id_vec(webgpu_context & ctx,
1742                                                    ggml_tensor *    src0,
1743                                                    ggml_tensor *    src1,
1744                                                    ggml_tensor *    src2,
1745                                                    ggml_tensor *    dst) {
1746    const uint32_t param_n_expert      = (uint32_t) src0->ne[2];
1747    const uint32_t param_n_expert_used = (uint32_t) dst->ne[1];
1748
1749    ggml_webgpu_shader_lib_context shader_lib_ctx = {};
1750    shader_lib_ctx.src0                           = src0;
1751    shader_lib_ctx.src1                           = src1;
1752    shader_lib_ctx.src2                           = src2;
1753    shader_lib_ctx.dst                            = dst;
1754    shader_lib_ctx.supports_subgroups             = ctx->global_ctx->capabilities.supports_subgroups;
1755    shader_lib_ctx.max_wg_size = ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
1756
1757    webgpu_pipeline pipeline = ctx->shader_lib->get_mul_mat_id_vec_pipeline(shader_lib_ctx);
1758
1759    std::vector<uint32_t> params = {
1760        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src0) / ggml_type_size(src0->type)),
1761        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src1) / ggml_type_size(src1->type)),
1762        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src2) / ggml_type_size(src2->type)),
1763        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, dst) / ggml_type_size(dst->type)),
1764        (uint32_t) src0->ne[0],
1765        (uint32_t) src0->ne[1],
1766        param_n_expert,
1767        param_n_expert_used,
1768        (uint32_t) src1->ne[1],
1769        (uint32_t) (src0->nb[1] / ggml_type_size(src0->type)),
1770        (uint32_t) (src1->nb[1] / ggml_type_size(src1->type)),
1771        (uint32_t) (src0->nb[2] / ggml_type_size(src0->type)),
1772        (uint32_t) (src1->nb[2] / ggml_type_size(src1->type)),
1773    };
1774
1775    std::vector<wgpu::BindGroupEntry> entries = {
1776        ggml_webgpu_make_bind_group_entry(0, ggml_webgpu_tensor_buf(src0), ggml_webgpu_tensor_align_offset(ctx, src0),
1777                                          ggml_webgpu_tensor_binding_size(ctx, src0)),
1778        ggml_webgpu_make_bind_group_entry(1, ggml_webgpu_tensor_buf(src1), ggml_webgpu_tensor_align_offset(ctx, src1),
1779                                          ggml_webgpu_tensor_binding_size(ctx, src1)),
1780        ggml_webgpu_make_bind_group_entry(2, ggml_webgpu_tensor_buf(src2), ggml_webgpu_tensor_align_offset(ctx, src2),
1781                                          ggml_webgpu_tensor_binding_size(ctx, src2)),
1782        ggml_webgpu_make_bind_group_entry(3, ggml_webgpu_tensor_buf(dst), ggml_webgpu_tensor_align_offset(ctx, dst),
1783                                          ggml_webgpu_tensor_binding_size(ctx, dst)),
1784    };
1785
1786    uint32_t wg_x = 1;
1787    uint32_t wg_y = 1;
1788
1789    auto * decisions = static_cast<ggml_webgpu_mul_mat_vec_shader_decisions *>(pipeline.context.get());
1790
1791    const uint32_t max_wg_per_dim = ctx->global_ctx->capabilities.limits.maxComputeWorkgroupsPerDimension;
1792    uint32_t       output_groups  = CEIL_DIV(dst->ne[0], decisions->outputs_per_wg);
1793    uint32_t       total_wg       = output_groups * param_n_expert_used;
1794    compute_2d_workgroups(total_wg, max_wg_per_dim, wg_x, wg_y);
1795
1796    return ggml_backend_webgpu_build(ctx, pipeline, params, entries, wg_x, wg_y);
1797}
1798
1799static webgpu_encoded_op ggml_webgpu_mul_mat_id(webgpu_context & ctx,
1800                                                ggml_tensor *    src0,
1801                                                ggml_tensor *    src1,
1802                                                ggml_tensor *    src2,
1803                                                ggml_tensor *    dst) {
1804    // we can use mat-vec fast path
1805    if (dst->ne[2] == 1) {
1806        return ggml_webgpu_mul_mat_id_vec(ctx, src0, src1, src2, dst);
1807    }
1808
1809    ggml_webgpu_shader_lib_context shader_lib_ctx = {};
1810    shader_lib_ctx.src0                           = src0;
1811    shader_lib_ctx.src1                           = src1;
1812    shader_lib_ctx.src2                           = src2;
1813    shader_lib_ctx.dst                            = dst;
1814    shader_lib_ctx.max_wg_size = ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
1815
1816    // Get or create pipeline
1817    webgpu_pipeline gather_pipeline;
1818    webgpu_pipeline main_pipeline;
1819
1820    std::vector<webgpu_dispatch_desc> dispatches;
1821
1822    gather_pipeline = ctx->shader_lib->get_mul_mat_id_gather_pipeline(shader_lib_ctx);
1823    main_pipeline   = ctx->shader_lib->get_mul_mat_id_pipeline(shader_lib_ctx);
1824
1825    const uint32_t param_n_expert      = (uint32_t) src0->ne[2];
1826    const uint32_t param_n_expert_used = (uint32_t) dst->ne[1];
1827    const uint32_t param_n_tokens      = (uint32_t) dst->ne[2];
1828
1829    // params for mul_mat_id_gather.wgsl
1830    std::vector<uint32_t> gather_params = {
1831        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src2) / ggml_type_size(src2->type)),
1832        param_n_expert,
1833        param_n_expert_used,
1834        param_n_tokens,
1835        (uint32_t) (src2->nb[1] / ggml_type_size(src2->type)),
1836    };
1837
1838    const size_t dst_offset          = ggml_webgpu_tensor_offset(dst);
1839    const size_t gathered_buf_nbytes = src0->ne[2] * src1->ne[2] * sizeof(uint32_t);
1840
1841    const size_t gathered_expert_used_align_offset = ROUNDUP_POW2(
1842        dst_offset + ggml_nbytes(dst), ctx->global_ctx->capabilities.limits.minStorageBufferOffsetAlignment);
1843    const size_t gathered_tokens_align_offset =
1844        ROUNDUP_POW2(gathered_expert_used_align_offset + gathered_buf_nbytes,
1845                     ctx->global_ctx->capabilities.limits.minStorageBufferOffsetAlignment);
1846    const size_t gathered_count_ids_align_offset =
1847        ROUNDUP_POW2(gathered_tokens_align_offset + gathered_buf_nbytes,
1848                     ctx->global_ctx->capabilities.limits.minStorageBufferOffsetAlignment);
1849
1850    const size_t gathered_binding_size = ROUNDUP_POW2(gathered_buf_nbytes, WEBGPU_STORAGE_BUF_BINDING_MULT);
1851    const size_t gathered_count_ids_binding_size =
1852        ROUNDUP_POW2(src0->ne[2] * sizeof(uint32_t), WEBGPU_STORAGE_BUF_BINDING_MULT);
1853
1854    // bind group entries for mul_mat_id_gather.wgsl
1855    std::vector<wgpu::BindGroupEntry> gather_entries = {
1856        ggml_webgpu_make_bind_group_entry(0, ggml_webgpu_tensor_buf(src2), ggml_webgpu_tensor_align_offset(ctx, src2),
1857                                          ggml_webgpu_tensor_binding_size(ctx, src2)),
1858        ggml_webgpu_make_bind_group_entry(1, ggml_webgpu_tensor_buf(dst), gathered_expert_used_align_offset,
1859                                          gathered_binding_size),
1860        ggml_webgpu_make_bind_group_entry(2, ggml_webgpu_tensor_buf(dst), gathered_tokens_align_offset,
1861                                          gathered_binding_size),
1862        ggml_webgpu_make_bind_group_entry(3, ggml_webgpu_tensor_buf(dst), gathered_count_ids_align_offset,
1863                                          gathered_count_ids_binding_size),
1864    };
1865
1866    // n_expert is much less than maxComputeWorkgroupsPerDimension (e.g., n_exeprt=256 at Qwen3.5-35B-A3B)
1867    const uint32_t gather_wg_x = param_n_expert;
1868
1869    dispatches.push_back({
1870        gather_pipeline, std::move(gather_params), std::move(gather_entries), { gather_wg_x, 1 }
1871    });
1872
1873    // params for mul_mat_id.wgsl
1874    std::vector<uint32_t> main_params = {
1875        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src0) / ggml_type_size(src0->type)),
1876        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src1) / ggml_type_size(src1->type)),
1877        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, dst) / ggml_type_size(dst->type)),
1878        (uint32_t) src0->ne[0],
1879        (uint32_t) src0->ne[1],
1880        param_n_expert,
1881        param_n_expert_used,
1882        param_n_tokens,
1883        (uint32_t) src1->ne[1],
1884        (uint32_t) (src0->nb[1] / ggml_type_size(src0->type)),
1885        (uint32_t) (src1->nb[1] / ggml_type_size(src1->type)),
1886        (uint32_t) (src0->nb[2] / ggml_type_size(src0->type)),
1887        (uint32_t) (src1->nb[2] / ggml_type_size(src1->type)),
1888    };
1889
1890    // bind group entries for mul_mat_id.wgsl
1891    std::vector<wgpu::BindGroupEntry> main_entries = {
1892        ggml_webgpu_make_bind_group_entry(0, ggml_webgpu_tensor_buf(src0), ggml_webgpu_tensor_align_offset(ctx, src0),
1893                                          ggml_webgpu_tensor_binding_size(ctx, src0)),
1894        ggml_webgpu_make_bind_group_entry(1, ggml_webgpu_tensor_buf(src1), ggml_webgpu_tensor_align_offset(ctx, src1),
1895                                          ggml_webgpu_tensor_binding_size(ctx, src1)),
1896        ggml_webgpu_make_bind_group_entry(2, ggml_webgpu_tensor_buf(dst), ggml_webgpu_tensor_align_offset(ctx, dst),
1897                                          ggml_webgpu_tensor_binding_size(ctx, dst)),
1898        ggml_webgpu_make_bind_group_entry(3, ggml_webgpu_tensor_buf(dst), gathered_expert_used_align_offset,
1899                                          gathered_binding_size),
1900        ggml_webgpu_make_bind_group_entry(4, ggml_webgpu_tensor_buf(dst), gathered_tokens_align_offset,
1901                                          gathered_binding_size),
1902        ggml_webgpu_make_bind_group_entry(5, ggml_webgpu_tensor_buf(dst), gathered_count_ids_align_offset,
1903                                          gathered_count_ids_binding_size),
1904    };
1905
1906    // Calculate workgroup dimensions
1907    uint32_t wg_x = 1;
1908    uint32_t wg_y = 1;
1909
1910    auto * main_decisions = static_cast<ggml_webgpu_mul_mat_shader_decisions *>(main_pipeline.context.get());
1911
1912    uint32_t wg_m;
1913
1914    uint32_t tile_m_s           = main_decisions->tile_m * main_decisions->wg_size_m;
1915    uint32_t tile_n_s           = main_decisions->tile_n * main_decisions->wg_size_n;
1916    wg_m                        = CEIL_DIV(dst->ne[0], tile_m_s);
1917    uint32_t total_gathered     = dst->ne[1] * dst->ne[2];
1918    uint32_t max_active_experts = std::min((uint32_t) src0->ne[2], total_gathered);
1919    uint32_t max_wg_n           = CEIL_DIV(total_gathered, tile_n_s) + max_active_experts;
1920    uint32_t total_wg           = wg_m * max_wg_n;
1921
1922    compute_2d_workgroups(total_wg, ctx->global_ctx->capabilities.limits.maxComputeWorkgroupsPerDimension, wg_x, wg_y);
1923
1924    dispatches.push_back({
1925        main_pipeline, std::move(main_params), std::move(main_entries), { wg_x, wg_y }
1926    });
1927
1928    return ggml_backend_webgpu_build_multi(ctx, dispatches);
1929}
1930
1931struct ggml_webgpu_flash_attn_op {
1932    ggml_webgpu_shader_lib_context    shader_lib_ctx = {};
1933    std::vector<uint32_t>             params;
1934    std::vector<wgpu::BindGroupEntry> entries;
1935    size_t                            kv_bind_offset = 0;
1936    size_t                            kv_bind_size   = 0;
1937    bool                              has_mask       = false;
1938    bool                              has_sinks      = false;
1939    bool                              kv_overlap     = false;
1940};
1941
1942static bool ggml_webgpu_flash_attn_use_vec_path(const webgpu_global_context & global_ctx,
1943                                                const ggml_tensor *           Q,
1944                                                const ggml_tensor *           K,
1945                                                const ggml_tensor *           V) {
1946    const size_t storage_offset_alignment = global_ctx->capabilities.limits.minStorageBufferOffsetAlignment;
1947
1948    const bool k_float_vec4_aligned = (K->type != GGML_TYPE_F16 && K->type != GGML_TYPE_F32) ||
1949                                      ggml_webgpu_flash_attn_float_vec4_aligned(K, storage_offset_alignment);
1950    const bool v_float_vec4_aligned = (V->type != GGML_TYPE_F16 && V->type != GGML_TYPE_F32) ||
1951                                      ggml_webgpu_flash_attn_float_vec4_aligned(V, storage_offset_alignment);
1952
1953    const uint32_t k_vec_head_align =
1954        ggml_is_quantized(K->type) ? ggml_blck_size(K->type) : GGML_WEBGPU_FLASH_ATTN_TILE_KV_VEC_WIDTH;
1955    const uint32_t v_vec_head_align =
1956        ggml_is_quantized(V->type) ? ggml_blck_size(V->type) : GGML_WEBGPU_FLASH_ATTN_TILE_KV_VEC_WIDTH;
1957    const bool kv_vec_head_dims_aligned = Q->ne[0] % k_vec_head_align == 0 && V->ne[0] % v_vec_head_align == 0;
1958
1959    return global_ctx->capabilities.supports_subgroups && (Q->ne[1] < GGML_WEBGPU_FLASH_ATTN_VEC_MAX_SEQ_LEN) &&
1960           kv_vec_head_dims_aligned && k_float_vec4_aligned && v_float_vec4_aligned;
1961}
1962
1963static ggml_webgpu_flash_attn_op ggml_webgpu_flash_attn_prepare(webgpu_context & ctx,
1964                                                                ggml_tensor *    Q,
1965                                                                ggml_tensor *    K,
1966                                                                ggml_tensor *    V,
1967                                                                ggml_tensor *    mask,
1968                                                                ggml_tensor *    sinks,
1969                                                                ggml_tensor *    dst) {
1970    float scale         = ggml_get_op_params_f32(dst, 0);
1971    float max_bias      = ggml_get_op_params_f32(dst, 1);
1972    float logit_softcap = ggml_get_op_params_f32(dst, 2);
1973    if (logit_softcap != 0.0f) {
1974        scale /= logit_softcap;
1975    }
1976    float n_head_log2 = float(1u << (uint32_t) floor(log2(Q->ne[2])));
1977    float m0          = powf(2.0f, -(max_bias) / n_head_log2);
1978    float m1          = powf(2.0f, -(max_bias / 2.0f) / n_head_log2);
1979
1980    ggml_webgpu_flash_attn_op op               = {};
1981    op.shader_lib_ctx.src0                     = Q;
1982    op.shader_lib_ctx.src1                     = K;
1983    op.shader_lib_ctx.src2                     = V;
1984    op.shader_lib_ctx.src3                     = mask;
1985    op.shader_lib_ctx.src4                     = sinks;
1986    op.shader_lib_ctx.dst                      = dst;
1987    op.shader_lib_ctx.supports_subgroups       = ctx->global_ctx->capabilities.supports_subgroups;
1988    op.shader_lib_ctx.supports_subgroup_matrix = ctx->global_ctx->capabilities.supports_subgroup_matrix;
1989    op.shader_lib_ctx.max_wg_size              = ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
1990    op.shader_lib_ctx.wg_mem_limit_bytes       = ctx->global_ctx->capabilities.limits.maxComputeWorkgroupStorageSize;
1991    op.shader_lib_ctx.sg_mat_m                 = ctx->global_ctx->capabilities.sg_mat_m;
1992    op.shader_lib_ctx.sg_mat_n                 = ctx->global_ctx->capabilities.sg_mat_n;
1993    op.shader_lib_ctx.sg_mat_k                 = ctx->global_ctx->capabilities.sg_mat_k;
1994    op.shader_lib_ctx.min_subgroup_size        = ctx->global_ctx->capabilities.min_subgroup_size;
1995    op.shader_lib_ctx.max_subgroup_size        = ctx->global_ctx->capabilities.max_subgroup_size;
1996
1997    op.has_mask   = mask != nullptr;
1998    op.has_sinks  = sinks != nullptr;
1999    op.kv_overlap = ggml_webgpu_tensor_binding_overlap(ctx->global_ctx, K, V);
2000
2001    uint32_t offset_k = (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, K) / ggml_type_size(K->type));
2002    uint32_t offset_v = (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, V) / ggml_type_size(V->type));
2003    if (op.kv_overlap) {
2004        const ggml_webgpu_merged_binding_range merged_range = ggml_webgpu_tensor_merged_binding_range(ctx, { K, V });
2005        op.kv_bind_offset                                   = merged_range.offset;
2006        op.kv_bind_size                                     = merged_range.size;
2007        offset_k                                            = ggml_webgpu_tensor_merged_element_offset(K, merged_range);
2008        offset_v                                            = ggml_webgpu_tensor_merged_element_offset(V, merged_range);
2009    }
2010
2011    op.params = {
2012        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, Q) / ggml_type_size(Q->type)),
2013        offset_k,
2014        offset_v,
2015        op.has_mask ? (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, mask) / ggml_type_size(mask->type)) : 0,
2016        op.has_sinks ? (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, sinks) / ggml_type_size(sinks->type)) : 0,
2017        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, dst) / ggml_type_size(dst->type)),
2018        (uint32_t) Q->ne[2],                              // number of heads
2019        (uint32_t) Q->ne[1],                              // sequence length (Q)
2020        (uint32_t) K->ne[1],                              // sequence length (K/V)
2021        (uint32_t) (Q->nb[1] / ggml_type_size(Q->type)),  // stride (elements/blocks) of Q in dimension 1
2022        (uint32_t) (Q->nb[2] / ggml_type_size(Q->type)),  // stride (elements/blocks) of Q in dimension 2
2023        (uint32_t) (Q->nb[3] / ggml_type_size(Q->type)),  // stride (elements/blocks) of Q in dimension 3
2024        (uint32_t) (K->nb[1] / ggml_type_size(K->type)),  // stride (elements/blocks) of K in dimension 1
2025        (uint32_t) (K->nb[2] / ggml_type_size(K->type)),  // stride (elements/blocks) of K in dimension 2
2026        (uint32_t) (K->nb[3] / ggml_type_size(K->type)),  // stride (elements/blocks) of K in dimension 3
2027        (uint32_t) (V->nb[1] / ggml_type_size(V->type)),  // stride (elements/blocks) of V in dimension 1
2028        (uint32_t) (V->nb[2] / ggml_type_size(V->type)),  // stride (elements/blocks) of V in dimension 2
2029        (uint32_t) (V->nb[3] / ggml_type_size(V->type)),  // stride (elements/blocks) of V in dimension 3
2030        op.has_mask ? (uint32_t) (mask->nb[3] / ggml_type_size(mask->type)) : 0,  // stride of mask dim 3
2031        (uint32_t) (Q->ne[2] / K->ne[2]),  // repeat factor for K/V in dim 2 (MHA/MQA/GQA)
2032        ggml_webgpu_u32_from_f32(scale),   // scale (possibly adjusted for logit softcap)
2033        ggml_webgpu_u32_from_f32(max_bias),
2034        ggml_webgpu_u32_from_f32(logit_softcap),
2035        ggml_webgpu_u32_from_f32(n_head_log2),
2036        ggml_webgpu_u32_from_f32(m0),
2037        ggml_webgpu_u32_from_f32(m1)
2038    };
2039    op.entries = {
2040        ggml_webgpu_make_tensor_bind_group_entry(ctx, 0, Q),
2041    };
2042    if (op.kv_overlap) {
2043        op.entries.push_back(
2044            ggml_webgpu_make_bind_group_entry(1, ggml_webgpu_tensor_buf(K), op.kv_bind_offset, op.kv_bind_size));
2045    } else {
2046        op.entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 1, K));
2047        op.entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 2, V));
2048    }
2049    uint32_t binding_index = op.kv_overlap ? 2u : 3u;
2050    if (op.has_mask) {
2051        op.entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, binding_index++, mask));
2052    }
2053    if (op.has_sinks) {
2054        op.entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, binding_index++, sinks));
2055    }
2056    op.entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, binding_index++, dst));
2057
2058    return op;
2059}
2060
2061static uint32_t ggml_webgpu_flash_attn_vec_nwg(uint32_t vec_nwg_cap, uint32_t kv_tile, uint32_t seq_len_kv) {
2062    uint32_t       nwg     = 1u;
2063    const uint64_t kv_span = (uint64_t) kv_tile;
2064    while ((2u * nwg * kv_span) < (uint64_t) seq_len_kv && nwg < vec_nwg_cap) {
2065        nwg <<= 1;
2066    }
2067    return std::min(nwg, vec_nwg_cap);
2068}
2069
2070static webgpu_encoded_op ggml_webgpu_flash_attn_direct(webgpu_context & ctx, const ggml_webgpu_flash_attn_op & op) {
2071    webgpu_pipeline pipeline    = ctx->shader_lib->get_flash_attn_pipeline(op.shader_lib_ctx, op.kv_overlap);
2072    auto *          decisions   = static_cast<ggml_webgpu_flash_attn_decisions *>(pipeline.context.get());
2073    uint32_t        wg_per_head = CEIL_DIV(op.shader_lib_ctx.src0->ne[1], decisions->q_tile);
2074    uint32_t        wg_x        = wg_per_head * op.shader_lib_ctx.src0->ne[2] * op.shader_lib_ctx.src0->ne[3];
2075    return ggml_backend_webgpu_build(ctx, pipeline, op.params, op.entries, wg_x);
2076}
2077
2078static webgpu_encoded_op ggml_webgpu_flash_attn_vec(webgpu_context &          ctx,
2079                                                    ggml_tensor *             Q,
2080                                                    ggml_tensor *             K,
2081                                                    ggml_tensor *             V,
2082                                                    ggml_tensor *             mask,
2083                                                    ggml_tensor *             sinks,
2084                                                    ggml_tensor *             dst,
2085                                                    ggml_webgpu_flash_attn_op op) {
2086    webgpu_pipeline pipeline  = ctx->shader_lib->get_flash_attn_vec_pipeline(op.shader_lib_ctx, op.kv_overlap);
2087    auto *          decisions = static_cast<ggml_webgpu_flash_attn_vec_decisions *>(pipeline.context.get());
2088
2089    wgpu::Buffer blk_buf         = {};
2090    uint64_t     blk_size_bytes  = 0;
2091    uint32_t     blk_nblk0       = 0;
2092    uint32_t     blk_nblk1       = 0;
2093    uint32_t     blk_batch_count = 0;
2094
2095    const uint32_t vec_nwg_cap = ctx->global_ctx->capabilities.min_subgroup_size;
2096    uint32_t       nwg         = ggml_webgpu_flash_attn_vec_nwg(vec_nwg_cap, decisions->kv_tile, (uint32_t) K->ne[1]);
2097    const uint64_t nrows       = (uint64_t) Q->ne[1] * Q->ne[2] * Q->ne[3];
2098    const bool     use_vec_reduce = nwg > 1u;
2099    GGML_ASSERT(nrows <= UINT32_MAX);
2100
2101    uint64_t     tmp_stats_base  = 0;
2102    uint64_t     tmp_size_bytes  = 0;
2103    wgpu::Buffer tmp_buf         = {};
2104    uint64_t     tmp_bind_offset = 0;
2105    uint64_t     tmp_bind_size   = 0;
2106    const size_t align_bytes     = ctx->global_ctx->capabilities.limits.minStorageBufferOffsetAlignment;
2107    const size_t dst_offset      = ggml_webgpu_tensor_offset(dst);
2108    size_t       scratch_offset  = ROUNDUP_POW2(dst_offset + ggml_nbytes(dst), align_bytes);
2109
2110    if (use_vec_reduce) {
2111        const uint64_t tmp_data_elems  = nrows * (uint64_t) V->ne[0] * nwg;
2112        const uint64_t tmp_stats_elems = nrows * 2u * nwg;
2113        tmp_stats_base                 = tmp_data_elems;
2114        tmp_size_bytes =
2115            ROUNDUP_POW2((tmp_data_elems + tmp_stats_elems) * sizeof(float), WEBGPU_STORAGE_BUF_BINDING_MULT);
2116        GGML_ASSERT(tmp_stats_base <= UINT32_MAX);
2117        tmp_buf         = ggml_webgpu_tensor_buf(dst);
2118        tmp_bind_offset = scratch_offset;
2119        tmp_bind_size   = tmp_size_bytes;
2120        scratch_offset  = ROUNDUP_POW2(scratch_offset + tmp_size_bytes, align_bytes);
2121    } else {
2122        // nwg==1 writes final dst directly in vec-split; bind tmp to a tiny non-overlapping scratch region.
2123        tmp_size_bytes  = WEBGPU_STORAGE_BUF_BINDING_MULT;
2124        tmp_buf         = ggml_webgpu_tensor_buf(dst);
2125        tmp_bind_offset = scratch_offset;
2126        tmp_bind_size   = tmp_size_bytes;
2127        scratch_offset  = ROUNDUP_POW2(scratch_offset + tmp_size_bytes, align_bytes);
2128    }
2129
2130    webgpu_pipeline                   blk_pipeline;
2131    std::vector<uint32_t>             blk_params;
2132    std::vector<wgpu::BindGroupEntry> blk_entries;
2133    if (op.has_mask) {
2134        blk_nblk0                   = CEIL_DIV((uint32_t) K->ne[1], decisions->kv_tile);
2135        blk_nblk1                   = (uint32_t) Q->ne[1];
2136        blk_buf                     = ggml_webgpu_tensor_buf(dst);
2137        const uint32_t stride_mask3 = (uint32_t) (mask->nb[3] / ggml_type_size(mask->type));
2138        blk_batch_count             = stride_mask3 > 0 ? (uint32_t) Q->ne[3] : 1u;
2139        const uint64_t blk_elems    = (uint64_t) blk_nblk0 * blk_nblk1 * blk_batch_count;
2140        blk_size_bytes              = ROUNDUP_POW2(blk_elems * sizeof(uint32_t), WEBGPU_STORAGE_BUF_BINDING_MULT);
2141        const ggml_webgpu_shader_lib_context blk_shader_ctx = op.shader_lib_ctx;
2142        blk_pipeline = ctx->shader_lib->get_flash_attn_blk_pipeline(blk_shader_ctx, decisions->kv_tile);
2143
2144        blk_params = {
2145            (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, mask) / ggml_type_size(mask->type)),  // offset_mask
2146            (uint32_t) Q->ne[1],                                                                   // seq_len_q
2147            (uint32_t) K->ne[1],                                                                   // seq_len_kv
2148            stride_mask3,                                                                          // stride_mask3
2149            blk_nblk0,                                                                             // nblk0
2150            blk_nblk1,                                                                             // nblk1
2151        };
2152        blk_entries = {
2153            ggml_webgpu_make_bind_group_entry(0, ggml_webgpu_tensor_buf(mask),
2154                                              ggml_webgpu_tensor_align_offset(ctx, mask),
2155                                              ggml_webgpu_tensor_binding_size(ctx, mask)),
2156            ggml_webgpu_make_bind_group_entry(1, blk_buf, scratch_offset, blk_size_bytes),
2157        };
2158        scratch_offset = ROUNDUP_POW2(scratch_offset + blk_size_bytes, align_bytes);
2159    }
2160
2161    std::vector<uint32_t> split_params = op.params;
2162    if (op.has_mask) {
2163        split_params.push_back(0u);                     // blk_base
2164        split_params.push_back(blk_nblk0);              // blk_nblk0
2165        split_params.push_back(blk_nblk1);              // blk_nblk1
2166    }
2167    split_params.push_back(0u);                         // tmp_data_base
2168    split_params.push_back((uint32_t) tmp_stats_base);  // tmp_stats_base
2169    split_params.push_back(nwg);                        // nwg
2170
2171    std::vector<wgpu::BindGroupEntry> split_entries = {
2172        ggml_webgpu_make_bind_group_entry(0, ggml_webgpu_tensor_buf(Q), ggml_webgpu_tensor_align_offset(ctx, Q),
2173                                          ggml_webgpu_tensor_binding_size(ctx, Q)),
2174    };
2175    if (op.kv_overlap) {
2176        split_entries.push_back(
2177            ggml_webgpu_make_bind_group_entry(1, ggml_webgpu_tensor_buf(K), op.kv_bind_offset, op.kv_bind_size));
2178    } else {
2179        split_entries.push_back(ggml_webgpu_make_bind_group_entry(1, ggml_webgpu_tensor_buf(K),
2180                                                                  ggml_webgpu_tensor_align_offset(ctx, K),
2181                                                                  ggml_webgpu_tensor_binding_size(ctx, K)));
2182        split_entries.push_back(ggml_webgpu_make_bind_group_entry(2, ggml_webgpu_tensor_buf(V),
2183                                                                  ggml_webgpu_tensor_align_offset(ctx, V),
2184                                                                  ggml_webgpu_tensor_binding_size(ctx, V)));
2185    }
2186    uint32_t split_binding_index = op.kv_overlap ? 2u : 3u;
2187    if (op.has_mask) {
2188        split_entries.push_back(ggml_webgpu_make_bind_group_entry(split_binding_index++, ggml_webgpu_tensor_buf(mask),
2189                                                                  ggml_webgpu_tensor_align_offset(ctx, mask),
2190                                                                  ggml_webgpu_tensor_binding_size(ctx, mask)));
2191    }
2192    if (op.has_sinks) {
2193        split_entries.push_back(ggml_webgpu_make_bind_group_entry(split_binding_index++, ggml_webgpu_tensor_buf(sinks),
2194                                                                  ggml_webgpu_tensor_align_offset(ctx, sinks),
2195                                                                  ggml_webgpu_tensor_binding_size(ctx, sinks)));
2196    }
2197    if (op.has_mask) {
2198        split_entries.push_back(
2199            ggml_webgpu_make_bind_group_entry(split_binding_index++, blk_buf, blk_entries[1].offset, blk_size_bytes));
2200    }
2201    split_entries.push_back(
2202        ggml_webgpu_make_bind_group_entry(split_binding_index++, tmp_buf, tmp_bind_offset, tmp_bind_size));
2203    split_entries.push_back(ggml_webgpu_make_bind_group_entry(split_binding_index++, ggml_webgpu_tensor_buf(dst),
2204                                                              ggml_webgpu_tensor_align_offset(ctx, dst),
2205                                                              ggml_webgpu_tensor_binding_size(ctx, dst)));
2206
2207    webgpu_pipeline                   reduce_pipeline;
2208    std::vector<uint32_t>             reduce_params;
2209    std::vector<wgpu::BindGroupEntry> reduce_entries;
2210    if (use_vec_reduce) {
2211        const uint32_t reduce_sg_size = ctx->global_ctx->capabilities.max_subgroup_size;
2212        const uint32_t reduce_wg_size = std::max(
2213            reduce_sg_size,
2214            (uint32_t) std::min<uint64_t>((uint64_t) nwg * reduce_sg_size,
2215                                          ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup));
2216        ggml_webgpu_shader_lib_context reduce_shader_ctx = op.shader_lib_ctx;
2217        reduce_shader_ctx.max_wg_size                    = reduce_wg_size;
2218        reduce_pipeline = ctx->shader_lib->get_flash_attn_vec_reduce_pipeline(reduce_shader_ctx);
2219
2220        reduce_params = {
2221            (uint32_t) nrows,                                                                    // nrows
2222            (uint32_t) Q->ne[1],                                                                 // seq_len_q
2223            (uint32_t) Q->ne[2],                                                                 // n_heads
2224            (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, dst) / ggml_type_size(dst->type)),  // offset_dst
2225            nwg,                                                                                 // nwg
2226            0u,                                                                                  // tmp_data_base
2227            (uint32_t) tmp_stats_base,                                                           // tmp_stats_base
2228        };
2229
2230        reduce_entries = {
2231            ggml_webgpu_make_bind_group_entry(0, tmp_buf, tmp_bind_offset, tmp_size_bytes),
2232            ggml_webgpu_make_bind_group_entry(1, ggml_webgpu_tensor_buf(dst), ggml_webgpu_tensor_align_offset(ctx, dst),
2233                                              ggml_webgpu_tensor_binding_size(ctx, dst)),
2234        };
2235    }
2236
2237    uint32_t       wg_x           = Q->ne[1] * Q->ne[2] * Q->ne[3];
2238    const uint64_t split_wg_total = (uint64_t) wg_x * nwg;
2239    GGML_ASSERT(split_wg_total <= UINT32_MAX);
2240
2241    std::vector<webgpu_dispatch_desc> dispatches;
2242
2243    if (op.has_mask) {
2244        dispatches.push_back({
2245            blk_pipeline, std::move(blk_params), std::move(blk_entries), { blk_nblk0, blk_nblk1 * blk_batch_count }
2246        });
2247    }
2248    dispatches.push_back({
2249        pipeline, std::move(split_params), std::move(split_entries), { (uint32_t) split_wg_total, 1u }
2250    });
2251    if (use_vec_reduce) {
2252        dispatches.push_back({
2253            reduce_pipeline, std::move(reduce_params), std::move(reduce_entries), { (uint32_t) nrows, 1u }
2254        });
2255    }
2256
2257    return ggml_backend_webgpu_build_multi(ctx, dispatches);
2258}
2259
2260static webgpu_encoded_op ggml_webgpu_flash_attn(webgpu_context & ctx,
2261                                                ggml_tensor *    Q,
2262                                                ggml_tensor *    K,
2263                                                ggml_tensor *    V,
2264                                                ggml_tensor *    mask,
2265                                                ggml_tensor *    sinks,
2266                                                ggml_tensor *    dst) {
2267    ggml_webgpu_flash_attn_op op = ggml_webgpu_flash_attn_prepare(ctx, Q, K, V, mask, sinks, dst);
2268    if (ggml_webgpu_flash_attn_use_vec_path(ctx->global_ctx, Q, K, V)) {
2269        return ggml_webgpu_flash_attn_vec(ctx, Q, K, V, mask, sinks, dst, std::move(op));
2270    }
2271    return ggml_webgpu_flash_attn_direct(ctx, op);
2272}
2273
2274static webgpu_encoded_op ggml_webgpu_unary_op(webgpu_context & ctx, ggml_tensor * src, ggml_tensor * dst) {
2275    bool is_unary = dst->op == GGML_OP_UNARY;
2276
2277    ggml_webgpu_shader_lib_context shader_lib_ctx = {};
2278    shader_lib_ctx.src0                           = src;
2279    shader_lib_ctx.src1                           = nullptr;
2280    shader_lib_ctx.dst                            = dst;
2281    shader_lib_ctx.max_wg_size = ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
2282
2283    webgpu_pipeline pipeline = ctx->shader_lib->get_unary_pipeline(shader_lib_ctx);
2284
2285    auto *     decisions = static_cast<ggml_webgpu_generic_shader_decisions *>(pipeline.context.get());
2286    const bool inplace   = decisions->inplace;
2287
2288    uint32_t ne = (uint32_t) ggml_nelements(dst);
2289
2290    std::vector<uint32_t> params = { ne,
2291                                     (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src) / ggml_type_size(src->type)),
2292                                     (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, dst) / ggml_type_size(dst->type)),
2293                                     (uint32_t) (src->nb[0] / ggml_type_size(src->type)),
2294                                     (uint32_t) (src->nb[1] / ggml_type_size(src->type)),
2295                                     (uint32_t) (src->nb[2] / ggml_type_size(src->type)),
2296                                     (uint32_t) (src->nb[3] / ggml_type_size(src->type)),
2297                                     (uint32_t) src->ne[0],
2298                                     (uint32_t) src->ne[1],
2299                                     (uint32_t) src->ne[2] };
2300
2301    ggml_tensor * effective_src = src;
2302    if (is_unary) {
2303        ggml_unary_op unary_op = ggml_get_unary_op(dst);
2304        switch (unary_op) {
2305            case GGML_UNARY_OP_XIELU:
2306                {
2307                    // Get float parameters and reinterpret their bit patterns as uint32_t
2308                    // for passing through the params buffer
2309                    float alpha_n = ggml_get_op_params_f32(dst, 1);
2310                    float alpha_p = ggml_get_op_params_f32(dst, 2);
2311                    float beta    = ggml_get_op_params_f32(dst, 3);
2312                    float eps     = ggml_get_op_params_f32(dst, 4);
2313                    params.push_back(ggml_webgpu_u32_from_f32(alpha_n));
2314                    params.push_back(ggml_webgpu_u32_from_f32(alpha_p));
2315                    params.push_back(ggml_webgpu_u32_from_f32(beta));
2316                    params.push_back(ggml_webgpu_u32_from_f32(eps));
2317                    break;
2318                }
2319            default:
2320                break;
2321        }
2322    } else if (dst->op == GGML_OP_CLAMP) {
2323        float clamp_min = ggml_get_op_params_f32(dst, 0);
2324        float clamp_max = ggml_get_op_params_f32(dst, 1);
2325        params.push_back(ggml_webgpu_u32_from_f32(clamp_min));
2326        params.push_back(ggml_webgpu_u32_from_f32(clamp_max));
2327    } else if (dst->op == GGML_OP_FILL) {
2328        float fill_val = ggml_get_op_params_f32(dst, 0);
2329        params.push_back(ggml_webgpu_u32_from_f32(fill_val));
2330        effective_src = dst;  // fill simply fills dst
2331    }
2332
2333    std::vector<wgpu::BindGroupEntry> entries = {
2334        ggml_webgpu_make_tensor_bind_group_entry(ctx, 0, effective_src),
2335    };
2336    if (!inplace) {
2337        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 1, dst));
2338    }
2339
2340    uint32_t wg_x, wg_y;
2341    uint32_t total_wg = CEIL_DIV(ggml_nelements(dst), decisions->wg_size);
2342    compute_2d_workgroups(total_wg, ctx->global_ctx->capabilities.limits.maxComputeWorkgroupsPerDimension, wg_x, wg_y);
2343    return ggml_backend_webgpu_build(ctx, pipeline, params, entries, wg_x, wg_y);
2344}
2345
2346static webgpu_encoded_op ggml_webgpu_binary_op(webgpu_context & ctx,
2347                                               ggml_tensor *    src0,
2348                                               ggml_tensor *    src1,
2349                                               ggml_tensor *    dst) {
2350    ggml_webgpu_shader_lib_context shader_lib_ctx = {};
2351    shader_lib_ctx.src0                           = src0;
2352    shader_lib_ctx.src1                           = src1;
2353    shader_lib_ctx.dst                            = dst;
2354    shader_lib_ctx.max_wg_size = ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
2355
2356    const bool      src_overlap = ggml_webgpu_tensor_binding_overlap(ctx->global_ctx, src0, src1);
2357    webgpu_pipeline pipeline    = ctx->shader_lib->get_binary_pipeline(shader_lib_ctx, src_overlap);
2358    auto *          decisions   = static_cast<ggml_webgpu_binary_shader_decisions *>(pipeline.context.get());
2359
2360    uint32_t ne = (uint32_t) ggml_nelements(dst);
2361
2362    size_t src0_webgpu_tensor_align_offset = ggml_webgpu_tensor_align_offset(ctx, src0);
2363    size_t src1_webgpu_tensor_align_offset = ggml_webgpu_tensor_align_offset(ctx, src1);
2364
2365    uint32_t offset_src0   = (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src0) / ggml_type_size(src0->type));
2366    uint32_t offset_src1   = (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src1) / ggml_type_size(src1->type));
2367    size_t   merged_offset = 0;
2368    size_t   merged_size   = 0;
2369    if (decisions->src_overlap) {
2370        const ggml_webgpu_merged_binding_range merged_range =
2371            ggml_webgpu_tensor_merged_binding_range(ctx, { src0, src1 });
2372        merged_offset = merged_range.offset;
2373        merged_size   = merged_range.size;
2374        offset_src0   = ggml_webgpu_tensor_merged_element_offset(src0, merged_range);
2375        offset_src1   = ggml_webgpu_tensor_merged_element_offset(src1, merged_range);
2376    }
2377
2378    std::vector<uint32_t> params = {
2379        ne,
2380        offset_src0,
2381        offset_src1,
2382        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, dst) / ggml_type_size(dst->type)),
2383        (uint32_t) (src0->nb[0] / ggml_type_size(src0->type)),
2384        (uint32_t) (src0->nb[1] / ggml_type_size(src0->type)),
2385        (uint32_t) (src0->nb[2] / ggml_type_size(src0->type)),
2386        (uint32_t) (src0->nb[3] / ggml_type_size(src0->type)),
2387        (uint32_t) (src1->nb[0] / ggml_type_size(src1->type)),
2388        (uint32_t) (src1->nb[1] / ggml_type_size(src1->type)),
2389        (uint32_t) (src1->nb[2] / ggml_type_size(src1->type)),
2390        (uint32_t) (src1->nb[3] / ggml_type_size(src1->type)),
2391        (uint32_t) src0->ne[0],
2392        (uint32_t) src0->ne[1],
2393        (uint32_t) src0->ne[2],
2394        (uint32_t) src1->ne[0],
2395        (uint32_t) src1->ne[1],
2396        (uint32_t) src1->ne[2],
2397        (uint32_t) src1->ne[3],
2398    };
2399
2400    std::vector<wgpu::BindGroupEntry> entries;
2401
2402    if (decisions->src_overlap) {
2403        entries.push_back(
2404            ggml_webgpu_make_bind_group_entry(0, ggml_webgpu_tensor_buf(src0), merged_offset, merged_size));
2405        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 1, dst));
2406    } else {
2407        entries.push_back(ggml_webgpu_make_bind_group_entry(0, ggml_webgpu_tensor_buf(src0),
2408                                                            src0_webgpu_tensor_align_offset,
2409                                                            ggml_webgpu_tensor_binding_size(ctx, src0)));
2410        entries.push_back(ggml_webgpu_make_bind_group_entry(1, ggml_webgpu_tensor_buf(src1),
2411                                                            src1_webgpu_tensor_align_offset,
2412                                                            ggml_webgpu_tensor_binding_size(ctx, src1)));
2413        if (!decisions->inplace && !decisions->overlap) {
2414            entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 2, dst));
2415        }
2416    }
2417
2418    uint32_t wg_x, wg_y;
2419    uint32_t total_wg = CEIL_DIV(ggml_nelements(dst), decisions->wg_size);
2420    compute_2d_workgroups(total_wg, ctx->global_ctx->capabilities.limits.maxComputeWorkgroupsPerDimension, wg_x, wg_y);
2421    return ggml_backend_webgpu_build(ctx, pipeline, params, entries, wg_x, wg_y);
2422}
2423
2424static webgpu_encoded_op ggml_webgpu_add_id(webgpu_context & ctx,
2425                                            ggml_tensor *    src0,
2426                                            ggml_tensor *    src1,
2427                                            ggml_tensor *    src2,
2428                                            ggml_tensor *    dst) {
2429    ggml_webgpu_shader_lib_context shader_lib_ctx = {};
2430    shader_lib_ctx.src0                           = src0;
2431    shader_lib_ctx.src1                           = src1;
2432    shader_lib_ctx.src2                           = src2;
2433    shader_lib_ctx.dst                            = dst;
2434    shader_lib_ctx.max_wg_size = ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
2435
2436    webgpu_pipeline pipeline = ctx->shader_lib->get_add_id_pipeline(shader_lib_ctx);
2437
2438    auto * decisions = static_cast<ggml_webgpu_generic_shader_decisions *>(pipeline.context.get());
2439
2440    std::vector<uint32_t> params = {
2441        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src0) / ggml_type_size(src0->type)),
2442        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src1) / ggml_type_size(src1->type)),
2443        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src2) / ggml_type_size(src2->type)),
2444        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, dst) / ggml_type_size(dst->type)),
2445        (uint32_t) (src0->nb[1] / ggml_type_size(src0->type)),
2446        (uint32_t) (src0->nb[2] / ggml_type_size(src0->type)),
2447        (uint32_t) (src1->nb[1] / ggml_type_size(src1->type)),
2448        (uint32_t) (src2->nb[0] / ggml_type_size(src2->type)),
2449        (uint32_t) (src2->nb[1] / ggml_type_size(src2->type)),
2450        (uint32_t) dst->ne[0],
2451        (uint32_t) dst->ne[1],
2452        (uint32_t) dst->ne[2],
2453    };
2454
2455    std::vector<wgpu::BindGroupEntry> entries;
2456
2457    entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 0, src0));
2458    entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 1, src1));
2459    entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 2, src2));
2460
2461    if (!decisions->inplace) {
2462        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 3, dst));
2463    }
2464
2465    uint32_t       wg_x           = 1;
2466    uint32_t       wg_y           = 1;
2467    uint32_t       total_wg       = ggml_nrows(dst);
2468    const uint32_t max_wg_per_dim = ctx->global_ctx->capabilities.limits.maxComputeWorkgroupsPerDimension;
2469    compute_2d_workgroups(total_wg, max_wg_per_dim, wg_x, wg_y);
2470
2471    return ggml_backend_webgpu_build(ctx, pipeline, params, entries, wg_x, wg_y);
2472}
2473
2474static webgpu_encoded_op ggml_webgpu_concat(webgpu_context & ctx,
2475                                            ggml_tensor *    src0,
2476                                            ggml_tensor *    src1,
2477                                            ggml_tensor *    dst) {
2478    uint32_t ne  = (uint32_t) ggml_nelements(dst);
2479    uint32_t dim = (uint32_t) dst->op_params[0];
2480    if (ggml_nbytes(src0) == 0 && ggml_nbytes(src1) == 0) {
2481        return {};
2482    }
2483
2484    ggml_webgpu_shader_lib_context shader_lib_ctx = {};
2485    shader_lib_ctx.src0                           = src0;
2486    shader_lib_ctx.src1                           = src1;
2487    shader_lib_ctx.dst                            = dst;
2488    shader_lib_ctx.max_wg_size = ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
2489
2490    const bool      src_overlap = ggml_webgpu_tensor_binding_overlap(ctx->global_ctx, src0, src1) ||
2491                                  ggml_nbytes(src0) == 0 || ggml_nbytes(src1) == 0;
2492    webgpu_pipeline pipeline    = ctx->shader_lib->get_concat_pipeline(shader_lib_ctx, src_overlap);
2493    auto *          decisions   = static_cast<ggml_webgpu_binary_shader_decisions *>(pipeline.context.get());
2494
2495    uint32_t offset_src0   = (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src0) / ggml_type_size(src0->type));
2496    uint32_t offset_src1   = (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src1) / ggml_type_size(src1->type));
2497    size_t   merged_offset = 0;
2498    size_t   merged_size   = 0;
2499    if (decisions->src_overlap) {
2500        if (ggml_nbytes(src0) == 0) {
2501            merged_offset = ggml_webgpu_tensor_align_offset(ctx, src1);
2502            merged_size   = ggml_webgpu_tensor_binding_size(ctx, src1);
2503            offset_src0   = 0;
2504            offset_src1   = (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src1) / ggml_type_size(src1->type));
2505        } else if (ggml_nbytes(src1) == 0) {
2506            merged_offset = ggml_webgpu_tensor_align_offset(ctx, src0);
2507            merged_size   = ggml_webgpu_tensor_binding_size(ctx, src0);
2508            offset_src0   = (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src0) / ggml_type_size(src0->type));
2509            offset_src1   = 0;
2510        } else {
2511            const ggml_webgpu_merged_binding_range merged_range =
2512                ggml_webgpu_tensor_merged_binding_range(ctx, { src0, src1 });
2513            merged_offset = merged_range.offset;
2514            merged_size   = merged_range.size;
2515            offset_src0   = ggml_webgpu_tensor_merged_element_offset(src0, merged_range);
2516            offset_src1   = ggml_webgpu_tensor_merged_element_offset(src1, merged_range);
2517        }
2518    }
2519
2520    std::vector<uint32_t> params = { ne,
2521                                     offset_src0,
2522                                     offset_src1,
2523                                     (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, dst) / ggml_type_size(dst->type)),
2524                                     (uint32_t) (src0->nb[0] / ggml_type_size(src0->type)),
2525                                     (uint32_t) (src0->nb[1] / ggml_type_size(src0->type)),
2526                                     (uint32_t) (src0->nb[2] / ggml_type_size(src0->type)),
2527                                     (uint32_t) (src0->nb[3] / ggml_type_size(src0->type)),
2528                                     (uint32_t) (src1->nb[0] / ggml_type_size(src1->type)),
2529                                     (uint32_t) (src1->nb[1] / ggml_type_size(src1->type)),
2530                                     (uint32_t) (src1->nb[2] / ggml_type_size(src1->type)),
2531                                     (uint32_t) (src1->nb[3] / ggml_type_size(src1->type)),
2532                                     (uint32_t) dst->ne[0],
2533                                     (uint32_t) dst->ne[1],
2534                                     (uint32_t) dst->ne[2],
2535                                     dim,
2536                                     (uint32_t) src0->ne[dim] };
2537
2538    std::vector<wgpu::BindGroupEntry> entries = {};
2539    if (decisions->src_overlap) {
2540        entries.push_back(
2541            ggml_webgpu_make_bind_group_entry(0, ggml_webgpu_tensor_buf(src0), merged_offset, merged_size));
2542        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 1, dst));
2543    } else {
2544        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 0, src0));
2545        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 1, src1));
2546        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 2, dst));
2547    }
2548
2549    uint32_t wg_x = CEIL_DIV(ne, decisions->wg_size);
2550    return ggml_backend_webgpu_build(ctx, pipeline, params, entries, wg_x);
2551}
2552
2553static webgpu_encoded_op ggml_webgpu_repeat(webgpu_context & ctx, ggml_tensor * src0, ggml_tensor * dst) {
2554    uint32_t ne = (uint32_t) ggml_nelements(dst);
2555
2556    std::vector<uint32_t> params = { ne,
2557                                     (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src0) /
2558                                                 ggml_type_size(src0->type)),
2559                                     (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, dst) / ggml_type_size(dst->type)),
2560                                     (uint32_t) (src0->nb[0] / ggml_type_size(src0->type)),
2561                                     (uint32_t) (src0->nb[1] / ggml_type_size(src0->type)),
2562                                     (uint32_t) (src0->nb[2] / ggml_type_size(src0->type)),
2563                                     (uint32_t) (src0->nb[3] / ggml_type_size(src0->type)),
2564                                     (uint32_t) (src0->ne[0]),
2565                                     (uint32_t) (src0->ne[1]),
2566                                     (uint32_t) (src0->ne[2]),
2567                                     (uint32_t) (src0->ne[3]),
2568                                     (uint32_t) (dst->ne[0]),
2569                                     (uint32_t) (dst->ne[1]),
2570                                     (uint32_t) (dst->ne[2]) };
2571
2572    std::vector<wgpu::BindGroupEntry> entries = {
2573        ggml_webgpu_make_tensor_bind_group_entry(ctx, 0, src0),
2574        ggml_webgpu_make_tensor_bind_group_entry(ctx, 1, dst),
2575    };
2576
2577    ggml_webgpu_shader_lib_context shader_lib_ctx = {};
2578    shader_lib_ctx.src0                           = src0;
2579    shader_lib_ctx.dst                            = dst;
2580    shader_lib_ctx.max_wg_size = ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
2581
2582    webgpu_pipeline pipeline  = ctx->shader_lib->get_repeat_pipeline(shader_lib_ctx);
2583    auto *          decisions = static_cast<ggml_webgpu_generic_shader_decisions *>(pipeline.context.get());
2584    uint32_t        wg_x      = CEIL_DIV(ne, decisions->wg_size);
2585    return ggml_backend_webgpu_build(ctx, pipeline, params, entries, wg_x);
2586}
2587
2588static std::optional<webgpu_encoded_op> ggml_webgpu_rms_norm_mul(webgpu_context & ctx,
2589                                                                 ggml_tensor *    rn_src,
2590                                                                 ggml_tensor *    rn_dst,
2591                                                                 ggml_tensor *    mul_src0,
2592                                                                 ggml_tensor *    mul_src1,
2593                                                                 ggml_tensor *    dst) {
2594    ggml_tensor * mul_src;
2595
2596    if (ggml_webgpu_tensor_equal(rn_dst, mul_src0)) {
2597        mul_src = mul_src1;
2598    } else if (ggml_webgpu_tensor_equal(rn_dst, mul_src1)) {
2599        mul_src = mul_src0;
2600    } else {
2601        GGML_ABORT("rms_norm must be equal to the one of mul_src0 and mul_src1");
2602    }
2603
2604    uint32_t offset_rn_src = (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, rn_src) / ggml_type_size(rn_src->type));
2605    uint32_t offset_mul_src =
2606        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, mul_src) / ggml_type_size(mul_src->type));
2607    size_t merged_offset = 0;
2608    size_t merged_size   = 0;
2609
2610    std::vector<uint32_t> params = {
2611        offset_rn_src,
2612        offset_mul_src,
2613        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, dst) / ggml_type_size(dst->type)),
2614        (uint32_t) (rn_src->nb[1] / ggml_type_size(rn_src->type)),
2615        (uint32_t) (rn_src->nb[2] / ggml_type_size(rn_src->type)),
2616        (uint32_t) (rn_src->nb[3] / ggml_type_size(rn_src->type)),
2617        (uint32_t) (mul_src->nb[1] / ggml_type_size(mul_src->type)),
2618        (uint32_t) (mul_src->nb[2] / ggml_type_size(mul_src->type)),
2619        (uint32_t) (mul_src->nb[3] / ggml_type_size(mul_src->type)),
2620        (uint32_t) (dst->nb[1] / ggml_type_size(dst->type)),
2621        (uint32_t) (dst->nb[2] / ggml_type_size(dst->type)),
2622        (uint32_t) (dst->nb[3] / ggml_type_size(dst->type)),
2623        (uint32_t) mul_src->ne[0],
2624        (uint32_t) mul_src->ne[1],
2625        (uint32_t) mul_src->ne[2],
2626        (uint32_t) mul_src->ne[3],
2627        (uint32_t) dst->ne[0],
2628        (uint32_t) dst->ne[1],
2629        (uint32_t) dst->ne[2],
2630        ggml_webgpu_u32_from_f32(ggml_get_op_params_f32(rn_dst, 0))  // epsilon, treated as f32 in the shader
2631    };
2632
2633    std::vector<wgpu::BindGroupEntry> entries;
2634
2635    ggml_webgpu_shader_lib_context shader_lib_ctx = {};
2636    shader_lib_ctx.src0                           = rn_src;
2637    shader_lib_ctx.src1                           = mul_src;
2638    shader_lib_ctx.dst                            = dst;
2639    shader_lib_ctx.max_wg_size = ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
2640
2641    const bool      src_overlap = ggml_webgpu_tensor_binding_overlap(ctx->global_ctx, rn_src, mul_src);
2642    webgpu_pipeline pipeline    = ctx->shader_lib->get_rms_norm_mul_pipeline(shader_lib_ctx, src_overlap);
2643    auto *          decisions   = static_cast<ggml_webgpu_rms_norm_mul_shader_decisions *>(pipeline.context.get());
2644
2645    if (decisions->src_overlap) {
2646        const ggml_webgpu_merged_binding_range merged_range =
2647            ggml_webgpu_tensor_merged_binding_range(ctx, { rn_src, mul_src });
2648        merged_offset  = merged_range.offset;
2649        merged_size    = merged_range.size;
2650        offset_rn_src  = ggml_webgpu_tensor_merged_element_offset(rn_src, merged_range);
2651        offset_mul_src = ggml_webgpu_tensor_merged_element_offset(mul_src, merged_range);
2652        params[0]      = offset_rn_src;
2653        params[1]      = offset_mul_src;
2654    }
2655
2656    if (decisions->inplace || decisions->overlap) {
2657        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 0, rn_src));
2658        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 1, mul_src));
2659    } else if (decisions->src_overlap) {
2660        entries.push_back(
2661            ggml_webgpu_make_bind_group_entry(0, ggml_webgpu_tensor_buf(rn_src), merged_offset, merged_size));
2662        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 1, dst));
2663    } else {
2664        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 0, rn_src));
2665        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 1, mul_src));
2666        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 2, dst));
2667    }
2668
2669    return ggml_backend_webgpu_build(ctx, pipeline, params, entries, ggml_nrows(dst));
2670}
2671
2672static webgpu_encoded_op ggml_webgpu_row_norm(webgpu_context & ctx, ggml_tensor * src, ggml_tensor * dst) {
2673    std::vector<uint32_t> params = {
2674        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src) / ggml_type_size(src->type)),
2675        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, dst) / ggml_type_size(dst->type)),
2676        (uint32_t) (src->nb[1] / ggml_type_size(src->type)),
2677        (uint32_t) (src->nb[2] / ggml_type_size(src->type)),
2678        (uint32_t) (src->nb[3] / ggml_type_size(src->type)),
2679        (uint32_t) (dst->nb[1] / ggml_type_size(dst->type)),
2680        (uint32_t) (dst->nb[2] / ggml_type_size(dst->type)),
2681        (uint32_t) (dst->nb[3] / ggml_type_size(dst->type)),
2682        (uint32_t) src->ne[0],
2683        (uint32_t) src->ne[1],
2684        (uint32_t) src->ne[2],
2685        ggml_webgpu_u32_from_f32(ggml_get_op_params_f32(dst, 0))  // epsilon, treated as f32 in the shader
2686    };
2687
2688    ggml_webgpu_shader_lib_context shader_lib_ctx = {};
2689    shader_lib_ctx.src0                           = src;
2690    shader_lib_ctx.dst                            = dst;
2691    shader_lib_ctx.max_wg_size = ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
2692
2693    webgpu_pipeline pipeline  = ctx->shader_lib->get_row_norm_pipeline(shader_lib_ctx);
2694    auto *          decisions = static_cast<ggml_webgpu_generic_shader_decisions *>(pipeline.context.get());
2695
2696    std::vector<wgpu::BindGroupEntry> entries = { ggml_webgpu_make_tensor_bind_group_entry(ctx, 0, src) };
2697    if (!decisions->inplace) {
2698        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 1, dst));
2699    }
2700    return ggml_backend_webgpu_build(ctx, pipeline, params, entries, ggml_nrows(src));
2701}
2702
2703static webgpu_encoded_op ggml_webgpu_rope(webgpu_context & ctx,
2704                                          ggml_tensor *    src0,
2705                                          ggml_tensor *    src1,
2706                                          ggml_tensor *    src2,
2707                                          ggml_tensor *    dst) {
2708    ggml_webgpu_shader_lib_context shader_lib_ctx = {};
2709    shader_lib_ctx.src0                           = src0;
2710    shader_lib_ctx.src1                           = src1;
2711    shader_lib_ctx.src2                           = src2;
2712    shader_lib_ctx.dst                            = dst;
2713    shader_lib_ctx.max_wg_size = ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
2714
2715    webgpu_pipeline pipeline = ctx->shader_lib->get_rope_pipeline(shader_lib_ctx);
2716
2717    auto * decisions = static_cast<ggml_webgpu_generic_shader_decisions *>(pipeline.context.get());
2718
2719    const bool inplace         = decisions->inplace;
2720    const int  has_freq_factor = (src2 != nullptr);
2721
2722    const int n_dims     = ((int32_t *) dst->op_params)[1];
2723    const int mode       = ((int32_t *) dst->op_params)[2];
2724    const int n_ctx_orig = ((int32_t *) dst->op_params)[4];
2725    const int n_offs     = ((int32_t *) dst->op_params)[15];
2726
2727    float freq_base;
2728    float freq_scale;
2729    float ext_factor;
2730    float attn_factor;
2731    float beta_fast;
2732    float beta_slow;
2733    memcpy(&freq_base, (int32_t *) dst->op_params + 5, sizeof(float));
2734    memcpy(&freq_scale, (int32_t *) dst->op_params + 6, sizeof(float));
2735    memcpy(&ext_factor, (int32_t *) dst->op_params + 7, sizeof(float));
2736    memcpy(&attn_factor, (int32_t *) dst->op_params + 8, sizeof(float));
2737    memcpy(&beta_fast, (int32_t *) dst->op_params + 9, sizeof(float));
2738    memcpy(&beta_slow, (int32_t *) dst->op_params + 10, sizeof(float));
2739
2740    int sections[4];
2741    memcpy(sections, (int32_t *) dst->op_params + 11, 4 * sizeof(int));
2742
2743    float theta_scale = powf(freq_base, -2.0f / n_dims);
2744
2745    float corr_dims[2];
2746    ggml_rope_yarn_corr_dims(n_dims, n_ctx_orig, freq_base, beta_fast, beta_slow, corr_dims);
2747
2748    std::vector<uint32_t> params = {
2749        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src0) / ggml_type_size(src0->type)),
2750        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src1) / ggml_type_size(src1->type)),
2751        src2 != nullptr ? (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src2) / ggml_type_size(src2->type)) : 0,
2752        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, dst) / ggml_type_size(dst->type)),
2753        (uint32_t) (src0->nb[1] / ggml_type_size(src0->type)),
2754        (uint32_t) (src0->nb[2] / ggml_type_size(src0->type)),
2755        (uint32_t) (src0->nb[3] / ggml_type_size(src0->type)),
2756        (uint32_t) (dst->nb[1] / ggml_type_size(dst->type)),
2757        (uint32_t) (dst->nb[2] / ggml_type_size(dst->type)),
2758        (uint32_t) (dst->nb[3] / ggml_type_size(dst->type)),
2759        (uint32_t) ggml_nelements(src0) / 2,
2760        (uint32_t) src0->ne[0],
2761        (uint32_t) src0->ne[1],
2762        (uint32_t) src0->ne[2],
2763        (uint32_t) n_dims,
2764        (uint32_t) mode,
2765        ggml_webgpu_u32_from_f32(theta_scale),
2766        ggml_webgpu_u32_from_f32(attn_factor),
2767        ggml_webgpu_u32_from_f32(freq_scale),
2768        ggml_webgpu_u32_from_f32(ext_factor),
2769        ggml_webgpu_u32_from_f32(corr_dims[0]),
2770        ggml_webgpu_u32_from_f32(corr_dims[1]),
2771        (uint32_t) sections[0],
2772        (uint32_t) sections[1],
2773        (uint32_t) sections[2],
2774        (uint32_t) sections[3],
2775        (uint32_t) n_offs
2776    };
2777
2778    std::vector<wgpu::BindGroupEntry> entries     = { ggml_webgpu_make_tensor_bind_group_entry(ctx, 0, src0),
2779                                                      ggml_webgpu_make_tensor_bind_group_entry(ctx, 1, src1) };
2780    uint32_t                          dst_binding = 2;
2781    if (has_freq_factor) {
2782        dst_binding = 3;
2783        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 2, src2));
2784    }
2785    if (!inplace) {
2786        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, dst_binding, dst));
2787    }
2788
2789    uint32_t wg_x = CEIL_DIV(ggml_nelements(dst), decisions->wg_size);
2790    return ggml_backend_webgpu_build(ctx, pipeline, params, entries, wg_x);
2791}
2792
2793static webgpu_encoded_op ggml_webgpu_glu(webgpu_context & ctx,
2794                                         ggml_tensor *    src0,
2795                                         ggml_tensor *    src1,
2796                                         ggml_tensor *    dst) {
2797    ggml_webgpu_shader_lib_context shader_lib_ctx = {};
2798    shader_lib_ctx.src0                           = src0;
2799    shader_lib_ctx.src1                           = src1;
2800    shader_lib_ctx.dst                            = dst;
2801    shader_lib_ctx.max_wg_size = ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
2802
2803    const bool      src_overlap = src1 != nullptr && ggml_webgpu_tensor_binding_overlap(ctx->global_ctx, src0, src1);
2804    webgpu_pipeline pipeline    = ctx->shader_lib->get_glu_pipeline(shader_lib_ctx, src_overlap);
2805
2806    auto * decisions = static_cast<ggml_webgpu_glu_shader_decisions *>(pipeline.context.get());
2807
2808    const int split = (src1 != nullptr);
2809
2810    uint32_t offset_src0 = (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src0) / ggml_type_size(src0->type));
2811    uint32_t offset_src1 =
2812        src1 != nullptr ? (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src1) / ggml_type_size(src1->type)) : 0;
2813    size_t merged_offset = 0;
2814    size_t merged_size   = 0;
2815    if (decisions->src_overlap) {
2816        const ggml_webgpu_merged_binding_range merged_range =
2817            ggml_webgpu_tensor_merged_binding_range(ctx, { src0, src1 });
2818        merged_offset = merged_range.offset;
2819        merged_size   = merged_range.size;
2820        offset_src0   = ggml_webgpu_tensor_merged_element_offset(src0, merged_range);
2821        offset_src1   = ggml_webgpu_tensor_merged_element_offset(src1, merged_range);
2822    }
2823
2824    std::vector<uint32_t> params = {
2825        offset_src0,
2826        offset_src1,
2827        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, dst) / ggml_type_size(dst->type)),
2828        (uint32_t) (src0->nb[1] / ggml_type_size(src0->type)),
2829        (uint32_t) (src0->nb[2] / ggml_type_size(src0->type)),
2830        (uint32_t) (src0->nb[3] / ggml_type_size(src0->type)),
2831        src1 != nullptr ? (uint32_t) (src1->nb[1] / ggml_type_size(src1->type)) :
2832                          (uint32_t) (src0->nb[1] / ggml_type_size(src0->type)),
2833        src1 != nullptr ? (uint32_t) (src1->nb[2] / ggml_type_size(src1->type)) :
2834                          (uint32_t) (src0->nb[2] / ggml_type_size(src0->type)),
2835        src1 != nullptr ? (uint32_t) (src1->nb[3] / ggml_type_size(src1->type)) :
2836                          (uint32_t) (src0->nb[3] / ggml_type_size(src0->type)),
2837        (uint32_t) (dst->nb[1] / ggml_type_size(dst->type)),
2838        (uint32_t) (dst->nb[2] / ggml_type_size(dst->type)),
2839        (uint32_t) (dst->nb[3] / ggml_type_size(dst->type)),
2840        (uint32_t) ggml_nelements(dst),
2841        (uint32_t) dst->ne[0],
2842        (uint32_t) dst->ne[1],
2843        (uint32_t) dst->ne[2],
2844        (uint32_t) ((int32_t *) dst->op_params)[1],                // swapped
2845        ggml_webgpu_u32_from_f32(ggml_get_op_params_f32(dst, 2)),  // alpha, for swiglu_oai
2846        ggml_webgpu_u32_from_f32(ggml_get_op_params_f32(dst, 3)),  // limit
2847    };
2848
2849    std::vector<wgpu::BindGroupEntry> entries;
2850    uint32_t                          dst_binding = 1;
2851    if (decisions->src_overlap) {
2852        entries.push_back(
2853            ggml_webgpu_make_bind_group_entry(0, ggml_webgpu_tensor_buf(src0), merged_offset, merged_size));
2854    } else {
2855        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 0, src0));
2856    }
2857    if (split && !decisions->src_overlap) {
2858        dst_binding = 2;
2859        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 1, src1));
2860    }
2861    entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, dst_binding, dst));
2862
2863    uint32_t wg_x = CEIL_DIV(ggml_nelements(dst), decisions->wg_size);
2864    return ggml_backend_webgpu_build(ctx, pipeline, params, entries, wg_x);
2865}
2866
2867static webgpu_encoded_op ggml_webgpu_scale(webgpu_context & ctx, ggml_tensor * src, ggml_tensor * dst) {
2868    ggml_webgpu_shader_lib_context shader_lib_ctx = {};
2869    shader_lib_ctx.src0                           = src;
2870    shader_lib_ctx.src1                           = nullptr;
2871    shader_lib_ctx.dst                            = dst;
2872    shader_lib_ctx.max_wg_size = ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
2873
2874    webgpu_pipeline pipeline  = ctx->shader_lib->get_scale_pipeline(shader_lib_ctx);
2875    auto *          decisions = static_cast<ggml_webgpu_generic_shader_decisions *>(pipeline.context.get());
2876
2877    // params unchanged
2878    std::vector<uint32_t> params = {
2879        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src) / ggml_type_size(src->type)),
2880        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, dst) / ggml_type_size(dst->type)),
2881        (uint32_t) (src->nb[1] / ggml_type_size(src->type)),
2882        (uint32_t) (src->nb[2] / ggml_type_size(src->type)),
2883        (uint32_t) (src->nb[3] / ggml_type_size(src->type)),
2884        (uint32_t) (dst->nb[1] / ggml_type_size(dst->type)),
2885        (uint32_t) (dst->nb[2] / ggml_type_size(dst->type)),
2886        (uint32_t) (dst->nb[3] / ggml_type_size(dst->type)),
2887        (uint32_t) ggml_nelements(dst),
2888        (uint32_t) src->ne[0],
2889        (uint32_t) src->ne[1],
2890        (uint32_t) src->ne[2],
2891        ggml_webgpu_u32_from_f32(ggml_get_op_params_f32(dst, 0)),  // scale
2892        ggml_webgpu_u32_from_f32(ggml_get_op_params_f32(dst, 1))   // bias
2893    };
2894
2895    // bindgroups unchanged
2896    std::vector<wgpu::BindGroupEntry> entries = { ggml_webgpu_make_tensor_bind_group_entry(ctx, 0, src) };
2897
2898    if (!decisions->inplace) {
2899        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, 1, dst));
2900    }
2901
2902    uint32_t wg_x, wg_y;
2903    uint32_t total_wg = CEIL_DIV(ggml_nelements(dst), decisions->wg_size);
2904    compute_2d_workgroups(total_wg, ctx->global_ctx->capabilities.limits.maxComputeWorkgroupsPerDimension, wg_x, wg_y);
2905    return ggml_backend_webgpu_build(ctx, pipeline, params, entries, wg_x, wg_y);
2906}
2907
2908static webgpu_encoded_op ggml_webgpu_soft_max(webgpu_context & ctx,
2909                                              ggml_tensor *    src0,
2910                                              ggml_tensor *    src1,
2911                                              ggml_tensor *    src2,
2912                                              ggml_tensor *    dst) {
2913    ggml_webgpu_shader_lib_context shader_lib_ctx = {};
2914    shader_lib_ctx.src0                           = src0;
2915    shader_lib_ctx.src1                           = src1;
2916    shader_lib_ctx.src2                           = src2;
2917    shader_lib_ctx.dst                            = dst;
2918    shader_lib_ctx.max_wg_size = ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
2919
2920    webgpu_pipeline pipeline  = ctx->shader_lib->get_soft_max_pipeline(shader_lib_ctx);
2921    auto *          decisions = static_cast<ggml_webgpu_generic_shader_decisions *>(pipeline.context.get());
2922
2923    const bool inplace     = decisions->inplace;
2924    const int  has_mask    = (src1 != nullptr);
2925    const int  has_sink    = (src2 != nullptr);
2926    float      max_bias    = ggml_get_op_params_f32(dst, 1);
2927    float      n_head_log2 = float(1u << (uint32_t) floor(log2(src0->ne[2])));
2928    float      m0          = powf(2.0f, -(max_bias) / n_head_log2);
2929    float      m1          = powf(2.0f, -(max_bias / 2.0f) / n_head_log2);
2930
2931    std::vector<uint32_t> params = {
2932        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src0) / ggml_type_size(src0->type)),
2933        has_mask ? (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src1) / ggml_type_size(src1->type)) : 0,
2934        has_sink ? (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src2) / ggml_type_size(src2->type)) : 0,
2935        (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, dst) / ggml_type_size(dst->type)),
2936        (uint32_t) (src0->nb[1] / ggml_type_size(src0->type)),
2937        (uint32_t) (src0->nb[2] / ggml_type_size(src0->type)),
2938        (uint32_t) (src0->nb[3] / ggml_type_size(src0->type)),
2939        has_mask ? (uint32_t) (src1->nb[1] / ggml_type_size(src1->type)) : 0,
2940        has_mask ? (uint32_t) (src1->nb[2] / ggml_type_size(src1->type)) : 0,
2941        has_mask ? (uint32_t) (src1->nb[3] / ggml_type_size(src1->type)) : 0,
2942        (uint32_t) (dst->nb[1] / ggml_type_size(dst->type)),
2943        (uint32_t) (dst->nb[2] / ggml_type_size(dst->type)),
2944        (uint32_t) (dst->nb[3] / ggml_type_size(dst->type)),
2945        (uint32_t) src0->ne[0],
2946        (uint32_t) src0->ne[1],
2947        (uint32_t) src0->ne[2],
2948        has_mask ? (uint32_t) src1->ne[2] : 0,
2949        has_mask ? (uint32_t) src1->ne[3] : 0,
2950        ggml_webgpu_u32_from_f32(ggml_get_op_params_f32(dst, 0)),  // scale
2951        ggml_webgpu_u32_from_f32(max_bias),
2952        ggml_webgpu_u32_from_f32(n_head_log2),
2953        ggml_webgpu_u32_from_f32(m0),
2954        ggml_webgpu_u32_from_f32(m1)
2955    };
2956
2957    std::vector<wgpu::BindGroupEntry> entries     = { ggml_webgpu_make_bind_group_entry(
2958        0, ggml_webgpu_tensor_buf(src0), ggml_webgpu_tensor_align_offset(ctx, src0),
2959        ggml_webgpu_tensor_binding_size(ctx, src0)) };
2960    uint32_t                          binding_num = 1;
2961    if (has_mask) {
2962        entries.push_back(ggml_webgpu_make_bind_group_entry(binding_num, ggml_webgpu_tensor_buf(src1),
2963                                                            ggml_webgpu_tensor_align_offset(ctx, src1),
2964                                                            ggml_webgpu_tensor_binding_size(ctx, src1)));
2965        binding_num++;
2966    }
2967    if (has_sink) {
2968        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, binding_num, src2));
2969        binding_num++;
2970    }
2971    if (!inplace) {
2972        entries.push_back(ggml_webgpu_make_tensor_bind_group_entry(ctx, binding_num, dst));
2973    }
2974
2975    return ggml_backend_webgpu_build(ctx, pipeline, params, entries, ggml_nrows(dst));
2976}
2977
2978static webgpu_encoded_op ggml_webgpu_argmax(webgpu_context & ctx, ggml_tensor * src, ggml_tensor * dst) {
2979    std::vector<uint32_t> params = { (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src) / ggml_type_size(src->type)),
2980                                     (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, dst) / ggml_type_size(dst->type)),
2981                                     (uint32_t) src->ne[0] };
2982
2983    std::vector<wgpu::BindGroupEntry> entries = { ggml_webgpu_make_tensor_bind_group_entry(ctx, 0, src),
2984                                                  ggml_webgpu_make_tensor_bind_group_entry(ctx, 1, dst) };
2985
2986    ggml_webgpu_shader_lib_context shader_lib_ctx = {};
2987    shader_lib_ctx.src0                           = src;
2988    shader_lib_ctx.dst                            = dst;
2989    shader_lib_ctx.max_wg_size = ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
2990
2991    webgpu_pipeline pipeline = ctx->shader_lib->get_argmax_pipeline(shader_lib_ctx);
2992    uint32_t        wg_x     = ggml_nelements(dst);
2993    return ggml_backend_webgpu_build(ctx, pipeline, params, entries, wg_x);
2994}
2995
2996static webgpu_encoded_op ggml_webgpu_argsort(webgpu_context & ctx, ggml_tensor * src, ggml_tensor * dst) {
2997    bool is_top_k = dst->op == GGML_OP_TOP_K;
2998
2999    ggml_webgpu_shader_lib_context shader_lib_ctx = {};
3000    shader_lib_ctx.src0                           = src;
3001    shader_lib_ctx.src1                           = nullptr;
3002    shader_lib_ctx.dst                            = dst;
3003    shader_lib_ctx.max_wg_size        = ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
3004    shader_lib_ctx.wg_mem_limit_bytes = ctx->global_ctx->capabilities.limits.maxComputeWorkgroupStorageSize;
3005
3006    webgpu_pipeline argsort_pipeline = ctx->shader_lib->get_argsort_pipeline(shader_lib_ctx);
3007    auto * argsort_decisions = static_cast<ggml_webgpu_generic_shader_decisions *>(argsort_pipeline.context.get());
3008
3009    webgpu_pipeline argsort_merge_pipeline = ctx->shader_lib->get_argsort_merge_pipeline(shader_lib_ctx);
3010
3011    const uint32_t src_ne0 = (uint32_t) src->ne[0];
3012    const uint32_t nrows   = (uint32_t) ggml_nrows(src);
3013    const uint32_t npr     = CEIL_DIV(src_ne0, argsort_decisions->wg_size);
3014    const uint32_t block_size =
3015        is_top_k ? std::min(argsort_decisions->wg_size, (uint32_t) dst->ne[0]) : argsort_decisions->wg_size;
3016    uint32_t out_ne0 = src_ne0;
3017    if (is_top_k) {
3018        if (npr > 1) {
3019            const uint32_t last_tile = src_ne0 - (npr - 1) * argsort_decisions->wg_size;
3020            out_ne0                  = (npr - 1) * block_size + std::min(last_tile, block_size);
3021        } else {
3022            out_ne0 = block_size;
3023        }
3024    }
3025
3026    uint32_t merge_len    = block_size;
3027    uint32_t merge_passes = 0;
3028    while (merge_len < out_ne0) {
3029        merge_len <<= 1;
3030        merge_passes++;
3031    }
3032
3033    const bool start_in_tmp = (merge_passes % 2) == 1;
3034
3035    const size_t dst_offset = ggml_webgpu_tensor_offset(dst);
3036    const size_t idx_nbytes = out_ne0 * ggml_nrows(dst) * sizeof(int32_t);
3037    const size_t tmp_offset =
3038        ROUNDUP_POW2(dst_offset + idx_nbytes, ctx->global_ctx->capabilities.limits.minStorageBufferOffsetAlignment);
3039    const size_t tmp_binding_size = ROUNDUP_POW2(idx_nbytes, WEBGPU_STORAGE_BUF_BINDING_MULT);
3040    const size_t dst_binding_size =
3041        ROUNDUP_POW2(idx_nbytes + ggml_webgpu_tensor_misalignment(ctx, dst), WEBGPU_STORAGE_BUF_BINDING_MULT);
3042
3043    const uint32_t offset_src  = (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src) / ggml_type_size(src->type));
3044    const uint32_t offset_dst  = (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, dst) / ggml_type_size(dst->type));
3045    const uint32_t offset_tmp  = 0;
3046    const uint32_t stride_src1 = (uint32_t) (src->nb[1] / ggml_type_size(src->type));
3047    const uint32_t stride_src2 = (uint32_t) (src->nb[2] / ggml_type_size(src->type));
3048    const uint32_t stride_src3 = (uint32_t) (src->nb[3] / ggml_type_size(src->type));
3049    const uint32_t stride_idx1 = out_ne0;
3050    const uint32_t stride_idx2 = out_ne0 * (uint32_t) dst->ne[1];
3051    const uint32_t stride_idx3 = stride_idx2 * (uint32_t) dst->ne[2];
3052
3053    std::vector<webgpu_dispatch_desc> dispatches;
3054
3055    const uint32_t init_offset       = start_in_tmp ? offset_tmp : offset_dst;
3056    const size_t   init_align_offset = start_in_tmp ? tmp_offset : ggml_webgpu_tensor_align_offset(ctx, dst);
3057    const size_t   init_binding_size = start_in_tmp ? tmp_binding_size : dst_binding_size;
3058
3059    std::vector<uint32_t> init_params = {
3060        offset_src,  init_offset, stride_src1, stride_src2,           stride_src3,           stride_idx1,
3061        stride_idx2, stride_idx3, src_ne0,     (uint32_t) src->ne[1], (uint32_t) src->ne[2], out_ne0,
3062        block_size,  npr,         nrows
3063    };
3064
3065    uint32_t       wg_x_init;
3066    uint32_t       wg_y_init;
3067    const uint32_t total_wg_init  = npr * nrows;
3068    const uint32_t max_wg_per_dim = ctx->global_ctx->capabilities.limits.maxComputeWorkgroupsPerDimension;
3069    compute_2d_workgroups(total_wg_init, max_wg_per_dim, wg_x_init, wg_y_init);
3070
3071    std::vector<wgpu::BindGroupEntry> init_entries = {
3072        ggml_webgpu_make_tensor_bind_group_entry(ctx, 0, src),
3073        ggml_webgpu_make_bind_group_entry(1, ggml_webgpu_tensor_buf(dst), init_align_offset, init_binding_size)
3074    };
3075
3076    dispatches.push_back({
3077        argsort_pipeline, std::move(init_params), std::move(init_entries), { wg_x_init, wg_y_init }
3078    });
3079
3080    if (merge_passes == 0) {
3081        return ggml_backend_webgpu_build_multi(ctx, dispatches);
3082    }
3083
3084    bool     in_is_tmp = start_in_tmp;
3085    uint32_t len       = block_size;
3086    while (len < out_ne0) {
3087        const uint32_t nm = CEIL_DIV(out_ne0, 2 * len);
3088
3089        const bool     out_is_tmp  = !in_is_tmp;
3090        const uint32_t offset_in   = in_is_tmp ? offset_tmp : offset_dst;
3091        const uint32_t offset_out  = out_is_tmp ? offset_tmp : offset_dst;
3092        const size_t   align_in    = in_is_tmp ? tmp_offset : ggml_webgpu_tensor_align_offset(ctx, dst);
3093        const size_t   align_out   = out_is_tmp ? tmp_offset : ggml_webgpu_tensor_align_offset(ctx, dst);
3094        const size_t   size_in     = in_is_tmp ? tmp_binding_size : dst_binding_size;
3095        const size_t   size_out    = out_is_tmp ? tmp_binding_size : dst_binding_size;
3096        const uint32_t top_k_out   = (is_top_k && nm == 1) ? (uint32_t) dst->ne[0] : out_ne0;
3097        const uint32_t stride_out1 = top_k_out;
3098        const uint32_t stride_out2 = top_k_out * (uint32_t) dst->ne[1];
3099        const uint32_t stride_out3 = stride_out2 * (uint32_t) dst->ne[2];
3100
3101        std::vector<uint32_t> merge_params = { offset_src,
3102                                               offset_in,
3103                                               offset_out,
3104                                               stride_src1,
3105                                               stride_src2,
3106                                               stride_src3,
3107                                               stride_idx1,
3108                                               stride_idx2,
3109                                               stride_idx3,
3110                                               stride_out1,
3111                                               stride_out2,
3112                                               stride_out3,
3113                                               out_ne0,
3114                                               (uint32_t) src->ne[1],
3115                                               (uint32_t) src->ne[2],
3116                                               top_k_out,
3117                                               len,
3118                                               nm,
3119                                               nrows };
3120
3121        std::vector<wgpu::BindGroupEntry> merge_entries = {
3122            ggml_webgpu_make_tensor_bind_group_entry(ctx, 0, src),
3123            ggml_webgpu_make_bind_group_entry(1, ggml_webgpu_tensor_buf(dst), align_in, size_in),
3124            ggml_webgpu_make_bind_group_entry(2, ggml_webgpu_tensor_buf(dst), align_out, size_out)
3125        };
3126
3127        uint32_t       wg_x_merge;
3128        uint32_t       wg_y_merge;
3129        const uint32_t total_wg_merge = nm * nrows;
3130        compute_2d_workgroups(total_wg_merge, max_wg_per_dim, wg_x_merge, wg_y_merge);
3131
3132        dispatches.push_back({
3133            argsort_merge_pipeline, std::move(merge_params), std::move(merge_entries), { wg_x_merge, wg_y_merge }
3134        });
3135
3136        len <<= 1;
3137        in_is_tmp = !in_is_tmp;
3138    }
3139
3140    return ggml_backend_webgpu_build_multi(ctx, dispatches);
3141}
3142
3143static webgpu_encoded_op ggml_webgpu_cumsum(webgpu_context & ctx, ggml_tensor * src, ggml_tensor * dst) {
3144    std::vector<uint32_t> params = { (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src) / ggml_type_size(src->type)),
3145                                     (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, dst) / ggml_type_size(dst->type)),
3146                                     (uint32_t) src->ne[0] };
3147
3148    std::vector<wgpu::BindGroupEntry> entries = { ggml_webgpu_make_tensor_bind_group_entry(ctx, 0, src),
3149                                                  ggml_webgpu_make_tensor_bind_group_entry(ctx, 1, dst) };
3150
3151    ggml_webgpu_shader_lib_context shader_lib_ctx = {};
3152    shader_lib_ctx.src0                           = src;
3153    shader_lib_ctx.src1                           = nullptr;
3154    shader_lib_ctx.dst                            = dst;
3155    shader_lib_ctx.max_wg_size = ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
3156
3157    webgpu_pipeline pipeline = ctx->shader_lib->get_cumsum_pipeline(shader_lib_ctx);
3158    uint32_t        wg_x     = ggml_nrows(dst);
3159    return ggml_backend_webgpu_build(ctx, pipeline, params, entries, wg_x);
3160}
3161
3162static webgpu_encoded_op ggml_webgpu_sum_rows(webgpu_context & ctx, ggml_tensor * src, ggml_tensor * dst) {
3163    bool                  total_sum = dst->op == GGML_OP_SUM;
3164    std::vector<uint32_t> params = { (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src) / ggml_type_size(src->type)),
3165                                     (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, dst) / ggml_type_size(dst->type)),
3166                                     total_sum ? 0 : (uint32_t) (src->nb[1] / ggml_type_size(src->type)),
3167                                     total_sum ? 0 : (uint32_t) (src->nb[2] / ggml_type_size(src->type)),
3168                                     total_sum ? 0 : (uint32_t) (src->nb[3] / ggml_type_size(src->type)),
3169                                     total_sum ? static_cast<uint32_t>(ggml_nelements(src)) : (uint32_t) src->ne[0],
3170                                     total_sum ? 1 : (uint32_t) src->ne[1],
3171                                     total_sum ? 1 : (uint32_t) src->ne[2] };
3172
3173    std::vector<wgpu::BindGroupEntry> entries = { ggml_webgpu_make_tensor_bind_group_entry(ctx, 0, src),
3174                                                  ggml_webgpu_make_tensor_bind_group_entry(ctx, 1, dst) };
3175
3176    ggml_webgpu_shader_lib_context shader_lib_ctx = {};
3177    shader_lib_ctx.src0                           = src;
3178    shader_lib_ctx.dst                            = dst;
3179    shader_lib_ctx.max_wg_size = ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
3180
3181    webgpu_pipeline pipeline = ctx->shader_lib->get_sum_rows_pipeline(shader_lib_ctx);
3182
3183    uint32_t wg_x = total_sum ? 1 : ggml_nrows(dst);
3184    return ggml_backend_webgpu_build(ctx, pipeline, params, entries, wg_x);
3185}
3186
3187static bool ggml_webgpu_can_fuse_rms_norm_mul(const struct ggml_cgraph * cgraph, int node_idx) {
3188    if (!ggml_can_fuse(cgraph, node_idx, { GGML_OP_RMS_NORM, GGML_OP_MUL })) {
3189        return false;
3190    }
3191
3192    // additional constraints specific to this fusion
3193    const ggml_tensor * rms_norm = cgraph->nodes[node_idx];
3194    const ggml_tensor * mul      = cgraph->nodes[node_idx + 1];
3195
3196    GGML_ASSERT(rms_norm->src[0]->type == GGML_TYPE_F32);
3197    GGML_ASSERT(rms_norm->type == GGML_TYPE_F32);
3198    // rms_norm only supports f32
3199    if (mul->src[0]->type != GGML_TYPE_F32 || mul->src[1]->type != GGML_TYPE_F32 || mul->type != GGML_TYPE_F32) {
3200        return false;
3201    }
3202    // if rms_norm is the B operand, then we don't handle broadcast
3203    if (rms_norm == mul->src[1] && !ggml_are_same_shape(mul->src[0], rms_norm)) {
3204        return false;
3205    }
3206    // rms_norm shader assumes contiguous rows
3207    if (!ggml_is_contiguous_rows(mul->src[0]) || !ggml_is_contiguous_rows(mul->src[1])) {
3208        return false;
3209    }
3210
3211    return true;
3212}
3213
3214static webgpu_encoded_op ggml_webgpu_upscale(webgpu_context ctx, ggml_tensor * src, ggml_tensor * dst) {
3215    const uint32_t        mode_flags = (uint32_t) ggml_get_op_params_i32(dst, 0);
3216    std::vector<uint32_t> params = { (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src) / ggml_type_size(src->type)),
3217                                     (uint32_t) (ggml_webgpu_tensor_misalignment(ctx, dst) / ggml_type_size(dst->type)),
3218
3219                                     (uint32_t) (src->nb[0] / ggml_type_size(src->type)),
3220                                     (uint32_t) (src->nb[1] / ggml_type_size(src->type)),
3221                                     (uint32_t) (src->nb[2] / ggml_type_size(src->type)),
3222                                     (uint32_t) (src->nb[3] / ggml_type_size(src->type)),
3223
3224                                     (uint32_t) (dst->nb[0] / ggml_type_size(dst->type)),
3225                                     (uint32_t) (dst->nb[1] / ggml_type_size(dst->type)),
3226                                     (uint32_t) (dst->nb[2] / ggml_type_size(dst->type)),
3227                                     (uint32_t) (dst->nb[3] / ggml_type_size(dst->type)),
3228
3229                                     (uint32_t) src->ne[0],
3230                                     (uint32_t) src->ne[1],
3231                                     (uint32_t) src->ne[2],
3232                                     (uint32_t) src->ne[3],
3233
3234                                     (uint32_t) dst->ne[0],
3235                                     (uint32_t) dst->ne[1],
3236                                     (uint32_t) dst->ne[2],
3237                                     (uint32_t) dst->ne[3],
3238
3239                                     mode_flags };
3240
3241    std::vector<wgpu::BindGroupEntry> entries = { ggml_webgpu_make_tensor_bind_group_entry(ctx, 0, src),
3242                                                  ggml_webgpu_make_tensor_bind_group_entry(ctx, 1, dst) };
3243
3244    ggml_webgpu_shader_lib_context shader_lib_ctx = {};
3245    shader_lib_ctx.src0                           = src;
3246    shader_lib_ctx.dst                            = dst;
3247    shader_lib_ctx.max_wg_size = ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
3248
3249    webgpu_pipeline pipeline  = ctx->shader_lib->get_upscale_pipeline(shader_lib_ctx);
3250    auto *          decisions = static_cast<ggml_webgpu_generic_shader_decisions *>(pipeline.context.get());
3251
3252    uint32_t wg_x;
3253    uint32_t wg_y;
3254    uint32_t total_wg = CEIL_DIV((uint32_t) ggml_nelements(dst), decisions->wg_size);
3255    compute_2d_workgroups(total_wg, ctx->global_ctx->capabilities.limits.maxComputeWorkgroupsPerDimension, wg_x, wg_y);
3256
3257    return ggml_backend_webgpu_build(ctx, pipeline, params, entries, wg_x, wg_y);
3258}
3259
3260// Returns the encoded command, or std::nullopt if the operation is a no-op
3261static std::optional<webgpu_encoded_op> ggml_webgpu_encode(webgpu_context ctx,
3262                                                           ggml_cgraph *  cgraph,
3263                                                           int            node_idx,
3264                                                           int &          num_encoded_ops) {
3265    ggml_tensor ** nodes = cgraph->nodes;
3266    ggml_tensor *  node  = nodes[node_idx];
3267
3268    if (ggml_is_empty(node)) {
3269        return std::nullopt;
3270    }
3271    if ((node->flags & GGML_TENSOR_FLAG_COMPUTE) == 0) {
3272        return std::nullopt;
3273    }
3274    WEBGPU_LOG_DEBUG("ggml_webgpu_encode(" << node << ", " << ggml_op_name(node->op) << ")");
3275
3276    ggml_tensor * src0 = node->src[0];
3277    ggml_tensor * src1 = node->src[1];
3278    ggml_tensor * src2 = node->src[2];
3279
3280    switch (node->op) {
3281            // no-ops
3282        case GGML_OP_NONE:
3283        case GGML_OP_VIEW:
3284        case GGML_OP_PERMUTE:
3285        case GGML_OP_TRANSPOSE:
3286        case GGML_OP_RESHAPE:
3287            return std::nullopt;
3288        case GGML_OP_CPY:
3289        case GGML_OP_CONT:
3290            return ggml_webgpu_cpy(ctx, src0, node);
3291        case GGML_OP_SET:
3292            return ggml_webgpu_set(ctx, src0, src1, node);
3293        case GGML_OP_SET_ROWS:
3294            return ggml_webgpu_set_rows(ctx, src0, src1, node);
3295        case GGML_OP_GET_ROWS:
3296            return ggml_webgpu_get_rows(ctx, src0, src1, node);
3297        case GGML_OP_MUL_MAT:
3298            return ggml_webgpu_mul_mat(ctx, src0, src1, node);
3299        case GGML_OP_MUL_MAT_ID:
3300            return ggml_webgpu_mul_mat_id(ctx, src0, src1, src2, node);
3301        case GGML_OP_FLASH_ATTN_EXT:
3302            return ggml_webgpu_flash_attn(ctx, src0, src1, src2, node->src[3], node->src[4], node);
3303        case GGML_OP_ADD:
3304        case GGML_OP_SUB:
3305        case GGML_OP_MUL:
3306        case GGML_OP_DIV:
3307            return ggml_webgpu_binary_op(ctx, src0, src1, node);
3308        case GGML_OP_ADD_ID:
3309            return ggml_webgpu_add_id(ctx, src0, src1, src2, node);
3310        case GGML_OP_CONCAT:
3311            return ggml_webgpu_concat(ctx, src0, src1, node);
3312        case GGML_OP_REPEAT:
3313            return ggml_webgpu_repeat(ctx, src0, node);
3314        case GGML_OP_RMS_NORM:
3315            if (ggml_webgpu_can_fuse_rms_norm_mul(cgraph, node_idx)) {
3316                num_encoded_ops        = 2;
3317                ggml_tensor * mul_node = nodes[node_idx + 1];
3318                return ggml_webgpu_rms_norm_mul(ctx, src0, node, mul_node->src[0], mul_node->src[1], mul_node);
3319            } else {
3320                return ggml_webgpu_row_norm(ctx, src0, node);
3321            }
3322        case GGML_OP_NORM:
3323        case GGML_OP_L2_NORM:
3324            return ggml_webgpu_row_norm(ctx, src0, node);
3325        case GGML_OP_ROPE:
3326            return ggml_webgpu_rope(ctx, src0, src1, src2, node);
3327        case GGML_OP_GLU:
3328            return ggml_webgpu_glu(ctx, src0, src1, node);
3329        case GGML_OP_SCALE:
3330            return ggml_webgpu_scale(ctx, src0, node);
3331        case GGML_OP_SOFT_MAX:
3332            return ggml_webgpu_soft_max(ctx, src0, src1, src2, node);
3333        case GGML_OP_UNARY:
3334        case GGML_OP_CLAMP:
3335        case GGML_OP_FILL:
3336        case GGML_OP_LOG:
3337        case GGML_OP_SQR:
3338        case GGML_OP_SQRT:
3339        case GGML_OP_SIN:
3340        case GGML_OP_COS:
3341        case GGML_OP_DIAG:
3342        case GGML_OP_TRI:
3343            return ggml_webgpu_unary_op(ctx, src0, node);
3344        case GGML_OP_SOLVE_TRI:
3345            return ggml_webgpu_solve_tri(ctx, src0, src1, node);
3346        case GGML_OP_SSM_CONV:
3347            return ggml_webgpu_ssm_conv(ctx, src0, src1, node);
3348        case GGML_OP_SSM_SCAN:
3349            return ggml_webgpu_ssm_scan(ctx, src0, src1, src2, node->src[3], node->src[4], node->src[5], node->src[6],
3350                                        node);
3351        case GGML_OP_GATED_DELTA_NET:
3352            return ggml_webgpu_gated_delta_net(ctx, src0, src1, src2, node->src[3], node->src[4], node->src[5], node);
3353        case GGML_OP_PAD:
3354            return ggml_webgpu_pad(ctx, src0, node);
3355        case GGML_OP_ARGMAX:
3356            return ggml_webgpu_argmax(ctx, src0, node);
3357        case GGML_OP_ARGSORT:
3358        case GGML_OP_TOP_K:
3359            // we reuse the same argsort implementation for top_k
3360            return ggml_webgpu_argsort(ctx, src0, node);
3361        case GGML_OP_CUMSUM:
3362            return ggml_webgpu_cumsum(ctx, src0, node);
3363        case GGML_OP_SUM:
3364        case GGML_OP_SUM_ROWS:
3365            return ggml_webgpu_sum_rows(ctx, src0, node);
3366        case GGML_OP_CONV_2D:
3367            return ggml_webgpu_conv_2d(ctx, src0, src1, node);
3368        case GGML_OP_CONV_2D_DW:
3369            return ggml_webgpu_conv_2d_dw(ctx, src0, src1, node);
3370        case GGML_OP_IM2COL:
3371            return ggml_webgpu_im2col(ctx, src0, src1, node);
3372        case GGML_OP_UPSCALE:
3373            return ggml_webgpu_upscale(ctx, src0, node);
3374        default:
3375            return std::nullopt;
3376    }
3377}
3378
3379#ifdef GGML_WEBGPU_GPU_PROFILE
3380static void ggml_backend_webgpu_collect_profile_results(webgpu_context &                 ctx,
3381                                                        const std::vector<std::string> & pipeline_names,
3382                                                        uint32_t &                       num_inflight_batches) {
3383    if (pipeline_names.empty()) {
3384        return;
3385    }
3386
3387    wgpu::CommandEncoder encoder = ctx->global_ctx->device.CreateCommandEncoder();
3388    encoder.ResolveQuerySet(ctx->profile_timestamp_query_set, 0, ctx->profile_timestamp_query_count,
3389                            ctx->profile_timestamp_dev_buf, 0);
3390    encoder.CopyBufferToBuffer(ctx->profile_timestamp_dev_buf, 0, ctx->profile_timestamp_host_buf, 0,
3391                               ctx->profile_timestamp_query_count * sizeof(uint64_t));
3392
3393    wgpu::CommandBuffer profile_commands = encoder.Finish();
3394    ggml_backend_webgpu_submit_commands(ctx, profile_commands, num_inflight_batches);
3395
3396    const size_t mapped_size = ctx->profile_timestamp_query_count * sizeof(uint64_t);
3397    GGML_ASSERT(ctx->profile_timestamp_query_count == 2 * pipeline_names.size());
3398
3399    ggml_backend_webgpu_map_buffer(ctx->global_ctx, ctx->profile_timestamp_host_buf, wgpu::MapMode::Read, 0,
3400                                   mapped_size);
3401    const uint64_t * ts_data = (const uint64_t *) ctx->profile_timestamp_host_buf.GetConstMappedRange(0, mapped_size);
3402
3403    for (size_t i = 0; i < pipeline_names.size(); ++i) {
3404        // WebGPU timestamps are in ns; convert to ms.
3405        const double elapsed_ms = double(ts_data[2 * i + 1] - ts_data[2 * i]) * 1e-6;
3406        ctx->shader_gpu_time_ms[pipeline_names[i]] += elapsed_ms;
3407    }
3408
3409    ctx->profile_timestamp_host_buf.Unmap();
3410}
3411#endif
3412
3413// Don't bother checking set_rows index overflow for now, since practically the WebGPU doesn't need to support
3414// models that would require it right now.
3415static void ggml_backend_webgpu_check_set_rows(webgpu_context & ctx, uint32_t & num_inflight_batches) {
3416#ifdef GGML_WEBGPU_CHECK_SET_ROWS
3417    wgpu::CommandEncoder encoder = ctx->global_ctx->device.CreateCommandEncoder();
3418    encoder.CopyBufferToBuffer(ctx->set_rows_dev_error_buf, 0, ctx->set_rows_host_error_buf, 0,
3419                               ctx->set_rows_host_error_buf.GetSize());
3420    wgpu::CommandBuffer commands = encoder.Finish();
3421    ggml_backend_webgpu_submit_commands(ctx, commands, num_inflight_batches);
3422    ggml_backend_webgpu_map_buffer(ctx->global_ctx, ctx->set_rows_host_error_buf, wgpu::MapMode::Read, 0,
3423                                   ctx->set_rows_host_error_buf.GetSize());
3424    const uint32_t * error_data = (const uint32_t *) ctx->set_rows_host_error_buf.GetConstMappedRange();
3425    if (*error_data) {
3426        GGML_ABORT("ggml_webgpu: SET_ROWS index > 2^32, unsupported.");
3427    }
3428    ctx->set_rows_host_error_buf.Unmap();
3429#else
3430    GGML_UNUSED(ctx);
3431    GGML_UNUSED(num_inflight_batches);
3432#endif
3433}
3434
3435static ggml_status ggml_backend_webgpu_graph_compute(ggml_backend_t backend, struct ggml_cgraph * cgraph) {
3436    WEBGPU_LOG_DEBUG("ggml_backend_webgpu_graph_compute(" << cgraph->n_nodes << " nodes)");
3437
3438    ggml_backend_webgpu_context * backend_ctx = (ggml_backend_webgpu_context *) backend->context;
3439    webgpu_context                ctx         = backend_ctx->webgpu_ctx;
3440
3441    WEBGPU_CPU_PROFILE_TOTAL_START(graph_compute);
3442
3443    std::vector<webgpu_encoded_op> commands;
3444
3445    uint32_t num_batched_kernels  = 0;
3446    uint32_t num_inflight_batches = 0;
3447    bool     contains_set_rows    = false;
3448    int      num_encoded_ops      = 1;
3449    int      node_idx             = 0;
3450
3451#ifdef GGML_WEBGPU_GPU_PROFILE
3452    ctx->profile_timestamp_query_count = 0;
3453    std::vector<std::string> profile_pipeline_names;
3454#endif
3455
3456    ctx->active_command_encoder = ctx->global_ctx->device.CreateCommandEncoder();
3457    if (ctx->batch_compute_passes) {
3458        ctx->active_compute_pass = ctx->active_command_encoder.BeginComputePass();
3459    }
3460
3461    while (node_idx < cgraph->n_nodes) {
3462        if (cgraph->nodes[node_idx]->op == GGML_OP_SET_ROWS) {
3463            contains_set_rows = true;
3464        }
3465        if (auto cmd = ggml_webgpu_encode(ctx, cgraph, node_idx, num_encoded_ops)) {
3466            commands.push_back(*cmd);
3467            num_batched_kernels += cmd.value().num_kernels;
3468#ifdef GGML_WEBGPU_GPU_PROFILE
3469            profile_pipeline_names.insert(profile_pipeline_names.end(), cmd->pipeline_names.begin(),
3470                                          cmd->pipeline_names.end());
3471#endif
3472        }
3473
3474        if (num_batched_kernels >= ctx->global_ctx->command_submit_batch_size) {
3475            if (ctx->active_compute_pass) {
3476                ctx->active_compute_pass.End();
3477            }
3478            num_batched_kernels                = 0;
3479            wgpu::CommandBuffer batch_commands = ctx->active_command_encoder.Finish();
3480            ggml_backend_webgpu_submit_commands(ctx, batch_commands, num_inflight_batches);
3481
3482            // reset state for next batch
3483            ctx->active_command_encoder = ctx->global_ctx->device.CreateCommandEncoder();
3484            if (ctx->batch_compute_passes) {
3485                ctx->active_compute_pass = ctx->active_command_encoder.BeginComputePass();
3486            }
3487            ctx->param_arena.reset();
3488            commands.clear();
3489#ifdef GGML_WEBGPU_GPU_PROFILE
3490            // flush before the next batch can overflow the QuerySet
3491            if (ctx->profile_timestamp_query_count + 2 * ctx->global_ctx->command_submit_batch_size >=
3492                WEBGPU_MAX_PROFILE_QUERY_COUNT) {
3493                ggml_backend_webgpu_collect_profile_results(ctx, profile_pipeline_names, num_inflight_batches);
3494                // reset profile timestamp state
3495                ctx->profile_timestamp_query_count = 0;
3496                profile_pipeline_names.clear();
3497            }
3498#endif
3499        }
3500
3501        node_idx += num_encoded_ops;
3502        num_encoded_ops = 1;
3503    }
3504
3505    if (ctx->active_compute_pass) {
3506        ctx->active_compute_pass.End();
3507        ctx->active_compute_pass = nullptr;
3508    }
3509
3510    if (num_batched_kernels > 0) {
3511        wgpu::CommandBuffer batch_commands = ctx->active_command_encoder.Finish();
3512        ggml_backend_webgpu_submit_commands(ctx, batch_commands, num_inflight_batches);
3513        ctx->param_arena.reset();
3514        commands.clear();
3515    }
3516    ctx->active_command_encoder = nullptr;
3517
3518#ifdef GGML_WEBGPU_GPU_PROFILE
3519    ggml_backend_webgpu_collect_profile_results(ctx, profile_pipeline_names, num_inflight_batches);
3520#endif
3521
3522    if (contains_set_rows) {
3523        ggml_backend_webgpu_check_set_rows(ctx, num_inflight_batches);
3524    }
3525
3526    WEBGPU_CPU_PROFILE_TOTAL_END(graph_compute, ctx->global_ctx);
3527    return GGML_STATUS_SUCCESS;
3528}
3529
3530struct ggml_backend_webgpu_event_context {
3531    webgpu_global_context global_ctx;
3532    wgpu::Future          future;
3533    bool                  recorded = false;
3534};
3535
3536static ggml_backend_event_t ggml_backend_webgpu_device_event_new(ggml_backend_dev_t device) {
3537    ggml_backend_webgpu_device_context * dev_ctx = (ggml_backend_webgpu_device_context *) device->context;
3538
3539    auto * event_ctx      = new ggml_backend_webgpu_event_context();
3540    event_ctx->global_ctx = dev_ctx->webgpu_global_ctx;
3541
3542    auto * event   = new ggml_backend_event;
3543    event->device  = device;
3544    event->context = event_ctx;
3545    return event;
3546}
3547
3548static void ggml_backend_webgpu_device_event_free(ggml_backend_dev_t dev, ggml_backend_event_t event) {
3549    GGML_UNUSED(dev);
3550    delete static_cast<ggml_backend_webgpu_event_context *>(event->context);
3551    delete event;
3552}
3553
3554static void ggml_backend_webgpu_device_event_synchronize(ggml_backend_dev_t dev, ggml_backend_event_t event) {
3555    GGML_UNUSED(dev);
3556    ggml_backend_webgpu_event_context * event_ctx = (ggml_backend_webgpu_event_context *) event->context;
3557    if (!event_ctx->recorded) {
3558        return;
3559    }
3560    wgpu::WaitStatus status =
3561        event_ctx->global_ctx->instance.WaitAny(event_ctx->future, WEBGPU_RUNTIME_WAIT_TIMEOUT_NS);
3562    if (status == wgpu::WaitStatus::TimedOut) {
3563        GGML_ABORT("ggml_webgpu: event_synchronize timed out after %u ms\n", WEBGPU_RUNTIME_WAIT_TIMEOUT_MS);
3564    }
3565    event_ctx->recorded = false;
3566}
3567
3568static void ggml_backend_webgpu_event_record(ggml_backend_t backend, ggml_backend_event_t event) {
3569    ggml_backend_webgpu_context *       backend_ctx = (ggml_backend_webgpu_context *) backend->context;
3570    ggml_backend_webgpu_event_context * event_ctx   = (ggml_backend_webgpu_event_context *) event->context;
3571
3572    event_ctx->future = backend_ctx->webgpu_ctx->global_ctx->queue.OnSubmittedWorkDone(
3573        wgpu::CallbackMode::AllowSpontaneous, [](wgpu::QueueWorkDoneStatus, wgpu::StringView) {});
3574    event_ctx->recorded = true;
3575}
3576
3577static void ggml_backend_webgpu_event_wait(ggml_backend_t backend, ggml_backend_event_t event) {
3578    GGML_UNUSED(backend);
3579    ggml_backend_webgpu_device_event_synchronize(nullptr, event);
3580}
3581
3582static void ggml_backend_webgpu_set_tensor_async(ggml_backend_t backend,
3583                                                 ggml_tensor *  tensor,
3584                                                 const void *   data,
3585                                                 size_t         offset,
3586                                                 size_t         size) {
3587    GGML_UNUSED(backend);
3588    auto * buf_ctx      = (ggml_backend_webgpu_buffer_context *) tensor->buffer->context;
3589    size_t total_offset = ggml_webgpu_tensor_offset(tensor) + offset;
3590
3591    // Write aligned portion
3592    buf_ctx->global_ctx->queue.WriteBuffer(buf_ctx->buffer, total_offset, data, (size / 4) * 4);
3593
3594    if (size % 4 != 0) {
3595        // If size is not a multiple of 4, we need to memset the remaining bytes
3596        size_t remaining_size = size % 4;
3597
3598        // pack the remaining bytes into a uint32_t
3599        uint32_t val32 = 0;
3600
3601        for (size_t i = 0; i < remaining_size; i++) {
3602            ((uint8_t *) &val32)[i] = ((const uint8_t *) data)[size - remaining_size + i];
3603        }
3604        // memset the remaining bytes
3605        ggml_backend_webgpu_buffer_memset(buf_ctx->global_ctx, buf_ctx->buffer, val32,
3606                                          total_offset + (size - remaining_size), remaining_size);
3607    }
3608}
3609
3610static void ggml_backend_webgpu_synchronize(ggml_backend_t backend) {
3611    ggml_backend_webgpu_context * backend_ctx = (ggml_backend_webgpu_context *) backend->context;
3612    ggml_backend_webgpu_wait_queue(backend_ctx->webgpu_ctx->global_ctx);
3613}
3614
3615static ggml_backend_i ggml_backend_webgpu_i = {
3616    /* .get_name                = */ ggml_backend_webgpu_name,
3617    /* .free                    = */ ggml_backend_webgpu_free,
3618    /* .set_tensor_async        = */ ggml_backend_webgpu_set_tensor_async,
3619    /* .get_tensor_async        = */ NULL,
3620    /* .set_tensor_2d_async     = */ NULL,
3621    /* .get_tensor_2d_async     = */ NULL,
3622    /* .cpy_tensor_async        = */ NULL,
3623    /* .synchronize             = */ ggml_backend_webgpu_synchronize,
3624    /* .graph_plan_create       = */ NULL,
3625    /* .graph_plan_free         = */ NULL,
3626    /* .graph_plan_update       = */ NULL,
3627    /* .graph_plan_compute      = */ NULL,
3628    /* .graph_compute           = */ ggml_backend_webgpu_graph_compute,
3629    /* .event_record            = */ ggml_backend_webgpu_event_record,
3630    /* .event_wait              = */ ggml_backend_webgpu_event_wait,
3631    /* .graph_optimize          = */ NULL,
3632};
3633
3634/* End GGML Backend Interface */
3635
3636/* GGML Backend Buffer Interface */
3637
3638static void ggml_backend_webgpu_buffer_free_buffer(ggml_backend_buffer_t buffer) {
3639    ggml_backend_webgpu_buffer_context * ctx = static_cast<ggml_backend_webgpu_buffer_context *>(buffer->context);
3640    if (ctx != nullptr && ctx->buffer != nullptr) {
3641        ctx->buffer.Destroy();
3642        delete ctx;
3643    }
3644}
3645
3646// Returns the "fake" base pointer.
3647static void * ggml_backend_webgpu_buffer_get_base(ggml_backend_buffer_t buffer) {
3648    GGML_UNUSED(buffer);
3649    return webgpu_ptr_base;
3650}
3651
3652static void ggml_backend_webgpu_buffer_memset_tensor(ggml_backend_buffer_t buffer,
3653                                                     ggml_tensor *         tensor,
3654                                                     uint8_t               value,
3655                                                     size_t                offset,
3656                                                     size_t                size) {
3657    if (size == 0) {
3658        WEBGPU_LOG_DEBUG(
3659            "ggml_backend_webgpu_buffer_memset_tensor: size is zero, "
3660            "nothing to do.");
3661        return;
3662    }
3663
3664    WEBGPU_CPU_PROFILE_TOTAL_START(memset_tensor);
3665
3666    ggml_backend_webgpu_buffer_context * buf_ctx = (ggml_backend_webgpu_buffer_context *) buffer->context;
3667
3668    WEBGPU_LOG_DEBUG("ggml_backend_webgpu_buffer_memset_tensor(" << buf_ctx->label << ", " << tensor << ", " << value
3669                                                                 << ", " << offset << ", " << size << ")");
3670
3671    size_t total_offset = ggml_webgpu_tensor_offset(tensor) + offset;
3672
3673    // This is a trick to set all bytes of a u32 to the same 1 byte value.
3674    uint32_t val32 = (uint32_t) value * 0x01010101;
3675    ggml_backend_webgpu_buffer_memset(buf_ctx->global_ctx, buf_ctx->buffer, val32, total_offset, size);
3676    WEBGPU_CPU_PROFILE_TOTAL_END(memset_tensor, buf_ctx->global_ctx);
3677}
3678
3679static void ggml_backend_webgpu_buffer_set_tensor(ggml_backend_buffer_t buffer,
3680                                                  ggml_tensor *         tensor,
3681                                                  const void *          data,
3682                                                  size_t                offset,
3683                                                  size_t                size) {
3684    WEBGPU_CPU_PROFILE_TOTAL_START(set_tensor);
3685    ggml_backend_webgpu_buffer_context * buf_ctx = (ggml_backend_webgpu_buffer_context *) buffer->context;
3686
3687    WEBGPU_LOG_DEBUG("ggml_backend_webgpu_buffer_set_tensor(" << buf_ctx->label << ", " << tensor << ", " << data
3688                                                              << ", " << offset << ", " << size << ")");
3689
3690    size_t total_offset = ggml_webgpu_tensor_offset(tensor) + offset;
3691
3692    buf_ctx->global_ctx->queue.WriteBuffer(buf_ctx->buffer, total_offset, data, (size / 4) * 4);
3693
3694    if (size % 4 != 0) {
3695        // If size is not a multiple of 4, we need to memset the remaining bytes
3696        size_t remaining_size = size % 4;
3697
3698        // pack the remaining bytes into a uint32_t
3699        uint32_t val32 = 0;
3700
3701        for (size_t i = 0; i < remaining_size; i++) {
3702            ((uint8_t *) &val32)[i] = ((const uint8_t *) data)[size - remaining_size + i];
3703        }
3704        // memset the remaining bytes
3705        ggml_backend_webgpu_buffer_memset(buf_ctx->global_ctx, buf_ctx->buffer, val32,
3706                                          total_offset + (size - remaining_size), remaining_size);
3707    }
3708    WEBGPU_CPU_PROFILE_TOTAL_END(set_tensor, buf_ctx->global_ctx);
3709}
3710
3711static void ggml_backend_webgpu_buffer_get_tensor(ggml_backend_buffer_t buffer,
3712                                                  const ggml_tensor *   tensor,
3713                                                  void *                data,
3714                                                  size_t                offset,
3715                                                  size_t                size) {
3716    WEBGPU_CPU_PROFILE_TOTAL_START(get_tensor);
3717    ggml_backend_webgpu_buffer_context * buf_ctx = (ggml_backend_webgpu_buffer_context *) buffer->context;
3718    WEBGPU_LOG_DEBUG("ggml_backend_webgpu_buffer_get_tensor(" << buf_ctx->label << ", " << tensor << ", " << data
3719                                                              << ", " << offset << ", " << size << ")");
3720    wgpu::Device device = buf_ctx->global_ctx->device;
3721
3722    size_t total_offset = ggml_webgpu_tensor_offset(tensor) + offset;
3723
3724    size_t local_offset = total_offset % 4;
3725    if (local_offset != 0) {
3726        // If offset is not a multiple of 4, we need to round it down to the previous
3727        // multiple of 4
3728        total_offset -= local_offset;
3729    }
3730
3731    size_t final_size = size + local_offset;
3732    if (final_size % 4 != 0) {
3733        // If size is not a multiple of 4, we need to round it up to the next
3734        // multiple of 4
3735        final_size += 4 - (final_size % 4);
3736    }
3737
3738    std::lock_guard<std::recursive_mutex> lock(buf_ctx->global_ctx->mutex);
3739
3740    if (buf_ctx->global_ctx->get_tensor_staging_buf == nullptr ||
3741        buf_ctx->global_ctx->get_tensor_staging_buf.GetSize() < final_size) {
3742        // Create a new staging buffer if it doesn't exist or is too small
3743        if (buf_ctx->global_ctx->get_tensor_staging_buf) {
3744            buf_ctx->global_ctx->get_tensor_staging_buf.Destroy();
3745        }
3746        ggml_webgpu_create_buffer(device, buf_ctx->global_ctx->get_tensor_staging_buf, final_size,
3747                                  wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapRead, "get_tensor_staging_buf");
3748    }
3749
3750    // Copy the data from the buffer to the staging buffer
3751    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
3752    encoder.CopyBufferToBuffer(buf_ctx->buffer, total_offset, buf_ctx->global_ctx->get_tensor_staging_buf, 0,
3753                               final_size);
3754    wgpu::CommandBuffer commands = encoder.Finish();
3755
3756    // Submit the command buffer to the queue
3757    buf_ctx->global_ctx->queue.Submit(1, &commands);
3758
3759    // Map the staging buffer to read the data
3760    ggml_backend_webgpu_map_buffer(buf_ctx->global_ctx, buf_ctx->global_ctx->get_tensor_staging_buf,
3761                                   wgpu::MapMode::Read, 0, final_size);
3762    // Must specify size here since the staging buffer might be larger than the tensor size
3763    const void * mapped_range = buf_ctx->global_ctx->get_tensor_staging_buf.GetConstMappedRange(0, final_size);
3764
3765    // Copy the data from the mapped range to the output buffer
3766    std::memcpy(data, (const void *) ((const char *) mapped_range + local_offset), size);
3767    buf_ctx->global_ctx->get_tensor_staging_buf.Unmap();
3768    WEBGPU_CPU_PROFILE_TOTAL_END(get_tensor, buf_ctx->global_ctx);
3769}
3770
3771static void ggml_backend_webgpu_buffer_clear(ggml_backend_buffer_t buffer, uint8_t value) {
3772    WEBGPU_LOG_DEBUG("ggml_backend_webgpu_buffer_clear(" << buffer << ", " << (uint32_t) value << ")");
3773    WEBGPU_CPU_PROFILE_TOTAL_START(clear);
3774    ggml_backend_webgpu_buffer_context * buf_ctx = (ggml_backend_webgpu_buffer_context *) buffer->context;
3775    ggml_backend_webgpu_buffer_memset(buf_ctx->global_ctx, buf_ctx->buffer, value, 0, buffer->size);
3776    WEBGPU_CPU_PROFILE_TOTAL_END(clear, buf_ctx->global_ctx);
3777}
3778
3779static ggml_backend_buffer_i ggml_backend_webgpu_buffer_interface = {
3780    /* .free_buffer     = */ ggml_backend_webgpu_buffer_free_buffer,
3781    /* .get_base        = */ ggml_backend_webgpu_buffer_get_base,
3782    /* .init_tensor     = */ NULL,  // TODO: optional, needed?
3783    /* .memset_tensor   = */ ggml_backend_webgpu_buffer_memset_tensor,
3784    /* .set_tensor      = */ ggml_backend_webgpu_buffer_set_tensor,
3785    /* .get_tensor      = */ ggml_backend_webgpu_buffer_get_tensor,
3786    /* .set_tensor_2d   = */ NULL,
3787    /* .get_tensor_2d   = */ NULL,
3788    /* .cpy_tensor      = */ NULL,  // TODO: optional, implement this
3789    /* .clear           = */ ggml_backend_webgpu_buffer_clear,
3790    /* .reset           = */ NULL,  // TODO: optional, think it coordinates with
3791                                    // .init_tensor
3792};
3793
3794/* End GGML Backend Buffer Interface */
3795
3796/* GGML Backend Buffer Type Interface */
3797
3798static const char * ggml_backend_webgpu_buffer_type_get_name(ggml_backend_buffer_type_t buft) {
3799    ggml_backend_webgpu_device_context * ctx = static_cast<ggml_backend_webgpu_device_context *>(buft->device->context);
3800    return ctx->device_name.c_str();
3801}
3802
3803static ggml_backend_buffer_t ggml_backend_webgpu_buffer_type_alloc_buffer(ggml_backend_buffer_type_t buft,
3804                                                                          size_t                     size) {
3805    static std::atomic<int> buffer_count;
3806    int                     buffer_id = buffer_count++;
3807    std::string             buf_name  = "tensor_buf" + std::to_string(buffer_id);
3808    WEBGPU_LOG_DEBUG("ggml_backend_webgpu_buffer_type_alloc_buffer_" << buffer_id << ": " << size << " bytes");
3809
3810    ggml_backend_webgpu_device_context * ctx = static_cast<ggml_backend_webgpu_device_context *>(buft->device->context);
3811    wgpu::Buffer                         buf;
3812    ggml_webgpu_create_buffer(ctx->webgpu_global_ctx->device, buf, ROUNDUP_POW2(size, WEBGPU_STORAGE_BUF_BINDING_MULT),
3813                              wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst,
3814                              buf_name.c_str());
3815
3816    ggml_backend_webgpu_buffer_context * buf_ctx =
3817        new ggml_backend_webgpu_buffer_context(buf, buf_name, ctx->webgpu_global_ctx);
3818
3819    return ggml_backend_buffer_init(buft, ggml_backend_webgpu_buffer_interface, buf_ctx, size);
3820}
3821
3822static size_t ggml_backend_webgpu_buffer_type_get_alignment(ggml_backend_buffer_type_t buft) {
3823    ggml_backend_webgpu_device_context * dev_ctx =
3824        static_cast<ggml_backend_webgpu_device_context *>(buft->device->context);
3825    return dev_ctx->webgpu_global_ctx->capabilities.limits.minStorageBufferOffsetAlignment;
3826}
3827
3828// maxBufferSize might be larger, but you can't bind more than
3829// maxStorageBufferBindingSize to a single binding.
3830static size_t ggml_backend_webgpu_buffer_type_get_max_size(ggml_backend_buffer_type_t buft) {
3831    ggml_backend_webgpu_device_context * dev_ctx =
3832        static_cast<ggml_backend_webgpu_device_context *>(buft->device->context);
3833    return dev_ctx->webgpu_global_ctx->capabilities.limits.maxStorageBufferBindingSize;
3834}
3835
3836static size_t ggml_backend_webgpu_buffer_type_get_alloc_size(ggml_backend_buffer_type_t buft,
3837                                                             const ggml_tensor *        tensor) {
3838    ggml_backend_webgpu_device_context * ctx = static_cast<ggml_backend_webgpu_device_context *>(buft->device->context);
3839    size_t                               res = ggml_nbytes(tensor);
3840    switch (tensor->op) {
3841        case GGML_OP_ARGSORT:
3842            res = ROUNDUP_POW2(res * 2 + ctx->webgpu_global_ctx->capabilities.limits.minStorageBufferOffsetAlignment,
3843                               WEBGPU_STORAGE_BUF_BINDING_MULT);
3844            break;
3845        case GGML_OP_TOP_K:
3846            {
3847                const ggml_tensor * src0 = tensor->src[0];
3848                if (src0) {
3849                    const size_t full = sizeof(int32_t) * ggml_nelements(src0);
3850                    res               = ROUNDUP_POW2(
3851                        full * 2 + ctx->webgpu_global_ctx->capabilities.limits.minStorageBufferOffsetAlignment,
3852                        WEBGPU_STORAGE_BUF_BINDING_MULT);
3853                }
3854            }
3855            break;
3856        case GGML_OP_FLASH_ATTN_EXT:
3857            {
3858                const ggml_tensor * Q            = tensor->src[0];
3859                const ggml_tensor * K            = tensor->src[1];
3860                const ggml_tensor * V            = tensor->src[2];
3861                const ggml_tensor * mask         = tensor->src[3];
3862                const auto &        capabilities = ctx->webgpu_global_ctx->capabilities;
3863                if (ggml_webgpu_flash_attn_use_vec_path(ctx->webgpu_global_ctx, Q, K, V)) {
3864                    const bool kv_direct =
3865                        ggml_webgpu_flash_attn_k_direct(Q, K, GGML_WEBGPU_FLASH_ATTN_TILE_KV_VEC_WIDTH) ||
3866                        ggml_webgpu_flash_attn_v_direct(Q, V, GGML_WEBGPU_FLASH_ATTN_TILE_KV_VEC_WIDTH);
3867                    const uint32_t kv_tile = ggml_webgpu_flash_attn_get_vec_kv_tile(
3868                        capabilities.limits.maxComputeWorkgroupStorageSize, (uint32_t) Q->ne[0], (uint32_t) V->ne[0],
3869                        mask != nullptr, kv_direct);
3870
3871                    const uint32_t vec_nwg_cap = capabilities.min_subgroup_size;
3872                    uint32_t       nwg = ggml_webgpu_flash_attn_vec_nwg(vec_nwg_cap, kv_tile, (uint32_t) K->ne[1]);
3873
3874                    const size_t   align = capabilities.limits.minStorageBufferOffsetAlignment;
3875                    const uint64_t nrows = (uint64_t) Q->ne[1] * Q->ne[2] * Q->ne[3];
3876                    if (nwg > 1u) {
3877                        const uint64_t tmp_data_elems  = nrows * (uint64_t) V->ne[0] * nwg;
3878                        const uint64_t tmp_stats_elems = nrows * 2u * nwg;
3879                        const size_t   tmp_size_bytes = ROUNDUP_POW2((tmp_data_elems + tmp_stats_elems) * sizeof(float),
3880                                                                     WEBGPU_STORAGE_BUF_BINDING_MULT);
3881                        res += tmp_size_bytes + align;
3882                    } else {
3883                        res += WEBGPU_STORAGE_BUF_BINDING_MULT + align;
3884                    }
3885                    if (mask != nullptr) {
3886                        const uint32_t blk_nblk0       = CEIL_DIV((uint32_t) K->ne[1], kv_tile);
3887                        const uint32_t blk_nblk1       = CEIL_DIV((uint32_t) Q->ne[1], 1u);
3888                        const uint32_t stride_mask3    = (uint32_t) (mask->nb[3] / ggml_type_size(mask->type));
3889                        const uint32_t blk_batch_count = stride_mask3 > 0 ? (uint32_t) Q->ne[3] : 1u;
3890                        const uint64_t blk_elems       = (uint64_t) blk_nblk0 * blk_nblk1 * blk_batch_count;
3891                        const size_t   blk_size_bytes =
3892                            ROUNDUP_POW2(blk_elems * sizeof(uint32_t), WEBGPU_STORAGE_BUF_BINDING_MULT);
3893                        res += blk_size_bytes + align;
3894                    }
3895                    res = ROUNDUP_POW2(res, WEBGPU_STORAGE_BUF_BINDING_MULT);
3896                }
3897            }
3898            break;
3899        case GGML_OP_MUL_MAT:
3900            {
3901                const ggml_tensor * src0 = tensor->src[0];
3902                const ggml_tensor * src1 = tensor->src[1];
3903                bool                use_mmvq =
3904                    ggml_webgpu_can_use_mmvq(src0, src1, ctx->webgpu_global_ctx->capabilities.supports_dot_product,
3905                                             ctx->webgpu_global_ctx->vendor);
3906                if (use_mmvq) {
3907                    const size_t q8_src1_size = src1->ne[3] * src1->ne[2] * src1->ne[1] *
3908                                                (36 /* sizeof(q8_1) */ * (src1->ne[0] / /* block_size */ 32));
3909                    res = ROUNDUP_POW2(res + q8_src1_size +
3910                                           ctx->webgpu_global_ctx->capabilities.limits.minStorageBufferOffsetAlignment,
3911                                       WEBGPU_STORAGE_BUF_BINDING_MULT);
3912                }
3913            }
3914            break;
3915        case GGML_OP_MUL_MAT_ID:
3916            {
3917                const ggml_tensor * src0 = tensor->src[0];
3918                const ggml_tensor * src1 = tensor->src[1];
3919                if (src0 && src1) {
3920                    const size_t gathered_size = sizeof(uint32_t) * tensor->src[0]->ne[2] * tensor->src[1]->ne[2];
3921                    const size_t gathered_count_ids_size = sizeof(uint32_t) * tensor->src[0]->ne[2];
3922                    res                                  = ROUNDUP_POW2(
3923                        res + gathered_size * 2 + gathered_count_ids_size +
3924                            ctx->webgpu_global_ctx->capabilities.limits.minStorageBufferOffsetAlignment * 3,
3925                        WEBGPU_STORAGE_BUF_BINDING_MULT);
3926                }
3927            }
3928            break;
3929        default:
3930            break;
3931    }
3932    return res;
3933}
3934
3935/* End GGML Backend Buffer Type Interface */
3936
3937/* GGML Backend Device Interface */
3938
3939static const char * ggml_backend_webgpu_device_get_name(ggml_backend_dev_t dev) {
3940    ggml_backend_webgpu_device_context * ctx = static_cast<ggml_backend_webgpu_device_context *>(dev->context);
3941    return ctx->device_name.c_str();
3942}
3943
3944static const char * ggml_backend_webgpu_device_get_description(ggml_backend_dev_t dev) {
3945    ggml_backend_webgpu_device_context * ctx = static_cast<ggml_backend_webgpu_device_context *>(dev->context);
3946    return ctx->device_desc.c_str();
3947}
3948
3949static void ggml_backend_webgpu_device_get_memory(ggml_backend_dev_t dev, size_t * free, size_t * total) {
3950    ggml_backend_webgpu_device_context * ctx = static_cast<ggml_backend_webgpu_device_context *>(dev->context);
3951    // TODO: for now, return maxBufferSize as both free and total memory
3952    // Track https://github.com/gpuweb/gpuweb/issues/5505 for updates.
3953    uint64_t                             max_buffer_size = ctx->webgpu_global_ctx->capabilities.limits.maxBufferSize;
3954    // If we're on a 32-bit system, clamp to UINTPTR_MAX
3955#if UINTPTR_MAX < UINT64_MAX
3956    uint64_t max_ptr_size = static_cast<uint64_t>(UINTPTR_MAX);
3957    if (max_buffer_size > max_ptr_size) {
3958        max_buffer_size = max_ptr_size;
3959    }
3960#endif
3961    *free  = static_cast<size_t>(max_buffer_size);
3962    *total = static_cast<size_t>(max_buffer_size);
3963}
3964
3965static enum ggml_backend_dev_type ggml_backend_webgpu_device_get_type(ggml_backend_dev_t dev) {
3966    GGML_UNUSED(dev);
3967    return GGML_BACKEND_DEVICE_TYPE_GPU;
3968}
3969
3970static void ggml_backend_webgpu_device_get_props(ggml_backend_dev_t dev, struct ggml_backend_dev_props * props) {
3971    props->name        = ggml_backend_webgpu_device_get_name(dev);
3972    props->description = ggml_backend_webgpu_device_get_description(dev);
3973    props->type        = ggml_backend_webgpu_device_get_type(dev);
3974    ggml_backend_webgpu_device_get_memory(dev, &props->memory_free, &props->memory_total);
3975    props->caps = {
3976        /* .async                 = */ false,
3977        /* .host_buffer           = */ false,
3978        /* .buffer_from_host_ptr  = */ false,
3979        /* .events                = */ false,
3980        /* .mmap_support          = */ true,
3981    };
3982}
3983
3984static ggml_guid_t ggml_backend_webgpu_guid(void) {
3985    static ggml_guid guid = { 0x67, 0xc7, 0xa4, 0xb1, 0x78, 0x74, 0x4f, 0x51,
3986                              0x9d, 0x65, 0x44, 0x6d, 0xe4, 0x1b, 0x82, 0x9a };
3987    return &guid;
3988}
3989
3990static void ggml_webgpu_init_memset_pipeline(webgpu_global_context & ctx) {
3991    // we use the maximum workgroup size for the memset pipeline
3992    size_t max_threads = ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup *
3993                         ctx->capabilities.limits.maxComputeWorkgroupsPerDimension;
3994    // Size the bytes_per_thread so that the largest buffer size can be handled
3995    ctx->capabilities.memset_bytes_per_thread =
3996        CEIL_DIV(ctx->capabilities.limits.maxStorageBufferBindingSize, max_threads);
3997    std::vector<wgpu::ConstantEntry> constants(2);
3998    constants[0].key     = "wg_size";
3999    constants[0].value   = ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
4000    constants[1].key     = "bytes_per_thread";
4001    constants[1].value   = ctx->capabilities.memset_bytes_per_thread;
4002    ctx->memset_pipeline = ggml_webgpu_create_pipeline(ctx->device, wgsl_memset, "memset", constants);
4003}
4004
4005static void ggml_backend_webgpu_request_adapter(wgpu::Instance & instance, wgpu::Adapter & adapter) {
4006    wgpu::RequestAdapterOptions options = {};
4007
4008#ifndef __EMSCRIPTEN__
4009    // TODO: track need for these toggles: https://issues.chromium.org/issues/42251215
4010    const char * const          adapterEnabledToggles[] = { "vulkan_enable_f16_on_nvidia", "use_vulkan_memory_model" };
4011    wgpu::DawnTogglesDescriptor adapterTogglesDesc;
4012    adapterTogglesDesc.enabledToggles     = adapterEnabledToggles;
4013    adapterTogglesDesc.enabledToggleCount = 2;
4014    options.nextInChain                   = &adapterTogglesDesc;
4015#endif
4016
4017    instance.WaitAny(
4018        instance.RequestAdapter(
4019            &options, wgpu::CallbackMode::AllowSpontaneous,
4020            [&adapter](wgpu::RequestAdapterStatus status, wgpu::Adapter _adapter, wgpu::StringView message) {
4021                if (status != wgpu::RequestAdapterStatus::Success) {
4022                    GGML_LOG_ERROR("ggml_webgpu: Failed to get an adapter: %s\n", std::string(message).c_str());
4023                    return;
4024                }
4025                adapter = std::move(_adapter);
4026            }),
4027        UINT64_MAX);
4028}
4029
4030static void create_webgpu_device(ggml_backend_webgpu_reg_context * ctx) {
4031    ggml_backend_webgpu_request_adapter(ctx->webgpu_global_ctx->instance, ctx->webgpu_global_ctx->adapter);
4032    GGML_ASSERT(ctx->webgpu_global_ctx->adapter != nullptr);
4033
4034    ctx->webgpu_global_ctx->adapter.GetLimits(&ctx->webgpu_global_ctx->capabilities.limits);
4035
4036    wgpu::AdapterInfo info{};
4037#ifndef __EMSCRIPTEN__
4038    wgpu::AdapterPropertiesSubgroupMatrixConfigs subgroup_matrix_configs{};
4039    if (ctx->webgpu_global_ctx->adapter.HasFeature(wgpu::FeatureName::ChromiumExperimentalSubgroupMatrix)) {
4040        info.nextInChain = &subgroup_matrix_configs;
4041    }
4042#endif
4043    ctx->webgpu_global_ctx->adapter.GetInfo(&info);
4044    ctx->webgpu_global_ctx->command_submit_batch_size = ggml_backend_webgpu_get_command_submit_batch_size();
4045    ctx->webgpu_global_ctx->max_inflight_batches      = ggml_backend_webgpu_get_max_inflight_batches();
4046    ctx->webgpu_global_ctx->vendor                    = info.vendor;
4047    ctx->webgpu_global_ctx->capabilities.supports_subgroups =
4048        ctx->webgpu_global_ctx->adapter.HasFeature(wgpu::FeatureName::Subgroups);
4049    // for dot4I8packed
4050    ctx->webgpu_global_ctx->capabilities.supports_dot_product = ctx->webgpu_global_ctx->instance.HasWGSLLanguageFeature(
4051        wgpu::WGSLLanguageFeatureName::Packed4x8IntegerDotProduct);
4052
4053    bool valid_subgroup_matrix_config = false;
4054#ifndef __EMSCRIPTEN__
4055    // Accept f16 subgroup matrix configurations (square or non-square).
4056    // NVIDIA GPUs typically report square configs (e.g. 16x16x16),
4057    // while Intel Xe2 GPUs report non-square configs (e.g. 8x16x16).
4058    // The shaders are already parameterized to handle any M/N/K dimensions.
4059    if (ctx->webgpu_global_ctx->adapter.HasFeature(wgpu::FeatureName::ChromiumExperimentalSubgroupMatrix)) {
4060        for (size_t i = 0; i < subgroup_matrix_configs.configCount; i++) {
4061            const wgpu::SubgroupMatrixConfig config = subgroup_matrix_configs.configs[i];
4062            if (config.componentType == wgpu::SubgroupMatrixComponentType::F16 &&
4063                config.resultComponentType == wgpu::SubgroupMatrixComponentType::F16) {
4064                ctx->webgpu_global_ctx->capabilities.sg_mat_m = config.M;
4065                ctx->webgpu_global_ctx->capabilities.sg_mat_n = config.N;
4066                ctx->webgpu_global_ctx->capabilities.sg_mat_k = config.K;
4067                valid_subgroup_matrix_config                  = true;
4068                break;
4069            }
4070        }
4071    }
4072#endif
4073    ctx->webgpu_global_ctx->capabilities.supports_subgroup_matrix = valid_subgroup_matrix_config;
4074
4075    // Runtime subgroup size can be any supported size in this range. Shaders
4076    // that allocate per-lane register arrays must size them for the minimum.
4077    ctx->webgpu_global_ctx->capabilities.min_subgroup_size = info.subgroupMinSize;
4078    ctx->webgpu_global_ctx->capabilities.max_subgroup_size = info.subgroupMaxSize;
4079    // Initialize device
4080    std::vector<wgpu::FeatureName> required_features       = { wgpu::FeatureName::ShaderF16 };
4081
4082#ifndef __EMSCRIPTEN__
4083    required_features.push_back(wgpu::FeatureName::ImplicitDeviceSynchronization);
4084    if (ctx->webgpu_global_ctx->capabilities.supports_subgroup_matrix) {
4085        required_features.push_back(wgpu::FeatureName::ChromiumExperimentalSubgroupMatrix);
4086    }
4087#endif
4088
4089    if (ctx->webgpu_global_ctx->capabilities.supports_subgroups) {
4090        required_features.push_back(wgpu::FeatureName::Subgroups);
4091    }
4092
4093#ifdef GGML_WEBGPU_GPU_PROFILE
4094    required_features.push_back(wgpu::FeatureName::TimestampQuery);
4095#endif
4096
4097    wgpu::DeviceDescriptor dev_desc;
4098    dev_desc.requiredLimits       = &ctx->webgpu_global_ctx->capabilities.limits;
4099    dev_desc.requiredFeatures     = required_features.data();
4100    dev_desc.requiredFeatureCount = required_features.size();
4101    dev_desc.SetDeviceLostCallback(
4102        wgpu::CallbackMode::AllowSpontaneous,
4103        [](const wgpu::Device & device, wgpu::DeviceLostReason reason, wgpu::StringView message) {
4104            if (reason == wgpu::DeviceLostReason::Destroyed) {
4105                return;
4106            }
4107            GGML_UNUSED(device);
4108            GGML_LOG_ERROR("ggml_webgpu: Device lost! Reason: %d, Message: %s\n", static_cast<int>(reason),
4109                           std::string(message).c_str());
4110        });
4111    dev_desc.SetUncapturedErrorCallback(
4112        [](const wgpu::Device & device, wgpu::ErrorType reason, wgpu::StringView message) {
4113            GGML_UNUSED(device);
4114            GGML_ABORT("ggml_webgpu: Device error! Reason: %d, Message: %s\n", static_cast<int>(reason),
4115                       std::string(message).c_str());
4116        });
4117
4118#ifndef __EMSCRIPTEN__
4119    // Enable Dawn-specific toggles to increase native performance
4120    // TODO: Maybe WebGPU needs a "fast" mode where you can request compilers skip adding checks like these,
4121    //       only for native performance?
4122    const char * const          deviceEnabledToggles[]  = { "disable_robustness", "disable_workgroup_init",
4123                                                            "disable_polyfills_on_integer_div_and_mod" };
4124    const char * const          deviceDisabledToggles[] = { "timestamp_quantization" };
4125    wgpu::DawnTogglesDescriptor deviceTogglesDesc;
4126    deviceTogglesDesc.enabledToggles      = deviceEnabledToggles;
4127    deviceTogglesDesc.enabledToggleCount  = 3;
4128    deviceTogglesDesc.disabledToggles     = deviceDisabledToggles;
4129    deviceTogglesDesc.disabledToggleCount = 1;
4130
4131    dev_desc.nextInChain = &deviceTogglesDesc;
4132#endif
4133
4134    ctx->webgpu_global_ctx->instance.WaitAny(
4135        ctx->webgpu_global_ctx->adapter.RequestDevice(
4136            &dev_desc, wgpu::CallbackMode::AllowSpontaneous,
4137            [ctx](wgpu::RequestDeviceStatus status, wgpu::Device device, wgpu::StringView message) {
4138                if (status != wgpu::RequestDeviceStatus::Success) {
4139                    GGML_LOG_ERROR("ggml_webgpu: Failed to get a device: %s\n", std::string(message).c_str());
4140                    return;
4141                }
4142                ctx->webgpu_global_ctx->device = std::move(device);
4143            }),
4144        UINT64_MAX);
4145    GGML_ASSERT(ctx->webgpu_global_ctx->device != nullptr);
4146
4147    ggml_webgpu_init_memset_pipeline(ctx->webgpu_global_ctx);
4148    ggml_webgpu_create_buffer(ctx->webgpu_global_ctx->device, ctx->webgpu_global_ctx->memset_params_buf,
4149                              WEBGPU_PARAMS_BUF_SIZE_BYTES, wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Uniform,
4150                              "memset_params_buf");
4151    ctx->webgpu_global_ctx->queue = ctx->webgpu_global_ctx->device.GetQueue();
4152
4153    GGML_LOG_INFO(
4154        "ggml_webgpu: adapter_info: vendor_id: %u | vendor: %s | architecture: %s | device_id: %u | name: %s | "
4155        "device_desc: %s\n",
4156        info.vendorID, std::string(info.vendor).c_str(), std::string(info.architecture).c_str(), info.deviceID,
4157        std::string(info.device).c_str(), std::string(info.description).c_str());
4158}
4159
4160static webgpu_context initialize_webgpu_context(ggml_backend_dev_t dev) {
4161    ggml_backend_webgpu_device_context * dev_ctx    = (ggml_backend_webgpu_device_context *) dev->context;
4162    webgpu_context                       webgpu_ctx = std::make_shared<webgpu_context_struct>();
4163    webgpu_ctx->global_ctx                          = dev_ctx->webgpu_global_ctx;
4164    webgpu_ctx->shader_lib = std::make_unique<ggml_webgpu_shader_lib>(dev_ctx->webgpu_global_ctx->device);
4165    webgpu_ctx->param_arena.init(
4166        webgpu_ctx->global_ctx->device, WEBGPU_PARAMS_BUF_SIZE_BYTES,
4167        webgpu_ctx->global_ctx->command_submit_batch_size + WEBGPU_NUM_PARAM_SLOT_SAFETY_MARGIN,
4168        webgpu_ctx->global_ctx->capabilities.limits.minUniformBufferOffsetAlignment);
4169    ggml_webgpu_create_buffer(webgpu_ctx->global_ctx->device, webgpu_ctx->set_rows_dev_error_buf,
4170                              WEBGPU_SET_ROWS_ERROR_BUF_SIZE_BYTES,
4171                              wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc, "set_rows_dev_error_buf");
4172    ggml_webgpu_create_buffer(webgpu_ctx->global_ctx->device, webgpu_ctx->set_rows_host_error_buf,
4173                              WEBGPU_SET_ROWS_ERROR_BUF_SIZE_BYTES,
4174                              wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapRead, "set_rows_host_error_buf");
4175
4176#ifdef GGML_WEBGPU_GPU_PROFILE
4177    webgpu_ctx->batch_compute_passes = false;
4178    ggml_webgpu_create_buffer(
4179        webgpu_ctx->global_ctx->device, webgpu_ctx->profile_timestamp_dev_buf, WEBGPU_TIMESTAMP_QUERY_BUF_SIZE_BYTES,
4180        wgpu::BufferUsage::QueryResolve | wgpu::BufferUsage::CopySrc, "profile_timestamp_dev_buf");
4181    ggml_webgpu_create_buffer(webgpu_ctx->global_ctx->device, webgpu_ctx->profile_timestamp_host_buf,
4182                              WEBGPU_TIMESTAMP_QUERY_BUF_SIZE_BYTES,
4183                              wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapRead, "profile_timestamp_host_buf");
4184    wgpu::QuerySetDescriptor query_set_desc = {};
4185    query_set_desc.type                     = wgpu::QueryType::Timestamp;
4186    query_set_desc.count                    = WEBGPU_MAX_PROFILE_QUERY_COUNT;
4187    webgpu_ctx->profile_timestamp_query_set = webgpu_ctx->global_ctx->device.CreateQuerySet(&query_set_desc);
4188#endif
4189
4190#ifdef GGML_WEBGPU_DEBUG
4191    // Initialize debug buffers
4192    ggml_webgpu_create_buffer(webgpu_ctx->global_ctx->device, webgpu_ctx->global_ctx->debug_host_buf,
4193                              WEBGPU_DEBUG_BUF_ELEMS * sizeof(uint32_t),
4194                              wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapRead, "debug_host_buf");
4195    ggml_webgpu_create_buffer(webgpu_ctx->global_ctx->device, webgpu_ctx->global_ctx->debug_dev_buf,
4196                              WEBGPU_DEBUG_BUF_ELEMS * sizeof(uint32_t),
4197                              wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc, "debug_dev_buf");
4198#endif
4199    return webgpu_ctx;
4200}
4201
4202static ggml_backend_t ggml_backend_webgpu_backend_init(ggml_backend_dev_t dev, const char * params) {
4203    GGML_UNUSED(params);
4204
4205    WEBGPU_LOG_DEBUG("ggml_backend_webgpu_backend_init()");
4206
4207    ggml_backend_webgpu_device_context * dev_ctx = static_cast<ggml_backend_webgpu_device_context *>(dev->context);
4208
4209    auto * backend_ctx      = new ggml_backend_webgpu_context();
4210    backend_ctx->name       = GGML_WEBGPU_NAME + std::string(": ") + dev_ctx->device_name;
4211    backend_ctx->webgpu_ctx = initialize_webgpu_context(dev);
4212
4213    // See GGML Backend Interface section
4214    auto * backend = new ggml_backend();
4215    *backend       = {
4216        /* .guid      = */ ggml_backend_webgpu_guid(),
4217        /* .interface = */ ggml_backend_webgpu_i,
4218        /* .device    = */ dev,
4219        /* .context   = */ backend_ctx,
4220    };
4221    return backend;
4222}
4223
4224static ggml_backend_buffer_type_t ggml_backend_webgpu_device_get_buffer_type(ggml_backend_dev_t dev) {
4225    // See GGML Backend Buffer Type Interface section
4226
4227    static struct ggml_backend_buffer_type ggml_backend_webgpu_buffer_type = {
4228        /* .iface = */ {
4229                        /* .get_name       = */ ggml_backend_webgpu_buffer_type_get_name,
4230                        /* .alloc_buffer   = */ ggml_backend_webgpu_buffer_type_alloc_buffer,
4231                        /* .get_alignment  = */ ggml_backend_webgpu_buffer_type_get_alignment,
4232                        /* .get_max_size   = */ ggml_backend_webgpu_buffer_type_get_max_size,
4233                        /* .get_alloc_size = */ ggml_backend_webgpu_buffer_type_get_alloc_size,
4234                        /* .is_host        = */ NULL,  // defaults to false
4235        },
4236        /* .device  = */
4237        dev,
4238        /* .context = */ NULL
4239    };
4240
4241    return &ggml_backend_webgpu_buffer_type;
4242}
4243
4244static bool ggml_backend_webgpu_device_supports_buft(ggml_backend_dev_t dev, ggml_backend_buffer_type_t buft) {
4245    GGML_UNUSED(dev);
4246    return buft->iface.get_name == ggml_backend_webgpu_buffer_type_get_name;
4247}
4248
4249static bool ggml_webgpu_supported_qtype(ggml_type type) {
4250    switch (type) {
4251        case GGML_TYPE_Q1_0:
4252        case GGML_TYPE_Q4_0:
4253        case GGML_TYPE_Q4_1:
4254        case GGML_TYPE_Q5_0:
4255        case GGML_TYPE_Q5_1:
4256        case GGML_TYPE_Q8_0:
4257        case GGML_TYPE_Q2_K:
4258        case GGML_TYPE_Q3_K:
4259        case GGML_TYPE_Q4_K:
4260        case GGML_TYPE_Q5_K:
4261        case GGML_TYPE_Q6_K:
4262        case GGML_TYPE_IQ2_XXS:
4263        case GGML_TYPE_IQ2_XS:
4264        case GGML_TYPE_IQ2_S:
4265        case GGML_TYPE_IQ3_XXS:
4266        case GGML_TYPE_IQ3_S:
4267        case GGML_TYPE_IQ1_S:
4268        case GGML_TYPE_IQ1_M:
4269        case GGML_TYPE_IQ4_NL:
4270        case GGML_TYPE_IQ4_XS:
4271        case GGML_TYPE_MXFP4:
4272        case GGML_TYPE_NVFP4:
4273            return true;
4274        default:
4275            return false;
4276    }
4277}
4278
4279static bool ggml_backend_webgpu_device_supports_op(ggml_backend_dev_t dev, const ggml_tensor * op) {
4280    ggml_backend_webgpu_device_context * ctx = static_cast<ggml_backend_webgpu_device_context *>(dev->context);
4281
4282    ggml_tensor * src0 = op->src[0];
4283    ggml_tensor * src1 = op->src[1];
4284    ggml_tensor * src2 = op->src[2];
4285
4286    // on smaller devices (or CI), tensors may be larger than the max storage buffer size
4287    if (ggml_nbytes(op) > ctx->webgpu_global_ctx->capabilities.limits.maxStorageBufferBindingSize ||
4288        (src0 != nullptr &&
4289         ggml_nbytes(src0) > ctx->webgpu_global_ctx->capabilities.limits.maxStorageBufferBindingSize) ||
4290        (src1 != nullptr &&
4291         ggml_nbytes(src1) > ctx->webgpu_global_ctx->capabilities.limits.maxStorageBufferBindingSize)) {
4292        return false;
4293    }
4294
4295    bool supports_op = false;
4296    switch (op->op) {
4297        case GGML_OP_NONE:
4298        case GGML_OP_VIEW:
4299        case GGML_OP_PERMUTE:
4300        case GGML_OP_TRANSPOSE:
4301        case GGML_OP_RESHAPE:
4302            supports_op = true;
4303            break;
4304        case GGML_OP_ADD:
4305        case GGML_OP_SUB:
4306        case GGML_OP_MUL:
4307        case GGML_OP_DIV:
4308            supports_op = (op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_F16) && (src0->type == op->type) &&
4309                          (src1->type == op->type);
4310            break;
4311        case GGML_OP_ADD_ID:
4312            supports_op = src0->type == GGML_TYPE_F32;
4313            break;
4314        case GGML_OP_CONCAT:
4315            supports_op = (src0->type == GGML_TYPE_F32 || src0->type == GGML_TYPE_I32);
4316            break;
4317        case GGML_OP_REPEAT:
4318            supports_op = (src0->type == GGML_TYPE_F32 || src0->type == GGML_TYPE_F16 || src0->type == GGML_TYPE_I32 ||
4319                           src0->type == GGML_TYPE_I16);
4320            break;
4321        case GGML_OP_CPY:
4322        case GGML_OP_CONT:
4323            supports_op = (op->type == GGML_TYPE_F16 || op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_I32) &&
4324                          (src0->type == GGML_TYPE_F16 || src0->type == GGML_TYPE_F32 || src0->type == GGML_TYPE_I32);
4325            break;
4326        case GGML_OP_SET:
4327            supports_op = src0->type == src1->type && src0->type == op->type &&
4328                          (op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_I32);
4329            break;
4330        case GGML_OP_SET_ROWS:
4331            supports_op = ((op->type == GGML_TYPE_F16 || op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_Q8_0 ||
4332                            op->type == GGML_TYPE_Q4_0) &&
4333                           src0->type == GGML_TYPE_F32 && (src1->type == GGML_TYPE_I64 || src1->type == GGML_TYPE_I32));
4334            break;
4335        case GGML_OP_GET_ROWS:
4336            {
4337                const size_t storage_alignment =
4338                    ctx->webgpu_global_ctx->capabilities.limits.minStorageBufferOffsetAlignment;
4339                const size_t src_address_unit =
4340                    src0->type == GGML_TYPE_F32 && op->ne[0] % 4 == 0 ? 4 * sizeof(float) : ggml_type_size(src0->type);
4341                if (ggml_webgpu_tensor_misalignment(src0, storage_alignment) % src_address_unit != 0) {
4342                    break;
4343                }
4344                if (src0->type == GGML_TYPE_F32 || src0->type == GGML_TYPE_F16 ||
4345                    ggml_webgpu_supported_qtype(src0->type)) {
4346                    supports_op = (op->type == GGML_TYPE_F32);
4347                } else if (src0->type == GGML_TYPE_I32) {
4348                    supports_op = op->type == GGML_TYPE_I32;
4349                }
4350                break;
4351            }
4352        case GGML_OP_MUL_MAT:
4353            {
4354                switch (src1->type) {
4355                    case GGML_TYPE_F16:
4356                        supports_op |= (src0->type == GGML_TYPE_F16);
4357                        break;
4358                    case GGML_TYPE_F32:
4359                        switch (src0->type) {
4360                            case GGML_TYPE_F32:
4361                            case GGML_TYPE_F16:
4362                            case GGML_TYPE_Q1_0:
4363                            case GGML_TYPE_Q4_0:
4364                            case GGML_TYPE_Q4_1:
4365                            case GGML_TYPE_Q5_0:
4366                            case GGML_TYPE_Q5_1:
4367                            case GGML_TYPE_Q8_0:
4368                            case GGML_TYPE_Q2_K:
4369                            case GGML_TYPE_Q3_K:
4370                            case GGML_TYPE_Q4_K:
4371                            case GGML_TYPE_Q5_K:
4372                            case GGML_TYPE_Q6_K:
4373                            case GGML_TYPE_IQ2_XXS:
4374                            case GGML_TYPE_IQ2_XS:
4375                            case GGML_TYPE_IQ2_S:
4376                            case GGML_TYPE_IQ3_XXS:
4377                            case GGML_TYPE_IQ3_S:
4378                            case GGML_TYPE_IQ1_S:
4379                            case GGML_TYPE_IQ1_M:
4380                            case GGML_TYPE_IQ4_NL:
4381                            case GGML_TYPE_IQ4_XS:
4382                            case GGML_TYPE_MXFP4:
4383                            case GGML_TYPE_NVFP4:
4384                                supports_op = true;
4385                                break;
4386                            default:
4387                                break;
4388                        }
4389                    default:
4390                        break;
4391                }
4392                break;
4393            }
4394        case GGML_OP_MUL_MAT_ID:
4395            switch (src1->type) {
4396                case GGML_TYPE_F16:
4397                    supports_op |= (src0->type == GGML_TYPE_F16);
4398                    break;
4399                case GGML_TYPE_F32:
4400                    switch (src0->type) {
4401                        case GGML_TYPE_F32:
4402                        case GGML_TYPE_F16:
4403                        case GGML_TYPE_Q1_0:
4404                        case GGML_TYPE_Q4_0:
4405                        case GGML_TYPE_Q4_1:
4406                        case GGML_TYPE_Q5_0:
4407                        case GGML_TYPE_Q5_1:
4408                        case GGML_TYPE_Q8_0:
4409                        case GGML_TYPE_Q2_K:
4410                        case GGML_TYPE_Q3_K:
4411                        case GGML_TYPE_Q4_K:
4412                        case GGML_TYPE_Q5_K:
4413                        case GGML_TYPE_Q6_K:
4414                        case GGML_TYPE_IQ1_S:
4415                        case GGML_TYPE_IQ1_M:
4416                        case GGML_TYPE_IQ2_XXS:
4417                        case GGML_TYPE_IQ2_XS:
4418                        case GGML_TYPE_IQ2_S:
4419                        case GGML_TYPE_IQ3_XXS:
4420                        case GGML_TYPE_IQ3_S:
4421                        case GGML_TYPE_IQ4_NL:
4422                        case GGML_TYPE_IQ4_XS:
4423                        case GGML_TYPE_MXFP4:
4424                        case GGML_TYPE_NVFP4:
4425                            supports_op = true;
4426                            break;
4427                        default:
4428                            break;
4429                    }
4430                    break;
4431                default:
4432                    break;
4433            }
4434            break;
4435        case GGML_OP_FLASH_ATTN_EXT:
4436            {
4437                // conservative support checks for whether the more resource-intensive shader paths
4438                // can be used, to avoid cases where flash_attn is assigned to the CPU later on
4439                supports_op = src0->type == GGML_TYPE_F32 &&
4440                              (src1->type == GGML_TYPE_F32 || src1->type == GGML_TYPE_F16 ||
4441                               src1->type == GGML_TYPE_Q4_0 || src1->type == GGML_TYPE_Q8_0) &&
4442                              (src2->type == GGML_TYPE_F32 || src2->type == GGML_TYPE_F16 ||
4443                               src2->type == GGML_TYPE_Q4_0 || src2->type == GGML_TYPE_Q8_0) &&
4444                              op->type == GGML_TYPE_F32;
4445                if (!supports_op) {
4446                    break;
4447                }
4448                if (ggml_webgpu_tensor_binding_overlap(ctx->webgpu_global_ctx, src1, src2) &&
4449                    src1->type != src2->type && !ggml_is_quantized(src1->type) && !ggml_is_quantized(src2->type)) {
4450                    supports_op = false;
4451                    break;
4452                }
4453                const auto & capabilities             = ctx->webgpu_global_ctx->capabilities;
4454                const size_t storage_offset_alignment = capabilities.limits.minStorageBufferOffsetAlignment;
4455
4456                // subgroup matrix path requirements
4457                const bool use_subgroup_matrix = ggml_webgpu_flash_attn_can_use_subgroup_matrix_path(
4458                    capabilities.supports_subgroup_matrix, capabilities.sg_mat_k, capabilities.sg_mat_n, src0, src2);
4459
4460                // tile path requirements
4461                const bool float_vec4_aligned =
4462                    ((src1->type != GGML_TYPE_F16 && src1->type != GGML_TYPE_F32) ||
4463                     ggml_webgpu_flash_attn_float_vec4_aligned(src1, storage_offset_alignment)) &&
4464                    ((src2->type != GGML_TYPE_F16 && src2->type != GGML_TYPE_F32) ||
4465                     ggml_webgpu_flash_attn_float_vec4_aligned(src2, storage_offset_alignment));
4466                const uint32_t k_tile_head_align = (src1->type == GGML_TYPE_F32 || src1->type == GGML_TYPE_F16) ?
4467                                                       GGML_WEBGPU_FLASH_ATTN_TILE_KV_VEC_WIDTH :
4468                                                       (uint32_t) ggml_blck_size(src1->type);
4469                const uint32_t v_tile_head_align = (src2->type == GGML_TYPE_F32 || src2->type == GGML_TYPE_F16) ?
4470                                                       GGML_WEBGPU_FLASH_ATTN_TILE_KV_VEC_WIDTH :
4471                                                       (uint32_t) ggml_blck_size(src2->type);
4472                const bool     tile_kv_head_dims_aligned =
4473                    src0->ne[0] % k_tile_head_align == 0 && src2->ne[0] % v_tile_head_align == 0;
4474                const bool tile_can_dispatch_all_q_rows =
4475                    capabilities.limits.maxComputeInvocationsPerWorkgroup >=
4476                    GGML_WEBGPU_FLASH_ATTN_TILE_Q_TILE * capabilities.max_subgroup_size;
4477                const bool use_tile = !use_subgroup_matrix && capabilities.supports_subgroups && float_vec4_aligned &&
4478                                      tile_kv_head_dims_aligned && tile_can_dispatch_all_q_rows;
4479
4480                if (!use_subgroup_matrix && !use_tile) {
4481                    supports_op = false;
4482                    break;
4483                }
4484                const uint32_t q_tile =
4485                    use_subgroup_matrix ? capabilities.sg_mat_m : GGML_WEBGPU_FLASH_ATTN_TILE_Q_TILE;
4486                const uint32_t kv_granularity = use_subgroup_matrix ? capabilities.sg_mat_n : 1u;
4487                const bool     kv_direct = use_subgroup_matrix ?
4488                                               ggml_webgpu_flash_attn_k_direct(src0, src1, capabilities.sg_mat_k) ||
4489                                                   ggml_webgpu_flash_attn_v_direct(src0, src2, capabilities.sg_mat_k) :
4490                                               false;
4491                const uint32_t max_kv_tile = ggml_webgpu_flash_attn_max_kv_tile(
4492                    capabilities.limits.maxComputeWorkgroupStorageSize, q_tile, kv_granularity, (uint32_t) src0->ne[0],
4493                    (uint32_t) src2->ne[0], op->src[3] != nullptr, kv_direct);
4494                supports_op = max_kv_tile > 0;
4495                break;
4496            }
4497        case GGML_OP_RMS_NORM:
4498        case GGML_OP_NORM:
4499        case GGML_OP_L2_NORM:
4500            supports_op = (op->type == GGML_TYPE_F32 && src0->type == GGML_TYPE_F32) && ggml_is_contiguous_rows(src0);
4501            break;
4502        case GGML_OP_ROPE:
4503            supports_op = op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_F16;
4504            break;
4505        case GGML_OP_GLU:
4506            switch (ggml_get_glu_op(op)) {
4507                case GGML_GLU_OP_REGLU:
4508                case GGML_GLU_OP_GEGLU:
4509                case GGML_GLU_OP_SWIGLU:
4510                case GGML_GLU_OP_GEGLU_ERF:
4511                case GGML_GLU_OP_GEGLU_QUICK:
4512                case GGML_GLU_OP_SWIGLU_CLAMP:
4513                    supports_op = op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_F16;
4514                    break;
4515                case GGML_GLU_OP_SWIGLU_OAI:
4516                    supports_op = op->type == GGML_TYPE_F32;
4517                    break;
4518                default:
4519                    break;
4520            }
4521            break;
4522        case GGML_OP_SCALE:
4523            supports_op = op->type == GGML_TYPE_F32;
4524            break;
4525        case GGML_OP_SOFT_MAX:
4526            supports_op = op->type == GGML_TYPE_F32;
4527            break;
4528        case GGML_OP_UNARY:
4529            {
4530                const ggml_unary_op UNARY_OP = ggml_get_unary_op(op);
4531
4532                switch (UNARY_OP) {
4533                    case GGML_UNARY_OP_ABS:
4534                    case GGML_UNARY_OP_SGN:
4535                    case GGML_UNARY_OP_NEG:
4536                    case GGML_UNARY_OP_STEP:
4537                    case GGML_UNARY_OP_TANH:
4538                    case GGML_UNARY_OP_ELU:
4539                    case GGML_UNARY_OP_RELU:
4540                    case GGML_UNARY_OP_SIGMOID:
4541                    case GGML_UNARY_OP_GELU:
4542                    case GGML_UNARY_OP_GELU_QUICK:
4543                    case GGML_UNARY_OP_SILU:
4544                    case GGML_UNARY_OP_HARDSWISH:
4545                    case GGML_UNARY_OP_HARDSIGMOID:
4546                    case GGML_UNARY_OP_EXP:
4547                    case GGML_UNARY_OP_GELU_ERF:
4548                    case GGML_UNARY_OP_SOFTPLUS:
4549                    case GGML_UNARY_OP_EXPM1:
4550                    case GGML_UNARY_OP_FLOOR:
4551                    case GGML_UNARY_OP_CEIL:
4552                    case GGML_UNARY_OP_ROUND:
4553                    case GGML_UNARY_OP_TRUNC:
4554                    case GGML_UNARY_OP_XIELU:
4555                        supports_op =
4556                            (op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_F16) && (src0->type == op->type);
4557                        break;
4558                    default:
4559                        break;
4560                }
4561            }
4562            break;
4563        case GGML_OP_TRI:
4564            supports_op = op->type == GGML_TYPE_F32 && src0->type == GGML_TYPE_F32;
4565            break;
4566        case GGML_OP_DIAG:
4567            supports_op = op->type == GGML_TYPE_F32 && src0->type == GGML_TYPE_F32;
4568            break;
4569        case GGML_OP_SOLVE_TRI:
4570            supports_op = op->type == GGML_TYPE_F32 && src0->type == GGML_TYPE_F32 && src1->type == GGML_TYPE_F32;
4571            break;
4572        case GGML_OP_CONV_2D:
4573            supports_op = (op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_F16) &&
4574                          (src0->type == GGML_TYPE_F32 || src0->type == GGML_TYPE_F16) &&
4575                          (src1->type == GGML_TYPE_F32 || src1->type == GGML_TYPE_F16);
4576            break;
4577        case GGML_OP_CONV_2D_DW:
4578            supports_op = (op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_F16) &&
4579                          (src0->type == GGML_TYPE_F32 || src0->type == GGML_TYPE_F16) &&
4580                          (src1->type == GGML_TYPE_F32 || src1->type == GGML_TYPE_F16) &&
4581                          (ggml_is_contiguous(src1) || ggml_is_contiguous_channels(src1));
4582            break;
4583        case GGML_OP_IM2COL:
4584            supports_op = (op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_F16) &&
4585                          (src0->type == GGML_TYPE_F32 || src0->type == GGML_TYPE_F16);
4586            break;
4587        case GGML_OP_SSM_CONV:
4588            supports_op = op->type == GGML_TYPE_F32;
4589            break;
4590        case GGML_OP_SSM_SCAN:
4591            supports_op = op->type == GGML_TYPE_F32 &&
4592                          src0->ne[0] <= ctx->webgpu_global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
4593            break;
4594        case GGML_OP_GATED_DELTA_NET:
4595            {
4596                const uint32_t s_v = (uint32_t) src2->ne[0];
4597                supports_op = op->type == GGML_TYPE_F32 && src0->type == GGML_TYPE_F32 && src1->type == GGML_TYPE_F32 &&
4598                              src2->type == GGML_TYPE_F32 && op->src[3]->type == GGML_TYPE_F32 &&
4599                              op->src[4]->type == GGML_TYPE_F32 && op->src[5]->type == GGML_TYPE_F32 &&
4600                              s_v <= ctx->webgpu_global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup;
4601            }
4602            break;
4603        case GGML_OP_CLAMP:
4604            supports_op = (op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_F16) && (src0->type == op->type);
4605            break;
4606        case GGML_OP_FILL:
4607            supports_op = op->type == GGML_TYPE_F32 && src0->type == GGML_TYPE_F32;
4608            break;
4609        case GGML_OP_LOG:
4610            supports_op = (op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_F16) && (src0->type == op->type);
4611            break;
4612        case GGML_OP_SQR:
4613            supports_op = (op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_F16) && (src0->type == op->type);
4614            break;
4615        case GGML_OP_SQRT:
4616            supports_op = (op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_F16) && (src0->type == op->type);
4617            break;
4618        case GGML_OP_SIN:
4619            supports_op = (op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_F16) && (src0->type == op->type);
4620            break;
4621        case GGML_OP_COS:
4622            supports_op = (op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_F16) && (src0->type == op->type);
4623            break;
4624        case GGML_OP_PAD:
4625            supports_op = op->type == GGML_TYPE_F32 && src0->type == GGML_TYPE_F32;
4626            break;
4627        case GGML_OP_ARGMAX:
4628            supports_op = op->type == GGML_TYPE_I32 && src0->type == GGML_TYPE_F32;
4629            break;
4630        case GGML_OP_ARGSORT:
4631            supports_op = op->type == GGML_TYPE_I32 && src0->type == GGML_TYPE_F32 && ggml_is_contiguous_rows(src0);
4632            break;
4633        case GGML_OP_TOP_K:
4634            supports_op = op->type == GGML_TYPE_I32 && src0->type == GGML_TYPE_F32 && ggml_is_contiguous_rows(src0);
4635            break;
4636        case GGML_OP_CUMSUM:
4637            supports_op = op->type == GGML_TYPE_F32 && src0->type == op->type;
4638            break;
4639        case GGML_OP_SUM:
4640        case GGML_OP_SUM_ROWS:
4641            supports_op = op->type == GGML_TYPE_F32 && src0->type == op->type && ggml_is_contiguous_rows(src0);
4642            break;
4643        case GGML_OP_UPSCALE:
4644            supports_op = (op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_F16) &&
4645                          (src0->type == GGML_TYPE_F32 || src0->type == GGML_TYPE_F16);
4646            break;
4647        default:
4648            break;
4649    }
4650    if (ggml_nbytes(op) > ctx->webgpu_global_ctx->capabilities.limits.maxStorageBufferBindingSize ||
4651        (src0 != nullptr &&
4652         ggml_nbytes(src0) > ctx->webgpu_global_ctx->capabilities.limits.maxStorageBufferBindingSize) ||
4653        (src1 != nullptr &&
4654         ggml_nbytes(src1) > ctx->webgpu_global_ctx->capabilities.limits.maxStorageBufferBindingSize) ||
4655        (src2 != nullptr &&
4656         ggml_nbytes(src2) > ctx->webgpu_global_ctx->capabilities.limits.maxStorageBufferBindingSize)) {
4657        supports_op = false;
4658        WEBGPU_LOG_DEBUG("ggml_webgpu op not supported due to size: ");
4659    }
4660
4661    if (!supports_op) {
4662        WEBGPU_LOG_DEBUG("ggml_webgpu op not supported: "
4663                         << ggml_op_name(op->op) << " with types dst: " << ggml_type_name(op->type)
4664                         << ", src0: " << (op->src[0] ? ggml_type_name(op->src[0]->type) : "null")
4665                         << ", src1: " << (op->src[1] ? ggml_type_name(op->src[1]->type) : "null"));
4666    } else {
4667        WEBGPU_LOG_DEBUG("ggml_webgpu op supported: "
4668                         << ggml_op_name(op->op) << " with types dst: " << ggml_type_name(op->type)
4669                         << ", src0: " << (op->src[0] ? ggml_type_name(op->src[0]->type) : "null")
4670                         << ", src1: " << (op->src[1] ? ggml_type_name(op->src[1]->type) : "null"));
4671    }
4672    return supports_op;
4673}
4674
4675static struct ggml_backend_device_i ggml_backend_webgpu_device_i = {
4676    /* .get_name             = */ ggml_backend_webgpu_device_get_name,
4677    /* .get_description      = */ ggml_backend_webgpu_device_get_description,
4678    /* .get_memory           = */ ggml_backend_webgpu_device_get_memory,
4679    /* .get_type             = */ ggml_backend_webgpu_device_get_type,
4680    /* .get_props            = */ ggml_backend_webgpu_device_get_props,
4681    /* .init_backend         = */ ggml_backend_webgpu_backend_init,
4682    /* .get_buffer_type      = */ ggml_backend_webgpu_device_get_buffer_type,
4683    /* .get_host_buffer_type = */ NULL,
4684    /* .buffer_from_host_ptr = */ NULL,
4685    /* .supports_op          = */ ggml_backend_webgpu_device_supports_op,
4686    /* .supports_buft        = */ ggml_backend_webgpu_device_supports_buft,
4687    /* .offload_op           = */ NULL,
4688    /* .event_new            = */ ggml_backend_webgpu_device_event_new,
4689    /* .event_free           = */ ggml_backend_webgpu_device_event_free,
4690    /* .event_synchronize    = */ ggml_backend_webgpu_device_event_synchronize,
4691};
4692
4693/* End GGML Backend Device Interface */
4694
4695/* GGML Backend Registration Interface */
4696
4697static const char * ggml_backend_webgpu_reg_get_name(ggml_backend_reg_t reg) {
4698    ggml_backend_webgpu_reg_context * ctx = static_cast<ggml_backend_webgpu_reg_context *>(reg->context);
4699    return ctx->name;
4700}
4701
4702static size_t ggml_backend_webgpu_reg_get_device_count(ggml_backend_reg_t reg) {
4703    ggml_backend_webgpu_reg_context * ctx = static_cast<ggml_backend_webgpu_reg_context *>(reg->context);
4704    return ctx->device_count;
4705}
4706
4707// Only one device is supported for now
4708static ggml_backend_dev_t ggml_backend_webgpu_reg_get_device(ggml_backend_reg_t reg, size_t index) {
4709    GGML_ASSERT(index == 0);
4710    WEBGPU_LOG_DEBUG("ggml_backend_reg_get_device()");
4711
4712    WEBGPU_CPU_PROFILE_TOTAL_START(reg_get_device);
4713
4714    ggml_backend_webgpu_reg_context * reg_ctx = static_cast<ggml_backend_webgpu_reg_context *>(reg->context);
4715
4716    create_webgpu_device(reg_ctx);
4717
4718    static ggml_backend_webgpu_device_context device_ctx;
4719    device_ctx.device_name            = GGML_WEBGPU_NAME;
4720    device_ctx.device_desc            = GGML_WEBGPU_NAME;
4721    device_ctx.webgpu_global_ctx      = reg_ctx->webgpu_global_ctx;
4722    // See GGML Backend Device Interface section
4723    static ggml_backend_device device = {
4724        /* .iface   = */ ggml_backend_webgpu_device_i,
4725        /* .reg     = */ reg,
4726        /* .context = */ &device_ctx,
4727    };
4728
4729    WEBGPU_CPU_PROFILE_TOTAL_END(reg_get_device, reg_ctx->webgpu_global_ctx);
4730    return &device;
4731}
4732
4733static const struct ggml_backend_reg_i ggml_backend_webgpu_reg_i = {
4734    /* .get_name         = */ ggml_backend_webgpu_reg_get_name,
4735    /* .get_device_count = */ ggml_backend_webgpu_reg_get_device_count,
4736    /* .get_device       = */ ggml_backend_webgpu_reg_get_device,
4737    /* .get_proc_address = */ NULL,
4738};
4739
4740/* End GGML Backend Registration Interface */
4741
4742ggml_backend_reg_t ggml_backend_webgpu_reg() {
4743    WEBGPU_LOG_DEBUG("ggml_backend_webgpu_reg()");
4744
4745    // Intentionally leak the global registry context to avoid crashing inside
4746    // Dawn/Vulkan static teardown during process exit.
4747    static ggml_backend_webgpu_reg_context * ctx = new ggml_backend_webgpu_reg_context();
4748
4749    static ggml_backend_reg reg = {
4750        /* .api_version = */ GGML_BACKEND_API_VERSION,
4751        /* .iface       = */ ggml_backend_webgpu_reg_i,
4752        /* .context     = */ ctx,
4753    };
4754
4755    ctx->name         = GGML_WEBGPU_NAME;
4756    ctx->device_count = 0;
4757
4758    // Keep one Dawn/WebGPU instance alive for the lifetime of the static backend
4759    // registry. Recreating it on repeated registry lookups can invalidate
4760    // adapter/device references that are still held by the backend/device layer.
4761    if (ctx->webgpu_global_ctx != nullptr && ctx->webgpu_global_ctx->instance != nullptr) {
4762        return &reg;
4763    }
4764
4765    wgpu::InstanceDescriptor               instance_descriptor{};
4766    std::vector<wgpu::InstanceFeatureName> instance_features = { wgpu::InstanceFeatureName::TimedWaitAny };
4767    instance_descriptor.requiredFeatures                     = instance_features.data();
4768    instance_descriptor.requiredFeatureCount                 = instance_features.size();
4769
4770#ifndef __EMSCRIPTEN__
4771    const char * const          instanceEnabledToggles[] = { "allow_unsafe_apis" };
4772    wgpu::DawnTogglesDescriptor instanceTogglesDesc;
4773    instanceTogglesDesc.enabledToggles     = instanceEnabledToggles;
4774    instanceTogglesDesc.enabledToggleCount = 1;
4775    instance_descriptor.nextInChain        = &instanceTogglesDesc;
4776#endif
4777
4778    wgpu::Instance inst              = wgpu::CreateInstance(&instance_descriptor);
4779    ctx->webgpu_global_ctx           = webgpu_global_context(new webgpu_global_context_struct());
4780    ctx->webgpu_global_ctx->instance = std::move(inst);
4781
4782    // Probe for adapter support
4783    wgpu::Adapter adapter;
4784    if (ctx->webgpu_global_ctx->instance != nullptr) {
4785        ggml_backend_webgpu_request_adapter(ctx->webgpu_global_ctx->instance, adapter);
4786    }
4787
4788    // WebGPU backend requires f16 support and, on native, implicit device synchronization.
4789    if (adapter != nullptr && adapter.HasFeature(wgpu::FeatureName::ShaderF16)
4790#ifndef __EMSCRIPTEN__
4791        && adapter.HasFeature(wgpu::FeatureName::ImplicitDeviceSynchronization)
4792#endif
4793    ) {
4794        ctx->device_count = 1;
4795    }
4796
4797    return &reg;
4798}
4799
4800ggml_backend_t ggml_backend_webgpu_init(void) {
4801    ggml_backend_reg_t reg = ggml_backend_webgpu_reg();
4802    if (ggml_backend_reg_dev_count(reg) == 0) {
4803        return nullptr;
4804    }
4805    ggml_backend_dev_t dev = ggml_backend_reg_dev_get(reg, 0);
4806    return ggml_backend_webgpu_backend_init(dev, nullptr);
4807}
4808
4809GGML_BACKEND_DL_IMPL(ggml_backend_webgpu_reg)