Skip to main content

logicaffeine_compile/spatial/decode_engine/
model.rs

1//! The decode engine's model: a hybrid Gated-DeltaNet checkpoint (`qwen35`, Qwen3.5 and Qwen3.8) run over a batch of
2//! sequences in the engine's arithmetic, every number from [`super::numerics`].
3//!
4//! Each sequence carries its own state: per Gated-DeltaNet block its conv ring and its memory, per attention block
5//! its key and value caches, held in the formats the knobs name ([`EngineNumerics`]). A step takes any rows of any
6//! sequences; a sequence's rows run in order through its own state, and every row's arithmetic is its own, so a
7//! sequence's numbers never depend on what else rides the batch.
8//!
9//! The matrices run as the array runs them: a block-format row's exact sums against its activations quantized as the
10//! knob names, per 32 elements or per the matrix's own block (`numerics::dot`), an f32 matrix's dot in the vector
11//! units' lanes.
12//!
13//! A step is the checkpoint's program at the model's knob point ([`super::program::lower`]: the model's one program,
14//! read off its step graph), its commands run in order over the step's token-rows: the model is the interpreter of the
15//! program the engine's sequencer issues.
16
17use std::collections::HashMap;
18use std::sync::Arc;
19
20use crate::model::quant::{f16_to_f32, GgmlType};
21use crate::model::reference::WeightSource;
22use crate::model::spec::{Ffn, Layer, Mixer, ModelSpec, Rope, RopeScaling};
23use crate::spatial::serving::{AttentionMath, StateFlush};
24
25use super::numerics::{
26    attend_int8, attend_vector, cache_row, f32_to_f16, fdot, gdn_flush, gdn_fold_trailing, gdn_read, gdn_row, hadamard, ldexp,
27    quantize, quantize_blocks, ActRow, ActScaling, CodedRow, FpUnit, Logged, Words, GROUP,
28};
29use super::program::{Command, Program, Slot};
30use super::vector;
31
32/// How a sequence's recurrent memory and conv ring are stored between tokens.
33#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
34pub enum StateFormat {
35    F32,
36    F16,
37}
38
39/// How an attention block's keys and values are stored.
40#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
41pub enum CacheFormat {
42    /// Each element binary16.
43    F16,
44    /// Each head row of a position int8 codes under one f16 scale ([`cache_row`]).
45    Int8,
46    /// Each head row rotated by the Walsh–Hadamard transform ([`hadamard`]) before its int8 codes, so a few large
47    /// channels spread across the row and its one scale fits every element; the query rotated alike (a score is the
48    /// rotated dot over the head's length) and the weighted sum of values rotated back.
49    Int8Hadamard,
50}
51
52impl CacheFormat {
53    /// Whether the cache holds int8 codes, so attention can run on the array.
54    pub fn is_int8(self) -> bool {
55        matches!(self, CacheFormat::Int8 | CacheFormat::Int8Hadamard)
56    }
57}
58
59/// The knobs of the engine's arithmetic.
60#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
61pub struct EngineNumerics {
62    pub activations: Words,
63    /// How the activations a matrix reads are scaled: per 32 elements, or per the matrix format's own block.
64    pub scaling: ActScaling,
65    pub state: StateFormat,
66    pub cache: CacheFormat,
67    pub attention: AttentionMath,
68    /// How a Gated-DeltaNet memory is carried: rewritten every token, or held at its last flush with a log of the
69    /// tokens since, read through the log and flushed once every `tokens`, or a window of `tokens` behind ([`Replay`]).
70    pub flush: StateFlush,
71    /// The multiplier every f32 product is computed by: the engine's own, or the card's floating-point DSP block.
72    pub fp_unit: FpUnit,
73}
74
75impl EngineNumerics {
76    /// The most precise knob point: two-word activations scaled per 32 elements, an f32 state, an f16 cache, attention
77    /// on the vector units.
78    pub fn precise() -> EngineNumerics {
79        EngineNumerics {
80            activations: Words::Two,
81            scaling: ActScaling::Sub32,
82            state: StateFormat::F32,
83            cache: CacheFormat::F16,
84            attention: AttentionMath::Vector,
85            flush: StateFlush::EveryPass,
86            fp_unit: FpUnit::Soft,
87        }
88    }
89
90    /// The fastest knob point the serving roofline prices: one-word activations scaled per weight block, an f16 state
91    /// flushed every 16 tokens, a rotated int8 cache, attention on the array.
92    pub fn fastest() -> EngineNumerics {
93        EngineNumerics {
94            activations: Words::One,
95            scaling: ActScaling::PerBlock,
96            state: StateFormat::F16,
97            cache: CacheFormat::Int8Hadamard,
98            attention: AttentionMath::Array,
99            flush: StateFlush::Every { tokens: FLUSH_TOKENS },
100            fp_unit: FpUnit::Soft,
101        }
102    }
103
104    /// The knob points held to the real checkpoints, both word counts at their most and least precise storage:
105    /// [`precise`](Self::precise); two words over an f16 state and an int8 cache on the array; one word over an f32
106    /// state and an f16 cache; [`fastest`](Self::fastest).
107    pub fn real_points() -> [EngineNumerics; 4] {
108        let at = |activations, state, cache, attention| EngineNumerics {
109            activations,
110            scaling: ActScaling::Sub32,
111            state,
112            cache,
113            attention,
114            flush: StateFlush::EveryPass,
115            fp_unit: FpUnit::Soft,
116        };
117        [
118            EngineNumerics::precise(),
119            at(Words::Two, StateFormat::F16, CacheFormat::Int8, AttentionMath::Array),
120            at(Words::One, StateFormat::F32, CacheFormat::F16, AttentionMath::Vector),
121            EngineNumerics::fastest(),
122        ]
123    }
124
125    /// The knob points a real checkpoint of `spec` is held to: [`real_points`](Self::real_points) when every attention
126    /// layer norms its queries and keys, and otherwise the same points with the two-word point's int8 cache rotated and
127    /// the family's [`fastest_for`](Self::fastest_for): un-normed keys carry outlier channels an unrotated int8 cache
128    /// loses and the rotation spreads. Measured on Llama-3.2-1B against llama.cpp's runs of its corpus: two words on an
129    /// int8 cache followed the f32 run 458 steps to the shipped run's 462, rotated 501.
130    pub fn real_points_for(spec: &ModelSpec) -> [EngineNumerics; 4] {
131        let mut points = Self::real_points();
132        if !keys_normed(spec) {
133            points[1].cache = CacheFormat::Int8Hadamard;
134            points[3] = Self::fastest_for(spec);
135        }
136        points
137    }
138
139    /// The fastest knob point `spec`'s family is held to: [`fastest`](Self::fastest), its attention on the vector units
140    /// when a layer's keys are not normed (Llama-3.2-1B's logits after the prompt sat 0.098 from llama.cpp's f32 run's
141    /// on the vector units, within the shipped run's 0.080 by two standard errors, and 0.112 on the array).
142    pub fn fastest_for(spec: &ModelSpec) -> EngineNumerics {
143        let fastest = Self::fastest();
144        if keys_normed(spec) {
145            fastest
146        } else {
147            EngineNumerics { attention: AttentionMath::Vector, ..fastest }
148        }
149    }
150
151    /// Every knob point the engine runs.
152    pub fn all() -> Vec<EngineNumerics> {
153        let mut out = Vec::new();
154        for activations in [Words::One, Words::Two] {
155            for scaling in [ActScaling::Sub32, ActScaling::PerBlock] {
156                for state in [StateFormat::F32, StateFormat::F16] {
157                    for cache in [CacheFormat::F16, CacheFormat::Int8, CacheFormat::Int8Hadamard] {
158                        for attention in [AttentionMath::Vector, AttentionMath::Array] {
159                            if attention == AttentionMath::Array && !cache.is_int8() {
160                                continue;
161                            }
162                            for flush in [StateFlush::EveryPass, StateFlush::Every { tokens: FLUSH_TOKENS }, StateFlush::Trailing { tokens: TRAILING_TOKENS }] {
163                                out.push(EngineNumerics { activations, scaling, state, cache, attention, flush, fp_unit: FpUnit::Soft });
164                            }
165                        }
166                    }
167                }
168            }
169        }
170        out
171    }
172}
173
174/// Whether every attention layer of `spec` norms its queries and keys (a model with none, vacuously).
175fn keys_normed(spec: &ModelSpec) -> bool {
176    spec.layers.iter().all(|l| match &l.mixer {
177        Mixer::Attention(a) => a.qk_norm,
178        _ => true,
179    })
180}
181
182/// Tokens a lazily flushed memory carries in its log between flushes.
183pub const FLUSH_TOKENS: u32 = 16;
184
185/// Tokens of each window a trailing flush replays: half of [`FLUSH_TOKENS`], so its log, two windows at its longest,
186/// is as long as a flush's every [`FLUSH_TOKENS`].
187pub const TRAILING_TOKENS: u32 = FLUSH_TOKENS / 2;
188
189/// What a lazily flushed head does at a token whose log, the token's own entry appended, holds `count` tokens
190/// ([`Replay::at`]).
191#[derive(Clone, Debug, PartialEq, Eq)]
192pub struct Replay {
193    /// The log's oldest tokens being replayed into the memory: rows below `replayed` are, and read through the fold
194    /// over the tokens after these alone; every other row reads through the whole log.
195    pub older: usize,
196    pub replayed: usize,
197    /// The rows the token replays the log's first `by` tokens into, each stored once.
198    pub rows: std::ops::Range<usize>,
199    pub by: usize,
200    /// Tokens dropped from the log's front once the token is through.
201    pub drop: usize,
202}
203
204impl Replay {
205    /// The token of a `width`-row head whose log holds `count` tokens under `flush`. Every `tokens`: every row reads
206    /// through the whole log, and once it holds `tokens` every row replays it and it empties. Trailing `tokens`: once
207    /// the log holds a window past the first, the token at `count` replays the first window into the rows of share
208    /// `j = count − tokens − 1`, from row `j·width/tokens` to row `(j+1)·width/tokens`; the rows of the shares before
209    /// read through the newer tokens; at `2·tokens` every row has replayed it and the window is dropped.
210    pub fn at(flush: StateFlush, width: usize, count: usize) -> Replay {
211        match flush {
212            StateFlush::EveryPass => unreachable!("a memory rewritten every pass keeps no log"),
213            StateFlush::Every { tokens } => {
214                let full = count >= tokens as usize;
215                Replay { older: 0, replayed: 0, rows: if full { 0..width } else { 0..0 }, by: count, drop: if full { count } else { 0 } }
216            }
217            StateFlush::Trailing { tokens } => {
218                let t = tokens as usize;
219                if count <= t {
220                    return Replay { older: 0, replayed: 0, rows: 0..0, by: 0, drop: 0 };
221                }
222                let j = count - t - 1;
223                let (lo, hi) = (j * width / t, (j + 1) * width / t);
224                Replay { older: t, replayed: lo, rows: lo..hi, by: t, drop: if count == 2 * t { t } else { 0 } }
225            }
226        }
227    }
228
229    /// The tokens a lazily flushed memory's log holds at its longest under `flush`.
230    pub fn capacity(flush: StateFlush) -> usize {
231        match flush {
232            StateFlush::EveryPass => 0,
233            StateFlush::Every { tokens } => tokens as usize,
234            StateFlush::Trailing { tokens } => 2 * tokens as usize,
235        }
236    }
237}
238
239/// Why the engine's model cannot run a checkpoint.
240#[derive(Debug)]
241pub enum EngineError {
242    Io(std::io::Error),
243    /// A feature of the architecture the engine does not run yet, by name.
244    NotYet(&'static str),
245    /// A matrix in a format the array does not read and the vector units do not hold.
246    Undecoded { tensor: String, ty: GgmlType },
247    /// A block whose scale is not finite.
248    Unreadable(String),
249    /// A sequence the model does not hold.
250    NoSequence(usize),
251    /// A run past the checkpoint's context.
252    PastContext { sequence: usize, position: usize },
253    /// A rotation scaled by frequency factors whose table (`rope_freqs.weight`) holds `found` factors for its `pairs`.
254    Factors { found: usize, pairs: usize },
255    /// No program for the model: its one program does not lower to the engine.
256    Program(super::program::ProgramError),
257}
258
259impl From<std::io::Error> for EngineError {
260    fn from(e: std::io::Error) -> Self {
261        EngineError::Io(e)
262    }
263}
264
265impl From<super::program::ProgramError> for EngineError {
266    fn from(e: super::program::ProgramError) -> Self {
267        EngineError::Program(e)
268    }
269}
270
271type R<T> = Result<T, EngineError>;
272
273/// One position's head rows of an attention cache: as stored, and as the vector units read them, decoded once.
274#[derive(Clone)]
275struct CacheRows {
276    stored: Stored,
277    values: Vec<Vec<f32>>,
278}
279
280/// A position's head rows as the cache stores them.
281#[derive(Clone)]
282enum Stored {
283    F16(Vec<Vec<u16>>),
284    Int8(Vec<(u16, Vec<i8>)>),
285}
286
287impl CacheRows {
288    fn of(heads: &[&[f32]], format: CacheFormat, fp: FpUnit) -> CacheRows {
289        let stored = match format {
290            CacheFormat::F16 => Stored::F16(heads.iter().map(|h| h.iter().map(|&v| f32_to_f16(v)).collect()).collect()),
291            CacheFormat::Int8 | CacheFormat::Int8Hadamard => Stored::Int8(heads.iter().map(|h| cache_row(fp, h)).collect()),
292        };
293        let values = match &stored {
294            Stored::F16(rows) => rows.iter().map(|r| r.iter().map(|&h| f16_to_f32(h)).collect()).collect(),
295            Stored::Int8(rows) => rows
296                .iter()
297                .map(|(s, codes)| {
298                    let s = f16_to_f32(*s);
299                    codes.iter().map(|&c| fp.mul(c as f32, s)).collect()
300                })
301                .collect(),
302        };
303        CacheRows { stored, values }
304    }
305
306    /// Head `g`'s row as the vector units read it.
307    fn values(&self, g: usize) -> &[f32] {
308        &self.values[g]
309    }
310
311    /// Head `g`'s int8 row as the array reads it: its scale's bits and its codes.
312    fn int8_row(&self, g: usize) -> (u16, &[i8]) {
313        let (s, codes) = self.int8(g);
314        (*s, codes.as_slice())
315    }
316
317    /// Head `g`'s int8 row and its scale, as the array reads it.
318    fn int8(&self, g: usize) -> &(u16, Vec<i8>) {
319        match &self.stored {
320            Stored::Int8(rows) => &rows[g],
321            Stored::F16(_) => unreachable!("attention on the array reads an int8 cache"),
322        }
323    }
324}
325
326/// One token's entry in a lazily flushed memory's log: every key group's key, every value head's value, the log of
327/// its decay and its gate, as stored. The decay is logged as its exponent `softplus(α + dt)·a`, whose stored
328/// significand holds the decay's distance from 1 to its own precision: a decay near 1 stored as itself would keep
329/// only the eleven bits of `1 − decay` an f16 has below 1.
330#[derive(Clone)]
331struct LogEntry {
332    k: Vec<f32>,
333    v: Vec<f32>,
334    log_decay: Vec<f32>,
335    beta: Vec<f32>,
336}
337
338/// What one block carries from position to position for one sequence.
339#[derive(Clone)]
340enum Carried {
341    Delta { ring: Vec<Vec<f32>>, memory: Vec<f32>, log: Vec<LogEntry> },
342    Attention { keys: Vec<CacheRows>, values: Vec<CacheRows> },
343}
344
345impl Carried {
346    fn fresh(layer: &Layer) -> Carried {
347        match &layer.mixer {
348            Mixer::GatedDelta(g) => {
349                let channels = 2 * g.k_heads * g.head_dim + g.v_heads * g.head_dim;
350                Carried::Delta {
351                    ring: vec![vec![0.0; channels]; g.conv_taps.saturating_sub(1)],
352                    memory: vec![0.0; g.v_heads * g.head_dim * g.head_dim],
353                    log: Vec::new(),
354                }
355            }
356            _ => Carried::Attention { keys: Vec::new(), values: Vec::new() },
357        }
358    }
359}
360
361struct Sequence {
362    position: usize,
363    layers: Vec<Carried>,
364}
365
366/// Rows of a matrix a projection carries against its token-rows at once.
367const ROW_TILE: usize = 16;
368
369/// The default bytes of tensor data the model keeps in memory.
370pub const CACHE_BUDGET: usize = 24 << 30;
371
372/// The default bytes of decoded rows the model keeps: a coded weight takes four bytes.
373pub const CODED_BUDGET: usize = 16 << 30;
374
375/// A hybrid Gated-DeltaNet checkpoint in the engine's arithmetic.
376pub struct EngineModel {
377    pub spec: ModelSpec,
378    pub numerics: EngineNumerics,
379    source: Box<dyn WeightSource>,
380    tensors: HashMap<String, Arc<Vec<u8>>>,
381    cached: usize,
382    budget: usize,
383    /// Matrices' coded rows, decoded once while they fit `coded_budget`; a matrix past it is decoded each use.
384    coded: HashMap<String, Arc<Vec<CodedRow>>>,
385    coded_bytes: usize,
386    coded_budget: usize,
387    sequences: Vec<Sequence>,
388    threads: usize,
389    /// The step this model runs, at its knob point.
390    program: Arc<Program>,
391}
392
393/// Whether the engine runs `spec` at `numerics`, or what of it the engine does not run yet, by name.
394pub fn runs(spec: &ModelSpec, numerics: EngineNumerics) -> R<()> {
395    if numerics.attention == AttentionMath::Array && !numerics.cache.is_int8() {
396        return Err(EngineError::NotYet("attention on the array over a cache that is not int8"));
397    }
398    if spec.embedding_scale.is_some() || spec.final_logit_softcap.is_some() {
399        return Err(EngineError::NotYet("an embedding scale or a final softcap"));
400    }
401    for layer in &spec.layers {
402        match &layer.mixer {
403            Mixer::GatedDelta(_) => {}
404            Mixer::Attention(a) => {
405                if a.fused_qkv || a.bias || a.output_bias || a.sinks || a.softcap.is_some() || a.window.is_some() {
406                    return Err(EngineError::NotYet("a fused, biased, windowed, softcapped or sinked attention"));
407                }
408                if a.qk_norm_whole {
409                    return Err(EngineError::NotYet("a QK norm across every head"));
410                }
411                if numerics.cache == CacheFormat::Int8Hadamard && !(a.head_dim.is_power_of_two() && a.value_dim.is_power_of_two()) {
412                    return Err(EngineError::NotYet("a rotated cache of heads whose width is not a power of two"));
413                }
414            }
415            Mixer::Mla(_) => return Err(EngineError::NotYet("multi-head latent attention")),
416        }
417        if !matches!(layer.ffn, Ffn::Dense { act: crate::model::spec::Act::Silu, .. }) {
418            return Err(EngineError::NotYet("a mixture of experts or a non-SiLU FFN"));
419        }
420        if layer.post_mixer_norm || layer.post_ffn_norm {
421            return Err(EngineError::NotYet("a sandwich norm"));
422        }
423    }
424    Ok(())
425}
426
427impl EngineModel {
428    pub fn new(spec: ModelSpec, source: impl WeightSource + 'static, numerics: EngineNumerics) -> R<EngineModel> {
429        runs(&spec, numerics)?;
430        let threads = std::thread::available_parallelism().map_or(4, |n| n.get());
431        let program = Arc::new(super::program::lower(&spec, numerics, spec.ctx_len)?.program);
432        Ok(EngineModel {
433            spec,
434            numerics,
435            source: Box::new(source),
436            tensors: HashMap::new(),
437            cached: 0,
438            budget: CACHE_BUDGET,
439            coded: HashMap::new(),
440            coded_bytes: 0,
441            coded_budget: CODED_BUDGET,
442            sequences: Vec::new(),
443            threads,
444            program,
445        })
446    }
447
448    /// A fresh sequence at position 0; its handle.
449    pub fn sequence(&mut self) -> usize {
450        let layers = self.spec.layers.iter().map(Carried::fresh).collect();
451        self.sequences.push(Sequence { position: 0, layers });
452        self.sequences.len() - 1
453    }
454
455    pub fn position(&self, sequence: usize) -> usize {
456        self.sequences[sequence].position
457    }
458
459    /// The step this model runs: the program [`step`](Self::step) interprets.
460    pub fn program(&self) -> &Program {
461        &self.program
462    }
463
464    /// A Gated-DeltaNet block's memory for a sequence, as stored at its last write, and the tokens its log holds.
465    pub fn memory(&self, sequence: usize, layer: usize) -> Option<(&[f32], usize)> {
466        match &self.sequences.get(sequence)?.layers.get(layer)? {
467            Carried::Delta { memory, log, .. } => Some((memory, log.len())),
468            Carried::Attention { .. } => None,
469        }
470    }
471
472    fn bytes(&mut self, name: &str) -> R<Arc<Vec<u8>>> {
473        if let Some(b) = self.tensors.get(name) {
474            return Ok(b.clone());
475        }
476        let (_, dims) = self.source.describe(name)?;
477        let row = dims.first().copied().unwrap_or(1).max(1) as usize;
478        let rows = dims.iter().product::<u64>() as usize / row;
479        let bytes = Arc::new(self.source.row_bytes(name, 0, rows)?);
480        if self.cached + bytes.len() <= self.budget {
481            self.cached += bytes.len();
482            self.tensors.insert(name.to_string(), bytes.clone());
483        }
484        Ok(bytes)
485    }
486
487    /// A vector tensor's values in f32.
488    fn vector(&mut self, name: &str) -> R<Vec<f32>> {
489        let (ty, _) = self.source.describe(name)?;
490        let raw = self.bytes(name)?;
491        let mut out = Vec::new();
492        ty.dequantize(&raw, &mut out);
493        Ok(out)
494    }
495
496    /// Row `r` of a table, each element its exact weight rounded to f32 (which is ggml's dequantized value).
497    fn table_row(&mut self, name: &str, r: usize) -> R<Vec<f32>> {
498        let (ty, dims) = self.source.describe(name)?;
499        let raw = self.bytes(name)?;
500        let row = ty.bytes_for(dims[0] as usize);
501        let mut out = Vec::new();
502        ty.dequantize(&raw[r * row..(r + 1) * row], &mut out);
503        Ok(out)
504    }
505
506    /// `W·x` for every row of `xs`, as the array (a block-format `W`) or the vector units (a float `W`) compute it.
507    fn project(&mut self, name: &str, xs: &[Vec<f32>]) -> R<Vec<Vec<f32>>> {
508        let fp = self.numerics.fp_unit;
509        let (ty, dims) = self.source.describe(name)?;
510        let [in_f, out_f] = dims[..] else { return Err(EngineError::NotYet("a projection that is not a matrix")) };
511        let (in_f, out_f) = (in_f as usize, out_f as usize);
512        let raw = self.bytes(name)?;
513        let row_bytes = ty.bytes_for(in_f);
514        enum Input {
515            Coded(Vec<ActRow>),
516            Float(Vec<Vec<f32>>),
517        }
518        let input = match ty {
519            GgmlType::F32 | GgmlType::F16 | GgmlType::Bf16 => Input::Float(xs.to_vec()),
520            _ if super::numerics::reads(ty) => {
521                let words = self.numerics.activations;
522                match self.numerics.scaling {
523                    ActScaling::Sub32 => {
524                        if in_f % GROUP != 0 {
525                            return Err(EngineError::NotYet("a matrix row that is not a whole number of 256-element groups"));
526                        }
527                        Input::Coded(xs.iter().map(|x| quantize(x, words)).collect())
528                    }
529                    ActScaling::PerBlock => Input::Coded(xs.iter().map(|x| quantize_blocks(x, words, ty.block_len())).collect()),
530                }
531            }
532            _ => return Err(EngineError::Undecoded { tensor: name.to_string(), ty }),
533        };
534        let coded = match &input {
535            Input::Coded(_) => self.coded_rows(name, ty, &raw, out_f, row_bytes)?,
536            Input::Float(_) => None,
537        };
538        // A thread for every million multiply-adds, up to the threads there are: a small matrix runs on one.
539        let workers = (out_f * in_f * xs.len() / (1 << 20)).clamp(1, self.threads);
540        let chunk = out_f.div_ceil(workers).max(1);
541        let results: Vec<R<Vec<Vec<f32>>>> = std::thread::scope(|scope| {
542            let handles: Vec<_> = (0..out_f)
543                .step_by(chunk)
544                .map(|first| {
545                    let (raw, input, coded) = (&raw, &input, &coded);
546                    scope.spawn(move || -> R<Vec<Vec<f32>>> {
547                        let last = (first + chunk).min(out_f);
548                        let mut out = vec![Vec::with_capacity(last - first); xs.len()];
549                        // A tile of rows against every token-row in turn, so each token-row's codes are read once a
550                        // tile and the tile's rows stay in cache across the token-rows.
551                        for tile in (first..last).step_by(ROW_TILE) {
552                            let end = (tile + ROW_TILE).min(last);
553                            match input {
554                                Input::Coded(acts) => {
555                                    let decoded: Vec<CodedRow> = match coded {
556                                        Some(_) => Vec::new(),
557                                        None => (tile..end)
558                                            .map(|r| {
559                                                CodedRow::decode(ty, &raw[r * row_bytes..(r + 1) * row_bytes])
560                                                    .expect("a format the array reads")
561                                                    .map_err(|_| EngineError::Unreadable(format!("{name} row {r}")))
562                                            })
563                                            .collect::<R<_>>()?,
564                                    };
565                                    let rows: &[CodedRow] = match coded {
566                                        Some(rows) => &rows[tile..end],
567                                        None => &decoded,
568                                    };
569                                    for (t, a) in acts.iter().enumerate() {
570                                        for row in rows {
571                                            out[t].push(super::numerics::dot(row, a));
572                                        }
573                                    }
574                                }
575                                Input::Float(rows) => {
576                                    let ws: Vec<Vec<f32>> = (tile..end)
577                                        .map(|r| {
578                                            let mut w = Vec::with_capacity(in_f);
579                                            ty.dequantize(&raw[r * row_bytes..(r + 1) * row_bytes], &mut w);
580                                            w
581                                        })
582                                        .collect();
583                                    for (t, x) in rows.iter().enumerate() {
584                                        for w in &ws {
585                                            out[t].push(fdot(fp, w, x));
586                                        }
587                                    }
588                                }
589                            }
590                        }
591                        Ok(out)
592                    })
593                })
594                .collect();
595            handles.into_iter().map(|h| h.join().expect("a projection thread")).collect()
596        });
597        let mut out = vec![Vec::with_capacity(out_f); xs.len()];
598        for part in results {
599            for (t, rows) in part?.into_iter().enumerate() {
600                out[t].extend(rows);
601            }
602        }
603        Ok(out)
604    }
605
606    /// A matrix's coded rows from the cache, or decoded across the threads and cached when they fit its budget; `None`
607    /// when they do not fit, so each use decodes its rows.
608    fn coded_rows(&mut self, name: &str, ty: GgmlType, raw: &[u8], rows: usize, row_bytes: usize) -> R<Option<Arc<Vec<CodedRow>>>> {
609        if let Some(c) = self.coded.get(name) {
610            return Ok(Some(c.clone()));
611        }
612        let elems = rows * ty.block_len() * (row_bytes / ty.block_bytes());
613        if self.coded_bytes + 4 * elems > self.coded_budget {
614            return Ok(None);
615        }
616        let chunk = rows.div_ceil(self.threads).max(1);
617        let parts: Vec<R<Vec<CodedRow>>> = std::thread::scope(|scope| {
618            let handles: Vec<_> = (0..rows)
619                .step_by(chunk)
620                .map(|first| {
621                    scope.spawn(move || -> R<Vec<CodedRow>> {
622                        (first..(first + chunk).min(rows))
623                            .map(|r| {
624                                CodedRow::decode(ty, &raw[r * row_bytes..(r + 1) * row_bytes])
625                                    .expect("a format the array reads")
626                                    .map_err(|_| EngineError::Unreadable(format!("{name} row {r}")))
627                            })
628                            .collect()
629                    })
630                })
631                .collect();
632            handles.into_iter().map(|h| h.join().expect("a decoding thread")).collect()
633        });
634        let mut all = Vec::with_capacity(rows);
635        for p in parts {
636            all.extend(p?);
637        }
638        let all = Arc::new(all);
639        self.coded_bytes += 4 * elems;
640        self.coded.insert(name.to_string(), all.clone());
641        Ok(Some(all))
642    }
643
644    /// Run each listed sequence's tokens at its next positions; returns, per entry, the logits after each token.
645    ///
646    /// The step is the program's commands in order over the step's rows ([`super::program`]): every row of every
647    /// entry, an entry's rows in its tokens' order.
648    pub fn step(&mut self, rows: &[(usize, Vec<u32>)]) -> R<Vec<Vec<Vec<f32>>>> {
649        let mut seen = std::collections::HashSet::new();
650        for (s, _) in rows {
651            if !seen.insert(*s) {
652                return Err(EngineError::NotYet("one sequence twice in a step"));
653            }
654        }
655        for (s, toks) in rows {
656            let seq = self.sequences.get(*s).ok_or(EngineError::NoSequence(*s))?;
657            if seq.position + toks.len() > self.spec.ctx_len {
658                return Err(EngineError::PastContext { sequence: *s, position: seq.position + toks.len() });
659            }
660        }
661        let mut plan = Vec::new();
662        for (e, (s, toks)) in rows.iter().enumerate() {
663            for (i, &t) in toks.iter().enumerate() {
664                plan.push(Row { entry: e, sequence: *s, position: self.sequences[*s].position + i, token: t });
665            }
666        }
667        let program = self.program.clone();
668        let mut store = Store::default();
669        for command in &program.commands {
670            self.run(command, &plan, &mut store)?;
671        }
672        let mut out: Vec<Vec<Vec<f32>>> = rows.iter().map(|_| Vec::new()).collect();
673        for (logits, row) in store.take(Slot::Logits).into_iter().zip(&plan) {
674            out[row.entry].push(logits);
675        }
676        Ok(out)
677    }
678
679    /// Greedy continuation of one sequence: the prompt in one step, then `n` tokens a step each.
680    pub fn generate(&mut self, sequence: usize, prompt: &[u32], n: usize) -> R<Vec<u32>> {
681        let mut logits = self.step(&[(sequence, prompt.to_vec())])?.remove(0).pop().expect("a prompt token");
682        let mut out = Vec::with_capacity(n);
683        for i in 0..n {
684            let t = argmax(&logits);
685            out.push(t);
686            if i + 1 < n {
687                logits = self.step(&[(sequence, vec![t])])?.remove(0).pop().expect("one token");
688            }
689        }
690        Ok(out)
691    }
692
693    /// One command over every row of the step, in the step's row order.
694    fn run(&mut self, command: &Command, plan: &[Row], store: &mut Store) -> R<()> {
695        let (eps, fp) = (self.spec.rms_eps as f32, self.numerics.fp_unit);
696        match command {
697            Command::Embed { table } => {
698                let rows = plan.iter().map(|row| self.table_row(table, row.token as usize)).collect::<R<Vec<_>>>()?;
699                store.put(Slot::Residual, rows);
700            }
701            Command::Norm { weight, from, to } => {
702                let w = self.vector(weight)?;
703                let program = vector::rmsnorm(fp, 0, 1, 2, 3, w.len(), eps);
704                let rows = store.get(*from).iter().map(|x| simd(fp, &program, [x.clone(), w.clone()], 2)).collect();
705                store.put(*to, rows);
706            }
707            Command::Matvec { tensor, from, to } => {
708                let rows = self.project(tensor, store.get(*from))?;
709                store.put(*to, rows);
710            }
711            Command::Add { from, to } => {
712                let add = [vector::Op::Vec { op: vector::Bin::Add, a: 0, b: 1, d: 2 }];
713                let rows = store.get(*to).iter().zip(store.get(*from)).map(|(x, y)| simd(fp, &add, [x.clone(), y.clone()], 2)).collect();
714                store.put(*to, rows);
715            }
716            Command::SwiGlu { gate, up, to } => {
717                let program = vector::swiglu(0, 1, 2);
718                let rows = store.get(*gate).iter().zip(store.get(*up)).map(|(g, u)| simd(fp, &program, [g.clone(), u.clone()], 2)).collect();
719                store.put(*to, rows);
720            }
721            Command::Conv { layer, kernel, taps, from, to } => {
722                let kernel = self.vector(kernel)?;
723                let taps = *taps;
724                let channels = kernel.len() / taps;
725                // The kernel tap by tap, as the vector unit reads it.
726                let by_tap: Vec<Vec<f32>> = (0..taps).map(|j| (0..channels).map(|ch| kernel[ch * taps + j]).collect()).collect();
727                let ring_regs: Vec<vector::VReg> = (0..taps as u8 - 1).collect();
728                let x_reg = taps as u8 - 1;
729                let tap_regs: Vec<vector::VReg> = (taps as u8..2 * taps as u8).collect();
730                let (acc, conv_out) = (2 * taps as u8, 2 * taps as u8 + 1);
731                let program = vector::conv(&ring_regs, x_reg, &tap_regs, acc, conv_out);
732                let stored_as = match self.numerics.state {
733                    StateFormat::F32 => None,
734                    StateFormat::F16 => Some([vector::Op::Map { f: vector::Func::F16, a: 0, d: 1 }]),
735                };
736                let mut out = Vec::with_capacity(plan.len());
737                for (x, row) in store.get(*from).iter().zip(plan) {
738                    let stored = match &stored_as {
739                        None => x.clone(),
740                        Some(p) => simd(fp, p, [x.clone()], 1),
741                    };
742                    let Carried::Delta { ring, .. } = &mut self.sequences[row.sequence].layers[*layer] else { unreachable!("a delta block") };
743                    let inputs: Vec<Vec<f32>> = ring.iter().cloned().chain(std::iter::once(x.clone())).chain(by_tap.iter().cloned()).collect();
744                    out.push(simd(fp, &program, inputs, conv_out));
745                    if taps > 1 {
746                        ring.remove(0);
747                        ring.push(stored);
748                    }
749                }
750                store.put(*to, out);
751            }
752            Command::L2Heads { from, at, heads, width, to } => {
753                let program = vector::l2norm(0, 1, eps);
754                let rows = store
755                    .get(*from)
756                    .iter()
757                    .map(|c| (0..*heads).flat_map(|h| simd(fp, &program, [c[at + h * width..at + (h + 1) * width].to_vec()], 1)).collect())
758                    .collect();
759                store.put(*to, rows);
760            }
761            Command::Gates { alpha, beta, dt, a, log_decay, decay, gate } => {
762                let (dt, a) = (self.vector(dt)?, self.vector(a)?);
763                let program = vector::delta_gates(0, 1, 2, 3, 4, 5, 6);
764                let (mut exponents, mut decays, mut gates) = (Vec::new(), Vec::new(), Vec::new());
765                for (al, be) in store.get(*alpha).iter().zip(store.get(*beta)) {
766                    let mut regs = vector::Regs::new(16, 8);
767                    regs.v[..4].clone_from_slice(&[al.clone(), dt.clone(), a.clone(), be.clone()]);
768                    vector::run(fp, &program, &mut regs);
769                    exponents.push(std::mem::take(&mut regs.v[4]));
770                    decays.push(std::mem::take(&mut regs.v[5]));
771                    gates.push(std::mem::take(&mut regs.v[6]));
772                }
773                store.put(*log_decay, exponents);
774                store.put(*decay, decays);
775                store.put(*gate, gates);
776            }
777            Command::Delta { layer, query, key, value, value_at, log_decay, decay, gate, k_heads, v_heads, width, to } => {
778                let reads = self.delta(*layer, plan, store, [*query, *key, *value, *log_decay, *decay, *gate], *value_at, (*k_heads, *v_heads, *width));
779                store.put(*to, reads);
780            }
781            Command::GatedNorm { weight, from, z, width, to } => {
782                let norm = self.vector(weight)?;
783                let program = vector::gdn_out(fp, 0, 1, 2, 3, 4, *width, eps);
784                let rows = store
785                    .get(*from)
786                    .iter()
787                    .zip(store.get(*z))
788                    .map(|(o, z)| {
789                        o.chunks(*width)
790                            .zip(z.chunks(*width))
791                            .flat_map(|(oh, zh)| simd(fp, &program, [oh.to_vec(), norm.clone(), zh.to_vec()], 3))
792                            .collect()
793                    })
794                    .collect();
795                store.put(*to, rows);
796            }
797            Command::Split { from, heads, width, query, gate } => {
798                let (mut qs, mut gs) = (Vec::with_capacity(plan.len()), Vec::with_capacity(plan.len()));
799                for p in store.get(*from) {
800                    qs.push((0..*heads).flat_map(|h| p[h * 2 * width..h * 2 * width + width].iter().copied()).collect());
801                    gs.push((0..*heads).flat_map(|h| p[h * 2 * width + width..(h + 1) * 2 * width].iter().copied()).collect());
802                }
803                store.put(*query, qs);
804                store.put(*gate, gs);
805            }
806            Command::RmsHeads { weight, slot, width } => {
807                let w = self.vector(weight)?;
808                let program = vector::rmsnorm(fp, 0, 1, 2, 3, *width, eps);
809                let mut rows = store.take(*slot);
810                for head in rows.iter_mut().flat_map(|r| r.chunks_mut(*width)) {
811                    let normed = simd(fp, &program, [head.to_vec(), w.clone()], 2);
812                    head.copy_from_slice(&normed);
813                }
814                store.put(*slot, rows);
815            }
816            Command::Rotate { slot, width, rope } => {
817                let program = vector::rotary(0, 1, 2, 3, rope.neox);
818                let factors = match rope.scaling {
819                    RopeScaling::Factors => Some(factors_for(rope, self.vector(FACTORS)?)?),
820                    _ => None,
821                };
822                let mut rows = store.take(*slot);
823                for (r, row) in rows.iter_mut().zip(plan) {
824                    let (cos, sin) = rotation_table(row.position, rope, factors.as_deref());
825                    for head in r.chunks_mut(*width) {
826                        let turned = simd(fp, &program, [head.to_vec(), cos.clone(), sin.clone()], 3);
827                        head.copy_from_slice(&turned);
828                    }
829                }
830                store.put(*slot, rows);
831            }
832            Command::Hadamard { slot, width } => {
833                let program = [vector::Op::Hadamard { a: 0, d: 1 }];
834                let mut rows = store.take(*slot);
835                for head in rows.iter_mut().flat_map(|r| r.chunks_mut(*width)) {
836                    let turned = simd(fp, &program, [head.to_vec()], 1);
837                    head.copy_from_slice(&turned);
838                }
839                store.put(*slot, rows);
840            }
841            Command::CacheAppend { layer, key, value, kv_heads, width, value_width } => {
842                let cache = self.numerics.cache;
843                for ((k, v), row) in store.get(*key).iter().zip(store.get(*value)).zip(plan) {
844                    let (kh, vh): (Vec<&[f32]>, Vec<&[f32]>) = (k.chunks(*width).collect(), v.chunks(*value_width).collect());
845                    assert!(kh.len() == *kv_heads && vh.len() == *kv_heads, "a row of {kv_heads} cached heads");
846                    let Carried::Attention { keys, values } = &mut self.sequences[row.sequence].layers[*layer] else { unreachable!("an attention block") };
847                    keys.push(CacheRows::of(&kh, cache, fp));
848                    values.push(CacheRows::of(&vh, cache, fp));
849                }
850            }
851            Command::Attend { layer, query, heads, kv_heads, width, value_width, scale, to } => {
852                let (hd, vd) = (*width, *value_width);
853                let group = heads / kv_heads;
854                let (math, words, scaling) = (self.numerics.attention, self.numerics.activations, self.numerics.scaling);
855                let rotated = self.numerics.cache == CacheFormat::Int8Hadamard;
856                // A rotated score is the dot of rotated rows, `hd` times the plain one: the scale takes `2^-log2 hd`.
857                let score_scale = if rotated { ldexp(*scale, -(hd.trailing_zeros() as i32)) } else { *scale };
858                let mut outs = Vec::with_capacity(plan.len());
859                for (q, row) in store.get(*query).iter().zip(plan) {
860                    let Carried::Attention { keys: kc, values: vc } = &self.sequences[row.sequence].layers[*layer] else { unreachable!("an attention block") };
861                    let mut out = vec![0f32; heads * vd];
862                    for h in 0..*heads {
863                        let g = h / group;
864                        let qh = &q[h * hd..(h + 1) * hd];
865                        let mut oh = match math {
866                            AttentionMath::Vector => {
867                                let keys: Vec<&[f32]> = (0..=row.position).map(|j| kc[j].values(g)).collect();
868                                let values: Vec<&[f32]> = (0..=row.position).map(|j| vc[j].values(g)).collect();
869                                attend_vector(fp, qh, &keys, &values, score_scale)
870                            }
871                            AttentionMath::Array => {
872                                let keys: Vec<(u16, &[i8])> = (0..=row.position).map(|j| kc[j].int8_row(g)).collect();
873                                let values: Vec<(u16, &[i8])> = (0..=row.position).map(|j| vc[j].int8_row(g)).collect();
874                                attend_int8(fp, qh, &keys, &values, score_scale, words, scaling)
875                            }
876                        };
877                        if rotated {
878                            hadamard(&mut oh);
879                            let back = -(vd.trailing_zeros() as i32);
880                            oh.iter_mut().for_each(|v| *v = ldexp(*v, back));
881                        }
882                        out[h * vd..(h + 1) * vd].copy_from_slice(&oh);
883                    }
884                    outs.push(out);
885                }
886                store.put(*to, outs);
887            }
888            Command::SigmoidGate { slot, gate } => {
889                let program = vector::sigmoid_gate(0, 1, 2, 3);
890                let rows = store.get(*slot).iter().zip(store.get(*gate)).map(|(o, g)| simd(fp, &program, [o.clone(), g.clone()], 2)).collect();
891                store.put(*slot, rows);
892            }
893            Command::Advance => {
894                for row in plan {
895                    self.sequences[row.sequence].position += 1;
896                }
897            }
898        }
899        Ok(())
900    }
901
902    /// A Gated-DeltaNet block's memory through each row, each sequence's rows in order: per value head the row's dots
903    /// with its key and query group's key and query, the rank-one update written back and the read-out
904    /// `decay·(S·q) + coef·(k·q)` — or, with a lazy flush, the token logged, the read through the log, and the rows
905    /// its [`Replay`] names replayed into the memory. The value heads run on the threads, each over its
906    /// own memory. `slots` are the query, key, value, decay exponent, decay and gate.
907    fn delta(&mut self, layer: usize, plan: &[Row], store: &Store, slots: [Slot; 6], value_at: usize, heads: (usize, usize, usize)) -> Vec<Vec<f32>> {
908        let (nk, nv, hd) = heads;
909        let fp = self.numerics.fp_unit;
910        let [query, key, value, log_decay, decay, gate] = slots.map(|s| store.get(s));
911        let scale = (1.0 / (hd as f64).sqrt()) as f32;
912        let (state, flush, threads) = (self.numerics.state, self.numerics.flush, self.threads);
913        let round = move |v: f32| match state {
914            StateFormat::F32 => v,
915            StateFormat::F16 => f16_to_f32(f32_to_f16(v)),
916        };
917        let mut reads = Vec::with_capacity(plan.len());
918        for (k, row) in plan.iter().enumerate() {
919            let (q, kk, v) = (&query[k], &key[k], &value[k][value_at..]);
920            let (log_decays, decays, betas) = (&log_decay[k], &decay[k], &gate[k]);
921            let Carried::Delta { memory, log, .. } = &mut self.sequences[row.sequence].layers[layer] else { unreachable!("a delta block") };
922            let mut outs = vec![vec![0f32; hd]; nv];
923            let per = nv.div_ceil(threads.min(nv).max(1));
924            match flush {
925                StateFlush::EveryPass => {
926                    std::thread::scope(|scope| {
927                        for (chunk, (mems, outs)) in memory.chunks_mut(per * hd * hd).zip(outs.chunks_mut(per)).enumerate() {
928                            scope.spawn(move || {
929                                for (i, (mem, out)) in mems.chunks_mut(hd * hd).zip(outs.iter_mut()).enumerate() {
930                                    let h = chunk * per + i;
931                                    let g = h % nk;
932                                    let (hq, hk) = (&q[g * hd..(g + 1) * hd], &kk[g * hd..(g + 1) * hd]);
933                                    let kq = fdot(fp, hk, hq);
934                                    for (r, row) in mem.chunks_mut(hd).enumerate() {
935                                        out[r] = gdn_row(fp, row, hk, hq, decays[h], betas[h], v[h * hd + r], kq, scale, round);
936                                    }
937                                }
938                            });
939                        }
940                    });
941                }
942                StateFlush::Every { .. } | StateFlush::Trailing { .. } => {
943                    log.push(LogEntry {
944                        k: kk.iter().map(|&x| round(x)).collect(),
945                        v: v.iter().map(|&x| round(x)).collect(),
946                        log_decay: log_decays.iter().map(|&x| round(x)).collect(),
947                        beta: betas.iter().map(|&x| round(x)).collect(),
948                    });
949                    let replay = Replay::at(flush, hd, log.len());
950                    let (log_entries, replay_at) = (&*log, &replay);
951                    std::thread::scope(|scope| {
952                        for (chunk, (mems, outs)) in memory.chunks_mut(per * hd * hd).zip(outs.chunks_mut(per)).enumerate() {
953                            scope.spawn(move || {
954                                for (i, (mem, out)) in mems.chunks_mut(hd * hd).zip(outs.iter_mut()).enumerate() {
955                                    let h = chunk * per + i;
956                                    let g = h % nk;
957                                    let head: Vec<Logged> = log_entries
958                                        .iter()
959                                        .map(|e| Logged {
960                                            k: &e.k[g * hd..(g + 1) * hd],
961                                            v: &e.v[h * hd..(h + 1) * hd],
962                                            log_decay: e.log_decay[h],
963                                            beta: e.beta[h],
964                                        })
965                                        .collect();
966                                    let folds = gdn_fold_trailing(fp, &q[g * hd..(g + 1) * hd], &head, hd, replay_at.older);
967                                    for (r, row) in mem.chunks(hd).enumerate() {
968                                        let (y, carried) = &folds[usize::from(r >= replay_at.replayed)];
969                                        out[r] = gdn_read(fp, row, y, carried[r], scale);
970                                    }
971                                    for r in replay_at.rows.clone() {
972                                        gdn_flush(fp, &mut mem[r * hd..(r + 1) * hd], &head[..replay_at.by], r, round);
973                                    }
974                                }
975                            });
976                        }
977                    });
978                    log.drain(..replay.drop);
979                }
980            }
981            reads.push(outs.concat());
982        }
983        reads
984    }
985}
986
987/// One token-row of a step: the entry it came in, its sequence, its position there and its token.
988#[derive(Clone, Copy, Debug)]
989struct Row {
990    entry: usize,
991    sequence: usize,
992    position: usize,
993    token: u32,
994}
995
996/// The token-row store of a step: each slot's vector for every row, in the step's row order.
997#[derive(Default)]
998struct Store(HashMap<Slot, Vec<Vec<f32>>>);
999
1000impl Store {
1001    fn get(&self, slot: Slot) -> &[Vec<f32>] {
1002        self.0.get(&slot).unwrap_or_else(|| panic!("{slot:?} read before the program writes it"))
1003    }
1004
1005    fn put(&mut self, slot: Slot, rows: Vec<Vec<f32>>) {
1006        self.0.insert(slot, rows);
1007    }
1008
1009    fn take(&mut self, slot: Slot) -> Vec<Vec<f32>> {
1010        self.0.remove(&slot).unwrap_or_else(|| panic!("{slot:?} read before the program writes it"))
1011    }
1012}
1013
1014/// The host's rotary table at `pos`: the f32 cosine and sine of [`rotation`] for each pair of the first `rope.dims`
1015/// dimensions.
1016fn rotation_table(pos: usize, rope: &Rope, factors: Option<&[f32]>) -> (Vec<f32>, Vec<f32>) {
1017    (0..rope.dims / 2)
1018        .map(|i| {
1019            let (c, s) = rotation(rope, i, pos, factors);
1020            (c as f32, s as f32)
1021        })
1022        .unzip()
1023}
1024
1025/// The checkpoint's table of per-frequency factors, one a pair, that a rotation scaled by them divides its angles by.
1026pub(crate) const FACTORS: &str = "rope_freqs.weight";
1027
1028/// `(cos, sin)` of pair `i` at `pos` ([`Rope::rotation`]), its angle divided by the pair's factor when the rotation
1029/// is scaled by `factors`.
1030pub(crate) fn rotation(rope: &Rope, i: usize, pos: usize, factors: Option<&[f32]>) -> (f64, f64) {
1031    rope.rotation(i, pos, factors.map(|f| f64::from(f[i])))
1032}
1033
1034/// The factors `rope` divides its angles by, read from `source` when the rotation is scaled by them, none otherwise.
1035pub(crate) fn rotary_factors(rope: &Rope, source: &mut dyn WeightSource) -> R<Option<Vec<f32>>> {
1036    if rope.scaling != RopeScaling::Factors {
1037        return Ok(None);
1038    }
1039    let (ty, dims) = source.describe(FACTORS)?;
1040    let row = dims.first().copied().unwrap_or(1).max(1) as usize;
1041    let raw = source.row_bytes(FACTORS, 0, dims.iter().product::<u64>() as usize / row)?;
1042    let mut factors = Vec::new();
1043    ty.dequantize(&raw, &mut factors);
1044    factors_for(rope, factors).map(Some)
1045}
1046
1047/// `factors` as the table of `rope`'s pairs: one a pair, or refused.
1048fn factors_for(rope: &Rope, factors: Vec<f32>) -> R<Vec<f32>> {
1049    let pairs = rope.dims / 2;
1050    if factors.len() != pairs {
1051        return Err(EngineError::Factors { found: factors.len(), pairs });
1052    }
1053    Ok(factors)
1054}
1055
1056/// `program` on the vector unit over `inputs` loaded into its vector registers from 0; register `output` after.
1057fn simd(fp: FpUnit, program: &[vector::Op], inputs: impl IntoIterator<Item = Vec<f32>>, output: vector::VReg) -> Vec<f32> {
1058    let mut regs = vector::Regs::new(16, 8);
1059    for (r, v) in inputs.into_iter().enumerate() {
1060        regs.v[r] = v;
1061    }
1062    vector::run(fp, program, &mut regs);
1063    std::mem::take(&mut regs.v[output as usize])
1064}
1065
1066/// The greedy token: the index of the largest logit ([`super::numerics::argmax`]).
1067pub fn argmax(logits: &[f32]) -> u32 {
1068    super::numerics::argmax(logits) as u32
1069}