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