metrale_model_layers/layers/ops/
fp8_moe.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-09-25: Launchers for the FP8 MoE batch-2/3 decode kernels, the NVFP4
4//! transposed-weight single-token MoE kernels, the uint8 transposes, and
5//! `causal_conv1d_fwd` and `bf16_concat`.
6//!
7//! Owner: model-layers ops.
8//! Invariants: none beyond the types.
9
10#![allow(unused_imports)]
11
12use anyhow::Result;
13use metrale_gpu_runtime::gpu::{DevicePtr, GpuBackend, KernelHandle};
14use metrale_gpu_runtime::kernel_args::{KernelLaunch, div_ceil};
15
16use crate::layers::moe;
17use crate::weight_map::{DenseWeight, Fp8DenseWeight, Fp8Weight, QuantizedWeight};
18
19use super::*;
20
21/// 2026-09-25: Causal depthwise conv1d followed by SiLU over a full sequence
22/// (kernel `causal_conv1d_fwd`), launched without a bias. Input
23/// `[batch, dim, seq_len]` BF16 (channel-first), weight `[dim, d_conv]` BF16,
24/// output `[batch, dim, seq_len]` BF16.
25#[allow(clippy::too_many_arguments)]
26pub fn conv1d_fwd(
27    gpu: &dyn GpuBackend,
28    kernel: KernelHandle,
29    input: DevicePtr,
30    weight: &DenseWeight,
31    output: DevicePtr,
32    batch: u32,
33    dim: u32,
34    seq_len: u32,
35    d_conv: u32,
36    stream: u64,
37) -> Result<()> {
38    let block_x = std::cmp::min(seq_len, 1024);
39    KernelLaunch::new(gpu, kernel)
40        .grid([dim, batch, 1])
41        .block([block_x, 1, 1])
42        .arg_ptr(input)
43        .arg_ptr(weight.weight)
44        .arg_ptr(DevicePtr::NULL)
45        .arg_ptr(output)
46        .arg_u32(batch)
47        .arg_u32(dim)
48        .arg_u32(seq_len)
49        .arg_u32(d_conv)
50        .launch(stream)
51}
52
53/// 2026-09-25: BF16 concatenation (kernel `bf16_concat`):
54/// `out[0..N] = a[0..N]`, `out[N..2N] = b[0..N]`.
55pub fn bf16_concat(
56    gpu: &dyn GpuBackend,
57    kernel: KernelHandle,
58    a: DevicePtr,
59    b: DevicePtr,
60    output: DevicePtr,
61    n: u32,
62    stream: u64,
63) -> Result<()> {
64    KernelLaunch::new(gpu, kernel)
65        .grid([div_ceil(n, 256), 1, 1])
66        .block([256, 1, 1])
67        .arg_ptr(a)
68        .arg_ptr(b)
69        .arg_ptr(output)
70        .arg_u32(n)
71        .launch(stream)
72}
73
74/// 2026-09-25: FP8 fused gate+up GEMV over the routed experts and the shared
75/// expert, for two tokens (`moe/forward_k2.rs`).
76#[allow(clippy::too_many_arguments)]
77pub fn moe_expert_gate_up_shared_fp8_batch2(
78    gpu: &dyn GpuBackend,
79    kernel: KernelHandle,
80    input: DevicePtr,
81    gp_w: DevicePtr,
82    gp_s: DevicePtr,
83    gate_out: DevicePtr,
84    up_w: DevicePtr,
85    up_s: DevicePtr,
86    up_out: DevicePtr,
87    indices: DevicePtr,
88    sh_gate: &Fp8Weight,
89    sh_gate_out: DevicePtr,
90    sh_up: &Fp8Weight,
91    sh_up_out: DevicePtr,
92    n: u32,
93    k: u32,
94    top_k: u32,
95    stream: u64,
96) -> Result<()> {
97    KernelLaunch::new(gpu, kernel)
98        .grid([div_ceil(n, 8), 2 * (top_k + 1), 2])
99        .block([128, 1, 1])
100        .arg_ptr(input)
101        .arg_ptr(gp_w)
102        .arg_ptr(gp_s)
103        .arg_ptr(gate_out)
104        .arg_ptr(up_w)
105        .arg_ptr(up_s)
106        .arg_ptr(up_out)
107        .arg_ptr(indices)
108        .arg_ptr(sh_gate.weight)
109        .arg_ptr(sh_gate.row_scale)
110        .arg_ptr(sh_gate_out)
111        .arg_ptr(sh_up.weight)
112        .arg_ptr(sh_up.row_scale)
113        .arg_ptr(sh_up_out)
114        .arg_u32(n)
115        .arg_u32(k)
116        .arg_u32(top_k)
117        .launch(stream)
118}
119
120/// 2026-09-25: FP8 fused SiLU+down GEMV over the routed experts and the shared
121/// expert, for two tokens.
122#[allow(clippy::too_many_arguments)]
123pub fn moe_expert_silu_down_shared_fp8_batch2(
124    gpu: &dyn GpuBackend,
125    kernel: KernelHandle,
126    gate_out: DevicePtr,
127    up_out: DevicePtr,
128    dp_w: DevicePtr,
129    dp_s: DevicePtr,
130    output: DevicePtr,
131    indices: DevicePtr,
132    sh_gate_in: DevicePtr,
133    sh_up_in: DevicePtr,
134    sh_down: &Fp8Weight,
135    sh_down_out: DevicePtr,
136    n: u32,
137    k: u32,
138    top_k: u32,
139    stream: u64,
140) -> Result<()> {
141    KernelLaunch::new(gpu, kernel)
142        .grid([div_ceil(n, 8), 2 * (top_k + 1), 1])
143        .block([128, 1, 1])
144        .arg_ptr(gate_out)
145        .arg_ptr(up_out)
146        .arg_ptr(dp_w)
147        .arg_ptr(dp_s)
148        .arg_ptr(output)
149        .arg_ptr(indices)
150        .arg_ptr(sh_gate_in)
151        .arg_ptr(sh_up_in)
152        .arg_ptr(sh_down.weight)
153        .arg_ptr(sh_down.row_scale)
154        .arg_ptr(sh_down_out)
155        .arg_u32(n)
156        .arg_u32(k)
157        .arg_u32(top_k)
158        .launch(stream)
159}
160
161/// 2026-09-25: FP8 fused gate+up GEMV over the routed experts and the shared
162/// expert, for three tokens (`moe/forward_k3.rs`).
163#[allow(clippy::too_many_arguments)]
164pub fn moe_expert_gate_up_shared_fp8_batch3(
165    gpu: &dyn GpuBackend,
166    kernel: KernelHandle,
167    input: DevicePtr,
168    gp_w: DevicePtr,
169    gp_s: DevicePtr,
170    gate_out: DevicePtr,
171    up_w: DevicePtr,
172    up_s: DevicePtr,
173    up_out: DevicePtr,
174    indices: DevicePtr,
175    sh_gate: &Fp8Weight,
176    sh_gate_out: DevicePtr,
177    sh_up: &Fp8Weight,
178    sh_up_out: DevicePtr,
179    n: u32,
180    k: u32,
181    top_k: u32,
182    stream: u64,
183) -> Result<()> {
184    KernelLaunch::new(gpu, kernel)
185        .grid([div_ceil(n, 8), 3 * (top_k + 1), 2])
186        .block([128, 1, 1])
187        .arg_ptr(input)
188        .arg_ptr(gp_w)
189        .arg_ptr(gp_s)
190        .arg_ptr(gate_out)
191        .arg_ptr(up_w)
192        .arg_ptr(up_s)
193        .arg_ptr(up_out)
194        .arg_ptr(indices)
195        .arg_ptr(sh_gate.weight)
196        .arg_ptr(sh_gate.row_scale)
197        .arg_ptr(sh_gate_out)
198        .arg_ptr(sh_up.weight)
199        .arg_ptr(sh_up.row_scale)
200        .arg_ptr(sh_up_out)
201        .arg_u32(n)
202        .arg_u32(k)
203        .arg_u32(top_k)
204        .launch(stream)
205}
206
207/// 2026-09-25: FP8 fused SiLU+down GEMV over the routed experts and the shared
208/// expert, for three tokens.
209#[allow(clippy::too_many_arguments)]
210pub fn moe_expert_silu_down_shared_fp8_batch3(
211    gpu: &dyn GpuBackend,
212    kernel: KernelHandle,
213    gate_out: DevicePtr,
214    up_out: DevicePtr,
215    dp_w: DevicePtr,
216    dp_s: DevicePtr,
217    output: DevicePtr,
218    indices: DevicePtr,
219    sh_gate_in: DevicePtr,
220    sh_up_in: DevicePtr,
221    sh_down: &Fp8Weight,
222    sh_down_out: DevicePtr,
223    n: u32,
224    k: u32,
225    top_k: u32,
226    stream: u64,
227) -> Result<()> {
228    KernelLaunch::new(gpu, kernel)
229        .grid([div_ceil(n, 8), 3 * (top_k + 1), 1])
230        .block([128, 1, 1])
231        .arg_ptr(gate_out)
232        .arg_ptr(up_out)
233        .arg_ptr(dp_w)
234        .arg_ptr(dp_s)
235        .arg_ptr(output)
236        .arg_ptr(indices)
237        .arg_ptr(sh_gate_in)
238        .arg_ptr(sh_up_in)
239        .arg_ptr(sh_down.weight)
240        .arg_ptr(sh_down.row_scale)
241        .arg_ptr(sh_down_out)
242        .arg_u32(n)
243        .arg_u32(k)
244        .arg_u32(top_k)
245        .launch(stream)
246}
247
248#[allow(clippy::too_many_arguments)]
249/// 2026-09-25: Single-matrix uint8 transpose `[rows, cols] -> [cols, rows]` on
250/// the GPU (`transpose_u8.cu`, 32x32 shared-memory tiles). The quantized
251/// weight transposes in `weight_map/quantized/transpose.rs` call it.
252pub fn transpose_u8(
253    gpu: &dyn GpuBackend,
254    kernel: KernelHandle,
255    src: DevicePtr,
256    dst: DevicePtr,
257    rows: u32,
258    cols: u32,
259    stream: u64,
260) -> Result<()> {
261    KernelLaunch::new(gpu, kernel)
262        .grid([div_ceil(cols, 32), div_ceil(rows, 32), 1])
263        .block([32, 8, 1])
264        .arg_ptr(src)
265        .arg_ptr(dst)
266        .arg_u32(rows)
267        .arg_u32(cols)
268        .launch(stream)
269}
270
271/// 2026-09-25: Per-expert uint8 transpose for the MoE down_proj relayout:
272/// reads expert `e`'s `[rows, cols]` matrix through `src_ptrs[e]` and writes
273/// it as `[cols, rows]` through `dst_ptrs[e]`. A NULL entry in either table
274/// skips that expert.
275pub fn moe_transpose_u8_batched(
276    gpu: &dyn GpuBackend,
277    kernel: KernelHandle,
278    src_ptrs: DevicePtr,
279    dst_ptrs: DevicePtr,
280    rows: u32,
281    cols: u32,
282    num_experts: u32,
283    stream: u64,
284) -> Result<()> {
285    KernelLaunch::new(gpu, kernel)
286        .grid([div_ceil(cols, 32), div_ceil(rows, 32), num_experts])
287        .block([32, 8, 1])
288        .arg_ptr(src_ptrs)
289        .arg_ptr(dst_ptrs)
290        .arg_u32(rows)
291        .arg_u32(cols)
292        .launch(stream)
293}
294
295// 2026-09-25: Launchers for the transposed-weight decode MoE kernels
296// (`kernels/gb10/common/moe_shared_expert_fused*_t.cu`). Their expert pointer
297// tables and shared-expert weights must hold the transposed layouts, `[K/2, N]`
298// for NVFP4 and `[K, N]` for FP8; the kernels take no layout flag.
299
300// 2026-09-25: Block size of the transposed-layout decode kernels: one warp,
301// one output per thread. Must equal `BLOCK_SIZE` in those `.cu` files, which
302// index outputs as `blockIdx.x * BLOCK_SIZE + threadIdx.x`.
303pub(super) const T_BLOCK: u32 = 32;
304
305/// 2026-09-25: NVFP4 fused gate+up GEMV over transposed weights, for one
306/// token.
307#[allow(clippy::too_many_arguments)]
308pub fn moe_expert_gate_up_shared_t(
309    gpu: &dyn GpuBackend,
310    kernel: KernelHandle,
311    input: DevicePtr,
312    gate_packed_t_ptrs: DevicePtr,
313    gate_scale_t_ptrs: DevicePtr,
314    gate_scale2_vals: DevicePtr,
315    gate_out: DevicePtr,
316    up_packed_t_ptrs: DevicePtr,
317    up_scale_t_ptrs: DevicePtr,
318    up_scale2_vals: DevicePtr,
319    up_out: DevicePtr,
320    expert_indices: DevicePtr,
321    sh_gate_t: &QuantizedWeight,
322    sh_gate_out: DevicePtr,
323    sh_up_t: &QuantizedWeight,
324    sh_up_out: DevicePtr,
325    n: u32,
326    k: u32,
327    top_k: u32,
328    stream: u64,
329) -> Result<()> {
330    KernelLaunch::new(gpu, kernel)
331        .grid([div_ceil(n, T_BLOCK), top_k + 1, 2])
332        .block([T_BLOCK, 1, 1])
333        .arg_ptr(input)
334        .arg_ptr(gate_packed_t_ptrs)
335        .arg_ptr(gate_scale_t_ptrs)
336        .arg_ptr(gate_scale2_vals)
337        .arg_ptr(gate_out)
338        .arg_ptr(up_packed_t_ptrs)
339        .arg_ptr(up_scale_t_ptrs)
340        .arg_ptr(up_scale2_vals)
341        .arg_ptr(up_out)
342        .arg_ptr(expert_indices)
343        .arg_ptr(sh_gate_t.weight)
344        .arg_ptr(sh_gate_t.weight_scale)
345        .arg_f32(sh_gate_t.weight_scale_2)
346        .arg_ptr(sh_gate_out)
347        .arg_ptr(sh_up_t.weight)
348        .arg_ptr(sh_up_t.weight_scale)
349        .arg_f32(sh_up_t.weight_scale_2)
350        .arg_ptr(sh_up_out)
351        .arg_u32(n)
352        .arg_u32(k)
353        .arg_u32(top_k)
354        .launch(stream)
355}
356
357/// 2026-09-25: NVFP4 fused SiLU+down GEMV over transposed weights, for one
358/// token.
359#[allow(clippy::too_many_arguments)]
360pub fn moe_expert_silu_down_shared_t(
361    gpu: &dyn GpuBackend,
362    kernel: KernelHandle,
363    gate_out: DevicePtr,
364    up_out: DevicePtr,
365    packed_t_ptrs: DevicePtr,
366    scale_t_ptrs: DevicePtr,
367    scale2_vals: DevicePtr,
368    output: DevicePtr,
369    expert_indices: DevicePtr,
370    sh_gate_in: DevicePtr,
371    sh_up_in: DevicePtr,
372    sh_down_t: &QuantizedWeight,
373    sh_down_out: DevicePtr,
374    n: u32,
375    k: u32,
376    top_k: u32,
377    stream: u64,
378) -> Result<()> {
379    let smem_bytes = (k as usize * std::mem::size_of::<f32>()) as u32;
380    KernelLaunch::new(gpu, kernel)
381        .grid([div_ceil(n, T_BLOCK), top_k + 1, 1])
382        .block([T_BLOCK, 1, 1])
383        .shared_mem(smem_bytes)
384        .arg_ptr(gate_out)
385        .arg_ptr(up_out)
386        .arg_ptr(packed_t_ptrs)
387        .arg_ptr(scale_t_ptrs)
388        .arg_ptr(scale2_vals)
389        .arg_ptr(output)
390        .arg_ptr(expert_indices)
391        .arg_ptr(sh_gate_in)
392        .arg_ptr(sh_up_in)
393        .arg_ptr(sh_down_t.weight)
394        .arg_ptr(sh_down_t.weight_scale)
395        .arg_f32(sh_down_t.weight_scale_2)
396        .arg_ptr(sh_down_out)
397        .arg_u32(n)
398        .arg_u32(k)
399        .arg_u32(top_k)
400        .launch(stream)
401}