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 fn chain(&self) -> Option<crate::gpu::LtxLin<'_>> {
217 if self.has_lora() {
218 return None;
219 }
220 let (w, dense): (Option<usize>, &[f32]) = match &self.w {
221 Proj::F32 { w, .. } => (None, w),
222 Proj::Q(q) => (Some(q.mapped_device_gemm()?.1), &[]),
223 };
224 Some(crate::gpu::LtxLin {
225 w,
226 dense,
227 rows: self.w.rows(),
228 cols: self.w.cols(),
229 bias: self.b.as_deref().unwrap_or(&[]),
230 })
231 }
232
233 pub(crate) fn add_bias(&self, out: &mut [f32], n: usize, pool: Option<&Pool>) {
235 let Some(b) = &self.b else { return };
236 let m = self.w.rows();
237 let dst = Shared(out.as_mut_ptr());
238 rows(pool, n, &|s, e| {
239 let r = unsafe { dst.at(s * m, (e - s) * m) };
240 for row in r.chunks_exact_mut(m) {
241 for (v, &bb) in row.iter_mut().zip(b) {
242 *v += bb;
243 }
244 }
245 });
246 }
247
248 pub(crate) fn apply(&self, x: &[f32], n: usize, pool: Option<&Pool>) -> Vec<f32> {
249 let m = self.w.rows();
250 let cols = self.w.cols();
251 let t_alloc = std::time::Instant::now();
252 let mut out = vec![0f32; n * m];
253 attn_prof::ALLOC.fetch_add(
254 t_alloc.elapsed().as_micros() as u64,
255 std::sync::atomic::Ordering::Relaxed,
256 );
257 let t_mm = std::time::Instant::now();
258 #[cfg(target_os = "macos")]
264 if let (Some(l), Some((model, idx, r, cl))) = (&self.lora, self.mapped()) {
265 if l.live()
269 && crate::ltxlora::route_threshold().is_none()
270 && n >= 32
271 && n * r * cl >= 128_000_000
272 && cl % 32 == 0
273 && !crate::gpu::mm_killed()
274 && crate::gpu::enabled_here()
275 && crate::gpu_metal::q4tp_matmat_lora(model, idx, x, n, r, cl, &mut out, &l.side())
276 {
277 attn_prof::MATMAT.fetch_add(
278 t_mm.elapsed().as_micros() as u64,
279 std::sync::atomic::Ordering::Relaxed,
280 );
281 self.add_bias(&mut out, n, pool);
282 return out;
283 }
284 }
285 let per_row = cols * 4;
290 let chunk = (0x1000_0000usize / per_row.max(1)).max(1);
291 let mut done = 0usize;
292 while done < n {
293 let take = chunk.min(n - done);
294 self.w.matmat(
295 &x[done * cols..(done + take) * cols],
296 take,
297 &mut out[done * m..(done + take) * m],
298 pool,
299 );
300 done += take;
301 }
302 attn_prof::MATMAT.fetch_add(
303 t_mm.elapsed().as_micros() as u64,
304 std::sync::atomic::Ordering::Relaxed,
305 );
306 if let Some(l) = &self.lora {
307 l.add(x, n, &mut out, pool);
308 }
309 if let Some(b) = &self.b {
310 let dst = Shared(out.as_mut_ptr());
311 rows(pool, n, &|s, e| {
312 let r = unsafe { dst.at(s * m, (e - s) * m) };
313 for row in r.chunks_exact_mut(m) {
314 for (v, &bb) in row.iter_mut().zip(b) {
315 *v += bb;
316 }
317 }
318 });
319 }
320 out
321 }
322}
323
324pub struct Rope {
328 cos: Vec<f32>,
329 sin: Vec<f32>,
330 heads: usize,
331 half: usize,
332}
333
334impl Rope {
335 pub fn build(
339 positions: &[Vec<f64>],
340 max_pos: &[f64],
341 dim: usize,
342 heads: usize,
343 theta: f64,
344 ) -> Rope {
345 let ndim = max_pos.len();
346 let count = dim / (2 * ndim);
347 let idx: Vec<f64> = (0..count)
349 .map(|j| {
350 let e = if count > 1 {
351 j as f64 / (count - 1) as f64
352 } else {
353 0.0
354 };
355 theta.powf(e) * std::f64::consts::PI / 2.0
356 })
357 .collect();
358 let n = positions.len();
359 let half = dim / 2;
360 let pad = half - count * ndim;
361 let mut cos = vec![0f32; n * half];
362 let mut sin = vec![0f32; n * half];
363 for (t, p) in positions.iter().enumerate() {
364 let base = t * half;
365 for i in 0..pad {
366 cos[base + i] = 1.0;
367 }
368 for (j, &ind) in idx.iter().enumerate() {
369 for (d, &mp) in max_pos.iter().enumerate() {
370 let f = ind * (p[d] / mp * 2.0 - 1.0);
371 let o = base + pad + j * ndim + d;
372 cos[o] = f.cos() as f32;
373 sin[o] = f.sin() as f32;
374 }
375 }
376 }
377 Rope {
378 cos,
379 sin,
380 heads,
381 half: half / heads,
382 }
383 }
384
385 fn apply_row(&self, t: usize, row: &mut [f32]) {
387 let dh = self.half * 2;
388 let stride = self.heads * self.half;
389 for h in 0..self.heads {
390 let off = t * stride + h * self.half;
391 let (c, s) = (
392 &self.cos[off..off + self.half],
393 &self.sin[off..off + self.half],
394 );
395 let v = &mut row[h * dh..(h + 1) * dh];
396 for i in 0..self.half {
397 let (a, b) = (v[i], v[i + self.half]);
398 v[i] = a * c[i] - b * s[i];
399 v[i + self.half] = b * c[i] + a * s[i];
400 }
401 }
402 }
403}
404
405pub(crate) mod attn_prof {
409 use std::sync::atomic::{AtomicU64, Ordering::Relaxed};
410 pub static PROJ: AtomicU64 = AtomicU64::new(0);
411 pub static NORM: AtomicU64 = AtomicU64::new(0);
412 pub static GATHER: AtomicU64 = AtomicU64::new(0);
413 pub static SCORE: AtomicU64 = AtomicU64::new(0);
414 pub static SOFT: AtomicU64 = AtomicU64::new(0);
415 pub static VALUE: AtomicU64 = AtomicU64::new(0);
416 pub static OUT: AtomicU64 = AtomicU64::new(0);
417 pub static ALLOC: AtomicU64 = AtomicU64::new(0);
418 pub static MATMAT: AtomicU64 = AtomicU64::new(0);
419
420 pub fn add(c: &AtomicU64, t: std::time::Instant) -> std::time::Instant {
421 c.fetch_add(t.elapsed().as_micros() as u64, Relaxed);
422 std::time::Instant::now()
423 }
424
425 pub fn report() -> String {
426 let s = |c: &AtomicU64| c.swap(0, Relaxed) as f64 / 1e6;
427 format!(
428 "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]",
429 s(&PROJ),
430 s(&NORM),
431 s(&GATHER),
432 s(&SCORE),
433 s(&SOFT),
434 s(&VALUE),
435 s(&OUT),
436 s(&ALLOC),
437 s(&MATMAT)
438 )
439 }
440}
441
442pub(crate) struct Attn {
445 q: Lin,
446 k: Lin,
447 v: Lin,
448 o: Lin,
449 q_norm: Vec<f32>,
450 k_norm: Vec<f32>,
451 gate: Option<Lin>,
452 heads: usize,
453 dh: usize,
454}
455
456impl Attn {
457 pub(crate) fn load(
458 model: &Arc<CmfModel>,
459 p: &str,
460 heads: usize,
461 dh: usize,
462 ) -> Result<Attn, String> {
463 Attn::load_lora(model, p, heads, dh, None)
464 }
465
466 pub(crate) fn load_lora(
467 model: &Arc<CmfModel>,
468 p: &str,
469 heads: usize,
470 dh: usize,
471 bank: Option<&crate::ltxlora::LoraBank>,
472 ) -> Result<Attn, String> {
473 Ok(Attn {
474 q: Lin::load_lora(model, &format!("{p}.to_q"), true, bank)?,
475 k: Lin::load_lora(model, &format!("{p}.to_k"), true, bank)?,
476 v: Lin::load_lora(model, &format!("{p}.to_v"), true, bank)?,
477 o: Lin::load_lora(model, &format!("{p}.to_out.0"), true, bank)?,
478 q_norm: cmf_f32(model, &format!("{p}.q_norm.weight"))?,
479 k_norm: cmf_f32(model, &format!("{p}.k_norm.weight"))?,
480 gate: match model.tensor(&format!("{p}.to_gate_logits.weight")) {
481 Some(_) => Some(Lin::load(model, &format!("{p}.to_gate_logits"), true)?),
482 None => None,
483 },
484 heads,
485 dh,
486 })
487 }
488
489 fn chain(&self) -> Option<crate::gpu::LtxAttn<'_>> {
491 Some(crate::gpu::LtxAttn {
492 q: self.q.chain()?,
493 k: self.k.chain()?,
494 v: self.v.chain()?,
495 o: self.o.chain()?,
496 q_norm: &self.q_norm,
497 k_norm: &self.k_norm,
498 gate: match &self.gate {
499 Some(g) => Some(g.chain()?),
500 None => None,
501 },
502 heads: self.heads,
503 dh: self.dh,
504 })
505 }
506
507 #[cfg(target_os = "macos")]
512 fn fused_qkv(
513 &self,
514 x: &[f32],
515 n: usize,
516 ctx: &[f32],
517 m: usize,
518 pool: Option<&Pool>,
519 ) -> Option<(Vec<f32>, Vec<f32>, Vec<f32>)> {
520 if !std::ptr::eq(x.as_ptr(), ctx.as_ptr()) || n != m {
521 return None;
522 }
523 if !crate::gpu::enabled_here() || crate::gpu::mm_killed() {
524 return None;
525 }
526 if self.q.has_lora() || self.k.has_lora() || self.v.has_lora() {
531 return None;
532 }
533 let (qw, kw, vw) = (self.q.mapped()?, self.k.mapped()?, self.v.mapped()?);
534 if !Arc::ptr_eq(qw.0, kw.0) || !Arc::ptr_eq(qw.0, vw.0) {
535 return None;
536 }
537 let jobs = [
538 crate::gpu_metal::MmJob {
539 idx: qw.1,
540 rows: qw.2,
541 cols: qw.3,
542 },
543 crate::gpu_metal::MmJob {
544 idx: kw.1,
545 rows: kw.2,
546 cols: kw.3,
547 },
548 crate::gpu_metal::MmJob {
549 idx: vw.1,
550 rows: vw.2,
551 cols: vw.3,
552 },
553 ];
554 if n * jobs[0].rows * jobs[0].cols < 128_000_000 || n < 32 {
555 return None;
556 }
557 let mut oq = vec![0f32; n * jobs[0].rows];
558 let mut ok = vec![0f32; n * jobs[1].rows];
559 let mut ov = vec![0f32; n * jobs[2].rows];
560 let done = {
561 let mut outs: [&mut [f32]; 3] = [&mut oq, &mut ok, &mut ov];
562 crate::gpu_metal::q4tp_matmat_many(qw.0, &jobs, x, n, &mut outs)
563 };
564 if !done {
565 return None;
566 }
567 self.q.add_lora(&mut oq, x, n, pool);
568 self.k.add_lora(&mut ok, x, n, pool);
569 self.v.add_lora(&mut ov, x, n, pool);
570 self.q.add_bias(&mut oq, n, pool);
571 self.k.add_bias(&mut ok, n, pool);
572 self.v.add_bias(&mut ov, n, pool);
573 Some((oq, ok, ov))
574 }
575
576 #[cfg(target_os = "macos")]
579 fn fused_kv(&self, ctx: &[f32], m: usize) -> Option<(Vec<f32>, Vec<f32>)> {
580 if !crate::gpu::enabled_here() || crate::gpu::mm_killed() {
581 return None;
582 }
583 if self.k.has_lora() || self.v.has_lora() {
584 return None;
585 }
586 let (kw, vw) = (self.k.mapped()?, self.v.mapped()?);
587 if !Arc::ptr_eq(kw.0, vw.0) {
588 return None;
589 }
590 let jobs = [
591 crate::gpu_metal::MmJob {
592 idx: kw.1,
593 rows: kw.2,
594 cols: kw.3,
595 },
596 crate::gpu_metal::MmJob {
597 idx: vw.1,
598 rows: vw.2,
599 cols: vw.3,
600 },
601 ];
602 if m < 32 || m * jobs[0].rows * jobs[0].cols < 128_000_000 {
603 return None;
604 }
605 let mut ok = vec![0f32; m * jobs[0].rows];
606 let mut ov = vec![0f32; m * jobs[1].rows];
607 let done = {
608 let mut outs: [&mut [f32]; 2] = [&mut ok, &mut ov];
609 crate::gpu_metal::q4tp_matmat_many(kw.0, &jobs, ctx, m, &mut outs)
610 };
611 if !done {
612 return None;
613 }
614 self.k.add_lora(&mut ok, ctx, m, None);
615 self.v.add_lora(&mut ov, ctx, m, None);
616 self.k.add_bias(&mut ok, m, None);
617 self.v.add_bias(&mut ov, m, None);
618 Some((ok, ov))
619 }
620
621 #[cfg(not(target_os = "macos"))]
622 fn fused_kv(&self, _ctx: &[f32], _m: usize) -> Option<(Vec<f32>, Vec<f32>)> {
623 None
624 }
625
626 #[cfg(not(target_os = "macos"))]
627 fn fused_qkv(
628 &self,
629 _x: &[f32],
630 _n: usize,
631 _ctx: &[f32],
632 _m: usize,
633 _pool: Option<&Pool>,
634 ) -> Option<(Vec<f32>, Vec<f32>, Vec<f32>)> {
635 None
636 }
637
638 #[allow(clippy::too_many_arguments)]
641 pub(crate) fn forward(
642 &self,
643 x: &[f32],
644 n: usize,
645 ctx: &[f32],
646 m: usize,
647 pe_q: Option<&Rope>,
648 pe_k: Option<&Rope>,
649 mask: Option<&[f32]>,
650 pool: Option<&Pool>,
651 ) -> Vec<f32> {
652 let inner = self.heads * self.dh;
653 let prof = std::env::var("CMF_LTX_PROF").is_ok();
654 let mut t = std::time::Instant::now();
655 let (mut q, mut k, v) = match self.fused_qkv(x, n, ctx, m, pool) {
661 Some(t) => t,
662 None => match self.fused_kv(ctx, m) {
666 Some((k, v)) => (self.q.apply(x, n, pool), k, v),
667 None => (
668 self.q.apply(x, n, pool),
669 self.k.apply(ctx, m, pool),
670 self.v.apply(ctx, m, pool),
671 ),
672 },
673 };
674 if prof {
675 t = attn_prof::add(&attn_prof::PROJ, t);
676 }
677
678 let qn = Shared(q.as_mut_ptr());
679 rows(pool, n, &|s, e| {
680 let r = unsafe { qn.at(s * inner, (e - s) * inner) };
681 for (i, row) in r.chunks_exact_mut(inner).enumerate() {
682 rms_w(row, &self.q_norm);
683 if let Some(pe) = pe_q {
684 pe.apply_row(s + i, row);
685 }
686 }
687 });
688 let kn = Shared(k.as_mut_ptr());
689 rows(pool, m, &|s, e| {
690 let r = unsafe { kn.at(s * inner, (e - s) * inner) };
691 for (i, row) in r.chunks_exact_mut(inner).enumerate() {
692 rms_w(row, &self.k_norm);
693 if let Some(pe) = pe_k {
694 pe.apply_row(s + i, row);
695 }
696 }
697 });
698
699 if prof {
705 t = attn_prof::add(&attn_prof::NORM, t);
706 }
707 let mut out = vec![0f32; n * inner];
708 let scale = 1.0 / (self.dh as f32).sqrt();
709 let dh = self.dh;
710 let mut qh = vec![0f32; n * dh];
711 let mut kh = vec![0f32; m * dh];
712 let mut vh = vec![0f32; m * dh];
713 let mut sc = vec![0f32; n * m];
714 let mut oh = vec![0f32; n * dh];
715 let pv_nt = std::env::var("CMF_LTX_PV_NT").as_deref() == Ok("1");
735 for h in 0..self.heads {
736 for i in 0..n {
737 qh[i * dh..(i + 1) * dh].copy_from_slice(&q[i * inner + h * dh..][..dh]);
738 }
739 for j in 0..m {
740 kh[j * dh..(j + 1) * dh].copy_from_slice(&k[j * inner + h * dh..][..dh]);
741 }
742 if pv_nt {
743 for j in 0..m {
745 let src = &v[j * inner + h * dh..][..dh];
746 for (d, s) in src.iter().enumerate() {
747 vh[d * m + j] = *s;
748 }
749 }
750 } else {
751 for j in 0..m {
752 vh[j * dh..(j + 1) * dh].copy_from_slice(&v[j * inner + h * dh..][..dh]);
753 }
754 }
755 if prof {
756 t = attn_prof::add(&attn_prof::GATHER, t);
757 }
758 crate::fcd_ops::gemm_nt(&qh, &kh, &mut sc, n, dh, m, pool);
759 if prof {
760 t = attn_prof::add(&attn_prof::SCORE, t);
761 }
762 let sp = Shared(sc.as_mut_ptr());
763 rows(pool, n, &|s, e| {
764 let r = unsafe { sp.at(s * m, (e - s) * m) };
765 for row in r.chunks_exact_mut(m) {
766 for (x, j) in row.iter_mut().zip(0..m) {
767 *x = *x * scale + mask.map_or(0.0, |mk| mk[j]);
768 }
769 softmax(row);
770 }
771 });
772 if prof {
773 t = attn_prof::add(&attn_prof::SOFT, t);
774 }
775 oh.iter_mut().for_each(|x| *x = 0.0);
776 if pv_nt {
777 crate::fcd_ops::gemm_nt(&sc, &vh, &mut oh, n, m, dh, pool);
780 } else {
781 crate::fcd_ops::gemm_dx(&sc, &vh, &mut oh, n, dh, m, pool);
782 }
783 if prof {
784 t = attn_prof::add(&attn_prof::VALUE, t);
785 }
786 for i in 0..n {
787 out[i * inner + h * dh..i * inner + (h + 1) * dh]
788 .copy_from_slice(&oh[i * dh..(i + 1) * dh]);
789 }
790 if prof {
791 t = attn_prof::add(&attn_prof::GATHER, t);
792 }
793 }
794
795 if let Some(g) = &self.gate {
796 let logits = g.apply(x, n, pool);
797 let h = self.heads;
798 let dst = Shared(out.as_mut_ptr());
799 rows(pool, n, &|s, e| {
800 let r = unsafe { dst.at(s * inner, (e - s) * inner) };
801 for (i, row) in r.chunks_exact_mut(inner).enumerate() {
802 for hh in 0..h {
803 let gate = 2.0 / (1.0 + (-logits[(s + i) * h + hh]).exp());
804 for d in row[hh * self.dh..(hh + 1) * self.dh].iter_mut() {
805 *d *= gate;
806 }
807 }
808 }
809 });
810 }
811 let r = self.o.apply(&out, n, pool);
812 if prof {
813 attn_prof::add(&attn_prof::OUT, t);
814 }
815 r
816 }
817}
818
819struct AdaLn {
824 l1: Lin,
825 l2: Lin,
826 lin: Lin,
827 dim: usize,
828}
829
830impl AdaLn {
831 fn load(model: &Arc<CmfModel>, p: &str, dim: usize) -> Result<AdaLn, String> {
832 Ok(AdaLn {
833 l1: Lin::load(model, &format!("{p}.emb.timestep_embedder.linear_1"), true)?,
834 l2: Lin::load(model, &format!("{p}.emb.timestep_embedder.linear_2"), true)?,
835 lin: Lin::load(model, &format!("{p}.linear"), true)?,
836 dim,
837 })
838 }
839
840 fn forward(&self, t: &[f32], pool: Option<&Pool>) -> (Vec<f32>, Vec<f32>) {
842 let n = t.len();
843 let half = 128usize;
846 let mut proj = vec![0f32; n * 256];
847 let ws: Vec<f64> = (0..half)
848 .map(|j| (-(10000f64).ln() * j as f64 / half as f64).exp())
849 .collect();
850 for (i, &tv) in t.iter().enumerate() {
851 for (j, &w) in ws.iter().enumerate() {
852 let a = tv as f64 * w;
853 proj[i * 256 + j] = a.cos() as f32;
854 proj[i * 256 + half + j] = a.sin() as f32;
855 }
856 }
857 let mut h = self.l1.apply(&proj, n, pool);
858 for v in h.iter_mut() {
859 *v = silu(*v);
860 }
861 let embedded = self.l2.apply(&h, n, pool);
862 let mut act = embedded.clone();
863 for v in act.iter_mut() {
864 *v = silu(*v);
865 }
866 (self.lin.apply(&act, n, pool), embedded)
867 }
868}
869
870struct TsTable {
875 vals: Vec<f32>,
876 emb: Vec<f32>,
877 idx: Vec<usize>,
878 width: usize,
879 edim: usize,
880}
881
882impl TsTable {
883 fn build(a: &AdaLn, ts: &[f32], scale: f64, pool: Option<&Pool>) -> TsTable {
884 let mut vals: Vec<f32> = Vec::new();
885 let mut idx = Vec::with_capacity(ts.len());
886 for &t in ts {
887 match vals.iter().position(|&v| v.to_bits() == t.to_bits()) {
888 Some(i) => idx.push(i),
889 None => {
890 vals.push(t);
891 idx.push(vals.len() - 1);
892 }
893 }
894 }
895 let scaled: Vec<f32> = vals.iter().map(|&v| (v as f64 * scale) as f32).collect();
896 let (v, e) = a.forward(&scaled, pool);
897 let width = v.len() / scaled.len().max(1);
898 TsTable {
899 vals: v,
900 emb: e,
901 idx,
902 width,
903 edim: a.dim,
904 }
905 }
906
907 fn distinct(&self) -> usize {
908 self.vals.len() / self.width.max(1)
909 }
910
911 fn row(&self, r: usize) -> &[f32] {
912 &self.vals[r * self.width..(r + 1) * self.width]
913 }
914
915 fn emb_row(&self, r: usize) -> &[f32] {
916 &self.emb[r * self.edim..(r + 1) * self.edim]
917 }
918
919 fn triples(&self, table: &[f32], dim: usize, off: usize) -> Vec<[Vec<f32>; 3]> {
923 (0..self.distinct())
924 .map(|r| {
925 let v = self.row(r);
926 std::array::from_fn(|j| {
927 let o = (off + j) * dim;
928 (0..dim).map(|d| table[o + d] + v[o + d]).collect()
929 })
930 })
931 .collect()
932 }
933
934 fn pairs(&self, table: &[f32], dim: usize, off: usize) -> Vec<[Vec<f32>; 2]> {
937 (0..self.distinct())
938 .map(|r| {
939 let v = self.row(r);
940 std::array::from_fn(|j| {
941 let o = (off + j) * dim;
942 (0..dim).map(|d| table[o + d] + v[o + d]).collect()
943 })
944 })
945 .collect()
946 }
947}
948
949fn ada_zero_rows(
953 x: &[f32],
954 out: &mut [f32],
955 n: usize,
956 dim: usize,
957 mods: &[[Vec<f32>; 3]],
958 idx: &[usize],
959 pool: Option<&Pool>,
960) {
961 let dst = Shared(out.as_mut_ptr());
962 rows(pool, n, &|s, e| {
963 let r = unsafe { dst.at(s * dim, (e - s) * dim) };
964 for (row, i) in r.chunks_exact_mut(dim).zip(s..e) {
965 let md = &mods[idx[i]];
966 rms_plain(&x[i * dim..(i + 1) * dim], row);
967 for d in 0..dim {
968 row[d] = row[d] * (1.0 + md[1][d]) + md[0][d];
969 }
970 }
971 });
972}
973
974fn add_gated(
976 x: &mut [f32],
977 y: &[f32],
978 n: usize,
979 dim: usize,
980 mods: &[[Vec<f32>; 3]],
981 idx: &[usize],
982 pool: Option<&Pool>,
983) {
984 let dst = Shared(x.as_mut_ptr());
985 rows(pool, n, &|s, e| {
986 let r = unsafe { dst.at(s * dim, (e - s) * dim) };
987 for (row, i) in r.chunks_exact_mut(dim).zip(s..e) {
988 let g = &mods[idx[i]][2];
989 for d in 0..dim {
990 row[d] += y[i * dim + d] * g[d];
991 }
992 }
993 });
994}
995
996fn post_sa_rows(
999 x: &mut [f32],
1000 y: &[f32],
1001 normed: &mut [f32],
1002 n: usize,
1003 dim: usize,
1004 mods: &[[Vec<f32>; 3]],
1005 idx: &[usize],
1006 pool: Option<&Pool>,
1007) {
1008 let a = Shared(x.as_mut_ptr());
1009 let b = Shared(normed.as_mut_ptr());
1010 rows(pool, n, &|s, e| {
1011 let xr = unsafe { a.at(s * dim, (e - s) * dim) };
1012 let nr = unsafe { b.at(s * dim, (e - s) * dim) };
1013 for ((row, nrow), i) in xr
1014 .chunks_exact_mut(dim)
1015 .zip(nr.chunks_exact_mut(dim))
1016 .zip(s..e)
1017 {
1018 let g = &mods[idx[i]][2];
1019 for d in 0..dim {
1020 row[d] += y[i * dim + d] * g[d];
1021 }
1022 rms_plain(row, nrow);
1023 }
1024 });
1025}
1026
1027fn add_scaled(x: &mut [f32], y: &[f32], n: usize, dim: usize, g: &[f32], pool: Option<&Pool>) {
1029 let dst = Shared(x.as_mut_ptr());
1030 rows(pool, n, &|s, e| {
1031 let r = unsafe { dst.at(s * dim, (e - s) * dim) };
1032 for (row, i) in r.chunks_exact_mut(dim).zip(s..e) {
1033 for d in 0..dim {
1034 row[d] += y[i * dim + d] * g[d];
1035 }
1036 }
1037 });
1038}
1039
1040fn affine_rows(
1042 x: &[f32],
1043 out: &mut [f32],
1044 n: usize,
1045 dim: usize,
1046 mods: &[[Vec<f32>; 3]],
1047 idx: &[usize],
1048 pool: Option<&Pool>,
1049) {
1050 let dst = Shared(out.as_mut_ptr());
1051 rows(pool, n, &|s, e| {
1052 let r = unsafe { dst.at(s * dim, (e - s) * dim) };
1053 for (row, i) in r.chunks_exact_mut(dim).zip(s..e) {
1054 let md = &mods[idx[i]];
1055 for d in 0..dim {
1056 row[d] = x[i * dim + d] * (1.0 + md[1][d]) + md[0][d];
1057 }
1058 }
1059 });
1060}
1061
1062#[derive(Default)]
1065struct Prof {
1066 on: bool,
1067 t: [f64; 6],
1068}
1069
1070const P_ADALN: usize = 0;
1071const P_SELF: usize = 1;
1072const P_CROSS: usize = 2;
1073const P_FUSE: usize = 3;
1074const P_FF: usize = 4;
1075const P_MOD: usize = 5;
1076
1077impl Prof {
1078 fn new() -> Prof {
1079 Prof {
1080 on: std::env::var("CMF_LTX_PROF").is_ok(),
1081 t: [0.0; 6],
1082 }
1083 }
1084 #[inline]
1085 fn tick(&mut self, slot: usize, at: std::time::Instant) -> std::time::Instant {
1086 if self.on {
1087 self.t[slot] += at.elapsed().as_secs_f64();
1088 return std::time::Instant::now();
1089 }
1090 at
1091 }
1092 fn report(&self) {
1093 if !self.on {
1094 return;
1095 }
1096 #[cfg(target_os = "macos")]
1099 {
1100 use std::sync::atomic::Ordering::Relaxed;
1101 let n = crate::gpu_metal::MM_N.swap(0, Relaxed);
1102 if n > 0 {
1103 let us = |a: &std::sync::atomic::AtomicU64| a.swap(0, Relaxed) as f64 / 1e6;
1104 println!(
1105 " q4tp on device: {n} calls, upload {:.2}s kernel {:.2}s download {:.2}s",
1106 us(&crate::gpu_metal::MM_UP),
1107 us(&crate::gpu_metal::MM_GPU),
1108 us(&crate::gpu_metal::MM_DN),
1109 );
1110 }
1111 }
1112 println!(" attention: {}", attn_prof::report());
1113 let names = [
1114 "adaln",
1115 "self-attn",
1116 "cross-attn",
1117 "a<->v",
1118 "ffn",
1119 "modulate",
1120 ];
1121 let total: f64 = self.t.iter().sum();
1122 let parts: Vec<String> = names
1123 .iter()
1124 .zip(&self.t)
1125 .map(|(n, v)| format!("{n} {v:.1}s ({:.0}%)", 100.0 * v / total.max(1e-9)))
1126 .collect();
1127 println!(" profile: {}", parts.join(" "));
1128 }
1129}
1130
1131struct Stream {
1134 attn1: Attn,
1135 attn2: Attn,
1136 ff_in: Lin,
1137 ff_out: Lin,
1138 sst: Vec<f32>, prompt_sst: Vec<f32>, }
1141
1142impl Stream {
1143 fn load(
1144 model: &Arc<CmfModel>,
1145 p: &str,
1146 prefix: &str,
1147 heads: usize,
1148 dh: usize,
1149 ff_bias: bool,
1150 bank: Option<&crate::ltxlora::LoraBank>,
1151 ) -> Result<Stream, String> {
1152 let a = |n: &str| format!("{p}.{prefix}{n}");
1153 Ok(Stream {
1154 attn1: Attn::load_lora(model, &a("attn1"), heads, dh, bank)?,
1155 attn2: Attn::load_lora(model, &a("attn2"), heads, dh, bank)?,
1156 ff_in: Lin::load_lora(model, &a("ff.net.0.proj"), ff_bias, bank)?,
1157 ff_out: Lin::load_lora(model, &a("ff.net.2"), ff_bias, bank)?,
1158 sst: cmf_f32(model, &a("scale_shift_table"))?,
1159 prompt_sst: cmf_f32(model, &a("prompt_scale_shift_table"))?,
1160 })
1161 }
1162
1163 fn ff(&self, x: &[f32], n: usize, pool: Option<&Pool>) -> Vec<f32> {
1164 let mut h = self.ff_in.apply(x, n, pool);
1165 gelu_tanh_rows(&mut h, pool);
1166 self.ff_out.apply(&h, n, pool)
1167 }
1168}
1169
1170struct Block {
1171 video: Stream,
1172 audio: Stream,
1173 a2v: Attn,
1174 v2a: Attn,
1175 sst_a2v_video: Vec<f32>, sst_a2v_audio: Vec<f32>, }
1178
1179pub struct StreamInput {
1184 pub latent: Vec<f32>,
1186 pub tokens: usize,
1187 pub timesteps: Vec<f32>,
1189 pub positions: Vec<Vec<f64>>,
1191 pub context: Vec<f32>,
1193 pub ctx_len: usize,
1194 pub context_mask: Vec<f32>,
1196 pub keyframes: Vec<f32>,
1198 pub sigma: f32,
1200}
1201
1202pub struct LtxDit {
1203 model: Arc<CmfModel>,
1204 blocks: Vec<Block>,
1205 patchify: Lin,
1206 a_patchify: Lin,
1207 keyframes_emb: Option<Vec<f32>>,
1208 adaln: AdaLn,
1209 a_adaln: AdaLn,
1210 prompt_adaln: AdaLn,
1211 a_prompt_adaln: AdaLn,
1212 av_v_ss: AdaLn,
1213 av_a_ss: AdaLn,
1214 av_a2v_gate: AdaLn,
1215 av_v2a_gate: AdaLn,
1216 proj_out: Lin,
1217 a_proj_out: Lin,
1218 sst_out: Vec<f32>,
1219 a_sst_out: Vec<f32>,
1220 pub heads: usize,
1221 pub dh: usize,
1222 pub a_heads: usize,
1223 pub a_dh: usize,
1224 pub max_pos: Vec<f64>,
1225 pub a_max_pos: Vec<f64>,
1226 pub cross_max_pos: f64,
1227 pub theta: f64,
1228 pub t_scale: f64,
1229 pub av_t_scale: f64,
1230 pub audio_cross_dim: usize,
1231}
1232
1233impl LtxDit {
1234 pub fn from_cmf(model: &Arc<CmfModel>) -> Result<LtxDit, String> {
1235 LtxDit::from_cmf_lora(model, None)
1236 }
1237
1238 pub fn from_cmf_lora(
1242 model: &Arc<CmfModel>,
1243 bank: Option<&crate::ltxlora::LoraBank>,
1244 ) -> Result<LtxDit, String> {
1245 let cfg_bytes = ["ltx.config_json", "dit.config_json"]
1246 .iter()
1247 .find_map(|n| model.tensor(n).map(|e| model.entry_bytes(e)))
1248 .ok_or("container carries no ltx.config_json")?;
1249 let cfg: serde_json::Value =
1250 serde_json::from_slice(cfg_bytes).map_err(|e| format!("ltx.config_json: {e}"))?;
1251 let t = cfg.get("transformer").unwrap_or(&cfg).clone();
1252 let g = |k: &str, d: f64| t.get(k).and_then(|v| v.as_f64()).unwrap_or(d);
1253 let heads = g("num_attention_heads", 32.0) as usize;
1254 let dh = g("attention_head_dim", 128.0) as usize;
1255 let a_heads = g("audio_num_attention_heads", 32.0) as usize;
1256 let a_dh = g("audio_attention_head_dim", 64.0) as usize;
1257 let n_layers = g("num_layers", 48.0) as usize;
1258 let ff_bias = t.get("ff_bias").and_then(|v| v.as_bool()).unwrap_or(true);
1259 let a_ff_bias = t
1260 .get("audio_ff_bias")
1261 .and_then(|v| v.as_bool())
1262 .unwrap_or(true);
1263 let arr = |k: &str, d: Vec<f64>| -> Vec<f64> {
1264 t.get(k)
1265 .and_then(|v| v.as_array())
1266 .map(|a| a.iter().filter_map(|x| x.as_f64()).collect())
1267 .unwrap_or(d)
1268 };
1269 let max_pos = arr("positional_embedding_max_pos", vec![20.0, 2048.0, 2048.0]);
1270 let a_max_pos = arr("audio_positional_embedding_max_pos", vec![20.0]);
1271 let cross_max_pos = max_pos[0].max(a_max_pos[0]);
1272 let dim = heads * dh;
1273 let a_dim = a_heads * a_dh;
1274
1275 let mut blocks = Vec::with_capacity(n_layers);
1276 for i in 0..n_layers {
1277 let p = format!("dit.transformer_blocks.{i}");
1278 blocks.push(Block {
1279 video: Stream::load(model, &p, "", heads, dh, ff_bias, bank)?,
1280 audio: Stream::load(model, &p, "audio_", a_heads, a_dh, a_ff_bias, bank)?,
1281 a2v: Attn::load(model, &format!("{p}.audio_to_video_attn"), a_heads, a_dh)?,
1282 v2a: Attn::load(model, &format!("{p}.video_to_audio_attn"), a_heads, a_dh)?,
1283 sst_a2v_video: cmf_f32(model, &format!("{p}.scale_shift_table_a2v_ca_video"))?,
1284 sst_a2v_audio: cmf_f32(model, &format!("{p}.scale_shift_table_a2v_ca_audio"))?,
1285 });
1286 }
1287 let _ = (dim, a_dim);
1288 Ok(LtxDit {
1289 blocks,
1290 patchify: Lin::load(model, "dit.patchify_proj", true)?,
1291 a_patchify: Lin::load(model, "dit.audio_patchify_proj", true)?,
1292 keyframes_emb: match model.tensor("dit.keyframes_abs_pos_embedding") {
1293 Some(_) => Some(cmf_f32(model, "dit.keyframes_abs_pos_embedding")?),
1294 None => None,
1295 },
1296 adaln: AdaLn::load(model, "dit.adaln_single", dim)?,
1297 a_adaln: AdaLn::load(model, "dit.audio_adaln_single", a_dim)?,
1298 prompt_adaln: AdaLn::load(model, "dit.prompt_adaln_single", dim)?,
1299 a_prompt_adaln: AdaLn::load(model, "dit.audio_prompt_adaln_single", a_dim)?,
1300 av_v_ss: AdaLn::load(model, "dit.av_ca_video_scale_shift_adaln_single", dim)?,
1301 av_a_ss: AdaLn::load(model, "dit.av_ca_audio_scale_shift_adaln_single", a_dim)?,
1302 av_a2v_gate: AdaLn::load(model, "dit.av_ca_a2v_gate_adaln_single", dim)?,
1303 av_v2a_gate: AdaLn::load(model, "dit.av_ca_v2a_gate_adaln_single", a_dim)?,
1304 proj_out: Lin::load(model, "dit.proj_out", true)?,
1305 a_proj_out: Lin::load(model, "dit.audio_proj_out", true)?,
1306 sst_out: cmf_f32(model, "dit.scale_shift_table")?,
1307 a_sst_out: cmf_f32(model, "dit.audio_scale_shift_table")?,
1308 heads,
1309 dh,
1310 a_heads,
1311 a_dh,
1312 max_pos,
1313 a_max_pos,
1314 cross_max_pos,
1315 theta: g("positional_embedding_theta", 10000.0),
1316 t_scale: g("timestep_scale_multiplier", 1000.0),
1317 av_t_scale: g("av_ca_timestep_scale_multiplier", 1.0),
1318 audio_cross_dim: g("audio_cross_attention_dim", 2048.0) as usize,
1319 model: model.clone(),
1320 })
1321 }
1322
1323 pub fn blocks(&self) -> usize {
1324 self.blocks.len()
1325 }
1326
1327 pub fn container(&self) -> &Arc<CmfModel> {
1328 &self.model
1329 }
1330
1331 pub fn forward(
1335 &self,
1336 video: &StreamInput,
1337 audio: &StreamInput,
1338 pool: Option<&Pool>,
1339 ) -> (Vec<f32>, Vec<f32>) {
1340 self.forward_impl(video, audio, pool, &mut |_, _| {}, true)
1341 }
1342
1343 fn run_chain(
1347 &self,
1348 video: &StreamInput,
1349 audio: &StreamInput,
1350 vx: &mut [f32],
1351 ax: &mut [f32],
1352 ts: [&TsTable; 8],
1353 pes: [&Rope; 4],
1354 ) -> bool {
1355 use crate::gpu::{LtxBlock, LtxChain, LtxSide, LtxStream};
1356 let [vt, at, vpt, apt, vxs, axs, vgt, agt] = ts;
1357 let [v_pe, a_pe, v_xpe, a_xpe] = pes;
1358 let (dim, a_dim) = (self.heads * self.dh, self.a_heads * self.a_dh);
1359 let sum = |t: &TsTable, table: &[f32], len: usize, all: bool| -> Option<Vec<f32>> {
1362 if table.len() < len || t.width < len || t.distinct() == 0 {
1363 return None;
1364 }
1365 let rows = if all { t.distinct() } else { 1 };
1366 let mut o = Vec::with_capacity(rows * len);
1367 for r in 0..rows {
1368 o.extend(table[..len].iter().zip(&t.row(r)[..len]).map(|(a, b)| a + b));
1369 }
1370 Some(o)
1371 };
1372 struct Tabs {
1373 v: Vec<f32>,
1374 a: Vec<f32>,
1375 vc: Vec<f32>,
1376 ac: Vec<f32>,
1377 xv: Vec<f32>,
1378 xa: Vec<f32>,
1379 ga: Vec<f32>,
1380 gv: Vec<f32>,
1381 }
1382 let mut tabs: Vec<Tabs> = Vec::with_capacity(self.blocks.len());
1383 for b in &self.blocks {
1384 if b.sst_a2v_video.len() < 5 * dim || b.sst_a2v_audio.len() < 5 * a_dim {
1385 return false;
1386 }
1387 let (Some(v), Some(a), Some(vc), Some(ac), Some(xv), Some(xa), Some(ga), Some(gv)) = (
1388 sum(vt, &b.video.sst, 9 * dim, true),
1389 sum(at, &b.audio.sst, 9 * a_dim, true),
1390 sum(vpt, &b.video.prompt_sst, 2 * dim, false),
1391 sum(apt, &b.audio.prompt_sst, 2 * a_dim, false),
1392 sum(vxs, &b.sst_a2v_video, 4 * dim, true),
1393 sum(axs, &b.sst_a2v_audio, 4 * a_dim, true),
1394 sum(vgt, &b.sst_a2v_video[4 * dim..], dim, false),
1395 sum(agt, &b.sst_a2v_audio[4 * a_dim..], a_dim, false),
1396 ) else {
1397 return false;
1398 };
1399 tabs.push(Tabs { v, a, vc, ac, xv, xa, ga, gv });
1400 }
1401 fn stream<'a>(s: &'a Stream, mods: &'a [f32], ctx_mod: &'a [f32]) -> Option<LtxStream<'a>> {
1402 Some(LtxStream {
1403 attn1: s.attn1.chain()?,
1404 attn2: s.attn2.chain()?,
1405 ff_in: s.ff_in.chain()?,
1406 ff_out: s.ff_out.chain()?,
1407 mods,
1408 ctx_mod,
1409 })
1410 }
1411 let mut blocks = Vec::with_capacity(self.blocks.len());
1412 for (b, t) in self.blocks.iter().zip(&tabs) {
1413 let (Some(v), Some(a), Some(a2v), Some(v2a)) =
1414 (stream(&b.video, &t.v, &t.vc), stream(&b.audio, &t.a, &t.ac), b.a2v.chain(), b.v2a.chain())
1415 else {
1416 return false;
1417 };
1418 blocks.push(LtxBlock {
1419 video: v,
1420 audio: a,
1421 a2v,
1422 v2a,
1423 xmods_v: &t.xv,
1424 xmods_a: &t.xa,
1425 gate_a2v: &t.ga,
1426 gate_v2a: &t.gv,
1427 });
1428 }
1429 let u32s = |i: &[usize]| i.iter().map(|&v| v as u32).collect::<Vec<u32>>();
1430 let (vr, vxr, ar, axr) = (u32s(&vt.idx), u32s(&vxs.idx), u32s(&at.idx), u32s(&axs.idx));
1431 fn side<'a>(
1432 s: &'a StreamInput,
1433 dim: usize,
1434 rows: &'a [u32],
1435 xrows: &'a [u32],
1436 pe: &'a Rope,
1437 xpe: &'a Rope,
1438 ) -> LtxSide<'a> {
1439 LtxSide {
1440 n: s.tokens,
1441 dim,
1442 rows,
1443 xrows,
1444 pe: (&pe.cos, &pe.sin, pe.half),
1445 xpe: (&xpe.cos, &xpe.sin, xpe.half),
1446 ctx: &s.context,
1447 ctx_len: s.ctx_len,
1448 mask: &s.context_mask,
1449 }
1450 }
1451 let job = LtxChain {
1452 model: &self.model,
1453 blocks: &blocks,
1454 video: side(video, dim, &vr, &vxr, v_pe, v_xpe),
1455 audio: side(audio, a_dim, &ar, &axr, a_pe, a_xpe),
1456 };
1457 crate::gpu::ltx_chain(&job, vx, ax)
1458 }
1459
1460 pub fn forward_traced(
1461 &self,
1462 video: &StreamInput,
1463 audio: &StreamInput,
1464 pool: Option<&Pool>,
1465 trace: &mut dyn FnMut(&str, &[f32]),
1466 ) -> (Vec<f32>, Vec<f32>) {
1467 self.forward_impl(video, audio, pool, trace, false)
1469 }
1470
1471 fn forward_impl(
1472 &self,
1473 video: &StreamInput,
1474 audio: &StreamInput,
1475 pool: Option<&Pool>,
1476 trace: &mut dyn FnMut(&str, &[f32]),
1477 chain_ok: bool,
1478 ) -> (Vec<f32>, Vec<f32>) {
1479 let _trust = crate::gpu::trust_gpu();
1483 let dim = self.heads * self.dh;
1484 let a_dim = self.a_heads * self.a_dh;
1485 let (n, m) = (video.tokens, audio.tokens);
1486
1487 let mut vx = self.patchify.apply(&video.latent, n, pool);
1489 if let Some(emb) = &self.keyframes_emb {
1490 for i in 0..n {
1491 if video.keyframes.get(i).copied().unwrap_or(0.0) > 0.0 {
1492 for (d, &e) in vx[i * dim..(i + 1) * dim].iter_mut().zip(emb) {
1493 *d += e;
1494 }
1495 }
1496 }
1497 }
1498 let mut ax = self.a_patchify.apply(&audio.latent, m, pool);
1499 trace("v.args.x", &vx);
1500 trace("a.args.x", &ax);
1501
1502 let to_chain = chain_ok
1507 && std::env::var("CMF_LTX_CHAIN").as_deref() != Ok("0")
1508 && crate::gpu::enabled_here()
1509 && crate::gpu::ltx_chain_available();
1510 let build = |a: &AdaLn, ts: &[f32], scale: f64| {
1511 if to_chain {
1512 crate::gpu::cpu_scope(|| TsTable::build(a, ts, scale, pool))
1513 } else {
1514 TsTable::build(a, ts, scale, pool)
1515 }
1516 };
1517 let vt = build(&self.adaln, &video.timesteps, self.t_scale);
1518 let at = build(&self.a_adaln, &audio.timesteps, self.t_scale);
1519 let vpt = build(&self.prompt_adaln, &[video.sigma], self.t_scale);
1520 let apt = build(&self.a_prompt_adaln, &[audio.sigma], self.t_scale);
1521 if std::env::var("CMF_LTX_PROMPTADALN").is_ok() {
1522 let r = vpt.row(0);
1523 let mx = r.iter().fold(0f32, |m, &v| m.max(v.abs()));
1524 let sum: f32 = r.iter().sum();
1525 eprintln!(
1526 "prompt-adaln sigma={:.6} max|row|={mx:.6e} sum={sum:.6e}",
1527 video.sigma
1528 );
1529 }
1530 let vxs = build(&self.av_v_ss, &video.timesteps, self.t_scale);
1531 let axs = build(&self.av_a_ss, &audio.timesteps, self.t_scale);
1532 let vgt = build(&self.av_a2v_gate, &[audio.sigma], self.av_t_scale);
1535 let agt = build(&self.av_v2a_gate, &[video.sigma], self.av_t_scale);
1536
1537 let v_pe = Rope::build(&video.positions, &self.max_pos, dim, self.heads, self.theta);
1539 let a_pe = Rope::build(
1540 &audio.positions,
1541 &self.a_max_pos,
1542 a_dim,
1543 self.a_heads,
1544 self.theta,
1545 );
1546 let time_only = |p: &[Vec<f64>]| p.iter().map(|r| vec![r[0]]).collect::<Vec<_>>();
1547 let v_xpe = Rope::build(
1548 &time_only(&video.positions),
1549 &[self.cross_max_pos],
1550 self.audio_cross_dim,
1551 self.heads,
1552 self.theta,
1553 );
1554 let a_xpe = Rope::build(
1555 &time_only(&audio.positions),
1556 &[self.cross_max_pos],
1557 self.audio_cross_dim,
1558 self.a_heads,
1559 self.theta,
1560 );
1561
1562 let vmask = (!video.context_mask.is_empty()).then_some(&video.context_mask[..]);
1563 let amask = (!audio.context_mask.is_empty()).then_some(&audio.context_mask[..]);
1564
1565 let mut prof = Prof::new();
1566 let chained = to_chain
1567 && self.run_chain(
1568 video,
1569 audio,
1570 &mut vx,
1571 &mut ax,
1572 [&vt, &at, &vpt, &apt, &vxs, &axs, &vgt, &agt],
1573 [&v_pe, &a_pe, &v_xpe, &a_xpe],
1574 );
1575 let blocks: &[Block] = if chained { &[] } else { &self.blocks };
1576 for (bi, blk) in blocks.iter().enumerate() {
1577 let mut pt = std::time::Instant::now();
1578 let v_msa = vt.triples(&blk.video.sst, dim, 0);
1579 let v_ca = vt.triples(&blk.video.sst, dim, 6);
1580 let v_mlp = vt.triples(&blk.video.sst, dim, 3);
1581 let a_msa = at.triples(&blk.audio.sst, a_dim, 0);
1582 let a_ca = at.triples(&blk.audio.sst, a_dim, 6);
1583 let a_mlp = at.triples(&blk.audio.sst, a_dim, 3);
1584
1585 pt = prof.tick(P_ADALN, pt);
1587 let mut vnorm = vec![0f32; n * dim];
1588 ada_zero_rows(&vx, &mut vnorm, n, dim, &v_msa, &vt.idx, pool);
1589 pt = prof.tick(P_MOD, pt);
1590 if bi == 0 {
1591 trace("v.b0.sa.in", &vnorm);
1592 }
1593 let vsa =
1594 blk.video
1595 .attn1
1596 .forward(&vnorm, n, &vnorm, n, Some(&v_pe), Some(&v_pe), None, pool);
1597 if bi == 0 {
1598 trace("v.b0.sa.out", &vsa);
1599 }
1600 pt = prof.tick(P_SELF, pt);
1601 let mut vnormed = vec![0f32; n * dim];
1602 post_sa_rows(&mut vx, &vsa, &mut vnormed, n, dim, &v_msa, &vt.idx, pool);
1603 let mut vq = vec![0f32; n * dim];
1604 affine_rows(&vnormed, &mut vq, n, dim, &v_ca, &vt.idx, pool);
1605 let vctx = modulate_kv(
1606 &video.context,
1607 video.ctx_len,
1608 dim,
1609 &blk.video.prompt_sst,
1610 vpt.row(0),
1611 pool,
1612 );
1613 let vca =
1614 blk.video
1615 .attn2
1616 .forward(&vq, n, &vctx, video.ctx_len, None, None, vmask, pool);
1617 if bi == 0 {
1618 trace("v.b0.ca.in", &vq);
1619 trace("v.b0.ca.ctx", &vctx);
1620 trace("v.b0.ca.out", &vca);
1621 }
1622 add_gated(&mut vx, &vca, n, dim, &v_ca, &vt.idx, pool);
1623 pt = prof.tick(P_CROSS, pt);
1624
1625 let mut anorm = vec![0f32; m * a_dim];
1627 ada_zero_rows(&ax, &mut anorm, m, a_dim, &a_msa, &at.idx, pool);
1628 if bi == 0 {
1629 trace("a.b0.sa.in", &anorm);
1630 }
1631 let asa =
1632 blk.audio
1633 .attn1
1634 .forward(&anorm, m, &anorm, m, Some(&a_pe), Some(&a_pe), None, pool);
1635 if bi == 0 {
1636 trace("a.b0.sa.out", &asa);
1637 }
1638 let mut anormed = vec![0f32; m * a_dim];
1639 post_sa_rows(&mut ax, &asa, &mut anormed, m, a_dim, &a_msa, &at.idx, pool);
1640 let mut aq = vec![0f32; m * a_dim];
1641 affine_rows(&anormed, &mut aq, m, a_dim, &a_ca, &at.idx, pool);
1642 let actx = modulate_kv(
1643 &audio.context,
1644 audio.ctx_len,
1645 a_dim,
1646 &blk.audio.prompt_sst,
1647 apt.row(0),
1648 pool,
1649 );
1650 let aca =
1651 blk.audio
1652 .attn2
1653 .forward(&aq, m, &actx, audio.ctx_len, None, None, amask, pool);
1654 if bi == 0 {
1655 trace("a.b0.ca.in", &aq);
1656 trace("a.b0.ca.ctx", &actx);
1657 trace("a.b0.ca.out", &aca);
1658 }
1659 add_gated(&mut ax, &aca, m, a_dim, &a_ca, &at.idx, pool);
1660 pt = prof.tick(P_CROSS, pt);
1661
1662 let vx_pre = vx.clone();
1664 let ax_pre = ax.clone();
1665 let a2v_vp = vxs.pairs(&blk.sst_a2v_video, dim, 0);
1666 let a2v_ap = axs.pairs(&blk.sst_a2v_audio, a_dim, 0);
1667 let a2v_v = ada_pair(&vx_pre, n, dim, &a2v_vp, &vxs.idx, pool);
1668 let a2v_a = ada_pair(&ax_pre, m, a_dim, &a2v_ap, &axs.idx, pool);
1669 let a2v = blk
1670 .a2v
1671 .forward(&a2v_v, n, &a2v_a, m, Some(&v_xpe), Some(&a_xpe), None, pool);
1672 if bi == 0 {
1673 trace("v.b0.a2v.in", &a2v_v);
1674 trace("v.b0.a2v.ctx", &a2v_a);
1675 trace("v.b0.a2v.out", &a2v);
1676 }
1677 let gate_a2v = gate_row(&blk.sst_a2v_video, dim, vgt.row(0));
1678 add_scaled(&mut vx, &a2v, n, dim, &gate_a2v, pool);
1679 let v2a_ap = axs.pairs(&blk.sst_a2v_audio, a_dim, 2);
1680 let v2a_vp = vxs.pairs(&blk.sst_a2v_video, dim, 2);
1681 let v2a_a = ada_pair(&ax_pre, m, a_dim, &v2a_ap, &axs.idx, pool);
1682 let v2a_v = ada_pair(&vx_pre, n, dim, &v2a_vp, &vxs.idx, pool);
1683 let v2a = blk
1684 .v2a
1685 .forward(&v2a_a, m, &v2a_v, n, Some(&a_xpe), Some(&v_xpe), None, pool);
1686 if bi == 0 {
1687 trace("a.b0.v2a.in", &v2a_a);
1688 trace("a.b0.v2a.ctx", &v2a_v);
1689 trace("a.b0.v2a.out", &v2a);
1690 }
1691 let gate_v2a = gate_row(&blk.sst_a2v_audio, a_dim, agt.row(0));
1692 add_scaled(&mut ax, &v2a, m, a_dim, &gate_v2a, pool);
1693 pt = prof.tick(P_FUSE, pt);
1694
1695 let mut vsc = vec![0f32; n * dim];
1697 ada_zero_rows(&vx, &mut vsc, n, dim, &v_mlp, &vt.idx, pool);
1698 let vff = blk.video.ff(&vsc, n, pool);
1699 if bi == 0 {
1700 trace("v.b0.ff.in", &vsc);
1701 trace("v.b0.ff.out", &vff);
1702 }
1703 add_gated(&mut vx, &vff, n, dim, &v_mlp, &vt.idx, pool);
1704 let mut asc = vec![0f32; m * a_dim];
1705 ada_zero_rows(&ax, &mut asc, m, a_dim, &a_mlp, &at.idx, pool);
1706 let aff = blk.audio.ff(&asc, m, pool);
1707 if bi == 0 {
1708 trace("a.b0.ff.in", &asc);
1709 trace("a.b0.ff.out", &aff);
1710 }
1711 add_gated(&mut ax, &aff, m, a_dim, &a_mlp, &at.idx, pool);
1712 pt = prof.tick(P_FF, pt);
1713 trace(&format!("v.block{bi}"), &vx);
1714 trace(&format!("a.block{bi}"), &ax);
1715 }
1716
1717 prof.report();
1718
1719 let vout = head(&vx, n, dim, &self.sst_out, &vt, &self.proj_out, pool);
1721 let aout = head(&ax, m, a_dim, &self.a_sst_out, &at, &self.a_proj_out, pool);
1722 trace("v.out", &vout);
1723 trace("a.out", &aout);
1724 (vout, aout)
1725 }
1726}
1727
1728fn modulate_kv(
1731 ctx: &[f32],
1732 len: usize,
1733 dim: usize,
1734 table: &[f32],
1735 extra: &[f32],
1736 pool: Option<&Pool>,
1737) -> Vec<f32> {
1738 let mut out = vec![0f32; len * dim];
1739 let shift: Vec<f32> = (0..dim).map(|d| table[d] + extra[d]).collect();
1740 let scale: Vec<f32> = (0..dim).map(|d| table[dim + d] + extra[dim + d]).collect();
1741 let dst = Shared(out.as_mut_ptr());
1745 rows(pool, len, &|s, e| {
1746 let r = unsafe { dst.at(s * dim, (e - s) * dim) };
1747 for (row, i) in r.chunks_exact_mut(dim).zip(s..e) {
1748 for d in 0..dim {
1749 row[d] = ctx[i * dim + d] * (1.0 + scale[d]) + shift[d];
1750 }
1751 }
1752 });
1753 out
1754}
1755
1756fn ada_pair(
1758 x: &[f32],
1759 n: usize,
1760 dim: usize,
1761 pairs: &[[Vec<f32>; 2]],
1762 idx: &[usize],
1763 pool: Option<&Pool>,
1764) -> Vec<f32> {
1765 let mut out = vec![0f32; n * dim];
1766 let dst = Shared(out.as_mut_ptr());
1767 rows(pool, n, &|s, e| {
1768 let r = unsafe { dst.at(s * dim, (e - s) * dim) };
1769 for (row, i) in r.chunks_exact_mut(dim).zip(s..e) {
1770 let p = &pairs[idx[i]];
1771 rms_plain(&x[i * dim..(i + 1) * dim], row);
1772 for d in 0..dim {
1773 row[d] = row[d] * (1.0 + p[0][d]) + p[1][d];
1774 }
1775 }
1776 });
1777 out
1778}
1779
1780fn gate_row(table: &[f32], dim: usize, extra: &[f32]) -> Vec<f32> {
1783 (0..dim).map(|d| table[4 * dim + d] + extra[d]).collect()
1784}
1785
1786fn head(
1789 x: &[f32],
1790 n: usize,
1791 dim: usize,
1792 sst: &[f32],
1793 ts: &TsTable,
1794 proj: &Lin,
1795 pool: Option<&Pool>,
1796) -> Vec<f32> {
1797 let mut y = vec![0f32; n * dim];
1798 let dst = Shared(y.as_mut_ptr());
1799 rows(pool, n, &|s, e| {
1800 let r = unsafe { dst.at(s * dim, (e - s) * dim) };
1801 let mut ln = vec![0f32; dim];
1802 for (row, i) in r.chunks_exact_mut(dim).zip(s..e) {
1803 let emb = ts.emb_row(ts.idx[i.min(ts.idx.len() - 1)]);
1804 layer_norm(&x[i * dim..(i + 1) * dim], &mut ln);
1805 for d in 0..dim {
1806 row[d] = ln[d] * (1.0 + sst[dim + d] + emb[d]) + sst[d] + emb[d];
1807 }
1808 }
1809 });
1810 proj.apply(&y, n, pool)
1811}