1use 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#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
34pub enum StateFormat {
35 F32,
36 F16,
37}
38
39#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
41pub enum CacheFormat {
42 F16,
44 Int8,
46 Int8Hadamard,
50}
51
52impl CacheFormat {
53 pub fn is_int8(self) -> bool {
55 matches!(self, CacheFormat::Int8 | CacheFormat::Int8Hadamard)
56 }
57}
58
59#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
61pub struct EngineNumerics {
62 pub activations: Words,
63 pub scaling: ActScaling,
65 pub state: StateFormat,
66 pub cache: CacheFormat,
67 pub attention: AttentionMath,
68 pub flush: StateFlush,
71 pub fp_unit: FpUnit,
73}
74
75impl EngineNumerics {
76 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 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 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 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 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 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
174fn 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
182pub const FLUSH_TOKENS: u32 = 16;
184
185pub const TRAILING_TOKENS: u32 = FLUSH_TOKENS / 2;
188
189#[derive(Clone, Debug, PartialEq, Eq)]
192pub struct Replay {
193 pub older: usize,
196 pub replayed: usize,
197 pub rows: std::ops::Range<usize>,
199 pub by: usize,
200 pub drop: usize,
202}
203
204impl Replay {
205 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 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#[derive(Debug)]
241pub enum EngineError {
242 Io(std::io::Error),
243 NotYet(&'static str),
245 Undecoded { tensor: String, ty: GgmlType },
247 Unreadable(String),
249 NoSequence(usize),
251 PastContext { sequence: usize, position: usize },
253 Factors { found: usize, pairs: usize },
255 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#[derive(Clone)]
275struct CacheRows {
276 stored: Stored,
277 values: Vec<Vec<f32>>,
278}
279
280#[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 fn values(&self, g: usize) -> &[f32] {
308 &self.values[g]
309 }
310
311 fn int8_row(&self, g: usize) -> (u16, &[i8]) {
313 let (s, codes) = self.int8(g);
314 (*s, codes.as_slice())
315 }
316
317 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#[derive(Clone)]
331struct LogEntry {
332 k: Vec<f32>,
333 v: Vec<f32>,
334 log_decay: Vec<f32>,
335 beta: Vec<f32>,
336}
337
338#[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
366const ROW_TILE: usize = 16;
368
369pub const CACHE_BUDGET: usize = 24 << 30;
371
372pub const CODED_BUDGET: usize = 16 << 30;
374
375pub 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 coded: HashMap<String, Arc<Vec<CodedRow>>>,
385 coded_bytes: usize,
386 coded_budget: usize,
387 sequences: Vec<Sequence>,
388 threads: usize,
389 program: Arc<Program>,
391}
392
393pub 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 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 pub fn program(&self) -> &Program {
461 &self.program
462 }
463
464 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 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 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 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 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 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 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 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 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 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 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 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 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#[derive(Clone, Copy, Debug)]
989struct Row {
990 entry: usize,
991 sequence: usize,
992 position: usize,
993 token: u32,
994}
995
996#[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
1014fn 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
1025pub(crate) const FACTORS: &str = "rope_freqs.weight";
1027
1028pub(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
1034pub(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
1047fn 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
1056fn 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
1066pub fn argmax(logits: &[f32]) -> u32 {
1068 super::numerics::argmax(logits) as u32
1069}