metrale_model_layers/layers/moe/
fp8_grouped_tc_w8a8.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-09-28: The opt-in W8A8 expert step of the grouped FP8 MoE decode
4//! (`moe_fp8_grouped_tc_w8a8.cu`): the layer input and the SiLU products are quantized to
5//! E4M3 per (row, 128 group), the checkpoint's declared `activation_scheme: dynamic`, and the
6//! FP8 weights go into E4M3 tensor-core MMAs undecoded.
7//!
8//! WHEN it runs is the weight-quantization policy's call, not this module's: the serve publishes
9//! whether the experts decode with FP8 activations ([`set_moe_expert_fp8_act`], the
10//! `--weight-quantization` policy's `fp8_decode_act` for the expert modules, published at serve
11//! setup); unpublished, the experts stay W8A16. This module says whether it CAN
12//! ([`MoeLayer::fp8_grouped_tc_w8a8_on`]: the tensor-core path is on, the three W8A8 kernels
13//! resolved and the shapes fit). Its output differs from the W8A16 kernels' by the activation
14//! rounding; a row's bits still do not depend on the other rows.
15//!
16//! Owner: model-layers (MoE).
17//! Invariants:
18//! - The first publication or read of the cell wins (`OnceLock`); the serve publishes before
19//!   the model runs, and anything that reads first fixes it at W8A16.
20//! - [`MoeLayer::run_fp8_grouped_w8a8`] launches nothing unless
21//!   [`MoeLayer::fp8_grouped_tc_w8a8_on`] holds and the buffers hold the layout.
22
23use super::*;
24
25static FP8_ACT: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
26
27/// 2026-09-28: Publish whether the MoE experts decode with FP8 activations (W8A8), as the
28/// weight-quantization policy decides for the expert modules. Returns the value in force; a
29/// caller that gets a different one should warn.
30pub fn set_moe_expert_fp8_act(fp8: bool) -> bool {
31    let _ = FP8_ACT.set(fp8);
32    *FP8_ACT.get().expect("just set")
33}
34
35/// 2026-09-28: Whether the MoE experts decode with FP8 activations: false (W8A16) unless the
36/// serve published otherwise.
37pub fn moe_expert_fp8_act() -> bool {
38    *FP8_ACT.get_or_init(|| false)
39}
40
41/// 2026-09-28: One grouped decode's inputs and outputs for the expert step.
42pub(super) struct GroupedExpertIo {
43    pub input: DevicePtr,
44    pub act: DevicePtr,
45    pub shared_act: DevicePtr,
46    pub expert_down_out: DevicePtr,
47    pub shared_out: DevicePtr,
48    pub rows: ops::Fp8GroupedW8a8Rows,
49}
50
51impl MoeLayer {
52    /// 2026-09-28: Whether this layer's grouped decode runs the W8A8 expert step: FP8 expert
53    /// activations published ([`moe_expert_fp8_act`]), the tensor-core W8A16 kernels'
54    /// conditions, the three W8A8 kernels, and gate+up and down in whole CTAs of their tiles.
55    pub(super) fn fp8_grouped_tc_w8a8_on(&self, hidden: usize, inter: usize) -> bool {
56        let (h, i) = (hidden as u32, inter as u32);
57        let k = &self.fp8_grouped_tc;
58        moe_expert_fp8_act()
59            && self.fp8_grouped_tc_on(hidden, inter)
60            && k.quant_w8a8.0 != 0
61            && k.gate_up_w8a8.0 != 0
62            && k.down_w8a8.0 != 0
63            && ops::fp8_grouped_tc_shape_ok(i, h, ops::FP8_GROUPED_GATE_UP_TC_W8A8)
64            && ops::fp8_grouped_tc_shape_ok(h, i, ops::FP8_GROUPED_DOWN_TC_W8A8)
65    }
66
67    /// 2026-09-28: Quantize the layer input, then W8A8 gate+up (quantized SiLU products) and
68    /// down, routed and shared, into `io.expert_down_out` / `io.shared_out`. The quantized
69    /// activations live in the SiLU buffers (`ops::Fp8GroupedW8a8Layout`).
70    pub(super) fn run_fp8_grouped_w8a8(
71        &self,
72        io: &GroupedExpertIo,
73        ctx: &ForwardContext,
74        stream: u64,
75    ) -> Result<()> {
76        let (Some(gp), Some(up), Some(dp), Some(sh)) = (
77            &self.fp8_gate_weight_ptrs,
78            &self.fp8_up_weight_ptrs,
79            &self.fp8_down_weight_ptrs,
80            &self.fp8_shared_expert,
81        ) else {
82            anyhow::bail!("W8A8 grouped decode: the FP8 expert tables are missing");
83        };
84        let cfg = ctx.config;
85        let (h, inter) = (cfg.hidden_size, cfg.moe_intermediate_size);
86        let m = io.rows.num_tokens as usize;
87        let lay = ops::Fp8GroupedW8a8Layout::new(
88            m,
89            cfg.num_experts_per_tok,
90            h,
91            inter,
92            ctx.buffers.expert_gate_out_bytes(),
93            ctx.buffers.logits_bytes(),
94        )?;
95        let k = &self.fp8_grouped_tc;
96        let (xq, xs) = (io.act.offset(lay.xq), io.act.offset(lay.xs));
97        let act = (io.act, io.act.offset(lay.act_s));
98        let sh_act = (io.shared_act, io.shared_act.offset(lay.sh_s));
99        let (h32, i32) = (h as u32, inter as u32);
100        ops::moe_act_quant_e4m3(
101            ctx.gpu,
102            k.quant_w8a8,
103            io.input,
104            xq,
105            xs,
106            m as u32,
107            h32,
108            stream,
109        )?;
110        ops::moe_expert_gate_up_act_fp8_grouped_tc_w8a8(
111            ctx.gpu,
112            k.gate_up_w8a8,
113            xq,
114            xs,
115            (gp.weight_ptrs, gp.scale_ptrs),
116            (up.weight_ptrs, up.scale_ptrs),
117            act,
118            &io.rows,
119            &sh.gate_proj,
120            &sh.up_proj,
121            sh_act,
122            i32,
123            h32,
124            stream,
125        )?;
126        ops::moe_expert_down_act_fp8_grouped_tc_w8a8(
127            ctx.gpu,
128            k.down_w8a8,
129            act,
130            (dp.weight_ptrs, dp.scale_ptrs),
131            io.expert_down_out,
132            &io.rows,
133            sh_act,
134            &sh.down_proj,
135            io.shared_out,
136            h32,
137            i32,
138            stream,
139        )
140    }
141}