Skip to main content

tritium_core/
shape.rs

1//! GEMM problem geometry.
2
3/// Dimensions of a matmul `C[M,N] = A[M,K] · Wᵀ`, where `W` is the `[N, K]`
4/// ternary weight (row-major, output-major) and `A` is the activation `[M, K]`.
5///
6/// - `m` — activation rows (batch × sequence).
7/// - `n` — output features (weight rows).
8/// - `k` — contraction / input features (weight + activation columns).
9#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
10pub struct GemmShape {
11    /// Activation rows (batch × sequence).
12    pub m: usize,
13    /// Output features (weight rows).
14    pub n: usize,
15    /// Contraction / input features (weight + activation columns).
16    pub k: usize,
17}
18
19impl GemmShape {
20    /// Construct a shape from its `m`, `n`, `k` dimensions.
21    #[inline]
22    pub const fn new(m: usize, n: usize, k: usize) -> Self {
23        Self { m, n, k }
24    }
25
26    /// Multiply-accumulate count `M·N·K`. For ternary this is the count of
27    /// add/sub/skip ops, not true FMAs — the metric backends report throughput on.
28    #[inline]
29    pub const fn macs(&self) -> u64 {
30        (self.m as u64) * (self.n as u64) * (self.k as u64)
31    }
32
33    /// Whether the operand/output buffer lengths are internally consistent.
34    #[inline]
35    pub const fn buffers_fit(&self, act_len: usize, weight_len: usize, out_len: usize) -> bool {
36        act_len == self.m * self.k && weight_len == self.n * self.k && out_len == self.m * self.n
37    }
38}
39
40/// Geometry of a ternary 1-D convolution `Y[B, C_out, L_out] = scale ⊙ conv1d(X, W)` — the codec's
41/// conv op (ADR 0030). The weight is packed 2-D `[C_out, (C_in/groups)·K]` (the per-output-channel
42/// ternary reshape), so `k_g()` is the matmul contraction and `n_g()` the per-group output count.
43#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
44pub struct ConvShape {
45    /// Batch size.
46    pub batch: usize,
47    /// Input channels (divisible by `groups`).
48    pub c_in: usize,
49    /// Output channels (divisible by `groups`).
50    pub c_out: usize,
51    /// Input length.
52    pub l_in: usize,
53    /// Kernel size.
54    pub k: usize,
55    /// Stride (≥ 1).
56    pub stride: usize,
57    /// Dilation (≥ 1).
58    pub dilation: usize,
59    /// Left zero-padding.
60    pub pad_left: usize,
61    /// Right zero-padding.
62    pub pad_right: usize,
63    /// Convolution groups (≥ 1).
64    pub groups: usize,
65}
66
67impl ConvShape {
68    /// Output length, or `0` if the dilated kernel is wider than the padded input (or the geometry is
69    /// degenerate).
70    #[inline]
71    pub const fn l_out(&self) -> usize {
72        if self.k == 0 || self.stride == 0 {
73            return 0;
74        }
75        let eff = self.dilation * (self.k - 1) + 1;
76        let padded = self.l_in + self.pad_left + self.pad_right;
77        if padded < eff {
78            return 0;
79        }
80        (padded - eff) / self.stride + 1
81    }
82
83    /// Input channels per group `C_in/groups`.
84    #[inline]
85    pub const fn c_in_pg(&self) -> usize {
86        self.c_in / self.groups
87    }
88
89    /// Output channels per group `N_g = C_out/groups`.
90    #[inline]
91    pub const fn n_g(&self) -> usize {
92        self.c_out / self.groups
93    }
94
95    /// Flattened per-output-channel weight width `K_g = (C_in/groups)·K`.
96    #[inline]
97    pub const fn k_g(&self) -> usize {
98        self.c_in_pg() * self.k
99    }
100
101    /// Whether the geometry is well-formed and the buffers are the right length (`x=B·C_in·L_in`,
102    /// `weights=C_out·K_g`, `scale=C_out`, `out=B·C_out·L_out`).
103    #[inline]
104    pub fn buffers_fit(
105        &self,
106        x_len: usize,
107        weight_len: usize,
108        scale_len: usize,
109        out_len: usize,
110    ) -> bool {
111        self.groups != 0
112            && self.k != 0
113            && self.stride != 0
114            && self.dilation != 0
115            && self.c_in.is_multiple_of(self.groups)
116            && self.c_out.is_multiple_of(self.groups)
117            && self.l_out() > 0
118            && x_len == self.batch * self.c_in * self.l_in
119            && weight_len == self.c_out * self.k_g()
120            && scale_len == self.c_out
121            && out_len == self.batch * self.c_out * self.l_out()
122    }
123}