1use std::cell::UnsafeCell;
17use std::sync::Arc;
18use std::time::Instant;
19
20use kime_tensor::plan::{Epilogue, Graph, Op, Rows, Val, layout};
21use kime_tensor::{Backend, Batch, Bucket, Caps, Error, HostTensor, Outputs, Result};
22
23use crate::attention::{self, HEAD, QB};
24use crate::gemm::{self, Gemm};
25use crate::ops::{Rope, geglu, layer_norm};
26use crate::par::{self, Shared};
27use crate::pool::Pool;
28use crate::qgemm::{self, QGemm, QMatrix};
29
30#[derive(Debug)]
32pub struct Tensor {
33 pub shape: Vec<usize>,
35 pub data: Vec<f32>,
37 pub packed: Vec<f32>,
40 pub quant: Option<QMatrix>,
42}
43
44#[derive(Debug, Clone)]
46pub struct Weights(Arc<[Tensor]>);
47
48#[derive(Debug)]
50pub struct CpuBackend {
51 pool: Pool,
52 int8: bool,
53}
54
55impl CpuBackend {
56 #[must_use]
58 pub fn new(threads: usize) -> Self {
59 Self { pool: Pool::new(threads.max(1)), int8: false }
60 }
61
62 #[must_use]
66 pub fn with_int8(mut self, on: bool) -> Self {
67 self.int8 = on;
68 self
69 }
70
71 #[must_use]
73 pub fn int8(&self) -> bool {
74 self.int8
75 }
76
77 fn int8_rows(&self, rows: Rows) -> bool {
79 self.int8 && rows == Rows::Tokens
80 }
81
82 #[must_use]
84 pub fn threads(&self) -> usize {
85 self.pool.threads()
86 }
87}
88
89#[derive(Debug, Clone, Copy)]
91struct Loc {
92 off: usize,
93 rows: Rows,
94 width: usize,
95}
96
97#[derive(Debug, Clone, Copy)]
98enum Step {
99 Embed { table: usize, out: Loc },
100 LayerNorm { x: Loc, w: usize, b: Option<usize>, eps: f64, out: Loc },
101 Gemm { a: Loc, w: usize, b: Option<usize>, ep: Epilogue, out: Loc },
102 Gemm8 { a: Loc, w: usize, b: Option<usize>, ep: Epilogue, out: Loc },
103 Rope { qkv: Loc, rope: usize },
104 Attention { qkv: Loc, window: Option<usize>, out: Loc },
105 GeGlu { x: Loc, out: Loc },
106 AddType { h: Loc, table: usize },
107 Gather { h: Loc, out: Loc },
108 ActFeatures { h: Loc, logits: Loc, out: Loc },
109 MeanPool { h: Loc, out: Loc },
110}
111
112impl Step {
113 fn name(&self) -> &'static str {
114 match self {
115 Step::Embed { .. } => "embed",
116 Step::LayerNorm { .. } => "layer norm",
117 Step::Gemm { .. } => "gemm",
118 Step::Gemm8 { .. } => "gemm int8",
119 Step::Rope { .. } => "rope",
120 Step::Attention { .. } => "attention",
121 Step::GeGlu { .. } => "geglu",
122 Step::AddType { .. } => "type embedding",
123 Step::Gather { .. } => "gather markers",
124 Step::ActFeatures { .. } => "act features",
125 Step::MeanPool { .. } => "mean pool",
126 }
127 }
128
129 fn out(&self) -> Loc {
131 match *self {
132 Step::Embed { out, .. }
133 | Step::LayerNorm { out, .. }
134 | Step::Gemm { out, .. }
135 | Step::Gemm8 { out, .. }
136 | Step::Attention { out, .. }
137 | Step::GeGlu { out, .. }
138 | Step::Gather { out, .. }
139 | Step::ActFeatures { out, .. }
140 | Step::MeanPool { out, .. } => out,
141 Step::Rope { qkv, .. } => qkv,
142 Step::AddType { h, .. } => h,
143 }
144 }
145}
146
147#[derive(Debug, Clone)]
149pub struct Dump {
150 pub name: &'static str,
152 pub rows: Rows,
154 pub width: usize,
156 pub data: Vec<f32>,
158}
159
160struct PerWorker<T>(Vec<UnsafeCell<T>>);
162
163unsafe impl<T: Send> Sync for PerWorker<T> {}
166
167impl<T> PerWorker<T> {
168 #[allow(clippy::mut_from_ref)]
172 unsafe fn get(&self, worker: usize) -> &mut T {
173 unsafe { &mut *self.0[worker].get() }
175 }
176}
177
178pub struct CpuPlan {
180 w: Weights,
181 bucket: Bucket,
182 steps: Vec<Step>,
183 arena: Vec<f32>,
184 ropes: Vec<Rope>,
185 logits: Option<Loc>,
186 act: Option<Loc>,
187 pooled: Option<Loc>,
188 cu: Vec<usize>,
191 mcu: Vec<usize>,
192 row_seq: Vec<u32>,
193 blocks: Vec<(u32, u32)>,
194 scratch: PerWorker<Vec<f32>>,
195 profile: Option<Vec<u64>>,
197 dumps: Option<Vec<Dump>>,
199}
200
201impl std::fmt::Debug for CpuPlan {
202 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203 f.debug_struct("CpuPlan")
204 .field("bucket", &self.bucket)
205 .field("steps", &self.steps.len())
206 .field("arena", &self.arena.len())
207 .finish_non_exhaustive()
208 }
209}
210
211impl CpuPlan {
212 #[must_use]
214 pub fn bucket(&self) -> Bucket {
215 self.bucket
216 }
217
218 #[must_use]
220 pub fn arena_bytes(&self) -> usize {
221 self.arena.len() * 4
222 }
223
224 pub fn profile(&mut self) {
226 self.profile = Some(vec![0; self.steps.len()]);
227 }
228
229 pub fn dump(&mut self) {
232 self.dumps = Some(Vec::new());
233 }
234
235 #[must_use]
237 pub fn dumps(&self) -> &[Dump] {
238 self.dumps.as_deref().unwrap_or_default()
239 }
240
241 #[must_use]
243 pub fn timings(&self) -> Vec<(&'static str, u64)> {
244 let mut by: Vec<(&'static str, u64)> = Vec::new();
245 for (s, &ns) in self.steps.iter().zip(self.profile.iter().flatten()) {
246 match by.iter_mut().find(|b| b.0 == s.name()) {
247 Some(b) => b.1 += ns,
248 None => by.push((s.name(), ns)),
249 }
250 }
251 by.sort_by_key(|b| std::cmp::Reverse(b.1));
252 by
253 }
254}
255
256#[derive(Clone, Copy)]
258struct Arena(*mut f32);
259
260unsafe impl Send for Arena {}
263unsafe impl Sync for Arena {}
265
266impl Arena {
267 unsafe fn slice<'a>(self, off: usize, len: usize) -> &'a [f32] {
271 unsafe { std::slice::from_raw_parts(self.0.add(off), len) }
273 }
274
275 #[allow(clippy::mut_from_ref)]
279 unsafe fn slice_mut<'a>(self, off: usize, len: usize) -> &'a mut [f32] {
280 unsafe { std::slice::from_raw_parts_mut(self.0.add(off), len) }
282 }
283}
284
285const ROWS: usize = 16;
287
288impl Backend for CpuBackend {
289 type Weights = Weights;
290 type Plan = CpuPlan;
291
292 fn caps(&self) -> Caps {
293 Caps { name: "cpu", threads: self.threads(), graphs: false, unified_memory: true }
294 }
295
296 fn weight_bytes(&self, w: &Weights) -> usize {
297 w.0.iter()
298 .map(|t| {
299 let q = t.quant.as_ref().map_or(0, |q| q.q.len() + 4 * q.scale.len());
300 4 * (t.data.len() + t.packed.len()) + q
301 })
302 .sum()
303 }
304
305 fn plan_bytes(&self, p: &CpuPlan) -> usize {
306 p.arena_bytes()
307 }
308
309 fn upload(&self, tensors: &[HostTensor<'_>], graph: &Graph) -> Result<Weights> {
310 let (mut gemm, mut other) = (vec![false; tensors.len()], vec![false; tensors.len()]);
313 let mut gemm8 = vec![false; tensors.len()];
314 let mark = |flags: &mut Vec<bool>, w: Option<usize>| {
315 if let Some(f) = w.and_then(|w| flags.get_mut(w)) {
316 *f = true;
317 }
318 };
319 for op in &graph.ops {
320 match *op {
321 Op::Gemm { a, w, b, .. } => {
322 match self.int8_rows(graph.shape(a).rows) {
323 true => mark(&mut gemm8, Some(w)),
324 false => mark(&mut gemm, Some(w)),
325 }
326 mark(&mut other, b);
327 }
328 Op::Embed { table, .. } | Op::AddType { table, .. } => {
329 mark(&mut other, Some(table))
330 }
331 Op::LayerNorm { w, b, .. } => {
332 mark(&mut other, Some(w));
333 mark(&mut other, b);
334 }
335 Op::Rope { .. }
336 | Op::Attention { .. }
337 | Op::GeGlu { .. }
338 | Op::GatherMarkers { .. }
339 | Op::ActFeatures { .. }
340 | Op::MeanPool { .. } => {}
341 }
342 }
343 let t = par::map(tensors.len(), self.threads(), |i| {
344 let h = &tensors[i];
345 let n = h.bytes.len() / h.dtype.size();
346 if n != h.shape.iter().product::<usize>() {
347 return Err(Error::Unsupported(format!(
348 "tensor {i} has {n} values for {:?}",
349 h.shape
350 )));
351 }
352 let data: Vec<f32> = (0..n).map(|j| h.dtype.read_f32(h.bytes, j)).collect();
353 let packed = match (gemm[i], h.shape) {
354 (true, &[rows, cols]) => gemm::pack(&data, rows, cols),
355 _ => Vec::new(),
356 };
357 let quant = match (gemm8[i], h.shape) {
358 (true, &[rows, cols]) => Some(QMatrix::quantize(&data, rows, cols)),
359 _ => None,
360 };
361 let copied = (!gemm[i] || !packed.is_empty()) && (!gemm8[i] || quant.is_some());
363 let data = if (gemm[i] || gemm8[i]) && !other[i] && copied { Vec::new() } else { data };
364 Ok(Tensor { shape: h.shape.to_vec(), data, packed, quant })
365 });
366 Ok(Weights(t.into_iter().collect::<Result<Vec<_>>>()?.into()))
367 }
368
369 fn lower(&self, w: &Weights, graph: &Graph, bucket: Bucket) -> Result<CpuPlan> {
370 let lay = layout(graph, |r| bucket.rows(r));
371 let loc = |v: Val| {
372 let s = graph.shape(v);
373 Loc { off: lay.offsets[v.0 as usize], rows: s.rows, width: s.width }
374 };
375 let bad = |m: String| Err(Error::Unsupported(m));
376 let shape = |i: usize| -> Result<&[usize]> {
377 match w.0.get(i) {
378 Some(t) => Ok(&t.shape),
379 None => Err(Error::Unsupported(format!("weight {i} is not in the checkpoint"))),
380 }
381 };
382 let mut ropes: Vec<(u64, Rope)> = Vec::new();
383 let mut scratch = attention::scratch_len(bucket.tokens);
384 let mut steps = Vec::with_capacity(graph.ops.len());
385 for (i, op) in graph.ops.iter().enumerate() {
386 let step = match *op {
387 Op::Embed { table, out } => {
388 let out = loc(out);
389 if shape(table)?.get(1) != Some(&out.width) || out.rows != Rows::Tokens {
390 return bad(format!("op {i}: embedding table does not match its output"));
391 }
392 Step::Embed { table, out }
393 }
394 Op::LayerNorm { x, w: nw, b, eps, out } => {
395 let (x, out) = (loc(x), loc(out));
396 let ok = shape(nw)? == [x.width]
397 && b.map_or(Ok(true), |b| shape(b).map(|s| s == [x.width]))?
398 && x.width == out.width
399 && x.rows == out.rows;
400 if !ok {
401 return bad(format!("op {i}: layer norm shapes do not match"));
402 }
403 Step::LayerNorm { x, w: nw, b, eps, out }
404 }
405 Op::Gemm { a, w: gw, b, epilogue, out } => {
406 let (a, out) = (loc(a), loc(out));
407 let ok = shape(gw)? == [out.width, a.width]
408 && b.map_or(Ok(true), |b| shape(b).map(|s| s == [out.width]))?
409 && a.rows == out.rows;
410 if !ok {
411 return bad(format!("op {i}: gemm shapes do not match"));
412 }
413 if self.int8_rows(a.rows) {
414 if w.0[gw].quant.is_none() {
415 return bad(format!("op {i}: gemm weight {gw} was not rounded"));
416 }
417 scratch = scratch.max(qgemm::scratch_len(a.width));
418 Step::Gemm8 { a, w: gw, b, ep: epilogue, out }
419 } else {
420 if w.0[gw].packed.is_empty() && a.width * out.width > 0 {
421 return bad(format!("op {i}: gemm weight {gw} was not packed"));
422 }
423 scratch = scratch.max(gemm::scratch_len(a.width, out.width));
424 Step::Gemm { a, w: gw, b, ep: epilogue, out }
425 }
426 }
427 Op::Rope { qkv, theta } => {
428 let qkv = loc(qkv);
429 if !qkv.width.is_multiple_of(3 * HEAD) || qkv.rows != Rows::Tokens {
430 return bad(format!("op {i}: rope needs token rows of 3 heads 64"));
431 }
432 let at = match ropes.iter().position(|r| r.0 == theta.to_bits()) {
433 Some(at) => at,
434 None => {
435 ropes.push((theta.to_bits(), Rope::new(theta, HEAD, bucket.tokens)));
436 ropes.len() - 1
437 }
438 };
439 Step::Rope { qkv, rope: at }
440 }
441 Op::Attention { qkv, window, out } => {
442 let (qkv, out) = (loc(qkv), loc(out));
443 let ok = qkv.width.is_multiple_of(3 * HEAD)
444 && out.width * 3 == qkv.width
445 && qkv.rows == Rows::Tokens
446 && out.rows == Rows::Tokens;
447 if !ok {
448 return bad(format!("op {i}: attention shapes do not match"));
449 }
450 Step::Attention { qkv, window, out }
451 }
452 Op::GeGlu { x, out } => {
453 let (x, out) = (loc(x), loc(out));
454 if x.width != 2 * out.width || x.rows != out.rows {
455 return bad(format!("op {i}: geglu input is not twice its output"));
456 }
457 Step::GeGlu { x, out }
458 }
459 Op::AddType { h, table } => {
460 let h = loc(h);
461 if shape(table)?.get(1) != Some(&h.width) || h.rows != Rows::Tokens {
462 return bad(format!("op {i}: type table does not match"));
463 }
464 Step::AddType { h, table }
465 }
466 Op::GatherMarkers { h, out } => {
467 let (h, out) = (loc(h), loc(out));
468 if h.width != out.width || h.rows != Rows::Tokens || out.rows != Rows::Markers {
469 return bad(format!("op {i}: gather shapes do not match"));
470 }
471 Step::Gather { h, out }
472 }
473 Op::ActFeatures { h, logits, out } => {
474 let (h, logits, out) = (loc(h), loc(logits), loc(out));
475 let ok = out.width == h.width + 4
476 && logits.width == 1
477 && logits.rows == Rows::Markers
478 && out.rows == Rows::Seqs;
479 if !ok {
480 return bad(format!("op {i}: act feature shapes do not match"));
481 }
482 Step::ActFeatures { h, logits, out }
483 }
484 Op::MeanPool { h, out } => {
485 let (h, out) = (loc(h), loc(out));
486 if h.rows != Rows::Tokens || out.rows != Rows::Seqs || out.width != h.width {
487 return bad(format!("op {i}: mean pool shapes do not match"));
488 }
489 Step::MeanPool { h, out }
490 }
491 };
492 steps.push(step);
493 }
494 let (logits, act, pooled) =
495 (graph.logits.map(loc), graph.act.map(loc), graph.pooled.map(loc));
496 if logits.is_some() != act.is_some() || (logits.is_none() && pooled.is_none()) {
497 return bad("the graph needs logits and act outputs, a pooled output, or both".into());
498 }
499 if logits.is_some_and(|l| l.width != 1 || l.rows != Rows::Markers)
500 || act.is_some_and(|a| a.width != 2 || a.rows != Rows::Seqs)
501 {
502 return bad(
503 "outputs must be one logit per marker and two act logits per sequence".into()
504 );
505 }
506 if pooled.is_some_and(|p| p.rows != Rows::Seqs) {
507 return bad("the pooled output must have one row per sequence".into());
508 }
509 let threads = self.threads();
510 Ok(CpuPlan {
511 w: w.clone(),
512 bucket,
513 steps,
514 arena: vec![0.0; lay.len],
515 ropes: ropes.into_iter().map(|r| r.1).collect(),
516 logits,
517 act,
518 pooled,
519 cu: Vec::with_capacity(bucket.seqs + 1),
520 mcu: Vec::with_capacity(bucket.seqs + 1),
521 row_seq: Vec::with_capacity(bucket.tokens),
522 blocks: Vec::with_capacity(bucket.tokens.div_ceil(QB) + bucket.seqs),
523 scratch: PerWorker(
524 (0..threads).map(|_| UnsafeCell::new(Vec::with_capacity(scratch))).collect(),
525 ),
526 profile: None,
527 dumps: None,
528 })
529 }
530
531 fn run(&self, plan: &mut CpuPlan, batch: &Batch<'_>, out: &mut Outputs) -> Result<()> {
532 let (t, s, m) = (batch.ids.len(), batch.seqs(), batch.markers.len());
533 if !plan.bucket.holds(t, s, m) {
534 return Err(Error::Batch(format!("batch does not fit bucket {}", plan.bucket)));
535 }
536 plan.cu.clear();
537 plan.cu.extend(batch.cu.iter().map(|&c| c as usize));
538 plan.mcu.clear();
539 plan.mcu.extend(batch.mcu.iter().map(|&c| c as usize));
540 plan.row_seq.clear();
541 plan.blocks.clear();
542 for q in 0..s {
543 let (lo, hi) = (plan.cu[q], plan.cu[q + 1]);
544 plan.row_seq.extend(std::iter::repeat_n(q as u32, hi - lo));
545 plan.blocks.extend((lo..hi).step_by(QB).map(|q0| (q as u32, q0 as u32)));
546 }
547 let ctx = Ctx {
548 pool: &self.pool,
549 w: &plan.w.0,
550 arena: Arena(plan.arena.as_mut_ptr()),
551 ropes: &plan.ropes,
552 batch,
553 cu: &plan.cu,
554 mcu: &plan.mcu,
555 row_seq: &plan.row_seq,
556 blocks: &plan.blocks,
557 scratch: &plan.scratch,
558 counts: [t, s, m],
559 };
560 if let Some(d) = plan.dumps.as_mut() {
561 d.clear();
562 }
563 for (i, step) in plan.steps.iter().enumerate() {
564 match plan.profile.as_mut() {
565 None => ctx.step(step),
566 Some(p) => {
567 let at = Instant::now();
568 ctx.step(step);
569 p[i] += u64::try_from(at.elapsed().as_nanos()).unwrap_or(u64::MAX);
570 }
571 }
572 if let Some(d) = plan.dumps.as_mut() {
574 let l = step.out();
575 let live = unsafe { ctx.arena.slice(l.off, ctx.rows(l.rows) * l.width) };
578 d.push(Dump {
579 name: step.name(),
580 rows: l.rows,
581 width: l.width,
582 data: live.to_vec(),
583 });
584 }
585 }
586
587 out.logits.clear();
588 out.act.clear();
589 out.pooled.clear();
590 unsafe {
592 if let Some(l) = plan.logits {
593 out.logits.extend_from_slice(ctx.arena.slice(l.off, m));
594 }
595 if let Some(a) = plan.act {
596 out.act.extend_from_slice(ctx.arena.slice(a.off, 2 * s).as_chunks::<2>().0);
597 }
598 if let Some(p) = plan.pooled {
599 out.pooled.extend_from_slice(ctx.arena.slice(p.off, p.width * s));
600 }
601 }
602 Ok(())
603 }
604}
605
606struct Ctx<'a> {
608 pool: &'a Pool,
609 w: &'a [Tensor],
610 arena: Arena,
611 ropes: &'a [Rope],
612 batch: &'a Batch<'a>,
613 cu: &'a [usize],
614 mcu: &'a [usize],
615 row_seq: &'a [u32],
616 blocks: &'a [(u32, u32)],
617 scratch: &'a PerWorker<Vec<f32>>,
618 counts: [usize; 3],
619}
620
621impl Ctx<'_> {
622 fn rows(&self, r: Rows) -> usize {
623 self.counts[r as usize]
624 }
625
626 fn w(&self, i: usize) -> &[f32] {
627 &self.w[i].data
628 }
629
630 unsafe fn get(&self, l: Loc) -> &[f32] {
636 unsafe { self.arena.slice(l.off, self.rows(l.rows) * l.width) }
638 }
639
640 unsafe fn rows_of(&self, out: Loc, f: &(dyn Fn(usize, &mut [f32]) + Sync)) {
646 let n = self.rows(out.rows);
647 let arena = self.arena;
648 self.pool.run(n.div_ceil(ROWS), &|task, _| {
649 let r0 = task * ROWS;
650 let r1 = (r0 + ROWS).min(n);
651 let rows = unsafe { arena.slice_mut(out.off + r0 * out.width, (r1 - r0) * out.width) };
653 f(r0, rows);
654 });
655 }
656
657 fn step(&self, step: &Step) {
658 match *step {
662 Step::Embed { table, out } => {
663 let (tab, ids, d) = (self.w(table), self.batch.ids, out.width);
664 unsafe {
666 self.rows_of(out, &|r0, rows| {
667 for (i, row) in rows.chunks_exact_mut(d).enumerate() {
668 let id = ids[r0 + i] as usize;
669 row.copy_from_slice(&tab[id * d..(id + 1) * d]);
670 }
671 });
672 }
673 }
674 Step::LayerNorm { x, w, b, eps, out } => {
675 let x = unsafe { self.get(x) };
677 let (nw, nb, d) = (self.w(w), b.map(|b| self.w(b)), out.width);
678 unsafe {
680 self.rows_of(out, &|r0, rows| {
681 layer_norm(&x[r0 * d..r0 * d + rows.len()], d, nw, nb, eps, rows);
682 });
683 }
684 }
685 Step::Gemm { a, w, b, ep, out } => {
686 let rows = self.rows(a.rows);
687 let x = unsafe { self.get(a) };
689 let y = unsafe { self.arena.slice_mut(out.off, rows * out.width) };
691 let g = Gemm {
692 x,
693 m: rows,
694 k: a.width,
695 w: &self.w[w].packed,
696 n: out.width,
697 b: b.map(|b| self.w(b)),
698 ep,
699 };
700 let scratch = self.scratch;
701 g.run(y, self.pool.threads(), |n, f| {
702 self.pool.run(n, &|i, worker| {
703 let s = unsafe { scratch.get(worker) };
706 s.resize(gemm::scratch_len(g.k, g.n), 0.0);
707 f(i, s);
708 });
709 });
710 }
711 Step::Gemm8 { a, w, b, ep, out } => {
712 let rows = self.rows(a.rows);
713 let x = unsafe { self.get(a) };
715 let y = unsafe { self.arena.slice_mut(out.off, rows * out.width) };
717 let q = self.w[w]
718 .quant
719 .as_ref()
720 .unwrap_or_else(|| unreachable!("checked when lowered"));
721 let g = QGemm { x, m: rows, w: q, b: b.map(|b| self.w(b)), ep };
722 let scratch = self.scratch;
723 g.run(y, self.pool.threads(), |n, f| {
724 self.pool.run(n, &|i, worker| {
725 let s = unsafe { scratch.get(worker) };
728 s.resize(qgemm::scratch_len(q.k), 0.0);
729 f(i, s);
730 });
731 });
732 }
733 Step::Rope { qkv, rope } => {
734 let (rope, d, cu, seq) = (&self.ropes[rope], qkv.width / 3, self.cu, self.row_seq);
735 unsafe {
737 self.rows_of(qkv, &|r0, rows| {
738 for (i, row) in rows.chunks_exact_mut(qkv.width).enumerate() {
739 let r = r0 + i;
740 let pos = r - cu[seq[r] as usize];
741 for head in row[..2 * d].as_chunks_mut::<HEAD>().0 {
742 rope.apply(head, pos);
743 }
744 }
745 });
746 }
747 }
748 Step::Attention { qkv, window, out } => {
749 let heads = out.width / HEAD;
750 let x = unsafe { self.get(qkv) };
752 let y = unsafe { self.arena.slice_mut(out.off, self.rows(out.rows) * out.width) };
754 let shared = Shared::new(y);
755 let (blocks, cu, scratch) = (self.blocks, self.cu, self.scratch);
756 self.pool.run(blocks.len() * heads, &|task, worker| {
757 let (s, q0) = blocks[task / heads];
758 let (s, q0, h) = (s as usize, q0 as usize, task % heads);
759 unsafe {
762 let p = scratch.get(worker);
763 debug_assert!(p.capacity() >= attention::scratch_len(cu[s + 1] - cu[s]));
765 attention::block(x, heads, (cu[s], cu[s + 1]), q0, h, window, p, &shared);
766 }
767 });
768 }
769 Step::GeGlu { x, out } => {
770 let (x, d) = (unsafe { self.get(x) }, out.width);
772 unsafe {
774 self.rows_of(out, &|r0, rows| {
775 geglu(&x[2 * r0 * d..2 * (r0 * d + rows.len())], d, rows);
776 });
777 }
778 }
779 Step::AddType { h, table } => {
780 let (tab, d, seq, qt) = (self.w(table), h.width, self.row_seq, self.batch.qtype);
781 unsafe {
783 self.rows_of(h, &|r0, rows| {
784 for (i, row) in rows.chunks_exact_mut(d).enumerate() {
785 let q = usize::from(qt[seq[r0 + i] as usize]);
786 row.iter_mut().zip(&tab[q * d..(q + 1) * d]).for_each(|(a, b)| *a += b);
787 }
788 });
789 }
790 }
791 Step::Gather { h, out } => {
792 let (x, d) = (unsafe { self.get(h) }, h.width);
794 let y = unsafe { self.arena.slice_mut(out.off, self.rows(out.rows) * d) };
796 let mut at = 0;
797 for s in 0..self.cu.len() - 1 {
798 for &p in &self.batch.markers[self.mcu[s]..self.mcu[s + 1]] {
799 let r = self.cu[s] + p as usize;
800 y[at * d..(at + 1) * d].copy_from_slice(&x[r * d..(r + 1) * d]);
801 at += 1;
802 }
803 }
804 }
805 Step::ActFeatures { h, logits, out } => {
806 let (x, d) = (unsafe { self.get(h) }, h.width);
808 let l = unsafe { self.get(logits) };
810 let y = unsafe { self.arena.slice_mut(out.off, self.rows(out.rows) * out.width) };
812 for (s, row) in y.chunks_exact_mut(out.width).enumerate() {
813 let (lo, hi) = (self.cu[s], self.cu[s + 1]);
814 if hi > lo {
815 row[..d].copy_from_slice(&x[lo * d..(lo + 1) * d]);
816 } else {
817 row[..d].fill(0.0);
818 }
819 row[d..].copy_from_slice(&act_features(&l[self.mcu[s]..self.mcu[s + 1]]));
820 }
821 }
822 Step::MeanPool { h, out } => {
823 let (x, d) = (unsafe { self.get(h) }, h.width);
825 let y = unsafe { self.arena.slice_mut(out.off, self.rows(out.rows) * d) };
827 for (s, row) in y.chunks_exact_mut(d).enumerate() {
828 mean_rows(&x[self.cu[s] * d..self.cu[s + 1] * d], row);
829 }
830 }
831 }
832 }
833}
834
835fn mean_rows(x: &[f32], out: &mut [f32]) {
838 const C: usize = 64;
839 let d = out.len();
840 let n = x.len() / d.max(1);
841 for c0 in (0..d).step_by(C) {
842 let c1 = (c0 + C).min(d);
843 let mut acc = [0f64; C];
844 for r in x.chunks_exact(d) {
845 acc.iter_mut().zip(&r[c0..c1]).for_each(|(a, &v)| *a += f64::from(v));
846 }
847 for (o, a) in out[c0..c1].iter_mut().zip(acc) {
848 *o = if n == 0 { 0.0 } else { (a / n as f64) as f32 };
849 }
850 }
851}
852
853fn act_features(logits: &[f32]) -> [f32; 4] {
856 let kf = logits.len().max(2) as f32;
857 if logits.is_empty() {
858 return [0.0, 0.0, 0.0, kf / 255.0];
859 }
860 let mx = logits.iter().copied().fold(f32::NEG_INFINITY, f32::max);
861 let sum: f32 = logits.iter().map(|&l| (l - mx).exp()).sum();
862 let (mut top1, mut top2, mut ent) = (f32::NEG_INFINITY, 0f32, 0f32);
863 let mut first = true;
864 for &l in logits {
865 let q = (l - mx).exp() / sum;
866 ent += q * q.max(1e-9).ln();
867 if q > top1 || first {
868 if !first {
869 top2 = top1;
870 }
871 top1 = q;
872 first = false;
873 } else if q > top2 {
874 top2 = q;
875 }
876 }
877 let ent = -ent / kf.ln();
878 [top1, top1 - top2, ent, kf / 255.0]
879}
880
881#[cfg(test)]
882mod tests {
883 use super::*;
884
885 #[test]
886 fn mean_rows_over_wide_and_empty_sequences() {
887 let d = 70;
889 let x: Vec<f32> = (0..3 * d).map(|i| i as f32 * 0.5).collect();
890 let mut out = vec![1.0; d];
891 mean_rows(&x, &mut out);
892 for (c, o) in out.iter().enumerate() {
893 assert!((o - (x[c] + x[d + c] + x[2 * d + c]) / 3.0).abs() < 1e-4, "column {c}");
894 }
895 mean_rows(&[], &mut out);
896 assert!(out.iter().all(|&o| o == 0.0));
897 }
898
899 #[test]
900 fn act_features_match_the_reference() {
901 let cases: [&[f32]; 6] =
902 [&[], &[0.3], &[1.0, 1.0], &[2.0, -1.0, 2.0], &[5.0, 0.1, -3.0, 4.9], &[-1e3, 0.0]];
903 for l in cases {
904 assert_eq!(
905 act_features(l).map(f32::to_bits),
906 crate::compat::act_features(l).map(f32::to_bits),
907 "{l:?}"
908 );
909 }
910 }
911}