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