metrale_model_layers/layers/
w8a8_layer.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-09-28: The W8A8 decode weights a layer carries when its checkpoint
4//! declares FP8 weights and FP8 activations for a projection (compressed-tensors
5//! `float-quantized` groups, HF `fp8`), and the shared per-model resources they
6//! run with (`ops::w8a8_proj`).
7//!
8//! A layer holding one of these runs that projection W8A8 at every decode row
9//! count in `1..=ops::W8A8_MAX_ROWS`, ahead of its other decode arms; wider
10//! launches and prefill keep the layer's other copies. Whether a checkpoint's
11//! projection gets one is the loader's decision (the declared-precision
12//! policy); these types only say that it can run.
13//!
14//! Owner: model-layers.
15//! Invariants:
16//! - Every `W8a8Ctx` of one model shares one scratch; the W8A8 projections run
17//!   on the decode stream only, one after another, so the quantize -> GEMV
18//!   pairs never interleave.
19//! - A row's output bits do not depend on how many rows share the launch
20//!   (`ops::w8a8_decode`), so a W8A8 projection is row-invariant under every
21//!   row-tier policy.
22
23use anyhow::Result;
24use metrale_gpu_runtime::gpu::{DevicePtr, GpuBackend};
25
26use crate::layers::ops::{self, W8a8Kernels, W8a8Scratch, W8a8Weight};
27
28/// 2026-09-28: The kernels and the quantized-activation scratch one model's
29/// W8A8 projections share.
30#[derive(Clone, Copy, Debug)]
31pub struct W8a8Ctx {
32    pub kernels: W8a8Kernels,
33    pub scratch: W8a8Scratch,
34}
35
36impl W8a8Ctx {
37    /// 2026-09-28: Look the kernels up and allocate a scratch for a `max_k`-wide
38    /// activation of `ops::W8A8_MAX_ROWS` rows.
39    pub fn new(gpu: &dyn GpuBackend, max_k: u32) -> Result<Self> {
40        Ok(Self {
41            kernels: W8a8Kernels::load(gpu),
42            scratch: W8a8Scratch::alloc(gpu, max_k)?,
43        })
44    }
45
46    /// 2026-09-28: Whether `w` can run W8A8 at `rows` rows.
47    pub fn available(&self, w: &W8a8Weight, rows: usize) -> bool {
48        ops::w8a8_decode_available(&self.kernels, w, rows, &self.scratch)
49    }
50
51    /// 2026-09-28: `out[rows, ldc]` = W8A8 of `x[rows, ldx]` (BF16) and `w`;
52    /// `Ok(false)`, launching nothing, when not [`Self::available`].
53    #[allow(clippy::too_many_arguments)]
54    pub fn proj(
55        &self,
56        gpu: &dyn GpuBackend,
57        w: &W8a8Weight,
58        x: DevicePtr,
59        ldx: u32,
60        rows: usize,
61        out: DevicePtr,
62        ldc: u32,
63        stream: u64,
64    ) -> Result<bool> {
65        if !self.available(w, rows) {
66            return Ok(false);
67        }
68        ops::w8a8_proj(
69            gpu,
70            &self.kernels,
71            w,
72            x,
73            ldx,
74            rows,
75            out,
76            ldc,
77            &self.scratch,
78            stream,
79        )?;
80        Ok(true)
81    }
82
83    /// 2026-09-28: The down projection of a SiLU FFN: `out` = W8A8 of
84    /// `bf16(silu(gate) * up)` (gate and up `[rows, ld]` BF16) and `w`, with the
85    /// SiLU product quantized in the same launch; `Ok(false)` when not available.
86    #[allow(clippy::too_many_arguments)]
87    pub fn silu_proj(
88        &self,
89        gpu: &dyn GpuBackend,
90        w: &W8a8Weight,
91        gate: DevicePtr,
92        up: DevicePtr,
93        ld: u32,
94        rows: usize,
95        out: DevicePtr,
96        ldc: u32,
97        stream: u64,
98    ) -> Result<bool> {
99        if !self.available(w, rows) {
100            return Ok(false);
101        }
102        ops::w8a8_act_quant_silu(
103            gpu,
104            &self.kernels,
105            w.scale(),
106            gate,
107            up,
108            ld,
109            rows,
110            w.k(),
111            &self.scratch,
112            stream,
113        )?;
114        ops::w8a8_gemv(gpu, &self.kernels, w, &self.scratch, rows, out, ldc, stream)?;
115        Ok(true)
116    }
117}
118
119/// 2026-09-28: The input and output projections of an attention layer
120/// (Q|K|V stacked, O) or a GDN layer (QKV|Z stacked, out_proj).
121#[derive(Clone, Copy, Debug)]
122pub struct W8a8Mixer {
123    pub ctx: W8a8Ctx,
124    pub input: W8a8Weight,
125    pub output: W8a8Weight,
126}
127
128/// 2026-09-28: A SiLU dense FFN's gate, up and down projections.
129#[derive(Clone, Copy, Debug)]
130pub struct W8a8Ffn {
131    pub ctx: W8a8Ctx,
132    pub gate: W8a8Weight,
133    pub up: W8a8Weight,
134    pub down: W8a8Weight,
135}