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