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
140 .iter()
141 .map(|&v| (v as f64 - mean) * (v as f64 - mean))
142 .sum::<f64>()
143 / n;
144 let inv = 1.0 / (var + EPS).sqrt();
145 for (d, &v) in dst.iter_mut().zip(x) {
146 *d = ((v as f64 - mean) * inv) as f32;
147 }
148}
149
150pub(crate) struct Lin {
154 w: Proj,
155 b: Option<Vec<f32>>,
156 lora: Option<crate::ltxlora::LoraBranch>,
159}
160
161impl Lin {
162 pub(crate) fn load(model: &Arc<CmfModel>, name: &str, bias: bool) -> Result<Lin, String> {
163 Lin::load_lora(model, name, bias, None)
164 }
165
166 pub(crate) fn load_lora(
170 model: &Arc<CmfModel>,
171 name: &str,
172 bias: bool,
173 bank: Option<&crate::ltxlora::LoraBank>,
174 ) -> Result<Lin, String> {
175 let w = Proj::from_model(model, &format!("{name}.weight"))?;
176 let b = if bias {
177 Some(cmf_f32(model, &format!("{name}.bias"))?)
178 } else {
179 None
180 };
181 let lora = match bank {
185 Some(k) => k.branch_for(name, w.rows(), w.cols())?,
186 None => None,
187 };
188 Ok(Lin { w, b, lora })
189 }
190
191 pub(crate) fn has_lora(&self) -> bool {
196 self.lora.as_ref().is_some_and(|l| l.live())
197 }
198
199 pub(crate) fn add_lora(&self, out: &mut [f32], x: &[f32], n: usize, pool: Option<&Pool>) {
200 if let Some(l) = &self.lora {
201 l.add(x, n, out, pool);
202 }
203 }
204
205 #[cfg(target_os = "macos")]
208 fn mapped(&self) -> Option<(&Arc<CmfModel>, usize, usize, usize)> {
209 let (model, idx) = self.w.q4tp_mapped()?;
210 Some((model, idx, self.w.rows(), self.w.cols()))
211 }
212
213 pub(crate) fn add_bias(&self, out: &mut [f32], n: usize, pool: Option<&Pool>) {
215 let Some(b) = &self.b else { return };
216 let m = self.w.rows();
217 let dst = Shared(out.as_mut_ptr());
218 rows(pool, n, &|s, e| {
219 let r = unsafe { dst.at(s * m, (e - s) * m) };
220 for row in r.chunks_exact_mut(m) {
221 for (v, &bb) in row.iter_mut().zip(b) {
222 *v += bb;
223 }
224 }
225 });
226 }
227
228 pub(crate) fn apply(&self, x: &[f32], n: usize, pool: Option<&Pool>) -> Vec<f32> {
229 let m = self.w.rows();
230 let cols = self.w.cols();
231 let t_alloc = std::time::Instant::now();
232 let mut out = vec![0f32; n * m];
233 attn_prof::ALLOC.fetch_add(
234 t_alloc.elapsed().as_micros() as u64,
235 std::sync::atomic::Ordering::Relaxed,
236 );
237 let t_mm = std::time::Instant::now();
238 #[cfg(target_os = "macos")]
244 if let (Some(l), Some((model, idx, r, cl))) = (&self.lora, self.mapped()) {
245 if l.live()
249 && crate::ltxlora::route_threshold().is_none()
250 && n >= 32
251 && n * r * cl >= 128_000_000
252 && cl % 32 == 0
253 && !crate::gpu::mm_killed()
254 && crate::gpu::enabled_here()
255 && crate::gpu_metal::q4tp_matmat_lora(model, idx, x, n, r, cl, &mut out, &l.side())
256 {
257 attn_prof::MATMAT.fetch_add(
258 t_mm.elapsed().as_micros() as u64,
259 std::sync::atomic::Ordering::Relaxed,
260 );
261 self.add_bias(&mut out, n, pool);
262 return out;
263 }
264 }
265 let per_row = cols * 4;
270 let chunk = (0x1000_0000usize / per_row.max(1)).max(1);
271 let mut done = 0usize;
272 while done < n {
273 let take = chunk.min(n - done);
274 self.w.matmat(
275 &x[done * cols..(done + take) * cols],
276 take,
277 &mut out[done * m..(done + take) * m],
278 pool,
279 );
280 done += take;
281 }
282 attn_prof::MATMAT.fetch_add(
283 t_mm.elapsed().as_micros() as u64,
284 std::sync::atomic::Ordering::Relaxed,
285 );
286 if let Some(l) = &self.lora {
287 l.add(x, n, &mut out, pool);
288 }
289 if let Some(b) = &self.b {
290 let dst = Shared(out.as_mut_ptr());
291 rows(pool, n, &|s, e| {
292 let r = unsafe { dst.at(s * m, (e - s) * m) };
293 for row in r.chunks_exact_mut(m) {
294 for (v, &bb) in row.iter_mut().zip(b) {
295 *v += bb;
296 }
297 }
298 });
299 }
300 out
301 }
302}
303
304pub struct Rope {
308 cos: Vec<f32>,
309 sin: Vec<f32>,
310 heads: usize,
311 half: usize,
312}
313
314impl Rope {
315 pub fn build(
319 positions: &[Vec<f64>],
320 max_pos: &[f64],
321 dim: usize,
322 heads: usize,
323 theta: f64,
324 ) -> Rope {
325 let ndim = max_pos.len();
326 let count = dim / (2 * ndim);
327 let idx: Vec<f64> = (0..count)
329 .map(|j| {
330 let e = if count > 1 {
331 j as f64 / (count - 1) as f64
332 } else {
333 0.0
334 };
335 theta.powf(e) * std::f64::consts::PI / 2.0
336 })
337 .collect();
338 let n = positions.len();
339 let half = dim / 2;
340 let pad = half - count * ndim;
341 let mut cos = vec![0f32; n * half];
342 let mut sin = vec![0f32; n * half];
343 for (t, p) in positions.iter().enumerate() {
344 let base = t * half;
345 for i in 0..pad {
346 cos[base + i] = 1.0;
347 }
348 for (j, &ind) in idx.iter().enumerate() {
349 for (d, &mp) in max_pos.iter().enumerate() {
350 let f = ind * (p[d] / mp * 2.0 - 1.0);
351 let o = base + pad + j * ndim + d;
352 cos[o] = f.cos() as f32;
353 sin[o] = f.sin() as f32;
354 }
355 }
356 }
357 Rope {
358 cos,
359 sin,
360 heads,
361 half: half / heads,
362 }
363 }
364
365 fn apply_row(&self, t: usize, row: &mut [f32]) {
367 let dh = self.half * 2;
368 let stride = self.heads * self.half;
369 for h in 0..self.heads {
370 let off = t * stride + h * self.half;
371 let (c, s) = (
372 &self.cos[off..off + self.half],
373 &self.sin[off..off + self.half],
374 );
375 let v = &mut row[h * dh..(h + 1) * dh];
376 for i in 0..self.half {
377 let (a, b) = (v[i], v[i + self.half]);
378 v[i] = a * c[i] - b * s[i];
379 v[i + self.half] = b * c[i] + a * s[i];
380 }
381 }
382 }
383}
384
385pub(crate) mod attn_prof {
389 use std::sync::atomic::{AtomicU64, Ordering::Relaxed};
390 pub static PROJ: AtomicU64 = AtomicU64::new(0);
391 pub static NORM: AtomicU64 = AtomicU64::new(0);
392 pub static GATHER: AtomicU64 = AtomicU64::new(0);
393 pub static SCORE: AtomicU64 = AtomicU64::new(0);
394 pub static SOFT: AtomicU64 = AtomicU64::new(0);
395 pub static VALUE: AtomicU64 = AtomicU64::new(0);
396 pub static OUT: AtomicU64 = AtomicU64::new(0);
397 pub static ALLOC: AtomicU64 = AtomicU64::new(0);
398 pub static MATMAT: AtomicU64 = AtomicU64::new(0);
399
400 pub fn add(c: &AtomicU64, t: std::time::Instant) -> std::time::Instant {
401 c.fetch_add(t.elapsed().as_micros() as u64, Relaxed);
402 std::time::Instant::now()
403 }
404
405 pub fn report() -> String {
406 let s = |c: &AtomicU64| c.swap(0, Relaxed) as f64 / 1e6;
407 format!(
408 "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]",
409 s(&PROJ),
410 s(&NORM),
411 s(&GATHER),
412 s(&SCORE),
413 s(&SOFT),
414 s(&VALUE),
415 s(&OUT),
416 s(&ALLOC),
417 s(&MATMAT)
418 )
419 }
420}
421
422pub(crate) struct Attn {
425 q: Lin,
426 k: Lin,
427 v: Lin,
428 o: Lin,
429 q_norm: Vec<f32>,
430 k_norm: Vec<f32>,
431 gate: Option<Lin>,
432 heads: usize,
433 dh: usize,
434}
435
436impl Attn {
437 pub(crate) fn load(
438 model: &Arc<CmfModel>,
439 p: &str,
440 heads: usize,
441 dh: usize,
442 ) -> Result<Attn, String> {
443 Attn::load_lora(model, p, heads, dh, None)
444 }
445
446 pub(crate) fn load_lora(
447 model: &Arc<CmfModel>,
448 p: &str,
449 heads: usize,
450 dh: usize,
451 bank: Option<&crate::ltxlora::LoraBank>,
452 ) -> Result<Attn, String> {
453 Ok(Attn {
454 q: Lin::load_lora(model, &format!("{p}.to_q"), true, bank)?,
455 k: Lin::load_lora(model, &format!("{p}.to_k"), true, bank)?,
456 v: Lin::load_lora(model, &format!("{p}.to_v"), true, bank)?,
457 o: Lin::load_lora(model, &format!("{p}.to_out.0"), true, bank)?,
458 q_norm: cmf_f32(model, &format!("{p}.q_norm.weight"))?,
459 k_norm: cmf_f32(model, &format!("{p}.k_norm.weight"))?,
460 gate: match model.tensor(&format!("{p}.to_gate_logits.weight")) {
461 Some(_) => Some(Lin::load(model, &format!("{p}.to_gate_logits"), true)?),
462 None => None,
463 },
464 heads,
465 dh,
466 })
467 }
468
469 #[cfg(target_os = "macos")]
474 fn fused_qkv(
475 &self,
476 x: &[f32],
477 n: usize,
478 ctx: &[f32],
479 m: usize,
480 pool: Option<&Pool>,
481 ) -> Option<(Vec<f32>, Vec<f32>, Vec<f32>)> {
482 if !std::ptr::eq(x.as_ptr(), ctx.as_ptr()) || n != m {
483 return None;
484 }
485 if !crate::gpu::enabled_here() || crate::gpu::mm_killed() {
486 return None;
487 }
488 if self.q.has_lora() || self.k.has_lora() || self.v.has_lora() {
493 return None;
494 }
495 let (qw, kw, vw) = (self.q.mapped()?, self.k.mapped()?, self.v.mapped()?);
496 if !Arc::ptr_eq(qw.0, kw.0) || !Arc::ptr_eq(qw.0, vw.0) {
497 return None;
498 }
499 let jobs = [
500 crate::gpu_metal::MmJob {
501 idx: qw.1,
502 rows: qw.2,
503 cols: qw.3,
504 },
505 crate::gpu_metal::MmJob {
506 idx: kw.1,
507 rows: kw.2,
508 cols: kw.3,
509 },
510 crate::gpu_metal::MmJob {
511 idx: vw.1,
512 rows: vw.2,
513 cols: vw.3,
514 },
515 ];
516 if n * jobs[0].rows * jobs[0].cols < 128_000_000 || n < 32 {
517 return None;
518 }
519 let mut oq = vec![0f32; n * jobs[0].rows];
520 let mut ok = vec![0f32; n * jobs[1].rows];
521 let mut ov = vec![0f32; n * jobs[2].rows];
522 let done = {
523 let mut outs: [&mut [f32]; 3] = [&mut oq, &mut ok, &mut ov];
524 crate::gpu_metal::q4tp_matmat_many(qw.0, &jobs, x, n, &mut outs)
525 };
526 if !done {
527 return None;
528 }
529 self.q.add_lora(&mut oq, x, n, pool);
530 self.k.add_lora(&mut ok, x, n, pool);
531 self.v.add_lora(&mut ov, x, n, pool);
532 self.q.add_bias(&mut oq, n, pool);
533 self.k.add_bias(&mut ok, n, pool);
534 self.v.add_bias(&mut ov, n, pool);
535 Some((oq, ok, ov))
536 }
537
538 #[cfg(target_os = "macos")]
541 fn fused_kv(&self, ctx: &[f32], m: usize) -> Option<(Vec<f32>, Vec<f32>)> {
542 if !crate::gpu::enabled_here() || crate::gpu::mm_killed() {
543 return None;
544 }
545 if self.k.has_lora() || self.v.has_lora() {
546 return None;
547 }
548 let (kw, vw) = (self.k.mapped()?, self.v.mapped()?);
549 if !Arc::ptr_eq(kw.0, vw.0) {
550 return None;
551 }
552 let jobs = [
553 crate::gpu_metal::MmJob {
554 idx: kw.1,
555 rows: kw.2,
556 cols: kw.3,
557 },
558 crate::gpu_metal::MmJob {
559 idx: vw.1,
560 rows: vw.2,
561 cols: vw.3,
562 },
563 ];
564 if m < 32 || m * jobs[0].rows * jobs[0].cols < 128_000_000 {
565 return None;
566 }
567 let mut ok = vec![0f32; m * jobs[0].rows];
568 let mut ov = vec![0f32; m * jobs[1].rows];
569 let done = {
570 let mut outs: [&mut [f32]; 2] = [&mut ok, &mut ov];
571 crate::gpu_metal::q4tp_matmat_many(kw.0, &jobs, ctx, m, &mut outs)
572 };
573 if !done {
574 return None;
575 }
576 self.k.add_lora(&mut ok, ctx, m, None);
577 self.v.add_lora(&mut ov, ctx, m, None);
578 self.k.add_bias(&mut ok, m, None);
579 self.v.add_bias(&mut ov, m, None);
580 Some((ok, ov))
581 }
582
583 #[cfg(not(target_os = "macos"))]
584 fn fused_kv(&self, _ctx: &[f32], _m: usize) -> Option<(Vec<f32>, Vec<f32>)> {
585 None
586 }
587
588 #[cfg(not(target_os = "macos"))]
589 fn fused_qkv(
590 &self,
591 _x: &[f32],
592 _n: usize,
593 _ctx: &[f32],
594 _m: usize,
595 _pool: Option<&Pool>,
596 ) -> Option<(Vec<f32>, Vec<f32>, Vec<f32>)> {
597 None
598 }
599
600 #[allow(clippy::too_many_arguments)]
603 pub(crate) fn forward(
604 &self,
605 x: &[f32],
606 n: usize,
607 ctx: &[f32],
608 m: usize,
609 pe_q: Option<&Rope>,
610 pe_k: Option<&Rope>,
611 mask: Option<&[f32]>,
612 pool: Option<&Pool>,
613 ) -> Vec<f32> {
614 let inner = self.heads * self.dh;
615 let prof = std::env::var("CMF_LTX_PROF").is_ok();
616 let mut t = std::time::Instant::now();
617 let (mut q, mut k, v) = match self.fused_qkv(x, n, ctx, m, pool) {
623 Some(t) => t,
624 None => match self.fused_kv(ctx, m) {
628 Some((k, v)) => (self.q.apply(x, n, pool), k, v),
629 None => (
630 self.q.apply(x, n, pool),
631 self.k.apply(ctx, m, pool),
632 self.v.apply(ctx, m, pool),
633 ),
634 },
635 };
636 if prof {
637 t = attn_prof::add(&attn_prof::PROJ, t);
638 }
639
640 let qn = Shared(q.as_mut_ptr());
641 rows(pool, n, &|s, e| {
642 let r = unsafe { qn.at(s * inner, (e - s) * inner) };
643 for (i, row) in r.chunks_exact_mut(inner).enumerate() {
644 rms_w(row, &self.q_norm);
645 if let Some(pe) = pe_q {
646 pe.apply_row(s + i, row);
647 }
648 }
649 });
650 let kn = Shared(k.as_mut_ptr());
651 rows(pool, m, &|s, e| {
652 let r = unsafe { kn.at(s * inner, (e - s) * inner) };
653 for (i, row) in r.chunks_exact_mut(inner).enumerate() {
654 rms_w(row, &self.k_norm);
655 if let Some(pe) = pe_k {
656 pe.apply_row(s + i, row);
657 }
658 }
659 });
660
661 if prof {
667 t = attn_prof::add(&attn_prof::NORM, t);
668 }
669 let mut out = vec![0f32; n * inner];
670 let scale = 1.0 / (self.dh as f32).sqrt();
671 let dh = self.dh;
672 let mut qh = vec![0f32; n * dh];
673 let mut kh = vec![0f32; m * dh];
674 let mut vh = vec![0f32; m * dh];
675 let mut sc = vec![0f32; n * m];
676 let mut oh = vec![0f32; n * dh];
677 let pv_nt = std::env::var("CMF_LTX_PV_NT").as_deref() == Ok("1");
697 for h in 0..self.heads {
698 for i in 0..n {
699 qh[i * dh..(i + 1) * dh].copy_from_slice(&q[i * inner + h * dh..][..dh]);
700 }
701 for j in 0..m {
702 kh[j * dh..(j + 1) * dh].copy_from_slice(&k[j * inner + h * dh..][..dh]);
703 }
704 if pv_nt {
705 for j in 0..m {
707 let src = &v[j * inner + h * dh..][..dh];
708 for (d, s) in src.iter().enumerate() {
709 vh[d * m + j] = *s;
710 }
711 }
712 } else {
713 for j in 0..m {
714 vh[j * dh..(j + 1) * dh].copy_from_slice(&v[j * inner + h * dh..][..dh]);
715 }
716 }
717 if prof {
718 t = attn_prof::add(&attn_prof::GATHER, t);
719 }
720 crate::fcd_ops::gemm_nt(&qh, &kh, &mut sc, n, dh, m, pool);
721 if prof {
722 t = attn_prof::add(&attn_prof::SCORE, t);
723 }
724 let sp = Shared(sc.as_mut_ptr());
725 rows(pool, n, &|s, e| {
726 let r = unsafe { sp.at(s * m, (e - s) * m) };
727 for row in r.chunks_exact_mut(m) {
728 for (x, j) in row.iter_mut().zip(0..m) {
729 *x = *x * scale + mask.map_or(0.0, |mk| mk[j]);
730 }
731 softmax(row);
732 }
733 });
734 if prof {
735 t = attn_prof::add(&attn_prof::SOFT, t);
736 }
737 oh.iter_mut().for_each(|x| *x = 0.0);
738 if pv_nt {
739 crate::fcd_ops::gemm_nt(&sc, &vh, &mut oh, n, m, dh, pool);
742 } else {
743 crate::fcd_ops::gemm_dx(&sc, &vh, &mut oh, n, dh, m, pool);
744 }
745 if prof {
746 t = attn_prof::add(&attn_prof::VALUE, t);
747 }
748 for i in 0..n {
749 out[i * inner + h * dh..i * inner + (h + 1) * dh]
750 .copy_from_slice(&oh[i * dh..(i + 1) * dh]);
751 }
752 if prof {
753 t = attn_prof::add(&attn_prof::GATHER, t);
754 }
755 }
756
757 if let Some(g) = &self.gate {
758 let logits = g.apply(x, n, pool);
759 let h = self.heads;
760 let dst = Shared(out.as_mut_ptr());
761 rows(pool, n, &|s, e| {
762 let r = unsafe { dst.at(s * inner, (e - s) * inner) };
763 for (i, row) in r.chunks_exact_mut(inner).enumerate() {
764 for hh in 0..h {
765 let gate = 2.0 / (1.0 + (-logits[(s + i) * h + hh]).exp());
766 for d in row[hh * self.dh..(hh + 1) * self.dh].iter_mut() {
767 *d *= gate;
768 }
769 }
770 }
771 });
772 }
773 let r = self.o.apply(&out, n, pool);
774 if prof {
775 attn_prof::add(&attn_prof::OUT, t);
776 }
777 r
778 }
779}
780
781struct AdaLn {
786 l1: Lin,
787 l2: Lin,
788 lin: Lin,
789 dim: usize,
790}
791
792impl AdaLn {
793 fn load(model: &Arc<CmfModel>, p: &str, dim: usize) -> Result<AdaLn, String> {
794 Ok(AdaLn {
795 l1: Lin::load(model, &format!("{p}.emb.timestep_embedder.linear_1"), true)?,
796 l2: Lin::load(model, &format!("{p}.emb.timestep_embedder.linear_2"), true)?,
797 lin: Lin::load(model, &format!("{p}.linear"), true)?,
798 dim,
799 })
800 }
801
802 fn forward(&self, t: &[f32], pool: Option<&Pool>) -> (Vec<f32>, Vec<f32>) {
804 let n = t.len();
805 let half = 128usize;
808 let mut proj = vec![0f32; n * 256];
809 let ws: Vec<f64> = (0..half)
810 .map(|j| (-(10000f64).ln() * j as f64 / half as f64).exp())
811 .collect();
812 for (i, &tv) in t.iter().enumerate() {
813 for (j, &w) in ws.iter().enumerate() {
814 let a = tv as f64 * w;
815 proj[i * 256 + j] = a.cos() as f32;
816 proj[i * 256 + half + j] = a.sin() as f32;
817 }
818 }
819 let mut h = self.l1.apply(&proj, n, pool);
820 for v in h.iter_mut() {
821 *v = silu(*v);
822 }
823 let embedded = self.l2.apply(&h, n, pool);
824 let mut act = embedded.clone();
825 for v in act.iter_mut() {
826 *v = silu(*v);
827 }
828 (self.lin.apply(&act, n, pool), embedded)
829 }
830}
831
832struct TsTable {
837 vals: Vec<f32>,
838 emb: Vec<f32>,
839 idx: Vec<usize>,
840 width: usize,
841 edim: usize,
842}
843
844impl TsTable {
845 fn build(a: &AdaLn, ts: &[f32], scale: f64, pool: Option<&Pool>) -> TsTable {
846 let mut vals: Vec<f32> = Vec::new();
847 let mut idx = Vec::with_capacity(ts.len());
848 for &t in ts {
849 match vals.iter().position(|&v| v.to_bits() == t.to_bits()) {
850 Some(i) => idx.push(i),
851 None => {
852 vals.push(t);
853 idx.push(vals.len() - 1);
854 }
855 }
856 }
857 let scaled: Vec<f32> = vals.iter().map(|&v| (v as f64 * scale) as f32).collect();
858 let (v, e) = a.forward(&scaled, pool);
859 let width = v.len() / scaled.len().max(1);
860 TsTable {
861 vals: v,
862 emb: e,
863 idx,
864 width,
865 edim: a.dim,
866 }
867 }
868
869 fn distinct(&self) -> usize {
870 self.vals.len() / self.width.max(1)
871 }
872
873 fn row(&self, r: usize) -> &[f32] {
874 &self.vals[r * self.width..(r + 1) * self.width]
875 }
876
877 fn emb_row(&self, r: usize) -> &[f32] {
878 &self.emb[r * self.edim..(r + 1) * self.edim]
879 }
880
881 fn triples(&self, table: &[f32], dim: usize, off: usize) -> Vec<[Vec<f32>; 3]> {
885 (0..self.distinct())
886 .map(|r| {
887 let v = self.row(r);
888 std::array::from_fn(|j| {
889 let o = (off + j) * dim;
890 (0..dim).map(|d| table[o + d] + v[o + d]).collect()
891 })
892 })
893 .collect()
894 }
895
896 fn pairs(&self, table: &[f32], dim: usize, off: usize) -> Vec<[Vec<f32>; 2]> {
899 (0..self.distinct())
900 .map(|r| {
901 let v = self.row(r);
902 std::array::from_fn(|j| {
903 let o = (off + j) * dim;
904 (0..dim).map(|d| table[o + d] + v[o + d]).collect()
905 })
906 })
907 .collect()
908 }
909}
910
911fn ada_zero_rows(
915 x: &[f32],
916 out: &mut [f32],
917 n: usize,
918 dim: usize,
919 mods: &[[Vec<f32>; 3]],
920 idx: &[usize],
921 pool: Option<&Pool>,
922) {
923 let dst = Shared(out.as_mut_ptr());
924 rows(pool, n, &|s, e| {
925 let r = unsafe { dst.at(s * dim, (e - s) * dim) };
926 for (row, i) in r.chunks_exact_mut(dim).zip(s..e) {
927 let md = &mods[idx[i]];
928 rms_plain(&x[i * dim..(i + 1) * dim], row);
929 for d in 0..dim {
930 row[d] = row[d] * (1.0 + md[1][d]) + md[0][d];
931 }
932 }
933 });
934}
935
936fn add_gated(
938 x: &mut [f32],
939 y: &[f32],
940 n: usize,
941 dim: usize,
942 mods: &[[Vec<f32>; 3]],
943 idx: &[usize],
944 pool: Option<&Pool>,
945) {
946 let dst = Shared(x.as_mut_ptr());
947 rows(pool, n, &|s, e| {
948 let r = unsafe { dst.at(s * dim, (e - s) * dim) };
949 for (row, i) in r.chunks_exact_mut(dim).zip(s..e) {
950 let g = &mods[idx[i]][2];
951 for d in 0..dim {
952 row[d] += y[i * dim + d] * g[d];
953 }
954 }
955 });
956}
957
958fn post_sa_rows(
961 x: &mut [f32],
962 y: &[f32],
963 normed: &mut [f32],
964 n: usize,
965 dim: usize,
966 mods: &[[Vec<f32>; 3]],
967 idx: &[usize],
968 pool: Option<&Pool>,
969) {
970 let a = Shared(x.as_mut_ptr());
971 let b = Shared(normed.as_mut_ptr());
972 rows(pool, n, &|s, e| {
973 let xr = unsafe { a.at(s * dim, (e - s) * dim) };
974 let nr = unsafe { b.at(s * dim, (e - s) * dim) };
975 for ((row, nrow), i) in xr
976 .chunks_exact_mut(dim)
977 .zip(nr.chunks_exact_mut(dim))
978 .zip(s..e)
979 {
980 let g = &mods[idx[i]][2];
981 for d in 0..dim {
982 row[d] += y[i * dim + d] * g[d];
983 }
984 rms_plain(row, nrow);
985 }
986 });
987}
988
989fn add_scaled(x: &mut [f32], y: &[f32], n: usize, dim: usize, g: &[f32], pool: Option<&Pool>) {
991 let dst = Shared(x.as_mut_ptr());
992 rows(pool, n, &|s, e| {
993 let r = unsafe { dst.at(s * dim, (e - s) * dim) };
994 for (row, i) in r.chunks_exact_mut(dim).zip(s..e) {
995 for d in 0..dim {
996 row[d] += y[i * dim + d] * g[d];
997 }
998 }
999 });
1000}
1001
1002fn affine_rows(
1004 x: &[f32],
1005 out: &mut [f32],
1006 n: usize,
1007 dim: usize,
1008 mods: &[[Vec<f32>; 3]],
1009 idx: &[usize],
1010 pool: Option<&Pool>,
1011) {
1012 let dst = Shared(out.as_mut_ptr());
1013 rows(pool, n, &|s, e| {
1014 let r = unsafe { dst.at(s * dim, (e - s) * dim) };
1015 for (row, i) in r.chunks_exact_mut(dim).zip(s..e) {
1016 let md = &mods[idx[i]];
1017 for d in 0..dim {
1018 row[d] = x[i * dim + d] * (1.0 + md[1][d]) + md[0][d];
1019 }
1020 }
1021 });
1022}
1023
1024#[derive(Default)]
1027struct Prof {
1028 on: bool,
1029 t: [f64; 6],
1030}
1031
1032const P_ADALN: usize = 0;
1033const P_SELF: usize = 1;
1034const P_CROSS: usize = 2;
1035const P_FUSE: usize = 3;
1036const P_FF: usize = 4;
1037const P_MOD: usize = 5;
1038
1039impl Prof {
1040 fn new() -> Prof {
1041 Prof {
1042 on: std::env::var("CMF_LTX_PROF").is_ok(),
1043 t: [0.0; 6],
1044 }
1045 }
1046 #[inline]
1047 fn tick(&mut self, slot: usize, at: std::time::Instant) -> std::time::Instant {
1048 if self.on {
1049 self.t[slot] += at.elapsed().as_secs_f64();
1050 return std::time::Instant::now();
1051 }
1052 at
1053 }
1054 fn report(&self) {
1055 if !self.on {
1056 return;
1057 }
1058 #[cfg(target_os = "macos")]
1061 {
1062 use std::sync::atomic::Ordering::Relaxed;
1063 let n = crate::gpu_metal::MM_N.swap(0, Relaxed);
1064 if n > 0 {
1065 let us = |a: &std::sync::atomic::AtomicU64| a.swap(0, Relaxed) as f64 / 1e6;
1066 println!(
1067 " q4tp on device: {n} calls, upload {:.2}s kernel {:.2}s download {:.2}s",
1068 us(&crate::gpu_metal::MM_UP),
1069 us(&crate::gpu_metal::MM_GPU),
1070 us(&crate::gpu_metal::MM_DN),
1071 );
1072 }
1073 }
1074 println!(" attention: {}", attn_prof::report());
1075 let names = [
1076 "adaln",
1077 "self-attn",
1078 "cross-attn",
1079 "a<->v",
1080 "ffn",
1081 "modulate",
1082 ];
1083 let total: f64 = self.t.iter().sum();
1084 let parts: Vec<String> = names
1085 .iter()
1086 .zip(&self.t)
1087 .map(|(n, v)| format!("{n} {v:.1}s ({:.0}%)", 100.0 * v / total.max(1e-9)))
1088 .collect();
1089 println!(" profile: {}", parts.join(" "));
1090 }
1091}
1092
1093struct Stream {
1096 attn1: Attn,
1097 attn2: Attn,
1098 ff_in: Lin,
1099 ff_out: Lin,
1100 sst: Vec<f32>, prompt_sst: Vec<f32>, }
1103
1104impl Stream {
1105 fn load(
1106 model: &Arc<CmfModel>,
1107 p: &str,
1108 prefix: &str,
1109 heads: usize,
1110 dh: usize,
1111 ff_bias: bool,
1112 bank: Option<&crate::ltxlora::LoraBank>,
1113 ) -> Result<Stream, String> {
1114 let a = |n: &str| format!("{p}.{prefix}{n}");
1115 Ok(Stream {
1116 attn1: Attn::load_lora(model, &a("attn1"), heads, dh, bank)?,
1117 attn2: Attn::load_lora(model, &a("attn2"), heads, dh, bank)?,
1118 ff_in: Lin::load_lora(model, &a("ff.net.0.proj"), ff_bias, bank)?,
1119 ff_out: Lin::load_lora(model, &a("ff.net.2"), ff_bias, bank)?,
1120 sst: cmf_f32(model, &a("scale_shift_table"))?,
1121 prompt_sst: cmf_f32(model, &a("prompt_scale_shift_table"))?,
1122 })
1123 }
1124
1125 fn ff(&self, x: &[f32], n: usize, pool: Option<&Pool>) -> Vec<f32> {
1126 let mut h = self.ff_in.apply(x, n, pool);
1127 gelu_tanh_rows(&mut h, pool);
1128 self.ff_out.apply(&h, n, pool)
1129 }
1130}
1131
1132struct Block {
1133 video: Stream,
1134 audio: Stream,
1135 a2v: Attn,
1136 v2a: Attn,
1137 sst_a2v_video: Vec<f32>, sst_a2v_audio: Vec<f32>, }
1140
1141pub struct StreamInput {
1146 pub latent: Vec<f32>,
1148 pub tokens: usize,
1149 pub timesteps: Vec<f32>,
1151 pub positions: Vec<Vec<f64>>,
1153 pub context: Vec<f32>,
1155 pub ctx_len: usize,
1156 pub context_mask: Vec<f32>,
1158 pub keyframes: Vec<f32>,
1160 pub sigma: f32,
1162}
1163
1164pub struct LtxDit {
1165 model: Arc<CmfModel>,
1166 blocks: Vec<Block>,
1167 patchify: Lin,
1168 a_patchify: Lin,
1169 keyframes_emb: Option<Vec<f32>>,
1170 adaln: AdaLn,
1171 a_adaln: AdaLn,
1172 prompt_adaln: AdaLn,
1173 a_prompt_adaln: AdaLn,
1174 av_v_ss: AdaLn,
1175 av_a_ss: AdaLn,
1176 av_a2v_gate: AdaLn,
1177 av_v2a_gate: AdaLn,
1178 proj_out: Lin,
1179 a_proj_out: Lin,
1180 sst_out: Vec<f32>,
1181 a_sst_out: Vec<f32>,
1182 pub heads: usize,
1183 pub dh: usize,
1184 pub a_heads: usize,
1185 pub a_dh: usize,
1186 pub max_pos: Vec<f64>,
1187 pub a_max_pos: Vec<f64>,
1188 pub cross_max_pos: f64,
1189 pub theta: f64,
1190 pub t_scale: f64,
1191 pub av_t_scale: f64,
1192 pub audio_cross_dim: usize,
1193}
1194
1195impl LtxDit {
1196 pub fn from_cmf(model: &Arc<CmfModel>) -> Result<LtxDit, String> {
1197 LtxDit::from_cmf_lora(model, None)
1198 }
1199
1200 pub fn from_cmf_lora(
1204 model: &Arc<CmfModel>,
1205 bank: Option<&crate::ltxlora::LoraBank>,
1206 ) -> Result<LtxDit, String> {
1207 let cfg_bytes = ["ltx.config_json", "dit.config_json"]
1208 .iter()
1209 .find_map(|n| model.tensor(n).map(|e| model.entry_bytes(e)))
1210 .ok_or("container carries no ltx.config_json")?;
1211 let cfg: serde_json::Value =
1212 serde_json::from_slice(cfg_bytes).map_err(|e| format!("ltx.config_json: {e}"))?;
1213 let t = cfg.get("transformer").unwrap_or(&cfg).clone();
1214 let g = |k: &str, d: f64| t.get(k).and_then(|v| v.as_f64()).unwrap_or(d);
1215 let heads = g("num_attention_heads", 32.0) as usize;
1216 let dh = g("attention_head_dim", 128.0) as usize;
1217 let a_heads = g("audio_num_attention_heads", 32.0) as usize;
1218 let a_dh = g("audio_attention_head_dim", 64.0) as usize;
1219 let n_layers = g("num_layers", 48.0) as usize;
1220 let ff_bias = t.get("ff_bias").and_then(|v| v.as_bool()).unwrap_or(true);
1221 let a_ff_bias = t
1222 .get("audio_ff_bias")
1223 .and_then(|v| v.as_bool())
1224 .unwrap_or(true);
1225 let arr = |k: &str, d: Vec<f64>| -> Vec<f64> {
1226 t.get(k)
1227 .and_then(|v| v.as_array())
1228 .map(|a| a.iter().filter_map(|x| x.as_f64()).collect())
1229 .unwrap_or(d)
1230 };
1231 let max_pos = arr("positional_embedding_max_pos", vec![20.0, 2048.0, 2048.0]);
1232 let a_max_pos = arr("audio_positional_embedding_max_pos", vec![20.0]);
1233 let cross_max_pos = max_pos[0].max(a_max_pos[0]);
1234 let dim = heads * dh;
1235 let a_dim = a_heads * a_dh;
1236
1237 let mut blocks = Vec::with_capacity(n_layers);
1238 for i in 0..n_layers {
1239 let p = format!("dit.transformer_blocks.{i}");
1240 blocks.push(Block {
1241 video: Stream::load(model, &p, "", heads, dh, ff_bias, bank)?,
1242 audio: Stream::load(model, &p, "audio_", a_heads, a_dh, a_ff_bias, bank)?,
1243 a2v: Attn::load(model, &format!("{p}.audio_to_video_attn"), a_heads, a_dh)?,
1244 v2a: Attn::load(model, &format!("{p}.video_to_audio_attn"), a_heads, a_dh)?,
1245 sst_a2v_video: cmf_f32(model, &format!("{p}.scale_shift_table_a2v_ca_video"))?,
1246 sst_a2v_audio: cmf_f32(model, &format!("{p}.scale_shift_table_a2v_ca_audio"))?,
1247 });
1248 }
1249 let _ = (dim, a_dim);
1250 Ok(LtxDit {
1251 blocks,
1252 patchify: Lin::load(model, "dit.patchify_proj", true)?,
1253 a_patchify: Lin::load(model, "dit.audio_patchify_proj", true)?,
1254 keyframes_emb: match model.tensor("dit.keyframes_abs_pos_embedding") {
1255 Some(_) => Some(cmf_f32(model, "dit.keyframes_abs_pos_embedding")?),
1256 None => None,
1257 },
1258 adaln: AdaLn::load(model, "dit.adaln_single", dim)?,
1259 a_adaln: AdaLn::load(model, "dit.audio_adaln_single", a_dim)?,
1260 prompt_adaln: AdaLn::load(model, "dit.prompt_adaln_single", dim)?,
1261 a_prompt_adaln: AdaLn::load(model, "dit.audio_prompt_adaln_single", a_dim)?,
1262 av_v_ss: AdaLn::load(model, "dit.av_ca_video_scale_shift_adaln_single", dim)?,
1263 av_a_ss: AdaLn::load(model, "dit.av_ca_audio_scale_shift_adaln_single", a_dim)?,
1264 av_a2v_gate: AdaLn::load(model, "dit.av_ca_a2v_gate_adaln_single", dim)?,
1265 av_v2a_gate: AdaLn::load(model, "dit.av_ca_v2a_gate_adaln_single", a_dim)?,
1266 proj_out: Lin::load(model, "dit.proj_out", true)?,
1267 a_proj_out: Lin::load(model, "dit.audio_proj_out", true)?,
1268 sst_out: cmf_f32(model, "dit.scale_shift_table")?,
1269 a_sst_out: cmf_f32(model, "dit.audio_scale_shift_table")?,
1270 heads,
1271 dh,
1272 a_heads,
1273 a_dh,
1274 max_pos,
1275 a_max_pos,
1276 cross_max_pos,
1277 theta: g("positional_embedding_theta", 10000.0),
1278 t_scale: g("timestep_scale_multiplier", 1000.0),
1279 av_t_scale: g("av_ca_timestep_scale_multiplier", 1.0),
1280 audio_cross_dim: g("audio_cross_attention_dim", 2048.0) as usize,
1281 model: model.clone(),
1282 })
1283 }
1284
1285 pub fn blocks(&self) -> usize {
1286 self.blocks.len()
1287 }
1288
1289 pub fn container(&self) -> &Arc<CmfModel> {
1290 &self.model
1291 }
1292
1293 pub fn forward(
1295 &self,
1296 video: &StreamInput,
1297 audio: &StreamInput,
1298 pool: Option<&Pool>,
1299 ) -> (Vec<f32>, Vec<f32>) {
1300 self.forward_traced(video, audio, pool, &mut |_, _| {})
1301 }
1302
1303 pub fn forward_traced(
1304 &self,
1305 video: &StreamInput,
1306 audio: &StreamInput,
1307 pool: Option<&Pool>,
1308 trace: &mut dyn FnMut(&str, &[f32]),
1309 ) -> (Vec<f32>, Vec<f32>) {
1310 let _trust = crate::gpu::trust_gpu();
1314 let dim = self.heads * self.dh;
1315 let a_dim = self.a_heads * self.a_dh;
1316 let (n, m) = (video.tokens, audio.tokens);
1317
1318 let mut vx = self.patchify.apply(&video.latent, n, pool);
1320 if let Some(emb) = &self.keyframes_emb {
1321 for i in 0..n {
1322 if video.keyframes.get(i).copied().unwrap_or(0.0) > 0.0 {
1323 for (d, &e) in vx[i * dim..(i + 1) * dim].iter_mut().zip(emb) {
1324 *d += e;
1325 }
1326 }
1327 }
1328 }
1329 let mut ax = self.a_patchify.apply(&audio.latent, m, pool);
1330 trace("v.args.x", &vx);
1331 trace("a.args.x", &ax);
1332
1333 let vt = TsTable::build(&self.adaln, &video.timesteps, self.t_scale, pool);
1335 let at = TsTable::build(&self.a_adaln, &audio.timesteps, self.t_scale, pool);
1336 let vpt = TsTable::build(&self.prompt_adaln, &[video.sigma], self.t_scale, pool);
1337 let apt = TsTable::build(&self.a_prompt_adaln, &[audio.sigma], self.t_scale, pool);
1338 if std::env::var("CMF_LTX_PROMPTADALN").is_ok() {
1339 let r = vpt.row(0);
1340 let mx = r.iter().fold(0f32, |m, &v| m.max(v.abs()));
1341 let sum: f32 = r.iter().sum();
1342 eprintln!(
1343 "prompt-adaln sigma={:.6} max|row|={mx:.6e} sum={sum:.6e}",
1344 video.sigma
1345 );
1346 }
1347 let vxs = TsTable::build(&self.av_v_ss, &video.timesteps, self.t_scale, pool);
1348 let axs = TsTable::build(&self.av_a_ss, &audio.timesteps, self.t_scale, pool);
1349 let vgt = TsTable::build(&self.av_a2v_gate, &[audio.sigma], self.av_t_scale, pool);
1352 let agt = TsTable::build(&self.av_v2a_gate, &[video.sigma], self.av_t_scale, pool);
1353
1354 let v_pe = Rope::build(&video.positions, &self.max_pos, dim, self.heads, self.theta);
1356 let a_pe = Rope::build(
1357 &audio.positions,
1358 &self.a_max_pos,
1359 a_dim,
1360 self.a_heads,
1361 self.theta,
1362 );
1363 let time_only = |p: &[Vec<f64>]| p.iter().map(|r| vec![r[0]]).collect::<Vec<_>>();
1364 let v_xpe = Rope::build(
1365 &time_only(&video.positions),
1366 &[self.cross_max_pos],
1367 self.audio_cross_dim,
1368 self.heads,
1369 self.theta,
1370 );
1371 let a_xpe = Rope::build(
1372 &time_only(&audio.positions),
1373 &[self.cross_max_pos],
1374 self.audio_cross_dim,
1375 self.a_heads,
1376 self.theta,
1377 );
1378
1379 let vmask = (!video.context_mask.is_empty()).then_some(&video.context_mask[..]);
1380 let amask = (!audio.context_mask.is_empty()).then_some(&audio.context_mask[..]);
1381
1382 let mut prof = Prof::new();
1383 for (bi, blk) in self.blocks.iter().enumerate() {
1384 let mut pt = std::time::Instant::now();
1385 let v_msa = vt.triples(&blk.video.sst, dim, 0);
1386 let v_ca = vt.triples(&blk.video.sst, dim, 6);
1387 let v_mlp = vt.triples(&blk.video.sst, dim, 3);
1388 let a_msa = at.triples(&blk.audio.sst, a_dim, 0);
1389 let a_ca = at.triples(&blk.audio.sst, a_dim, 6);
1390 let a_mlp = at.triples(&blk.audio.sst, a_dim, 3);
1391
1392 pt = prof.tick(P_ADALN, pt);
1394 let mut vnorm = vec![0f32; n * dim];
1395 ada_zero_rows(&vx, &mut vnorm, n, dim, &v_msa, &vt.idx, pool);
1396 pt = prof.tick(P_MOD, pt);
1397 if bi == 0 {
1398 trace("v.b0.sa.in", &vnorm);
1399 }
1400 let vsa =
1401 blk.video
1402 .attn1
1403 .forward(&vnorm, n, &vnorm, n, Some(&v_pe), Some(&v_pe), None, pool);
1404 if bi == 0 {
1405 trace("v.b0.sa.out", &vsa);
1406 }
1407 pt = prof.tick(P_SELF, pt);
1408 let mut vnormed = vec![0f32; n * dim];
1409 post_sa_rows(&mut vx, &vsa, &mut vnormed, n, dim, &v_msa, &vt.idx, pool);
1410 let mut vq = vec![0f32; n * dim];
1411 affine_rows(&vnormed, &mut vq, n, dim, &v_ca, &vt.idx, pool);
1412 let vctx = modulate_kv(
1413 &video.context,
1414 video.ctx_len,
1415 dim,
1416 &blk.video.prompt_sst,
1417 vpt.row(0),
1418 pool,
1419 );
1420 let vca =
1421 blk.video
1422 .attn2
1423 .forward(&vq, n, &vctx, video.ctx_len, None, None, vmask, pool);
1424 if bi == 0 {
1425 trace("v.b0.ca.in", &vq);
1426 trace("v.b0.ca.ctx", &vctx);
1427 trace("v.b0.ca.out", &vca);
1428 }
1429 add_gated(&mut vx, &vca, n, dim, &v_ca, &vt.idx, pool);
1430 pt = prof.tick(P_CROSS, pt);
1431
1432 let mut anorm = vec![0f32; m * a_dim];
1434 ada_zero_rows(&ax, &mut anorm, m, a_dim, &a_msa, &at.idx, pool);
1435 if bi == 0 {
1436 trace("a.b0.sa.in", &anorm);
1437 }
1438 let asa =
1439 blk.audio
1440 .attn1
1441 .forward(&anorm, m, &anorm, m, Some(&a_pe), Some(&a_pe), None, pool);
1442 if bi == 0 {
1443 trace("a.b0.sa.out", &asa);
1444 }
1445 let mut anormed = vec![0f32; m * a_dim];
1446 post_sa_rows(&mut ax, &asa, &mut anormed, m, a_dim, &a_msa, &at.idx, pool);
1447 let mut aq = vec![0f32; m * a_dim];
1448 affine_rows(&anormed, &mut aq, m, a_dim, &a_ca, &at.idx, pool);
1449 let actx = modulate_kv(
1450 &audio.context,
1451 audio.ctx_len,
1452 a_dim,
1453 &blk.audio.prompt_sst,
1454 apt.row(0),
1455 pool,
1456 );
1457 let aca =
1458 blk.audio
1459 .attn2
1460 .forward(&aq, m, &actx, audio.ctx_len, None, None, amask, pool);
1461 if bi == 0 {
1462 trace("a.b0.ca.in", &aq);
1463 trace("a.b0.ca.ctx", &actx);
1464 trace("a.b0.ca.out", &aca);
1465 }
1466 add_gated(&mut ax, &aca, m, a_dim, &a_ca, &at.idx, pool);
1467 pt = prof.tick(P_CROSS, pt);
1468
1469 let vx_pre = vx.clone();
1471 let ax_pre = ax.clone();
1472 let a2v_vp = vxs.pairs(&blk.sst_a2v_video, dim, 0);
1473 let a2v_ap = axs.pairs(&blk.sst_a2v_audio, a_dim, 0);
1474 let a2v_v = ada_pair(&vx_pre, n, dim, &a2v_vp, &vxs.idx, pool);
1475 let a2v_a = ada_pair(&ax_pre, m, a_dim, &a2v_ap, &axs.idx, pool);
1476 let a2v = blk
1477 .a2v
1478 .forward(&a2v_v, n, &a2v_a, m, Some(&v_xpe), Some(&a_xpe), None, pool);
1479 if bi == 0 {
1480 trace("v.b0.a2v.in", &a2v_v);
1481 trace("v.b0.a2v.ctx", &a2v_a);
1482 trace("v.b0.a2v.out", &a2v);
1483 }
1484 let gate_a2v = gate_row(&blk.sst_a2v_video, dim, vgt.row(0));
1485 add_scaled(&mut vx, &a2v, n, dim, &gate_a2v, pool);
1486 let v2a_ap = axs.pairs(&blk.sst_a2v_audio, a_dim, 2);
1487 let v2a_vp = vxs.pairs(&blk.sst_a2v_video, dim, 2);
1488 let v2a_a = ada_pair(&ax_pre, m, a_dim, &v2a_ap, &axs.idx, pool);
1489 let v2a_v = ada_pair(&vx_pre, n, dim, &v2a_vp, &vxs.idx, pool);
1490 let v2a = blk
1491 .v2a
1492 .forward(&v2a_a, m, &v2a_v, n, Some(&a_xpe), Some(&v_xpe), None, pool);
1493 if bi == 0 {
1494 trace("a.b0.v2a.in", &v2a_a);
1495 trace("a.b0.v2a.ctx", &v2a_v);
1496 trace("a.b0.v2a.out", &v2a);
1497 }
1498 let gate_v2a = gate_row(&blk.sst_a2v_audio, a_dim, agt.row(0));
1499 add_scaled(&mut ax, &v2a, m, a_dim, &gate_v2a, pool);
1500 pt = prof.tick(P_FUSE, pt);
1501
1502 let mut vsc = vec![0f32; n * dim];
1504 ada_zero_rows(&vx, &mut vsc, n, dim, &v_mlp, &vt.idx, pool);
1505 let vff = blk.video.ff(&vsc, n, pool);
1506 if bi == 0 {
1507 trace("v.b0.ff.in", &vsc);
1508 trace("v.b0.ff.out", &vff);
1509 }
1510 add_gated(&mut vx, &vff, n, dim, &v_mlp, &vt.idx, pool);
1511 let mut asc = vec![0f32; m * a_dim];
1512 ada_zero_rows(&ax, &mut asc, m, a_dim, &a_mlp, &at.idx, pool);
1513 let aff = blk.audio.ff(&asc, m, pool);
1514 if bi == 0 {
1515 trace("a.b0.ff.in", &asc);
1516 trace("a.b0.ff.out", &aff);
1517 }
1518 add_gated(&mut ax, &aff, m, a_dim, &a_mlp, &at.idx, pool);
1519 pt = prof.tick(P_FF, pt);
1520 trace(&format!("v.block{bi}"), &vx);
1521 trace(&format!("a.block{bi}"), &ax);
1522 }
1523
1524 prof.report();
1525
1526 let vout = head(&vx, n, dim, &self.sst_out, &vt, &self.proj_out, pool);
1528 let aout = head(&ax, m, a_dim, &self.a_sst_out, &at, &self.a_proj_out, pool);
1529 trace("v.out", &vout);
1530 trace("a.out", &aout);
1531 (vout, aout)
1532 }
1533}
1534
1535fn modulate_kv(
1538 ctx: &[f32],
1539 len: usize,
1540 dim: usize,
1541 table: &[f32],
1542 extra: &[f32],
1543 pool: Option<&Pool>,
1544) -> Vec<f32> {
1545 let mut out = vec![0f32; len * dim];
1546 let shift: Vec<f32> = (0..dim).map(|d| table[d] + extra[d]).collect();
1547 let scale: Vec<f32> = (0..dim).map(|d| table[dim + d] + extra[dim + d]).collect();
1548 let dst = Shared(out.as_mut_ptr());
1552 rows(pool, len, &|s, e| {
1553 let r = unsafe { dst.at(s * dim, (e - s) * dim) };
1554 for (row, i) in r.chunks_exact_mut(dim).zip(s..e) {
1555 for d in 0..dim {
1556 row[d] = ctx[i * dim + d] * (1.0 + scale[d]) + shift[d];
1557 }
1558 }
1559 });
1560 out
1561}
1562
1563fn ada_pair(
1565 x: &[f32],
1566 n: usize,
1567 dim: usize,
1568 pairs: &[[Vec<f32>; 2]],
1569 idx: &[usize],
1570 pool: Option<&Pool>,
1571) -> Vec<f32> {
1572 let mut out = vec![0f32; n * dim];
1573 let dst = Shared(out.as_mut_ptr());
1574 rows(pool, n, &|s, e| {
1575 let r = unsafe { dst.at(s * dim, (e - s) * dim) };
1576 for (row, i) in r.chunks_exact_mut(dim).zip(s..e) {
1577 let p = &pairs[idx[i]];
1578 rms_plain(&x[i * dim..(i + 1) * dim], row);
1579 for d in 0..dim {
1580 row[d] = row[d] * (1.0 + p[0][d]) + p[1][d];
1581 }
1582 }
1583 });
1584 out
1585}
1586
1587fn gate_row(table: &[f32], dim: usize, extra: &[f32]) -> Vec<f32> {
1590 (0..dim).map(|d| table[4 * dim + d] + extra[d]).collect()
1591}
1592
1593fn head(
1596 x: &[f32],
1597 n: usize,
1598 dim: usize,
1599 sst: &[f32],
1600 ts: &TsTable,
1601 proj: &Lin,
1602 pool: Option<&Pool>,
1603) -> Vec<f32> {
1604 let mut y = vec![0f32; n * dim];
1605 let dst = Shared(y.as_mut_ptr());
1606 rows(pool, n, &|s, e| {
1607 let r = unsafe { dst.at(s * dim, (e - s) * dim) };
1608 let mut ln = vec![0f32; dim];
1609 for (row, i) in r.chunks_exact_mut(dim).zip(s..e) {
1610 let emb = ts.emb_row(ts.idx[i.min(ts.idx.len() - 1)]);
1611 layer_norm(&x[i * dim..(i + 1) * dim], &mut ln);
1612 for d in 0..dim {
1613 row[d] = ln[d] * (1.0 + sst[dim + d] + emb[d]) + sst[d] + emb[d];
1614 }
1615 }
1616 });
1617 proj.apply(&y, n, pool)
1618}