1use crate::dit::{Proj, cmf_f32};
31use crate::pool::Pool;
32use cortiq_core::CmfModel;
33use std::sync::Arc;
34
35const EPS: f64 = 1e-6;
36
37pub(crate) fn rows(pool: Option<&Pool>, n: usize, f: &(dyn Fn(usize, usize) + Sync)) {
41 match pool {
42 Some(p) => p.run_rows(n, f),
43 None => f(0, n),
44 }
45}
46
47pub(crate) struct Shared(pub(crate) *mut f32);
49unsafe impl Send for Shared {}
50unsafe impl Sync for Shared {}
51impl Shared {
52 #[allow(clippy::mut_from_ref)]
54 pub(crate) unsafe fn at(&self, off: usize, len: usize) -> &mut [f32] {
55 unsafe { std::slice::from_raw_parts_mut(self.0.add(off), len) }
56 }
57}
58
59pub(crate) fn rms_plain(x: &[f32], dst: &mut [f32]) {
61 let ss = x.iter().map(|&v| (v as f64) * (v as f64)).sum::<f64>() / x.len() as f64;
62 let inv = 1.0 / (ss + EPS).sqrt();
63 for (d, &v) in dst.iter_mut().zip(x) {
64 *d = (v as f64 * inv) as f32;
65 }
66}
67
68fn rms_w(x: &mut [f32], w: &[f32]) {
70 let ss = x.iter().map(|&v| (v as f64) * (v as f64)).sum::<f64>() / x.len() as f64;
71 let inv = 1.0 / (ss + EPS).sqrt();
72 for (v, &g) in x.iter_mut().zip(w) {
73 *v = (*v as f64 * inv) as f32 * g;
74 }
75}
76
77fn silu(v: f32) -> f32 {
78 v / (1.0 + (-v).exp())
79}
80
81#[inline]
89pub(crate) fn gelu_tanh(v: f32) -> f32 {
90 const K: f32 = 0.797_884_56; 0.5 * v * (1.0 + (K * (v + 0.044715 * v * v * v)).tanh())
92}
93
94pub(crate) fn gelu_tanh_rows(x: &mut [f32], pool: Option<&Pool>) {
96 let dst = Shared(x.as_mut_ptr());
97 let n = x.len();
98 let grain = 4096usize;
99 let chunks = n.div_ceil(grain);
100 rows(pool, chunks, &|s, e| {
101 let (lo, hi) = (s * grain, (e * grain).min(n));
102 let r = unsafe { dst.at(lo, hi - lo) };
103 for v in r.iter_mut() {
104 *v = gelu_tanh(*v);
105 }
106 });
107}
108
109pub(crate) fn softmax(row: &mut [f32]) {
114 #[cfg(target_arch = "aarch64")]
115 {
116 crate::attention::softmax_row(row);
117 }
118 #[cfg(not(target_arch = "aarch64"))]
119 {
120 let mx = row.iter().cloned().fold(f32::MIN, f32::max);
121 let mut den = 0f32;
122 for r in row.iter_mut() {
123 *r = (*r - mx).exp();
124 den += *r;
125 }
126 if den > 0.0 {
127 let inv = 1.0 / den;
128 for r in row.iter_mut() {
129 *r *= inv;
130 }
131 }
132 }
133}
134
135fn layer_norm(x: &[f32], dst: &mut [f32]) {
137 let n = x.len() as f64;
138 let mean = x.iter().map(|&v| v as f64).sum::<f64>() / n;
139 let var = x.iter().map(|&v| (v as f64 - mean) * (v as f64 - mean)).sum::<f64>() / n;
140 let inv = 1.0 / (var + EPS).sqrt();
141 for (d, &v) in dst.iter_mut().zip(x) {
142 *d = ((v as f64 - mean) * inv) as f32;
143 }
144}
145
146pub(crate) struct Lin {
150 w: Proj,
151 b: Option<Vec<f32>>,
152}
153
154impl Lin {
155 pub(crate) fn load(model: &Arc<CmfModel>, name: &str, bias: bool) -> Result<Lin, String> {
156 let w = Proj::from_model(model, &format!("{name}.weight"))?;
157 let b = if bias {
158 Some(cmf_f32(model, &format!("{name}.bias"))?)
159 } else {
160 None
161 };
162 Ok(Lin { w, b })
163 }
164
165 #[cfg(target_os = "macos")]
168 fn mapped(&self) -> Option<(&Arc<CmfModel>, usize, usize, usize)> {
169 let (model, idx) = self.w.q4tp_mapped()?;
170 Some((model, idx, self.w.rows(), self.w.cols()))
171 }
172
173 pub(crate) fn add_bias(&self, out: &mut [f32], n: usize, pool: Option<&Pool>) {
175 let Some(b) = &self.b else { return };
176 let m = self.w.rows();
177 let dst = Shared(out.as_mut_ptr());
178 rows(pool, n, &|s, e| {
179 let r = unsafe { dst.at(s * m, (e - s) * m) };
180 for row in r.chunks_exact_mut(m) {
181 for (v, &bb) in row.iter_mut().zip(b) {
182 *v += bb;
183 }
184 }
185 });
186 }
187
188 pub(crate) fn apply(&self, x: &[f32], n: usize, pool: Option<&Pool>) -> Vec<f32> {
189 let m = self.w.rows();
190 let cols = self.w.cols();
191 let t_alloc = std::time::Instant::now();
192 let mut out = vec![0f32; n * m];
193 attn_prof::ALLOC.fetch_add(
194 t_alloc.elapsed().as_micros() as u64,
195 std::sync::atomic::Ordering::Relaxed,
196 );
197 let t_mm = std::time::Instant::now();
198 let per_row = cols * 4;
203 let chunk = (0x1000_0000usize / per_row.max(1)).max(1);
204 let mut done = 0usize;
205 while done < n {
206 let take = chunk.min(n - done);
207 self.w.matmat(
208 &x[done * cols..(done + take) * cols],
209 take,
210 &mut out[done * m..(done + take) * m],
211 pool,
212 );
213 done += take;
214 }
215 attn_prof::MATMAT.fetch_add(
216 t_mm.elapsed().as_micros() as u64,
217 std::sync::atomic::Ordering::Relaxed,
218 );
219 if let Some(b) = &self.b {
220 let dst = Shared(out.as_mut_ptr());
221 rows(pool, n, &|s, e| {
222 let r = unsafe { dst.at(s * m, (e - s) * m) };
223 for row in r.chunks_exact_mut(m) {
224 for (v, &bb) in row.iter_mut().zip(b) {
225 *v += bb;
226 }
227 }
228 });
229 }
230 out
231 }
232}
233
234pub struct Rope {
238 cos: Vec<f32>,
239 sin: Vec<f32>,
240 heads: usize,
241 half: usize,
242}
243
244impl Rope {
245 pub fn build(positions: &[Vec<f64>], max_pos: &[f64], dim: usize, heads: usize, theta: f64) -> Rope {
249 let ndim = max_pos.len();
250 let count = dim / (2 * ndim);
251 let idx: Vec<f64> = (0..count)
253 .map(|j| {
254 let e = if count > 1 { j as f64 / (count - 1) as f64 } else { 0.0 };
255 theta.powf(e) * std::f64::consts::PI / 2.0
256 })
257 .collect();
258 let n = positions.len();
259 let half = dim / 2;
260 let pad = half - count * ndim;
261 let mut cos = vec![0f32; n * half];
262 let mut sin = vec![0f32; n * half];
263 for (t, p) in positions.iter().enumerate() {
264 let base = t * half;
265 for i in 0..pad {
266 cos[base + i] = 1.0;
267 }
268 for (j, &ind) in idx.iter().enumerate() {
269 for (d, &mp) in max_pos.iter().enumerate() {
270 let f = ind * (p[d] / mp * 2.0 - 1.0);
271 let o = base + pad + j * ndim + d;
272 cos[o] = f.cos() as f32;
273 sin[o] = f.sin() as f32;
274 }
275 }
276 }
277 Rope { cos, sin, heads, half: half / heads }
278 }
279
280 fn apply_row(&self, t: usize, row: &mut [f32]) {
282 let dh = self.half * 2;
283 let stride = self.heads * self.half;
284 for h in 0..self.heads {
285 let off = t * stride + h * self.half;
286 let (c, s) = (&self.cos[off..off + self.half], &self.sin[off..off + self.half]);
287 let v = &mut row[h * dh..(h + 1) * dh];
288 for i in 0..self.half {
289 let (a, b) = (v[i], v[i + self.half]);
290 v[i] = a * c[i] - b * s[i];
291 v[i + self.half] = b * c[i] + a * s[i];
292 }
293 }
294 }
295}
296
297
298pub(crate) mod attn_prof {
302 use std::sync::atomic::{AtomicU64, Ordering::Relaxed};
303 pub static PROJ: AtomicU64 = AtomicU64::new(0);
304 pub static NORM: AtomicU64 = AtomicU64::new(0);
305 pub static GATHER: AtomicU64 = AtomicU64::new(0);
306 pub static SCORE: AtomicU64 = AtomicU64::new(0);
307 pub static SOFT: AtomicU64 = AtomicU64::new(0);
308 pub static VALUE: AtomicU64 = AtomicU64::new(0);
309 pub static OUT: AtomicU64 = AtomicU64::new(0);
310 pub static ALLOC: AtomicU64 = AtomicU64::new(0);
311 pub static MATMAT: AtomicU64 = AtomicU64::new(0);
312
313 pub fn add(c: &AtomicU64, t: std::time::Instant) -> std::time::Instant {
314 c.fetch_add(t.elapsed().as_micros() as u64, Relaxed);
315 std::time::Instant::now()
316 }
317
318 pub fn report() -> String {
319 let s = |c: &AtomicU64| c.swap(0, Relaxed) as f64 / 1e6;
320 format!(
321 "proj {:.2}s qk-norm+rope {:.2}s gather {:.2}s scores {:.2}s softmax {:.2}s values {:.2}s out {:.2}s [linear: alloc {:.2}s matmat {:.2}s]",
322 s(&PROJ), s(&NORM), s(&GATHER), s(&SCORE), s(&SOFT), s(&VALUE), s(&OUT),
323 s(&ALLOC), s(&MATMAT)
324 )
325 }
326}
327
328pub(crate) struct Attn {
331 q: Lin,
332 k: Lin,
333 v: Lin,
334 o: Lin,
335 q_norm: Vec<f32>,
336 k_norm: Vec<f32>,
337 gate: Option<Lin>,
338 heads: usize,
339 dh: usize,
340}
341
342impl Attn {
343 pub(crate) fn load(model: &Arc<CmfModel>, p: &str, heads: usize, dh: usize) -> Result<Attn, String> {
344 Ok(Attn {
345 q: Lin::load(model, &format!("{p}.to_q"), true)?,
346 k: Lin::load(model, &format!("{p}.to_k"), true)?,
347 v: Lin::load(model, &format!("{p}.to_v"), true)?,
348 o: Lin::load(model, &format!("{p}.to_out.0"), true)?,
349 q_norm: cmf_f32(model, &format!("{p}.q_norm.weight"))?,
350 k_norm: cmf_f32(model, &format!("{p}.k_norm.weight"))?,
351 gate: match model.tensor(&format!("{p}.to_gate_logits.weight")) {
352 Some(_) => Some(Lin::load(model, &format!("{p}.to_gate_logits"), true)?),
353 None => None,
354 },
355 heads,
356 dh,
357 })
358 }
359
360 #[cfg(target_os = "macos")]
365 fn fused_qkv(
366 &self,
367 x: &[f32],
368 n: usize,
369 ctx: &[f32],
370 m: usize,
371 pool: Option<&Pool>,
372 ) -> Option<(Vec<f32>, Vec<f32>, Vec<f32>)> {
373 if !std::ptr::eq(x.as_ptr(), ctx.as_ptr()) || n != m {
374 return None;
375 }
376 if !crate::gpu::enabled_here() || crate::gpu::mm_killed() {
377 return None;
378 }
379 let (qw, kw, vw) = (self.q.mapped()?, self.k.mapped()?, self.v.mapped()?);
380 if !Arc::ptr_eq(qw.0, kw.0) || !Arc::ptr_eq(qw.0, vw.0) {
381 return None;
382 }
383 let jobs = [
384 crate::gpu_metal::MmJob { idx: qw.1, rows: qw.2, cols: qw.3 },
385 crate::gpu_metal::MmJob { idx: kw.1, rows: kw.2, cols: kw.3 },
386 crate::gpu_metal::MmJob { idx: vw.1, rows: vw.2, cols: vw.3 },
387 ];
388 if n * jobs[0].rows * jobs[0].cols < 128_000_000 || n < 32 {
389 return None;
390 }
391 let mut oq = vec![0f32; n * jobs[0].rows];
392 let mut ok = vec![0f32; n * jobs[1].rows];
393 let mut ov = vec![0f32; n * jobs[2].rows];
394 let done = {
395 let mut outs: [&mut [f32]; 3] = [&mut oq, &mut ok, &mut ov];
396 crate::gpu_metal::q4tp_matmat_many(qw.0, &jobs, x, n, &mut outs)
397 };
398 if !done {
399 return None;
400 }
401 self.q.add_bias(&mut oq, n, pool);
402 self.k.add_bias(&mut ok, n, pool);
403 self.v.add_bias(&mut ov, n, pool);
404 Some((oq, ok, ov))
405 }
406
407 #[cfg(target_os = "macos")]
410 fn fused_kv(&self, ctx: &[f32], m: usize) -> Option<(Vec<f32>, Vec<f32>)> {
411 if !crate::gpu::enabled_here() || crate::gpu::mm_killed() {
412 return None;
413 }
414 let (kw, vw) = (self.k.mapped()?, self.v.mapped()?);
415 if !Arc::ptr_eq(kw.0, vw.0) {
416 return None;
417 }
418 let jobs = [
419 crate::gpu_metal::MmJob { idx: kw.1, rows: kw.2, cols: kw.3 },
420 crate::gpu_metal::MmJob { idx: vw.1, rows: vw.2, cols: vw.3 },
421 ];
422 if m < 32 || m * jobs[0].rows * jobs[0].cols < 128_000_000 {
423 return None;
424 }
425 let mut ok = vec![0f32; m * jobs[0].rows];
426 let mut ov = vec![0f32; m * jobs[1].rows];
427 let done = {
428 let mut outs: [&mut [f32]; 2] = [&mut ok, &mut ov];
429 crate::gpu_metal::q4tp_matmat_many(kw.0, &jobs, ctx, m, &mut outs)
430 };
431 if !done {
432 return None;
433 }
434 self.k.add_bias(&mut ok, m, None);
435 self.v.add_bias(&mut ov, m, None);
436 Some((ok, ov))
437 }
438
439 #[cfg(not(target_os = "macos"))]
440 fn fused_kv(&self, _ctx: &[f32], _m: usize) -> Option<(Vec<f32>, Vec<f32>)> {
441 None
442 }
443
444 #[cfg(not(target_os = "macos"))]
445 fn fused_qkv(
446 &self,
447 _x: &[f32],
448 _n: usize,
449 _ctx: &[f32],
450 _m: usize,
451 _pool: Option<&Pool>,
452 ) -> Option<(Vec<f32>, Vec<f32>, Vec<f32>)> {
453 None
454 }
455
456 #[allow(clippy::too_many_arguments)]
459 pub(crate) fn forward(
460 &self,
461 x: &[f32],
462 n: usize,
463 ctx: &[f32],
464 m: usize,
465 pe_q: Option<&Rope>,
466 pe_k: Option<&Rope>,
467 mask: Option<&[f32]>,
468 pool: Option<&Pool>,
469 ) -> Vec<f32> {
470 let inner = self.heads * self.dh;
471 let prof = std::env::var("CMF_LTX_PROF").is_ok();
472 let mut t = std::time::Instant::now();
473 let (mut q, mut k, v) = match self.fused_qkv(x, n, ctx, m, pool) {
479 Some(t) => t,
480 None => match self.fused_kv(ctx, m) {
484 Some((k, v)) => (self.q.apply(x, n, pool), k, v),
485 None => (
486 self.q.apply(x, n, pool),
487 self.k.apply(ctx, m, pool),
488 self.v.apply(ctx, m, pool),
489 ),
490 },
491 };
492 if prof {
493 t = attn_prof::add(&attn_prof::PROJ, t);
494 }
495
496 let qn = Shared(q.as_mut_ptr());
497 rows(pool, n, &|s, e| {
498 let r = unsafe { qn.at(s * inner, (e - s) * inner) };
499 for (i, row) in r.chunks_exact_mut(inner).enumerate() {
500 rms_w(row, &self.q_norm);
501 if let Some(pe) = pe_q {
502 pe.apply_row(s + i, row);
503 }
504 }
505 });
506 let kn = Shared(k.as_mut_ptr());
507 rows(pool, m, &|s, e| {
508 let r = unsafe { kn.at(s * inner, (e - s) * inner) };
509 for (i, row) in r.chunks_exact_mut(inner).enumerate() {
510 rms_w(row, &self.k_norm);
511 if let Some(pe) = pe_k {
512 pe.apply_row(s + i, row);
513 }
514 }
515 });
516
517 if prof {
523 t = attn_prof::add(&attn_prof::NORM, t);
524 }
525 let mut out = vec![0f32; n * inner];
526 let scale = 1.0 / (self.dh as f32).sqrt();
527 let dh = self.dh;
528 let mut qh = vec![0f32; n * dh];
529 let mut kh = vec![0f32; m * dh];
530 let mut vh = vec![0f32; m * dh];
531 let mut sc = vec![0f32; n * m];
532 let mut oh = vec![0f32; n * dh];
533 for h in 0..self.heads {
534 for i in 0..n {
535 qh[i * dh..(i + 1) * dh].copy_from_slice(&q[i * inner + h * dh..][..dh]);
536 }
537 for j in 0..m {
538 kh[j * dh..(j + 1) * dh].copy_from_slice(&k[j * inner + h * dh..][..dh]);
539 vh[j * dh..(j + 1) * dh].copy_from_slice(&v[j * inner + h * dh..][..dh]);
540 }
541 if prof {
542 t = attn_prof::add(&attn_prof::GATHER, t);
543 }
544 crate::fcd_ops::gemm_nt(&qh, &kh, &mut sc, n, dh, m, pool);
545 if prof {
546 t = attn_prof::add(&attn_prof::SCORE, t);
547 }
548 let sp = Shared(sc.as_mut_ptr());
549 rows(pool, n, &|s, e| {
550 let r = unsafe { sp.at(s * m, (e - s) * m) };
551 for row in r.chunks_exact_mut(m) {
552 for (x, j) in row.iter_mut().zip(0..m) {
553 *x = *x * scale + mask.map_or(0.0, |mk| mk[j]);
554 }
555 softmax(row);
556 }
557 });
558 if prof {
559 t = attn_prof::add(&attn_prof::SOFT, t);
560 }
561 oh.iter_mut().for_each(|x| *x = 0.0);
562 crate::fcd_ops::gemm_dx(&sc, &vh, &mut oh, n, dh, m, pool);
563 if prof {
564 t = attn_prof::add(&attn_prof::VALUE, t);
565 }
566 for i in 0..n {
567 out[i * inner + h * dh..i * inner + (h + 1) * dh]
568 .copy_from_slice(&oh[i * dh..(i + 1) * dh]);
569 }
570 if prof {
571 t = attn_prof::add(&attn_prof::GATHER, t);
572 }
573 }
574
575 if let Some(g) = &self.gate {
576 let logits = g.apply(x, n, pool);
577 let h = self.heads;
578 let dst = Shared(out.as_mut_ptr());
579 rows(pool, n, &|s, e| {
580 let r = unsafe { dst.at(s * inner, (e - s) * inner) };
581 for (i, row) in r.chunks_exact_mut(inner).enumerate() {
582 for hh in 0..h {
583 let gate = 2.0 / (1.0 + (-logits[(s + i) * h + hh]).exp());
584 for d in row[hh * self.dh..(hh + 1) * self.dh].iter_mut() {
585 *d *= gate;
586 }
587 }
588 }
589 });
590 }
591 let r = self.o.apply(&out, n, pool);
592 if prof {
593 attn_prof::add(&attn_prof::OUT, t);
594 }
595 r
596 }
597}
598
599struct AdaLn {
604 l1: Lin,
605 l2: Lin,
606 lin: Lin,
607 dim: usize,
608}
609
610impl AdaLn {
611 fn load(model: &Arc<CmfModel>, p: &str, dim: usize) -> Result<AdaLn, String> {
612 Ok(AdaLn {
613 l1: Lin::load(model, &format!("{p}.emb.timestep_embedder.linear_1"), true)?,
614 l2: Lin::load(model, &format!("{p}.emb.timestep_embedder.linear_2"), true)?,
615 lin: Lin::load(model, &format!("{p}.linear"), true)?,
616 dim,
617 })
618 }
619
620 fn forward(&self, t: &[f32], pool: Option<&Pool>) -> (Vec<f32>, Vec<f32>) {
622 let n = t.len();
623 let half = 128usize;
626 let mut proj = vec![0f32; n * 256];
627 let ws: Vec<f64> = (0..half)
628 .map(|j| (-(10000f64).ln() * j as f64 / half as f64).exp())
629 .collect();
630 for (i, &tv) in t.iter().enumerate() {
631 for (j, &w) in ws.iter().enumerate() {
632 let a = tv as f64 * w;
633 proj[i * 256 + j] = a.cos() as f32;
634 proj[i * 256 + half + j] = a.sin() as f32;
635 }
636 }
637 let mut h = self.l1.apply(&proj, n, pool);
638 for v in h.iter_mut() {
639 *v = silu(*v);
640 }
641 let embedded = self.l2.apply(&h, n, pool);
642 let mut act = embedded.clone();
643 for v in act.iter_mut() {
644 *v = silu(*v);
645 }
646 (self.lin.apply(&act, n, pool), embedded)
647 }
648}
649
650struct TsTable {
655 vals: Vec<f32>,
656 emb: Vec<f32>,
657 idx: Vec<usize>,
658 width: usize,
659 edim: usize,
660}
661
662impl TsTable {
663 fn build(a: &AdaLn, ts: &[f32], scale: f64, pool: Option<&Pool>) -> TsTable {
664 let mut vals: Vec<f32> = Vec::new();
665 let mut idx = Vec::with_capacity(ts.len());
666 for &t in ts {
667 match vals.iter().position(|&v| v.to_bits() == t.to_bits()) {
668 Some(i) => idx.push(i),
669 None => {
670 vals.push(t);
671 idx.push(vals.len() - 1);
672 }
673 }
674 }
675 let scaled: Vec<f32> = vals.iter().map(|&v| (v as f64 * scale) as f32).collect();
676 let (v, e) = a.forward(&scaled, pool);
677 let width = v.len() / scaled.len().max(1);
678 TsTable { vals: v, emb: e, idx, width, edim: a.dim }
679 }
680
681 fn distinct(&self) -> usize {
682 self.vals.len() / self.width.max(1)
683 }
684
685 fn row(&self, r: usize) -> &[f32] {
686 &self.vals[r * self.width..(r + 1) * self.width]
687 }
688
689 fn emb_row(&self, r: usize) -> &[f32] {
690 &self.emb[r * self.edim..(r + 1) * self.edim]
691 }
692
693 fn triples(&self, table: &[f32], dim: usize, off: usize) -> Vec<[Vec<f32>; 3]> {
697 (0..self.distinct())
698 .map(|r| {
699 let v = self.row(r);
700 std::array::from_fn(|j| {
701 let o = (off + j) * dim;
702 (0..dim).map(|d| table[o + d] + v[o + d]).collect()
703 })
704 })
705 .collect()
706 }
707
708 fn pairs(&self, table: &[f32], dim: usize, off: usize) -> Vec<[Vec<f32>; 2]> {
711 (0..self.distinct())
712 .map(|r| {
713 let v = self.row(r);
714 std::array::from_fn(|j| {
715 let o = (off + j) * dim;
716 (0..dim).map(|d| table[o + d] + v[o + d]).collect()
717 })
718 })
719 .collect()
720 }
721}
722
723
724fn ada_zero_rows(
728 x: &[f32],
729 out: &mut [f32],
730 n: usize,
731 dim: usize,
732 mods: &[[Vec<f32>; 3]],
733 idx: &[usize],
734 pool: Option<&Pool>,
735) {
736 let dst = Shared(out.as_mut_ptr());
737 rows(pool, n, &|s, e| {
738 let r = unsafe { dst.at(s * dim, (e - s) * dim) };
739 for (row, i) in r.chunks_exact_mut(dim).zip(s..e) {
740 let md = &mods[idx[i]];
741 rms_plain(&x[i * dim..(i + 1) * dim], row);
742 for d in 0..dim {
743 row[d] = row[d] * (1.0 + md[1][d]) + md[0][d];
744 }
745 }
746 });
747}
748
749fn add_gated(
751 x: &mut [f32],
752 y: &[f32],
753 n: usize,
754 dim: usize,
755 mods: &[[Vec<f32>; 3]],
756 idx: &[usize],
757 pool: Option<&Pool>,
758) {
759 let dst = Shared(x.as_mut_ptr());
760 rows(pool, n, &|s, e| {
761 let r = unsafe { dst.at(s * dim, (e - s) * dim) };
762 for (row, i) in r.chunks_exact_mut(dim).zip(s..e) {
763 let g = &mods[idx[i]][2];
764 for d in 0..dim {
765 row[d] += y[i * dim + d] * g[d];
766 }
767 }
768 });
769}
770
771fn post_sa_rows(
774 x: &mut [f32],
775 y: &[f32],
776 normed: &mut [f32],
777 n: usize,
778 dim: usize,
779 mods: &[[Vec<f32>; 3]],
780 idx: &[usize],
781 pool: Option<&Pool>,
782) {
783 let a = Shared(x.as_mut_ptr());
784 let b = Shared(normed.as_mut_ptr());
785 rows(pool, n, &|s, e| {
786 let xr = unsafe { a.at(s * dim, (e - s) * dim) };
787 let nr = unsafe { b.at(s * dim, (e - s) * dim) };
788 for ((row, nrow), i) in xr.chunks_exact_mut(dim).zip(nr.chunks_exact_mut(dim)).zip(s..e) {
789 let g = &mods[idx[i]][2];
790 for d in 0..dim {
791 row[d] += y[i * dim + d] * g[d];
792 }
793 rms_plain(row, nrow);
794 }
795 });
796}
797
798fn add_scaled(x: &mut [f32], y: &[f32], n: usize, dim: usize, g: &[f32], pool: Option<&Pool>) {
800 let dst = Shared(x.as_mut_ptr());
801 rows(pool, n, &|s, e| {
802 let r = unsafe { dst.at(s * dim, (e - s) * dim) };
803 for (row, i) in r.chunks_exact_mut(dim).zip(s..e) {
804 for d in 0..dim {
805 row[d] += y[i * dim + d] * g[d];
806 }
807 }
808 });
809}
810
811fn affine_rows(
813 x: &[f32],
814 out: &mut [f32],
815 n: usize,
816 dim: usize,
817 mods: &[[Vec<f32>; 3]],
818 idx: &[usize],
819 pool: Option<&Pool>,
820) {
821 let dst = Shared(out.as_mut_ptr());
822 rows(pool, n, &|s, e| {
823 let r = unsafe { dst.at(s * dim, (e - s) * dim) };
824 for (row, i) in r.chunks_exact_mut(dim).zip(s..e) {
825 let md = &mods[idx[i]];
826 for d in 0..dim {
827 row[d] = x[i * dim + d] * (1.0 + md[1][d]) + md[0][d];
828 }
829 }
830 });
831}
832
833
834#[derive(Default)]
837struct Prof {
838 on: bool,
839 t: [f64; 6],
840}
841
842const P_ADALN: usize = 0;
843const P_SELF: usize = 1;
844const P_CROSS: usize = 2;
845const P_FUSE: usize = 3;
846const P_FF: usize = 4;
847const P_MOD: usize = 5;
848
849impl Prof {
850 fn new() -> Prof {
851 Prof { on: std::env::var("CMF_LTX_PROF").is_ok(), t: [0.0; 6] }
852 }
853 #[inline]
854 fn tick(&mut self, slot: usize, at: std::time::Instant) -> std::time::Instant {
855 if self.on {
856 self.t[slot] += at.elapsed().as_secs_f64();
857 return std::time::Instant::now();
858 }
859 at
860 }
861 fn report(&self) {
862 if !self.on {
863 return;
864 }
865 #[cfg(target_os = "macos")]
868 {
869 use std::sync::atomic::Ordering::Relaxed;
870 let n = crate::gpu_metal::MM_N.swap(0, Relaxed);
871 if n > 0 {
872 let us = |a: &std::sync::atomic::AtomicU64| a.swap(0, Relaxed) as f64 / 1e6;
873 println!(
874 " q4tp on device: {n} calls, upload {:.2}s kernel {:.2}s download {:.2}s",
875 us(&crate::gpu_metal::MM_UP),
876 us(&crate::gpu_metal::MM_GPU),
877 us(&crate::gpu_metal::MM_DN),
878 );
879 }
880 }
881 println!(" attention: {}", attn_prof::report());
882 let names = ["adaln", "self-attn", "cross-attn", "a<->v", "ffn", "modulate"];
883 let total: f64 = self.t.iter().sum();
884 let parts: Vec<String> = names
885 .iter()
886 .zip(&self.t)
887 .map(|(n, v)| format!("{n} {v:.1}s ({:.0}%)", 100.0 * v / total.max(1e-9)))
888 .collect();
889 println!(" profile: {}", parts.join(" "));
890 }
891}
892
893struct Stream {
896 attn1: Attn,
897 attn2: Attn,
898 ff_in: Lin,
899 ff_out: Lin,
900 sst: Vec<f32>, prompt_sst: Vec<f32>, }
903
904impl Stream {
905 fn load(
906 model: &Arc<CmfModel>,
907 p: &str,
908 prefix: &str,
909 heads: usize,
910 dh: usize,
911 ff_bias: bool,
912 ) -> Result<Stream, String> {
913 let a = |n: &str| format!("{p}.{prefix}{n}");
914 Ok(Stream {
915 attn1: Attn::load(model, &a("attn1"), heads, dh)?,
916 attn2: Attn::load(model, &a("attn2"), heads, dh)?,
917 ff_in: Lin::load(model, &a("ff.net.0.proj"), ff_bias)?,
918 ff_out: Lin::load(model, &a("ff.net.2"), ff_bias)?,
919 sst: cmf_f32(model, &a("scale_shift_table"))?,
920 prompt_sst: cmf_f32(model, &a("prompt_scale_shift_table"))?,
921 })
922 }
923
924 fn ff(&self, x: &[f32], n: usize, pool: Option<&Pool>) -> Vec<f32> {
925 let mut h = self.ff_in.apply(x, n, pool);
926 gelu_tanh_rows(&mut h, pool);
927 self.ff_out.apply(&h, n, pool)
928 }
929}
930
931struct Block {
932 video: Stream,
933 audio: Stream,
934 a2v: Attn,
935 v2a: Attn,
936 sst_a2v_video: Vec<f32>, sst_a2v_audio: Vec<f32>, }
939
940pub struct StreamInput {
945 pub latent: Vec<f32>,
947 pub tokens: usize,
948 pub timesteps: Vec<f32>,
950 pub positions: Vec<Vec<f64>>,
952 pub context: Vec<f32>,
954 pub ctx_len: usize,
955 pub context_mask: Vec<f32>,
957 pub keyframes: Vec<f32>,
959 pub sigma: f32,
961}
962
963pub struct LtxDit {
964 model: Arc<CmfModel>,
965 blocks: Vec<Block>,
966 patchify: Lin,
967 a_patchify: Lin,
968 keyframes_emb: Option<Vec<f32>>,
969 adaln: AdaLn,
970 a_adaln: AdaLn,
971 prompt_adaln: AdaLn,
972 a_prompt_adaln: AdaLn,
973 av_v_ss: AdaLn,
974 av_a_ss: AdaLn,
975 av_a2v_gate: AdaLn,
976 av_v2a_gate: AdaLn,
977 proj_out: Lin,
978 a_proj_out: Lin,
979 sst_out: Vec<f32>,
980 a_sst_out: Vec<f32>,
981 pub heads: usize,
982 pub dh: usize,
983 pub a_heads: usize,
984 pub a_dh: usize,
985 pub max_pos: Vec<f64>,
986 pub a_max_pos: Vec<f64>,
987 pub cross_max_pos: f64,
988 pub theta: f64,
989 pub t_scale: f64,
990 pub av_t_scale: f64,
991 pub audio_cross_dim: usize,
992}
993
994impl LtxDit {
995 pub fn from_cmf(model: &Arc<CmfModel>) -> Result<LtxDit, String> {
996 let cfg_bytes = ["ltx.config_json", "dit.config_json"]
997 .iter()
998 .find_map(|n| model.tensor(n).map(|e| model.entry_bytes(e)))
999 .ok_or("container carries no ltx.config_json")?;
1000 let cfg: serde_json::Value =
1001 serde_json::from_slice(cfg_bytes).map_err(|e| format!("ltx.config_json: {e}"))?;
1002 let t = cfg.get("transformer").unwrap_or(&cfg).clone();
1003 let g = |k: &str, d: f64| t.get(k).and_then(|v| v.as_f64()).unwrap_or(d);
1004 let heads = g("num_attention_heads", 32.0) as usize;
1005 let dh = g("attention_head_dim", 128.0) as usize;
1006 let a_heads = g("audio_num_attention_heads", 32.0) as usize;
1007 let a_dh = g("audio_attention_head_dim", 64.0) as usize;
1008 let n_layers = g("num_layers", 48.0) as usize;
1009 let ff_bias = t.get("ff_bias").and_then(|v| v.as_bool()).unwrap_or(true);
1010 let a_ff_bias = t.get("audio_ff_bias").and_then(|v| v.as_bool()).unwrap_or(true);
1011 let arr = |k: &str, d: Vec<f64>| -> Vec<f64> {
1012 t.get(k)
1013 .and_then(|v| v.as_array())
1014 .map(|a| a.iter().filter_map(|x| x.as_f64()).collect())
1015 .unwrap_or(d)
1016 };
1017 let max_pos = arr("positional_embedding_max_pos", vec![20.0, 2048.0, 2048.0]);
1018 let a_max_pos = arr("audio_positional_embedding_max_pos", vec![20.0]);
1019 let cross_max_pos = max_pos[0].max(a_max_pos[0]);
1020 let dim = heads * dh;
1021 let a_dim = a_heads * a_dh;
1022
1023 let mut blocks = Vec::with_capacity(n_layers);
1024 for i in 0..n_layers {
1025 let p = format!("dit.transformer_blocks.{i}");
1026 blocks.push(Block {
1027 video: Stream::load(model, &p, "", heads, dh, ff_bias)?,
1028 audio: Stream::load(model, &p, "audio_", a_heads, a_dh, a_ff_bias)?,
1029 a2v: Attn::load(model, &format!("{p}.audio_to_video_attn"), a_heads, a_dh)?,
1030 v2a: Attn::load(model, &format!("{p}.video_to_audio_attn"), a_heads, a_dh)?,
1031 sst_a2v_video: cmf_f32(model, &format!("{p}.scale_shift_table_a2v_ca_video"))?,
1032 sst_a2v_audio: cmf_f32(model, &format!("{p}.scale_shift_table_a2v_ca_audio"))?,
1033 });
1034 }
1035 let _ = (dim, a_dim);
1036 Ok(LtxDit {
1037 blocks,
1038 patchify: Lin::load(model, "dit.patchify_proj", true)?,
1039 a_patchify: Lin::load(model, "dit.audio_patchify_proj", true)?,
1040 keyframes_emb: match model.tensor("dit.keyframes_abs_pos_embedding") {
1041 Some(_) => Some(cmf_f32(model, "dit.keyframes_abs_pos_embedding")?),
1042 None => None,
1043 },
1044 adaln: AdaLn::load(model, "dit.adaln_single", dim)?,
1045 a_adaln: AdaLn::load(model, "dit.audio_adaln_single", a_dim)?,
1046 prompt_adaln: AdaLn::load(model, "dit.prompt_adaln_single", dim)?,
1047 a_prompt_adaln: AdaLn::load(model, "dit.audio_prompt_adaln_single", a_dim)?,
1048 av_v_ss: AdaLn::load(model, "dit.av_ca_video_scale_shift_adaln_single", dim)?,
1049 av_a_ss: AdaLn::load(model, "dit.av_ca_audio_scale_shift_adaln_single", a_dim)?,
1050 av_a2v_gate: AdaLn::load(model, "dit.av_ca_a2v_gate_adaln_single", dim)?,
1051 av_v2a_gate: AdaLn::load(model, "dit.av_ca_v2a_gate_adaln_single", a_dim)?,
1052 proj_out: Lin::load(model, "dit.proj_out", true)?,
1053 a_proj_out: Lin::load(model, "dit.audio_proj_out", true)?,
1054 sst_out: cmf_f32(model, "dit.scale_shift_table")?,
1055 a_sst_out: cmf_f32(model, "dit.audio_scale_shift_table")?,
1056 heads,
1057 dh,
1058 a_heads,
1059 a_dh,
1060 max_pos,
1061 a_max_pos,
1062 cross_max_pos,
1063 theta: g("positional_embedding_theta", 10000.0),
1064 t_scale: g("timestep_scale_multiplier", 1000.0),
1065 av_t_scale: g("av_ca_timestep_scale_multiplier", 1.0),
1066 audio_cross_dim: g("audio_cross_attention_dim", 2048.0) as usize,
1067 model: model.clone(),
1068 })
1069 }
1070
1071 pub fn blocks(&self) -> usize {
1072 self.blocks.len()
1073 }
1074
1075 pub fn container(&self) -> &Arc<CmfModel> {
1076 &self.model
1077 }
1078
1079 pub fn forward(
1081 &self,
1082 video: &StreamInput,
1083 audio: &StreamInput,
1084 pool: Option<&Pool>,
1085 ) -> (Vec<f32>, Vec<f32>) {
1086 self.forward_traced(video, audio, pool, &mut |_, _| {})
1087 }
1088
1089 pub fn forward_traced(
1090 &self,
1091 video: &StreamInput,
1092 audio: &StreamInput,
1093 pool: Option<&Pool>,
1094 trace: &mut dyn FnMut(&str, &[f32]),
1095 ) -> (Vec<f32>, Vec<f32>) {
1096 let _trust = crate::gpu::trust_gpu();
1100 let dim = self.heads * self.dh;
1101 let a_dim = self.a_heads * self.a_dh;
1102 let (n, m) = (video.tokens, audio.tokens);
1103
1104 let mut vx = self.patchify.apply(&video.latent, n, pool);
1106 if let Some(emb) = &self.keyframes_emb {
1107 for i in 0..n {
1108 if video.keyframes.get(i).copied().unwrap_or(0.0) > 0.0 {
1109 for (d, &e) in vx[i * dim..(i + 1) * dim].iter_mut().zip(emb) {
1110 *d += e;
1111 }
1112 }
1113 }
1114 }
1115 let mut ax = self.a_patchify.apply(&audio.latent, m, pool);
1116 trace("v.args.x", &vx);
1117 trace("a.args.x", &ax);
1118
1119 let vt = TsTable::build(&self.adaln, &video.timesteps, self.t_scale, pool);
1121 let at = TsTable::build(&self.a_adaln, &audio.timesteps, self.t_scale, pool);
1122 let vpt = TsTable::build(&self.prompt_adaln, &[video.sigma], self.t_scale, pool);
1123 let apt = TsTable::build(&self.a_prompt_adaln, &[audio.sigma], self.t_scale, pool);
1124 let vxs = TsTable::build(&self.av_v_ss, &video.timesteps, self.t_scale, pool);
1125 let axs = TsTable::build(&self.av_a_ss, &audio.timesteps, self.t_scale, pool);
1126 let vgt = TsTable::build(&self.av_a2v_gate, &[audio.sigma], self.av_t_scale, pool);
1129 let agt = TsTable::build(&self.av_v2a_gate, &[video.sigma], self.av_t_scale, pool);
1130
1131 let v_pe = Rope::build(&video.positions, &self.max_pos, dim, self.heads, self.theta);
1133 let a_pe = Rope::build(&audio.positions, &self.a_max_pos, a_dim, self.a_heads, self.theta);
1134 let time_only = |p: &[Vec<f64>]| p.iter().map(|r| vec![r[0]]).collect::<Vec<_>>();
1135 let v_xpe = Rope::build(
1136 &time_only(&video.positions),
1137 &[self.cross_max_pos],
1138 self.audio_cross_dim,
1139 self.heads,
1140 self.theta,
1141 );
1142 let a_xpe = Rope::build(
1143 &time_only(&audio.positions),
1144 &[self.cross_max_pos],
1145 self.audio_cross_dim,
1146 self.a_heads,
1147 self.theta,
1148 );
1149
1150 let vmask = (!video.context_mask.is_empty()).then_some(&video.context_mask[..]);
1151 let amask = (!audio.context_mask.is_empty()).then_some(&audio.context_mask[..]);
1152
1153 let mut prof = Prof::new();
1154 for (bi, blk) in self.blocks.iter().enumerate() {
1155 let mut pt = std::time::Instant::now();
1156 let v_msa = vt.triples(&blk.video.sst, dim, 0);
1157 let v_ca = vt.triples(&blk.video.sst, dim, 6);
1158 let v_mlp = vt.triples(&blk.video.sst, dim, 3);
1159 let a_msa = at.triples(&blk.audio.sst, a_dim, 0);
1160 let a_ca = at.triples(&blk.audio.sst, a_dim, 6);
1161 let a_mlp = at.triples(&blk.audio.sst, a_dim, 3);
1162
1163 pt = prof.tick(P_ADALN, pt);
1165 let mut vnorm = vec![0f32; n * dim];
1166 ada_zero_rows(&vx, &mut vnorm, n, dim, &v_msa, &vt.idx, pool);
1167 pt = prof.tick(P_MOD, pt);
1168 if bi == 0 {
1169 trace("v.b0.sa.in", &vnorm);
1170 }
1171 let vsa = blk
1172 .video
1173 .attn1
1174 .forward(&vnorm, n, &vnorm, n, Some(&v_pe), Some(&v_pe), None, pool);
1175 if bi == 0 {
1176 trace("v.b0.sa.out", &vsa);
1177 }
1178 pt = prof.tick(P_SELF, pt);
1179 let mut vnormed = vec![0f32; n * dim];
1180 post_sa_rows(&mut vx, &vsa, &mut vnormed, n, dim, &v_msa, &vt.idx, pool);
1181 let mut vq = vec![0f32; n * dim];
1182 affine_rows(&vnormed, &mut vq, n, dim, &v_ca, &vt.idx, pool);
1183 let vctx = modulate_kv(&video.context, video.ctx_len, dim, &blk.video.prompt_sst, vpt.row(0), pool);
1184 let vca = blk.video.attn2.forward(&vq, n, &vctx, video.ctx_len, None, None, vmask, pool);
1185 if bi == 0 {
1186 trace("v.b0.ca.in", &vq);
1187 trace("v.b0.ca.ctx", &vctx);
1188 trace("v.b0.ca.out", &vca);
1189 }
1190 add_gated(&mut vx, &vca, n, dim, &v_ca, &vt.idx, pool);
1191 pt = prof.tick(P_CROSS, pt);
1192
1193 let mut anorm = vec![0f32; m * a_dim];
1195 ada_zero_rows(&ax, &mut anorm, m, a_dim, &a_msa, &at.idx, pool);
1196 if bi == 0 {
1197 trace("a.b0.sa.in", &anorm);
1198 }
1199 let asa = blk
1200 .audio
1201 .attn1
1202 .forward(&anorm, m, &anorm, m, Some(&a_pe), Some(&a_pe), None, pool);
1203 if bi == 0 {
1204 trace("a.b0.sa.out", &asa);
1205 }
1206 let mut anormed = vec![0f32; m * a_dim];
1207 post_sa_rows(&mut ax, &asa, &mut anormed, m, a_dim, &a_msa, &at.idx, pool);
1208 let mut aq = vec![0f32; m * a_dim];
1209 affine_rows(&anormed, &mut aq, m, a_dim, &a_ca, &at.idx, pool);
1210 let actx = modulate_kv(&audio.context, audio.ctx_len, a_dim, &blk.audio.prompt_sst, apt.row(0), pool);
1211 let aca = blk.audio.attn2.forward(&aq, m, &actx, audio.ctx_len, None, None, amask, pool);
1212 if bi == 0 {
1213 trace("a.b0.ca.in", &aq);
1214 trace("a.b0.ca.ctx", &actx);
1215 trace("a.b0.ca.out", &aca);
1216 }
1217 add_gated(&mut ax, &aca, m, a_dim, &a_ca, &at.idx, pool);
1218 pt = prof.tick(P_CROSS, pt);
1219
1220 let vx_pre = vx.clone();
1222 let ax_pre = ax.clone();
1223 let a2v_vp = vxs.pairs(&blk.sst_a2v_video, dim, 0);
1224 let a2v_ap = axs.pairs(&blk.sst_a2v_audio, a_dim, 0);
1225 let a2v_v = ada_pair(&vx_pre, n, dim, &a2v_vp, &vxs.idx, pool);
1226 let a2v_a = ada_pair(&ax_pre, m, a_dim, &a2v_ap, &axs.idx, pool);
1227 let a2v = blk
1228 .a2v
1229 .forward(&a2v_v, n, &a2v_a, m, Some(&v_xpe), Some(&a_xpe), None, pool);
1230 if bi == 0 {
1231 trace("v.b0.a2v.in", &a2v_v);
1232 trace("v.b0.a2v.ctx", &a2v_a);
1233 trace("v.b0.a2v.out", &a2v);
1234 }
1235 let gate_a2v = gate_row(&blk.sst_a2v_video, dim, vgt.row(0));
1236 add_scaled(&mut vx, &a2v, n, dim, &gate_a2v, pool);
1237 let v2a_ap = axs.pairs(&blk.sst_a2v_audio, a_dim, 2);
1238 let v2a_vp = vxs.pairs(&blk.sst_a2v_video, dim, 2);
1239 let v2a_a = ada_pair(&ax_pre, m, a_dim, &v2a_ap, &axs.idx, pool);
1240 let v2a_v = ada_pair(&vx_pre, n, dim, &v2a_vp, &vxs.idx, pool);
1241 let v2a = blk
1242 .v2a
1243 .forward(&v2a_a, m, &v2a_v, n, Some(&a_xpe), Some(&v_xpe), None, pool);
1244 if bi == 0 {
1245 trace("a.b0.v2a.in", &v2a_a);
1246 trace("a.b0.v2a.ctx", &v2a_v);
1247 trace("a.b0.v2a.out", &v2a);
1248 }
1249 let gate_v2a = gate_row(&blk.sst_a2v_audio, a_dim, agt.row(0));
1250 add_scaled(&mut ax, &v2a, m, a_dim, &gate_v2a, pool);
1251 pt = prof.tick(P_FUSE, pt);
1252
1253 let mut vsc = vec![0f32; n * dim];
1255 ada_zero_rows(&vx, &mut vsc, n, dim, &v_mlp, &vt.idx, pool);
1256 let vff = blk.video.ff(&vsc, n, pool);
1257 if bi == 0 {
1258 trace("v.b0.ff.in", &vsc);
1259 trace("v.b0.ff.out", &vff);
1260 }
1261 add_gated(&mut vx, &vff, n, dim, &v_mlp, &vt.idx, pool);
1262 let mut asc = vec![0f32; m * a_dim];
1263 ada_zero_rows(&ax, &mut asc, m, a_dim, &a_mlp, &at.idx, pool);
1264 let aff = blk.audio.ff(&asc, m, pool);
1265 if bi == 0 {
1266 trace("a.b0.ff.in", &asc);
1267 trace("a.b0.ff.out", &aff);
1268 }
1269 add_gated(&mut ax, &aff, m, a_dim, &a_mlp, &at.idx, pool);
1270 pt = prof.tick(P_FF, pt);
1271 trace(&format!("v.block{bi}"), &vx);
1272 trace(&format!("a.block{bi}"), &ax);
1273 }
1274
1275 prof.report();
1276
1277 let vout = head(&vx, n, dim, &self.sst_out, &vt, &self.proj_out, pool);
1279 let aout = head(&ax, m, a_dim, &self.a_sst_out, &at, &self.a_proj_out, pool);
1280 trace("v.out", &vout);
1281 trace("a.out", &aout);
1282 (vout, aout)
1283 }
1284}
1285
1286fn modulate_kv(
1289 ctx: &[f32],
1290 len: usize,
1291 dim: usize,
1292 table: &[f32],
1293 extra: &[f32],
1294 pool: Option<&Pool>,
1295) -> Vec<f32> {
1296 let mut out = vec![0f32; len * dim];
1297 let shift: Vec<f32> = (0..dim).map(|d| table[d] + extra[d]).collect();
1298 let scale: Vec<f32> = (0..dim).map(|d| table[dim + d] + extra[dim + d]).collect();
1299 let dst = Shared(out.as_mut_ptr());
1303 rows(pool, len, &|s, e| {
1304 let r = unsafe { dst.at(s * dim, (e - s) * dim) };
1305 for (row, i) in r.chunks_exact_mut(dim).zip(s..e) {
1306 for d in 0..dim {
1307 row[d] = ctx[i * dim + d] * (1.0 + scale[d]) + shift[d];
1308 }
1309 }
1310 });
1311 out
1312}
1313
1314
1315fn ada_pair(
1317 x: &[f32],
1318 n: usize,
1319 dim: usize,
1320 pairs: &[[Vec<f32>; 2]],
1321 idx: &[usize],
1322 pool: Option<&Pool>,
1323) -> Vec<f32> {
1324 let mut out = vec![0f32; n * dim];
1325 let dst = Shared(out.as_mut_ptr());
1326 rows(pool, n, &|s, e| {
1327 let r = unsafe { dst.at(s * dim, (e - s) * dim) };
1328 for (row, i) in r.chunks_exact_mut(dim).zip(s..e) {
1329 let p = &pairs[idx[i]];
1330 rms_plain(&x[i * dim..(i + 1) * dim], row);
1331 for d in 0..dim {
1332 row[d] = row[d] * (1.0 + p[0][d]) + p[1][d];
1333 }
1334 }
1335 });
1336 out
1337}
1338
1339fn gate_row(table: &[f32], dim: usize, extra: &[f32]) -> Vec<f32> {
1342 (0..dim).map(|d| table[4 * dim + d] + extra[d]).collect()
1343}
1344
1345fn head(
1348 x: &[f32],
1349 n: usize,
1350 dim: usize,
1351 sst: &[f32],
1352 ts: &TsTable,
1353 proj: &Lin,
1354 pool: Option<&Pool>,
1355) -> Vec<f32> {
1356 let mut y = vec![0f32; n * dim];
1357 let dst = Shared(y.as_mut_ptr());
1358 rows(pool, n, &|s, e| {
1359 let r = unsafe { dst.at(s * dim, (e - s) * dim) };
1360 let mut ln = vec![0f32; dim];
1361 for (row, i) in r.chunks_exact_mut(dim).zip(s..e) {
1362 let emb = ts.emb_row(ts.idx[i.min(ts.idx.len() - 1)]);
1363 layer_norm(&x[i * dim..(i + 1) * dim], &mut ln);
1364 for d in 0..dim {
1365 row[d] = ln[d] * (1.0 + sst[dim + d] + emb[d]) + sst[d] + emb[d];
1366 }
1367 }
1368 });
1369 proj.apply(&y, n, pool)
1370}