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
81pub(crate) fn gelu_tanh(v: f32) -> f32 {
83 let x = v as f64;
84 let inner = (2.0f64 / std::f64::consts::PI).sqrt() * (x + 0.044715 * x * x * x);
85 (0.5 * x * (1.0 + inner.tanh())) as f32
86}
87
88pub(crate) fn softmax(row: &mut [f32]) {
89 let mx = row.iter().cloned().fold(f32::MIN, f32::max);
90 let mut den = 0f32;
91 for r in row.iter_mut() {
92 *r = (*r - mx).exp();
93 den += *r;
94 }
95 if den > 0.0 {
96 let inv = 1.0 / den;
97 for r in row.iter_mut() {
98 *r *= inv;
99 }
100 }
101}
102
103fn layer_norm(x: &[f32], dst: &mut [f32]) {
105 let n = x.len() as f64;
106 let mean = x.iter().map(|&v| v as f64).sum::<f64>() / n;
107 let var = x.iter().map(|&v| (v as f64 - mean) * (v as f64 - mean)).sum::<f64>() / n;
108 let inv = 1.0 / (var + EPS).sqrt();
109 for (d, &v) in dst.iter_mut().zip(x) {
110 *d = ((v as f64 - mean) * inv) as f32;
111 }
112}
113
114pub(crate) struct Lin {
118 w: Proj,
119 b: Option<Vec<f32>>,
120}
121
122impl Lin {
123 pub(crate) fn load(model: &Arc<CmfModel>, name: &str, bias: bool) -> Result<Lin, String> {
124 let w = Proj::from_model(model, &format!("{name}.weight"))?;
125 let b = if bias {
126 Some(cmf_f32(model, &format!("{name}.bias"))?)
127 } else {
128 None
129 };
130 Ok(Lin { w, b })
131 }
132
133 pub(crate) fn apply(&self, x: &[f32], n: usize, pool: Option<&Pool>) -> Vec<f32> {
134 let m = self.w.rows();
135 let cols = self.w.cols();
136 let mut out = vec![0f32; n * m];
137 let per_row = cols * 4;
142 let chunk = (0x1000_0000usize / per_row.max(1)).max(1);
143 let mut done = 0usize;
144 while done < n {
145 let take = chunk.min(n - done);
146 self.w.matmat(
147 &x[done * cols..(done + take) * cols],
148 take,
149 &mut out[done * m..(done + take) * m],
150 pool,
151 );
152 done += take;
153 }
154 if let Some(b) = &self.b {
155 let dst = Shared(out.as_mut_ptr());
156 rows(pool, n, &|s, e| {
157 let r = unsafe { dst.at(s * m, (e - s) * m) };
158 for row in r.chunks_exact_mut(m) {
159 for (v, &bb) in row.iter_mut().zip(b) {
160 *v += bb;
161 }
162 }
163 });
164 }
165 out
166 }
167}
168
169pub struct Rope {
173 cos: Vec<f32>,
174 sin: Vec<f32>,
175 heads: usize,
176 half: usize,
177}
178
179impl Rope {
180 pub fn build(positions: &[Vec<f64>], max_pos: &[f64], dim: usize, heads: usize, theta: f64) -> Rope {
184 let ndim = max_pos.len();
185 let count = dim / (2 * ndim);
186 let idx: Vec<f64> = (0..count)
188 .map(|j| {
189 let e = if count > 1 { j as f64 / (count - 1) as f64 } else { 0.0 };
190 theta.powf(e) * std::f64::consts::PI / 2.0
191 })
192 .collect();
193 let n = positions.len();
194 let half = dim / 2;
195 let pad = half - count * ndim;
196 let mut cos = vec![0f32; n * half];
197 let mut sin = vec![0f32; n * half];
198 for (t, p) in positions.iter().enumerate() {
199 let base = t * half;
200 for i in 0..pad {
201 cos[base + i] = 1.0;
202 }
203 for (j, &ind) in idx.iter().enumerate() {
204 for (d, &mp) in max_pos.iter().enumerate() {
205 let f = ind * (p[d] / mp * 2.0 - 1.0);
206 let o = base + pad + j * ndim + d;
207 cos[o] = f.cos() as f32;
208 sin[o] = f.sin() as f32;
209 }
210 }
211 }
212 Rope { cos, sin, heads, half: half / heads }
213 }
214
215 fn apply_row(&self, t: usize, row: &mut [f32]) {
217 let dh = self.half * 2;
218 let stride = self.heads * self.half;
219 for h in 0..self.heads {
220 let off = t * stride + h * self.half;
221 let (c, s) = (&self.cos[off..off + self.half], &self.sin[off..off + self.half]);
222 let v = &mut row[h * dh..(h + 1) * dh];
223 for i in 0..self.half {
224 let (a, b) = (v[i], v[i + self.half]);
225 v[i] = a * c[i] - b * s[i];
226 v[i + self.half] = b * c[i] + a * s[i];
227 }
228 }
229 }
230}
231
232pub(crate) struct Attn {
235 q: Lin,
236 k: Lin,
237 v: Lin,
238 o: Lin,
239 q_norm: Vec<f32>,
240 k_norm: Vec<f32>,
241 gate: Option<Lin>,
242 heads: usize,
243 dh: usize,
244}
245
246impl Attn {
247 pub(crate) fn load(model: &Arc<CmfModel>, p: &str, heads: usize, dh: usize) -> Result<Attn, String> {
248 Ok(Attn {
249 q: Lin::load(model, &format!("{p}.to_q"), true)?,
250 k: Lin::load(model, &format!("{p}.to_k"), true)?,
251 v: Lin::load(model, &format!("{p}.to_v"), true)?,
252 o: Lin::load(model, &format!("{p}.to_out.0"), true)?,
253 q_norm: cmf_f32(model, &format!("{p}.q_norm.weight"))?,
254 k_norm: cmf_f32(model, &format!("{p}.k_norm.weight"))?,
255 gate: match model.tensor(&format!("{p}.to_gate_logits.weight")) {
256 Some(_) => Some(Lin::load(model, &format!("{p}.to_gate_logits"), true)?),
257 None => None,
258 },
259 heads,
260 dh,
261 })
262 }
263
264 #[allow(clippy::too_many_arguments)]
267 pub(crate) fn forward(
268 &self,
269 x: &[f32],
270 n: usize,
271 ctx: &[f32],
272 m: usize,
273 pe_q: Option<&Rope>,
274 pe_k: Option<&Rope>,
275 mask: Option<&[f32]>,
276 pool: Option<&Pool>,
277 ) -> Vec<f32> {
278 let inner = self.heads * self.dh;
279 let mut q = self.q.apply(x, n, pool);
280 let mut k = self.k.apply(ctx, m, pool);
281 let v = self.v.apply(ctx, m, pool);
282
283 let qn = Shared(q.as_mut_ptr());
284 rows(pool, n, &|s, e| {
285 let r = unsafe { qn.at(s * inner, (e - s) * inner) };
286 for (i, row) in r.chunks_exact_mut(inner).enumerate() {
287 rms_w(row, &self.q_norm);
288 if let Some(pe) = pe_q {
289 pe.apply_row(s + i, row);
290 }
291 }
292 });
293 let kn = Shared(k.as_mut_ptr());
294 rows(pool, m, &|s, e| {
295 let r = unsafe { kn.at(s * inner, (e - s) * inner) };
296 for (i, row) in r.chunks_exact_mut(inner).enumerate() {
297 rms_w(row, &self.k_norm);
298 if let Some(pe) = pe_k {
299 pe.apply_row(s + i, row);
300 }
301 }
302 });
303
304 let mut out = vec![0f32; n * inner];
310 let scale = 1.0 / (self.dh as f32).sqrt();
311 let dh = self.dh;
312 let mut qh = vec![0f32; n * dh];
313 let mut kh = vec![0f32; m * dh];
314 let mut vh = vec![0f32; m * dh];
315 let mut sc = vec![0f32; n * m];
316 let mut oh = vec![0f32; n * dh];
317 for h in 0..self.heads {
318 for i in 0..n {
319 qh[i * dh..(i + 1) * dh].copy_from_slice(&q[i * inner + h * dh..][..dh]);
320 }
321 for j in 0..m {
322 kh[j * dh..(j + 1) * dh].copy_from_slice(&k[j * inner + h * dh..][..dh]);
323 vh[j * dh..(j + 1) * dh].copy_from_slice(&v[j * inner + h * dh..][..dh]);
324 }
325 crate::fcd_ops::gemm_nt(&qh, &kh, &mut sc, n, dh, m, pool);
326 let sp = Shared(sc.as_mut_ptr());
327 rows(pool, n, &|s, e| {
328 let r = unsafe { sp.at(s * m, (e - s) * m) };
329 for row in r.chunks_exact_mut(m) {
330 for (x, j) in row.iter_mut().zip(0..m) {
331 *x = *x * scale + mask.map_or(0.0, |mk| mk[j]);
332 }
333 softmax(row);
334 }
335 });
336 oh.iter_mut().for_each(|x| *x = 0.0);
337 crate::fcd_ops::gemm_dx(&sc, &vh, &mut oh, n, dh, m, pool);
338 for i in 0..n {
339 out[i * inner + h * dh..i * inner + (h + 1) * dh]
340 .copy_from_slice(&oh[i * dh..(i + 1) * dh]);
341 }
342 }
343
344 if let Some(g) = &self.gate {
345 let logits = g.apply(x, n, pool);
346 let h = self.heads;
347 let dst = Shared(out.as_mut_ptr());
348 rows(pool, n, &|s, e| {
349 let r = unsafe { dst.at(s * inner, (e - s) * inner) };
350 for (i, row) in r.chunks_exact_mut(inner).enumerate() {
351 for hh in 0..h {
352 let gate = 2.0 / (1.0 + (-logits[(s + i) * h + hh]).exp());
353 for d in row[hh * self.dh..(hh + 1) * self.dh].iter_mut() {
354 *d *= gate;
355 }
356 }
357 }
358 });
359 }
360 self.o.apply(&out, n, pool)
361 }
362}
363
364struct AdaLn {
369 l1: Lin,
370 l2: Lin,
371 lin: Lin,
372 dim: usize,
373}
374
375impl AdaLn {
376 fn load(model: &Arc<CmfModel>, p: &str, dim: usize) -> Result<AdaLn, String> {
377 Ok(AdaLn {
378 l1: Lin::load(model, &format!("{p}.emb.timestep_embedder.linear_1"), true)?,
379 l2: Lin::load(model, &format!("{p}.emb.timestep_embedder.linear_2"), true)?,
380 lin: Lin::load(model, &format!("{p}.linear"), true)?,
381 dim,
382 })
383 }
384
385 fn forward(&self, t: &[f32], pool: Option<&Pool>) -> (Vec<f32>, Vec<f32>) {
387 let n = t.len();
388 let half = 128usize;
391 let mut proj = vec![0f32; n * 256];
392 let ws: Vec<f64> = (0..half)
393 .map(|j| (-(10000f64).ln() * j as f64 / half as f64).exp())
394 .collect();
395 for (i, &tv) in t.iter().enumerate() {
396 for (j, &w) in ws.iter().enumerate() {
397 let a = tv as f64 * w;
398 proj[i * 256 + j] = a.cos() as f32;
399 proj[i * 256 + half + j] = a.sin() as f32;
400 }
401 }
402 let mut h = self.l1.apply(&proj, n, pool);
403 for v in h.iter_mut() {
404 *v = silu(*v);
405 }
406 let embedded = self.l2.apply(&h, n, pool);
407 let mut act = embedded.clone();
408 for v in act.iter_mut() {
409 *v = silu(*v);
410 }
411 (self.lin.apply(&act, n, pool), embedded)
412 }
413}
414
415struct TsTable {
420 vals: Vec<f32>,
421 emb: Vec<f32>,
422 idx: Vec<usize>,
423 width: usize,
424 edim: usize,
425}
426
427impl TsTable {
428 fn build(a: &AdaLn, ts: &[f32], scale: f64, pool: Option<&Pool>) -> TsTable {
429 let mut vals: Vec<f32> = Vec::new();
430 let mut idx = Vec::with_capacity(ts.len());
431 for &t in ts {
432 match vals.iter().position(|&v| v.to_bits() == t.to_bits()) {
433 Some(i) => idx.push(i),
434 None => {
435 vals.push(t);
436 idx.push(vals.len() - 1);
437 }
438 }
439 }
440 let scaled: Vec<f32> = vals.iter().map(|&v| (v as f64 * scale) as f32).collect();
441 let (v, e) = a.forward(&scaled, pool);
442 let width = v.len() / scaled.len().max(1);
443 TsTable { vals: v, emb: e, idx, width, edim: a.dim }
444 }
445
446 fn distinct(&self) -> usize {
447 self.vals.len() / self.width.max(1)
448 }
449
450 fn row(&self, r: usize) -> &[f32] {
451 &self.vals[r * self.width..(r + 1) * self.width]
452 }
453
454 fn emb_row(&self, r: usize) -> &[f32] {
455 &self.emb[r * self.edim..(r + 1) * self.edim]
456 }
457
458 fn triples(&self, table: &[f32], dim: usize, off: usize) -> Vec<[Vec<f32>; 3]> {
462 (0..self.distinct())
463 .map(|r| {
464 let v = self.row(r);
465 std::array::from_fn(|j| {
466 let o = (off + j) * dim;
467 (0..dim).map(|d| table[o + d] + v[o + d]).collect()
468 })
469 })
470 .collect()
471 }
472
473 fn pairs(&self, table: &[f32], dim: usize, off: usize) -> Vec<[Vec<f32>; 2]> {
476 (0..self.distinct())
477 .map(|r| {
478 let v = self.row(r);
479 std::array::from_fn(|j| {
480 let o = (off + j) * dim;
481 (0..dim).map(|d| table[o + d] + v[o + d]).collect()
482 })
483 })
484 .collect()
485 }
486}
487
488
489fn ada_zero_rows(
493 x: &[f32],
494 out: &mut [f32],
495 n: usize,
496 dim: usize,
497 mods: &[[Vec<f32>; 3]],
498 idx: &[usize],
499 pool: Option<&Pool>,
500) {
501 let dst = Shared(out.as_mut_ptr());
502 rows(pool, n, &|s, e| {
503 let r = unsafe { dst.at(s * dim, (e - s) * dim) };
504 for (row, i) in r.chunks_exact_mut(dim).zip(s..e) {
505 let md = &mods[idx[i]];
506 rms_plain(&x[i * dim..(i + 1) * dim], row);
507 for d in 0..dim {
508 row[d] = row[d] * (1.0 + md[1][d]) + md[0][d];
509 }
510 }
511 });
512}
513
514fn add_gated(
516 x: &mut [f32],
517 y: &[f32],
518 n: usize,
519 dim: usize,
520 mods: &[[Vec<f32>; 3]],
521 idx: &[usize],
522 pool: Option<&Pool>,
523) {
524 let dst = Shared(x.as_mut_ptr());
525 rows(pool, n, &|s, e| {
526 let r = unsafe { dst.at(s * dim, (e - s) * dim) };
527 for (row, i) in r.chunks_exact_mut(dim).zip(s..e) {
528 let g = &mods[idx[i]][2];
529 for d in 0..dim {
530 row[d] += y[i * dim + d] * g[d];
531 }
532 }
533 });
534}
535
536fn post_sa_rows(
539 x: &mut [f32],
540 y: &[f32],
541 normed: &mut [f32],
542 n: usize,
543 dim: usize,
544 mods: &[[Vec<f32>; 3]],
545 idx: &[usize],
546 pool: Option<&Pool>,
547) {
548 let a = Shared(x.as_mut_ptr());
549 let b = Shared(normed.as_mut_ptr());
550 rows(pool, n, &|s, e| {
551 let xr = unsafe { a.at(s * dim, (e - s) * dim) };
552 let nr = unsafe { b.at(s * dim, (e - s) * dim) };
553 for ((row, nrow), i) in xr.chunks_exact_mut(dim).zip(nr.chunks_exact_mut(dim)).zip(s..e) {
554 let g = &mods[idx[i]][2];
555 for d in 0..dim {
556 row[d] += y[i * dim + d] * g[d];
557 }
558 rms_plain(row, nrow);
559 }
560 });
561}
562
563fn add_scaled(x: &mut [f32], y: &[f32], n: usize, dim: usize, g: &[f32], pool: Option<&Pool>) {
565 let dst = Shared(x.as_mut_ptr());
566 rows(pool, n, &|s, e| {
567 let r = unsafe { dst.at(s * dim, (e - s) * dim) };
568 for (row, i) in r.chunks_exact_mut(dim).zip(s..e) {
569 for d in 0..dim {
570 row[d] += y[i * dim + d] * g[d];
571 }
572 }
573 });
574}
575
576fn affine_rows(
578 x: &[f32],
579 out: &mut [f32],
580 n: usize,
581 dim: usize,
582 mods: &[[Vec<f32>; 3]],
583 idx: &[usize],
584 pool: Option<&Pool>,
585) {
586 let dst = Shared(out.as_mut_ptr());
587 rows(pool, n, &|s, e| {
588 let r = unsafe { dst.at(s * dim, (e - s) * dim) };
589 for (row, i) in r.chunks_exact_mut(dim).zip(s..e) {
590 let md = &mods[idx[i]];
591 for d in 0..dim {
592 row[d] = x[i * dim + d] * (1.0 + md[1][d]) + md[0][d];
593 }
594 }
595 });
596}
597
598
599#[derive(Default)]
602struct Prof {
603 on: bool,
604 t: [f64; 6],
605}
606
607const P_ADALN: usize = 0;
608const P_SELF: usize = 1;
609const P_CROSS: usize = 2;
610const P_FUSE: usize = 3;
611const P_FF: usize = 4;
612const P_MOD: usize = 5;
613
614impl Prof {
615 fn new() -> Prof {
616 Prof { on: std::env::var("CMF_LTX_PROF").is_ok(), t: [0.0; 6] }
617 }
618 #[inline]
619 fn tick(&mut self, slot: usize, at: std::time::Instant) -> std::time::Instant {
620 if self.on {
621 self.t[slot] += at.elapsed().as_secs_f64();
622 return std::time::Instant::now();
623 }
624 at
625 }
626 fn report(&self) {
627 if !self.on {
628 return;
629 }
630 let names = ["adaln", "self-attn", "cross-attn", "a<->v", "ffn", "modulate"];
631 let total: f64 = self.t.iter().sum();
632 let parts: Vec<String> = names
633 .iter()
634 .zip(&self.t)
635 .map(|(n, v)| format!("{n} {v:.1}s ({:.0}%)", 100.0 * v / total.max(1e-9)))
636 .collect();
637 println!(" profile: {}", parts.join(" "));
638 }
639}
640
641struct Stream {
644 attn1: Attn,
645 attn2: Attn,
646 ff_in: Lin,
647 ff_out: Lin,
648 sst: Vec<f32>, prompt_sst: Vec<f32>, }
651
652impl Stream {
653 fn load(
654 model: &Arc<CmfModel>,
655 p: &str,
656 prefix: &str,
657 heads: usize,
658 dh: usize,
659 ff_bias: bool,
660 ) -> Result<Stream, String> {
661 let a = |n: &str| format!("{p}.{prefix}{n}");
662 Ok(Stream {
663 attn1: Attn::load(model, &a("attn1"), heads, dh)?,
664 attn2: Attn::load(model, &a("attn2"), heads, dh)?,
665 ff_in: Lin::load(model, &a("ff.net.0.proj"), ff_bias)?,
666 ff_out: Lin::load(model, &a("ff.net.2"), ff_bias)?,
667 sst: cmf_f32(model, &a("scale_shift_table"))?,
668 prompt_sst: cmf_f32(model, &a("prompt_scale_shift_table"))?,
669 })
670 }
671
672 fn ff(&self, x: &[f32], n: usize, pool: Option<&Pool>) -> Vec<f32> {
673 let mut h = self.ff_in.apply(x, n, pool);
674 for v in h.iter_mut() {
675 *v = gelu_tanh(*v);
676 }
677 self.ff_out.apply(&h, n, pool)
678 }
679}
680
681struct Block {
682 video: Stream,
683 audio: Stream,
684 a2v: Attn,
685 v2a: Attn,
686 sst_a2v_video: Vec<f32>, sst_a2v_audio: Vec<f32>, }
689
690pub struct StreamInput {
695 pub latent: Vec<f32>,
697 pub tokens: usize,
698 pub timesteps: Vec<f32>,
700 pub positions: Vec<Vec<f64>>,
702 pub context: Vec<f32>,
704 pub ctx_len: usize,
705 pub context_mask: Vec<f32>,
707 pub keyframes: Vec<f32>,
709 pub sigma: f32,
711}
712
713pub struct LtxDit {
714 model: Arc<CmfModel>,
715 blocks: Vec<Block>,
716 patchify: Lin,
717 a_patchify: Lin,
718 keyframes_emb: Option<Vec<f32>>,
719 adaln: AdaLn,
720 a_adaln: AdaLn,
721 prompt_adaln: AdaLn,
722 a_prompt_adaln: AdaLn,
723 av_v_ss: AdaLn,
724 av_a_ss: AdaLn,
725 av_a2v_gate: AdaLn,
726 av_v2a_gate: AdaLn,
727 proj_out: Lin,
728 a_proj_out: Lin,
729 sst_out: Vec<f32>,
730 a_sst_out: Vec<f32>,
731 pub heads: usize,
732 pub dh: usize,
733 pub a_heads: usize,
734 pub a_dh: usize,
735 pub max_pos: Vec<f64>,
736 pub a_max_pos: Vec<f64>,
737 pub cross_max_pos: f64,
738 pub theta: f64,
739 pub t_scale: f64,
740 pub av_t_scale: f64,
741 pub audio_cross_dim: usize,
742}
743
744impl LtxDit {
745 pub fn from_cmf(model: &Arc<CmfModel>) -> Result<LtxDit, String> {
746 let cfg_bytes = ["ltx.config_json", "dit.config_json"]
747 .iter()
748 .find_map(|n| model.tensor(n).map(|e| model.entry_bytes(e)))
749 .ok_or("container carries no ltx.config_json")?;
750 let cfg: serde_json::Value =
751 serde_json::from_slice(cfg_bytes).map_err(|e| format!("ltx.config_json: {e}"))?;
752 let t = cfg.get("transformer").unwrap_or(&cfg).clone();
753 let g = |k: &str, d: f64| t.get(k).and_then(|v| v.as_f64()).unwrap_or(d);
754 let heads = g("num_attention_heads", 32.0) as usize;
755 let dh = g("attention_head_dim", 128.0) as usize;
756 let a_heads = g("audio_num_attention_heads", 32.0) as usize;
757 let a_dh = g("audio_attention_head_dim", 64.0) as usize;
758 let n_layers = g("num_layers", 48.0) as usize;
759 let ff_bias = t.get("ff_bias").and_then(|v| v.as_bool()).unwrap_or(true);
760 let a_ff_bias = t.get("audio_ff_bias").and_then(|v| v.as_bool()).unwrap_or(true);
761 let arr = |k: &str, d: Vec<f64>| -> Vec<f64> {
762 t.get(k)
763 .and_then(|v| v.as_array())
764 .map(|a| a.iter().filter_map(|x| x.as_f64()).collect())
765 .unwrap_or(d)
766 };
767 let max_pos = arr("positional_embedding_max_pos", vec![20.0, 2048.0, 2048.0]);
768 let a_max_pos = arr("audio_positional_embedding_max_pos", vec![20.0]);
769 let cross_max_pos = max_pos[0].max(a_max_pos[0]);
770 let dim = heads * dh;
771 let a_dim = a_heads * a_dh;
772
773 let mut blocks = Vec::with_capacity(n_layers);
774 for i in 0..n_layers {
775 let p = format!("dit.transformer_blocks.{i}");
776 blocks.push(Block {
777 video: Stream::load(model, &p, "", heads, dh, ff_bias)?,
778 audio: Stream::load(model, &p, "audio_", a_heads, a_dh, a_ff_bias)?,
779 a2v: Attn::load(model, &format!("{p}.audio_to_video_attn"), a_heads, a_dh)?,
780 v2a: Attn::load(model, &format!("{p}.video_to_audio_attn"), a_heads, a_dh)?,
781 sst_a2v_video: cmf_f32(model, &format!("{p}.scale_shift_table_a2v_ca_video"))?,
782 sst_a2v_audio: cmf_f32(model, &format!("{p}.scale_shift_table_a2v_ca_audio"))?,
783 });
784 }
785 let _ = (dim, a_dim);
786 Ok(LtxDit {
787 blocks,
788 patchify: Lin::load(model, "dit.patchify_proj", true)?,
789 a_patchify: Lin::load(model, "dit.audio_patchify_proj", true)?,
790 keyframes_emb: match model.tensor("dit.keyframes_abs_pos_embedding") {
791 Some(_) => Some(cmf_f32(model, "dit.keyframes_abs_pos_embedding")?),
792 None => None,
793 },
794 adaln: AdaLn::load(model, "dit.adaln_single", dim)?,
795 a_adaln: AdaLn::load(model, "dit.audio_adaln_single", a_dim)?,
796 prompt_adaln: AdaLn::load(model, "dit.prompt_adaln_single", dim)?,
797 a_prompt_adaln: AdaLn::load(model, "dit.audio_prompt_adaln_single", a_dim)?,
798 av_v_ss: AdaLn::load(model, "dit.av_ca_video_scale_shift_adaln_single", dim)?,
799 av_a_ss: AdaLn::load(model, "dit.av_ca_audio_scale_shift_adaln_single", a_dim)?,
800 av_a2v_gate: AdaLn::load(model, "dit.av_ca_a2v_gate_adaln_single", dim)?,
801 av_v2a_gate: AdaLn::load(model, "dit.av_ca_v2a_gate_adaln_single", a_dim)?,
802 proj_out: Lin::load(model, "dit.proj_out", true)?,
803 a_proj_out: Lin::load(model, "dit.audio_proj_out", true)?,
804 sst_out: cmf_f32(model, "dit.scale_shift_table")?,
805 a_sst_out: cmf_f32(model, "dit.audio_scale_shift_table")?,
806 heads,
807 dh,
808 a_heads,
809 a_dh,
810 max_pos,
811 a_max_pos,
812 cross_max_pos,
813 theta: g("positional_embedding_theta", 10000.0),
814 t_scale: g("timestep_scale_multiplier", 1000.0),
815 av_t_scale: g("av_ca_timestep_scale_multiplier", 1.0),
816 audio_cross_dim: g("audio_cross_attention_dim", 2048.0) as usize,
817 model: model.clone(),
818 })
819 }
820
821 pub fn blocks(&self) -> usize {
822 self.blocks.len()
823 }
824
825 pub fn container(&self) -> &Arc<CmfModel> {
826 &self.model
827 }
828
829 pub fn forward(
831 &self,
832 video: &StreamInput,
833 audio: &StreamInput,
834 pool: Option<&Pool>,
835 ) -> (Vec<f32>, Vec<f32>) {
836 self.forward_traced(video, audio, pool, &mut |_, _| {})
837 }
838
839 pub fn forward_traced(
840 &self,
841 video: &StreamInput,
842 audio: &StreamInput,
843 pool: Option<&Pool>,
844 trace: &mut dyn FnMut(&str, &[f32]),
845 ) -> (Vec<f32>, Vec<f32>) {
846 let _trust = crate::gpu::trust_gpu();
850 let dim = self.heads * self.dh;
851 let a_dim = self.a_heads * self.a_dh;
852 let (n, m) = (video.tokens, audio.tokens);
853
854 let mut vx = self.patchify.apply(&video.latent, n, pool);
856 if let Some(emb) = &self.keyframes_emb {
857 for i in 0..n {
858 if video.keyframes.get(i).copied().unwrap_or(0.0) > 0.0 {
859 for (d, &e) in vx[i * dim..(i + 1) * dim].iter_mut().zip(emb) {
860 *d += e;
861 }
862 }
863 }
864 }
865 let mut ax = self.a_patchify.apply(&audio.latent, m, pool);
866 trace("v.args.x", &vx);
867 trace("a.args.x", &ax);
868
869 let vt = TsTable::build(&self.adaln, &video.timesteps, self.t_scale, pool);
871 let at = TsTable::build(&self.a_adaln, &audio.timesteps, self.t_scale, pool);
872 let vpt = TsTable::build(&self.prompt_adaln, &[video.sigma], self.t_scale, pool);
873 let apt = TsTable::build(&self.a_prompt_adaln, &[audio.sigma], self.t_scale, pool);
874 let vxs = TsTable::build(&self.av_v_ss, &video.timesteps, self.t_scale, pool);
875 let axs = TsTable::build(&self.av_a_ss, &audio.timesteps, self.t_scale, pool);
876 let vgt = TsTable::build(&self.av_a2v_gate, &[audio.sigma], self.av_t_scale, pool);
879 let agt = TsTable::build(&self.av_v2a_gate, &[video.sigma], self.av_t_scale, pool);
880
881 let v_pe = Rope::build(&video.positions, &self.max_pos, dim, self.heads, self.theta);
883 let a_pe = Rope::build(&audio.positions, &self.a_max_pos, a_dim, self.a_heads, self.theta);
884 let time_only = |p: &[Vec<f64>]| p.iter().map(|r| vec![r[0]]).collect::<Vec<_>>();
885 let v_xpe = Rope::build(
886 &time_only(&video.positions),
887 &[self.cross_max_pos],
888 self.audio_cross_dim,
889 self.heads,
890 self.theta,
891 );
892 let a_xpe = Rope::build(
893 &time_only(&audio.positions),
894 &[self.cross_max_pos],
895 self.audio_cross_dim,
896 self.a_heads,
897 self.theta,
898 );
899
900 let vmask = (!video.context_mask.is_empty()).then_some(&video.context_mask[..]);
901 let amask = (!audio.context_mask.is_empty()).then_some(&audio.context_mask[..]);
902
903 let mut prof = Prof::new();
904 for (bi, blk) in self.blocks.iter().enumerate() {
905 let mut pt = std::time::Instant::now();
906 let v_msa = vt.triples(&blk.video.sst, dim, 0);
907 let v_ca = vt.triples(&blk.video.sst, dim, 6);
908 let v_mlp = vt.triples(&blk.video.sst, dim, 3);
909 let a_msa = at.triples(&blk.audio.sst, a_dim, 0);
910 let a_ca = at.triples(&blk.audio.sst, a_dim, 6);
911 let a_mlp = at.triples(&blk.audio.sst, a_dim, 3);
912
913 pt = prof.tick(P_ADALN, pt);
915 let mut vnorm = vec![0f32; n * dim];
916 ada_zero_rows(&vx, &mut vnorm, n, dim, &v_msa, &vt.idx, pool);
917 pt = prof.tick(P_MOD, pt);
918 if bi == 0 {
919 trace("v.b0.sa.in", &vnorm);
920 }
921 let vsa = blk
922 .video
923 .attn1
924 .forward(&vnorm, n, &vnorm, n, Some(&v_pe), Some(&v_pe), None, pool);
925 if bi == 0 {
926 trace("v.b0.sa.out", &vsa);
927 }
928 pt = prof.tick(P_SELF, pt);
929 let mut vnormed = vec![0f32; n * dim];
930 post_sa_rows(&mut vx, &vsa, &mut vnormed, n, dim, &v_msa, &vt.idx, pool);
931 let mut vq = vec![0f32; n * dim];
932 affine_rows(&vnormed, &mut vq, n, dim, &v_ca, &vt.idx, pool);
933 let vctx = modulate_kv(&video.context, video.ctx_len, dim, &blk.video.prompt_sst, vpt.row(0));
934 let vca = blk.video.attn2.forward(&vq, n, &vctx, video.ctx_len, None, None, vmask, pool);
935 if bi == 0 {
936 trace("v.b0.ca.in", &vq);
937 trace("v.b0.ca.ctx", &vctx);
938 trace("v.b0.ca.out", &vca);
939 }
940 add_gated(&mut vx, &vca, n, dim, &v_ca, &vt.idx, pool);
941 pt = prof.tick(P_CROSS, pt);
942
943 let mut anorm = vec![0f32; m * a_dim];
945 ada_zero_rows(&ax, &mut anorm, m, a_dim, &a_msa, &at.idx, pool);
946 if bi == 0 {
947 trace("a.b0.sa.in", &anorm);
948 }
949 let asa = blk
950 .audio
951 .attn1
952 .forward(&anorm, m, &anorm, m, Some(&a_pe), Some(&a_pe), None, pool);
953 if bi == 0 {
954 trace("a.b0.sa.out", &asa);
955 }
956 let mut anormed = vec![0f32; m * a_dim];
957 post_sa_rows(&mut ax, &asa, &mut anormed, m, a_dim, &a_msa, &at.idx, pool);
958 let mut aq = vec![0f32; m * a_dim];
959 affine_rows(&anormed, &mut aq, m, a_dim, &a_ca, &at.idx, pool);
960 let actx = modulate_kv(&audio.context, audio.ctx_len, a_dim, &blk.audio.prompt_sst, apt.row(0));
961 let aca = blk.audio.attn2.forward(&aq, m, &actx, audio.ctx_len, None, None, amask, pool);
962 if bi == 0 {
963 trace("a.b0.ca.in", &aq);
964 trace("a.b0.ca.ctx", &actx);
965 trace("a.b0.ca.out", &aca);
966 }
967 add_gated(&mut ax, &aca, m, a_dim, &a_ca, &at.idx, pool);
968 pt = prof.tick(P_CROSS, pt);
969
970 let vx_pre = vx.clone();
972 let ax_pre = ax.clone();
973 let a2v_vp = vxs.pairs(&blk.sst_a2v_video, dim, 0);
974 let a2v_ap = axs.pairs(&blk.sst_a2v_audio, a_dim, 0);
975 let a2v_v = ada_pair(&vx_pre, n, dim, &a2v_vp, &vxs.idx, pool);
976 let a2v_a = ada_pair(&ax_pre, m, a_dim, &a2v_ap, &axs.idx, pool);
977 let a2v = blk
978 .a2v
979 .forward(&a2v_v, n, &a2v_a, m, Some(&v_xpe), Some(&a_xpe), None, pool);
980 if bi == 0 {
981 trace("v.b0.a2v.in", &a2v_v);
982 trace("v.b0.a2v.ctx", &a2v_a);
983 trace("v.b0.a2v.out", &a2v);
984 }
985 let gate_a2v = gate_row(&blk.sst_a2v_video, dim, vgt.row(0));
986 add_scaled(&mut vx, &a2v, n, dim, &gate_a2v, pool);
987 let v2a_ap = axs.pairs(&blk.sst_a2v_audio, a_dim, 2);
988 let v2a_vp = vxs.pairs(&blk.sst_a2v_video, dim, 2);
989 let v2a_a = ada_pair(&ax_pre, m, a_dim, &v2a_ap, &axs.idx, pool);
990 let v2a_v = ada_pair(&vx_pre, n, dim, &v2a_vp, &vxs.idx, pool);
991 let v2a = blk
992 .v2a
993 .forward(&v2a_a, m, &v2a_v, n, Some(&a_xpe), Some(&v_xpe), None, pool);
994 if bi == 0 {
995 trace("a.b0.v2a.in", &v2a_a);
996 trace("a.b0.v2a.ctx", &v2a_v);
997 trace("a.b0.v2a.out", &v2a);
998 }
999 let gate_v2a = gate_row(&blk.sst_a2v_audio, a_dim, agt.row(0));
1000 add_scaled(&mut ax, &v2a, m, a_dim, &gate_v2a, pool);
1001 pt = prof.tick(P_FUSE, pt);
1002
1003 let mut vsc = vec![0f32; n * dim];
1005 ada_zero_rows(&vx, &mut vsc, n, dim, &v_mlp, &vt.idx, pool);
1006 let vff = blk.video.ff(&vsc, n, pool);
1007 if bi == 0 {
1008 trace("v.b0.ff.in", &vsc);
1009 trace("v.b0.ff.out", &vff);
1010 }
1011 add_gated(&mut vx, &vff, n, dim, &v_mlp, &vt.idx, pool);
1012 let mut asc = vec![0f32; m * a_dim];
1013 ada_zero_rows(&ax, &mut asc, m, a_dim, &a_mlp, &at.idx, pool);
1014 let aff = blk.audio.ff(&asc, m, pool);
1015 if bi == 0 {
1016 trace("a.b0.ff.in", &asc);
1017 trace("a.b0.ff.out", &aff);
1018 }
1019 add_gated(&mut ax, &aff, m, a_dim, &a_mlp, &at.idx, pool);
1020 pt = prof.tick(P_FF, pt);
1021 trace(&format!("v.block{bi}"), &vx);
1022 trace(&format!("a.block{bi}"), &ax);
1023 }
1024
1025 prof.report();
1026
1027 let vout = head(&vx, n, dim, &self.sst_out, &vt, &self.proj_out, pool);
1029 let aout = head(&ax, m, a_dim, &self.a_sst_out, &at, &self.a_proj_out, pool);
1030 trace("v.out", &vout);
1031 trace("a.out", &aout);
1032 (vout, aout)
1033 }
1034}
1035
1036fn modulate_kv(ctx: &[f32], len: usize, dim: usize, table: &[f32], extra: &[f32]) -> Vec<f32> {
1039 let mut out = vec![0f32; len * dim];
1040 let shift: Vec<f32> = (0..dim).map(|d| table[d] + extra[d]).collect();
1041 let scale: Vec<f32> = (0..dim).map(|d| table[dim + d] + extra[dim + d]).collect();
1042 for i in 0..len {
1043 for d in 0..dim {
1044 out[i * dim + d] = ctx[i * dim + d] * (1.0 + scale[d]) + shift[d];
1045 }
1046 }
1047 out
1048}
1049
1050
1051fn ada_pair(
1053 x: &[f32],
1054 n: usize,
1055 dim: usize,
1056 pairs: &[[Vec<f32>; 2]],
1057 idx: &[usize],
1058 pool: Option<&Pool>,
1059) -> Vec<f32> {
1060 let mut out = vec![0f32; n * dim];
1061 let dst = Shared(out.as_mut_ptr());
1062 rows(pool, n, &|s, e| {
1063 let r = unsafe { dst.at(s * dim, (e - s) * dim) };
1064 for (row, i) in r.chunks_exact_mut(dim).zip(s..e) {
1065 let p = &pairs[idx[i]];
1066 rms_plain(&x[i * dim..(i + 1) * dim], row);
1067 for d in 0..dim {
1068 row[d] = row[d] * (1.0 + p[0][d]) + p[1][d];
1069 }
1070 }
1071 });
1072 out
1073}
1074
1075fn gate_row(table: &[f32], dim: usize, extra: &[f32]) -> Vec<f32> {
1078 (0..dim).map(|d| table[4 * dim + d] + extra[d]).collect()
1079}
1080
1081fn head(
1084 x: &[f32],
1085 n: usize,
1086 dim: usize,
1087 sst: &[f32],
1088 ts: &TsTable,
1089 proj: &Lin,
1090 pool: Option<&Pool>,
1091) -> Vec<f32> {
1092 let mut y = vec![0f32; n * dim];
1093 let dst = Shared(y.as_mut_ptr());
1094 rows(pool, n, &|s, e| {
1095 let r = unsafe { dst.at(s * dim, (e - s) * dim) };
1096 let mut ln = vec![0f32; dim];
1097 for (row, i) in r.chunks_exact_mut(dim).zip(s..e) {
1098 let emb = ts.emb_row(ts.idx[i.min(ts.idx.len() - 1)]);
1099 layer_norm(&x[i * dim..(i + 1) * dim], &mut ln);
1100 for d in 0..dim {
1101 row[d] = ln[d] * (1.0 + sst[dim + d] + emb[d]) + sst[d] + emb[d];
1102 }
1103 }
1104 });
1105 proj.apply(&y, n, pool)
1106}