Skip to main content

logicaffeine_compile/tensor_graph/
eval.rs

1//! What a graph computes, answered without a chip: the evaluator every backend is held to.
2//!
3//! Each primitive computes what the LOGOS statements its sugar desugars to compute, in the same loop order, with the same
4//! operations: Reals in `f64`, `e^x` as `E ** x` (`powf`), `max` and `min` as the language's (`maximumNumber`,
5//! `minimumNumber`), Ints exactly (a sum past `i64` is refused by name, where the VM would widen it), integer division
6//! truncating and `//` flooring. So a lifted graph evaluates to exactly what the VM prints for its program, bit for bit.
7//!
8//! A rewritten graph means what its ops say: an operand read [`Strided`](super::graph::Strided) is the part of every
9//! unit of the wider tensor it names, a result with a row or unit destination lands in rows or units of the tensor it
10//! names, a view is its operand's values from its offset on, a quantize is its operand's own values (the meaning is
11//! exact; a chip's quantized arithmetic is its numerics, held to a declared bound), and a fused symbol is its parts, run
12//! in order, of which only its result, what it keeps and the state it writes outlive it.
13
14use std::collections::{BTreeMap, BTreeSet};
15use std::fmt;
16
17use logicaffeine_base::numeric::{float_max, float_min};
18
19use crate::tensor_recognize::qwen::{GateAct, MoeAct, MoeGroups, MoeScore, RowIndex};
20use crate::tensor_recognize::{ElementwiseOp, Fill, RowReduceKind};
21
22use super::front::TensorOpSpec;
23use super::graph::{Access, Dst, Elem, Op, RowSel, Sym, TensorGraph};
24
25const E: f64 = std::f64::consts::E;
26
27/// A tensor's values.
28#[derive(Clone, Debug, PartialEq)]
29pub enum Data {
30    Real(Vec<f64>),
31    Int(Vec<i64>),
32}
33
34impl Data {
35    pub fn len(&self) -> usize {
36        match self {
37            Data::Real(v) => v.len(),
38            Data::Int(v) => v.len(),
39        }
40    }
41
42    pub fn is_empty(&self) -> bool {
43        self.len() == 0
44    }
45
46    /// The values as Reals: an Int widens as the VM widens it when a Real meets it.
47    pub fn reals(&self) -> Vec<f64> {
48        match self {
49            Data::Real(v) => v.clone(),
50            Data::Int(v) => v.iter().map(|&i| i as f64).collect(),
51        }
52    }
53
54    fn slice(&self, from: usize, to: usize) -> Data {
55        match self {
56            Data::Real(v) => Data::Real(v[from..to].to_vec()),
57            Data::Int(v) => Data::Int(v[from..to].to_vec()),
58        }
59    }
60}
61
62/// Why a graph has no value, by name.
63#[derive(Clone, Debug, PartialEq)]
64pub enum EvalError {
65    /// A tensor an op reads that nothing gives a value: no input, no fill, no op before it.
66    Missing { op: usize, tensor: String },
67    /// A row, position or index outside what the program declares — the check the program's statements make.
68    OutOfRange { op: usize, what: String },
69    /// An integer past `i64`, where the VM would widen to an arbitrary-precision Int the graph's tensors do not hold.
70    Overflow { op: usize, what: String },
71    /// An operand whose element type or length is not the one the op reads.
72    Operand { op: usize, tensor: String, why: String },
73    /// An integer division by zero.
74    DivideByZero { op: usize },
75}
76
77impl fmt::Display for EvalError {
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        match self {
80            EvalError::Missing { op, tensor } => write!(f, "op {op} reads `{tensor}`, which nothing gives a value"),
81            EvalError::OutOfRange { op, what } => write!(f, "op {op}: {what}"),
82            EvalError::Overflow { op, what } => write!(f, "op {op}: {what} is past a 64-bit Int"),
83            EvalError::Operand { op, tensor, why } => write!(f, "op {op} reads `{tensor}`: {why}"),
84            EvalError::DivideByZero { op } => write!(f, "op {op}: an integer division by zero"),
85        }
86    }
87}
88
89impl std::error::Error for EvalError {}
90
91/// The values a fill gives a tensor of `len` elements, or `None` for one the program leaves to its caller.
92pub fn filled(fill: &Fill, len: usize) -> Option<Data> {
93    match fill {
94        Fill::Const(c) => Some(Data::Int(vec![*c; len])),
95        Fill::Pattern { modulus, shift } => Some(Data::Int((0..len as i64).map(|p| p % modulus + shift).collect())),
96        Fill::Overrides { base, sets } => {
97            let Some(Data::Int(mut v)) = filled(base, len) else { return None };
98            for &(i, x) in sets {
99                if i < len {
100                    v[i] = x;
101                }
102            }
103            Some(Data::Int(v))
104        }
105        Fill::ConstF(b) => Some(Data::Real(vec![f64::from_bits(*b); len])),
106        Fill::FloatOverrides { base, sets } => {
107            let mut v = vec![f64::from_bits(*base); len];
108            for &(i, x) in sets {
109                if i < len {
110                    v[i] = f64::from_bits(x);
111                }
112            }
113            Some(Data::Real(v))
114        }
115        Fill::RealPattern { modulus, shift, divisor } => {
116            Some(Data::Real((0..len as i64).map(|p| (p % modulus + shift) as f64 / f64::from_bits(*divisor)).collect()))
117        }
118        Fill::Unknown => None,
119    }
120}
121
122/// The values every tensor of `g` holds after its step: each declared tensor from `inputs` where given, else from the
123/// fill the program declares it with, then every op in order.
124pub fn eval(g: &TensorGraph, inputs: &BTreeMap<String, Data>) -> Result<BTreeMap<String, Data>, EvalError> {
125    let mut env: BTreeMap<String, Data> = BTreeMap::new();
126    for t in g.tensors.values() {
127        if let Some(v) = inputs.get(&t.name) {
128            env.insert(t.name.clone(), v.clone());
129        } else if let Some(v) = t.fill.as_ref().and_then(|f| filled(f, t.len())) {
130            env.insert(t.name.clone(), v);
131        }
132    }
133    for (i, op) in g.ops.iter().enumerate() {
134        eval_op(i, op, g, &mut env)?;
135    }
136    Ok(env)
137}
138
139/// Run a fused op's parts in order against `env`, a member a rule built running its own parts in turn.
140fn run_parts(at: usize, op: &Op, g: &TensorGraph, env: &mut BTreeMap<String, Data>) -> Result<(), EvalError> {
141    let Sym::Fused(f) = &op.sym else { return eval_op(at, op, g, env) };
142    for part in &f.parts {
143        match part.sym {
144            Sym::Fused(_) => run_parts(at, part, g, env)?,
145            _ => eval_op(at, part, g, env)?,
146        }
147    }
148    Ok(())
149}
150
151/// Run one op against `env`.
152fn eval_op(at: usize, op: &Op, g: &TensorGraph, env: &mut BTreeMap<String, Data>) -> Result<(), EvalError> {
153    match &op.sym {
154        Sym::Fused(_) => {
155            let before: BTreeSet<String> = env.keys().cloned().collect();
156            run_parts(at, op, g, env)?;
157            // Each part lands its result where it says (the part computing the op's result carries the op's placement),
158            // and a member a rule built gives its siblings every value it computes; only what the op writes outside
159            // itself — its result, what it keeps, the tables and state its parts write — outlives it.
160            let outlive: BTreeSet<&str> = op.writes().into_iter().collect();
161            let fresh: Vec<String> = env.keys().filter(|k| !before.contains(*k) && !outlive.contains(k.as_str())).cloned().collect();
162            for k in fresh {
163                env.remove(&k);
164            }
165            Ok(())
166        }
167        Sym::View { offset } => {
168            let src = read(at, op, 0, env)?;
169            let len = g.tensors.get(&op.out).map(|t| t.len()).ok_or_else(|| EvalError::Missing { op: at, tensor: op.out.clone() })?;
170            if offset + len > src.len() {
171                return Err(EvalError::OutOfRange { op: at, what: format!("a view of {len} values from {offset} into {} values", src.len()) });
172            }
173            land(at, &op.dst, src.slice(*offset, offset + len), g, env)
174        }
175        Sym::Quantize(_) => {
176            let x = read(at, op, 0, env)?;
177            land(at, &op.dst, x, g, env)
178        }
179        Sym::Prim(spec) => {
180            let ins: Vec<Data> = (0..op.ins.len()).map(|k| read(at, op, k, env)).collect::<Result<_, _>>()?;
181            let (result, state) = prim(at, spec, &op.ins, &ins, env)?;
182            for (k, v) in state {
183                env.insert(op.ins[k].clone(), v);
184            }
185            if let Some(result) = result {
186                land(at, &op.dst, result, g, env)?;
187            }
188            Ok(())
189        }
190    }
191}
192
193/// Operand `k` of `op`, read as its access says.
194fn read(at: usize, op: &Op, k: usize, env: &BTreeMap<String, Data>) -> Result<Data, EvalError> {
195    let name = &op.ins[k];
196    let whole = env.get(name).ok_or_else(|| EvalError::Missing { op: at, tensor: name.clone() })?;
197    match op.access.get(k).copied().unwrap_or(Access::Whole) {
198        Access::Whole => Ok(whole.clone()),
199        Access::Strided(s) => {
200            if s.pitch == 0 || s.offset + s.width > s.pitch || whole.len() % s.pitch != 0 {
201                return Err(EvalError::Operand {
202                    op: at,
203                    tensor: name.clone(),
204                    why: format!("{} values are no whole units of {} to read {} from {} of", whole.len(), s.pitch, s.width, s.offset),
205                });
206            }
207            let units = whole.len() / s.pitch;
208            let pick = |u: usize, i: usize| u * s.pitch + s.offset + i;
209            Ok(match whole {
210                Data::Real(v) => Data::Real((0..units).flat_map(|u| (0..s.width).map(move |i| v[pick(u, i)])).collect()),
211                Data::Int(v) => Data::Int((0..units).flat_map(|u| (0..s.width).map(move |i| v[pick(u, i)])).collect()),
212            })
213        }
214    }
215}
216
217/// Store an op's result where its destination says. A destination of rows or units in a tensor nothing has written yet
218/// starts from that tensor's zeros: its buffer exists from the first op that lands a part of it.
219fn land(at: usize, dst: &Dst, result: Data, g: &TensorGraph, env: &mut BTreeMap<String, Data>) -> Result<(), EvalError> {
220    if let Some(name) = dst.own() {
221        env.insert(name.to_string(), result);
222        return Ok(());
223    }
224    let table = match env.get(&dst.buf) {
225        Some(t) => t.clone(),
226        None => match g.tensors.get(&dst.buf) {
227            Some(t) if t.elem == Elem::Int => Data::Int(vec![0; t.len()]),
228            Some(t) => Data::Real(vec![0.0; t.len()]),
229            None => return Err(EvalError::Missing { op: at, tensor: dst.buf.clone() }),
230        },
231    };
232    let n = result.len();
233    // Each result value's place in the table.
234    let row_of = |sel: &RowSel, env: &BTreeMap<String, Data>| -> Result<i64, EvalError> {
235        match sel {
236            RowSel::Fixed(r) => Ok(*r as i64),
237            RowSel::Param(p) => scalar(at, p, env),
238        }
239    };
240    // Where the result starts in the table: its first row's start, or the table's.
241    let base = match &dst.row {
242        Some((sel, pitch)) => {
243            let row = row_of(sel, env)?;
244            if row < 1 {
245                return Err(EvalError::OutOfRange { op: at, what: format!("row {row} of `{}`", dst.buf) });
246            }
247            (row - 1) as usize * pitch
248        }
249        None => 0,
250    };
251    // Each result value's place in the table: one after another, or each unit's share in its part of the unit.
252    let places: Vec<usize> = match &dst.unit {
253        None => (0..n).map(|i| base + i).collect(),
254        Some(u) => {
255            if u.width == 0 || n % u.width != 0 || u.offset + u.width > u.pitch {
256                return Err(EvalError::OutOfRange { op: at, what: format!("{n} values in shares of {} at {} of units of {}", u.width, u.offset, u.pitch) });
257            }
258            (0..n).map(|i| base + (i / u.width) * u.pitch + u.offset + i % u.width).collect()
259        }
260    };
261    if places.last().is_some_and(|&last| last >= table.len()) {
262        return Err(EvalError::OutOfRange { op: at, what: format!("{n} values past the {} of `{}`", table.len(), dst.buf) });
263    }
264    let merged = match (table, result) {
265        (Data::Real(mut t), Data::Real(r)) => {
266            for (i, v) in places.into_iter().zip(r) {
267                t[i] = v;
268            }
269            Data::Real(t)
270        }
271        (Data::Int(mut t), Data::Int(r)) => {
272            for (i, v) in places.into_iter().zip(r) {
273                t[i] = v;
274            }
275            Data::Int(t)
276        }
277        (Data::Int(_), Data::Real(_)) | (Data::Real(_), Data::Int(_)) => {
278            return Err(EvalError::Operand { op: at, tensor: dst.buf.clone(), why: "a result of one element type landing in a tensor of another".into() })
279        }
280    };
281    env.insert(dst.buf.clone(), merged);
282    Ok(())
283}
284
285/// The value of the one-element Int tensor `name`.
286fn scalar(at: usize, name: &str, env: &BTreeMap<String, Data>) -> Result<i64, EvalError> {
287    match env.get(name) {
288        Some(Data::Int(v)) if v.len() == 1 => Ok(v[0]),
289        Some(Data::Real(v)) if v.len() == 1 && v[0].fract() == 0.0 => Ok(v[0] as i64),
290        Some(_) => Err(EvalError::Operand { op: at, tensor: name.into(), why: "an index that is not one Int".into() }),
291        None => Err(EvalError::Missing { op: at, tensor: name.into() }),
292    }
293}
294
295/// A row index's value: a literal, or the scalar named at operand `k`.
296fn index(at: usize, i: &RowIndex, names: &[String], k: usize, env: &BTreeMap<String, Data>) -> Result<i64, EvalError> {
297    match i {
298        RowIndex::Literal(n) => Ok(*n),
299        RowIndex::Named(_) => scalar(at, &names[k], env),
300    }
301}
302
303fn ints(at: usize, name: &str, d: &Data) -> Result<Vec<i64>, EvalError> {
304    match d {
305        Data::Int(v) => Ok(v.clone()),
306        Data::Real(_) => Err(EvalError::Operand { op: at, tensor: name.into(), why: "Reals where the op reads Ints".into() }),
307    }
308}
309
310fn need(at: usize, name: &str, d: &Data, len: usize) -> Result<(), EvalError> {
311    if d.len() < len {
312        return Err(EvalError::Operand { op: at, tensor: name.into(), why: format!("{} values where the op reads {len}", d.len()) });
313    }
314    Ok(())
315}
316
317fn checked(at: usize, what: &str, v: Option<i64>) -> Result<i64, EvalError> {
318    v.ok_or_else(|| EvalError::Overflow { op: at, what: what.into() })
319}
320
321/// `x / (1 + e^(0 − x))`, as the sugar writes it.
322fn silu(x: f64) -> f64 {
323    x / (1.0 + E.powf(0.0 - x))
324}
325
326/// The result an op computes, and the operands it writes in place (by position) with their new values.
327type Computed = (Option<Data>, Vec<(usize, Data)>);
328
329#[allow(clippy::too_many_lines)]
330fn prim(at: usize, spec: &TensorOpSpec, names: &[String], ins: &[Data], env: &BTreeMap<String, Data>) -> Result<Computed, EvalError> {
331    let real = |k: usize, len: usize| -> Result<Vec<f64>, EvalError> {
332        need(at, &names[k], &ins[k], len)?;
333        Ok(ins[k].reals())
334    };
335    let out = |v: Vec<f64>| Ok((Some(Data::Real(v)), Vec::new()));
336    match spec {
337        TensorOpSpec::Matmul(m) => {
338            need(at, &names[0], &ins[0], m.m * m.k)?;
339            need(at, &names[1], &ins[1], m.k * m.n)?;
340            if let (Data::Int(a), Data::Int(b)) = (&ins[0], &ins[1]) {
341                let mut c = vec![0i64; m.m * m.n];
342                for i in 0..m.m {
343                    for kk in 0..m.k {
344                        for j in 0..m.n {
345                            let p = checked(at, "a product", a[i * m.k + kk].checked_mul(b[kk * m.n + j]))?;
346                            c[i * m.n + j] = checked(at, "a sum", c[i * m.n + j].checked_add(p))?;
347                        }
348                    }
349                }
350                return Ok((Some(Data::Int(c)), Vec::new()));
351            }
352            let (a, b) = (ins[0].reals(), ins[1].reals());
353            let mut c = vec![0.0f64; m.m * m.n];
354            for i in 0..m.m {
355                for kk in 0..m.k {
356                    for j in 0..m.n {
357                        c[i * m.n + j] = c[i * m.n + j] + a[i * m.k + kk] * b[kk * m.n + j];
358                    }
359                }
360            }
361            out(c)
362        }
363        TensorOpSpec::HeadMatmul(h) => {
364            let (x, w) = (real(0, h.m * h.heads * h.a)?, real(1, h.heads * h.a * h.b)?);
365            let mut y = vec![0.0f64; h.m * h.heads * h.b];
366            for t in 0..h.m {
367                for hh in 0..h.heads {
368                    for e in 0..h.a {
369                        for r in 0..h.b {
370                            let yi = t * h.heads * h.b + hh * h.b + r;
371                            y[yi] = y[yi] + x[t * h.heads * h.a + hh * h.a + e] * w[(hh * h.a + e) * h.b + r];
372                        }
373                    }
374                }
375            }
376            out(y)
377        }
378        TensorOpSpec::Elementwise(e) => {
379            need(at, &names[0], &ins[0], e.n)?;
380            need(at, &names[1], &ins[1], e.row.unwrap_or(e.n))?;
381            let b_at = |p: usize| e.row.map_or(p, |w| p % w);
382            if let (Data::Int(a), Data::Int(b)) = (&ins[0], &ins[1]) {
383                let c = (0..e.n)
384                    .map(|p| match e.op {
385                        ElementwiseOp::Add => checked(at, "a sum", a[p].checked_add(b[b_at(p)])),
386                        ElementwiseOp::Mul => checked(at, "a product", a[p].checked_mul(b[b_at(p)])),
387                    })
388                    .collect::<Result<Vec<i64>, _>>()?;
389                return Ok((Some(Data::Int(c)), Vec::new()));
390            }
391            let (a, b) = (ins[0].reals(), ins[1].reals());
392            out((0..e.n)
393                .map(|p| match e.op {
394                    ElementwiseOp::Add => a[p] + b[b_at(p)],
395                    ElementwiseOp::Mul => a[p] * b[b_at(p)],
396                })
397                .collect())
398        }
399        TensorOpSpec::Scale(s) => {
400            let f = s.factor.get();
401            out(real(0, s.n)?.into_iter().take(s.n).map(|x| x * f).collect())
402        }
403        TensorOpSpec::Softcap(s) => {
404            let c = s.cap.get();
405            out(real(0, s.n)?.into_iter().take(s.n).map(|x| capped(x, c)).collect())
406        }
407        TensorOpSpec::Silu(s) => out(real(0, s.n)?.into_iter().take(s.n).map(silu).collect()),
408        TensorOpSpec::Sigmoid(s) => out(real(0, s.n)?.into_iter().take(s.n).map(|x| 1.0 / (1.0 + E.powf(0.0 - x))).collect()),
409        TensorOpSpec::Gelu(g) => {
410            const SQRT_2_OVER_PI: f64 = 0.797_884_560_802_865_4;
411            const K: f64 = 0.044_715;
412            out(real(0, g.n)?
413                .into_iter()
414                .take(g.n)
415                .map(|x| {
416                    let x3 = x * (x * x);
417                    let z = SQRT_2_OVER_PI * (x + K * x3);
418                    x / (1.0 + E.powf(0.0 - 2.0 * z))
419                })
420                .collect())
421        }
422        TensorOpSpec::SwiGlu(s) => {
423            let (g, u) = (real(0, s.total)?, real(1, s.total)?);
424            out((0..s.total).map(|i| silu(g[i]) * u[i]).collect())
425        }
426        TensorOpSpec::Decay(d) => {
427            let heads = d.n / d.rows.max(1);
428            let (ar, dt, al) = (real(0, d.n)?, real(1, heads)?, real(2, heads)?);
429            let mut y = Vec::with_capacity(d.n);
430            for r in 0..d.rows.max(1) {
431                for i in 0..heads {
432                    let base = 1.0 + E.powf(ar[r * heads + i] + dt[i]);
433                    y.push(base.powf(al[i]));
434                }
435            }
436            out(y)
437        }
438        TensorOpSpec::L2Norm(l) => {
439            let x = real(0, l.rows * l.cols)?;
440            let eps = l.eps.get();
441            let mut y = Vec::with_capacity(l.rows * l.cols);
442            for r in 0..l.rows {
443                let base = r * l.cols;
444                let mut ss = 0.0f64;
445                for p in 0..l.cols {
446                    ss = ss + x[base + p] * x[base + p];
447                }
448                let denom = (ss + eps).sqrt();
449                for q in 0..l.cols {
450                    y.push(x[base + q] / denom);
451                }
452            }
453            out(y)
454        }
455        TensorOpSpec::FusedRow(r) => fused_row(at, r, names, ins),
456        TensorOpSpec::Rope(r) => {
457            let x = real(0, r.h * r.hd)?;
458            let (rc, rs) = (ins[1].reals(), ins[2].reals());
459            let per_row = r.h / r.rows.max(1);
460            // The tables' first row: the position's, behind the program's check that the block's rows are the cache's.
461            let first = match &r.from {
462                Some((p, _)) => {
463                    let p = index(at, p, names, 3, env)?;
464                    let cache = rc.len() / r.half.max(1);
465                    if p < 1 || p as usize + r.rows - 1 > cache {
466                        return Err(EvalError::OutOfRange { op: at, what: format!("rotation from position {p} over {} rows of a {cache}-row table", r.rows) });
467                    }
468                    (p - 1) as usize
469                }
470                None => 0,
471            };
472            let mut y = Vec::with_capacity(r.h * r.hd);
473            for rh in 0..r.h {
474                let row = first + if r.rows > 1 || r.from.is_some() { rh / per_row } else { 0 };
475                let t = |ri: usize| row * r.half + ri;
476                if t(r.half - 1) >= rc.len().min(rs.len()) {
477                    return Err(EvalError::OutOfRange { op: at, what: format!("table row {} past the rotary tables", row + 1) });
478                }
479                for ri in 0..r.half {
480                    y.push(x[rh * r.hd + ri] * rc[t(ri)] - x[rh * r.hd + ri + r.half] * rs[t(ri)]);
481                }
482                for ri in 0..r.half {
483                    y.push(x[rh * r.hd + ri] * rs[t(ri)] + x[rh * r.hd + ri + r.half] * rc[t(ri)]);
484                }
485                for rp in 2 * r.half..r.hd {
486                    y.push(x[rh * r.hd + rp]);
487                }
488            }
489            out(y)
490        }
491        TensorOpSpec::Transpose(t) => {
492            need(at, &names[0], &ins[0], t.rows * t.cols)?;
493            let pick: Vec<usize> = (0..t.cols).flat_map(|j| (0..t.rows).map(move |i| i * t.cols + j)).collect();
494            Ok((Some(gather(&ins[0], &pick)), Vec::new()))
495        }
496        TensorOpSpec::HeadSlice(s) => {
497            need(at, &names[0], &ins[0], s.n)?;
498            let pick: Vec<usize> = (0..s.n / s.every).flat_map(|g| (0..s.width).map(move |i| g * s.every + s.from + i)).collect();
499            Ok((Some(gather(&ins[0], &pick)), Vec::new()))
500        }
501        TensorOpSpec::Join(j) => {
502            let (ha, hb) = (j.wa / j.heads, j.wb / j.heads);
503            need(at, &names[0], &ins[0], j.rows * j.wa)?;
504            need(at, &names[1], &ins[1], j.rows * j.wb)?;
505            let both = |a: &Data, b: &Data| -> Option<Data> {
506                match (a, b) {
507                    (Data::Int(a), Data::Int(b)) => Some(Data::Int(join(a, b, j.rows, j.heads, ha, hb))),
508                    _ => None,
509                }
510            };
511            let joined = both(&ins[0], &ins[1]).unwrap_or_else(|| Data::Real(join(&ins[0].reals(), &ins[1].reals(), j.rows, j.heads, ha, hb)));
512            Ok((Some(joined), Vec::new()))
513        }
514        TensorOpSpec::CausalConv(c) => {
515            let x = real(0, c.rows * c.chan)?;
516            let wc = real(1, c.taps * c.chan)?;
517            let mut cs = real(2, c.taps * c.chan)?;
518            let mut y = vec![0.0f64; c.rows * c.chan];
519            for r in 0..c.rows {
520                for t1 in 1..c.taps {
521                    for ch in 0..c.chan {
522                        cs[(t1 - 1) * c.chan + ch] = cs[t1 * c.chan + ch];
523                    }
524                }
525                for ch in 0..c.chan {
526                    cs[(c.taps - 1) * c.chan + ch] = x[r * c.chan + ch];
527                }
528                for ch in 0..c.chan {
529                    let mut acc = 0.0f64;
530                    for t in 0..c.taps {
531                        acc = acc + cs[t * c.chan + ch] * wc[t * c.chan + ch];
532                    }
533                    y[r * c.chan + ch] = acc;
534                }
535            }
536            Ok((Some(Data::Real(y)), vec![(2, Data::Real(cs))]))
537        }
538        TensorOpSpec::GatedDelta(g) => {
539            let (hv, hk, d, rows) = (g.hv, g.hk, g.d, g.rows.max(1));
540            let q = real(0, rows * hk * d)?;
541            let k = real(1, rows * hk * d)?;
542            let v = real(2, rows * hv * d)?;
543            let z = real(3, rows * hv * d)?;
544            let b = real(4, rows * hv)?;
545            let a = real(5, rows * hv)?;
546            let mut st = real(6, hv * d * d)?;
547            let gn = real(7, d)?;
548            let eps = g.eps.get();
549            let gate = |x: f64| match g.gate {
550                GateAct::Silu => silu(x),
551                GateAct::Sigmoid => 1.0 / (1.0 + E.powf(0.0 - x)),
552            };
553            let read_scale = 1.0 / (d as f64).sqrt();
554            let mut o_all = vec![0.0f64; hv * d * rows];
555            for r in 0..rows {
556                let (rq, rv, rh) = (r * hk * d, r * hv * d, r * hv);
557                for head in 0..hv {
558                    let kb = rq + (head % hk) * d;
559                    let vb = rv + head * d;
560                    let sb = head * d * d;
561                    let (alpha, beta) = (a[rh + head], b[rh + head]);
562                    let mut u = Vec::with_capacity(d);
563                    for ua in 0..d {
564                        let mut acc = 0.0f64;
565                        for ub in 0..d {
566                            acc = acc + st[sb + ua * d + ub] * k[kb + ub];
567                        }
568                        u.push(acc);
569                    }
570                    for sa in 0..d {
571                        let coef = beta * (v[vb + sa] - alpha * u[sa]);
572                        for sbi in 0..d {
573                            st[sb + sa * d + sbi] = alpha * st[sb + sa * d + sbi] + coef * k[kb + sbi];
574                        }
575                    }
576                    let mut o = Vec::with_capacity(d);
577                    for oa in 0..d {
578                        let mut acc = 0.0f64;
579                        for ob in 0..d {
580                            acc = acc + st[sb + oa * d + ob] * q[kb + ob];
581                        }
582                        o.push(acc * read_scale);
583                    }
584                    let mut ssq = 0.0f64;
585                    for x in &o {
586                        ssq = ssq + x * x;
587                    }
588                    let den = (ssq / d as f64 + eps).sqrt();
589                    for na in 0..d {
590                        o_all[vb + na] = gn[na] * (o[na] / den) * gate(z[vb + na]);
591                    }
592                }
593            }
594            Ok((Some(Data::Real(o_all)), vec![(6, Data::Real(st))]))
595        }
596        TensorOpSpec::Attention(a) => attention(at, a, names, ins, env),
597        TensorOpSpec::FullAttention(f) => {
598            let width = f.h * f.hd;
599            let (q, k, v) = (real(0, f.t * width)?, real(1, f.t * width)?, real(2, f.t * width)?);
600            let scale = 1.0f64 / (f.hd as f64).sqrt();
601            let mut c = vec![0.0f64; f.t * width];
602            for row in 0..f.t {
603                for head in 0..f.h {
604                    let mut scores = Vec::with_capacity(f.t);
605                    for j in 0..f.t {
606                        let mut acc = 0.0f64;
607                        for e in 0..f.hd {
608                            acc = acc + q[row * width + head * f.hd + e] * k[j * width + head * f.hd + e];
609                        }
610                        scores.push(acc * scale);
611                    }
612                    let mut mx = scores[0];
613                    for s in &scores[1..] {
614                        mx = float_max(mx, *s);
615                    }
616                    let mut den = 0.0f64;
617                    for s in &scores {
618                        den = den + E.powf(s - mx);
619                    }
620                    for e in 0..f.hd {
621                        let mut acc = 0.0f64;
622                        for j in 0..f.t {
623                            acc = acc + E.powf(scores[j] - mx) / den * v[j * width + head * f.hd + e];
624                        }
625                        c[row * width + head * f.hd + e] = acc;
626                    }
627                }
628            }
629            out(c)
630        }
631        TensorOpSpec::Moe(m) => moe(at, m, names, ins),
632        TensorOpSpec::RowGather(r) => {
633            let row = index(at, &r.index, names, 1, env)?;
634            if row < 1 || row as usize > r.rows {
635                return Err(EvalError::OutOfRange { op: at, what: format!("row {row} of `{}`'s {} rows", names[0], r.rows) });
636            }
637            need(at, &names[0], &ins[0], r.rows * r.cols)?;
638            let from = (row - 1) as usize * r.cols;
639            Ok((Some(ins[0].slice(from, from + r.cols)), Vec::new()))
640        }
641        TensorOpSpec::RowsGather(r) => {
642            let idx = ints(at, &names[1], &ins[1])?;
643            need(at, &names[0], &ins[0], r.rows * r.cols)?;
644            let mut pick = Vec::with_capacity(r.count * r.cols);
645            for &row in idx.iter().take(r.count) {
646                if row < 1 || row as usize > r.rows {
647                    return Err(EvalError::OutOfRange { op: at, what: format!("row {row} of `{}`'s {} rows", names[0], r.rows) });
648                }
649                pick.extend((0..r.cols).map(|j| (row - 1) as usize * r.cols + j));
650            }
651            Ok((Some(gather(&ins[0], &pick)), Vec::new()))
652        }
653        TensorOpSpec::ArgMax(a) => {
654            need(at, &names[0], &ins[0], a.n)?;
655            let mut best_at = 0usize;
656            match &ins[0] {
657                Data::Real(x) => {
658                    for i in 1..a.n {
659                        if x[i] > x[best_at] {
660                            best_at = i;
661                        }
662                    }
663                }
664                Data::Int(x) => {
665                    for i in 1..a.n {
666                        if x[i] > x[best_at] {
667                            best_at = i;
668                        }
669                    }
670                }
671            }
672            Ok((Some(Data::Int(vec![best_at as i64 + 1])), Vec::new()))
673        }
674        TensorOpSpec::SetRow(r) => {
675            let row = index(at, &r.index, names, 2, env)?;
676            let last = row + r.count as i64 - 1;
677            if row < 1 || last as usize > r.rows {
678                return Err(EvalError::OutOfRange { op: at, what: format!("rows {row}..={last} of `{}`'s {} rows", names[0], r.rows) });
679            }
680            need(at, &names[0], &ins[0], r.rows * r.cols)?;
681            need(at, &names[1], &ins[1], r.cols * r.count)?;
682            let from = (row - 1) as usize * r.cols;
683            let table = match (&ins[0], &ins[1]) {
684                (Data::Real(t), src) => {
685                    let mut t = t.clone();
686                    let s = src.reals();
687                    t[from..from + r.cols * r.count].copy_from_slice(&s[..r.cols * r.count]);
688                    Data::Real(t)
689                }
690                (Data::Int(t), Data::Int(s)) => {
691                    let mut t = t.clone();
692                    t[from..from + r.cols * r.count].copy_from_slice(&s[..r.cols * r.count]);
693                    Data::Int(t)
694                }
695                (Data::Int(_), Data::Real(_)) => {
696                    return Err(EvalError::Operand { op: at, tensor: names[1].clone(), why: "Reals written into a table of Ints".into() })
697                }
698            };
699            Ok((None, vec![(0, table)]))
700        }
701        TensorOpSpec::IntRequant(r) => {
702            let x = ints(at, &names[0], &ins[0])?;
703            let y = x
704                .iter()
705                .take(r.n)
706                .map(|&v| checked(at, "a requant's product", v.checked_mul(r.m)).map(|p| p.wrapping_shr(r.s)))
707                .collect::<Result<Vec<i64>, _>>()?;
708            Ok((Some(Data::Int(y)), Vec::new()))
709        }
710        TensorOpSpec::IntClamp(c) => {
711            let x = ints(at, &names[0], &ins[0])?;
712            Ok((Some(Data::Int(x.iter().take(c.n).map(|&v| v.max(c.lo).min(c.hi)).collect())), Vec::new()))
713        }
714        TensorOpSpec::Relu(r) => {
715            need(at, &names[0], &ins[0], r.n)?;
716            let y = match &ins[0] {
717                Data::Int(x) => Data::Int(x.iter().take(r.n).map(|&v| v.max(0)).collect()),
718                Data::Real(x) => Data::Real(x.iter().take(r.n).map(|&v| float_max(v, 0.0)).collect()),
719            };
720            Ok((Some(y), Vec::new()))
721        }
722        TensorOpSpec::LutAttention(l) => lut_attention(at, l, names, ins, env),
723    }
724}
725
726fn gather(d: &Data, pick: &[usize]) -> Data {
727    match d {
728        Data::Real(v) => Data::Real(pick.iter().map(|&i| v[i]).collect()),
729        Data::Int(v) => Data::Int(pick.iter().map(|&i| v[i]).collect()),
730    }
731}
732
733fn join<T: Copy>(a: &[T], b: &[T], rows: usize, heads: usize, ha: usize, hb: usize) -> Vec<T> {
734    let mut out = Vec::with_capacity(rows * heads * (ha + hb));
735    for r in 0..rows {
736        for h in 0..heads {
737            out.extend((0..ha).map(|i| a[r * heads * ha + h * ha + i]));
738            out.extend((0..hb).map(|i| b[r * heads * hb + h * hb + i]));
739        }
740    }
741    out
742}
743
744fn fused_row(at: usize, r: &crate::tensor_recognize::FusedRowSpec, names: &[String], ins: &[Data]) -> Result<Computed, EvalError> {
745    let n = r.rows * r.cols;
746    need(at, &names[0], &ins[0], n)?;
747    let x = ins[0].reals();
748    // The operands after `x`, in the spec's order: the weight, the residual, the bias — those present.
749    let mut k = 1;
750    let mut next = |present: bool| -> Option<Vec<f64>> {
751        present.then(|| {
752            let v = ins[k].reals();
753            k += 1;
754            v
755        })
756    };
757    let (w, res, bias) = (next(r.weight.is_some()), next(r.residual.is_some()), next(r.bias.is_some()));
758    let eps = r.eps.get();
759    let cols_f = r.cols as f64;
760    let mut y = Vec::with_capacity(n);
761    match r.reduce {
762        RowReduceKind::RmsNorm => match &w {
763            None => {
764                let mut ss = 0.0f64;
765                for p in 0..n {
766                    ss = ss + x[p] * x[p];
767                }
768                let denom = (ss / n as f64 + eps).sqrt();
769                for q in 0..n {
770                    y.push(x[q] / denom);
771                }
772            }
773            Some(w) => {
774                for row in 0..r.rows {
775                    let elem = |i: usize| match &res {
776                        Some(res) => x[row * r.cols + i] + res[row * r.cols + i],
777                        None => x[row * r.cols + i],
778                    };
779                    let mut ss = 0.0f64;
780                    for i in 0..r.cols {
781                        ss = ss + elem(i) * elem(i);
782                    }
783                    let denom = (ss / cols_f + eps).sqrt();
784                    for j in 0..r.cols {
785                        y.push(elem(j) / denom * w[j]);
786                    }
787                }
788            }
789        },
790        RowReduceKind::Softmax => {
791            for row in 0..r.rows {
792                let xr = &x[row * r.cols..(row + 1) * r.cols];
793                let mut m = xr[0];
794                for v in &xr[1..] {
795                    m = float_max(m, *v);
796                }
797                let mut s = 0.0f64;
798                for v in xr {
799                    s = s + E.powf(v - m);
800                }
801                for v in xr {
802                    y.push(E.powf(v - m) / s);
803                }
804            }
805        }
806        RowReduceKind::LayerNorm => {
807            let (Some(w), Some(b)) = (&w, &bias) else {
808                return Err(EvalError::Operand { op: at, tensor: names[0].clone(), why: "a layer norm without its weight and bias".into() });
809            };
810            for row in 0..r.rows {
811                let xr = &x[row * r.cols..(row + 1) * r.cols];
812                let mut sm = 0.0f64;
813                for v in xr {
814                    sm = sm + v;
815                }
816                let mean = sm / cols_f;
817                let mut vs = 0.0f64;
818                for v in xr {
819                    vs = vs + (v - mean) * (v - mean);
820                }
821                let denom = (vs / cols_f + eps).sqrt();
822                for (j, v) in xr.iter().enumerate() {
823                    y.push((v - mean) / denom * w[j] + b[j]);
824                }
825            }
826        }
827    }
828    Ok((Some(Data::Real(y)), Vec::new()))
829}
830
831/// `x` soft-capped at `c` as the language writes it: `c·(1 − 2/(e^(x·(2/c)) + 1))`.
832fn capped(x: f64, c: f64) -> f64 {
833    c * (1.0 - 2.0 / (E.powf(x * (2.0 / c)) + 1.0))
834}
835
836fn attention(
837    at: usize,
838    a: &crate::tensor_recognize::qwen::AttentionSpec,
839    names: &[String],
840    ins: &[Data],
841    env: &BTreeMap<String, Data>,
842) -> Result<Computed, EvalError> {
843    let (h, kv, hd, vd, l, rows) = (a.h, a.kv, a.hd, a.vd, a.l, a.rows.max(1));
844    let group = if kv > 0 { h / kv } else { 1 };
845    let scale = a.scale.map_or(1.0f64 / (hd as f64).sqrt(), |s| s.get());
846    need(at, &names[0], &ins[0], rows * h * hd)?;
847    need(at, &names[1], &ins[1], l * kv * hd)?;
848    need(at, &names[2], &ins[2], l * kv * vd)?;
849    let (q, kc, vc) = (ins[0].reals(), ins[1].reals(), ins[2].reals());
850    // The operands after the caches: the position, then the sinks — those present.
851    let mut k = 3;
852    let pos = match &a.pos {
853        Some(p) => {
854            let v = index(at, p, names, k, env)?;
855            if matches!(p, RowIndex::Named(_)) {
856                k += 1;
857            }
858            Some(v)
859        }
860        None => None,
861    };
862    let sinks = a.sinks.map(|_| ins[k].reals());
863    if let Some(p) = pos {
864        if p < 1 || p as usize + rows - 1 > l {
865            return Err(EvalError::OutOfRange {
866                op: at,
867                what: format!("attention length out of range: `{}` has {l} positions, and {rows} rows from {p} need {p} ≥ 1 and {p} + {} ≤ {l}", names[1], rows - 1),
868            });
869        }
870    }
871    let mut c = vec![0.0f64; rows * h * vd];
872    for r in 0..rows {
873        // The positions this row attends: up to its own, or every cached one.
874        let n = pos.map_or(l, |p| p as usize + r);
875        for head in 0..h {
876            let kvh = head / group;
877            let lo = a.window.map_or(1, |w| w.first(n));
878            let mut scores = Vec::with_capacity(n + 1 - lo);
879            for j in lo..=n {
880                let mut acc = 0.0f64;
881                for e in 0..hd {
882                    acc = acc + q[r * h * hd + head * hd + e] * kc[(j - 1) * kv * hd + kvh * hd + e];
883                }
884                let score = acc * scale;
885                scores.push(a.softcap.map_or(score, |c| capped(score, c.get())));
886            }
887            let mut mx = match &sinks {
888                Some(s) => float_max(scores[0], s[head]),
889                None => scores[0],
890            };
891            for s in &scores[1..] {
892                mx = float_max(mx, *s);
893            }
894            let mut den = match &sinks {
895                Some(s) => E.powf(s[head] - mx),
896                None => 0.0,
897            };
898            for s in &scores {
899                den = den + E.powf(s - mx);
900            }
901            for e in 0..vd {
902                let mut acc = 0.0f64;
903                for j in lo..=n {
904                    acc = acc + E.powf(scores[j - lo] - mx) / den * vc[(j - 1) * kv * vd + kvh * vd + e];
905                }
906                c[r * h * vd + head * vd + e] = acc;
907            }
908        }
909    }
910    Ok((Some(Data::Real(c)), Vec::new()))
911}
912
913fn moe(at: usize, m: &crate::tensor_recognize::qwen::MoeSpec, names: &[String], ins: &[Data]) -> Result<Computed, EvalError> {
914    let (d, dff, ne, k) = (m.d, m.dff, m.ne, m.k);
915    let x = ins[0].reals();
916    need(at, &names[0], &ins[0], d)?;
917    need(at, &names[1], &ins[1], d * ne)?;
918    need(at, &names[2], &ins[2], ne * dff * d)?;
919    need(at, &names[3], &ins[3], ne * dff * d)?;
920    need(at, &names[4], &ins[4], ne * d * dff)?;
921    let (wr, wg, wu, wd) = (ins[1].reals(), ins[2].reals(), ins[3].reals(), ins[4].reals());
922    // The operands after the four matrices: the router's bias, then the gate's, up's and down's, then the selection
923    // bias — those present.
924    let mut at_k = 5;
925    let mut next = |present: bool| -> Option<Vec<f64>> {
926        present.then(|| {
927            let v = ins[at_k].reals();
928            at_k += 1;
929            v
930        })
931    };
932    let rb = next(m.router_bias.is_some());
933    let [bg, bu, bd] = [next(m.biases[0].is_some()), next(m.biases[1].is_some()), next(m.biases[2].is_some())];
934    let bs = next(m.selection_bias.is_some());
935    let mut logits = Vec::with_capacity(ne);
936    for e in 0..ne {
937        let mut acc = 0.0f64;
938        for t in 0..d {
939            acc = acc + x[t] * wr[t * ne + e];
940        }
941        logits.push(match &rb {
942            Some(b) => acc + b[e],
943            None => acc,
944        });
945    }
946    let p: Vec<f64> = match m.score {
947        MoeScore::Softmax => {
948            let mut mx = logits[0];
949            for l in &logits[1..] {
950                mx = float_max(mx, *l);
951            }
952            let mut sum = 0.0f64;
953            for l in &logits {
954                sum = sum + E.powf(l - mx);
955            }
956            logits.iter().map(|l| E.powf(l - mx) / sum).collect()
957        }
958        // σ(L[e]) = 1 / (1 + e^(0 − L[e])), the desugar's arrangement.
959        MoeScore::Sigmoid => logits.iter().map(|l| 1.0 / (1.0 + E.powf(0.0 - l))).collect(),
960    };
961    // The selection scores: each score plus its selection bias; the weights stay the scores.
962    let mut pm: Vec<f64> = match &bs {
963        Some(b) => p.iter().zip(b).map(|(v, b)| v + b).collect(),
964        None => p.clone(),
965    };
966    // The group limit: each group scored by its two best selection scores, the best group left chosen in turn (and
967    // marked below every score), only the chosen groups' experts keeping their selection scores.
968    if let Some(MoeGroups { count, kept }) = m.groups {
969        let per = ne / count;
970        let mut scores: Vec<f64> = (0..count)
971            .map(|g| {
972                let (mut t1, mut t2) = (-1.0e30f64, -1.0e30f64);
973                for v in &pm[g * per..(g + 1) * per] {
974                    if *v > t1 {
975                        t2 = t1;
976                        t1 = *v;
977                    } else if *v > t2 {
978                        t2 = *v;
979                    }
980                }
981                if per > 1 {
982                    t1 + t2
983                } else {
984                    t1
985                }
986            })
987            .collect();
988        let mut chosen = vec![-1.0e30f64; ne];
989        for _ in 0..kept {
990            let (mut bv, mut bi) = (-1.0e30f64, 0usize);
991            for (g, v) in scores.iter().enumerate() {
992                if *v > bv {
993                    bv = *v;
994                    bi = g;
995                }
996            }
997            chosen[bi * per..(bi + 1) * per].copy_from_slice(&pm[bi * per..(bi + 1) * per]);
998            scores[bi] = -2.0e30;
999        }
1000        pm = chosen;
1001    }
1002    let mut w = vec![0.0f64; ne];
1003    // The kept experts, one each, when the weights scale the inputs: their outputs join the sum unweighted.
1004    let mut sel = vec![0.0f64; ne];
1005    let mut wsum = 0.0f64;
1006    for _ in 0..k {
1007        let (mut bv, mut bi) = (-1.0e30f64, 0usize);
1008        for (sci, v) in pm.iter().enumerate() {
1009            if *v > bv {
1010                bv = *v;
1011                bi = sci;
1012            }
1013        }
1014        wsum = wsum + p[bi];
1015        w[bi] = p[bi];
1016        sel[bi] = 1.0;
1017        pm[bi] = -1.0e30;
1018    }
1019    if m.renormalize {
1020        // The sum clamped at 2⁻¹⁴, as llama.cpp's `build_moe_ffn` clamps it.
1021        let wsum = float_max(wsum, 6.103515625e-5);
1022        for v in &mut w {
1023            *v = *v / wsum;
1024        }
1025    }
1026    if m.scale.is_some() {
1027        for v in &mut w {
1028            *v = *v * m.scale();
1029        }
1030    }
1031    let mut out = vec![0.0f64; d];
1032    for ee in 0..ne {
1033        let project = |wm: &[f64], bias: &Option<Vec<f64>>| -> Vec<f64> {
1034            (0..dff)
1035                .map(|o| {
1036                    let mut acc = 0.0f64;
1037                    for t in 0..d {
1038                        acc = acc + x[t] * wm[(ee * dff + o) * d + t];
1039                    }
1040                    let acc = if m.weigh_inputs { w[ee] * acc } else { acc };
1041                    match bias {
1042                        Some(b) => acc + b[ee * dff + o],
1043                        None => acc,
1044                    }
1045                })
1046                .collect()
1047        };
1048        let (gate, up) = (project(&wg, &bg), project(&wu, &bu));
1049        let hidden = |t: usize| match m.act {
1050            MoeAct::Swiglu => silu(gate[t]) * up[t],
1051            MoeAct::ClampedSwiglu { limit, slope } => {
1052                let (limit, slope) = (f64::from_bits(limit), f64::from_bits(slope));
1053                let gc = float_min(gate[t], limit);
1054                let uc = float_max(float_min(up[t], limit), -limit);
1055                gc / (1.0 + E.powf(0.0 - slope * gc)) * (uc + 1.0)
1056            }
1057        };
1058        for oo in 0..d {
1059            let mut acc = 0.0f64;
1060            for t in 0..dff {
1061                acc = acc + hidden(t) * wd[(ee * d + oo) * dff + t];
1062            }
1063            let down = match &bd {
1064                Some(b) => acc + b[ee * d + oo],
1065                None => acc,
1066            };
1067            out[oo] = out[oo] + (if m.weigh_inputs { sel[ee] } else { w[ee] }) * down;
1068        }
1069    }
1070    Ok((Some(Data::Real(out)), Vec::new()))
1071}
1072
1073/// `a // b`: the quotient rounded toward negative infinity, exactly, as the VM floors the rational quotient.
1074fn floor_div(a: i64, b: i64) -> i128 {
1075    let (a, b) = (a as i128, b as i128);
1076    let q = a / b;
1077    if a % b != 0 && ((a < 0) != (b < 0)) {
1078        q - 1
1079    } else {
1080        q
1081    }
1082}
1083
1084fn lut_attention(
1085    at: usize,
1086    a: &crate::tensor_recognize::qwen::LutAttentionSpec,
1087    names: &[String],
1088    ins: &[Data],
1089    env: &BTreeMap<String, Data>,
1090) -> Result<Computed, EvalError> {
1091    let (h, kv, hd, l, n) = (a.h, a.kv, a.hd, a.l, a.table_len);
1092    let group = h / kv.max(1);
1093    let (q, kc, vc, table) = (ints(at, &names[0], &ins[0])?, ints(at, &names[1], &ins[1])?, ints(at, &names[2], &ins[2])?, ints(at, &names[3], &ins[3])?);
1094    need(at, &names[0], &ins[0], h * hd)?;
1095    need(at, &names[1], &ins[1], l * kv * hd)?;
1096    need(at, &names[2], &ins[2], l * kv * hd)?;
1097    need(at, &names[3], &ins[3], n)?;
1098    let len = match &a.pos {
1099        Some(p) => {
1100            let p = index(at, p, names, 4, env)?;
1101            if p < 1 || p as usize > l {
1102                return Err(EvalError::OutOfRange { op: at, what: format!("attention length out of range: `{}` has {l} positions, not {p}", names[1]) });
1103            }
1104            p as usize
1105        }
1106        None => l,
1107    };
1108    let mut c = vec![0i64; h * hd];
1109    for head in 0..h {
1110        let kvh = head / group;
1111        let mut scores = Vec::with_capacity(len);
1112        for j in 0..len {
1113            let mut acc = 0i64;
1114            for e in 0..hd {
1115                let p = checked(at, "a score's product", q[head * hd + e].checked_mul(kc[j * kv * hd + kvh * hd + e]))?;
1116                acc = checked(at, "a score", acc.checked_add(p))?;
1117            }
1118            scores.push(acc);
1119        }
1120        let mut mx = scores[0];
1121        for s in &scores[1..] {
1122            mx = mx.max(*s);
1123        }
1124        let mut wts = Vec::with_capacity(len);
1125        let mut den = 0i64;
1126        for s in &scores {
1127            let d = checked(at, "a score's distance", mx.checked_sub(*s))?;
1128            let scaled = checked(at, "a scaled distance", d.checked_mul(a.m))?.wrapping_shr(a.s);
1129            let w = table[scaled.max(0).min(n as i64 - 1) as usize];
1130            wts.push(w);
1131            den = checked(at, "a weight sum", den.checked_add(w))?;
1132        }
1133        for e in 0..hd {
1134            let mut num = 0i64;
1135            for j in 0..len {
1136                let p = checked(at, "a weighted value", wts[j].checked_mul(vc[j * kv * hd + kvh * hd + e]))?;
1137                num = checked(at, "a weighted sum", num.checked_add(p))?;
1138            }
1139            if den == 0 {
1140                return Err(EvalError::DivideByZero { op: at });
1141            }
1142            c[head * hd + e] = checked(at, "a quotient", i64::try_from(floor_div(num, den)).ok())?;
1143        }
1144    }
1145    Ok((Some(Data::Int(c)), Vec::new()))
1146}