Skip to main content

rlx_wgpu/kernels/
mod.rs

1// RLX — versatile ML compiler + runtime.
2// Copyright (C) 2026 Eugene Hauptmann, Nataliya Kosmyna.
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, version 3.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with this program. If not, see <https://www.gnu.org/licenses/>.
15
16//! WGSL kernel sources + per-kernel pipeline cache.
17//!
18//! Pipelines are content-addressed: same WGSL source + same entry
19//! point yields the same pipeline. We hold them in `OnceLock`s so a
20//! single device dispatches every (graph, op) pair against a cached
21//! compilation.
22
23use std::sync::OnceLock;
24
25use bytemuck::{Pod, Zeroable};
26
27pub const MATMUL_WGSL: &str = include_str!("matmul.wgsl");
28pub const MATMUL_WIDE_WGSL: &str = include_str!("matmul_wide.wgsl");
29pub const MATMUL_WIDE_NV_WGSL: &str = include_str!("matmul_wide_nv.wgsl");
30pub const MATMUL_F16W_WGSL: &str = include_str!("matmul_f16w.wgsl");
31pub const MATMUL_F16_COMPUTE_WGSL: &str = include_str!("matmul_f16_compute.wgsl");
32pub const MATMUL_COOP16_WGSL: &str = include_str!("matmul_coop16.wgsl");
33pub const MATMUL_COOP_F32_WGSL: &str = include_str!("matmul_coop_f32.wgsl");
34pub const MATMUL_COOP_F32_PORTABLE_WGSL: &str = include_str!("matmul_coop_f32_portable.wgsl");
35pub const MATMUL_COOP_F16_VULKAN_WGSL: &str = include_str!("matmul_coop_f16_vulkan.wgsl");
36pub const MATMUL_COOP_F16_VULKAN_WIDEN_WGSL: &str =
37    include_str!("matmul_coop_f16_vulkan_widen.wgsl");
38pub const MATMUL_COOP_F16_VULKAN_F32ACC_WGSL: &str =
39    include_str!("matmul_coop_f16_vulkan_f32acc.wgsl");
40pub const MATMUL_COOP_F16_VULKAN_WIDEN_F32ACC_WGSL: &str =
41    include_str!("matmul_coop_f16_vulkan_widen_f32acc.wgsl");
42pub const MATMUL_QKV_COOP_F16_VK_WGSL: &str = include_str!("matmul_qkv_coop_f16_vk.wgsl");
43pub const MATMUL_QKV_COOP_F16_VK_WIDEN_WGSL: &str =
44    include_str!("matmul_qkv_coop_f16_vk_widen.wgsl");
45pub const MATMUL_QKV_COOP_F16_VK_F32ACC_WGSL: &str =
46    include_str!("matmul_qkv_coop_f16_vk_f32acc.wgsl");
47pub const MATMUL_QKV_COOP_F16_VK_WIDEN_F32ACC_WGSL: &str =
48    include_str!("matmul_qkv_coop_f16_vk_widen_f32acc.wgsl");
49pub const CAST_F32_TO_F16_WGSL: &str = include_str!("cast_f32_to_f16.wgsl");
50pub const BINARY_WGSL: &str = include_str!("binary.wgsl");
51pub const UNARY_WGSL: &str = include_str!("unary.wgsl");
52pub const UNARY_F16_MIRROR_WGSL: &str = include_str!("unary_f16_mirror.wgsl");
53pub const COMPARE_WGSL: &str = include_str!("compare.wgsl");
54pub const WHERE_WGSL: &str = include_str!("where.wgsl");
55pub const FMA_WGSL: &str = include_str!("fma.wgsl");
56pub const REDUCE_WGSL: &str = include_str!("reduce.wgsl");
57pub const SOFTMAX_WGSL: &str = include_str!("softmax.wgsl");
58pub const SOFTMAX_CROSS_ENTROPY_WGSL: &str = include_str!("softmax_cross_entropy.wgsl");
59pub const LAYERNORM_WGSL: &str = include_str!("layernorm.wgsl");
60pub const RMS_NORM_BWD_WGSL: &str = include_str!("rms_norm_backward.wgsl");
61pub const LAYER_NORM_BWD_WGSL: &str = include_str!("layer_norm_backward.wgsl");
62pub const CUMSUM_BWD_WGSL: &str = include_str!("cumsum_backward.wgsl");
63pub const ROPE_BWD_WGSL: &str = include_str!("rope_backward.wgsl");
64pub const GATHER_BWD_WGSL: &str = include_str!("gather_backward.wgsl");
65pub const CUMSUM_WGSL: &str = include_str!("cumsum.wgsl");
66pub const FFT_GPU_WGSL: &str = include_str!("fft_gpu.wgsl");
67pub const COPY_WGSL: &str = include_str!("copy.wgsl");
68pub const ELEMENTWISE_REGION_WGSL: &str = include_str!("elementwise_region.wgsl");
69pub const TRANSPOSE_WGSL: &str = include_str!("transpose.wgsl");
70pub const NARROW_WGSL: &str = include_str!("narrow.wgsl");
71pub const CONCAT_WGSL: &str = include_str!("concat.wgsl");
72pub const GATHER_WGSL: &str = include_str!("gather.wgsl");
73pub const GATHER_SPLIT_WGSL: &str = include_str!("gather_split.wgsl");
74pub const GATHER_AXIS_WGSL: &str = include_str!("gather_axis.wgsl");
75pub const ATTENTION_WGSL: &str = include_str!("attention.wgsl");
76pub const ATTENTION_BWD_WGSL: &str = include_str!("attention_bwd.wgsl");
77pub const ROPE_WGSL: &str = include_str!("rope.wgsl");
78pub const EXPAND_WGSL: &str = include_str!("expand.wgsl");
79pub const ARGMAX_WGSL: &str = include_str!("argmax.wgsl");
80pub const POOL2D_WGSL: &str = include_str!("pool2d.wgsl");
81pub const CONV2D_WGSL: &str = include_str!("conv2d.wgsl");
82pub const POOL1D_WGSL: &str = include_str!("pool1d.wgsl");
83pub const POOL3D_WGSL: &str = include_str!("pool3d.wgsl");
84pub const CONV1D_WGSL: &str = include_str!("conv1d.wgsl");
85pub const CONV3D_WGSL: &str = include_str!("conv3d.wgsl");
86pub const SCATTER_ADD_WGSL: &str = include_str!("scatter_add.wgsl");
87pub const TOPK_WGSL: &str = include_str!("topk.wgsl");
88pub const WELCH_PEAKS_GPU_WGSL: &str = include_str!("welch_peaks_gpu.wgsl");
89pub const UMAP_KNN_WGSL: &str = include_str!("umap_knn.wgsl");
90pub const GROUPED_MATMUL_WGSL: &str = include_str!("grouped_matmul.wgsl");
91pub const SAMPLE_WGSL: &str = include_str!("sample.wgsl");
92pub const SELECTIVE_SCAN_WGSL: &str = include_str!("selective_scan.wgsl");
93pub const MAMBA2_WGSL: &str = include_str!("mamba2.wgsl");
94pub const GRU_WGSL: &str = include_str!("gru.wgsl");
95pub const RNN_WGSL: &str = include_str!("rnn.wgsl");
96pub const DEQUANT_MATMUL_WGSL: &str = include_str!("dequant_matmul.wgsl");
97pub const DEQUANT_GGUF_WGSL: &str = include_str!("dequant_gguf.wgsl");
98pub const DEQUANT_GEMV_GGUF_WGSL: &str = include_str!("dequant_gemv_gguf.wgsl");
99pub const FUSED_RESIDUAL_LN_WGSL: &str = include_str!("fused_residual_ln.wgsl");
100pub const FUSED_RESIDUAL_LN_TEE_WGSL: &str = include_str!("fused_residual_ln_tee.wgsl");
101pub const FUSED_RESIDUAL_RMS_NORM_WGSL: &str = include_str!("fused_residual_rms_norm.wgsl");
102pub const MATMUL_QKV_WGSL: &str = include_str!("matmul_qkv.wgsl");
103pub const MATMUL_QKV_COOP_F32_WGSL: &str = include_str!("matmul_qkv_coop_f32.wgsl");
104
105#[repr(C)]
106#[derive(Debug, Clone, Copy, Pod, Zeroable)]
107pub struct MatmulParams {
108    pub m: u32,
109    pub k: u32,
110    pub n: u32,
111    pub a_off: u32,
112    pub b_off: u32,
113    pub c_off: u32,
114    pub batch: u32,
115    pub a_batch_stride: u32,
116    pub b_batch_stride: u32,
117    pub c_batch_stride: u32,
118    pub has_bias: u32,
119    pub bias_off: u32,
120    pub act_id: u32, // 0xFFFF = no activation
121    pub _pad0: u32,
122    pub _pad1: u32,
123    pub _pad2: u32,
124}
125
126/// Shared layout for binary, compare. 32 bytes (8 u32s).
127#[repr(C)]
128#[derive(Debug, Clone, Copy, Pod, Zeroable)]
129pub struct BinaryParams {
130    pub n: u32,
131    pub a_off: u32,
132    pub b_off: u32,
133    pub c_off: u32,
134    pub op: u32,
135    pub _p0: u32,
136    pub _p1: u32,
137    pub _p2: u32,
138}
139
140/// Layout for unary kernel. 32 bytes.
141#[repr(C)]
142#[derive(Debug, Clone, Copy, Pod, Zeroable)]
143pub struct UnaryParams {
144    pub n: u32,
145    pub in_off: u32,
146    pub out_off: u32,
147    pub op: u32,
148    pub _p0: u32,
149    pub _p1: u32,
150    pub _p2: u32,
151    pub _p3: u32,
152}
153
154/// Layout for where (3-input select). 32 bytes.
155#[repr(C)]
156#[derive(Debug, Clone, Copy, Pod, Zeroable)]
157pub struct WhereParams {
158    pub n: u32,
159    pub cond_off: u32,
160    pub x_off: u32,
161    pub y_off: u32,
162    pub out_off: u32,
163    pub _p0: u32,
164    pub _p1: u32,
165    pub _p2: u32,
166}
167
168/// Layout for fma (3-input fused multiply-add). 32 bytes.
169#[repr(C)]
170#[derive(Debug, Clone, Copy, Pod, Zeroable)]
171pub struct FmaParams {
172    pub n: u32,
173    pub a_off: u32,
174    pub b_off: u32,
175    pub c_off: u32,
176    pub out_off: u32,
177    pub _p0: u32,
178    pub _p1: u32,
179    pub _p2: u32,
180}
181
182/// Layout for reductions. 32 bytes.
183///
184/// Supports arbitrary-axis reductions. The reduce kernel walks the
185/// input as a 3D tensor `[outer, reduce_dim, inner]` where:
186///   * `outer` = product of dims BEFORE the reduce axis
187///   * `reduce_dim` = the reduce axis itself
188///   * `inner` = product of dims AFTER the reduce axis (=1 for the
189///     last-axis case, which is what the v3 dispatcher emitted).
190/// Output shape is `[outer, inner]` (or with the reduce axis kept as 1
191/// when `keep_dim`; the dispatcher handles the shape arithmetic).
192#[repr(C)]
193pub struct ReduceParams {
194    pub outer: u32,
195    pub reduce_dim: u32,
196    pub inner: u32,
197    pub in_off: u32,
198    pub out_off: u32,
199    pub op: u32,
200    pub _p0: u32,
201    pub _p1: u32,
202}
203
204// Manual impls to avoid issues with structural derives if any field
205// arrangement subtly trips bytemuck.
206unsafe impl Pod for ReduceParams {}
207unsafe impl Zeroable for ReduceParams {}
208impl Copy for ReduceParams {}
209impl Clone for ReduceParams {
210    fn clone(&self) -> Self {
211        *self
212    }
213}
214impl std::fmt::Debug for ReduceParams {
215    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
216        write!(
217            f,
218            "ReduceParams {{ outer: {}, reduce_dim: {}, inner: {}, op: {} }}",
219            self.outer, self.reduce_dim, self.inner, self.op
220        )
221    }
222}
223
224/// Layout for softmax. 32 bytes.
225#[repr(C)]
226#[derive(Debug, Clone, Copy, Pod, Zeroable)]
227pub struct SoftmaxParams {
228    pub outer: u32,
229    pub inner: u32,
230    pub in_off: u32,
231    pub out_off: u32,
232    pub _p0: u32,
233    pub _p1: u32,
234    pub _p2: u32,
235    pub _p3: u32,
236}
237
238/// Layout for the fused dense softmax cross-entropy. 32 bytes.
239#[repr(C)]
240#[derive(Debug, Clone, Copy, Pod, Zeroable)]
241pub struct SceParams {
242    pub outer: u32,
243    pub inner: u32,
244    pub logits_off: u32,
245    pub targets_off: u32,
246    pub out_off: u32,
247    pub _p0: u32,
248    pub _p1: u32,
249    pub _p2: u32,
250}
251
252/// Layout for LayerNorm / RmsNorm.
253#[repr(C)]
254#[derive(Debug, Clone, Copy, Pod, Zeroable)]
255pub struct LayerNormParams {
256    pub outer: u32,
257    pub inner: u32,
258    pub in_off: u32,
259    pub out_off: u32,
260    pub gamma_off: u32,
261    pub beta_off: u32,
262    pub eps_bits: u32, // bitcast::<u32>(eps)
263    pub op: u32,       // 0=LayerNorm, 1=RmsNorm
264}
265
266/// LayerNorm backward kernel params (f32 element offsets). Shared by
267/// the three entry points; the dispatcher picks `layer_norm_bwd_input`,
268/// `layer_norm_bwd_gamma_partial`, or `layer_norm_bwd_gamma_reduce`
269/// based on which Step variant fired. dbeta isn't a dedicated op — it's
270/// a plain `Reduce::Sum` over the batch dim of `dy`, handled by the
271/// general reduce kernel.
272///
273/// `scratch_off` is the f32-element offset of the tail scratch zone
274/// (only used by the gamma partial/reduce kernels). For the reduce
275/// kernel `outer` carries the number of partial chunks emitted by the
276/// partial kernel.
277#[repr(C)]
278#[derive(Debug, Clone, Copy, Pod, Zeroable)]
279pub struct LayerNormBwdParams {
280    pub outer: u32,
281    pub inner: u32,
282    pub x_off: u32,
283    pub gamma_off: u32,
284    pub dy_off: u32,
285    pub out_off: u32,
286    pub eps_bits: u32,
287    pub scratch_off: u32,
288}
289
290/// RMSNorm backward kernel params (f32 element offsets). `wrt`: 0=dx, 1=dgamma, 2=dbeta.
291#[repr(C)]
292#[derive(Debug, Clone, Copy, Pod, Zeroable)]
293pub struct RmsNormBwdParams {
294    pub outer: u32,
295    pub inner: u32,
296    pub x_off: u32,
297    pub gamma_off: u32,
298    pub beta_off: u32,
299    pub dy_off: u32,
300    pub out_off: u32,
301    pub eps_bits: u32,
302    pub wrt: u32,
303}
304
305#[repr(C)]
306#[derive(Debug, Clone, Copy, Pod, Zeroable)]
307pub struct CumsumBwdParams {
308    pub outer: u32,
309    pub inner: u32,
310    pub dy_off: u32,
311    pub dx_off: u32,
312    pub exclusive: u32,
313    pub _p0: u32,
314    pub _p1: u32,
315    pub _p2: u32,
316}
317
318#[repr(C)]
319#[derive(Debug, Clone, Copy, Pod, Zeroable)]
320pub struct RopeBwdParams {
321    pub batch: u32,
322    pub seq: u32,
323    pub hidden: u32,
324    pub head_dim: u32,
325    pub n_rot: u32,
326    pub dy_off: u32,
327    pub cos_off: u32,
328    pub sin_off: u32,
329    pub dx_off: u32,
330    pub cos_len: u32,
331}
332
333#[repr(C)]
334#[derive(Debug, Clone, Copy, Pod, Zeroable)]
335pub struct GatherBwdParams {
336    pub outer: u32,
337    pub axis_dim: u32,
338    pub num_idx: u32,
339    pub trailing: u32,
340    pub dy_off: u32,
341    pub idx_off: u32,
342    pub dst_off: u32,
343    pub _p0: u32,
344}
345
346/// Layout for cumsum. 32 bytes.
347#[repr(C)]
348#[derive(Debug, Clone, Copy, Pod, Zeroable)]
349pub struct CumsumParams {
350    pub outer: u32,
351    pub inner: u32,
352    pub in_off: u32,
353    pub out_off: u32,
354    pub exclusive: u32,
355    pub _p0: u32,
356    pub _p1: u32,
357    pub _p2: u32,
358}
359
360/// Layout for FFT. 32 bytes. Matches `fft.wgsl::Params`.
361#[repr(C)]
362#[derive(Debug, Clone, Copy, Pod, Zeroable)]
363pub struct FftParams {
364    pub src_off: u32,
365    pub dst_off: u32,
366    pub n: u32,
367    pub log2n: u32,
368    pub inverse: u32,
369    pub norm_scale: f32,
370    pub _p1: u32,
371    pub _p2: u32,
372}
373
374/// Uniform block for multi-kernel FFT (`fft_gpu.wgsl::Params`). 48 bytes.
375#[repr(C)]
376#[derive(Debug, Clone, Copy, Pod, Zeroable)]
377pub struct FftGpuParams {
378    pub off: u32,
379    pub dst_off: u32,
380    pub n: u32,
381    pub log2n: u32,
382    pub inverse: u32,
383    pub norm_scale: f32,
384    pub outer: u32,
385    pub tile: u32,
386    pub inner_stages: u32,
387    pub q_or_hs: u32,
388}
389
390/// PLAN L2 — interpreted N-ary element-wise region. Chain encoded
391/// as 4 u32s per step (op_kind, op_sub, lhs_enc, rhs_enc). Operand
392/// encoding: bit 31 = src kind (0=Input, 1=Step), bits 0..30 = index.
393/// `scalar_input_mask` is the per-input scalar fast-path bitfield;
394/// `input_modulus[i]` is the per-input element count for trailing-
395/// shape broadcast (`0` ⇒ no broadcast, kernel reads gid; `>0` ⇒
396/// kernel reads `gid % input_modulus[i]`). Fixed cap at 32 steps +
397/// 16 inputs (ample for chains rlx produces). 12 padding bytes
398/// after `scalar_input_mask` align the next array on WGSL's
399/// 16-byte uniform alignment boundary.
400#[repr(C)]
401#[derive(Debug, Clone, Copy, Pod, Zeroable)]
402pub struct ElementwiseRegionParams {
403    pub len: u32,
404    pub num_inputs: u32,
405    pub num_steps: u32,
406    pub dst_off: u32,
407    pub input_offs: [u32; 16],
408    pub chain: [u32; 128], // 32 steps * 4 u32s
409    pub scalar_input_mask: u32,
410    pub prologue: u32,
411    pub out_n: u32,
412    pub out_c: u32,
413    pub out_h: u32,
414    pub out_w: u32,
415    pub prologue_input: u32,
416    pub input_modulus: [u32; 16],
417}
418
419/// FKL batch region: `batch_input_offs[slice]` + shared chain (no prologue).
420#[repr(C)]
421#[derive(Debug, Clone, Copy, Pod, Zeroable)]
422pub struct BatchElementwiseRegionParams {
423    pub slice_len: u32,
424    pub num_batch: u32,
425    pub num_steps: u32,
426    pub base_dst_off: u32,
427    pub slice_elems: u32,
428    pub batch_input_offs: [u32; 64],
429    pub chain: [u32; 128],
430    pub scalar_input_mask: u32,
431    pub input_modulus: [u32; 16],
432}
433
434/// Layout shared by Reshape / Cast / generic full copy. 32 bytes.
435#[repr(C)]
436#[derive(Debug, Clone, Copy, Pod, Zeroable)]
437pub struct CopyParams {
438    pub n: u32,
439    pub in_off: u32,
440    pub out_off: u32,
441    pub _p0: u32,
442    pub _p1: u32,
443    pub _p2: u32,
444    pub _p3: u32,
445    pub _p4: u32,
446}
447
448/// Layout for transpose (uses the 3-binding bind layout).
449#[repr(C)]
450#[derive(Debug, Clone, Copy, Pod, Zeroable)]
451pub struct TransposeParams {
452    pub rank: u32,
453    pub out_total: u32,
454    pub in_off: u32,
455    pub out_off: u32,
456    /// PLAN L1 — precomputed at compile time. `1` when `perm[0] == 0`
457    /// (= bucket axis stays at output axis 0). Active-extent path
458    /// scales `out_total` proportionally only when this is `1`.
459    pub bucket_outermost: u32,
460    /// PLAN L1 — `out_dims[0]` for active-extent scaling math.
461    pub out_dim_0: u32,
462    pub _p2: u32,
463    pub _p3: u32,
464}
465
466/// Layout for narrow / concat (the same struct serves both).
467#[repr(C)]
468#[derive(Debug, Clone, Copy, Pod, Zeroable)]
469pub struct NarrowConcatParams {
470    pub total: u32, // total elements (output for narrow, input for concat)
471    pub outer: u32,
472    pub inner: u32,
473    pub axis_in_size: u32,
474    pub axis_out_size: u32,
475    pub start: u32,
476    pub in_off: u32,
477    pub out_off: u32,
478}
479
480/// Layout for gather.
481#[repr(C)]
482#[derive(Debug, Clone, Copy, Pod, Zeroable)]
483pub struct GatherParams {
484    pub n_out: u32,
485    pub n_idx: u32,
486    pub dim: u32,
487    pub vocab: u32,
488    pub in_off: u32,
489    pub idx_off: u32,
490    pub out_off: u32,
491    pub _p0: u32,
492}
493
494/// Layout for gather along a non-zero axis.
495#[repr(C)]
496#[derive(Debug, Clone, Copy, Pod, Zeroable)]
497pub struct GatherAxisParams {
498    pub total: u32,
499    pub outer: u32,
500    pub axis_dim: u32,
501    pub num_idx: u32,
502    pub trailing: u32,
503    pub table_off: u32,
504    pub idx_off: u32,
505    pub out_off: u32,
506}
507
508/// Layout for fused SDPA.
509///
510/// Per-tensor (Q, K, V, output) strides are passed explicitly so the
511/// kernel can read either canonical [B, H, S, D] or transposed
512/// [B, S, H, D] without inserting upstream Transpose dispatches. The
513/// layout-elimination saves ~24 transpose dispatches per BERT-L6
514/// forward (one per Q/K/V/output × layers), each ~50µs at small batch.
515///
516/// The `seq_q_stride` / `seq_k_stride` fields are retained because
517/// they describe the MASK layout `[B, H, S_q, S_k]` (separate from
518/// Q/K/V layout), used by `MaskKind::Custom`.
519///
520/// 144 bytes (36 u32s); WebGPU uniform-buffer 16-byte alignment OK.
521#[repr(C)]
522#[derive(Debug, Clone, Copy, Pod, Zeroable)]
523pub struct AttentionParams {
524    pub batch: u32,
525    pub heads: u32,
526    pub seq_q: u32,
527    pub seq_k: u32,
528    pub head_dim: u32,
529    pub q_off: u32,
530    pub k_off: u32,
531    pub v_off: u32,
532    pub out_off: u32,
533    pub mask_off: u32,
534    pub mask_kind: u32,
535    pub scale_bits: u32,
536    pub window: u32,
537    /// MASK address strides. Mask address math (per-element):
538    ///   addr = mask_off
539    ///        + b  * mask_batch_stride
540    ///        + h  * mask_head_stride
541    ///        + qi * seq_q_stride         (per-query stride)
542    ///        + s  * seq_k_stride         (per-key   stride)
543    /// Setting some strides to 0 lets the kernel read a *broadcast*
544    /// mask without materializing the broadcast. e.g. BERT padding mask
545    /// `[B, S]`: mask_batch_stride=S, mask_head_stride=0, seq_q_stride=0,
546    /// seq_k_stride=1. Saves the Expand pre-pass that unfuse used to
547    /// emit per attention block.
548    pub seq_q_stride: u32,
549    pub seq_k_stride: u32,
550    pub mask_batch_stride: u32,
551    pub mask_head_stride: u32,
552    pub _pad_mask_0: u32,
553    pub _pad_mask_1: u32,
554    pub _pad_mask_2: u32,
555
556    // Q stride triple (in f32 elements). For [B, H, S, D]:
557    //   q_batch_stride = H·S·D, q_head_stride = S·D, q_seq_stride = D
558    // For [B, S, H, D]:
559    //   q_batch_stride = S·H·D, q_head_stride = D,   q_seq_stride = H·D
560    pub q_batch_stride: u32,
561    pub q_head_stride: u32,
562    pub q_seq_stride: u32,
563    pub _pad_q: u32,
564
565    pub k_batch_stride: u32,
566    pub k_head_stride: u32,
567    pub k_seq_stride: u32,
568    pub _pad_k: u32,
569
570    pub v_batch_stride: u32,
571    pub v_head_stride: u32,
572    pub v_seq_stride: u32,
573    pub _pad_v: u32,
574
575    pub o_batch_stride: u32,
576    pub o_head_stride: u32,
577    pub o_seq_stride: u32,
578    pub _pad_o: u32,
579}
580
581/// Layout for [`attention_bwd.wgsl`] — forward strides + `dy_off` + `wrt`.
582#[repr(C)]
583#[derive(Debug, Clone, Copy, Pod, Zeroable)]
584pub struct AttentionBwdParams {
585    pub batch: u32,
586    pub heads: u32,
587    pub seq_q: u32,
588    pub seq_k: u32,
589    pub head_dim: u32,
590    pub q_off: u32,
591    pub k_off: u32,
592    pub v_off: u32,
593    pub dy_off: u32,
594    pub out_off: u32,
595    pub mask_off: u32,
596    pub mask_kind: u32,
597    pub scale_bits: u32,
598    pub window: u32,
599    pub wrt: u32,
600    pub seq_q_stride: u32,
601    pub seq_k_stride: u32,
602    pub mask_batch_stride: u32,
603    pub mask_head_stride: u32,
604    pub _pad_mask_0: u32,
605    pub _pad_mask_1: u32,
606    pub _pad_mask_2: u32,
607    pub q_batch_stride: u32,
608    pub q_head_stride: u32,
609    pub q_seq_stride: u32,
610    pub _pad_q: u32,
611    pub k_batch_stride: u32,
612    pub k_head_stride: u32,
613    pub k_seq_stride: u32,
614    pub _pad_k: u32,
615    pub v_batch_stride: u32,
616    pub v_head_stride: u32,
617    pub v_seq_stride: u32,
618    pub _pad_v: u32,
619    pub o_batch_stride: u32,
620    pub o_head_stride: u32,
621    pub o_seq_stride: u32,
622    pub _pad_o: u32,
623}
624
625/// Layout for Rope.
626#[repr(C)]
627#[derive(Debug, Clone, Copy, Pod, Zeroable)]
628pub struct RopeParams {
629    pub n_total: u32,
630    pub seq: u32,
631    pub head_dim: u32,
632    pub half: u32,
633    pub in_off: u32,
634    pub cos_off: u32,
635    pub sin_off: u32,
636    pub out_off: u32,
637    pub last_dim: u32,
638    /// PLAN L1 — set at compile time. Together with `seq_stride`,
639    /// lets the WGSL kernel decompose iteration index into
640    /// `(bi, si, d)` while indexing into the underlying full-extent
641    /// buffer. `n_total` is the runtime-scaled iteration bound;
642    /// `seq_stride` is the compile-time-fixed full seq for stride.
643    pub batch: u32,
644    pub seq_stride: u32,
645    /// RoPE pairing flavor: `0` = NeoX rotate-half `(i, i+half)`, `1` = GPT-J /
646    /// llama.cpp-NORM interleaved adjacent pairs `(2i, 2i+1)`. GGUF Llama weights
647    /// are permuted for the GPT-J layout, so GGUF-backed decode needs `style=1`.
648    pub style: u32,
649}
650
651/// Layout for Expand. Mirrors TransposeParams (rank, total, offsets);
652/// per-axis dims/strides ride in the meta storage buffer.
653#[repr(C)]
654#[derive(Debug, Clone, Copy, Pod, Zeroable)]
655pub struct ExpandParams {
656    pub rank: u32,
657    pub out_total: u32,
658    pub in_off: u32,
659    pub out_off: u32,
660    /// PLAN L1 — precomputed at compile time. `1` when the bucket
661    /// axis stays at output axis 0 after the expand mapping.
662    pub bucket_outermost: u32,
663    /// PLAN L1 — `out_dims[0]` for active-extent scaling math.
664    pub out_dim_0: u32,
665    pub _p2: u32,
666    pub _p3: u32,
667}
668
669/// Layout for argmax (matches Reduce shape).
670#[repr(C)]
671#[derive(Debug, Clone, Copy, Pod, Zeroable)]
672pub struct ArgmaxParams {
673    pub outer: u32,
674    pub inner: u32,
675    pub in_off: u32,
676    pub out_off: u32,
677    pub _p0: u32,
678    pub _p1: u32,
679    pub _p2: u32,
680    pub _p3: u32,
681}
682
683/// Layout for Pool2D NCHW.
684#[repr(C)]
685#[derive(Debug, Clone, Copy, Pod, Zeroable)]
686pub struct Pool2dParams {
687    pub n: u32,
688    pub c: u32,
689    pub h: u32,
690    pub w: u32,
691    pub h_out: u32,
692    pub w_out: u32,
693    pub kh: u32,
694    pub kw: u32,
695    pub sh: u32,
696    pub sw: u32,
697    pub ph: u32,
698    pub pw: u32,
699    pub op: u32,
700    pub in_off: u32,
701    pub out_off: u32,
702    pub _p0: u32,
703    pub _p1: u32,
704    pub _p2: u32,
705}
706
707/// Layout for Conv2D NCHW.
708#[repr(C)]
709#[derive(Debug, Clone, Copy, Pod, Zeroable)]
710pub struct Conv2dParams {
711    pub n: u32,
712    pub c_in: u32,
713    pub c_out: u32,
714    pub h: u32,
715    pub w: u32,
716    pub h_out: u32,
717    pub w_out: u32,
718    pub kh: u32,
719    pub kw: u32,
720    pub sh: u32,
721    pub sw: u32,
722    pub ph: u32,
723    pub pw: u32,
724    pub dh: u32,
725    pub dw: u32,
726    pub groups: u32,
727    pub in_off: u32,
728    pub w_off: u32,
729    pub out_off: u32,
730}
731
732/// Layout for Pool1D NCL.
733#[repr(C)]
734#[derive(Debug, Clone, Copy, Pod, Zeroable)]
735pub struct Pool1dParams {
736    pub n: u32,
737    pub c: u32,
738    pub l: u32,
739    pub l_out: u32,
740    pub kl: u32,
741    pub sl: u32,
742    pub pl: u32,
743    pub op: u32,
744    pub in_off: u32,
745    pub out_off: u32,
746    pub _p0: u32,
747    pub _p1: u32,
748    pub _p2: u32,
749    pub _p3: u32,
750    pub _p4: u32,
751    pub _p5: u32,
752}
753
754/// Layout for Pool3D NCDHW.
755#[repr(C)]
756#[derive(Debug, Clone, Copy, Pod, Zeroable)]
757pub struct Pool3dParams {
758    pub n: u32,
759    pub c: u32,
760    pub d: u32,
761    pub h: u32,
762    pub w: u32,
763    pub d_out: u32,
764    pub h_out: u32,
765    pub w_out: u32,
766    pub kd: u32,
767    pub kh: u32,
768    pub kw: u32,
769    pub sd: u32,
770    pub sh: u32,
771    pub sw: u32,
772    pub pd: u32,
773    pub ph: u32,
774    pub pw: u32,
775    pub op: u32,
776    pub in_off: u32,
777    pub out_off: u32,
778    pub _p0: u32,
779    pub _p1: u32,
780}
781
782/// Layout for Conv1D NCL.
783#[repr(C)]
784#[derive(Debug, Clone, Copy, Pod, Zeroable)]
785pub struct Conv1dParams {
786    pub n: u32,
787    pub c_in: u32,
788    pub c_out: u32,
789    pub l: u32,
790    pub l_out: u32,
791    pub kl: u32,
792    pub sl: u32,
793    pub pl: u32,
794    pub dl: u32,
795    pub groups: u32,
796    pub in_off: u32,
797    pub w_off: u32,
798    pub out_off: u32,
799    pub _p0: u32,
800    pub _p1: u32,
801    pub _p2: u32,
802}
803
804/// Layout for dequant_gguf. 16 bytes.
805#[repr(C)]
806#[derive(Debug, Clone, Copy, Pod, Zeroable)]
807pub struct DequantGgufParams {
808    pub w_byte_off: u32,
809    pub dst_f32_off: u32,
810    pub scheme_id: u32,
811    pub num_blocks: u32,
812}
813
814/// Layout for the fused GGUF K-quant GEMV (`dequant_gemv_gguf.wgsl`). 32 bytes.
815/// Offsets are relative to each kernel binding's windowed base.
816#[repr(C)]
817#[derive(Debug, Clone, Copy, Pod, Zeroable)]
818pub struct DequantGemvGgufParams {
819    pub k: u32,
820    pub n: u32,
821    pub scheme_id: u32,
822    pub x_f32_off: u32,
823    pub w_byte_off: u32,
824    pub out_f32_off: u32,
825    pub _p0: u32,
826    pub _p1: u32,
827}
828
829/// Layout for DequantMatMul. 48 bytes.
830#[repr(C)]
831#[derive(Debug, Clone, Copy, Pod, Zeroable)]
832pub struct DequantMatmulParams {
833    pub m: u32,
834    pub k: u32,
835    pub n: u32,
836    pub block_size: u32,
837    pub scheme_id: u32,
838    pub x_off: u32,
839    pub w_off: u32,
840    pub scale_off: u32,
841    pub zp_off: u32,
842    pub out_off: u32,
843    pub _p0: u32,
844    pub _p1: u32,
845}
846
847/// Layout for FusedResidualLN-Tee. 48 bytes (12 u32s).
848#[repr(C)]
849#[derive(Debug, Clone, Copy, Pod, Zeroable)]
850pub struct FusedResidualLnTeeParams {
851    pub outer: u32,
852    pub inner: u32,
853    pub in_off: u32,
854    pub residual_off: u32,
855    pub bias_off: u32,
856    pub gamma_off: u32,
857    pub beta_off: u32,
858    pub sum_off: u32,
859    pub ln_out_off: u32,
860    pub eps_bits: u32,
861    pub has_bias: u32,
862    pub _p0: u32,
863}
864
865/// Layout for matmul_qkv (split-write QKV matmul).
866/// 64 bytes (16 u32s); WebGPU uniform-buffer 16-byte alignment OK.
867#[repr(C)]
868#[derive(Debug, Clone, Copy, Pod, Zeroable)]
869pub struct MatmulQkvParams {
870    pub m: u32,
871    pub k: u32,
872    pub n: u32,
873    pub a_off: u32,
874    pub b_off: u32,
875    pub q_off: u32,
876    pub k_off: u32,
877    pub v_off: u32,
878    pub head_width: u32,
879    pub has_bias: u32,
880    pub bias_off: u32,
881    pub _p0: u32,
882    pub _p1: u32,
883    pub _p2: u32,
884    pub _p3: u32,
885    pub _p4: u32,
886}
887
888/// Layout for FusedResidualRmsNorm (same bind layout as FusedResidualLN).
889pub type FusedResidualRmsNormParams = FusedResidualLnParams;
890
891/// Layout for FusedResidualLN. 48 bytes.
892#[repr(C)]
893#[derive(Debug, Clone, Copy, Pod, Zeroable)]
894pub struct FusedResidualLnParams {
895    pub outer: u32,
896    pub inner: u32,
897    pub in_off: u32,
898    pub residual_off: u32,
899    pub bias_off: u32,
900    pub gamma_off: u32,
901    pub beta_off: u32,
902    pub out_off: u32,
903    pub eps_bits: u32,
904    pub has_bias: u32,
905    pub _p0: u32,
906    pub _p1: u32,
907}
908
909/// Layout for Mamba2 (SSD scan). 68→72 bytes padded to 16.
910#[repr(C)]
911#[derive(Debug, Clone, Copy, Pod, Zeroable)]
912pub struct Mamba2Params {
913    pub batch: u32,
914    pub seq: u32,
915    pub heads: u32,
916    pub head_dim: u32,
917    pub state_size: u32,
918    pub x_off: u32,
919    pub dt_off: u32,
920    pub a_off: u32,
921    pub b_off: u32,
922    pub c_off: u32,
923    pub out_off: u32,
924    pub seq_stride: u32,
925    pub _p1: u32,
926    pub _p2: u32,
927    pub _p3: u32,
928    pub _p4: u32,
929}
930
931/// Layout for GRU (native WGSL). 64 bytes.
932#[repr(C)]
933#[derive(Debug, Clone, Copy, Pod, Zeroable)]
934pub struct GruParams {
935    pub batch: u32,
936    pub seq: u32,
937    pub input_size: u32,
938    pub hidden: u32,
939    pub x_off: u32,
940    pub wih_off: u32,
941    pub whh_off: u32,
942    pub bih_off: u32,
943    pub bhh_off: u32,
944    pub out_off: u32,
945    pub seq_stride: u32,
946    pub _p1: u32,
947    pub _p2: u32,
948    pub _p3: u32,
949    pub _p4: u32,
950    pub _p5: u32,
951}
952
953/// Layout for Elman RNN (native WGSL). 68→padded. `relu` selects activation.
954#[repr(C)]
955#[derive(Debug, Clone, Copy, Pod, Zeroable)]
956pub struct RnnParams {
957    pub batch: u32,
958    pub seq: u32,
959    pub input_size: u32,
960    pub hidden: u32,
961    pub x_off: u32,
962    pub wih_off: u32,
963    pub whh_off: u32,
964    pub bias_off: u32,
965    pub out_off: u32,
966    pub seq_stride: u32,
967    pub relu: u32,
968    pub _p1: u32,
969    pub _p2: u32,
970    pub _p3: u32,
971    pub _p4: u32,
972    pub _p5: u32,
973}
974
975/// Layout for SelectiveScan. 64 bytes.
976#[repr(C)]
977#[derive(Debug, Clone, Copy, Pod, Zeroable)]
978pub struct SelectiveScanParams {
979    pub batch: u32,
980    pub seq: u32,
981    pub hidden: u32,
982    pub state_size: u32,
983    pub x_off: u32,
984    pub delta_off: u32,
985    pub a_off: u32,
986    pub b_off: u32,
987    pub c_off: u32,
988    pub out_off: u32,
989    /// PLAN L1 — full-extent seq stride for per-batch offset math.
990    /// Stays at compile-time `seq` even when runtime `seq` is scaled,
991    /// so per-batch arena offsets stay correct under active-extent.
992    pub seq_stride: u32,
993    pub _p1: u32,
994    pub _p2: u32,
995    pub _p3: u32,
996    pub _p4: u32,
997    pub _p5: u32,
998}
999
1000/// Layout for Sample. 48 bytes.
1001#[repr(C)]
1002#[derive(Debug, Clone, Copy, Pod, Zeroable)]
1003pub struct SampleParams {
1004    pub outer: u32,
1005    pub inner: u32,
1006    pub in_off: u32,
1007    pub out_off: u32,
1008    pub top_k: u32,
1009    pub top_p_bits: u32,
1010    pub temp_bits: u32,
1011    pub seed_lo: u32,
1012    pub seed_hi: u32,
1013    pub _p0: u32,
1014    pub _p1: u32,
1015    pub _p2: u32,
1016}
1017
1018/// Layout for GroupedMatMul. 32 bytes.
1019#[repr(C)]
1020#[derive(Debug, Clone, Copy, Pod, Zeroable)]
1021pub struct GroupedMatmulParams {
1022    pub m: u32,
1023    pub k: u32,
1024    pub n: u32,
1025    pub num_experts: u32,
1026    pub in_off: u32,
1027    pub w_off: u32,
1028    pub idx_off: u32,
1029    pub out_off: u32,
1030}
1031
1032/// Layout for TopK. 32 bytes.
1033#[repr(C)]
1034#[derive(Debug, Clone, Copy, Pod, Zeroable)]
1035pub struct TopKParams {
1036    pub outer: u32,
1037    pub inner: u32,
1038    pub k: u32,
1039    pub in_off: u32,
1040    pub out_off: u32,
1041    pub _p0: u32,
1042    pub _p1: u32,
1043    pub _p2: u32,
1044}
1045
1046/// Native GPU WelchPeaks dispatch parameters.
1047#[repr(C)]
1048#[derive(Debug, Clone, Copy, Pod, Zeroable)]
1049pub struct WelchPeaksGpuParams {
1050    pub spec_off: u32,
1051    pub dst_off: u32,
1052    pub welch_batch: u32,
1053    pub n_fft: u32,
1054    pub n_segments: u32,
1055    pub k: u32,
1056    pub n_bins: u32,
1057    pub _p0: u32,
1058    pub _p1: u32,
1059}
1060
1061/// Layout for UMAP k-NN on a pairwise `[n, n]` matrix. 32 bytes.
1062#[repr(C)]
1063#[derive(Debug, Clone, Copy, Pod, Zeroable)]
1064pub struct UmapKnnParams {
1065    pub n: u32,
1066    pub k: u32,
1067    pub pw_off: u32,
1068    pub out_off: u32,
1069    pub _p0: u32,
1070    pub _p1: u32,
1071    pub _p2: u32,
1072}
1073
1074/// Layout for ScatterAdd. 32 bytes (8 u32s).
1075#[repr(C)]
1076#[derive(Debug, Clone, Copy, Pod, Zeroable)]
1077pub struct ScatterAddParams {
1078    pub op: u32, // 0 = zero phase, 1 = accumulate phase
1079    pub out_off: u32,
1080    pub upd_off: u32,
1081    pub idx_off: u32,
1082    pub out_total: u32,
1083    pub num_updates: u32,
1084    pub trailing: u32,
1085    pub out_dim: u32,
1086}
1087
1088/// Layout for Conv3D NCDHW.
1089#[repr(C)]
1090#[derive(Debug, Clone, Copy, Pod, Zeroable)]
1091pub struct Conv3dParams {
1092    pub n: u32,
1093    pub c_in: u32,
1094    pub c_out: u32,
1095    pub d: u32,
1096    pub h: u32,
1097    pub w: u32,
1098    pub d_out: u32,
1099    pub h_out: u32,
1100    pub w_out: u32,
1101    pub kd: u32,
1102    pub kh: u32,
1103    pub kw: u32,
1104    pub sd: u32,
1105    pub sh: u32,
1106    pub sw: u32,
1107    pub pd: u32,
1108    pub ph: u32,
1109    pub pw: u32,
1110    pub dd: u32,
1111    pub dh: u32,
1112    pub dw: u32,
1113    pub groups: u32,
1114    pub in_off: u32,
1115    pub w_off: u32,
1116    pub out_off: u32,
1117    pub _p0: u32,
1118}
1119
1120/// Lazy-init container for a compute pipeline + its bind-group layout.
1121pub struct Kernel {
1122    pub pipeline: wgpu::ComputePipeline,
1123    pub bgl: wgpu::BindGroupLayout,
1124}
1125
1126impl Kernel {
1127    pub fn bind_two(
1128        &self,
1129        device: &wgpu::Device,
1130        arena: &wgpu::Buffer,
1131        uniform: &wgpu::Buffer,
1132    ) -> wgpu::BindGroup {
1133        device.create_bind_group(&wgpu::BindGroupDescriptor {
1134            label: Some("rlx-wgpu fft gpu bg"),
1135            layout: &self.bgl,
1136            entries: &[
1137                wgpu::BindGroupEntry {
1138                    binding: 0,
1139                    resource: arena.as_entire_binding(),
1140                },
1141                wgpu::BindGroupEntry {
1142                    binding: 1,
1143                    resource: uniform.as_entire_binding(),
1144                },
1145            ],
1146        })
1147    }
1148}
1149
1150/// Build a 4-binding compute kernel: storage(rw) / uniform / storage(ro)
1151/// / storage(ro). Currently unused — `matmul_coop16` switched to a
1152/// 3-binding layout (A is staged from arena through workgroup memory
1153/// instead of from a separate f16 binding). Kept for future kernels
1154/// that genuinely need a 4th binding.
1155#[allow(dead_code)]
1156/// Used by the cooperative-matrix matmul which needs a
1157/// fourth binding for the f16 activation shadow buffer.
1158fn build_kernel_4(
1159    device: &wgpu::Device,
1160    label: &'static str,
1161    wgsl: &str,
1162    entry_point: &'static str,
1163) -> Kernel {
1164    let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1165        label: Some(label),
1166        source: wgpu::ShaderSource::Wgsl(wgsl.into()),
1167    });
1168    let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1169        label: Some(label),
1170        entries: &[
1171            wgpu::BindGroupLayoutEntry {
1172                binding: 0,
1173                visibility: wgpu::ShaderStages::COMPUTE,
1174                ty: wgpu::BindingType::Buffer {
1175                    ty: wgpu::BufferBindingType::Storage { read_only: false },
1176                    has_dynamic_offset: false,
1177                    min_binding_size: None,
1178                },
1179                count: None,
1180            },
1181            wgpu::BindGroupLayoutEntry {
1182                binding: 1,
1183                visibility: wgpu::ShaderStages::COMPUTE,
1184                ty: wgpu::BindingType::Buffer {
1185                    ty: wgpu::BufferBindingType::Uniform,
1186                    has_dynamic_offset: false,
1187                    min_binding_size: None,
1188                },
1189                count: None,
1190            },
1191            wgpu::BindGroupLayoutEntry {
1192                binding: 2,
1193                visibility: wgpu::ShaderStages::COMPUTE,
1194                ty: wgpu::BindingType::Buffer {
1195                    ty: wgpu::BufferBindingType::Storage { read_only: true },
1196                    has_dynamic_offset: false,
1197                    min_binding_size: None,
1198                },
1199                count: None,
1200            },
1201            wgpu::BindGroupLayoutEntry {
1202                binding: 3,
1203                visibility: wgpu::ShaderStages::COMPUTE,
1204                ty: wgpu::BindingType::Buffer {
1205                    ty: wgpu::BufferBindingType::Storage { read_only: true },
1206                    has_dynamic_offset: false,
1207                    min_binding_size: None,
1208                },
1209                count: None,
1210            },
1211        ],
1212    });
1213    let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1214        label: Some(label),
1215        bind_group_layouts: &[Some(&bgl)],
1216        immediate_size: 0,
1217    });
1218    let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
1219        label: Some(label),
1220        layout: Some(&layout),
1221        module: &module,
1222        entry_point: Some(entry_point),
1223        compilation_options: Default::default(),
1224        cache: None,
1225    });
1226    Kernel { pipeline, bgl }
1227}
1228
1229fn build_kernel_3(
1230    device: &wgpu::Device,
1231    label: &'static str,
1232    wgsl: &str,
1233    entry_point: &'static str,
1234) -> Kernel {
1235    let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1236        label: Some(label),
1237        source: wgpu::ShaderSource::Wgsl(wgsl.into()),
1238    });
1239    let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1240        label: Some(label),
1241        entries: &[
1242            wgpu::BindGroupLayoutEntry {
1243                binding: 0,
1244                visibility: wgpu::ShaderStages::COMPUTE,
1245                ty: wgpu::BindingType::Buffer {
1246                    ty: wgpu::BufferBindingType::Storage { read_only: false },
1247                    has_dynamic_offset: false,
1248                    min_binding_size: None,
1249                },
1250                count: None,
1251            },
1252            wgpu::BindGroupLayoutEntry {
1253                binding: 1,
1254                visibility: wgpu::ShaderStages::COMPUTE,
1255                ty: wgpu::BindingType::Buffer {
1256                    ty: wgpu::BufferBindingType::Uniform,
1257                    has_dynamic_offset: false,
1258                    min_binding_size: None,
1259                },
1260                count: None,
1261            },
1262            wgpu::BindGroupLayoutEntry {
1263                binding: 2,
1264                visibility: wgpu::ShaderStages::COMPUTE,
1265                ty: wgpu::BindingType::Buffer {
1266                    ty: wgpu::BufferBindingType::Storage { read_only: true },
1267                    has_dynamic_offset: false,
1268                    min_binding_size: None,
1269                },
1270                count: None,
1271            },
1272        ],
1273    });
1274    let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1275        label: Some(label),
1276        bind_group_layouts: &[Some(&bgl)],
1277        immediate_size: 0,
1278    });
1279    let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
1280        label: Some(label),
1281        layout: Some(&layout),
1282        module: &module,
1283        entry_point: Some(entry_point),
1284        compilation_options: Default::default(),
1285        cache: None,
1286    });
1287    Kernel { pipeline, bgl }
1288}
1289
1290/// 4-binding layout: storage(ro) + uniform + storage(ro) + storage(rw).
1291/// For the GGUF GEMV: x (ro arena window) + params + weight (ro arena window) +
1292/// out (rw separate buffer). The arena is bound read-only twice (allowed), and
1293/// the single read-write binding is a distinct buffer — sidestepping wgpu's
1294/// "STORAGE_READ_WRITE is exclusive" rule for same-buffer aliasing.
1295fn build_kernel_ro_u_ro_rw(
1296    device: &wgpu::Device,
1297    label: &'static str,
1298    wgsl: &str,
1299    entry_point: &'static str,
1300) -> Kernel {
1301    let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1302        label: Some(label),
1303        source: wgpu::ShaderSource::Wgsl(wgsl.into()),
1304    });
1305    let storage = |read_only: bool| wgpu::BindingType::Buffer {
1306        ty: wgpu::BufferBindingType::Storage { read_only },
1307        has_dynamic_offset: false,
1308        min_binding_size: None,
1309    };
1310    let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1311        label: Some(label),
1312        entries: &[
1313            wgpu::BindGroupLayoutEntry {
1314                binding: 0,
1315                visibility: wgpu::ShaderStages::COMPUTE,
1316                ty: storage(true),
1317                count: None,
1318            },
1319            wgpu::BindGroupLayoutEntry {
1320                binding: 1,
1321                visibility: wgpu::ShaderStages::COMPUTE,
1322                ty: wgpu::BindingType::Buffer {
1323                    ty: wgpu::BufferBindingType::Uniform,
1324                    has_dynamic_offset: false,
1325                    min_binding_size: None,
1326                },
1327                count: None,
1328            },
1329            wgpu::BindGroupLayoutEntry {
1330                binding: 2,
1331                visibility: wgpu::ShaderStages::COMPUTE,
1332                ty: storage(true),
1333                count: None,
1334            },
1335            wgpu::BindGroupLayoutEntry {
1336                binding: 3,
1337                visibility: wgpu::ShaderStages::COMPUTE,
1338                ty: storage(false),
1339                count: None,
1340            },
1341        ],
1342    });
1343    let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1344        label: Some(label),
1345        bind_group_layouts: &[Some(&bgl)],
1346        immediate_size: 0,
1347    });
1348    let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
1349        label: Some(label),
1350        layout: Some(&layout),
1351        module: &module,
1352        entry_point: Some(entry_point),
1353        compilation_options: Default::default(),
1354        cache: None,
1355    });
1356    Kernel { pipeline, bgl }
1357}
1358
1359/// f16 shadow (rw) + uniform + f32 arena (rw) — `cast_f32_to_f16` only.
1360/// Separate from `build_kernel_3`: cast reads f32 written by a prior unary in
1361/// the same arena; other 3-binding kernels keep binding 2 read-only.
1362fn build_kernel_cast_f32_to_f16(
1363    device: &wgpu::Device,
1364    label: &'static str,
1365    wgsl: &str,
1366    entry_point: &'static str,
1367) -> Kernel {
1368    let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1369        label: Some(label),
1370        source: wgpu::ShaderSource::Wgsl(wgsl.into()),
1371    });
1372    let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1373        label: Some(label),
1374        entries: &[
1375            wgpu::BindGroupLayoutEntry {
1376                binding: 0,
1377                visibility: wgpu::ShaderStages::COMPUTE,
1378                ty: wgpu::BindingType::Buffer {
1379                    ty: wgpu::BufferBindingType::Storage { read_only: false },
1380                    has_dynamic_offset: false,
1381                    min_binding_size: None,
1382                },
1383                count: None,
1384            },
1385            wgpu::BindGroupLayoutEntry {
1386                binding: 1,
1387                visibility: wgpu::ShaderStages::COMPUTE,
1388                ty: wgpu::BindingType::Buffer {
1389                    ty: wgpu::BufferBindingType::Uniform,
1390                    has_dynamic_offset: false,
1391                    min_binding_size: None,
1392                },
1393                count: None,
1394            },
1395            wgpu::BindGroupLayoutEntry {
1396                binding: 2,
1397                visibility: wgpu::ShaderStages::COMPUTE,
1398                ty: wgpu::BindingType::Buffer {
1399                    ty: wgpu::BufferBindingType::Storage { read_only: false },
1400                    has_dynamic_offset: false,
1401                    min_binding_size: None,
1402                },
1403                count: None,
1404            },
1405        ],
1406    });
1407    let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1408        label: Some(label),
1409        bind_group_layouts: &[Some(&bgl)],
1410        immediate_size: 0,
1411    });
1412    let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
1413        label: Some(label),
1414        layout: Some(&layout),
1415        module: &module,
1416        entry_point: Some(entry_point),
1417        compilation_options: Default::default(),
1418        cache: None,
1419    });
1420    Kernel { pipeline, bgl }
1421}
1422
1423/// f32 arena (rw) + uniform + f16 shadow (rw) — unary with CoopF16Vk mirror.
1424fn build_kernel_f32_rw_uniform_f16_rw(
1425    device: &wgpu::Device,
1426    label: &'static str,
1427    wgsl: &str,
1428    entry_point: &'static str,
1429) -> Kernel {
1430    let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1431        label: Some(label),
1432        source: wgpu::ShaderSource::Wgsl(wgsl.into()),
1433    });
1434    let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1435        label: Some(label),
1436        entries: &[
1437            wgpu::BindGroupLayoutEntry {
1438                binding: 0,
1439                visibility: wgpu::ShaderStages::COMPUTE,
1440                ty: wgpu::BindingType::Buffer {
1441                    ty: wgpu::BufferBindingType::Storage { read_only: false },
1442                    has_dynamic_offset: false,
1443                    min_binding_size: None,
1444                },
1445                count: None,
1446            },
1447            wgpu::BindGroupLayoutEntry {
1448                binding: 1,
1449                visibility: wgpu::ShaderStages::COMPUTE,
1450                ty: wgpu::BindingType::Buffer {
1451                    ty: wgpu::BufferBindingType::Uniform,
1452                    has_dynamic_offset: false,
1453                    min_binding_size: None,
1454                },
1455                count: None,
1456            },
1457            wgpu::BindGroupLayoutEntry {
1458                binding: 2,
1459                visibility: wgpu::ShaderStages::COMPUTE,
1460                ty: wgpu::BindingType::Buffer {
1461                    ty: wgpu::BufferBindingType::Storage { read_only: false },
1462                    has_dynamic_offset: false,
1463                    min_binding_size: None,
1464                },
1465                count: None,
1466            },
1467        ],
1468    });
1469    let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1470        label: Some(label),
1471        bind_group_layouts: &[Some(&bgl)],
1472        immediate_size: 0,
1473    });
1474    let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
1475        label: Some(label),
1476        layout: Some(&layout),
1477        module: &module,
1478        entry_point: Some(entry_point),
1479        compilation_options: Default::default(),
1480        cache: None,
1481    });
1482    Kernel { pipeline, bgl }
1483}
1484
1485/// f16 shadow (read) + f32 arena (rw) + uniform — Vulkan/DX12 coop f16 matmul.
1486fn build_kernel_coop_f16_vk(
1487    device: &wgpu::Device,
1488    label: &'static str,
1489    wgsl: &str,
1490    entry_point: &'static str,
1491) -> Kernel {
1492    let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1493        label: Some(label),
1494        source: wgpu::ShaderSource::Wgsl(wgsl.into()),
1495    });
1496    let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1497        label: Some(label),
1498        entries: &[
1499            wgpu::BindGroupLayoutEntry {
1500                binding: 0,
1501                visibility: wgpu::ShaderStages::COMPUTE,
1502                ty: wgpu::BindingType::Buffer {
1503                    ty: wgpu::BufferBindingType::Storage { read_only: true },
1504                    has_dynamic_offset: false,
1505                    min_binding_size: None,
1506                },
1507                count: None,
1508            },
1509            wgpu::BindGroupLayoutEntry {
1510                binding: 1,
1511                visibility: wgpu::ShaderStages::COMPUTE,
1512                ty: wgpu::BindingType::Buffer {
1513                    ty: wgpu::BufferBindingType::Storage { read_only: false },
1514                    has_dynamic_offset: false,
1515                    min_binding_size: None,
1516                },
1517                count: None,
1518            },
1519            wgpu::BindGroupLayoutEntry {
1520                binding: 2,
1521                visibility: wgpu::ShaderStages::COMPUTE,
1522                ty: wgpu::BindingType::Buffer {
1523                    ty: wgpu::BufferBindingType::Uniform,
1524                    has_dynamic_offset: false,
1525                    min_binding_size: None,
1526                },
1527                count: None,
1528            },
1529        ],
1530    });
1531    let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1532        label: Some(label),
1533        bind_group_layouts: &[Some(&bgl)],
1534        immediate_size: 0,
1535    });
1536    let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
1537        label: Some(label),
1538        layout: Some(&layout),
1539        module: &module,
1540        entry_point: Some(entry_point),
1541        compilation_options: Default::default(),
1542        cache: None,
1543    });
1544    Kernel { pipeline, bgl }
1545}
1546
1547fn try_build_kernel_coop_f16_vk(
1548    device: &wgpu::Device,
1549    label: &'static str,
1550    wgsl: &str,
1551    entry_point: &'static str,
1552) -> Option<Kernel> {
1553    std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1554        build_kernel_coop_f16_vk(device, label, wgsl, entry_point)
1555    }))
1556    .ok()
1557}
1558
1559fn build_kernel(
1560    device: &wgpu::Device,
1561    label: &'static str,
1562    wgsl: &str,
1563    entry_point: &'static str,
1564) -> Kernel {
1565    let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1566        label: Some(label),
1567        source: wgpu::ShaderSource::Wgsl(wgsl.into()),
1568    });
1569    let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1570        label: Some(label),
1571        entries: &[
1572            wgpu::BindGroupLayoutEntry {
1573                binding: 0,
1574                visibility: wgpu::ShaderStages::COMPUTE,
1575                ty: wgpu::BindingType::Buffer {
1576                    ty: wgpu::BufferBindingType::Storage { read_only: false },
1577                    has_dynamic_offset: false,
1578                    min_binding_size: None,
1579                },
1580                count: None,
1581            },
1582            wgpu::BindGroupLayoutEntry {
1583                binding: 1,
1584                visibility: wgpu::ShaderStages::COMPUTE,
1585                ty: wgpu::BindingType::Buffer {
1586                    ty: wgpu::BufferBindingType::Uniform,
1587                    has_dynamic_offset: false,
1588                    min_binding_size: None,
1589                },
1590                count: None,
1591            },
1592        ],
1593    });
1594    let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1595        label: Some(label),
1596        bind_group_layouts: &[Some(&bgl)],
1597        immediate_size: 0,
1598    });
1599    let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
1600        label: Some(label),
1601        layout: Some(&layout),
1602        module: &module,
1603        entry_point: Some(entry_point),
1604        compilation_options: Default::default(),
1605        cache: None,
1606    });
1607    Kernel { pipeline, bgl }
1608}
1609
1610static MATMUL: OnceLock<Kernel> = OnceLock::new();
1611static MATMUL_WIDE: OnceLock<Kernel> = OnceLock::new();
1612static MATMUL_WIDE_NV: OnceLock<Kernel> = OnceLock::new();
1613static MATMUL_F16W: OnceLock<Kernel> = OnceLock::new();
1614static MATMUL_F16_COMPUTE: OnceLock<Kernel> = OnceLock::new();
1615static MATMUL_COOP16: OnceLock<Kernel> = OnceLock::new();
1616static MATMUL_COOP_F32: OnceLock<Kernel> = OnceLock::new();
1617static MATMUL_COOP_F32_PORTABLE: OnceLock<Kernel> = OnceLock::new();
1618static MATMUL_COOP_F16_VULKAN: OnceLock<Kernel> = OnceLock::new();
1619static MATMUL_COOP_F16_VULKAN_WIDEN: OnceLock<Kernel> = OnceLock::new();
1620static MATMUL_COOP_F16_VULKAN_F32ACC: OnceLock<Option<Kernel>> = OnceLock::new();
1621static MATMUL_COOP_F16_VULKAN_WIDEN_F32ACC: OnceLock<Option<Kernel>> = OnceLock::new();
1622static CAST_F32_TO_F16: OnceLock<Kernel> = OnceLock::new();
1623static BINARY: OnceLock<Kernel> = OnceLock::new();
1624static UNARY: OnceLock<Kernel> = OnceLock::new();
1625static UNARY_F16_MIRROR: OnceLock<Kernel> = OnceLock::new();
1626static COMPARE: OnceLock<Kernel> = OnceLock::new();
1627static WHEREK: OnceLock<Kernel> = OnceLock::new();
1628static FMAK: OnceLock<Kernel> = OnceLock::new();
1629static REDUCE: OnceLock<Kernel> = OnceLock::new();
1630static SOFTMAX: OnceLock<Kernel> = OnceLock::new();
1631static SOFTMAX_CROSS_ENTROPY: OnceLock<Kernel> = OnceLock::new();
1632static LAYERNORM: OnceLock<Kernel> = OnceLock::new();
1633static RMS_NORM_BWD: OnceLock<Kernel> = OnceLock::new();
1634static RMS_NORM_BWD_PARAM: OnceLock<Kernel> = OnceLock::new();
1635static LAYER_NORM_BWD_INPUT: OnceLock<Kernel> = OnceLock::new();
1636static LAYER_NORM_BWD_GAMMA: OnceLock<Kernel> = OnceLock::new();
1637static LAYER_NORM_BWD_GAMMA_REDUCE: OnceLock<Kernel> = OnceLock::new();
1638static CUMSUM_BWD: OnceLock<Kernel> = OnceLock::new();
1639static ROPE_BWD: OnceLock<Kernel> = OnceLock::new();
1640static GATHER_BWD_ZERO: OnceLock<Kernel> = OnceLock::new();
1641static GATHER_BWD_ACC: OnceLock<Kernel> = OnceLock::new();
1642static CUMSUM: OnceLock<Kernel> = OnceLock::new();
1643static FFT_GPU_RADIX2: OnceLock<Kernel> = OnceLock::new();
1644static FFT_GPU_BITREV: OnceLock<Kernel> = OnceLock::new();
1645static FFT_GPU_INNER: OnceLock<Kernel> = OnceLock::new();
1646static FFT_GPU_OUTER_R4: OnceLock<Kernel> = OnceLock::new();
1647static FFT_GPU_OUTER_R2: OnceLock<Kernel> = OnceLock::new();
1648static COPY: OnceLock<Kernel> = OnceLock::new();
1649static ELEMENTWISE_REGION: OnceLock<Kernel> = OnceLock::new();
1650static ELEMENTWISE_REGION_SPATIAL: OnceLock<Kernel> = OnceLock::new();
1651static TRANSPOSE: OnceLock<Kernel> = OnceLock::new();
1652static NARROW: OnceLock<Kernel> = OnceLock::new();
1653static CONCAT: OnceLock<Kernel> = OnceLock::new();
1654static GATHER: OnceLock<Kernel> = OnceLock::new();
1655static GATHER_SPLIT: OnceLock<Kernel> = OnceLock::new();
1656static GATHER_AXIS: OnceLock<Kernel> = OnceLock::new();
1657static ATTENTION: OnceLock<Kernel> = OnceLock::new();
1658static ATTENTION_BWD: OnceLock<Kernel> = OnceLock::new();
1659static ROPE: OnceLock<Kernel> = OnceLock::new();
1660static EXPAND: OnceLock<Kernel> = OnceLock::new();
1661static ARGMAX: OnceLock<Kernel> = OnceLock::new();
1662static POOL2D: OnceLock<Kernel> = OnceLock::new();
1663static CONV2D: OnceLock<Kernel> = OnceLock::new();
1664static POOL1D: OnceLock<Kernel> = OnceLock::new();
1665static POOL3D: OnceLock<Kernel> = OnceLock::new();
1666static CONV1D: OnceLock<Kernel> = OnceLock::new();
1667static CONV3D: OnceLock<Kernel> = OnceLock::new();
1668static SCATTER_ADD: OnceLock<Kernel> = OnceLock::new();
1669static TOPK: OnceLock<Kernel> = OnceLock::new();
1670static WELCH_PEAKS_GPU: OnceLock<Kernel> = OnceLock::new();
1671static UMAP_KNN: OnceLock<Kernel> = OnceLock::new();
1672static GROUPED_MATMUL: OnceLock<Kernel> = OnceLock::new();
1673static SAMPLE: OnceLock<Kernel> = OnceLock::new();
1674static SELECTIVE_SCAN: OnceLock<Kernel> = OnceLock::new();
1675static MAMBA2: OnceLock<Kernel> = OnceLock::new();
1676static GRU: OnceLock<Kernel> = OnceLock::new();
1677static RNN: OnceLock<Kernel> = OnceLock::new();
1678static DEQUANT_MATMUL: OnceLock<Kernel> = OnceLock::new();
1679static DEQUANT_GGUF: OnceLock<Kernel> = OnceLock::new();
1680static DEQUANT_GEMV_GGUF: OnceLock<Kernel> = OnceLock::new();
1681static MATMUL_BT: OnceLock<Kernel> = OnceLock::new();
1682static FUSED_RESIDUAL_LN: OnceLock<Kernel> = OnceLock::new();
1683static FUSED_RESIDUAL_LN_TEE: OnceLock<Kernel> = OnceLock::new();
1684static FUSED_RESIDUAL_RMS_NORM: OnceLock<Kernel> = OnceLock::new();
1685static MATMUL_QKV: OnceLock<Kernel> = OnceLock::new();
1686static MATMUL_QKV_COOP_F32: OnceLock<Kernel> = OnceLock::new();
1687static MATMUL_QKV_COOP_F16_VK: OnceLock<Kernel> = OnceLock::new();
1688static MATMUL_QKV_COOP_F16_VK_WIDEN: OnceLock<Kernel> = OnceLock::new();
1689static MATMUL_QKV_COOP_F16_VK_F32ACC: OnceLock<Option<Kernel>> = OnceLock::new();
1690static MATMUL_QKV_COOP_F16_VK_WIDEN_F32ACC: OnceLock<Option<Kernel>> = OnceLock::new();
1691
1692pub fn matmul_kernel(device: &wgpu::Device) -> &'static Kernel {
1693    MATMUL.get_or_init(|| build_kernel(device, "rlx-wgpu matmul", MATMUL_WGSL, "matmul"))
1694}
1695pub fn matmul_wide_kernel(device: &wgpu::Device) -> &'static Kernel {
1696    MATMUL_WIDE.get_or_init(|| {
1697        build_kernel(
1698            device,
1699            "rlx-wgpu matmul_wide",
1700            MATMUL_WIDE_WGSL,
1701            "matmul_wide",
1702        )
1703    })
1704}
1705/// 64×64 / 256-thread variant for discrete GPUs (Vulkan path).
1706pub fn matmul_wide_nv_kernel(device: &wgpu::Device) -> &'static Kernel {
1707    MATMUL_WIDE_NV.get_or_init(|| {
1708        build_kernel(
1709            device,
1710            "rlx-wgpu matmul_wide_nv",
1711            MATMUL_WIDE_NV_WGSL,
1712            "matmul_wide_nv",
1713        )
1714    })
1715}
1716/// f16-weight matmul (f32 compute). Returns Some only when the device
1717/// exposes the `SHADER_F16` feature. EXPERIMENTAL: currently slower
1718/// than the f32 baseline on Apple Silicon — kept as foundation; see
1719/// `matmul_f16w.wgsl` for the empirical analysis.
1720pub fn matmul_f16w_kernel(device: &wgpu::Device) -> Option<&'static Kernel> {
1721    if !device.features().contains(wgpu::Features::SHADER_F16) {
1722        return None;
1723    }
1724    Some(MATMUL_F16W.get_or_init(|| {
1725        build_kernel_3(
1726            device,
1727            "rlx-wgpu matmul_f16w",
1728            MATMUL_F16W_WGSL,
1729            "matmul_f16w",
1730        )
1731    }))
1732}
1733/// f16-compute matmul: f16 operands, f16 multiply, f32 accumulator.
1734/// Targets the 2× f16 ALU throughput on Apple Silicon. Returns Some
1735/// only when the device exposes `SHADER_F16`.
1736pub fn matmul_f16_compute_kernel(device: &wgpu::Device) -> Option<&'static Kernel> {
1737    if !device.features().contains(wgpu::Features::SHADER_F16) {
1738        return None;
1739    }
1740    Some(MATMUL_F16_COMPUTE.get_or_init(|| {
1741        build_kernel_3(
1742            device,
1743            "rlx-wgpu matmul_f16_compute",
1744            MATMUL_F16_COMPUTE_WGSL,
1745            "matmul_f16_compute",
1746        )
1747    }))
1748}
1749/// Cooperative-matrix matmul (8×8 tiles, hardware GEMM units).
1750/// Lowers to MSL `simdgroup_matrix` on Metal and SPIR-V's
1751/// `OpCooperativeMatrixMulAddKHR` on Vulkan. Returns Some only when
1752/// the device exposes both `SHADER_F16` and
1753/// `EXPERIMENTAL_COOPERATIVE_MATRIX`.
1754pub fn matmul_coop16_kernel(device: &wgpu::Device) -> Option<&'static Kernel> {
1755    let feats = device.features();
1756    if !feats.contains(wgpu::Features::SHADER_F16)
1757        || !feats.contains(wgpu::Features::EXPERIMENTAL_COOPERATIVE_MATRIX)
1758    {
1759        return None;
1760    }
1761    Some(MATMUL_COOP16.get_or_init(|| {
1762        build_kernel_3(
1763            device,
1764            "rlx-wgpu matmul_coop16",
1765            MATMUL_COOP16_WGSL,
1766            "matmul_coop16",
1767        )
1768    }))
1769}
1770/// Pure-f32 cooperative-matrix matmul. No SHADER_F16 needed — uses
1771/// `coop_mat8x8<f32>` throughout (lowers to `simdgroup_float8x8` on
1772/// Apple). Returns None if the cooperative-matrix feature is missing
1773/// OR if the device's WGSL→backend lowering can't compile it (some
1774/// implementations only expose half-precision coop matrices).
1775pub fn matmul_coop_f32_kernel(device: &wgpu::Device) -> Option<&'static Kernel> {
1776    let feats = device.features();
1777    if !feats.contains(wgpu::Features::EXPERIMENTAL_COOPERATIVE_MATRIX) {
1778        return None;
1779    }
1780    Some(MATMUL_COOP_F32.get_or_init(|| {
1781        build_kernel(
1782            device,
1783            "rlx-wgpu matmul_coop_f32",
1784            MATMUL_COOP_F32_WGSL,
1785            "matmul_coop_f32",
1786        )
1787    }))
1788}
1789/// Vulkan/DX12-oriented coop f32 matmul (`coopLoad`, 8×8 workgroups).
1790pub fn matmul_coop_f32_portable_kernel(device: &wgpu::Device) -> Option<&'static Kernel> {
1791    let feats = device.features();
1792    if !feats.contains(wgpu::Features::EXPERIMENTAL_COOPERATIVE_MATRIX)
1793        || !crate::device::coop_f32_8x8_supported()
1794    {
1795        return None;
1796    }
1797    Some(MATMUL_COOP_F32_PORTABLE.get_or_init(|| {
1798        build_kernel(
1799            device,
1800            "rlx-wgpu matmul_coop_f32_portable",
1801            MATMUL_COOP_F32_PORTABLE_WGSL,
1802            "matmul_coop_f32_portable",
1803        )
1804    }))
1805}
1806fn coop_f16_vk_device_ready(device: &wgpu::Device) -> bool {
1807    // Cooperative-matrix Vulkan/DX12 matmul is OFF by default — see
1808    // `coop_f16_vk_eligible` in `backend.rs` for the rationale. Opt in
1809    // with `RLX_WGPU_COOP_F16_VK_ENABLE=1`. Legacy
1810    // `RLX_WGPU_COOP_F16_VK_DISABLE=1` also fully disables.
1811    if rlx_ir::env::flag("RLX_WGPU_COOP_F16_VK_DISABLE")
1812        || !rlx_ir::env::flag("RLX_WGPU_COOP_F16_VK_ENABLE")
1813    {
1814        return false;
1815    }
1816    device.features().contains(wgpu::Features::SHADER_F16)
1817        && device
1818            .features()
1819            .contains(wgpu::Features::EXPERIMENTAL_COOPERATIVE_MATRIX)
1820        && crate::device::coop_f16_16x16_supported()
1821        && crate::device::coop_discrete_backend()
1822}
1823
1824fn coop_f16_vk_f32acc_device_ready(device: &wgpu::Device) -> bool {
1825    coop_f16_vk_device_ready(device) && crate::device::coop_f16_16x16_f32_acc_supported()
1826}
1827
1828pub fn matmul_coop_f16_vulkan_f32acc_kernel(device: &wgpu::Device) -> Option<&'static Kernel> {
1829    if !coop_f16_vk_f32acc_device_ready(device) {
1830        return None;
1831    }
1832    MATMUL_COOP_F16_VULKAN_F32ACC
1833        .get_or_init(|| {
1834            try_build_kernel_coop_f16_vk(
1835                device,
1836                "rlx-wgpu matmul_coop_f16_vulkan_f32acc",
1837                MATMUL_COOP_F16_VULKAN_F32ACC_WGSL,
1838                "matmul_coop_f16_vulkan_f32acc",
1839            )
1840        })
1841        .as_ref()
1842}
1843
1844pub fn matmul_coop_f16_vulkan_widen_f32acc_kernel(
1845    device: &wgpu::Device,
1846) -> Option<&'static Kernel> {
1847    if !coop_f16_vk_f32acc_device_ready(device) {
1848        return None;
1849    }
1850    MATMUL_COOP_F16_VULKAN_WIDEN_F32ACC
1851        .get_or_init(|| {
1852            try_build_kernel_coop_f16_vk(
1853                device,
1854                "rlx-wgpu matmul_coop_f16_vulkan_widen_f32acc",
1855                MATMUL_COOP_F16_VULKAN_WIDEN_F32ACC_WGSL,
1856                "matmul_coop_f16_vulkan_widen_f32acc",
1857            )
1858        })
1859        .as_ref()
1860}
1861
1862fn coop_f16_vk_use_f32acc(device: &wgpu::Device) -> bool {
1863    !rlx_ir::env::flag("RLX_WGPU_COOP_F16_VK_NO_F32ACC")
1864        && matmul_coop_f16_vulkan_f32acc_kernel(device).is_some()
1865}
1866
1867fn pick_coop_f16_vk_matmul(
1868    device: &wgpu::Device,
1869    n: u32,
1870    loadt: fn(&wgpu::Device) -> Option<&'static Kernel>,
1871    loadt_f32acc: fn(&wgpu::Device) -> Option<&'static Kernel>,
1872    widen: fn(&wgpu::Device) -> Option<&'static Kernel>,
1873    widen_f32acc: fn(&wgpu::Device) -> Option<&'static Kernel>,
1874) -> Option<&'static Kernel> {
1875    if coop_f16_vk_use_f32acc(device) {
1876        if coop_f16_vk_widen_b_load(n) {
1877            return widen_f32acc(device).or_else(|| loadt_f32acc(device));
1878        }
1879        return loadt_f32acc(device);
1880    }
1881    if coop_f16_vk_widen_b_load(n) {
1882        widen(device).or_else(|| loadt(device))
1883    } else {
1884        loadt(device)
1885    }
1886}
1887
1888/// Matmul CoopF16Vk kernel for column count `n`.
1889pub fn matmul_coop_f16_vulkan_active_kernel(
1890    device: &wgpu::Device,
1891    n: u32,
1892) -> Option<&'static Kernel> {
1893    pick_coop_f16_vk_matmul(
1894        device,
1895        n,
1896        matmul_coop_f16_vulkan_kernel,
1897        matmul_coop_f16_vulkan_f32acc_kernel,
1898        matmul_coop_f16_vulkan_widen_kernel,
1899        matmul_coop_f16_vulkan_widen_f32acc_kernel,
1900    )
1901}
1902
1903pub fn matmul_coop_f16_vulkan_kernel(device: &wgpu::Device) -> Option<&'static Kernel> {
1904    if !coop_f16_vk_device_ready(device) {
1905        return None;
1906    }
1907    Some(MATMUL_COOP_F16_VULKAN.get_or_init(|| {
1908        build_kernel_coop_f16_vk(
1909            device,
1910            "rlx-wgpu matmul_coop_f16_vulkan",
1911            MATMUL_COOP_F16_VULKAN_WGSL,
1912            "matmul_coop_f16_vulkan",
1913        )
1914    }))
1915}
1916/// N above which coop may use the row-major B-load variant (`RLX_WGPU_COOP_F16_VK_LARGE_N`).
1917pub const COOP_F16_VK_WIDEN_N: u32 = 768;
1918
1919/// Use `coopLoad` on B instead of `coopLoadT` when N > 768 and `RLX_WGPU_COOP_F16_VK_LOAD_T` is unset.
1920pub fn coop_f16_vk_widen_b_load(n: u32) -> bool {
1921    n > COOP_F16_VK_WIDEN_N && !rlx_ir::env::flag("RLX_WGPU_COOP_F16_VK_LOAD_T")
1922}
1923
1924pub fn matmul_coop_f16_vulkan_widen_kernel(device: &wgpu::Device) -> Option<&'static Kernel> {
1925    if !coop_f16_vk_device_ready(device) {
1926        return None;
1927    }
1928    Some(MATMUL_COOP_F16_VULKAN_WIDEN.get_or_init(|| {
1929        build_kernel_coop_f16_vk(
1930            device,
1931            "rlx-wgpu matmul_coop_f16_vulkan_widen",
1932            MATMUL_COOP_F16_VULKAN_WIDEN_WGSL,
1933            "matmul_coop_f16_vulkan_widen",
1934        )
1935    }))
1936}
1937pub fn coop_f16_vk_f32acc_available(device: &wgpu::Device) -> bool {
1938    matmul_coop_f16_vulkan_f32acc_kernel(device).is_some()
1939}
1940/// CoopF32 kernel for the active wgpu backend (Metal simdgroup vs Vulkan/DX12 portable).
1941pub fn matmul_coop_f32_active_kernel(device: &wgpu::Device) -> Option<&'static Kernel> {
1942    match crate::device::wgpu_device().map(|d| d.backend) {
1943        Some(wgpu::Backend::Metal) => matmul_coop_f32_kernel(device),
1944        Some(wgpu::Backend::Vulkan) | Some(wgpu::Backend::Dx12) => {
1945            matmul_coop_f32_portable_kernel(device)
1946        }
1947        _ => None,
1948    }
1949}
1950/// Wide f32 matmul kernel for the active backend.
1951pub fn matmul_wide_active_kernel(device: &wgpu::Device) -> &'static Kernel {
1952    match crate::device::wgpu_device().map(|d| d.backend) {
1953        Some(wgpu::Backend::Vulkan) | Some(wgpu::Backend::Dx12) => matmul_wide_nv_kernel(device),
1954        _ => matmul_wide_kernel(device),
1955    }
1956}
1957/// Mirrors a region of the f32 arena into the f16 shadow buffer.
1958/// Used before `matmul_coop16` for the matmul's activation operand
1959/// (intermediate activations don't go through `set_param` /
1960/// `write_f32`, so they aren't in the f16 buffer otherwise).
1961pub fn cast_f32_to_f16_kernel(device: &wgpu::Device) -> Option<&'static Kernel> {
1962    if !device.features().contains(wgpu::Features::SHADER_F16) {
1963        return None;
1964    }
1965    Some(CAST_F32_TO_F16.get_or_init(|| {
1966        build_kernel_cast_f32_to_f16(
1967            device,
1968            "rlx-wgpu cast_f32_to_f16",
1969            CAST_F32_TO_F16_WGSL,
1970            "cast_f32_to_f16",
1971        )
1972    }))
1973}
1974pub fn binary_kernel(device: &wgpu::Device) -> &'static Kernel {
1975    BINARY.get_or_init(|| build_kernel(device, "rlx-wgpu binary", BINARY_WGSL, "binary"))
1976}
1977pub fn unary_kernel(device: &wgpu::Device) -> &'static Kernel {
1978    UNARY.get_or_init(|| build_kernel(device, "rlx-wgpu unary", UNARY_WGSL, "unary"))
1979}
1980pub fn unary_f16_mirror_kernel(device: &wgpu::Device) -> Option<&'static Kernel> {
1981    if !device.features().contains(wgpu::Features::SHADER_F16) {
1982        return None;
1983    }
1984    Some(UNARY_F16_MIRROR.get_or_init(|| {
1985        build_kernel_f32_rw_uniform_f16_rw(
1986            device,
1987            "rlx-wgpu unary_f16_mirror",
1988            UNARY_F16_MIRROR_WGSL,
1989            "unary_f16_mirror",
1990        )
1991    }))
1992}
1993pub fn compare_kernel(device: &wgpu::Device) -> &'static Kernel {
1994    COMPARE.get_or_init(|| build_kernel(device, "rlx-wgpu compare", COMPARE_WGSL, "compare"))
1995}
1996pub fn where_kernel(device: &wgpu::Device) -> &'static Kernel {
1997    WHEREK.get_or_init(|| build_kernel(device, "rlx-wgpu where", WHERE_WGSL, "where_select"))
1998}
1999pub fn fma_kernel(device: &wgpu::Device) -> &'static Kernel {
2000    FMAK.get_or_init(|| build_kernel(device, "rlx-wgpu fma", FMA_WGSL, "fma_main"))
2001}
2002pub fn reduce_kernel(device: &wgpu::Device) -> &'static Kernel {
2003    REDUCE.get_or_init(|| build_kernel(device, "rlx-wgpu reduce", REDUCE_WGSL, "reduce"))
2004}
2005pub fn softmax_kernel(device: &wgpu::Device) -> &'static Kernel {
2006    SOFTMAX.get_or_init(|| build_kernel(device, "rlx-wgpu softmax", SOFTMAX_WGSL, "softmax"))
2007}
2008pub fn softmax_cross_entropy_kernel(device: &wgpu::Device) -> &'static Kernel {
2009    SOFTMAX_CROSS_ENTROPY.get_or_init(|| {
2010        build_kernel(
2011            device,
2012            "rlx-wgpu softmax_cross_entropy",
2013            SOFTMAX_CROSS_ENTROPY_WGSL,
2014            "softmax_cross_entropy",
2015        )
2016    })
2017}
2018pub fn layernorm_kernel(device: &wgpu::Device) -> &'static Kernel {
2019    LAYERNORM.get_or_init(|| build_kernel(device, "rlx-wgpu layernorm", LAYERNORM_WGSL, "norm"))
2020}
2021pub fn rms_norm_backward_kernel(device: &wgpu::Device) -> &'static Kernel {
2022    RMS_NORM_BWD.get_or_init(|| {
2023        build_kernel(
2024            device,
2025            "rlx-wgpu rms_norm_bwd",
2026            RMS_NORM_BWD_WGSL,
2027            "rms_norm_bwd",
2028        )
2029    })
2030}
2031pub fn rms_norm_backward_param_kernel(device: &wgpu::Device) -> &'static Kernel {
2032    RMS_NORM_BWD_PARAM.get_or_init(|| {
2033        build_kernel(
2034            device,
2035            "rlx-wgpu rms_norm_bwd_param",
2036            RMS_NORM_BWD_WGSL,
2037            "rms_norm_bwd_param",
2038        )
2039    })
2040}
2041pub fn layer_norm_backward_input_kernel(device: &wgpu::Device) -> &'static Kernel {
2042    LAYER_NORM_BWD_INPUT.get_or_init(|| {
2043        build_kernel(
2044            device,
2045            "rlx-wgpu layer_norm_bwd_input",
2046            LAYER_NORM_BWD_WGSL,
2047            "layer_norm_bwd_input",
2048        )
2049    })
2050}
2051pub fn layer_norm_backward_gamma_partial_kernel(device: &wgpu::Device) -> &'static Kernel {
2052    LAYER_NORM_BWD_GAMMA.get_or_init(|| {
2053        build_kernel(
2054            device,
2055            "rlx-wgpu layer_norm_bwd_gamma_partial",
2056            LAYER_NORM_BWD_WGSL,
2057            "layer_norm_bwd_gamma_partial",
2058        )
2059    })
2060}
2061
2062pub fn layer_norm_backward_gamma_reduce_kernel(device: &wgpu::Device) -> &'static Kernel {
2063    LAYER_NORM_BWD_GAMMA_REDUCE.get_or_init(|| {
2064        build_kernel(
2065            device,
2066            "rlx-wgpu layer_norm_bwd_gamma_reduce",
2067            LAYER_NORM_BWD_WGSL,
2068            "layer_norm_bwd_gamma_reduce",
2069        )
2070    })
2071}
2072pub fn cumsum_backward_kernel(device: &wgpu::Device) -> &'static Kernel {
2073    CUMSUM_BWD
2074        .get_or_init(|| build_kernel(device, "rlx-wgpu cumsum_bwd", CUMSUM_BWD_WGSL, "cumsum_bwd"))
2075}
2076pub fn rope_backward_kernel(device: &wgpu::Device) -> &'static Kernel {
2077    ROPE_BWD.get_or_init(|| build_kernel(device, "rlx-wgpu rope_bwd", ROPE_BWD_WGSL, "rope_bwd"))
2078}
2079pub fn gather_backward_zero_kernel(device: &wgpu::Device) -> &'static Kernel {
2080    GATHER_BWD_ZERO.get_or_init(|| {
2081        build_kernel(
2082            device,
2083            "rlx-wgpu gather_bwd_zero",
2084            GATHER_BWD_WGSL,
2085            "gather_bwd_zero",
2086        )
2087    })
2088}
2089pub fn gather_backward_acc_kernel(device: &wgpu::Device) -> &'static Kernel {
2090    GATHER_BWD_ACC.get_or_init(|| {
2091        build_kernel(
2092            device,
2093            "rlx-wgpu gather_bwd_acc",
2094            GATHER_BWD_WGSL,
2095            "gather_bwd_acc",
2096        )
2097    })
2098}
2099pub fn cumsum_kernel(device: &wgpu::Device) -> &'static Kernel {
2100    CUMSUM.get_or_init(|| build_kernel(device, "rlx-wgpu cumsum", CUMSUM_WGSL, "cumsum"))
2101}
2102pub fn fft_gpu_radix2_full_kernel(device: &wgpu::Device) -> &'static Kernel {
2103    FFT_GPU_RADIX2.get_or_init(|| {
2104        build_kernel(
2105            device,
2106            "rlx-wgpu fft_radix2_full",
2107            FFT_GPU_WGSL,
2108            "fft_radix2_full",
2109        )
2110    })
2111}
2112pub fn fft_gpu_bit_reverse_kernel(device: &wgpu::Device) -> &'static Kernel {
2113    FFT_GPU_BITREV.get_or_init(|| {
2114        build_kernel(
2115            device,
2116            "rlx-wgpu fft_bit_reverse",
2117            FFT_GPU_WGSL,
2118            "fft_bit_reverse",
2119        )
2120    })
2121}
2122pub fn fft_gpu_inner_kernel(device: &wgpu::Device) -> &'static Kernel {
2123    FFT_GPU_INNER
2124        .get_or_init(|| build_kernel(device, "rlx-wgpu fft_inner", FFT_GPU_WGSL, "fft_inner"))
2125}
2126pub fn fft_gpu_outer_r4_kernel(device: &wgpu::Device) -> &'static Kernel {
2127    FFT_GPU_OUTER_R4.get_or_init(|| {
2128        build_kernel(
2129            device,
2130            "rlx-wgpu fft_outer_r4",
2131            FFT_GPU_WGSL,
2132            "fft_outer_r4",
2133        )
2134    })
2135}
2136pub fn fft_gpu_outer_r2_kernel(device: &wgpu::Device) -> &'static Kernel {
2137    FFT_GPU_OUTER_R2.get_or_init(|| {
2138        build_kernel(
2139            device,
2140            "rlx-wgpu fft_outer_r2",
2141            FFT_GPU_WGSL,
2142            "fft_outer_r2",
2143        )
2144    })
2145}
2146pub fn copy_kernel(device: &wgpu::Device) -> &'static Kernel {
2147    COPY.get_or_init(|| build_kernel(device, "rlx-wgpu copy", COPY_WGSL, "copy"))
2148}
2149pub fn elementwise_region_kernel(device: &wgpu::Device) -> &'static Kernel {
2150    // Region params bind as a STORAGE buffer (not uniform) — WGSL's
2151    // uniform-storage spec requires 16-byte stride for `array<T, N>`,
2152    // which our packed `array<u32, N>` chain layout doesn't satisfy.
2153    // Storage allows arbitrary stride.
2154    ELEMENTWISE_REGION.get_or_init(|| {
2155        build_kernel_region(
2156            device,
2157            "rlx-wgpu elementwise_region",
2158            ELEMENTWISE_REGION_WGSL,
2159            "elementwise_region",
2160        )
2161    })
2162}
2163
2164pub fn elementwise_region_spatial_kernel(device: &wgpu::Device) -> &'static Kernel {
2165    ELEMENTWISE_REGION_SPATIAL.get_or_init(|| {
2166        build_kernel_region(
2167            device,
2168            "rlx-wgpu elementwise_region_spatial",
2169            ELEMENTWISE_REGION_WGSL,
2170            "elementwise_region_spatial",
2171        )
2172    })
2173}
2174
2175static BATCH_ELEMENTWISE_REGION: std::sync::OnceLock<Kernel> = std::sync::OnceLock::new();
2176
2177pub fn batch_elementwise_region_kernel(device: &wgpu::Device) -> &'static Kernel {
2178    BATCH_ELEMENTWISE_REGION.get_or_init(|| {
2179        build_kernel_region(
2180            device,
2181            "rlx-wgpu batch_elementwise_region",
2182            ELEMENTWISE_REGION_WGSL,
2183            "batch_elementwise_region",
2184        )
2185    })
2186}
2187
2188fn build_kernel_region(
2189    device: &wgpu::Device,
2190    label: &'static str,
2191    wgsl: &str,
2192    entry_point: &'static str,
2193) -> Kernel {
2194    let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
2195        label: Some(label),
2196        source: wgpu::ShaderSource::Wgsl(wgsl.into()),
2197    });
2198    let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
2199        label: Some(label),
2200        entries: &[
2201            wgpu::BindGroupLayoutEntry {
2202                binding: 0,
2203                visibility: wgpu::ShaderStages::COMPUTE,
2204                ty: wgpu::BindingType::Buffer {
2205                    ty: wgpu::BufferBindingType::Storage { read_only: false },
2206                    has_dynamic_offset: false,
2207                    min_binding_size: None,
2208                },
2209                count: None,
2210            },
2211            wgpu::BindGroupLayoutEntry {
2212                binding: 1,
2213                visibility: wgpu::ShaderStages::COMPUTE,
2214                ty: wgpu::BindingType::Buffer {
2215                    // Region params: read-only storage (vs uniform).
2216                    ty: wgpu::BufferBindingType::Storage { read_only: true },
2217                    has_dynamic_offset: false,
2218                    min_binding_size: None,
2219                },
2220                count: None,
2221            },
2222        ],
2223    });
2224    let pl = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
2225        label: Some(label),
2226        bind_group_layouts: &[Some(&bgl)],
2227        immediate_size: 0,
2228    });
2229    let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
2230        label: Some(label),
2231        layout: Some(&pl),
2232        module: &module,
2233        entry_point: Some(entry_point),
2234        compilation_options: Default::default(),
2235        cache: None,
2236    });
2237    Kernel { pipeline, bgl }
2238}
2239pub fn transpose_kernel(device: &wgpu::Device) -> &'static Kernel {
2240    TRANSPOSE
2241        .get_or_init(|| build_kernel_3(device, "rlx-wgpu transpose", TRANSPOSE_WGSL, "transpose"))
2242}
2243pub fn narrow_kernel(device: &wgpu::Device) -> &'static Kernel {
2244    NARROW.get_or_init(|| build_kernel(device, "rlx-wgpu narrow", NARROW_WGSL, "narrow"))
2245}
2246pub fn concat_kernel(device: &wgpu::Device) -> &'static Kernel {
2247    CONCAT.get_or_init(|| build_kernel(device, "rlx-wgpu concat", CONCAT_WGSL, "concat"))
2248}
2249pub fn gather_kernel(device: &wgpu::Device) -> &'static Kernel {
2250    GATHER.get_or_init(|| build_kernel(device, "rlx-wgpu gather", GATHER_WGSL, "gather"))
2251}
2252/// Split-binding gather: table (ro) + uniform + idx (ro) + out (rw, separate
2253/// buffer). For >4 GiB arenas where the embedding output lies outside the
2254/// table's bind window. See [`build_kernel_ro_u_ro_rw`].
2255pub fn gather_split_kernel(device: &wgpu::Device) -> &'static Kernel {
2256    GATHER_SPLIT.get_or_init(|| {
2257        build_kernel_ro_u_ro_rw(device, "rlx-wgpu gather_split", GATHER_SPLIT_WGSL, "gather")
2258    })
2259}
2260pub fn gather_axis_kernel(device: &wgpu::Device) -> &'static Kernel {
2261    GATHER_AXIS.get_or_init(|| {
2262        build_kernel(
2263            device,
2264            "rlx-wgpu gather_axis",
2265            GATHER_AXIS_WGSL,
2266            "gather_axis",
2267        )
2268    })
2269}
2270pub fn attention_kernel(device: &wgpu::Device) -> &'static Kernel {
2271    ATTENTION
2272        .get_or_init(|| build_kernel(device, "rlx-wgpu attention", ATTENTION_WGSL, "attention"))
2273}
2274pub fn attention_bwd_kernel(device: &wgpu::Device) -> &'static Kernel {
2275    ATTENTION_BWD.get_or_init(|| {
2276        build_kernel(
2277            device,
2278            "rlx-wgpu attention_bwd",
2279            ATTENTION_BWD_WGSL,
2280            "attention_bwd",
2281        )
2282    })
2283}
2284pub fn rope_kernel(device: &wgpu::Device) -> &'static Kernel {
2285    ROPE.get_or_init(|| build_kernel(device, "rlx-wgpu rope", ROPE_WGSL, "rope"))
2286}
2287pub fn expand_kernel(device: &wgpu::Device) -> &'static Kernel {
2288    EXPAND.get_or_init(|| build_kernel_3(device, "rlx-wgpu expand", EXPAND_WGSL, "expand"))
2289}
2290pub fn argmax_kernel(device: &wgpu::Device) -> &'static Kernel {
2291    ARGMAX.get_or_init(|| build_kernel(device, "rlx-wgpu argmax", ARGMAX_WGSL, "argmax"))
2292}
2293pub fn pool2d_kernel(device: &wgpu::Device) -> &'static Kernel {
2294    POOL2D.get_or_init(|| build_kernel(device, "rlx-wgpu pool2d", POOL2D_WGSL, "pool2d"))
2295}
2296pub fn conv2d_kernel(device: &wgpu::Device) -> &'static Kernel {
2297    CONV2D.get_or_init(|| build_kernel(device, "rlx-wgpu conv2d", CONV2D_WGSL, "conv2d"))
2298}
2299pub fn pool1d_kernel(device: &wgpu::Device) -> &'static Kernel {
2300    POOL1D.get_or_init(|| build_kernel(device, "rlx-wgpu pool1d", POOL1D_WGSL, "pool1d"))
2301}
2302pub fn pool3d_kernel(device: &wgpu::Device) -> &'static Kernel {
2303    POOL3D.get_or_init(|| build_kernel(device, "rlx-wgpu pool3d", POOL3D_WGSL, "pool3d"))
2304}
2305pub fn conv1d_kernel(device: &wgpu::Device) -> &'static Kernel {
2306    CONV1D.get_or_init(|| build_kernel(device, "rlx-wgpu conv1d", CONV1D_WGSL, "conv1d"))
2307}
2308pub fn conv3d_kernel(device: &wgpu::Device) -> &'static Kernel {
2309    CONV3D.get_or_init(|| build_kernel(device, "rlx-wgpu conv3d", CONV3D_WGSL, "conv3d"))
2310}
2311pub fn scatter_add_kernel(device: &wgpu::Device) -> &'static Kernel {
2312    SCATTER_ADD.get_or_init(|| {
2313        build_kernel(
2314            device,
2315            "rlx-wgpu scatter_add",
2316            SCATTER_ADD_WGSL,
2317            "scatter_add",
2318        )
2319    })
2320}
2321pub fn topk_kernel(device: &wgpu::Device) -> &'static Kernel {
2322    TOPK.get_or_init(|| build_kernel(device, "rlx-wgpu topk", TOPK_WGSL, "topk"))
2323}
2324pub fn welch_peaks_gpu_kernel(device: &wgpu::Device) -> &'static Kernel {
2325    WELCH_PEAKS_GPU.get_or_init(|| {
2326        build_kernel(
2327            device,
2328            "rlx-wgpu welch_peaks_gpu",
2329            WELCH_PEAKS_GPU_WGSL,
2330            "welch_peaks_gpu",
2331        )
2332    })
2333}
2334pub fn umap_knn_kernel(device: &wgpu::Device) -> &'static Kernel {
2335    UMAP_KNN.get_or_init(|| build_kernel(device, "rlx-wgpu umap_knn", UMAP_KNN_WGSL, "umap_knn"))
2336}
2337pub fn grouped_matmul_kernel(device: &wgpu::Device) -> &'static Kernel {
2338    GROUPED_MATMUL.get_or_init(|| {
2339        build_kernel(
2340            device,
2341            "rlx-wgpu grouped_matmul",
2342            GROUPED_MATMUL_WGSL,
2343            "grouped_matmul",
2344        )
2345    })
2346}
2347pub fn sample_kernel(device: &wgpu::Device) -> &'static Kernel {
2348    SAMPLE.get_or_init(|| build_kernel(device, "rlx-wgpu sample", SAMPLE_WGSL, "sample"))
2349}
2350pub fn selective_scan_kernel(device: &wgpu::Device) -> &'static Kernel {
2351    SELECTIVE_SCAN.get_or_init(|| {
2352        build_kernel(
2353            device,
2354            "rlx-wgpu selective_scan",
2355            SELECTIVE_SCAN_WGSL,
2356            "selective_scan",
2357        )
2358    })
2359}
2360pub fn mamba2_kernel(device: &wgpu::Device) -> &'static Kernel {
2361    MAMBA2.get_or_init(|| build_kernel(device, "rlx-wgpu mamba2", MAMBA2_WGSL, "mamba2"))
2362}
2363pub fn gru_kernel(device: &wgpu::Device) -> &'static Kernel {
2364    GRU.get_or_init(|| build_kernel(device, "rlx-wgpu gru", GRU_WGSL, "gru"))
2365}
2366pub fn rnn_kernel(device: &wgpu::Device) -> &'static Kernel {
2367    RNN.get_or_init(|| build_kernel(device, "rlx-wgpu rnn", RNN_WGSL, "rnn"))
2368}
2369pub fn dequant_matmul_kernel(device: &wgpu::Device) -> &'static Kernel {
2370    DEQUANT_MATMUL.get_or_init(|| {
2371        build_kernel(
2372            device,
2373            "rlx-wgpu dequant_matmul",
2374            DEQUANT_MATMUL_WGSL,
2375            "dequant_matmul",
2376        )
2377    })
2378}
2379pub fn dequant_gguf_kernel(device: &wgpu::Device) -> &'static Kernel {
2380    DEQUANT_GGUF.get_or_init(|| {
2381        build_kernel_3(
2382            device,
2383            "rlx-wgpu dequant_gguf",
2384            DEQUANT_GGUF_WGSL,
2385            "dequant_gguf",
2386        )
2387    })
2388}
2389pub fn matmul_bt_kernel(device: &wgpu::Device) -> &'static Kernel {
2390    MATMUL_BT.get_or_init(|| build_kernel(device, "rlx-wgpu matmul_bt", MATMUL_WGSL, "matmul_bt"))
2391}
2392pub fn dequant_gemv_gguf_kernel(device: &wgpu::Device) -> &'static Kernel {
2393    DEQUANT_GEMV_GGUF.get_or_init(|| {
2394        build_kernel_ro_u_ro_rw(
2395            device,
2396            "rlx-wgpu dequant_gemv_gguf",
2397            DEQUANT_GEMV_GGUF_WGSL,
2398            "dequant_gemv",
2399        )
2400    })
2401}
2402pub fn fused_residual_ln_kernel(device: &wgpu::Device) -> &'static Kernel {
2403    FUSED_RESIDUAL_LN.get_or_init(|| {
2404        build_kernel(
2405            device,
2406            "rlx-wgpu fused_residual_ln",
2407            FUSED_RESIDUAL_LN_WGSL,
2408            "fused_residual_ln",
2409        )
2410    })
2411}
2412pub fn fused_residual_ln_tee_kernel(device: &wgpu::Device) -> &'static Kernel {
2413    FUSED_RESIDUAL_LN_TEE.get_or_init(|| {
2414        build_kernel(
2415            device,
2416            "rlx-wgpu fused_residual_ln_tee",
2417            FUSED_RESIDUAL_LN_TEE_WGSL,
2418            "fused_residual_ln_tee",
2419        )
2420    })
2421}
2422pub fn fused_residual_rms_norm_kernel(device: &wgpu::Device) -> &'static Kernel {
2423    FUSED_RESIDUAL_RMS_NORM.get_or_init(|| {
2424        build_kernel(
2425            device,
2426            "rlx-wgpu fused_residual_rms_norm",
2427            FUSED_RESIDUAL_RMS_NORM_WGSL,
2428            "fused_residual_rms_norm",
2429        )
2430    })
2431}
2432pub fn matmul_qkv_kernel(device: &wgpu::Device) -> &'static Kernel {
2433    MATMUL_QKV
2434        .get_or_init(|| build_kernel(device, "rlx-wgpu matmul_qkv", MATMUL_QKV_WGSL, "matmul_qkv"))
2435}
2436pub fn matmul_qkv_coop_f32_kernel(device: &wgpu::Device) -> Option<&'static Kernel> {
2437    if !device
2438        .features()
2439        .contains(wgpu::Features::EXPERIMENTAL_COOPERATIVE_MATRIX)
2440    {
2441        return None;
2442    }
2443    Some(MATMUL_QKV_COOP_F32.get_or_init(|| {
2444        build_kernel(
2445            device,
2446            "rlx-wgpu matmul_qkv_coop_f32",
2447            MATMUL_QKV_COOP_F32_WGSL,
2448            "matmul_qkv_coop_f32",
2449        )
2450    }))
2451}
2452pub fn matmul_qkv_coop_f16_vk_f32acc_kernel(device: &wgpu::Device) -> Option<&'static Kernel> {
2453    if !coop_f16_vk_f32acc_device_ready(device) {
2454        return None;
2455    }
2456    MATMUL_QKV_COOP_F16_VK_F32ACC
2457        .get_or_init(|| {
2458            try_build_kernel_coop_f16_vk(
2459                device,
2460                "rlx-wgpu matmul_qkv_coop_f16_vk_f32acc",
2461                MATMUL_QKV_COOP_F16_VK_F32ACC_WGSL,
2462                "matmul_qkv_coop_f16_vk_f32acc",
2463            )
2464        })
2465        .as_ref()
2466}
2467
2468pub fn matmul_qkv_coop_f16_vk_widen_f32acc_kernel(
2469    device: &wgpu::Device,
2470) -> Option<&'static Kernel> {
2471    if !coop_f16_vk_f32acc_device_ready(device) {
2472        return None;
2473    }
2474    MATMUL_QKV_COOP_F16_VK_WIDEN_F32ACC
2475        .get_or_init(|| {
2476            try_build_kernel_coop_f16_vk(
2477                device,
2478                "rlx-wgpu matmul_qkv_coop_f16_vk_widen_f32acc",
2479                MATMUL_QKV_COOP_F16_VK_WIDEN_F32ACC_WGSL,
2480                "matmul_qkv_coop_f16_vk_widen_f32acc",
2481            )
2482        })
2483        .as_ref()
2484}
2485
2486pub fn matmul_qkv_coop_f16_vk_active_kernel(
2487    device: &wgpu::Device,
2488    n: u32,
2489) -> Option<&'static Kernel> {
2490    pick_coop_f16_vk_matmul(
2491        device,
2492        n,
2493        matmul_qkv_coop_f16_vk_kernel,
2494        matmul_qkv_coop_f16_vk_f32acc_kernel,
2495        matmul_qkv_coop_f16_vk_widen_kernel,
2496        matmul_qkv_coop_f16_vk_widen_f32acc_kernel,
2497    )
2498}
2499
2500pub fn matmul_qkv_coop_f16_vk_kernel(device: &wgpu::Device) -> Option<&'static Kernel> {
2501    if !coop_f16_vk_device_ready(device) {
2502        return None;
2503    }
2504    Some(MATMUL_QKV_COOP_F16_VK.get_or_init(|| {
2505        build_kernel_coop_f16_vk(
2506            device,
2507            "rlx-wgpu matmul_qkv_coop_f16_vk",
2508            MATMUL_QKV_COOP_F16_VK_WGSL,
2509            "matmul_qkv_coop_f16_vk",
2510        )
2511    }))
2512}
2513pub fn matmul_qkv_coop_f16_vk_widen_kernel(device: &wgpu::Device) -> Option<&'static Kernel> {
2514    if !coop_f16_vk_device_ready(device) {
2515        return None;
2516    }
2517    Some(MATMUL_QKV_COOP_F16_VK_WIDEN.get_or_init(|| {
2518        build_kernel_coop_f16_vk(
2519            device,
2520            "rlx-wgpu matmul_qkv_coop_f16_vk_widen",
2521            MATMUL_QKV_COOP_F16_VK_WIDEN_WGSL,
2522            "matmul_qkv_coop_f16_vk_widen",
2523        )
2524    }))
2525}