Skip to main content

logicaffeine_compile/spatial/decode_engine/
vector.rs

1//! The vector unit's instruction set: sixteen f32 lanes over vector and scalar registers, every instruction one of
2//! the engine's f32 operations in the order [`super::numerics`] defines it, so a micro-program computes what the
3//! numerics function it is written from computes, bit for bit.
4//!
5//! An instruction over a vector runs element `i` in lane `i mod 16`: elementwise ones lane by lane, `Dot` and `Sum`
6//! accumulating each lane in index order and folding the lanes by the pairwise tree ([`super::numerics::fdot`]),
7//! `Max`, `AbsMax` and `ArgMax` each lane's largest folded the same way, an order the result does not depend on
8//! ([`super::numerics::fmax`], [`super::numerics::absmax`], [`super::numerics::argmax`]). Scalars are the
9//! reductions' results and the constants a program loads. The micro-programs here are the norms, the softmax, the
10//! gated products, the decay a Gated-DeltaNet block takes, the rotary turn, a cache row's int8 codes, the causal
11//! convolution and the greedy token, each the numerics function it replaces.
12
13use super::numerics::{
14    absmax, argmax, exp, f32_to_f16, fadd, fdot, fmax, fsub, fsum, hadamard, int8_codes, ldexp, ln1p, recip, rope, rsqrt,
15    sigmoid, silu, softplus, FpUnit, INV_127,
16};
17use crate::model::quant::f16_to_f32;
18
19/// A vector register.
20pub type VReg = u8;
21/// A scalar register.
22pub type SReg = u8;
23
24/// A function a lane applies to one element.
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub enum Func {
27    Exp,
28    Recip,
29    Rsqrt,
30    Sigmoid,
31    Silu,
32    Softplus,
33    Ln1p,
34    /// The sign flipped, exactly.
35    Neg,
36    /// Rounded to binary16 and read back ([`super::numerics::f32_to_f16`]): a value as an f16 store keeps it.
37    F16,
38}
39
40impl Func {
41    /// `f(x)`, its products on `fp`.
42    pub fn apply(self, fp: FpUnit, x: f32) -> f32 {
43        match self {
44            Func::Exp => exp(fp, x),
45            Func::Recip => recip(fp, x),
46            Func::Rsqrt => rsqrt(fp, x),
47            Func::Sigmoid => sigmoid(fp, x),
48            Func::Silu => silu(fp, x),
49            Func::Softplus => softplus(fp, x),
50            Func::Ln1p => ln1p(fp, x),
51            Func::Neg => -x,
52            Func::F16 => f16_to_f32(f32_to_f16(x)),
53        }
54    }
55}
56
57/// A two-operand f32 operation.
58#[derive(Clone, Copy, Debug, PartialEq, Eq)]
59pub enum Bin {
60    Mul,
61    Add,
62    Sub,
63}
64
65impl Bin {
66    /// `a ∘ b`, a product on `fp`.
67    pub fn apply(self, fp: FpUnit, a: f32, b: f32) -> f32 {
68        match self {
69            Bin::Mul => fp.mul(a, b),
70            Bin::Add => fadd(a, b),
71            Bin::Sub => fsub(a, b),
72        }
73    }
74}
75
76/// One instruction.
77#[derive(Clone, Copy, Debug, PartialEq)]
78pub enum Op {
79    /// `s[d] = Σ v[a]_i·v[b]_i`, lanes then the tree.
80    Dot { a: VReg, b: VReg, d: SReg },
81    /// `s[d] = Σ v[a]_i`, lanes then the tree.
82    Sum { a: VReg, d: SReg },
83    /// `s[d] = max_i v[a]_i` ([`super::numerics::fmax`]).
84    Max { a: VReg, d: SReg },
85    /// `v[d]_i = v[a]_i ∘ v[b]_i`.
86    Vec { op: Bin, a: VReg, b: VReg, d: VReg },
87    /// `v[d]_i = v[a]_i ∘ s[s]`.
88    VecScalar { op: Bin, a: VReg, s: SReg, d: VReg },
89    /// `v[d]_i = f(v[a]_i)`.
90    Map { f: Func, a: VReg, d: VReg },
91    /// `s[d] = s[a] ∘ s[b]`.
92    Scalar { op: Bin, a: SReg, b: SReg, d: SReg },
93    /// `s[d] = f(s[a])`.
94    ScalarMap { f: Func, a: SReg, d: SReg },
95    /// `s[d] = value`.
96    Const { value: f32, d: SReg },
97    /// `v[d] = v[a]` rotated by the Walsh–Hadamard transform, unnormalized.
98    Hadamard { a: VReg, d: VReg },
99    /// `v[d] = v[a]` with its first `2·len(v[cos])` elements turned in pairs by the rotary table `v[cos]`, `v[sin]`
100    /// ([`super::numerics::rope`]): pair `i` is `(i, i + len)` under NEOX, `(2i, 2i + 1)` otherwise.
101    Rotate { a: VReg, cos: VReg, sin: VReg, d: VReg, neox: bool },
102    /// `s[d]` = the index of `v[a]`'s largest element, the first of equals, as an f32 ([`super::numerics::argmax`]).
103    ArgMax { a: VReg, d: SReg },
104    /// `s[d]` = the largest magnitude in `v[a]` ([`super::numerics::absmax`]).
105    AbsMax { a: VReg, d: SReg },
106    /// `v[d]_i` = the int8 code of `v[a]_i` under the inverse scale `s[s]`, as an f32
107    /// ([`super::numerics::int8_codes`]).
108    Codes { a: VReg, s: SReg, d: VReg },
109    /// `v[d]_i = v[a]_i·2^n` ([`super::numerics::ldexp`]): exact while normal, infinite past the top, a zero of its sign
110    /// below the normals — not a product, which rounds a value just below the normals up into them.
111    Scale { a: VReg, n: i8, d: VReg },
112    /// `v[d]_i = v[c]_i + v[a]_i·v[b]_i`: the product rounded, then the sum — a `Mul` then an `Add`, in one pass.
113    MulAdd { a: VReg, b: VReg, c: VReg, d: VReg },
114    /// `v[d]_i = v[a]_i·v[b]_i + s[s]`: the product rounded, then the sum.
115    MulAddScalar { a: VReg, b: VReg, s: SReg, d: VReg },
116}
117
118impl Op {
119    /// The vector and scalar registers the instruction reads.
120    pub fn reads(self) -> (Vec<VReg>, Vec<SReg>) {
121        match self {
122            Op::Dot { a, b, .. } | Op::Vec { a, b, .. } => (vec![a, b], vec![]),
123            Op::Sum { a, .. } | Op::Max { a, .. } | Op::Map { a, .. } | Op::Hadamard { a, .. } | Op::ArgMax { a, .. } | Op::AbsMax { a, .. } | Op::Scale { a, .. } => (vec![a], vec![]),
124            Op::VecScalar { a, s, .. } | Op::Codes { a, s, .. } => (vec![a], vec![s]),
125            Op::Scalar { a, b, .. } => (vec![], vec![a, b]),
126            Op::ScalarMap { a, .. } => (vec![], vec![a]),
127            Op::Const { .. } => (vec![], vec![]),
128            Op::Rotate { a, cos, sin, .. } => (vec![a, cos, sin], vec![]),
129            Op::MulAdd { a, b, c, .. } => (vec![a, b, c], vec![]),
130            Op::MulAddScalar { a, b, s, .. } => (vec![a, b], vec![s]),
131        }
132    }
133
134    /// The scalar register the instruction writes, if it writes one.
135    pub fn writes_scalar(self) -> Option<SReg> {
136        match self {
137            Op::Dot { d, .. } | Op::Sum { d, .. } | Op::Max { d, .. } | Op::Scalar { d, .. } | Op::ScalarMap { d, .. } | Op::Const { d, .. } | Op::ArgMax { d, .. } | Op::AbsMax { d, .. } => Some(d),
138            Op::Vec { .. }
139            | Op::VecScalar { .. }
140            | Op::Map { .. }
141            | Op::Hadamard { .. }
142            | Op::Rotate { .. }
143            | Op::Codes { .. }
144            | Op::Scale { .. }
145            | Op::MulAdd { .. }
146            | Op::MulAddScalar { .. } => None,
147        }
148    }
149
150    /// The vector register the instruction writes, if it writes one.
151    pub fn writes(self) -> Option<VReg> {
152        match self {
153            Op::Vec { d, .. }
154            | Op::VecScalar { d, .. }
155            | Op::Map { d, .. }
156            | Op::Hadamard { d, .. }
157            | Op::Rotate { d, .. }
158            | Op::Codes { d, .. }
159            | Op::Scale { d, .. }
160            | Op::MulAdd { d, .. }
161            | Op::MulAddScalar { d, .. } => Some(d),
162            Op::Dot { .. } | Op::Sum { .. } | Op::Max { .. } | Op::Scalar { .. } | Op::ScalarMap { .. } | Op::Const { .. } | Op::ArgMax { .. } | Op::AbsMax { .. } => None,
163        }
164    }
165}
166
167/// The registers a program runs over.
168#[derive(Clone, Debug, Default)]
169pub struct Regs {
170    pub v: Vec<Vec<f32>>,
171    pub s: Vec<f32>,
172}
173
174impl Regs {
175    pub fn new(vectors: usize, scalars: usize) -> Regs {
176        Regs { v: vec![Vec::new(); vectors], s: vec![0.0; scalars] }
177    }
178}
179
180/// Run `program` over `regs`, every product on `fp`.
181pub fn run(fp: FpUnit, program: &[Op], regs: &mut Regs) {
182    for &op in program {
183        match op {
184            Op::Dot { a, b, d } => regs.s[d as usize] = fdot(fp, &regs.v[a as usize], &regs.v[b as usize]),
185            Op::Sum { a, d } => regs.s[d as usize] = fsum(&regs.v[a as usize]),
186            Op::Max { a, d } => regs.s[d as usize] = fmax(&regs.v[a as usize]),
187            Op::Vec { op, a, b, d } => {
188                let out: Vec<f32> = regs.v[a as usize].iter().zip(&regs.v[b as usize]).map(|(&x, &y)| op.apply(fp, x, y)).collect();
189                regs.v[d as usize] = out;
190            }
191            Op::VecScalar { op, a, s, d } => {
192                let k = regs.s[s as usize];
193                let out: Vec<f32> = regs.v[a as usize].iter().map(|&x| op.apply(fp, x, k)).collect();
194                regs.v[d as usize] = out;
195            }
196            Op::Map { f, a, d } => {
197                let out: Vec<f32> = regs.v[a as usize].iter().map(|&x| f.apply(fp, x)).collect();
198                regs.v[d as usize] = out;
199            }
200            Op::Scalar { op, a, b, d } => regs.s[d as usize] = op.apply(fp, regs.s[a as usize], regs.s[b as usize]),
201            Op::ScalarMap { f, a, d } => regs.s[d as usize] = f.apply(fp, regs.s[a as usize]),
202            Op::Const { value, d } => regs.s[d as usize] = value,
203            Op::Hadamard { a, d } => {
204                let mut out = regs.v[a as usize].clone();
205                hadamard(&mut out);
206                regs.v[d as usize] = out;
207            }
208            Op::Rotate { a, cos, sin, d, neox } => {
209                let mut out = regs.v[a as usize].clone();
210                rope(fp, &mut out, &regs.v[cos as usize], &regs.v[sin as usize], neox);
211                regs.v[d as usize] = out;
212            }
213            Op::ArgMax { a, d } => regs.s[d as usize] = argmax(&regs.v[a as usize]) as f32,
214            Op::AbsMax { a, d } => regs.s[d as usize] = absmax(&regs.v[a as usize]),
215            Op::Codes { a, s, d } => {
216                let out: Vec<f32> = int8_codes(fp, &regs.v[a as usize], regs.s[s as usize]).into_iter().map(f32::from).collect();
217                regs.v[d as usize] = out;
218            }
219            Op::Scale { a, n, d } => {
220                let out: Vec<f32> = regs.v[a as usize].iter().map(|&x| ldexp(x, n as i32)).collect();
221                regs.v[d as usize] = out;
222            }
223            Op::MulAdd { a, b, c, d } => {
224                let (x, y, z) = (&regs.v[a as usize], &regs.v[b as usize], &regs.v[c as usize]);
225                let out: Vec<f32> = x.iter().zip(y).zip(z).map(|((&x, &y), &z)| Bin::Add.apply(fp, z, Bin::Mul.apply(fp, x, y))).collect();
226                regs.v[d as usize] = out;
227            }
228            Op::MulAddScalar { a, b, s, d } => {
229                let k = regs.s[s as usize];
230                let out: Vec<f32> = regs.v[a as usize].iter().zip(&regs.v[b as usize]).map(|(&x, &y)| Bin::Add.apply(fp, Bin::Mul.apply(fp, x, y), k)).collect();
231                regs.v[d as usize] = out;
232            }
233        }
234    }
235}
236
237// ── The micro-programs ──────────────────────────────────────────────────────────────────────────────
238
239/// RMSNorm of `v[x]` by the weights `v[w]` into `v[out]`, `n` the vector's length: `rsqrt(Σx²·(1/n) + ε)`, then each
240/// `x·r·w`, `1/n` the reciprocal at `fp` held as a constant. Scalars 0..2 are its scratch, `v[t]` too.
241pub fn rmsnorm(fp: FpUnit, x: VReg, w: VReg, out: VReg, t: VReg, n: usize, eps: f32) -> Vec<Op> {
242    vec![
243        Op::Dot { a: x, b: x, d: 0 },
244        Op::Const { value: recip(fp, n as f32), d: 1 },
245        Op::Scalar { op: Bin::Mul, a: 0, b: 1, d: 0 },
246        Op::Const { value: eps, d: 2 },
247        Op::Scalar { op: Bin::Add, a: 0, b: 2, d: 0 },
248        Op::ScalarMap { f: Func::Rsqrt, a: 0, d: 0 },
249        Op::VecScalar { op: Bin::Mul, a: x, s: 0, d: t },
250        Op::Vec { op: Bin::Mul, a: t, b: w, d: out },
251    ]
252}
253
254/// L2 normalization of `v[x]` into `v[out]`: `x·rsqrt(Σx² + ε)`. Scalars 0..1 are its scratch.
255pub fn l2norm(x: VReg, out: VReg, eps: f32) -> Vec<Op> {
256    vec![
257        Op::Dot { a: x, b: x, d: 0 },
258        Op::Const { value: eps, d: 1 },
259        Op::Scalar { op: Bin::Add, a: 0, b: 1, d: 0 },
260        Op::ScalarMap { f: Func::Rsqrt, a: 0, d: 0 },
261        Op::VecScalar { op: Bin::Mul, a: x, s: 0, d: out },
262    ]
263}
264
265/// The softmax of the scores `v[x]` into `v[out]`: the maximum, each `exp(s − max)`, their sum, one reciprocal, the
266/// products. Scalars 0..1 are its scratch.
267pub fn softmax(x: VReg, out: VReg) -> Vec<Op> {
268    vec![
269        Op::Max { a: x, d: 0 },
270        Op::VecScalar { op: Bin::Sub, a: x, s: 0, d: out },
271        Op::Map { f: Func::Exp, a: out, d: out },
272        Op::Sum { a: out, d: 1 },
273        Op::ScalarMap { f: Func::Recip, a: 1, d: 1 },
274        Op::VecScalar { op: Bin::Mul, a: out, s: 1, d: out },
275    ]
276}
277
278/// The FFN's gated product `silu(v[g])·v[u]` into `v[out]`.
279pub fn swiglu(g: VReg, u: VReg, out: VReg) -> Vec<Op> {
280    vec![Op::Map { f: Func::Silu, a: g, d: out }, Op::Vec { op: Bin::Mul, a: out, b: u, d: out }]
281}
282
283/// Attention's output gate `v[o]·σ(v[g])` into `v[out]`, `v[t]` its scratch.
284pub fn sigmoid_gate(o: VReg, g: VReg, out: VReg, t: VReg) -> Vec<Op> {
285    vec![Op::Map { f: Func::Sigmoid, a: g, d: t }, Op::Vec { op: Bin::Mul, a: o, b: t, d: out }]
286}
287
288/// A Gated-DeltaNet head's decay and gate from the scalars `s[alpha]`, `s[dt]`, `s[a]`, `s[beta]`: the decay
289/// exponent `softplus(α + dt)·a` into `s[log_decay]`, the decay `exp` of it into `s[decay]`, the gate `σ(β)` into
290/// `s[gate]`.
291pub fn gdn_gates(alpha: SReg, dt: SReg, a: SReg, beta: SReg, log_decay: SReg, decay: SReg, gate: SReg) -> Vec<Op> {
292    vec![
293        Op::Scalar { op: Bin::Add, a: alpha, b: dt, d: log_decay },
294        Op::ScalarMap { f: Func::Softplus, a: log_decay, d: log_decay },
295        Op::Scalar { op: Bin::Mul, a: log_decay, b: a, d: log_decay },
296        Op::ScalarMap { f: Func::Exp, a: log_decay, d: decay },
297        Op::ScalarMap { f: Func::Sigmoid, a: beta, d: gate },
298    ]
299}
300
301/// Every value head's decay and gate at once from the vectors `v[alpha]`, `v[dt]`, `v[a]`, `v[beta]`: the decay
302/// exponents `softplus(α + dt)·a` into `v[log_decay]`, the decays into `v[decay]`, the gates `σ(β)` into `v[gate]`.
303pub fn delta_gates(alpha: VReg, dt: VReg, a: VReg, beta: VReg, log_decay: VReg, decay: VReg, gate: VReg) -> Vec<Op> {
304    vec![
305        Op::Vec { op: Bin::Add, a: alpha, b: dt, d: log_decay },
306        Op::Map { f: Func::Softplus, a: log_decay, d: log_decay },
307        Op::Vec { op: Bin::Mul, a: log_decay, b: a, d: log_decay },
308        Op::Map { f: Func::Exp, a: log_decay, d: decay },
309        Op::Map { f: Func::Sigmoid, a: beta, d: gate },
310    ]
311}
312
313/// A head turned by its position's rotary table into `v[out]` ([`super::numerics::rope`]).
314pub fn rotary(x: VReg, cos: VReg, sin: VReg, out: VReg, neox: bool) -> Vec<Op> {
315    vec![Op::Rotate { a: x, cos, sin, d: out, neox }]
316}
317
318/// A cache row as int8 ([`super::numerics::cache_row`]): the stored scale `amax/127` rounded to f16 into
319/// `s[scale]`, the codes under its reciprocal into `v[codes]`. Scalar `s[scale] + 1` is its scratch.
320pub fn cache_row(x: VReg, codes: VReg, scale: SReg) -> Vec<Op> {
321    let t = scale + 1;
322    vec![
323        Op::AbsMax { a: x, d: scale },
324        Op::Const { value: INV_127, d: t },
325        Op::Scalar { op: Bin::Mul, a: scale, b: t, d: scale },
326        Op::ScalarMap { f: Func::F16, a: scale, d: scale },
327        Op::ScalarMap { f: Func::Recip, a: scale, d: t },
328        Op::Codes { a: x, s: t, d: codes },
329    ]
330}
331
332/// The causal convolution of a Gated-DeltaNet block's channels through SiLU into `v[out]`
333/// ([`super::numerics::conv`]): the ring's rows `v[ring[j]]`, oldest first, against the kernel's taps `v[taps[j]]`,
334/// then the token's row `v[x]` against the last tap, each channel's products summed in tap order from `+0`, a
335/// multiply-add a tap. The kernel is held tap by tap (`taps[j]_ch` the channel's tap `j`); `v[acc]` is its scratch,
336/// scalar 0 the zero.
337pub fn conv(ring: &[VReg], x: VReg, taps: &[VReg], acc: VReg, out: VReg) -> Vec<Op> {
338    assert_eq!(taps.len(), ring.len() + 1, "a tap for every ring row and the token's own");
339    let mut p = vec![Op::Const { value: 0.0, d: 0 }];
340    let rows = ring.iter().copied().chain(std::iter::once(x));
341    for (j, (row, &tap)) in rows.zip(taps).enumerate() {
342        p.push(if j == 0 { Op::MulAddScalar { a: row, b: tap, s: 0, d: acc } } else { Op::MulAdd { a: row, b: tap, c: acc, d: acc } });
343    }
344    p.push(Op::Map { f: Func::Silu, a: acc, d: out });
345    p
346}
347
348/// A Gated-DeltaNet head's read normalized by the weights `v[w]` and gated by `silu(v[z])` into `v[out]`, `n` the
349/// head's width. `v[t]` and scalars 0..2 are its scratch.
350pub fn gdn_out(fp: FpUnit, o: VReg, w: VReg, z: VReg, out: VReg, t: VReg, n: usize, eps: f32) -> Vec<Op> {
351    let mut p = rmsnorm(fp, o, w, out, t, n, eps);
352    p.push(Op::Map { f: Func::Silu, a: z, d: t });
353    p.push(Op::Vec { op: Bin::Mul, a: out, b: t, d: out });
354    p
355}
356
357/// The greedy token of the logits `v[x]` into `s[d]`.
358pub fn greedy(x: VReg, d: SReg) -> Vec<Op> {
359    vec![Op::ArgMax { a: x, d }]
360}