1use 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#[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 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#[derive(Clone, Debug, PartialEq)]
64pub enum EvalError {
65 Missing { op: usize, tensor: String },
67 OutOfRange { op: usize, what: String },
69 Overflow { op: usize, what: String },
71 Operand { op: usize, tensor: String, why: String },
73 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
91pub 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
122pub 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
139fn 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
151fn 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 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
193fn 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
217fn 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 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 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 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
285fn 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
295fn 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
321fn silu(x: f64) -> f64 {
323 x / (1.0 + E.powf(0.0 - x))
324}
325
326type 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 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 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
831fn 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 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 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 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 MoeScore::Sigmoid => logits.iter().map(|l| 1.0 / (1.0 + E.powf(0.0 - l))).collect(),
960 };
961 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 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 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 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
1073fn 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}