Skip to main content

logicaffeine_compile/spatial/decode_engine/
machine.rs

1//! The decode engine's machine: a step as the jobs its sequencer runs over the card's memory, every byte where it
2//! lives.
3//!
4//! [`super::program`] says what a step computes; the machine says where each number is kept and how the units move
5//! it. The card's memory ([`Layout`]) holds the weight image — every tensor's GGUF bytes in the order the program first
6//! reads them — the rotary tables, and a region for every sequence the machine holds: per Gated-DeltaNet block its
7//! conv ring, its memory and its log, per attention block its key and value caches, each in the format the knobs name.
8//! A step's rows are its row table: each row's sequence, position and token.
9//!
10//! [`lower`] turns each command into one [`Job`]. The vector unit's jobs ([`VectorJob`]) name what each pass loads
11//! into the vector registers — a span of a token-row's slot, a tensor's elements, the row's rotary table, its
12//! sequence's ring — the micro-program it runs and what it stores, every pass a row or a head of a row. The array's,
13//! the recurrence unit's and the attention unit's jobs name their tensors, slots and blocks. [`Machine::step`] runs the
14//! jobs over the memory, reading every value as stored and writing it as stored, and computes what
15//! [`super::model::EngineModel::step`] computes, bit for bit: the jobs are what the sequencer issues, and the memory
16//! is what the card holds.
17
18use std::collections::{HashMap, HashSet};
19
20use crate::model::quant::{f16_to_f32, GgmlType};
21use crate::model::reference::WeightSource;
22use crate::model::spec::{Mixer, ModelSpec, Rope};
23use crate::spatial::serving::{AttentionMath, StateFlush};
24
25use super::model::{rotary_factors, rotation, runs, CacheFormat, EngineError, EngineNumerics, Replay, StateFormat};
26use super::numerics::{
27    attend_int8, attend_vector, f32_to_f16, fdot, gdn_flush, gdn_fold_trailing, gdn_read, gdn_row, ldexp, quantize, quantize_blocks,
28    reads, silu, fadd, ActRow, ActScaling, CodedRow, FpUnit, Logged, GROUP,
29};
30use super::program::{Command, Program, Slot};
31use super::vector::{self, Op, Regs, SReg, VReg};
32
33type R<T> = Result<T, EngineError>;
34
35/// Bytes every placed thing starts on: one line of the card's memory.
36pub const LINE: u64 = 64;
37
38/// Vector and scalar registers a vector job's passes run over.
39pub const VREGS: usize = 16;
40pub const SREGS: usize = 8;
41
42fn align(at: u64) -> u64 {
43    at.div_ceil(LINE) * LINE
44}
45
46/// A format of plain floats, which a matrix runs on the vector unit in.
47fn is_float(ty: GgmlType) -> bool {
48    matches!(ty, GgmlType::F32 | GgmlType::F16 | GgmlType::Bf16)
49}
50
51// ── The memory ──────────────────────────────────────────────────────────────────────────────────────
52
53/// A tensor of the weight image: its format, its dims, where its bytes start and how many there are.
54#[derive(Clone, Debug, PartialEq)]
55pub struct Placed {
56    pub name: String,
57    pub ty: GgmlType,
58    pub dims: Vec<u64>,
59    pub at: u64,
60    pub bytes: u64,
61    /// For a convolution's kernel, its taps: the image holds it tap by tap, each tap's every channel together — the
62    /// transpose of the checkpoint's order, each channel's taps together — so a tap is one contiguous read.
63    pub taps: Option<usize>,
64    /// A matrix of weight blocks a matvec job streams through the array (a float matrix runs on the vector unit).
65    pub streamed: bool,
66}
67
68impl Placed {
69    /// Bytes of one row of `dims[0]` elements.
70    pub fn row_bytes(&self) -> u64 {
71        self.ty.bytes_for(self.dims[0] as usize) as u64
72    }
73
74    fn elems(&self) -> usize {
75        self.dims.iter().product::<u64>() as usize
76    }
77}
78
79/// Where one block keeps a sequence's state, from the start of the sequence's region.
80#[derive(Clone, Copy, Debug, PartialEq)]
81pub enum BlockState {
82    Delta {
83        /// The conv ring: `taps − 1` rows of `channels` elements, position `p`'s row at `p mod (taps − 1)`.
84        ring: u64,
85        channels: usize,
86        taps: usize,
87        /// The memory: per value head `width` rows of `width` elements.
88        memory: u64,
89        /// A lazily flushed memory's log: the count of tokens it holds and the slot of its oldest (four bytes each),
90        /// then a line on, [`Replay::capacity`] slots of `entry` bytes, the log's `i`th token in slot
91        /// `(oldest + i) mod capacity`, each every key group's key, every value head's values, every value head's decay
92        /// exponent and every value head's gate, each field from a whole beat of sixteen elements ([`LogFields`]).
93        log: Option<u64>,
94        entry: u64,
95        k_heads: usize,
96        v_heads: usize,
97        width: usize,
98    },
99    Attention {
100        /// Per position, every cached head's key row; per position, every cached head's value row: binary16 elements,
101        /// or int8 codes.
102        keys: u64,
103        values: u64,
104        /// An int8 cache's scales, per position every cached head's key row's, then its value row's, one f16 each.
105        key_scales: Option<u64>,
106        value_scales: Option<u64>,
107        kv_heads: usize,
108        width: usize,
109        value_width: usize,
110    },
111}
112
113/// The card's memory as a machine lays it out.
114#[derive(Clone, Debug, PartialEq)]
115pub struct Layout {
116    pub numerics: EngineNumerics,
117    /// Every tensor the program reads, in [`Program::tensors`] order.
118    pub tensors: Vec<Placed>,
119    /// Every rotary table the program turns heads by, in [`Program::ropes`] order, each at its address: per position,
120    /// `dims/2` cosines then `dims/2` sines, f32.
121    pub rotary: Vec<(Rope, u64)>,
122    /// Positions every sequence may reach.
123    pub ctx: usize,
124    /// Where the sequences' regions start, each `region` bytes, `sequences` of them.
125    pub regions: u64,
126    pub region: u64,
127    pub sequences: usize,
128    /// Each layer's place in a region.
129    pub blocks: Vec<BlockState>,
130    pub bytes: u64,
131}
132
133impl Layout {
134    /// The memory of `sequences` sequences of `spec` at `numerics`, each reaching `ctx` positions, over the tensors
135    /// `program` reads from `source`.
136    pub fn of(spec: &ModelSpec, numerics: EngineNumerics, program: &Program, source: &dyn WeightSource, sequences: usize, ctx: usize) -> R<Layout> {
137        if ctx == 0 || ctx > spec.ctx_len {
138            return Err(EngineError::PastContext { sequence: 0, position: ctx });
139        }
140        let kernels: HashMap<&str, usize> = program
141            .commands
142            .iter()
143            .filter_map(|c| match c {
144                Command::Conv { kernel, taps, .. } => Some((kernel.as_str(), *taps)),
145                _ => None,
146            })
147            .collect();
148        let matrices: HashSet<&str> = program
149            .commands
150            .iter()
151            .filter_map(|c| match c {
152                Command::Matvec { tensor, .. } => Some(tensor.as_str()),
153                _ => None,
154            })
155            .collect();
156        let mut at = 0u64;
157        let mut tensors = Vec::new();
158        for name in program.tensors() {
159            let (ty, dims) = source.describe(name)?;
160            let elems = dims.iter().product::<u64>() as usize;
161            let bytes = ty.bytes_for(elems) as u64;
162            let taps = kernels.get(name).copied();
163            if taps.is_some() && ty != GgmlType::F32 {
164                return Err(EngineError::NotYet("a convolution kernel not stored as f32"));
165            }
166            let streamed = matrices.contains(name) && !is_float(ty);
167            tensors.push(Placed { name: name.to_string(), ty, dims, at, bytes, taps, streamed });
168            at = align(at + bytes);
169        }
170        let mut rotary = Vec::new();
171        for rope in program.ropes() {
172            if (rope.dims / 2 * 2 * 4) as u64 % LINE != 0 {
173                return Err(EngineError::NotYet("a rotary table whose position the card's lines do not hold whole"));
174            }
175            rotary.push((rope.clone(), at));
176            at = align(at + (ctx * rope.dims / 2 * 2 * 4) as u64);
177        }
178        let eb = state_bytes(numerics.state);
179        let whole = |bytes: u64| bytes % LINE == 0;
180        let mut blocks = Vec::new();
181        let mut region = 0u64;
182        for layer in &spec.layers {
183            match &layer.mixer {
184                Mixer::GatedDelta(g) => {
185                    let channels = 2 * g.k_heads * g.head_dim + g.v_heads * g.head_dim;
186                    if !whole(channels as u64 * eb) || !whole(g.head_dim as u64 * eb) || g.head_dim % LANES != 0 {
187                        return Err(EngineError::NotYet("a Gated-DeltaNet row the card's lines do not hold whole"));
188                    }
189                    let ring = region;
190                    let memory = align(ring + (g.conv_taps.saturating_sub(1) * channels) as u64 * eb);
191                    region = align(memory + (g.v_heads * g.head_dim * g.head_dim) as u64 * eb);
192                    let entry = align(LogFields::of(g.k_heads, g.v_heads, g.head_dim).elems as u64 * eb);
193                    let log = match numerics.flush {
194                        StateFlush::EveryPass => None,
195                        StateFlush::Every { .. } | StateFlush::Trailing { .. } => {
196                            let at = region;
197                            region = align(at + LINE + Replay::capacity(numerics.flush) as u64 * entry);
198                            Some(at)
199                        }
200                    };
201                    blocks.push(BlockState::Delta {
202                        ring,
203                        channels,
204                        taps: g.conv_taps,
205                        memory,
206                        log,
207                        entry,
208                        k_heads: g.k_heads,
209                        v_heads: g.v_heads,
210                        width: g.head_dim,
211                    });
212                }
213                Mixer::Attention(a) => {
214                    if !whole(cache_row_bytes(numerics.cache, a.head_dim)) || !whole(cache_row_bytes(numerics.cache, a.value_dim)) {
215                        return Err(EngineError::NotYet("a cached head row the card's lines do not hold whole"));
216                    }
217                    let rows = |w: usize| ctx as u64 * a.kv_heads as u64 * cache_row_bytes(numerics.cache, w);
218                    let scales = ctx as u64 * a.kv_heads as u64 * 2;
219                    let keys = region;
220                    let values = align(keys + rows(a.head_dim));
221                    region = align(values + rows(a.value_dim));
222                    let (key_scales, value_scales) = if numerics.cache.is_int8() {
223                        let k = region;
224                        region = align(k + scales);
225                        let v = region;
226                        region = align(v + scales);
227                        (Some(k), Some(v))
228                    } else {
229                        (None, None)
230                    };
231                    blocks.push(BlockState::Attention {
232                        keys,
233                        values,
234                        key_scales,
235                        value_scales,
236                        kv_heads: a.kv_heads,
237                        width: a.head_dim,
238                        value_width: a.value_dim,
239                    });
240                }
241                Mixer::Mla(_) => return Err(EngineError::NotYet("multi-head latent attention")),
242            }
243        }
244        let regions = at;
245        let bytes = regions + region * sequences as u64;
246        Ok(Layout { numerics, tensors, rotary, ctx, regions, region, sequences, blocks, bytes })
247    }
248
249    /// Where sequence `s`'s region starts.
250    pub fn region_of(&self, s: usize) -> u64 {
251        self.regions + s as u64 * self.region
252    }
253
254    /// The most lines a lazily flushed memory's log holds past its count's line, every entry it holds at its longest:
255    /// none for a layout of no such memory.
256    pub fn log_lines(&self) -> usize {
257        let capacity = Replay::capacity(self.numerics.flush);
258        self.blocks
259            .iter()
260            .filter_map(|b| match b {
261                BlockState::Delta { log: Some(_), entry, .. } => Some(capacity * (entry / LINE) as usize),
262                _ => None,
263            })
264            .max()
265            .unwrap_or(0)
266    }
267}
268
269/// Bytes of one element of a recurrent state stored as `format`.
270pub fn state_bytes(format: StateFormat) -> u64 {
271    match format {
272        StateFormat::F32 => 4,
273        StateFormat::F16 => 2,
274    }
275}
276
277/// Elements a beat carries: the vector unit's lanes.
278pub const LANES: usize = 16;
279
280/// Where a log entry keeps its fields, in elements: every key group's key, every value head's values, every value
281/// head's decay exponent, every value head's gate, each field from a whole beat.
282#[derive(Clone, Copy, Debug, PartialEq, Eq)]
283pub struct LogFields {
284    pub values: usize,
285    pub log_decays: usize,
286    pub gates: usize,
287    pub elems: usize,
288}
289
290impl LogFields {
291    pub fn of(k_heads: usize, v_heads: usize, width: usize) -> LogFields {
292        let beats = |n: usize| n.div_ceil(LANES) * LANES;
293        let values = beats(k_heads * width);
294        let log_decays = values + beats(v_heads * width);
295        let gates = log_decays + beats(v_heads);
296        LogFields { values, log_decays, gates, elems: gates + beats(v_heads) }
297    }
298}
299
300/// Bytes of one head row of `width` elements in a cache of `format`: binary16 elements, or int8 codes.
301pub fn cache_row_bytes(format: CacheFormat, width: usize) -> u64 {
302    match format {
303        CacheFormat::F16 => 2 * width as u64,
304        CacheFormat::Int8 | CacheFormat::Int8Hadamard => width as u64,
305    }
306}
307
308// ── The jobs ────────────────────────────────────────────────────────────────────────────────────────
309
310/// A register a store reads.
311#[derive(Clone, Copy, Debug, PartialEq, Eq)]
312pub enum Reg {
313    V(VReg),
314    S(SReg),
315}
316
317/// Where a vector job's load reads, for the pass's row and head.
318#[derive(Clone, Copy, Debug, PartialEq, Eq)]
319pub enum Source {
320    /// Elements from `at + head·stride` of the row's `slot`.
321    Slot { slot: Slot, at: usize, stride: usize },
322    /// Elements of tensor `tensor` from element `at + head·stride` of the image's order, in f32.
323    Tensor { tensor: usize, at: usize, stride: usize },
324    /// The row's position's cosines, or sines, in rotary table `table`.
325    Rotary { table: usize, sines: bool },
326    /// Layer `layer`'s ring row `age` of the row's sequence, the oldest `0`.
327    Ring { layer: usize, age: usize },
328}
329
330/// Which part of a cache row a store writes.
331#[derive(Clone, Copy, Debug, PartialEq, Eq)]
332pub enum CachePart {
333    /// Every element as binary16.
334    Halves,
335    /// Every element as an int8 code.
336    Codes,
337    /// The row's f16 scale.
338    Scale,
339}
340
341/// Where a vector job's store writes, for the pass's row and head.
342#[derive(Clone, Copy, Debug, PartialEq, Eq)]
343pub enum Sink {
344    Slot { slot: Slot, at: usize, stride: usize },
345    /// Layer `layer`'s ring, the row's own row in place of the oldest, as the state stores it.
346    Ring { layer: usize },
347    /// Layer `layer`'s cache at the row's position, the pass's head: its keys, or its values.
348    Cache { layer: usize, values: bool, part: CachePart },
349}
350
351#[derive(Clone, Copy, Debug, PartialEq, Eq)]
352pub struct Load {
353    pub reg: VReg,
354    pub from: Source,
355    pub len: usize,
356}
357
358#[derive(Clone, Copy, Debug, PartialEq, Eq)]
359pub struct Put {
360    pub reg: Reg,
361    pub to: Sink,
362    pub len: usize,
363}
364
365/// The order a vector job's passes run in.
366#[derive(Clone, Copy, Debug, PartialEq, Eq)]
367pub enum Order {
368    /// Row after row, every head of a row before the next row.
369    Rows,
370    /// Head after head, a head's pass over every row before the next head: a job whose tensor span changes with the
371    /// head reads each span once for all the rows.
372    Heads,
373}
374
375/// A job of the vector unit: for every row of the step, in order, and every head of the row, a pass that loads its
376/// registers, runs `program` and stores from them.
377#[derive(Clone, Debug, PartialEq)]
378pub struct VectorJob {
379    pub heads: usize,
380    pub order: Order,
381    /// Slots the job writes whole, made afresh at its start, each of its width.
382    pub fresh: Vec<(Slot, usize)>,
383    pub loads: Vec<Load>,
384    pub program: Vec<Op>,
385    pub puts: Vec<Put>,
386}
387
388impl VectorJob {
389    /// Whether a pass reads or writes its row's sequence's state, so a sequence's rows must run in order.
390    pub fn ordered(&self) -> bool {
391        self.loads.iter().any(|l| matches!(l.from, Source::Ring { .. })) || self.puts.iter().any(|p| matches!(p.to, Sink::Ring { .. }))
392    }
393
394    /// Whether the job may run a chunk of its vectors at a time, each chunk's elements of every vector it loads and
395    /// stores: its rows cross nowhere — one head, every load a vector of one length from a slot, a tensor or a ring,
396    /// every store that length from a vector register into a slot or a ring, and every instruction elementwise or a
397    /// scalar's, none reducing, turning, rotating or coding across elements — so a chunk of its rows computes that chunk
398    /// of its result. A ring's rows are a channel each, so a sequence's tokens still see each other's in order.
399    pub fn chunked(&self) -> bool {
400        let Some(len) = self.loads.first().map(|l| l.len) else { return false };
401        self.heads == 1
402            && self.loads.iter().all(|l| l.len == len && matches!(l.from, Source::Slot { .. } | Source::Tensor { .. } | Source::Ring { .. }))
403            && self.puts.iter().all(|p| p.len == len && matches!(p.reg, Reg::V(_)) && matches!(p.to, Sink::Slot { .. } | Sink::Ring { .. }))
404            && self.program.iter().all(|op| {
405                matches!(
406                    op,
407                    Op::Vec { .. }
408                        | Op::VecScalar { .. }
409                        | Op::Map { .. }
410                        | Op::Scalar { .. }
411                        | Op::ScalarMap { .. }
412                        | Op::Const { .. }
413                        | Op::Scale { .. }
414                        | Op::MulAdd { .. }
415                        | Op::MulAddScalar { .. }
416                )
417            })
418    }
419
420    /// Whether the job's passes stand alone, so they may run in groups side by side (`logos_vector_side` with groups,
421    /// each pass's registers its own): every vector load and store one length, every load a slot's or a tensor's, no
422    /// ring row stored, no Hadamard or rotary instruction, every register a pass reads — an instruction's operand, a
423    /// store's value — loaded or written earlier in that pass, and no pass loading store elements an earlier pass of its
424    /// row stores (a group loads every pass before it stores any); a job of one chunk at a time is none. A pass's values
425    /// are then its own whatever passes run beside it.
426    pub fn grouped(&self) -> bool {
427        let Some(len) = self.loads.first().map(|l| l.len) else { return false };
428        let mut v: HashSet<VReg> = self.loads.iter().map(|l| l.reg).collect();
429        let mut s: HashSet<SReg> = HashSet::new();
430        let mut alone = true;
431        for op in &self.program {
432            let (rv, rs) = op.reads();
433            alone &= rv.iter().all(|r| v.contains(r)) && rs.iter().all(|r| s.contains(r));
434            v.extend(op.writes());
435            s.extend(op.writes_scalar());
436        }
437        alone
438            && !self.chunked()
439            && self.loads.iter().all(|l| l.len == len && matches!(l.from, Source::Slot { .. } | Source::Tensor { .. }))
440            && self.puts.iter().all(|p| match p.reg {
441                Reg::V(r) => p.len == len && v.contains(&r),
442                Reg::S(r) => s.contains(&r),
443            })
444            && self.puts.iter().all(|p| !matches!(p.to, Sink::Ring { .. }))
445            && self.program.iter().all(|op| !matches!(op, Op::Hadamard { .. } | Op::Rotate { .. }))
446            && self.puts.iter().all(|p| {
447                self.loads.iter().all(|l| match (p.to, l.from) {
448                    (Sink::Slot { slot, at, stride }, Source::Slot { slot: from, at: lat, stride: lstride }) if slot == from => (0..self.heads)
449                        .all(|h| (h + 1..self.heads).all(|g| at + h * stride + p.len <= lat + g * lstride || lat + g * lstride + l.len <= at + h * stride)),
450                    _ => true,
451                })
452            })
453    }
454
455    /// Whether load `i` brings every pass the same elements into a register nothing else of the job writes — a tensor's
456    /// span at stride 0, its register no other load's and no instruction's — so a register holding it from the job's
457    /// first pass holds it for every pass after.
458    pub fn kept(&self, i: usize) -> bool {
459        let l = &self.loads[i];
460        matches!(l.from, Source::Tensor { stride: 0, .. })
461            && self.loads.iter().enumerate().all(|(j, o)| j == i || o.reg != l.reg)
462            && self.program.iter().all(|op| op.writes() != Some(l.reg))
463    }
464}
465
466/// Each job's store slots, as masks of slot codes: those it reads, makes afresh and writes in part, and those live into
467/// it, the step taken round until nothing changes.
468struct Liveness {
469    reads: Vec<u32>,
470    fresh: Vec<u32>,
471    partial: Vec<u32>,
472    live_in: Vec<u32>,
473}
474
475impl Liveness {
476    fn of(jobs: &[Job]) -> Liveness {
477        let mask = |slots: &[Slot]| slots.iter().fold(0u32, |m, s| m | 1u32 << s.code());
478        let reads: Vec<u32> = jobs.iter().map(|j| mask(&j.reads())).collect();
479        let (fresh, partial): (Vec<u32>, Vec<u32>) = jobs
480            .iter()
481            .map(|j| {
482                let (f, p) = j.writes();
483                (mask(&f), mask(&p))
484            })
485            .unzip();
486        let mut l = Liveness { reads, fresh, partial, live_in: vec![0u32; jobs.len()] };
487        loop {
488            let mut changed = false;
489            for i in (0..jobs.len()).rev() {
490                let held = l.reads[i] | l.partial[i] | (l.out(i) & !l.fresh[i]);
491                if held != l.live_in[i] {
492                    l.live_in[i] = held;
493                    changed = true;
494                }
495            }
496            if !changed {
497                return l;
498            }
499        }
500    }
501
502    /// The slots live out of job `i`: into the next, or after the last into the step's first and the host's logits.
503    fn out(&self, i: usize) -> u32 {
504        if i + 1 < self.live_in.len() {
505            self.live_in[i + 1]
506        } else {
507            self.live_in[0] | 1u32 << Slot::Logits.code()
508        }
509    }
510}
511
512/// How a matrix job's sums meet the elements of its output slot ([`fold`]): each written as it is, added to the
513/// element it lands on (a residual connection, [`Command::Add`]'s), or multiplied by the SiLU of the element, the
514/// gate's sum a gated FFN's product takes ([`Command::SwiGlu`]'s, the element its gate).
515#[derive(Clone, Copy, Debug, PartialEq, Eq)]
516pub enum Fold {
517    Put,
518    Add,
519    SwiGlu,
520}
521
522impl Fold {
523    /// The element a sum leaves where the slot held `old`: `sum`, `old + sum`, `silu(old)·sum`, each as the vector unit
524    /// computes the add and the product.
525    pub fn apply(self, fp: FpUnit, old: f32, sum: f32) -> f32 {
526        match self {
527            Fold::Put => sum,
528            Fold::Add => fadd(old, sum),
529            Fold::SwiGlu => fp.mul(silu(fp, old), sum),
530        }
531    }
532
533    /// The fold's code in the matrix job's word.
534    pub fn code(self) -> u64 {
535        match self {
536            Fold::Put => 0,
537            Fold::Add => 1,
538            Fold::SwiGlu => 2,
539        }
540    }
541
542    /// The fold `code` names, or `None` for a code none does.
543    pub fn of_code(code: u64) -> Option<Fold> {
544        [Fold::Put, Fold::Add, Fold::SwiGlu].into_iter().find(|f| f.code() == code)
545    }
546}
547
548/// A recurrence job's read-outs gated and normed where they are made ([`fold_delta_norms`]): each head RMS-normed by
549/// tensor `weight` and gated by `silu` of the head's elements of `z`, `eps` the norm's epsilon — the gated norm
550/// ([`Command::GatedNorm`]'s job) the read-outs took next, run as part of the job.
551#[derive(Clone, Copy, Debug, PartialEq)]
552pub struct DeltaNorm {
553    pub weight: usize,
554    pub z: Slot,
555    pub eps: f32,
556}
557
558/// One job of the sequencer.
559#[derive(Clone, Debug, PartialEq)]
560pub enum Job {
561    /// Each row's [`Slot::Residual`] its token's row of tensor `tensor`, dequantized.
562    Embed { tensor: usize },
563    /// The array: tensor `tensor` times each row of `from`, each sum folded into its element of `to` by `fold`.
564    Matvec { tensor: usize, from: Slot, to: Slot, fold: Fold },
565    Vector(VectorJob),
566    /// The recurrence unit: layer `layer`'s memory through each row, each sequence's rows in order, its read-outs gated
567    /// and normed by `norm` when it has one.
568    Delta { layer: usize, query: Slot, key: Slot, value: Slot, value_at: usize, log_decay: Slot, decay: Slot, gate: Slot, to: Slot, norm: Option<DeltaNorm> },
569    /// The attention unit: each row's `heads` query heads over layer `layer`'s cache of its sequence.
570    Attend { layer: usize, query: Slot, heads: usize, scale: f32, to: Slot },
571    /// Every sequence the step advances moves past its rows.
572    Advance,
573}
574
575impl Job {
576    /// The store slots the job reads, each once.
577    pub fn reads(&self) -> Vec<Slot> {
578        let mut slots = match self {
579            Job::Embed { .. } | Job::Advance => Vec::new(),
580            Job::Matvec { from, to, fold: Fold::Add | Fold::SwiGlu, .. } => vec![*from, *to],
581            Job::Matvec { from, .. } => vec![*from],
582            Job::Vector(v) => v.loads.iter().filter_map(|l| if let Source::Slot { slot, .. } = l.from { Some(slot) } else { None }).collect(),
583            Job::Delta { query, key, value, log_decay, decay, gate, norm, .. } => {
584                let mut v = vec![*query, *key, *value, *log_decay, *decay, *gate];
585                v.extend(norm.map(|n| n.z));
586                v
587            }
588            Job::Attend { query, .. } => vec![*query],
589        };
590        slots.sort_by_key(|s| s.code());
591        slots.dedup();
592        slots
593    }
594
595    /// The store slots the job writes: those it makes afresh, whole, and those it writes in part, the rest of each as
596    /// the job found it.
597    pub fn writes(&self) -> (Vec<Slot>, Vec<Slot>) {
598        let (fresh, mut partial) = match self {
599            Job::Embed { .. } => (vec![Slot::Residual], Vec::new()),
600            Job::Matvec { to, .. } | Job::Delta { to, .. } | Job::Attend { to, .. } => (vec![*to], Vec::new()),
601            Job::Vector(v) => {
602                let fresh: Vec<Slot> = v.fresh.iter().map(|&(s, _)| s).collect();
603                let partial = v
604                    .puts
605                    .iter()
606                    .filter_map(|p| if let Sink::Slot { slot, .. } = p.to { Some(slot) } else { None })
607                    .filter(|s| !fresh.contains(s))
608                    .collect();
609                (fresh, partial)
610            }
611            Job::Advance => (Vec::new(), Vec::new()),
612        };
613        partial.sort_by_key(|s| s.code());
614        partial.dedup();
615        (fresh, partial)
616    }
617}
618
619/// The jobs of `program` over the tensors `layout` places, `eps` the norms' epsilon: one a command, its unit's, and for
620/// a rotated cache's attention a second, the vector unit's rotation back.
621pub fn lower(program: &Program, layout: &Layout, eps: f32) -> Vec<Job> {
622    let index: HashMap<&str, usize> = layout.tensors.iter().enumerate().map(|(i, t)| (t.name.as_str(), i)).collect();
623    let t = |name: &str| *index.get(name).unwrap_or_else(|| panic!("{name} is not in the weight image"));
624    let elems = |name: &str| layout.tensors[t(name)].elems();
625    let fp = layout.numerics.fp_unit;
626    let ropes = program.ropes();
627    // Each slot's width as the commands so far leave it.
628    let mut width: HashMap<Slot, usize> = HashMap::new();
629    let whole = |slot: Slot| Source::Slot { slot, at: 0, stride: 0 };
630    let into = |slot: Slot| Sink::Slot { slot, at: 0, stride: 0 };
631    let ld = |reg: VReg, from: Source, len: usize| Load { reg, from, len };
632    let put = |reg: VReg, to: Sink, len: usize| Put { reg: Reg::V(reg), to, len };
633    let mut jobs = Vec::with_capacity(program.commands.len());
634    for c in &program.commands {
635        let job = match c {
636            Command::Embed { table } => {
637                width.insert(Slot::Residual, layout.tensors[t(table)].dims[0] as usize);
638                Job::Embed { tensor: t(table) }
639            }
640            Command::Norm { weight, from, to } => {
641                let n = elems(weight);
642                width.insert(*to, n);
643                Job::Vector(VectorJob {
644                    order: Order::Rows,
645                    heads: 1,
646                    fresh: vec![(*to, n)],
647                    loads: vec![ld(0, whole(*from), n), ld(1, Source::Tensor { tensor: t(weight), at: 0, stride: 0 }, n)],
648                    program: vector::rmsnorm(fp, 0, 1, 2, 3, n, eps),
649                    puts: vec![put(2, into(*to), n)],
650                })
651            }
652            Command::Matvec { tensor, from, to } => {
653                let m = &layout.tensors[t(tensor)];
654                let (in_f, out_f) = (m.dims[0] as usize, m.dims[1] as usize);
655                width.insert(*to, out_f);
656                if !m.streamed {
657                    // A float matrix on the vector unit, a weight row a head: each row read once and dotted with every
658                    // token-row.
659                    Job::Vector(VectorJob {
660                        order: Order::Heads,
661                        heads: out_f,
662                        fresh: vec![(*to, out_f)],
663                        loads: vec![ld(0, Source::Tensor { tensor: t(tensor), at: 0, stride: in_f }, in_f), ld(1, whole(*from), in_f)],
664                        program: vec![Op::Dot { a: 0, b: 1, d: 0 }],
665                        puts: vec![Put { reg: Reg::S(0), to: Sink::Slot { slot: *to, at: 0, stride: 1 }, len: 1 }],
666                    })
667                } else {
668                    Job::Matvec { tensor: t(tensor), from: *from, to: *to, fold: Fold::Put }
669                }
670            }
671            Command::Add { from, to } => Job::Vector(add_job(*from, *to, width[to])),
672            Command::SwiGlu { gate, up, to } => {
673                let n = width[gate];
674                width.insert(*to, n);
675                Job::Vector(swiglu_job(*gate, *up, *to, n))
676            }
677            Command::Conv { layer, kernel, taps, from, to } => {
678                let channels = width[from];
679                width.insert(*to, channels);
680                let taps = *taps;
681                let ring: Vec<VReg> = (0..taps as u8 - 1).collect();
682                let x = taps as u8 - 1;
683                let tap: Vec<VReg> = (taps as u8..2 * taps as u8).collect();
684                let (acc, out) = (2 * taps as u8, 2 * taps as u8 + 1);
685                let mut loads: Vec<Load> = ring.iter().enumerate().map(|(age, &r)| ld(r, Source::Ring { layer: *layer, age }, channels)).collect();
686                loads.push(ld(x, whole(*from), channels));
687                loads.extend(tap.iter().enumerate().map(|(j, &r)| ld(r, Source::Tensor { tensor: t(kernel), at: j * channels, stride: 0 }, channels)));
688                let mut puts = vec![put(out, into(*to), channels)];
689                if taps > 1 {
690                    puts.push(put(x, Sink::Ring { layer: *layer }, channels));
691                }
692                Job::Vector(VectorJob { heads: 1, order: Order::Rows, fresh: vec![(*to, channels)], loads, program: vector::conv(&ring, x, &tap, acc, out), puts })
693            }
694            Command::L2Heads { from, at, heads, width: w, to } => {
695                width.insert(*to, heads * w);
696                Job::Vector(VectorJob {
697                    order: Order::Rows,
698                    heads: *heads,
699                    fresh: vec![(*to, heads * w)],
700                    loads: vec![ld(0, Source::Slot { slot: *from, at: *at, stride: *w }, *w)],
701                    program: vector::l2norm(0, 1, eps),
702                    puts: vec![put(1, Sink::Slot { slot: *to, at: 0, stride: *w }, *w)],
703                })
704            }
705            Command::Gates { alpha, beta, dt, a, log_decay, decay, gate } => {
706                let n = width[alpha];
707                for s in [log_decay, decay, gate] {
708                    width.insert(*s, n);
709                }
710                Job::Vector(VectorJob {
711                    order: Order::Rows,
712                    heads: 1,
713                    fresh: vec![(*log_decay, n), (*decay, n), (*gate, n)],
714                    loads: vec![
715                        ld(0, whole(*alpha), n),
716                        ld(1, Source::Tensor { tensor: t(dt), at: 0, stride: 0 }, n),
717                        ld(2, Source::Tensor { tensor: t(a), at: 0, stride: 0 }, n),
718                        ld(3, whole(*beta), n),
719                    ],
720                    program: vector::delta_gates(0, 1, 2, 3, 4, 5, 6),
721                    puts: vec![put(4, into(*log_decay), n), put(5, into(*decay), n), put(6, into(*gate), n)],
722                })
723            }
724            Command::Delta { layer, query, key, value, value_at, log_decay, decay, gate, v_heads, width: w, to, .. } => {
725                width.insert(*to, v_heads * w);
726                Job::Delta {
727                    layer: *layer,
728                    query: *query,
729                    key: *key,
730                    value: *value,
731                    value_at: *value_at,
732                    log_decay: *log_decay,
733                    decay: *decay,
734                    gate: *gate,
735                    to: *to,
736                    norm: None,
737                }
738            }
739            Command::GatedNorm { weight, from, z, width: w, to } => {
740                let n = width[from];
741                width.insert(*to, n);
742                Job::Vector(gated_norm_job(fp, t(weight), *from, *z, *w, n, *to, eps))
743            }
744            Command::Split { from, heads, width: w, query, gate } => {
745                width.insert(*query, heads * w);
746                width.insert(*gate, heads * w);
747                Job::Vector(VectorJob {
748                    order: Order::Rows,
749                    heads: *heads,
750                    fresh: vec![(*query, heads * w), (*gate, heads * w)],
751                    loads: vec![ld(0, Source::Slot { slot: *from, at: 0, stride: 2 * w }, *w), ld(1, Source::Slot { slot: *from, at: *w, stride: 2 * w }, *w)],
752                    program: vec![],
753                    puts: vec![put(0, Sink::Slot { slot: *query, at: 0, stride: *w }, *w), put(1, Sink::Slot { slot: *gate, at: 0, stride: *w }, *w)],
754                })
755            }
756            Command::RmsHeads { weight, slot, width: w } => Job::Vector(VectorJob {
757                order: Order::Rows,
758                heads: width[slot] / w,
759                fresh: vec![],
760                loads: vec![ld(0, Source::Slot { slot: *slot, at: 0, stride: *w }, *w), ld(1, Source::Tensor { tensor: t(weight), at: 0, stride: 0 }, *w)],
761                program: vector::rmsnorm(fp, 0, 1, 2, 3, *w, eps),
762                puts: vec![put(2, Sink::Slot { slot: *slot, at: 0, stride: *w }, *w)],
763            }),
764            Command::Rotate { slot, width: w, rope } => {
765                let table = ropes.iter().position(|r| *r == rope).expect("a rotary table the program turns by");
766                let pairs = rope.dims / 2;
767                Job::Vector(VectorJob {
768                    order: Order::Rows,
769                    heads: width[slot] / w,
770                    fresh: vec![],
771                    loads: vec![
772                        ld(0, Source::Slot { slot: *slot, at: 0, stride: *w }, *w),
773                        ld(1, Source::Rotary { table, sines: false }, pairs),
774                        ld(2, Source::Rotary { table, sines: true }, pairs),
775                    ],
776                    program: vector::rotary(0, 1, 2, 3, rope.neox),
777                    puts: vec![put(3, Sink::Slot { slot: *slot, at: 0, stride: *w }, *w)],
778                })
779            }
780            Command::Hadamard { slot, width: w } => Job::Vector(VectorJob {
781                order: Order::Rows,
782                heads: width[slot] / w,
783                fresh: vec![],
784                loads: vec![ld(0, Source::Slot { slot: *slot, at: 0, stride: *w }, *w)],
785                program: vec![Op::Hadamard { a: 0, d: 1 }],
786                puts: vec![put(1, Sink::Slot { slot: *slot, at: 0, stride: *w }, *w)],
787            }),
788            Command::CacheAppend { layer, key, value, kv_heads, width: w, value_width: vw } => {
789                let loads = vec![ld(0, Source::Slot { slot: *key, at: 0, stride: *w }, *w), ld(1, Source::Slot { slot: *value, at: 0, stride: *vw }, *vw)];
790                let cache = |values: bool, part: CachePart| Sink::Cache { layer: *layer, values, part };
791                let (program, puts) = if layout.numerics.cache.is_int8() {
792                    let mut p = vector::cache_row(0, 2, 0);
793                    p.extend(vector::cache_row(1, 3, 2));
794                    let puts = vec![
795                        put(2, cache(false, CachePart::Codes), *w),
796                        Put { reg: Reg::S(0), to: cache(false, CachePart::Scale), len: 1 },
797                        put(3, cache(true, CachePart::Codes), *vw),
798                        Put { reg: Reg::S(2), to: cache(true, CachePart::Scale), len: 1 },
799                    ];
800                    (p, puts)
801                } else {
802                    (vec![], vec![put(0, cache(false, CachePart::Halves), *w), put(1, cache(true, CachePart::Halves), *vw)])
803                };
804                Job::Vector(VectorJob { heads: *kv_heads, order: Order::Rows, fresh: vec![], loads, program, puts })
805            }
806            Command::Attend { layer, query, heads, width: w, value_width, scale, to, .. } => {
807                width.insert(*to, heads * value_width);
808                if layout.numerics.cache == CacheFormat::Int8Hadamard {
809                    // A rotated score is the dot of rotated rows, `w` times the plain one: the scale takes `2^-log2 w`;
810                    // each head's weighted sum is rotated back, the transform then `2^-log2 value_width`.
811                    let scale = ldexp(*scale, -(w.trailing_zeros() as i32));
812                    jobs.push(Job::Attend { layer: *layer, query: *query, heads: *heads, scale, to: *to });
813                    let head = Sink::Slot { slot: *to, at: 0, stride: *value_width };
814                    Job::Vector(VectorJob {
815                        order: Order::Rows,
816                        heads: *heads,
817                        fresh: vec![],
818                        loads: vec![ld(0, Source::Slot { slot: *to, at: 0, stride: *value_width }, *value_width)],
819                        program: vec![Op::Hadamard { a: 0, d: 1 }, Op::Scale { a: 1, n: -(value_width.trailing_zeros() as i8), d: 2 }],
820                        puts: vec![put(2, head, *value_width)],
821                    })
822                } else {
823                    Job::Attend { layer: *layer, query: *query, heads: *heads, scale: *scale, to: *to }
824                }
825            }
826            Command::SigmoidGate { slot, gate } => {
827                let n = width[slot];
828                Job::Vector(VectorJob {
829                    order: Order::Rows,
830                    heads: 1,
831                    fresh: vec![],
832                    loads: vec![ld(0, whole(*slot), n), ld(1, whole(*gate), n)],
833                    program: vector::sigmoid_gate(0, 1, 2, 3),
834                    puts: vec![put(2, into(*slot), n)],
835                })
836            }
837            Command::Advance => Job::Advance,
838        };
839        jobs.push(job);
840    }
841    jobs
842}
843
844/// [`Command::Add`]'s job: `from` added into `to`, `n` elements.
845/// [`Command::GatedNorm`]'s job: each head of `width` elements of `from` (`n` a row) RMS-normed by tensor `weight` and
846/// gated by `silu` of the head's elements of `z`, into `to`, `eps` the norm's epsilon.
847#[allow(clippy::too_many_arguments)]
848pub fn gated_norm_job(fp: FpUnit, weight: usize, from: Slot, z: Slot, width: usize, n: usize, to: Slot, eps: f32) -> VectorJob {
849    let head = |slot: Slot| Source::Slot { slot, at: 0, stride: width };
850    VectorJob {
851        order: Order::Rows,
852        heads: n / width,
853        fresh: if to == from || to == z { vec![] } else { vec![(to, n)] },
854        loads: vec![
855            Load { reg: 0, from: head(from), len: width },
856            Load { reg: 1, from: Source::Tensor { tensor: weight, at: 0, stride: 0 }, len: width },
857            Load { reg: 2, from: head(z), len: width },
858        ],
859        program: vector::gdn_out(fp, 0, 1, 2, 3, 4, width, eps),
860        puts: vec![Put { reg: Reg::V(3), to: Sink::Slot { slot: to, at: 0, stride: width }, len: width }],
861    }
862}
863
864fn add_job(from: Slot, to: Slot, n: usize) -> VectorJob {
865    let whole = |slot: Slot| Source::Slot { slot, at: 0, stride: 0 };
866    VectorJob {
867        order: Order::Rows,
868        heads: 1,
869        fresh: vec![],
870        loads: vec![Load { reg: 0, from: whole(to), len: n }, Load { reg: 1, from: whole(from), len: n }],
871        program: vec![Op::Vec { op: vector::Bin::Add, a: 0, b: 1, d: 2 }],
872        puts: vec![Put { reg: Reg::V(2), to: Sink::Slot { slot: to, at: 0, stride: 0 }, len: n }],
873    }
874}
875
876/// [`Command::SwiGlu`]'s job: `silu(gate)·up` into `to`, `n` elements.
877fn swiglu_job(gate: Slot, up: Slot, to: Slot, n: usize) -> VectorJob {
878    let whole = |slot: Slot| Source::Slot { slot, at: 0, stride: 0 };
879    VectorJob {
880        order: Order::Rows,
881        heads: 1,
882        fresh: vec![(to, n)],
883        loads: vec![Load { reg: 0, from: whole(gate), len: n }, Load { reg: 1, from: whole(up), len: n }],
884        program: vector::swiglu(0, 1, 2),
885        puts: vec![Put { reg: Reg::V(2), to: Sink::Slot { slot: to, at: 0, stride: 0 }, len: n }],
886    }
887}
888
889/// The step `jobs` with each matrix whose sums only a residual add or a gated FFN's product takes folding them into
890/// that job's output slot ([`Fold`]): a matrix job and the add of its output into another slot become one job adding
891/// its sums into that slot; a gate's and an up projection's matrix jobs and their product become the gate's sums into
892/// the product's slot and the up projection's folded into them through the gate's SiLU. Jobs fold only where they stand
893/// next to each other and the slots the step no longer writes are read by no job after
894/// ([`StoreLayout::live_out`]), so every value the step computes is the one it computed, and those slots leave the
895/// store.
896pub fn fold(jobs: &[Job], layout: &Layout) -> Vec<Job> {
897    let live = StoreLayout::live_out(jobs);
898    let dead = |slot: Slot, after: usize| live[after] >> slot.code() & 1 == 0;
899    let width = |tensor: usize| layout.tensors[tensor].dims[1] as usize;
900    let slot_put = |v: &VectorJob| match v.puts.first().map(|p| p.to) {
901        Some(Sink::Slot { slot, .. }) => Some(slot),
902        _ => None,
903    };
904    let mut out = Vec::with_capacity(jobs.len());
905    let mut i = 0;
906    while i < jobs.len() {
907        if let (Some(&Job::Matvec { tensor: tg, from: xg, to: g, fold: Fold::Put }), Some(&Job::Matvec { tensor: tu, from: xu, to: u, fold: Fold::Put }), Some(Job::Vector(v))) =
908            (jobs.get(i), jobs.get(i + 1), jobs.get(i + 2))
909        {
910            let n = width(tu);
911            if let Some(h) = slot_put(v) {
912                let fits = *v == swiglu_job(g, u, h, n) && width(tg) == n && g != u && u != h && xu != g && h != xg && h != xu;
913                if fits && (g == h || dead(g, i + 2)) && dead(u, i + 2) {
914                    out.push(Job::Matvec { tensor: tg, from: xg, to: h, fold: Fold::Put });
915                    out.push(Job::Matvec { tensor: tu, from: xu, to: h, fold: Fold::SwiGlu });
916                    i += 3;
917                    continue;
918                }
919            }
920        }
921        if let (Some(&Job::Matvec { tensor, from, to: o, fold: Fold::Put }), Some(Job::Vector(v))) = (jobs.get(i), jobs.get(i + 1)) {
922            if let Some(r) = slot_put(v) {
923                if *v == add_job(o, r, width(tensor)) && r != o && dead(o, i + 1) {
924                    out.push(Job::Matvec { tensor, from, to: r, fold: Fold::Add });
925                    i += 2;
926                    continue;
927                }
928            }
929        }
930        out.push(jobs[i].clone());
931        i += 1;
932    }
933    out
934}
935
936/// The step `jobs` with each recurrence job whose read-outs a gated norm in place takes next carrying that norm
937/// ([`DeltaNorm`]): the two jobs become one, the recurrence's read-outs gated and normed where they are made. The norm's
938/// gate is a slot other than the read-outs', so it is still the one the norm read. The machine runs a job with a norm
939/// as the recurrence then the norm's own job, so every value the step computes is the one it computed.
940pub fn fold_delta_norms(jobs: &[Job], layout: &Layout) -> Vec<Job> {
941    let fp = layout.numerics.fp_unit;
942    let mut out = Vec::with_capacity(jobs.len());
943    let mut i = 0;
944    while i < jobs.len() {
945        if let (Some(Job::Delta { layer, to, norm: None, .. }), Some(Job::Vector(v))) = (jobs.get(i), jobs.get(i + 1)) {
946            let BlockState::Delta { v_heads, width, .. } = layout.blocks[*layer] else { unreachable!("a delta block") };
947            let parts = match (v.loads.get(1), v.loads.get(2), v.program.get(3)) {
948                (Some(Load { from: Source::Tensor { tensor, .. }, .. }), Some(Load { from: Source::Slot { slot, .. }, .. }), Some(Op::Const { value, .. })) => {
949                    Some(DeltaNorm { weight: *tensor, z: *slot, eps: *value })
950                }
951                _ => None,
952            };
953            if let Some(n) = parts {
954                if n.z != *to && *v == gated_norm_job(fp, n.weight, *to, n.z, width, v_heads * width, *to, n.eps) {
955                    let mut job = jobs[i].clone();
956                    if let Job::Delta { norm, .. } = &mut job {
957                        *norm = Some(n);
958                    }
959                    out.push(job);
960                    i += 2;
961                    continue;
962                }
963            }
964        }
965        out.push(jobs[i].clone());
966        i += 1;
967    }
968    out
969}
970
971// ── The machine ─────────────────────────────────────────────────────────────────────────────────────
972
973/// One row of a step's row table: its sequence, its position and token, and where its position's row sits in its
974/// sequence's conv rings, `position mod (taps − 1)`, the host's division so the engine never divides.
975#[derive(Clone, Copy, Debug, PartialEq)]
976pub struct Row {
977    pub sequence: usize,
978    pub position: usize,
979    pub token: u32,
980    pub ring: usize,
981}
982
983/// The token-row store: each slot's vector for every row of the step, in the step's row order.
984pub type Store = HashMap<Slot, Vec<Vec<f32>>>;
985
986/// The machine before one of a step's jobs runs: the job's index (the job count once the last has run), the step's
987/// rows, the store and the card's memory.
988pub struct Trace<'a> {
989    pub job: usize,
990    pub rows: &'a [Row],
991    pub store: &'a Store,
992    pub memory: &'a [u8],
993}
994
995/// The orders [`StoreLayout::of`] tries past the widest first, each the one before shuffled, before it takes the shortest
996/// row it laid.
997pub const STORE_ORDERS: usize = 1 << 12;
998
999/// The token-row store as the engine holds it: beats of [`LANES`] f32 elements, slot `s`'s row `r` from beat
1000/// `base + r·beats` of its entry, every slot wide enough for the widest vector the step's jobs write to it, and two
1001/// slots over the same beats only when no point of the step holds both. A greedy store ([`StoreLayout::greedy`]) keeps
1002/// each row's logits as their arg-max.
1003#[derive(Clone, Debug, PartialEq)]
1004pub struct StoreLayout {
1005    pub rows: usize,
1006    /// Per slot code: the slot's first beat and its beats a row.
1007    pub slots: Vec<(u32, u32)>,
1008    pub beats: u32,
1009    /// Whether the logits slot holds each row's arg-max in place of its logits.
1010    pub greedy: bool,
1011}
1012
1013impl StoreLayout {
1014    /// The store `jobs` write over `rows` rows, each slot's width the widest any job writes to it, each slot at the
1015    /// lowest beat no slot live beside it holds ([`StoreLayout::live_together`]), the widest first — or, where that
1016    /// leaves a row longer than the most beats a point of the step holds together ([`StoreLayout::widest`]), in the first
1017    /// order of a fixed sequence of them that lays the row that short, the shortest of them all if none does.
1018    pub fn of(jobs: &[Job], layout: &Layout, rows: usize) -> StoreLayout {
1019        StoreLayout::laid(jobs, layout, rows, false)
1020    }
1021
1022    /// [`StoreLayout::of`] with the head's read-out never in the store: the logits slot is a beat a row, its first lane
1023    /// the index [`numerics::argmax`](super::numerics::argmax) picks of the row's logits and its second that logit's
1024    /// bits, so the store holds what a greedy step reads back however wide the vocabulary.
1025    pub fn greedy(jobs: &[Job], layout: &Layout, rows: usize) -> StoreLayout {
1026        StoreLayout::laid(jobs, layout, rows, true)
1027    }
1028
1029    fn laid(jobs: &[Job], layout: &Layout, rows: usize, greedy: bool) -> StoreLayout {
1030        let mut width = vec![0usize; Slot::ALL.len()];
1031        let mut widen = |slot: Slot, w: usize| width[slot.code() as usize] = width[slot.code() as usize].max(w);
1032        for job in jobs {
1033            match job {
1034                Job::Embed { tensor } => widen(Slot::Residual, layout.tensors[*tensor].dims[0] as usize),
1035                Job::Matvec { tensor, to, .. } => widen(*to, layout.tensors[*tensor].dims[1] as usize),
1036                Job::Vector(v) => v.fresh.iter().for_each(|&(s, w)| widen(s, w)),
1037                Job::Delta { layer, to, .. } => {
1038                    let BlockState::Delta { v_heads, width, .. } = layout.blocks[*layer] else { unreachable!("a delta block") };
1039                    widen(*to, v_heads * width);
1040                }
1041                Job::Attend { layer, heads, to, .. } => {
1042                    let BlockState::Attention { value_width, .. } = layout.blocks[*layer] else { unreachable!("an attention block") };
1043                    widen(*to, heads * value_width);
1044                }
1045                Job::Advance => {}
1046            }
1047        }
1048        if greedy {
1049            width[Slot::Logits.code() as usize] = 2;
1050        }
1051        let beats: Vec<u32> = width.iter().map(|w| w.div_ceil(LANES) as u32).collect();
1052        let clash = StoreLayout::live_together(jobs);
1053        let first_fit = |order: &[usize]| -> (Vec<(u32, u32)>, u32) {
1054            let mut slots = vec![(0u32, 0u32); beats.len()];
1055            let mut placed: Vec<(usize, u32, u32)> = Vec::new();
1056            for &c in order {
1057                let span = beats[c] * rows as u32;
1058                let mut at = 0u32;
1059                while let Some(&(_, _, end)) = placed.iter().find(|&&(o, b, e)| clash[c] >> o & 1 == 1 && b < at + span && at < e) {
1060                    at = end;
1061                }
1062                placed.push((c, at, at + span));
1063                slots[c] = (at, beats[c]);
1064            }
1065            (slots, placed.iter().map(|&(_, _, e)| e).max().unwrap_or(0))
1066        };
1067        let mut order: Vec<usize> = (0..beats.len()).filter(|&c| beats[c] > 0).collect();
1068        order.sort_by_key(|&c| (std::cmp::Reverse(beats[c]), c));
1069        let least = StoreLayout::widest_of(jobs, &beats) * rows as u32;
1070        let mut best = first_fit(&order);
1071        let mut seed = 0x9e37_79b9_7f4a_7c15u64;
1072        for _ in 0..STORE_ORDERS {
1073            if best.1 <= least {
1074                break;
1075            }
1076            for i in (1..order.len()).rev() {
1077                seed ^= seed << 13;
1078                seed ^= seed >> 7;
1079                seed ^= seed << 17;
1080                order.swap(i, (seed % (i as u64 + 1)) as usize);
1081            }
1082            let next = first_fit(&order);
1083            if next.1 < best.1 {
1084                best = next;
1085            }
1086        }
1087        StoreLayout { rows, slots: best.0, beats: best.1, greedy }
1088    }
1089
1090    /// The most beats a row of the store holds at any point of the step `jobs`, the store laid for: the widest set of its
1091    /// slots a job reads, writes or holds live across it ([`StoreLayout::live_together`]) — no layout's row is shorter.
1092    pub fn widest(&self, jobs: &[Job]) -> u32 {
1093        let beats: Vec<u32> = self.slots.iter().map(|&(_, b)| b).collect();
1094        StoreLayout::widest_of(jobs, &beats)
1095    }
1096
1097    fn widest_of(jobs: &[Job], beats: &[u32]) -> u32 {
1098        let l = Liveness::of(jobs);
1099        (0..jobs.len())
1100            .map(|i| {
1101                let held = l.live_in[i] | l.out(i) | l.reads[i] | l.fresh[i] | l.partial[i];
1102                (0..beats.len()).filter(|&c| held >> c & 1 == 1).map(|c| beats[c]).sum::<u32>()
1103            })
1104            .max()
1105            .unwrap_or(0)
1106    }
1107
1108    /// For each slot code, the slots some point of the step `jobs` holds beside it, as a mask of slot codes: those a
1109    /// job reads or writes, and those live across it ([`StoreLayout::live_out`]).
1110    pub fn live_together(jobs: &[Job]) -> Vec<u32> {
1111        let l = Liveness::of(jobs);
1112        let mut clash = vec![0u32; Slot::ALL.len()];
1113        for i in 0..jobs.len() {
1114            let held = l.live_in[i] | l.out(i) | l.reads[i] | l.fresh[i] | l.partial[i];
1115            for (c, m) in clash.iter_mut().enumerate() {
1116                if held >> c & 1 == 1 {
1117                    *m |= held;
1118                }
1119            }
1120        }
1121        clash
1122    }
1123
1124    /// For each job of the step `jobs`, the slots live out of it, as a mask of slot codes. A slot is live from a job
1125    /// that makes it afresh to the last job that reads it before it is made again, the step taken round (the next step
1126    /// follows it, and the host reads the logits after its last job); a job that writes a slot in part leaves it live.
1127    pub fn live_out(jobs: &[Job]) -> Vec<u32> {
1128        let l = Liveness::of(jobs);
1129        (0..jobs.len()).map(|i| l.out(i)).collect()
1130    }
1131
1132    /// Where slot `slot`'s row `row` starts.
1133    pub fn beat(&self, slot: Slot, row: usize) -> u32 {
1134        let (base, beats) = self.slots[slot.code() as usize];
1135        base + row as u32 * beats
1136    }
1137
1138    /// The store's beats, each element its f32 bits, an element no vector holds zero; a greedy store's logits slot
1139    /// each row's arg-max and that logit.
1140    pub fn image(&self, store: &Store) -> Vec<[u32; LANES]> {
1141        let mut out = vec![[0u32; LANES]; self.beats as usize];
1142        for (slot, rows) in store {
1143            for (r, v) in rows.iter().enumerate() {
1144                let at = self.beat(*slot, r) as usize;
1145                if self.greedy && *slot == Slot::Logits {
1146                    let best = super::numerics::argmax(v);
1147                    out[at][0] = best as u32;
1148                    out[at][1] = v.get(best).map_or(0, |x| x.to_bits());
1149                    continue;
1150                }
1151                for (i, x) in v.iter().enumerate() {
1152                    out[at + i / LANES][i % LANES] = x.to_bits();
1153                }
1154            }
1155        }
1156        out
1157    }
1158}
1159
1160/// A checkpoint on the machine: its layout, its jobs, and the card's memory they run over.
1161pub struct Machine {
1162    pub spec: ModelSpec,
1163    /// The cut of the model's one program the machine's step was read off.
1164    pub identity: crate::model::program::Identity,
1165    pub layout: Layout,
1166    pub jobs: Vec<Job>,
1167    memory: Vec<u8>,
1168    /// Each region's sequence's position, or `None` for a region no sequence holds.
1169    positions: Vec<Option<usize>>,
1170}
1171
1172impl Machine {
1173    /// `spec` at `numerics` with room for `sequences` sequences of `ctx` positions each, its weight image and rotary
1174    /// tables written from `source`.
1175    pub fn new(spec: ModelSpec, mut source: impl WeightSource, numerics: EngineNumerics, sequences: usize, ctx: usize) -> R<Machine> {
1176        runs(&spec, numerics)?;
1177        let super::program::Lowered { program, identity } = super::program::lower(&spec, numerics, ctx)?;
1178        let layout = Layout::of(&spec, numerics, &program, &source, sequences, ctx)?;
1179        let jobs = lower(&program, &layout, spec.rms_eps as f32);
1180        let mut memory = vec![0u8; layout.bytes as usize];
1181        for t in &layout.tensors {
1182            let rows = t.elems() / t.dims[0].max(1) as usize;
1183            let bytes = source.row_bytes(&t.name, 0, rows)?;
1184            let at = t.at as usize;
1185            match t.taps {
1186                None => memory[at..at + bytes.len()].copy_from_slice(&bytes),
1187                Some(taps) => {
1188                    // Each channel's taps together in the checkpoint, each tap's channels together in the image.
1189                    let channels = t.elems() / taps;
1190                    for (i, word) in bytes.chunks_exact(4).enumerate() {
1191                        let (ch, j) = (i / taps, i % taps);
1192                        let to = at + 4 * (j * channels + ch);
1193                        memory[to..to + 4].copy_from_slice(word);
1194                    }
1195                }
1196            }
1197        }
1198        for (rope, at) in &layout.rotary {
1199            let pairs = rope.dims / 2;
1200            let factors = rotary_factors(rope, &mut source)?;
1201            for p in 0..layout.ctx {
1202                let base = *at as usize + p * pairs * 8;
1203                for i in 0..pairs {
1204                    let (c, s) = rotation(rope, i, p, factors.as_deref());
1205                    memory[base + 4 * i..base + 4 * i + 4].copy_from_slice(&(c as f32).to_le_bytes());
1206                    memory[base + 4 * (pairs + i)..base + 4 * (pairs + i) + 4].copy_from_slice(&(s as f32).to_le_bytes());
1207                }
1208            }
1209        }
1210        let positions = vec![None; layout.sequences];
1211        Ok(Machine { spec, identity, layout, jobs, memory, positions })
1212    }
1213
1214    /// The machine with its step's residual adds and gated products folded into the matrix jobs that alone feed them
1215    /// ([`fold`]): every value the step computes as it was, in fewer jobs over fewer slots.
1216    pub fn folded(mut self) -> Machine {
1217        self.jobs = fold(&self.jobs, &self.layout);
1218        self
1219    }
1220
1221    /// The machine with each recurrence job's gated norm folded into it ([`fold_delta_norms`]): every value the step
1222    /// computes as it was, the norm made where the read-outs are.
1223    pub fn delta_normed(mut self) -> Machine {
1224        self.jobs = fold_delta_norms(&self.jobs, &self.layout);
1225        self
1226    }
1227
1228    /// The card's memory.
1229    pub fn memory(&self) -> &[u8] {
1230        &self.memory
1231    }
1232
1233    /// A fresh sequence at position 0 in the first region no sequence holds, the region cleared; its handle, the
1234    /// region's index.
1235    pub fn sequence(&mut self) -> R<usize> {
1236        let s = self.positions.iter().position(Option::is_none).ok_or(EngineError::NotYet("more sequences than the machine's regions"))?;
1237        let at = self.layout.region_of(s) as usize;
1238        self.memory[at..at + self.layout.region as usize].fill(0);
1239        self.positions[s] = Some(0);
1240        Ok(s)
1241    }
1242
1243    /// The sequence `sequence` ends: its region is free for the next.
1244    pub fn release(&mut self, sequence: usize) {
1245        self.positions[sequence] = None;
1246    }
1247
1248    pub fn position(&self, sequence: usize) -> usize {
1249        self.positions[sequence].expect("a sequence the machine holds")
1250    }
1251
1252    /// Run each listed sequence's tokens at its next positions; returns, per entry, the logits after each token, as
1253    /// [`super::model::EngineModel::step`] does.
1254    pub fn step(&mut self, entries: &[(usize, Vec<u32>)]) -> R<Vec<Vec<Vec<f32>>>> {
1255        self.step_traced(entries, &mut |_| {})
1256    }
1257
1258    /// [`step`](Self::step), with `trace` shown the machine before each job and after the last.
1259    pub fn step_traced(&mut self, entries: &[(usize, Vec<u32>)], trace: &mut dyn FnMut(Trace)) -> R<Vec<Vec<Vec<f32>>>> {
1260        let mut seen = std::collections::HashSet::new();
1261        for (s, toks) in entries {
1262            if !seen.insert(*s) {
1263                return Err(EngineError::NotYet("one sequence twice in a step"));
1264            }
1265            let at = self.positions.get(*s).copied().flatten().ok_or(EngineError::NoSequence(*s))?;
1266            if at + toks.len() > self.layout.ctx {
1267                return Err(EngineError::PastContext { sequence: *s, position: at + toks.len() });
1268            }
1269        }
1270        let ring = self.ring_rows();
1271        let mut rows = Vec::new();
1272        let mut entry_of = Vec::new();
1273        for (e, (s, toks)) in entries.iter().enumerate() {
1274            for (i, &token) in toks.iter().enumerate() {
1275                let position = self.position(*s) + i;
1276                rows.push(Row { sequence: *s, position, token, ring: position % ring });
1277                entry_of.push(e);
1278            }
1279        }
1280        let mut store: Store = HashMap::new();
1281        let jobs = std::mem::take(&mut self.jobs);
1282        // The store as the engine's holds it: a slot gone once it is dead, its beats another's.
1283        let live = StoreLayout::live_out(&jobs);
1284        let mut result = Ok(());
1285        for (j, job) in jobs.iter().enumerate() {
1286            trace(Trace { job: j, rows: &rows, store: &store, memory: &self.memory });
1287            result = self.run(job, &rows, &mut store);
1288            if result.is_err() {
1289                break;
1290            }
1291            store.retain(|s, _| live[j] >> s.code() & 1 == 1);
1292        }
1293        self.jobs = jobs;
1294        result?;
1295        trace(Trace { job: self.jobs.len(), rows: &rows, store: &store, memory: &self.memory });
1296        let mut out: Vec<Vec<Vec<f32>>> = entries.iter().map(|_| Vec::new()).collect();
1297        for (logits, e) in store.remove(&Slot::Logits).expect("the head's logits").into_iter().zip(entry_of) {
1298            out[e].push(logits);
1299        }
1300        Ok(out)
1301    }
1302
1303    fn run(&mut self, job: &Job, rows: &[Row], store: &mut Store) -> R<()> {
1304        match job {
1305            Job::Embed { tensor } => {
1306                let t = &self.layout.tensors[*tensor];
1307                let rb = t.row_bytes() as usize;
1308                let out = rows
1309                    .iter()
1310                    .map(|r| {
1311                        let at = t.at as usize + r.token as usize * rb;
1312                        let mut v = Vec::new();
1313                        t.ty.dequantize(&self.memory[at..at + rb], &mut v);
1314                        v
1315                    })
1316                    .collect();
1317                store.insert(Slot::Residual, out);
1318            }
1319            Job::Matvec { tensor, from, to, fold } => {
1320                let sums = self.matvec(*tensor, &store[from])?;
1321                let out = match fold {
1322                    Fold::Put => sums,
1323                    _ => {
1324                        let fp = self.layout.numerics.fp_unit;
1325                        let old = &store[to];
1326                        sums.iter().zip(old).map(|(s, o)| s.iter().zip(o).map(|(&s, &o)| fold.apply(fp, o, s)).collect()).collect()
1327                    }
1328                };
1329                store.insert(*to, out);
1330            }
1331            Job::Vector(v) => self.vector(v, rows, store),
1332            Job::Delta { layer, query, key, value, value_at, log_decay, decay, gate, to, norm } => {
1333                let reads = self.delta(*layer, rows, store, [*query, *key, *value, *log_decay, *decay, *gate], *value_at);
1334                store.insert(*to, reads);
1335                if let Some(n) = norm {
1336                    let BlockState::Delta { v_heads, width, .. } = self.layout.blocks[*layer] else { unreachable!("a delta block") };
1337                    let job = gated_norm_job(self.layout.numerics.fp_unit, n.weight, *to, n.z, width, v_heads * width, *to, n.eps);
1338                    self.vector(&job, rows, store);
1339                }
1340            }
1341            Job::Attend { layer, query, heads, scale, to } => {
1342                let out = self.attend(*layer, rows, &store[query], *heads, *scale);
1343                store.insert(*to, out);
1344            }
1345            Job::Advance => {
1346                for r in rows {
1347                    *self.positions[r.sequence].as_mut().expect("a sequence the machine holds") += 1;
1348                }
1349            }
1350        }
1351        Ok(())
1352    }
1353
1354    /// Tensor `tensor`'s elements in f32.
1355    fn tensor_values(&self, tensor: usize) -> Vec<f32> {
1356        let t = &self.layout.tensors[tensor];
1357        let mut v = Vec::new();
1358        t.ty.dequantize(&self.memory[t.at as usize..(t.at + t.bytes) as usize], &mut v);
1359        v
1360    }
1361
1362    /// The block-format matrix `tensor` times each of `xs`, as the array computes it.
1363    fn matvec(&self, tensor: usize, xs: &[Vec<f32>]) -> R<Vec<Vec<f32>>> {
1364        let t = &self.layout.tensors[tensor];
1365        let [in_f, out_f] = t.dims[..] else { return Err(EngineError::NotYet("a projection that is not a matrix")) };
1366        let (in_f, out_f) = (in_f as usize, out_f as usize);
1367        let rb = t.row_bytes() as usize;
1368        let row = |r: usize| &self.memory[t.at as usize + r * rb..t.at as usize + (r + 1) * rb];
1369        let n = self.layout.numerics;
1370        if !reads(t.ty) {
1371            return Err(EngineError::Undecoded { tensor: t.name.clone(), ty: t.ty });
1372        }
1373        let acts: Vec<ActRow> = match n.scaling {
1374            ActScaling::Sub32 => {
1375                if in_f % GROUP != 0 {
1376                    return Err(EngineError::NotYet("a matrix row that is not a whole number of 256-element groups"));
1377                }
1378                xs.iter().map(|x| quantize(x, n.activations)).collect()
1379            }
1380            ActScaling::PerBlock => xs.iter().map(|x| quantize_blocks(x, n.activations, t.ty.block_len())).collect(),
1381        };
1382        let coded: Vec<CodedRow> = (0..out_f)
1383            .map(|r| {
1384                CodedRow::decode(t.ty, row(r)).expect("a format the array reads").map_err(|_| EngineError::Unreadable(format!("{} row {r}", t.name)))
1385            })
1386            .collect::<R<_>>()?;
1387        Ok(acts.iter().map(|a| coded.iter().map(|c| super::numerics::dot(c, a)).collect()).collect())
1388    }
1389
1390
1391    fn vector(&mut self, job: &VectorJob, rows: &[Row], store: &mut Store) {
1392        let fp = self.layout.numerics.fp_unit;
1393        for &(slot, w) in &job.fresh {
1394            store.insert(slot, vec![vec![0f32; w]; rows.len()]);
1395        }
1396        let tensors: HashMap<usize, Vec<f32>> = job
1397            .loads
1398            .iter()
1399            .filter_map(|l| match l.from {
1400                Source::Tensor { tensor, .. } => Some(tensor),
1401                _ => None,
1402            })
1403            .map(|t| (t, self.tensor_values(t)))
1404            .collect();
1405        let passes: Vec<(usize, usize)> = match job.order {
1406            Order::Rows => (0..rows.len()).flat_map(|k| (0..job.heads).map(move |h| (k, h))).collect(),
1407            Order::Heads => (0..job.heads).flat_map(|h| (0..rows.len()).map(move |k| (k, h))).collect(),
1408        };
1409        // The registers hold from pass to pass, as the vector unit's do: a kept load is loaded on the job's first pass
1410        // alone.
1411        let kept: Vec<bool> = (0..job.loads.len()).map(|i| job.kept(i)).collect();
1412        let mut regs = Regs::new(VREGS, SREGS);
1413        for (pass, (k, head)) in passes.into_iter().enumerate() {
1414            let row = &rows[k];
1415            {
1416                for (l, _) in job.loads.iter().zip(&kept).filter(|&(_, &held)| pass == 0 || !held) {
1417                    regs.v[l.reg as usize] = match l.from {
1418                        Source::Slot { slot, at, stride } => {
1419                            let from = at + head * stride;
1420                            store[&slot][k][from..from + l.len].to_vec()
1421                        }
1422                        Source::Tensor { tensor, at, stride } => {
1423                            let from = at + head * stride;
1424                            tensors[&tensor][from..from + l.len].to_vec()
1425                        }
1426                        Source::Rotary { table, sines } => {
1427                            self.read_f32(self.layout.rotary[table].1 as usize + (row.position * 2 + sines as usize) * l.len * 4, l.len)
1428                        }
1429                        Source::Ring { layer, age } => self.read_state(self.ring_at(row, layer, age), l.len),
1430                    };
1431                }
1432                vector::run(fp, &job.program, &mut regs);
1433                for p in &job.puts {
1434                    let v: Vec<f32> = match p.reg {
1435                        Reg::V(r) => regs.v[r as usize][..p.len].to_vec(),
1436                        Reg::S(s) => vec![regs.s[s as usize]],
1437                    };
1438                    match p.to {
1439                        Sink::Slot { slot, at, stride } => {
1440                            let from = at + head * stride;
1441                            store.get_mut(&slot).unwrap_or_else(|| panic!("{slot:?} stored before the program writes it"))[k][from..from + p.len]
1442                                .copy_from_slice(&v);
1443                        }
1444                        // The row's own ring row takes the place of the oldest, which is the row `taps − 1` back.
1445                        Sink::Ring { layer } => self.write_state(self.ring_at(row, layer, 0), &v),
1446                        Sink::Cache { layer, values, part } => match part {
1447                            CachePart::Halves => {
1448                                let at = self.cache_at(row.sequence, layer, row.position, head, values);
1449                                for (i, x) in v.iter().enumerate() {
1450                                    self.memory[at + 2 * i..at + 2 * i + 2].copy_from_slice(&f32_to_f16(*x).to_le_bytes());
1451                                }
1452                            }
1453                            CachePart::Codes => {
1454                                let at = self.cache_at(row.sequence, layer, row.position, head, values);
1455                                for (i, x) in v.iter().enumerate() {
1456                                    self.memory[at + i] = *x as i8 as u8;
1457                                }
1458                            }
1459                            CachePart::Scale => {
1460                                let at = self.scale_at(row.sequence, layer, row.position, head, values);
1461                                self.memory[at..at + 2].copy_from_slice(&f32_to_f16(v[0]).to_le_bytes());
1462                            }
1463                        },
1464                    }
1465                }
1466            }
1467        }
1468    }
1469
1470    fn read_f32(&self, at: usize, n: usize) -> Vec<f32> {
1471        (0..n).map(|i| f32::from_le_bytes(self.memory[at + 4 * i..at + 4 * i + 4].try_into().expect("four bytes"))).collect()
1472    }
1473
1474    /// `n` elements of a recurrent state at `at`, as the state stores them.
1475    fn read_state(&self, at: usize, n: usize) -> Vec<f32> {
1476        match self.layout.numerics.state {
1477            StateFormat::F32 => self.read_f32(at, n),
1478            StateFormat::F16 => (0..n).map(|i| f16_to_f32(u16::from_le_bytes([self.memory[at + 2 * i], self.memory[at + 2 * i + 1]]))).collect(),
1479        }
1480    }
1481
1482    /// `values` stored as the state stores them at `at`.
1483    fn write_state(&mut self, at: usize, values: &[f32]) {
1484        match self.layout.numerics.state {
1485            StateFormat::F32 => {
1486                for (i, v) in values.iter().enumerate() {
1487                    self.memory[at + 4 * i..at + 4 * i + 4].copy_from_slice(&v.to_le_bytes());
1488                }
1489            }
1490            StateFormat::F16 => {
1491                for (i, v) in values.iter().enumerate() {
1492                    self.memory[at + 2 * i..at + 2 * i + 2].copy_from_slice(&f32_to_f16(*v).to_le_bytes());
1493                }
1494            }
1495        }
1496    }
1497
1498    /// The rows every conv ring holds, `taps − 1`, the same for every block (1 for a model of none).
1499    fn ring_rows(&self) -> usize {
1500        let mut rows = self.layout.blocks.iter().filter_map(|b| match b {
1501            BlockState::Delta { taps, .. } => Some(taps.saturating_sub(1).max(1)),
1502            _ => None,
1503        });
1504        let first = rows.next().unwrap_or(1);
1505        assert!(rows.all(|r| r == first), "every conv ring of one length");
1506        first
1507    }
1508
1509    /// Where `row`'s sequence keeps layer `layer`'s ring row `age` positions past the oldest: the row of position
1510    /// `p − (taps − 1) + age`, at its index modulo `taps − 1`, which is `row.ring + age` less `taps − 1` once it passes.
1511    fn ring_at(&self, row: &Row, layer: usize, age: usize) -> usize {
1512        let BlockState::Delta { ring, channels, taps, .. } = self.layout.blocks[layer] else { unreachable!("a ring in a delta block") };
1513        let index = (row.ring + age) % (taps - 1);
1514        (self.layout.region_of(row.sequence) + ring) as usize + index * channels * state_bytes(self.layout.numerics.state) as usize
1515    }
1516
1517    /// Where sequence `sequence` keeps layer `layer`'s cached head `head` of position `position`: its keys, or its
1518    /// values.
1519    fn cache_at(&self, sequence: usize, layer: usize, position: usize, head: usize, values: bool) -> usize {
1520        let BlockState::Attention { keys, values: vs, kv_heads, width, value_width, .. } = self.layout.blocks[layer] else {
1521            unreachable!("a cache in an attention block")
1522        };
1523        let (base, w) = if values { (vs, value_width) } else { (keys, width) };
1524        let row = cache_row_bytes(self.layout.numerics.cache, w) as usize;
1525        (self.layout.region_of(sequence) + base) as usize + (position * kv_heads + head) * row
1526    }
1527
1528    /// A cached head row as the vector units read it: binary16 elements, or int8 codes times their scale.
1529    fn cache_values(&self, sequence: usize, layer: usize, position: usize, head: usize, values: bool, width: usize) -> Vec<f32> {
1530        let at = self.cache_at(sequence, layer, position, head, values);
1531        match self.layout.numerics.cache {
1532            CacheFormat::F16 => (0..width).map(|i| f16_to_f32(u16::from_le_bytes([self.memory[at + 2 * i], self.memory[at + 2 * i + 1]]))).collect(),
1533            CacheFormat::Int8 | CacheFormat::Int8Hadamard => {
1534                let (scale, codes) = self.cache_int8(sequence, layer, position, head, values, width);
1535                let s = f16_to_f32(scale);
1536                codes.iter().map(|&c| self.layout.numerics.fp_unit.mul(c as f32, s)).collect()
1537            }
1538        }
1539    }
1540
1541    /// Where sequence `sequence` keeps the scale of layer `layer`'s int8 cached head `head` of position `position`.
1542    fn scale_at(&self, sequence: usize, layer: usize, position: usize, head: usize, values: bool) -> usize {
1543        let BlockState::Attention { key_scales: Some(ks), value_scales: Some(vs), kv_heads, .. } = self.layout.blocks[layer] else {
1544            unreachable!("an int8 cache's scales")
1545        };
1546        (self.layout.region_of(sequence) + if values { vs } else { ks }) as usize + (position * kv_heads + head) * 2
1547    }
1548
1549    /// A cached int8 head row as the array reads it: its scale's bits and its codes.
1550    fn cache_int8(&self, sequence: usize, layer: usize, position: usize, head: usize, values: bool, width: usize) -> (u16, Vec<i8>) {
1551        let (at, s) = (self.cache_at(sequence, layer, position, head, values), self.scale_at(sequence, layer, position, head, values));
1552        (u16::from_le_bytes([self.memory[s], self.memory[s + 1]]), self.memory[at..at + width].iter().map(|&b| b as i8).collect())
1553    }
1554
1555    /// A Gated-DeltaNet block's memory through each row, each sequence's rows in order, every value row read from and
1556    /// written back to the sequence's region as the state stores it; with a lazy flush, the row's token logged after
1557    /// the log's tokens, the query folded through the log, and the rows its [`Replay`] names replayed into the memory
1558    /// and written back.
1559    fn delta(&mut self, layer: usize, rows: &[Row], store: &Store, slots: [Slot; 6], value_at: usize) -> Vec<Vec<f32>> {
1560        let BlockState::Delta { memory, log, entry, k_heads: nk, v_heads: nv, width: hd, .. } = self.layout.blocks[layer] else {
1561            unreachable!("a delta block")
1562        };
1563        let fp = self.layout.numerics.fp_unit;
1564        let [query, key, value, log_decay, decay, gate] = slots.map(|s| &store[&s]);
1565        let scale = (1.0 / (hd as f64).sqrt()) as f32;
1566        let n = self.layout.numerics;
1567        let eb = state_bytes(n.state) as usize;
1568        let round = move |v: f32| match n.state {
1569            StateFormat::F32 => v,
1570            StateFormat::F16 => f16_to_f32(f32_to_f16(v)),
1571        };
1572        let mut reads = Vec::with_capacity(rows.len());
1573        for (k, row) in rows.iter().enumerate() {
1574            let region = self.layout.region_of(row.sequence) as usize;
1575            let mem = region + memory as usize;
1576            let (q, kk, v) = (&query[k], &key[k], &value[k][value_at..]);
1577            let (log_decays, decays, betas) = (&log_decay[k], &decay[k], &gate[k]);
1578            let mut out = vec![0f32; nv * hd];
1579            let row_at = |h: usize, r: usize| mem + (h * hd + r) * hd * eb;
1580            match n.flush {
1581                StateFlush::EveryPass => {
1582                    for h in 0..nv {
1583                        let g = h % nk;
1584                        let (hq, hk) = (&q[g * hd..(g + 1) * hd], &kk[g * hd..(g + 1) * hd]);
1585                        let kq = fdot(fp, hk, hq);
1586                        for r in 0..hd {
1587                            let mut m = self.read_state(row_at(h, r), hd);
1588                            out[h * hd + r] = gdn_row(fp, &mut m, hk, hq, decays[h], betas[h], v[h * hd + r], kq, scale, round);
1589                            self.write_state(row_at(h, r), &m);
1590                        }
1591                    }
1592                }
1593                StateFlush::Every { .. } | StateFlush::Trailing { .. } => {
1594                    let lg = region + log.expect("a lazily flushed memory's log") as usize;
1595                    let f = LogFields::of(nk, nv, hd);
1596                    let capacity = Replay::capacity(n.flush);
1597                    let word = |m: &[u8], i: usize| u32::from_le_bytes(m[lg + 4 * i..lg + 4 * i + 4].try_into().expect("four bytes")) as usize;
1598                    let (count, oldest) = (word(&self.memory, 0), word(&self.memory, 1));
1599                    let entry_at = |i: usize| lg + LINE as usize + (oldest + i) % capacity * entry as usize;
1600                    let mut logged = vec![0f32; f.elems];
1601                    logged[..nk * hd].copy_from_slice(kk);
1602                    logged[f.values..f.values + nv * hd].copy_from_slice(&v[..nv * hd]);
1603                    logged[f.log_decays..f.log_decays + nv].copy_from_slice(log_decays);
1604                    logged[f.gates..f.gates + nv].copy_from_slice(betas);
1605                    self.write_state(entry_at(count), &logged);
1606                    let count = count + 1;
1607                    let replay = Replay::at(n.flush, hd, count);
1608                    let entries: Vec<Vec<f32>> = (0..count).map(|i| self.read_state(entry_at(i), f.elems)).collect();
1609                    for h in 0..nv {
1610                        let g = h % nk;
1611                        let head: Vec<Logged> = entries
1612                            .iter()
1613                            .map(|e| Logged {
1614                                k: &e[g * hd..(g + 1) * hd],
1615                                v: &e[f.values + h * hd..f.values + (h + 1) * hd],
1616                                log_decay: e[f.log_decays + h],
1617                                beta: e[f.gates + h],
1618                            })
1619                            .collect();
1620                        let folds = gdn_fold_trailing(fp, &q[g * hd..(g + 1) * hd], &head, hd, replay.older);
1621                        for r in 0..hd {
1622                            let mut m = self.read_state(row_at(h, r), hd);
1623                            let (y, carried) = &folds[usize::from(r >= replay.replayed)];
1624                            out[h * hd + r] = gdn_read(fp, &m, y, carried[r], scale);
1625                            if replay.rows.contains(&r) {
1626                                gdn_flush(fp, &mut m, &head[..replay.by], r, round);
1627                                self.write_state(row_at(h, r), &m);
1628                            }
1629                        }
1630                    }
1631                    let (count, oldest) = (count - replay.drop, (oldest + replay.drop) % capacity);
1632                    self.memory[lg..lg + 4].copy_from_slice(&(count as u32).to_le_bytes());
1633                    self.memory[lg + 4..lg + 8].copy_from_slice(&(oldest as u32).to_le_bytes());
1634                }
1635            }
1636            reads.push(out);
1637        }
1638        reads
1639    }
1640
1641    /// Each row's `heads` query heads over layer `layer`'s cache of its sequence to its own position, the heads in
1642    /// groups over the cached heads, each score scaled by `scale`.
1643    fn attend(&self, layer: usize, rows: &[Row], queries: &[Vec<f32>], heads: usize, scale: f32) -> Vec<Vec<f32>> {
1644        let BlockState::Attention { kv_heads, width: hd, value_width: vd, .. } = self.layout.blocks[layer] else { unreachable!("an attention block") };
1645        let group = heads / kv_heads;
1646        let n = self.layout.numerics;
1647        let fp = n.fp_unit;
1648        rows.iter()
1649            .zip(queries)
1650            .map(|(row, q)| {
1651                let mut out = vec![0f32; heads * vd];
1652                for h in 0..heads {
1653                    let g = h / group;
1654                    let qh = &q[h * hd..(h + 1) * hd];
1655                    let oh = match n.attention {
1656                        AttentionMath::Vector => {
1657                            let keys: Vec<Vec<f32>> = (0..=row.position).map(|j| self.cache_values(row.sequence, layer, j, g, false, hd)).collect();
1658                            let values: Vec<Vec<f32>> = (0..=row.position).map(|j| self.cache_values(row.sequence, layer, j, g, true, vd)).collect();
1659                            let (keys, values): (Vec<&[f32]>, Vec<&[f32]>) = (keys.iter().map(|k| k.as_slice()).collect(), values.iter().map(|v| v.as_slice()).collect());
1660                            attend_vector(fp, qh, &keys, &values, scale)
1661                        }
1662                        AttentionMath::Array => {
1663                            let keys: Vec<(u16, Vec<i8>)> = (0..=row.position).map(|j| self.cache_int8(row.sequence, layer, j, g, false, hd)).collect();
1664                            let values: Vec<(u16, Vec<i8>)> = (0..=row.position).map(|j| self.cache_int8(row.sequence, layer, j, g, true, vd)).collect();
1665                            let keys: Vec<(u16, &[i8])> = keys.iter().map(|(s, c)| (*s, c.as_slice())).collect();
1666                            let values: Vec<(u16, &[i8])> = values.iter().map(|(s, c)| (*s, c.as_slice())).collect();
1667                            attend_int8(fp, qh, &keys, &values, scale, n.activations, n.scaling)
1668                        }
1669                    };
1670                    out[h * vd..(h + 1) * vd].copy_from_slice(&oh);
1671                }
1672                out
1673            })
1674            .collect()
1675    }
1676}
1677
1678// ── The sequencer's words ───────────────────────────────────────────────────────────────────────────
1679
1680/// A job as the sequencer reads it: 64-bit words, a header and then the job's own.
1681///
1682/// The header's `[3:0]` is the unit — 0 the embedding, 1 the array, 2 the vector unit, 3 the recurrence unit, 4 the
1683/// attention unit, 5 the step's advance — `[4]` a vector job's order (heads first when set), `[12:5]` the layer,
1684/// `[20:13]` a vector job's fresh slots, `[28:21]` its loads, `[36:29]` its stores, `[52:37]` its instructions. The
1685/// embedding's word is its tensor; the array's its tensor `[19:0]`, its slots `[24:20]` and `[29:25]` and its fold
1686/// `[31:30]` ([`Fold::code`]); the recurrence
1687/// unit's its seven slots five bits each from bit 0 and the values' element `[54:35]`; a vector job's header its chunk
1688/// flag at `[53]` ([`VectorJob::chunked`]: run a chunk of its vectors at a time) and its group flag at `[54]`
1689/// ([`VectorJob::grouped`]: its passes stand alone); the attention unit's its query
1690/// and output slots `[4:0]` and `[9:5]`, its heads `[25:10]` and its score scale's bits `[57:26]`. A vector job's words
1691/// are its heads, each fresh slot (`[4:0]` the slot, `[36:5]` its width), each load and each store in two words, and
1692/// each instruction ([`super::rtl::simd::encode`]). A load's first word holds its register `[3:0]`, its source `[7:4]`
1693/// (0 a slot, 1 a tensor, 2 a rotary table, 3 a ring), the slot, tensor, table or layer `[23:8]`, the ring row's age, the
1694/// sines' flag or a tensor's kept flag ([`VectorJob::kept`]: loaded on the job's first pass alone) `[31:24]` and its
1695/// length `[63:32]`; a store's its register `[3:0]`, scalar `[4]`, its sink `[7:5]`
1696/// (0 a slot, 1 a ring, 2 a cache), the slot or layer `[23:8]`, values `[24]`, the cache part `[26:25]` (halves, codes,
1697/// scale) and its length `[63:32]`; the second word of each its element `[31:0]` and its stride a head `[63:32]`.
1698pub fn encode(jobs: &[Job]) -> Vec<u64> {
1699    let mut w = Vec::new();
1700    let header = |unit: u64, order: bool, layer: usize, counts: [usize; 4]| -> u64 {
1701        assert!(layer < 256 && counts[0] < 256 && counts[1] < 256 && counts[2] < 256 && counts[3] < 1 << 16, "a job past its header's fields");
1702        unit | (order as u64) << 4 | (layer as u64) << 5 | (counts[0] as u64) << 13 | (counts[1] as u64) << 21 | (counts[2] as u64) << 29 | (counts[3] as u64) << 37
1703    };
1704    let s = |slot: Slot| slot.code() as u64;
1705    let n32 = |v: usize| u32::try_from(v).expect("a field within 32 bits") as u64;
1706    for job in jobs {
1707        match job {
1708            Job::Embed { tensor } => w.extend([header(0, false, 0, [0; 4]), n32(*tensor)]),
1709            Job::Matvec { tensor, from, to, fold } => {
1710                assert!(*tensor < 1 << 20, "a tensor index past 20 bits");
1711                w.extend([header(1, false, 0, [0; 4]), *tensor as u64 | s(*from) << 20 | s(*to) << 25 | fold.code() << 30]);
1712            }
1713            Job::Delta { layer, query, key, value, value_at, log_decay, decay, gate, to, norm } => {
1714                assert!(*value_at < 1 << 20, "an element past 20 bits");
1715                let slots = [*query, *key, *value, *log_decay, *decay, *gate, *to].iter().enumerate().fold(0u64, |a, (i, &x)| a | s(x) << (5 * i));
1716                let normed = norm.map_or(0, |n| 1 << 55 | s(n.z) << 56);
1717                w.extend([header(3, false, *layer, [0; 4]), slots | (*value_at as u64) << 35 | normed]);
1718                if let Some(n) = norm {
1719                    assert!(n.weight < 1 << 20, "a tensor index past 20 bits");
1720                    w.push(n.weight as u64 | (n.eps.to_bits() as u64) << 20);
1721                }
1722            }
1723            Job::Attend { layer, query, heads, scale, to } => {
1724                assert!(*heads < 1 << 16, "heads past 16 bits");
1725                w.extend([header(4, false, *layer, [0; 4]), s(*query) | s(*to) << 5 | (*heads as u64) << 10 | (scale.to_bits() as u64) << 26]);
1726            }
1727            Job::Advance => w.push(header(5, false, 0, [0; 4])),
1728            Job::Vector(v) => {
1729                w.push(header(2, v.order == Order::Heads, 0, [v.fresh.len(), v.loads.len(), v.puts.len(), v.program.len()]) | (v.chunked() as u64) << 53 | (v.grouped() as u64) << 54);
1730                w.push(n32(v.heads));
1731                for &(slot, width) in &v.fresh {
1732                    w.push(s(slot) | n32(width) << 5);
1733                }
1734                for (i, l) in v.loads.iter().enumerate() {
1735                    let (kind, what, extra, at, stride) = match l.from {
1736                        Source::Slot { slot, at, stride } => (0u64, s(slot), 0u64, at, stride),
1737                        Source::Tensor { tensor, at, stride } => (1, tensor as u64, u64::from(v.kept(i)), at, stride),
1738                        Source::Rotary { table, sines } => (2, table as u64, sines as u64, 0, 0),
1739                        Source::Ring { layer, age } => (3, layer as u64, age as u64, 0, 0),
1740                    };
1741                    assert!(what < 1 << 16 && extra < 256 && (l.reg as usize) < VREGS, "a load past its fields: {l:?}");
1742                    w.push(l.reg as u64 | kind << 4 | what << 8 | extra << 24 | n32(l.len) << 32);
1743                    w.push(n32(at) | n32(stride) << 32);
1744                }
1745                for p in &v.puts {
1746                    let (reg, scalar) = match p.reg {
1747                        Reg::V(r) => (r as u64, 0u64),
1748                        Reg::S(r) => (r as u64, 1),
1749                    };
1750                    let (kind, what, values, part, at, stride) = match p.to {
1751                        Sink::Slot { slot, at, stride } => (0u64, s(slot), 0u64, 0u64, at, stride),
1752                        Sink::Ring { layer } => (1, layer as u64, 0, 0, 0, 0),
1753                        Sink::Cache { layer, values, part } => {
1754                            let part = match part {
1755                                CachePart::Halves => 0,
1756                                CachePart::Codes => 1,
1757                                CachePart::Scale => 2,
1758                            };
1759                            (2, layer as u64, values as u64, part, 0, 0)
1760                        }
1761                    };
1762                    assert!(what < 1 << 16 && reg < 16, "a store past its fields: {p:?}");
1763                    w.push(reg | scalar << 4 | kind << 5 | what << 8 | values << 24 | part << 25 | n32(p.len) << 32);
1764                    w.push(n32(at) | n32(stride) << 32);
1765                }
1766                w.extend(v.program.iter().map(super::rtl::simd::encode));
1767            }
1768        }
1769    }
1770    w
1771}
1772
1773/// The jobs `words` stand for, or where a word stands for none.
1774pub fn decode(words: &[u64]) -> Result<Vec<Job>, String> {
1775    struct Cursor<'a> {
1776        words: &'a [u64],
1777        at: usize,
1778    }
1779    impl Cursor<'_> {
1780        fn next(&mut self, what: &str) -> Result<u64, String> {
1781            let w = *self.words.get(self.at).ok_or_else(|| format!("the words end inside {what}"))?;
1782            self.at += 1;
1783            Ok(w)
1784        }
1785    }
1786    let mut c = Cursor { words, at: 0 };
1787    let mut jobs = Vec::new();
1788    let slot = |code: u64| Slot::of_code((code & 0x1f) as u8).ok_or_else(|| format!("no slot {}", code & 0x1f));
1789    while c.at < words.len() {
1790        let mut next = |what: &str| c.next(what);
1791        let h = next("a header")?;
1792        let (unit, heads_first, layer) = (h & 0xf, h >> 4 & 1 == 1, (h >> 5 & 0xff) as usize);
1793        let (fresh, loads, puts, program) = ((h >> 13 & 0xff) as usize, (h >> 21 & 0xff) as usize, (h >> 29 & 0xff) as usize, (h >> 37 & 0xffff) as usize);
1794        jobs.push(match unit {
1795            0 => Job::Embed { tensor: next("an embedding")? as usize },
1796            1 => {
1797                let x = next("a matrix")?;
1798                let fold = Fold::of_code(x >> 30 & 3).ok_or_else(|| format!("no fold {}", x >> 30 & 3))?;
1799                Job::Matvec { tensor: (x & 0xf_ffff) as usize, from: slot(x >> 20)?, to: slot(x >> 25)?, fold }
1800            }
1801            3 => {
1802                let x = next("a recurrence")?;
1803                let sl = |k: u32| slot(x >> (5 * k));
1804                Job::Delta {
1805                    layer,
1806                    query: sl(0)?,
1807                    key: sl(1)?,
1808                    value: sl(2)?,
1809                    log_decay: sl(3)?,
1810                    decay: sl(4)?,
1811                    gate: sl(5)?,
1812                    to: sl(6)?,
1813                    value_at: (x >> 35 & 0xf_ffff) as usize,
1814                    norm: if x >> 55 & 1 == 1 {
1815                        let n = next("a recurrence's norm")?;
1816                        Some(DeltaNorm { weight: (n & 0xf_ffff) as usize, z: slot(x >> 56)?, eps: f32::from_bits((n >> 20) as u32) })
1817                    } else {
1818                        None
1819                    },
1820                }
1821            }
1822            4 => {
1823                let x = next("an attention")?;
1824                Job::Attend {
1825                    layer,
1826                    query: slot(x)?,
1827                    to: slot(x >> 5)?,
1828                    heads: (x >> 10 & 0xffff) as usize,
1829                    scale: f32::from_bits((x >> 26) as u32),
1830                }
1831            }
1832            5 => Job::Advance,
1833            2 => {
1834                let heads = next("a vector job's heads")? as u32 as usize;
1835                let fresh = (0..fresh)
1836                    .map(|_| {
1837                        let x = next("a fresh slot")?;
1838                        Ok((slot(x)?, (x >> 5) as u32 as usize))
1839                    })
1840                    .collect::<Result<Vec<_>, String>>()?;
1841                let (loads, flags): (Vec<Load>, Vec<Option<usize>>) = (0..loads)
1842                    .map(|_| {
1843                        let (a, b) = (next("a load")?, next("a load")?);
1844                        let (what, extra, at, stride) = ((a >> 8 & 0xffff) as usize, (a >> 24 & 0xff) as usize, b as u32 as usize, (b >> 32) as usize);
1845                        let (from, kept) = match a >> 4 & 0xf {
1846                            0 => (Source::Slot { slot: slot(what as u64)?, at, stride }, None),
1847                            1 => (Source::Tensor { tensor: what, at, stride }, Some(extra)),
1848                            2 => (Source::Rotary { table: what, sines: extra == 1 }, None),
1849                            3 => (Source::Ring { layer: what, age: extra }, None),
1850                            k => return Err(format!("no load source {k}")),
1851                        };
1852                        Ok((Load { reg: (a & 0xf) as VReg, from, len: (a >> 32) as usize }, kept))
1853                    })
1854                    .collect::<Result<Vec<_>, String>>()?
1855                    .into_iter()
1856                    .unzip();
1857                let puts = (0..puts)
1858                    .map(|_| {
1859                        let (a, b) = (next("a store")?, next("a store")?);
1860                        let reg = if a >> 4 & 1 == 1 { Reg::S((a & 0xf) as SReg) } else { Reg::V((a & 0xf) as VReg) };
1861                        let what = (a >> 8 & 0xffff) as usize;
1862                        let to = match a >> 5 & 0x7 {
1863                            0 => Sink::Slot { slot: slot(what as u64)?, at: b as u32 as usize, stride: (b >> 32) as usize },
1864                            1 => Sink::Ring { layer: what },
1865                            2 => Sink::Cache {
1866                                layer: what,
1867                                values: a >> 24 & 1 == 1,
1868                                part: match a >> 25 & 0x3 {
1869                                    0 => CachePart::Halves,
1870                                    1 => CachePart::Codes,
1871                                    2 => CachePart::Scale,
1872                                    p => return Err(format!("no cache part {p}")),
1873                                },
1874                            },
1875                            k => return Err(format!("no store sink {k}")),
1876                        };
1877                        Ok(Put { reg, to, len: (a >> 32) as usize })
1878                    })
1879                    .collect::<Result<Vec<_>, String>>()?;
1880                let program = (0..program)
1881                    .map(|_| {
1882                        let x = next("an instruction")?;
1883                        super::rtl::simd::decode(x).ok_or_else(|| format!("no instruction {x:#018x}"))
1884                    })
1885                    .collect::<Result<Vec<_>, String>>()?;
1886                let job = VectorJob { heads, order: if heads_first { Order::Heads } else { Order::Rows }, fresh, loads, program, puts };
1887                for (i, flag) in flags.iter().enumerate() {
1888                    if let Some(flag) = *flag {
1889                        if flag != usize::from(job.kept(i)) {
1890                            return Err(format!("load {i}'s kept flag {flag}, the job keeping it {}", job.kept(i)));
1891                        }
1892                    }
1893                }
1894                if (h >> 53 & 1 == 1) != job.chunked() {
1895                    return Err(format!("a vector job's chunk flag {}, the job running a chunk at a time {}", h >> 53 & 1, job.chunked()));
1896                }
1897                if (h >> 54 & 1 == 1) != job.grouped() {
1898                    return Err(format!("a vector job's group flag {}, the job's passes standing alone {}", h >> 54 & 1, job.grouped()));
1899                }
1900                Job::Vector(job)
1901            }
1902            u => return Err(format!("no unit {u}")),
1903        });
1904    }
1905    Ok(jobs)
1906}