1use crate::dit::Proj;
38use crate::ltxlora::{LoraBank, LoraBranch};
39
40pub static MMH3_PROF: [std::sync::atomic::AtomicU64; 9] = [
44 std::sync::atomic::AtomicU64::new(0),
45 std::sync::atomic::AtomicU64::new(0),
46 std::sync::atomic::AtomicU64::new(0),
47 std::sync::atomic::AtomicU64::new(0),
48 std::sync::atomic::AtomicU64::new(0),
49 std::sync::atomic::AtomicU64::new(0),
50 std::sync::atomic::AtomicU64::new(0),
51 std::sync::atomic::AtomicU64::new(0),
52 std::sync::atomic::AtomicU64::new(0),
53];
54
55pub(crate) fn mmh3_prof_on() -> bool {
56 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
57 *ON.get_or_init(|| std::env::var("CMF_MMH3_PROF").is_ok())
58}
59
60pub fn mmh3_prof_report() -> Option<String> {
63 if !mmh3_prof_on() {
64 return None;
65 }
66 const NAMES: [&str; 9] = [
67 "norm+mod",
68 "qkv gemm",
69 "qknorm+rope",
70 "attention",
71 "out gemm",
72 "fc1 gemm",
73 "swiglu",
74 "fc2 gemm",
75 "residual",
76 ];
77 let mut v: Vec<(u64, &str)> = MMH3_PROF
78 .iter()
79 .map(|a| a.load(std::sync::atomic::Ordering::Relaxed))
80 .zip(NAMES)
81 .collect();
82 let total: u64 = v.iter().map(|(us, _)| *us).sum();
83 v.sort_by(|a, b| b.0.cmp(&a.0));
84 let mut out = format!("mmh3 phases (total {:.1} s):", total as f64 / 1e6);
85 for (us, name) in v {
86 out.push_str(&format!(
87 "\n {name:<14} {:>7.1} s {:>5.1}%",
88 us as f64 / 1e6,
89 100.0 * us as f64 / total.max(1) as f64
90 ));
91 }
92 Some(out)
93}
94
95use crate::pool::Pool;
96use cortiq_core::CmfModel;
97use std::sync::Arc;
98
99const FRAME_PER_TOKEN: [f64; 5] = [1.0, 4.0, 4.0, 4.0, 4.0];
102const FRAME_RESCALE: f64 = 5.0 / 3.0;
103
104const TAG_VIDEO: usize = 0;
106const TAG_TEXT: usize = 1;
107const TAG_AUDIO: usize = 2;
108const MODALITIES: usize = 3;
109const EXPAND: usize = 6;
111const VISUAL_COND_TIMESTEP: f64 = 0.999;
113const AUDIO_COND_TIMESTEP: f64 = 1.0;
115
116pub fn time_shift_sigma(sigma: f64, from_shift: f64, to_shift: f64) -> f64 {
120 let base = sigma / (from_shift + sigma * (1.0 - from_shift));
121 to_shift * base / (1.0 + (to_shift - 1.0) * base)
122}
123
124pub fn time_shift_slope(sigma: f64, from_shift: f64, to_shift: f64) -> f64 {
126 let base = sigma / (from_shift + sigma * (1.0 - from_shift));
127 (to_shift * (1.0 + (from_shift - 1.0) * base).powi(2))
128 / (from_shift * (1.0 + (to_shift - 1.0) * base).powi(2))
129}
130
131#[derive(Clone, Copy, PartialEq, Debug)]
134pub enum Kind {
135 Text,
136 Cond,
138 RefImg,
140 RefAudio,
142 Audio,
143 Video,
144 CtxVideo,
148 CtxAudio,
150}
151
152#[derive(Clone, Copy, Debug)]
154pub struct Segment {
155 pub start: usize,
156 pub stop: usize,
157 pub kind: Kind,
158}
159
160pub struct Layout {
163 pub seq_len: usize,
164 pub segments: Vec<Segment>,
165 pub pos: Vec<[f64; 3]>,
167 pub text_len: usize,
168 pub audio_t: usize,
169 pub latent_t: usize,
170 pub lat_h: usize,
171 pub lat_w: usize,
172 pub frame_rows: usize,
174 pub text_tags: Vec<u8>,
177}
178
179#[derive(Clone, Debug)]
181pub enum Ref {
182 Image { lat_h: usize, lat_w: usize },
184 Audio { t: usize },
186 Video {
189 latent_t: usize,
190 lat_h: usize,
191 lat_w: usize,
192 audio_t: usize,
193 },
194}
195
196fn push_audio_grid(pos: &mut Vec<[f64; 3]>, cursor: f64, t: usize, w_low: f64, w_high: f64) {
200 for ch in 0..2 {
201 let w = if ch == 0 { w_low } else { w_high };
202 for i in 0..t {
203 pos.push([cursor + i as f64, 0.0, w]);
204 }
205 }
206}
207
208fn axis_from_sqrt_area(dim: usize, patch: usize, sqrt_area: f64) -> Vec<f64> {
210 let ratio = dim as f64 / sqrt_area;
211 let n = dim / patch;
212 (0..n)
213 .map(|i| (i as f64 * (ratio / n as f64) + (1.0 - ratio) / 2.0) * 32.0)
214 .collect()
215}
216
217impl Layout {
218 pub fn t2va(
222 text_len: usize,
223 latent_t: usize,
224 lat_h: usize,
225 lat_w: usize,
226 audio_t: usize,
227 ) -> Self {
228 Self::build(text_len, latent_t, lat_h, lat_w, audio_t, &[], &[])
229 }
230
231 pub fn fl2va(
241 text_len: usize,
242 latent_t: usize,
243 lat_h: usize,
244 lat_w: usize,
245 audio_t: usize,
246 frames: &[(usize, usize)],
247 text_tags: &[u8],
248 ) -> Self {
249 Self::build(text_len, latent_t, lat_h, lat_w, audio_t, frames, text_tags)
250 }
251
252 pub fn ref2va(
257 text_len: usize,
258 latent_t: usize,
259 lat_h: usize,
260 lat_w: usize,
261 audio_t: usize,
262 refs: &[Ref],
263 text_tags: &[u8],
264 ) -> Self {
265 Self::build_full(
266 text_len,
267 latent_t,
268 lat_h,
269 lat_w,
270 audio_t,
271 &[],
272 refs,
273 text_tags,
274 )
275 }
276
277 fn build(
278 text_len: usize,
279 latent_t: usize,
280 lat_h: usize,
281 lat_w: usize,
282 audio_t: usize,
283 frames: &[(usize, usize)],
284 text_tags: &[u8],
285 ) -> Self {
286 Self::build_full(
287 text_len,
288 latent_t,
289 lat_h,
290 lat_w,
291 audio_t,
292 frames,
293 &[],
294 text_tags,
295 )
296 }
297
298 #[allow(clippy::too_many_arguments)]
299 fn build_full(
300 text_len: usize,
301 latent_t: usize,
302 lat_h: usize,
303 lat_w: usize,
304 audio_t: usize,
305 frames: &[(usize, usize)],
306 refs: &[Ref],
307 text_tags: &[u8],
308 ) -> Self {
309 let area = ((lat_h * lat_w) as f64).sqrt();
310 let h_axis = axis_from_sqrt_area(lat_h, 2, area);
311 let w_axis = axis_from_sqrt_area(lat_w, 2, area);
312 let frame_rows = h_axis.len() * w_axis.len();
313
314 let mut pos: Vec<[f64; 3]> = Vec::new();
315 let mut segments = Vec::new();
316
317 segments.push(Segment {
318 start: 0,
319 stop: text_len,
320 kind: Kind::Text,
321 });
322 for i in 0..text_len {
323 pos.push([i as f64, 0.0, 0.0]);
324 }
325
326 let mut cursor = text_len as f64;
330 let (w_low_t, w_high_t) = (w_axis[0], w_axis[w_axis.len() - 1]);
331
332 for r in refs {
333 match r {
334 Ref::Image { lat_h, lat_w } => {
335 let (rh, rw) = (
336 axis_from_sqrt_area(*lat_h, 2, ((lat_h * lat_w) as f64).sqrt()),
337 axis_from_sqrt_area(*lat_w, 2, ((lat_h * lat_w) as f64).sqrt()),
338 );
339 let start = pos.len();
340 for &h in &rh {
341 for &w in &rw {
342 pos.push([cursor, h, w]);
343 }
344 }
345 segments.push(Segment {
346 start,
347 stop: pos.len(),
348 kind: Kind::RefImg,
349 });
350 cursor += 1.0;
351 }
352 Ref::Audio { t } => {
353 if *t > 0 {
354 let start = pos.len();
355 push_audio_grid(&mut pos, cursor, *t, w_low_t, w_high_t);
356 segments.push(Segment {
357 start,
358 stop: pos.len(),
359 kind: Kind::RefAudio,
360 });
361 }
362 cursor += *t as f64;
363 }
364 Ref::Video {
365 latent_t: vt,
366 lat_h: rh_,
367 lat_w: rw_,
368 audio_t: rt,
369 } => {
370 let area = ((rh_ * rw_) as f64).sqrt();
371 let rh = axis_from_sqrt_area(*rh_, 2, area);
372 let rw = axis_from_sqrt_area(*rw_, 2, area);
373 if *rt > 0 {
377 let start = pos.len();
378 push_audio_grid(&mut pos, cursor, *rt, rw[0], rw[rw.len() - 1]);
379 segments.push(Segment {
380 start,
381 stop: pos.len(),
382 kind: Kind::RefAudio,
383 });
384 }
385 let start = pos.len();
386 let mut t_coord = cursor;
387 for k in 0..*vt {
388 for &h in &rh {
389 for &w in &rw {
390 pos.push([t_coord, h, w]);
391 }
392 }
393 t_coord += FRAME_RESCALE * FRAME_PER_TOKEN[k % 5];
394 }
395 segments.push(Segment {
396 start,
397 stop: pos.len(),
398 kind: Kind::RefImg,
399 });
400 let spans: f64 = (0..*vt)
401 .map(|k| FRAME_RESCALE * FRAME_PER_TOKEN[k % 5])
402 .sum();
403 cursor += (*rt as f64).max(spans);
404 }
405 }
406 }
407 let cursor = cursor;
408
409 let spans: f64 = (0..latent_t)
411 .map(|k| FRAME_RESCALE * FRAME_PER_TOKEN[k % 5])
412 .sum();
413 for &(pixel_index, frame_count) in frames {
414 let cond_t = if pixel_index == 0 {
415 cursor
416 } else if frame_count > 0 && pixel_index == frame_count - 1 {
417 cursor + spans - FRAME_RESCALE
418 } else {
419 panic!("only the first and last frame can anchor a keyframe");
420 };
421 let start = pos.len();
422 for &h in &h_axis {
423 for &w in &w_axis {
424 pos.push([cond_t, h, w]);
425 }
426 }
427 segments.push(Segment {
428 start,
429 stop: pos.len(),
430 kind: Kind::Cond,
431 });
432 }
433
434 let a_start = pos.len();
438 push_audio_grid(&mut pos, cursor, audio_t, w_low_t, w_high_t);
439 segments.push(Segment {
440 start: a_start,
441 stop: pos.len(),
442 kind: Kind::Audio,
443 });
444
445 let v_start = pos.len();
448 let mut t_coord = cursor;
449 for k in 0..latent_t {
450 for &h in &h_axis {
451 for &w in &w_axis {
452 pos.push([t_coord, h, w]);
453 }
454 }
455 t_coord += FRAME_RESCALE * FRAME_PER_TOKEN[k % 5];
456 }
457 segments.push(Segment {
458 start: v_start,
459 stop: pos.len(),
460 kind: Kind::Video,
461 });
462
463 Self {
464 seq_len: pos.len(),
465 segments,
466 pos,
467 text_len,
468 audio_t,
469 latent_t,
470 lat_h,
471 lat_w,
472 frame_rows,
473 text_tags: if text_tags.is_empty() {
474 vec![TAG_TEXT as u8; text_len]
475 } else {
476 text_tags.to_vec()
477 },
478 }
479 }
480
481 #[allow(clippy::too_many_arguments)]
501 pub fn streaming(
502 text_len: usize,
503 text_tags: &[u8],
504 lat_h: usize,
505 lat_w: usize,
506 ctx_video: &[usize],
507 cur_video: &[usize],
508 ctx_audio: &[usize],
509 cur_audio: &[usize],
510 ) -> Self {
511 let area = ((lat_h * lat_w) as f64).sqrt();
512 let h_axis = axis_from_sqrt_area(lat_h, 2, area);
513 let w_axis = axis_from_sqrt_area(lat_w, 2, area);
514 let frame_rows = h_axis.len() * w_axis.len();
515 let (w_low, w_high) = (w_axis[0], w_axis[w_axis.len() - 1]);
516
517 let mut pos: Vec<[f64; 3]> = Vec::new();
518 let mut segments = Vec::new();
519 segments.push(Segment {
520 start: 0,
521 stop: text_len,
522 kind: Kind::Text,
523 });
524 for i in 0..text_len {
525 pos.push([i as f64, 0.0, 0.0]);
526 }
527 let cursor = text_len as f64;
528
529 let t_at = |k: usize| -> f64 {
532 cursor
533 + (0..k)
534 .map(|j| FRAME_RESCALE * FRAME_PER_TOKEN[j % 5])
535 .sum::<f64>()
536 };
537
538 for (idxs, kind) in [(ctx_audio, Kind::CtxAudio), (cur_audio, Kind::Audio)] {
545 if idxs.is_empty() {
546 continue;
547 }
548 let start = pos.len();
549 for ch in 0..2 {
550 let w = if ch == 0 { w_low } else { w_high };
551 for &i in idxs {
552 pos.push([cursor + i as f64, 0.0, w]);
553 }
554 }
555 segments.push(Segment {
556 start,
557 stop: pos.len(),
558 kind,
559 });
560 }
561
562 for (idxs, kind) in [(ctx_video, Kind::CtxVideo), (cur_video, Kind::Video)] {
563 if idxs.is_empty() {
564 continue;
565 }
566 let start = pos.len();
567 for &k in idxs {
568 let t = t_at(k);
569 for &h in &h_axis {
570 for &w in &w_axis {
571 pos.push([t, h, w]);
572 }
573 }
574 }
575 segments.push(Segment {
576 start,
577 stop: pos.len(),
578 kind,
579 });
580 }
581
582 Self {
583 seq_len: pos.len(),
584 segments,
585 pos,
586 text_len,
587 audio_t: ctx_audio.len() + cur_audio.len(),
588 latent_t: ctx_video.len() + cur_video.len(),
589 lat_h,
590 lat_w,
591 frame_rows,
592 text_tags: if text_tags.is_empty() {
593 vec![TAG_TEXT as u8; text_len]
594 } else {
595 text_tags.to_vec()
596 },
597 }
598 }
599
600 pub fn video_block(&self) -> (usize, usize) {
603 self.span(&[Kind::CtxVideo, Kind::Video])
604 }
605
606 pub fn audio_block(&self) -> (usize, usize) {
608 self.span(&[Kind::CtxAudio, Kind::Audio])
609 }
610
611 fn span(&self, kinds: &[Kind]) -> (usize, usize) {
612 let mut lo = usize::MAX;
613 let mut hi = 0usize;
614 for s in self.segments.iter().filter(|s| kinds.contains(&s.kind)) {
615 lo = lo.min(s.start);
616 hi = hi.max(s.stop);
617 }
618 if lo == usize::MAX { (0, 0) } else { (lo, hi) }
619 }
620
621 pub fn cond_rows(&self) -> usize {
623 self.segments
624 .iter()
625 .filter(|s| s.kind == Kind::Cond)
626 .map(|s| s.stop - s.start)
627 .sum()
628 }
629
630 fn segment(&self, kind: Kind) -> Segment {
631 *self
632 .segments
633 .iter()
634 .find(|s| s.kind == kind)
635 .expect("every layout carries all three streams")
636 }
637}
638
639struct Adaln {
642 w: Proj,
644 b: Vec<f32>,
645 table: Vec<f32>,
647 rank: usize,
648 out: usize,
649}
650
651impl Adaln {
652 fn load(model: &Arc<CmfModel>, prefix: &str) -> Result<Self, String> {
653 let w = Proj::from_model(model, &format!("{prefix}.weight"))?;
654 let table = crate::dit::cmf_f32(model, &format!("{prefix}.table"))?;
655 let b = crate::dit::cmf_f32(model, &format!("{prefix}.bias"))?;
656 let out = w.rows();
657 let rank = table.len() / CURVE_GRID;
658 Ok(Self {
659 w,
660 b,
661 table,
662 rank,
663 out,
664 })
665 }
666
667 fn eval(&self, ts: &[f64], pool: Option<&Pool>) -> Vec<f32> {
671 let g = CURVE_GRID;
672 let mut coords = vec![0f32; ts.len() * self.rank];
673 for (i, &t) in ts.iter().enumerate() {
674 let p = (t.clamp(0.0, 1.0) * (g - 1) as f64) as f32;
678 let i0 = (p.floor() as usize).min(g - 2);
679 let f = p - i0 as f32;
680 for k in 0..self.rank {
681 let (a, b) = (
682 self.table[i0 * self.rank + k],
683 self.table[(i0 + 1) * self.rank + k],
684 );
685 coords[i * self.rank + k] = a + (b - a) * f;
686 }
687 }
688 let mut out = vec![0f32; ts.len() * self.out];
689 self.w.matmat(&coords, ts.len(), &mut out, pool);
690 for row in out.chunks_exact_mut(self.out) {
691 for (v, &bv) in row.iter_mut().zip(&self.b) {
692 *v += bv;
693 }
694 }
695 out
696 }
697}
698
699struct Block {
700 norm1: Vec<f32>,
701 norm2: Vec<f32>,
702 qkv: Proj, out: Proj, q_norm: Vec<f32>,
705 k_norm: Vec<f32>,
706 fc1: Proj, fc2: Proj, adaln: Option<Adaln>,
709 lora: BlockLora,
714}
715
716#[derive(Default)]
721struct BlockLora {
722 qkv: Option<LoraBranch>,
723 out: Option<LoraBranch>,
724 fc1: Option<LoraBranch>,
725 fc2: Option<LoraBranch>,
726}
727
728fn proj_with_lora(
741 p: &Proj,
742 br: &Option<LoraBranch>,
743 x: &[f32],
744 n: usize,
745 out: &mut [f32],
746 pool: Option<&Pool>,
747) {
748 #[cfg(target_os = "macos")]
749 if let Some(l) = br.as_ref().filter(|l| l.live()) {
750 if let Some((model, idx)) = p.q4tp_mapped() {
751 let (rows, cols) = (p.rows(), p.cols());
752 if n >= 32
753 && cols % 32 == 0
754 && crate::gpu::enabled_here()
755 && !crate::gpu::mm_killed()
756 && (!crate::ltxlora::wants_measurement() || l.resonance() != 0.0)
757 && crate::gpu_metal::q4tp_matmat_lora(model, idx, x, n, rows, cols, out, &l.side())
758 {
759 return;
760 }
761 }
762 }
763 p.matmat(x, n, out, pool);
764 if let Some(l) = br {
765 l.add(x, n, out, pool);
766 }
767}
768
769impl BlockLora {
770 fn live(br: &Option<LoraBranch>) -> bool {
773 br.as_ref().is_some_and(|l| l.live())
774 }
775 fn ffn_any(&self) -> bool {
776 Self::live(&self.fc1) || Self::live(&self.fc2)
777 }
778}
779
780pub(crate) const CURVE_GRID: usize = 1025;
781
782pub struct MiniMaxH3 {
783 video_patch: Proj,
784 video_patch_b: Vec<f32>,
785 audio_patch: Proj,
786 audio_patch_b: Vec<f32>,
787 condition: Proj,
788 condition_b: Vec<f32>,
789 refiner: Vec<Block>,
790 refiner_norm: Vec<f32>,
791 blocks: Vec<Block>,
792 final_norm: Vec<f32>,
793 final_adaln: Adaln,
794 video_out: Proj,
795 video_out_b: Vec<f32>,
796 audio_out: Proj,
797 audio_out_b: Vec<f32>,
798 inv_freq: Vec<f32>,
799 pool: Option<Arc<Pool>>,
800 pub hidden: usize,
801 pub heads: usize,
802 pub head_dim: usize,
803 pub ffn: usize,
804 pub latents_dim: usize,
805 pub audio_dim: usize,
806 pub text_dim: usize,
807 pub shift_video: f64,
808 pub shift_audio: f64,
809 eps: f64,
810 qk_eps: f64,
811 final_eps: f64,
812 pub cond_aug: f64,
816 pub cond_aug_audio: f64,
819 lora_bound: std::collections::BTreeSet<String>,
821}
822
823fn rms_norm_into(x: &[f32], w: &[f32], eps: f64, dst: &mut [f32]) {
824 let ss = x.iter().map(|&v| (v as f64) * (v as f64)).sum::<f64>() / x.len() as f64;
825 let inv = 1.0 / (ss + eps).sqrt();
826 for ((d, &v), &g) in dst.iter_mut().zip(x).zip(w) {
827 *d = (v as f64 * inv) as f32 * g;
828 }
829}
830
831fn silu(v: f32) -> f32 {
832 v / (1.0 + (-v).exp())
833}
834
835impl MiniMaxH3 {
836 pub fn from_cmf(model: &Arc<CmfModel>) -> Result<Self, String> {
837 Self::from_cmf_lora(model, None)
838 }
839
840 pub fn from_cmf_lora(model: &Arc<CmfModel>, bank: Option<&LoraBank>) -> Result<Self, String> {
847 let cfg: serde_json::Value = serde_json::from_slice(
848 model
849 .tensor_bytes("dit.config_json")
850 .map_err(|e| e.to_string())?,
851 )
852 .map_err(|e| format!("dit.config_json: {e}"))?;
853 let u = |k: &str| cfg[k].as_u64().unwrap_or(0) as usize;
854 let f = |k: &str, d: f64| cfg[k].as_f64().unwrap_or(d);
855 let n_blocks = u("num_layers");
856 let n_refiner = u("token_refiner_num_layers");
857 let f32v = |n: &str| crate::dit::cmf_f32(model, n);
858
859 let bound: std::cell::RefCell<std::collections::BTreeSet<String>> = Default::default();
861 let load_block = |prefix: &str, with_adaln: bool| -> Result<Block, String> {
862 let qkv = Proj::from_model(model, &format!("dit.{prefix}.attn.qkv_proj.weight"))?;
863 let out = Proj::from_model(model, &format!("dit.{prefix}.attn.out_proj.weight"))?;
864 let fc1 = Proj::from_model(model, &format!("dit.{prefix}.mlp.fc1.weight"))?;
865 let fc2 = Proj::from_model(model, &format!("dit.{prefix}.mlp.fc2.weight"))?;
866 let lora = match bank {
871 None => BlockLora::default(),
872 Some(k) => {
873 let mut take = |suffix: &str, p: &Proj| -> Result<Option<LoraBranch>, String> {
874 let key = format!("{prefix}.{suffix}");
875 let br = k.branch_for(&format!("dit.{key}"), p.rows(), p.cols())?;
876 if br.is_some() {
877 bound.borrow_mut().insert(key);
878 }
879 Ok(br)
880 };
881 BlockLora {
882 qkv: take("attn.qkv_proj", &qkv)?,
883 out: take("attn.out_proj", &out)?,
884 fc1: take("mlp.fc1", &fc1)?,
885 fc2: take("mlp.fc2", &fc2)?,
886 }
887 }
888 };
889 Ok(Block {
890 norm1: f32v(&format!("dit.{prefix}.norm1.weight"))?,
891 norm2: f32v(&format!("dit.{prefix}.norm2.weight"))?,
892 qkv,
893 out,
894 q_norm: f32v(&format!("dit.{prefix}.attn.q_norm.weight"))?,
895 k_norm: f32v(&format!("dit.{prefix}.attn.k_norm.weight"))?,
896 fc1,
897 fc2,
898 adaln: if with_adaln {
899 Some(Adaln::load(model, &format!("dit.{prefix}.adaln"))?)
900 } else {
901 None
902 },
903 lora,
904 })
905 };
906 let blocks = (0..n_blocks)
907 .map(|i| load_block(&format!("blocks.{i}"), true))
908 .collect::<Result<Vec<_>, _>>()?;
909 let refiner = (0..n_refiner)
910 .map(|i| load_block(&format!("token_refiner.blocks.{i}"), false))
911 .collect::<Result<Vec<_>, _>>()?;
912
913 Ok(Self {
914 video_patch: Proj::from_model(model, "dit.video_patch_proj.weight")?,
915 video_patch_b: f32v("dit.video_patch_proj.bias")?,
916 audio_patch: Proj::from_model(model, "dit.audio_patch_proj.weight")?,
917 audio_patch_b: f32v("dit.audio_patch_proj.bias")?,
918 condition: Proj::from_model(model, "dit.condition_proj.weight")?,
919 condition_b: f32v("dit.condition_proj.bias")?,
920 refiner,
921 refiner_norm: f32v("dit.token_refiner.final_norm.weight")?,
922 blocks,
923 final_norm: f32v("dit.final_layer.norm.weight")?,
924 final_adaln: Adaln::load(model, "dit.final_layer.adaln")?,
925 video_out: Proj::from_model(model, "dit.final_layer.video_out.weight")?,
926 video_out_b: f32v("dit.final_layer.video_out.bias")?,
927 audio_out: Proj::from_model(model, "dit.final_layer.audio_out.weight")?,
928 audio_out_b: f32v("dit.final_layer.audio_out.bias")?,
929 inv_freq: f32v("dit.rope_inv_freq")?,
930 pool: Pool::from_env(),
931 hidden: u("hidden_size"),
932 heads: u("num_attention_heads"),
933 head_dim: u("attention_head_dim"),
934 ffn: u("ffn_hidden_size"),
935 latents_dim: u("latents_dim"),
936 audio_dim: u("audio_latents_dim"),
937 text_dim: u("text_dim"),
938 shift_video: f("sigma_shift_video", 12.0),
939 shift_audio: f("sigma_shift_audio", 3.0),
940 eps: f("norm_eps", 1e-5),
941 qk_eps: f("qk_norm_eps", 1e-5),
942 final_eps: f("final_norm_eps", 1e-5),
943 cond_aug: VISUAL_COND_TIMESTEP,
944 cond_aug_audio: AUDIO_COND_TIMESTEP,
945 lora_bound: bound.into_inner(),
946 })
947 }
948
949 pub fn lora_bound(&self) -> usize {
951 self.lora_bound.len()
952 }
953
954 pub fn lora_binds(&self, key: &str) -> bool {
956 self.lora_bound.contains(key)
957 }
958
959 pub fn lora_report(&self) -> Option<String> {
964 if !crate::ltxlora::probe_report_on() {
965 return None;
966 }
967 let mut rows: Vec<(f32, bool, String)> = Vec::new();
968 let mut walk = |blocks: &[Block], pre: &str| {
969 for (i, b) in blocks.iter().enumerate() {
970 for (name, br) in [
971 ("attn.qkv_proj", &b.lora.qkv),
972 ("attn.out_proj", &b.lora.out),
973 ("mlp.fc1", &b.lora.fc1),
974 ("mlp.fc2", &b.lora.fc2),
975 ] {
976 if let Some(l) = br {
977 rows.push((l.resonance(), l.live(), format!("{pre}{i}.{name}")));
978 }
979 }
980 }
981 };
982 walk(&self.blocks, "blocks.");
983 walk(&self.refiner, "token_refiner.blocks.");
984 if rows.is_empty() {
985 return None;
986 }
987 rows.sort_by(|a, b| b.0.total_cmp(&a.0));
988 let live = rows.iter().filter(|r| r.1).count();
989 let mut s = format!(
990 "lora branches by contribution ‖sΔY‖/‖Y‖ ({} of {} live):\n",
991 live,
992 rows.len()
993 );
994 for (r, on, name) in &rows {
995 s.push_str(&format!(
996 " {:>8.4} {} {name}\n",
997 r,
998 if *on { "on " } else { "off" }
999 ));
1000 }
1001 Some(s)
1002 }
1003
1004 pub fn refine_text(&self, states: &[f32], n: usize) -> Vec<f32> {
1008 let pool = self.pool.as_deref();
1009 let mut h = vec![0f32; n * self.hidden];
1010 self.condition.matmat(states, n, &mut h, pool);
1011 for row in h.chunks_exact_mut(self.hidden) {
1012 for (v, &b) in row.iter_mut().zip(&self.condition_b) {
1013 *v += b;
1014 }
1015 }
1016 let ids: Vec<[f64; 3]> = Vec::new();
1017 for blk in &self.refiner {
1018 self.block_forward(blk, &mut h, n, None, &ids, &[]);
1019 }
1020 let mut out = vec![0f32; n * self.hidden];
1021 for (o, x) in out
1022 .chunks_exact_mut(self.hidden)
1023 .zip(h.chunks_exact(self.hidden))
1024 {
1025 rms_norm_into(x, &self.refiner_norm, self.final_eps, o);
1026 }
1027 out
1028 }
1029
1030 fn rope_angles(&self, pos: &[[f64; 3]]) -> Vec<f32> {
1034 let k = self.inv_freq.len();
1035 let mut out = Vec::with_capacity(pos.len() * 3 * k);
1036 for p in pos {
1037 for axis in 0..3 {
1038 for j in 0..k {
1039 out.push((p[axis] * self.inv_freq[j] as f64) as f32);
1040 }
1041 }
1042 }
1043 out
1044 }
1045
1046 #[allow(clippy::too_many_arguments)]
1057 fn norm_rope_w(
1058 &self,
1059 v: &mut [f32],
1060 n: usize,
1061 heads: usize,
1062 w: &[f32],
1063 angles: &[f32],
1064 stride: usize,
1065 off: usize,
1066 ) {
1067 let hd = self.head_dim;
1068 let pairs = if angles.is_empty() {
1069 0
1070 } else {
1071 angles.len() / n
1072 };
1073 let pool = self.pool.as_deref();
1074 let ptr = SendPtr(v.as_mut_ptr());
1075 let work = |lo: usize, hi: usize| {
1076 for p in lo..hi {
1077 for h in 0..heads {
1078 let x = unsafe { ptr.row(p * stride + off + h * hd, hd) };
1081 let ss = x.iter().map(|&a| (a as f64) * (a as f64)).sum::<f64>() / hd as f64;
1082 let inv = 1.0 / (ss + self.qk_eps).sqrt();
1083 for (d, &g) in x.iter_mut().zip(w) {
1084 *d = (*d as f64 * inv) as f32 * g;
1085 }
1086 for j in 0..pairs {
1087 let a = angles[p * pairs + j];
1088 let (s, c) = a.sin_cos();
1089 let (lo_v, hi_v) = (x[j], x[j + pairs]);
1090 x[j] = lo_v * c - hi_v * s;
1091 x[j + pairs] = lo_v * s + hi_v * c;
1092 }
1093 }
1094 }
1095 };
1096 match pool {
1097 Some(pl) => pl.run_rows(n, &work),
1098 None => work(0, n),
1099 }
1100 }
1101
1102 fn attention(
1108 &self,
1109 qkv: &[f32],
1110 n: usize,
1111 attn: &mut [f32],
1112 nr: Option<(&[f32], &[f32], &[f32], f32)>,
1113 ) {
1114 let t_repack = std::time::Instant::now();
1115 let (nh, hd) = (self.heads, self.head_dim);
1116 let inner = nh * hd;
1117 let scale = 1.0 / (hd as f32).sqrt();
1118 let pool = self.pool.as_deref();
1119 if std::env::var("CMF_MMH3_ATTN").as_deref() != Ok("cpu")
1127 && crate::gpu::enabled_here()
1128 && n >= 256
1129 {
1130 if std::env::var("CMF_MMH3_ATTN").as_deref() != Ok("repack")
1143 && crate::gpu::dit_attention_packed(qkv, nh, n, hd, scale, nr, attn)
1144 {
1145 return;
1151 }
1152 assert!(
1153 nr.is_none(),
1154 "device qk-norm was requested but the device path refused; \
1155 the host loops below expect q/k already normalized"
1156 );
1157 let mut qh = vec![0f32; nh * n * hd];
1158 let mut kh = vec![0f32; nh * n * hd];
1159 let mut vh = vec![0f32; nh * n * hd];
1160 {
1161 let (pq, pk, pv) = (
1162 SendPtr(qh.as_mut_ptr()),
1163 SendPtr(kh.as_mut_ptr()),
1164 SendPtr(vh.as_mut_ptr()),
1165 );
1166 pool_rows(pool, n, &|lo, hi| {
1167 for p in lo..hi {
1168 let base = p * 3 * inner;
1169 for h in 0..nh {
1170 let dst = (h * n + p) * hd;
1171 unsafe {
1174 pq.row(dst, hd)
1175 .copy_from_slice(&qkv[base + h * hd..base + (h + 1) * hd]);
1176 pk.row(dst, hd).copy_from_slice(
1177 &qkv[base + inner + h * hd..base + inner + (h + 1) * hd],
1178 );
1179 pv.row(dst, hd).copy_from_slice(
1180 &qkv[base + 2 * inner + h * hd
1181 ..base + 2 * inner + (h + 1) * hd],
1182 );
1183 }
1184 }
1185 }
1186 });
1187 }
1188 Self::prof(2, t_repack);
1195 if crate::gpu::dit_attention(&qh, &kh, &vh, nh, nh, n, hd, scale, attn) {
1198 return;
1199 }
1200 }
1201 let mut qh = vec![0f32; n * hd];
1202 let mut kh = vec![0f32; n * hd];
1203 let mut vt = vec![0f32; hd * n];
1204 let mut scores = vec![0f32; n * n];
1205 let mut oh = vec![0f32; n * hd];
1206 for h in 0..nh {
1207 for p in 0..n {
1208 let base = p * 3 * inner;
1209 let qs = &qkv[base + h * hd..base + (h + 1) * hd];
1210 for (d, &val) in qs.iter().enumerate() {
1211 qh[p * hd + d] = val * scale;
1212 }
1213 kh[p * hd..(p + 1) * hd]
1214 .copy_from_slice(&qkv[base + inner + h * hd..base + inner + (h + 1) * hd]);
1215 let vs = &qkv[base + 2 * inner + h * hd..base + 2 * inner + (h + 1) * hd];
1216 for (d, &val) in vs.iter().enumerate() {
1217 vt[d * n + p] = val;
1218 }
1219 }
1220 crate::fcd_ops::gemm_nt(&qh, &kh, &mut scores, n, hd, n, pool);
1221 let sp = SendPtr(scores.as_mut_ptr());
1222 let soft = |lo: usize, hi: usize| {
1223 for r in lo..hi {
1224 softmax_inplace(unsafe { sp.row(r * n, n) });
1226 }
1227 };
1228 match pool {
1229 Some(pl) => pl.run_rows(n, &soft),
1230 None => soft(0, n),
1231 }
1232 crate::fcd_ops::gemm_nt(&scores, &vt, &mut oh, n, n, hd, pool);
1233 for p in 0..n {
1234 attn[p * inner + h * hd..p * inner + (h + 1) * hd]
1235 .copy_from_slice(&oh[p * hd..(p + 1) * hd]);
1236 }
1237 }
1238 }
1239
1240 fn prof(slot: usize, t: std::time::Instant) {
1244 if !mmh3_prof_on() {
1245 return;
1246 }
1247 MMH3_PROF[slot].fetch_add(
1248 t.elapsed().as_micros() as u64,
1249 std::sync::atomic::Ordering::Relaxed,
1250 );
1251 }
1252
1253 fn block_forward(
1257 &self,
1258 blk: &Block,
1259 x: &mut [f32],
1260 n: usize,
1261 mods: Option<&[f32]>,
1262 pos: &[[f64; 3]],
1263 rows: &[u32],
1264 ) {
1265 let hs = self.hidden;
1266 let pool = self.pool.as_deref();
1267 let inner = self.heads * self.head_dim;
1268 let angles = if pos.is_empty() {
1269 Vec::new()
1270 } else {
1271 self.rope_angles(pos)
1272 };
1273
1274 let t = std::time::Instant::now();
1275 let mut xn = vec![0f32; n * hs];
1276 self.norm_rows(&mut xn, x, n, hs, &blk.norm1, self.eps);
1277 if let (Some(m), false) = (mods, rows.is_empty()) {
1278 self.modulate(&mut xn, hs, m, rows, 0, 1);
1279 }
1280 Self::prof(0, t);
1281 let t = std::time::Instant::now();
1282 let t_qkv = std::time::Instant::now();
1289 let qk_gpu = std::env::var("CMF_MMH3_QKNORM").as_deref() != Ok("cpu")
1295 && crate::gpu::enabled_here()
1296 && crate::gpu::dit_attention_packed_available()
1297 && n >= 256;
1298 if qk_gpu
1303 && !BlockLora::live(&blk.lora.qkv)
1304 && std::env::var("CMF_MMH3_FUSEQKV").as_deref() != Ok("0")
1305 {
1306 let fuse_out = !BlockLora::live(&blk.lora.out)
1311 && std::env::var("CMF_MMH3_FUSEOUT").as_deref() != Ok("0");
1312 if std::env::var("CMF_GPU_DEBUG").is_ok() {
1313 static ONCE: std::sync::Once = std::sync::Once::new();
1314 ONCE.call_once(|| {
1315 eprintln!(
1316 "mmh3 fuse-out gate: qkv_q={} out_q={} qkv_map={} out_map={}",
1317 matches!(&blk.qkv, Proj::Q(_)),
1318 matches!(&blk.out, Proj::Q(_)),
1319 matches!(&blk.qkv, Proj::Q(q) if q.mapped_device_gemm().is_some()),
1320 matches!(&blk.out, Proj::Q(o) if o.mapped_device_gemm().is_some()),
1321 )
1322 });
1323 }
1324 if let (true, Proj::Q(q), Proj::Q(o)) = (fuse_out, &blk.qkv, &blk.out) {
1325 if let (Some((m, i)), Some((_, oi))) =
1326 (q.mapped_device_gemm(), o.mapped_device_gemm())
1327 {
1328 let mut proj = vec![0f32; n * hs];
1329 if crate::gpu::dit_qkv_attn_out(
1330 m,
1331 i,
1332 oi,
1333 &xn,
1334 n,
1335 hs,
1336 self.heads,
1337 self.head_dim,
1338 1.0 / (self.head_dim as f32).sqrt(),
1339 (
1340 &angles[..],
1341 &blk.q_norm[..],
1342 &blk.k_norm[..],
1343 self.qk_eps as f32,
1344 ),
1345 &mut proj,
1346 ) {
1347 Self::prof(1, t_qkv);
1348 let t_res = std::time::Instant::now();
1349 self.residual(x, hs, &proj, mods, rows, 2);
1350 Self::prof(8, t_res);
1351 self.ffn_tail(blk, x, n, mods, rows);
1352 return;
1353 }
1354 }
1355 }
1356 if let Proj::Q(q) = &blk.qkv {
1357 if let Some((m, i)) = q.mapped_device_gemm() {
1358 let mut attn = vec![0f32; n * inner];
1359 if crate::gpu::dit_qkv_attention(
1360 m,
1361 i,
1362 &xn,
1363 n,
1364 hs,
1365 self.heads,
1366 self.head_dim,
1367 1.0 / (self.head_dim as f32).sqrt(),
1368 (
1369 &angles[..],
1370 &blk.q_norm[..],
1371 &blk.k_norm[..],
1372 self.qk_eps as f32,
1373 ),
1374 &mut attn,
1375 ) {
1376 Self::prof(1, t_qkv);
1382 let t_out = std::time::Instant::now();
1383 let mut proj = vec![0f32; n * hs];
1384 proj_with_lora(&blk.out, &blk.lora.out, &attn, n, &mut proj, pool);
1385 Self::prof(4, t_out);
1386 let t_res = std::time::Instant::now();
1387 self.residual(x, hs, &proj, mods, rows, 2);
1388 Self::prof(8, t_res);
1389 self.ffn_tail(blk, x, n, mods, rows);
1390 return;
1391 }
1392 }
1393 }
1394 }
1395 let mut qkv = vec![0f32; n * 3 * inner];
1396 proj_with_lora(&blk.qkv, &blk.lora.qkv, &xn, n, &mut qkv, pool);
1397 Self::prof(1, t);
1398 let qk_on_gpu = qk_gpu;
1411 let t = std::time::Instant::now();
1412 if !qk_on_gpu {
1413 for (which, w) in [(0usize, &blk.q_norm), (1usize, &blk.k_norm)] {
1414 self.norm_rope_w(
1415 &mut qkv,
1416 n,
1417 self.heads,
1418 w,
1419 &angles,
1420 3 * inner,
1421 which * inner,
1422 );
1423 }
1424 }
1425 Self::prof(2, t);
1426 let t = std::time::Instant::now();
1427 let mut attn = vec![0f32; n * inner];
1428 self.attention(
1429 &qkv,
1430 n,
1431 &mut attn,
1432 qk_on_gpu.then_some((
1433 &angles[..],
1434 &blk.q_norm[..],
1435 &blk.k_norm[..],
1436 self.qk_eps as f32,
1437 )),
1438 );
1439 Self::prof(3, t);
1440 let t = std::time::Instant::now();
1441 let mut proj = vec![0f32; n * hs];
1442 proj_with_lora(&blk.out, &blk.lora.out, &attn, n, &mut proj, pool);
1443 Self::prof(4, t);
1444 let t = std::time::Instant::now();
1448 self.residual(x, hs, &proj, mods, rows, 2);
1449 Self::prof(8, t);
1450
1451 self.ffn_tail(blk, x, n, mods, rows);
1452 }
1453
1454 fn ffn_tail(&self, blk: &Block, x: &mut [f32], n: usize, mods: Option<&[f32]>, rows: &[u32]) {
1459 let hs = self.hidden;
1460 let pool = self.pool.as_deref();
1461 let mut xn = vec![0f32; n * hs];
1462 let mut proj = vec![0f32; n * hs];
1463 let t = std::time::Instant::now();
1464 self.norm_rows(&mut xn, x, n, hs, &blk.norm2, self.eps);
1465 if let (Some(m), false) = (mods, rows.is_empty()) {
1466 self.modulate(&mut xn, hs, m, rows, 3, 4);
1467 }
1468 Self::prof(0, t);
1469 let t = std::time::Instant::now();
1470 if std::env::var("CMF_MMH3_FFN").as_deref() != Ok("cpu")
1475 && !blk.lora.ffn_any()
1476 && crate::gpu::enabled_here()
1477 && n >= 64
1478 {
1479 if let (Proj::Q(q1), Proj::Q(q2)) = (&blk.fc1, &blk.fc2) {
1480 if let (Some((m, i1)), Some((_, i2))) =
1481 (q1.mapped_device_gemm(), q2.mapped_device_gemm())
1482 {
1483 let mut fout = vec![0f32; n * hs];
1484 if crate::gpu::q4tp_ffn_packed(m, i1, i2, &xn, n, hs, self.ffn, None, &mut fout)
1485 {
1486 Self::prof(5, t);
1487 self.residual(x, hs, &fout, mods, rows, 5);
1488 return;
1489 }
1490 }
1491 }
1492 }
1493 let mut gu = vec![0f32; n * 2 * self.ffn];
1494 proj_with_lora(&blk.fc1, &blk.lora.fc1, &xn, n, &mut gu, pool);
1495 Self::prof(5, t);
1496 let t = std::time::Instant::now();
1497 let ffn = self.ffn;
1499 let mut act = vec![0f32; n * ffn];
1500 let ap = SendPtr(act.as_mut_ptr());
1501 pool_rows(pool, n, &|lo, hi| {
1502 for p in lo..hi {
1503 let row = &gu[p * 2 * ffn..(p + 1) * 2 * ffn];
1504 let (g, up) = row.split_at(ffn);
1505 for (o, (&a, &b)) in unsafe { ap.row(p * ffn, ffn) }
1507 .iter_mut()
1508 .zip(g.iter().zip(up))
1509 {
1510 *o = silu(a) * b;
1511 }
1512 }
1513 });
1514 Self::prof(6, t);
1515 let t = std::time::Instant::now();
1516 proj_with_lora(&blk.fc2, &blk.lora.fc2, &act, n, &mut proj, pool);
1517 Self::prof(7, t);
1518 self.residual(x, hs, &proj, mods, rows, 5);
1519 }
1520
1521 fn norm_rows(&self, dst: &mut [f32], src: &[f32], n: usize, hs: usize, w: &[f32], eps: f64) {
1526 let ptr = SendPtr(dst.as_mut_ptr());
1527 pool_rows(self.pool.as_deref(), n, &|lo, hi| {
1528 for p in lo..hi {
1529 rms_norm_into(&src[p * hs..(p + 1) * hs], w, eps, unsafe {
1531 ptr.row(p * hs, hs)
1532 });
1533 }
1534 });
1535 }
1536
1537 fn modulate(
1539 &self,
1540 x: &mut [f32],
1541 hs: usize,
1542 mods: &[f32],
1543 rows: &[u32],
1544 shift_e: usize,
1545 scale_e: usize,
1546 ) {
1547 let stride = EXPAND * hs;
1548 let ptr = SendPtr(x.as_mut_ptr());
1549 pool_rows(self.pool.as_deref(), rows.len(), &|lo, hi| {
1550 for p in lo..hi {
1551 let base = rows[p] as usize * stride;
1552 let shift = &mods[base + shift_e * hs..base + (shift_e + 1) * hs];
1553 let scale = &mods[base + scale_e * hs..base + (scale_e + 1) * hs];
1554 for ((v, &sc), &sh) in unsafe { ptr.row(p * hs, hs) }
1556 .iter_mut()
1557 .zip(scale)
1558 .zip(shift)
1559 {
1560 *v = *v * (1.0 + sc) + sh;
1561 }
1562 }
1563 });
1564 }
1565
1566 fn residual(
1569 &self,
1570 x: &mut [f32],
1571 hs: usize,
1572 other: &[f32],
1573 mods: Option<&[f32]>,
1574 rows: &[u32],
1575 gate_e: usize,
1576 ) {
1577 let n = x.len() / hs;
1578 let stride = EXPAND * hs;
1579 let ptr = SendPtr(x.as_mut_ptr());
1580 let gated = mods.filter(|_| !rows.is_empty());
1581 pool_rows(self.pool.as_deref(), n, &|lo, hi| {
1582 for p in lo..hi {
1583 let row = unsafe { ptr.row(p * hs, hs) };
1585 let src = &other[p * hs..(p + 1) * hs];
1586 match gated {
1587 Some(m) => {
1588 let base = rows[p] as usize * stride;
1589 let gate = &m[base + gate_e * hs..base + (gate_e + 1) * hs];
1590 for ((v, &g), &o) in row.iter_mut().zip(gate).zip(src) {
1591 *v += g * o;
1592 }
1593 }
1594 None => {
1595 for (v, &o) in row.iter_mut().zip(src) {
1596 *v += o;
1597 }
1598 }
1599 }
1600 }
1601 });
1602 }
1603
1604 pub fn forward(
1611 &self,
1612 layout: &Layout,
1613 text: &[f32],
1614 video: &[f32],
1615 audio: &[f32],
1616 sigma_v: f64,
1617 cond: &[Vec<f32>],
1618 ) -> (Vec<f32>, Vec<f32>) {
1619 let hs = self.hidden;
1620 let pool = self.pool.as_deref();
1621 let sigma_v = sigma_v.max(1e-6);
1622 let t_v = 1.0 - sigma_v;
1623 let t_a = 1.0 - time_shift_sigma(sigma_v, self.shift_video, self.shift_audio);
1624
1625 let has_cond = layout
1630 .segments
1631 .iter()
1632 .any(|s| matches!(s.kind, Kind::Cond | Kind::RefImg));
1633 let has_ref_audio = layout.segments.iter().any(|s| s.kind == Kind::RefAudio);
1634 let t_cond_a = t_a.max(self.cond_aug_audio);
1637 let t_cond = t_v.max(self.cond_aug);
1642 let has_ctx = layout
1647 .segments
1648 .iter()
1649 .any(|s| matches!(s.kind, Kind::CtxVideo | Kind::CtxAudio));
1650 let mut ts = vec![t_v, t_a];
1651 if has_ctx {
1652 ts.push(0.0);
1653 }
1654 if has_cond {
1655 ts.push(t_cond);
1656 }
1657 if has_ref_audio {
1658 ts.push(t_cond_a);
1659 }
1660 ts.sort_by(|a, b| a.partial_cmp(b).unwrap());
1661 ts.dedup();
1662 let row_of = |t: f64| ts.iter().position(|&x| x == t).unwrap();
1663 let (row_v, row_a) = (row_of(t_v), row_of(t_a));
1664 let row_c = if has_cond { row_of(t_cond) } else { 0 };
1665 let row_ctx = if has_ctx { row_of(0.0) } else { 0 };
1666 let row_ca = if has_ref_audio { row_of(t_cond_a) } else { 0 };
1667
1668 let mut rows = vec![0u32; layout.seq_len];
1672 for s in &layout.segments {
1673 match s.kind {
1674 Kind::Text => {
1675 for (i, v) in rows[s.start..s.stop].iter_mut().enumerate() {
1676 let tag = *layout.text_tags.get(i).unwrap_or(&(TAG_TEXT as u8));
1677 *v = (row_v * MODALITIES + tag as usize) as u32;
1678 }
1679 }
1680 Kind::Cond | Kind::RefImg => {
1684 for v in rows[s.start..s.stop].iter_mut() {
1685 *v = (row_c * MODALITIES + TAG_VIDEO) as u32;
1686 }
1687 }
1688 Kind::RefAudio => {
1689 for v in rows[s.start..s.stop].iter_mut() {
1690 *v = (row_ca * MODALITIES + TAG_AUDIO) as u32;
1691 }
1692 }
1693 Kind::Video => {
1694 for v in rows[s.start..s.stop].iter_mut() {
1695 *v = (row_v * MODALITIES + TAG_VIDEO) as u32;
1696 }
1697 }
1698 Kind::Audio => {
1699 for v in rows[s.start..s.stop].iter_mut() {
1700 *v = (row_a * MODALITIES + TAG_AUDIO) as u32;
1701 }
1702 }
1703 Kind::CtxVideo => {
1704 for v in rows[s.start..s.stop].iter_mut() {
1705 *v = (row_ctx * MODALITIES + TAG_VIDEO) as u32;
1706 }
1707 }
1708 Kind::CtxAudio => {
1709 for v in rows[s.start..s.stop].iter_mut() {
1710 *v = (row_ctx * MODALITIES + TAG_AUDIO) as u32;
1711 }
1712 }
1713 }
1714 }
1715
1716 let v_rows = patchify_video(
1718 video,
1719 self.latents_dim,
1720 layout.latent_t,
1721 layout.lat_h,
1722 layout.lat_w,
1723 );
1724 let v_n = v_rows.len() / (self.latents_dim * 4);
1725 let a_rows = pack_audio(audio, self.audio_dim, layout.audio_t);
1726 let vd = self.latents_dim * 4;
1729 let cond_rows: Vec<Vec<f32>> = cond
1730 .iter()
1731 .enumerate()
1732 .map(|(i, z)| {
1733 let mut r = patchify_video(z, self.latents_dim, 1, layout.lat_h, layout.lat_w);
1734 if self.cond_aug < 1.0 {
1735 let noise = crate::videogen::gauss_pub(r.len(), 0x5EED ^ i as u64);
1740 let a = self.cond_aug as f32;
1741 for (v, n) in r.iter_mut().zip(&noise) {
1742 *v = a * *v + (1.0 - a) * n;
1743 }
1744 }
1745 r
1746 })
1747 .collect();
1748
1749 let mut h = vec![0f32; layout.seq_len * hs];
1750 let mut ci = 0usize;
1751 for s in layout.segments.iter().filter(|s| s.kind == Kind::Cond) {
1752 let n = s.stop - s.start;
1753 let r = cond_rows.get(ci).unwrap_or_else(|| {
1754 panic!(
1755 "layout has {} cond segments, {} latents given",
1756 ci + 1,
1757 cond_rows.len()
1758 )
1759 });
1760 self.video_patch
1761 .matmat(r, n, &mut h[s.start * hs..s.stop * hs], pool);
1762 for row in h[s.start * hs..s.stop * hs].chunks_exact_mut(hs) {
1763 for (v, &b) in row.iter_mut().zip(&self.video_patch_b) {
1764 *v += b;
1765 }
1766 }
1767 ci += 1;
1768 }
1769 let (vblk_start, vblk_stop) = layout.video_block();
1773 let (ablk_start, ablk_stop) = layout.audio_block();
1774 let vseg = layout.segment(Kind::Video);
1775 let aseg = layout.segment(Kind::Audio);
1776 let tseg = layout.segment(Kind::Text);
1777 h[tseg.start * hs..tseg.stop * hs].copy_from_slice(&text[..(tseg.stop - tseg.start) * hs]);
1778 self.video_patch
1779 .matmat(&v_rows, v_n, &mut h[vblk_start * hs..vblk_stop * hs], pool);
1780 self.audio_patch.matmat(
1781 &a_rows,
1782 ablk_stop - ablk_start,
1783 &mut h[ablk_start * hs..ablk_stop * hs],
1784 pool,
1785 );
1786 for row in h[vblk_start * hs..vblk_stop * hs].chunks_exact_mut(hs) {
1787 for (v, &b) in row.iter_mut().zip(&self.video_patch_b) {
1788 *v += b;
1789 }
1790 }
1791 for row in h[ablk_start * hs..ablk_stop * hs].chunks_exact_mut(hs) {
1792 for (v, &b) in row.iter_mut().zip(&self.audio_patch_b) {
1793 *v += b;
1794 }
1795 }
1796
1797 for (i, blk) in self.blocks.iter().enumerate() {
1799 let mods = blk.adaln.as_ref().unwrap().eval(&ts, pool);
1800 self.block_forward(blk, &mut h, layout.seq_len, Some(&mods), &layout.pos, &rows);
1801 if let Ok(w) = std::env::var("CMF_MMH3_WATCHROW") {
1804 if let Ok(r) = w.parse::<usize>() {
1805 let row = &h[r * hs..(r + 1) * hs];
1806 let amax = row
1807 .iter()
1808 .filter(|v| v.is_finite())
1809 .fold(0f32, |a, v| a.max(v.abs()));
1810 let bad = row.iter().filter(|v| !v.is_finite()).count();
1811 if bad > 0 || i == 0 || amax > 1e3 {
1812 eprintln!(" watch blk {i}: row {r} absmax {amax:.3e} nonfinite {bad}");
1813 }
1814 }
1815 }
1816 if std::env::var_os("CMF_DIT_PROGRESS").is_some() {
1817 eprint!("\r block {}/{}", i + 1, self.blocks.len());
1818 }
1819 }
1820
1821 if std::env::var("CMF_MMH3_NANPROBE").is_ok() {
1823 let bad: Vec<usize> = (0..h.len() / hs)
1824 .filter(|r| h[r * hs..(r + 1) * hs].iter().any(|v| !v.is_finite()))
1825 .collect();
1826 if !bad.is_empty() {
1827 eprintln!(
1828 " nanprobe rows: {} of {} bad, first 8 {:?}, video seg {}..{}, audio seg {}..{}",
1829 bad.len(),
1830 h.len() / hs,
1831 &bad[..bad.len().min(8)],
1832 vseg.start,
1833 vseg.stop,
1834 aseg.start,
1835 aseg.stop
1836 );
1837 }
1838 }
1839 let fm = self.final_adaln.eval(&ts, pool);
1840 let mut video_out = vec![0f32; (vseg.stop - vseg.start) * vd];
1841 let mut audio_out = vec![0f32; (aseg.stop - aseg.start) * self.audio_dim];
1842 for (seg, row, w, b, dst, dim) in [
1843 (
1844 vseg,
1845 row_v,
1846 &self.video_out,
1847 &self.video_out_b,
1848 &mut video_out,
1849 vd,
1850 ),
1851 (
1852 aseg,
1853 row_a,
1854 &self.audio_out,
1855 &self.audio_out_b,
1856 &mut audio_out,
1857 self.audio_dim,
1858 ),
1859 ] {
1860 let n = seg.stop - seg.start;
1861 let mut hn = vec![0f32; n * hs];
1862 for (o, src) in hn
1863 .chunks_exact_mut(hs)
1864 .zip(h[seg.start * hs..seg.stop * hs].chunks_exact(hs))
1865 {
1866 rms_norm_into(src, &self.final_norm, self.final_eps, o);
1867 }
1868 let shift = &fm[row * 2 * hs..row * 2 * hs + hs];
1871 let scale = &fm[row * 2 * hs + hs..(row + 1) * 2 * hs];
1872 for r in hn.chunks_exact_mut(hs) {
1873 for ((v, &sc), &sh) in r.iter_mut().zip(scale).zip(shift) {
1874 *v = *v * (1.0 + sc) + sh;
1875 }
1876 }
1877 let probe = std::env::var("CMF_MMH3_NANPROBE").is_ok();
1882 if probe {
1883 let bad_in = hn.iter().filter(|v| !v.is_finite()).count();
1884 eprintln!(
1885 " nanprobe dim={dim} n={n} in_bad={bad_in} in_absmax={:.4}",
1886 hn.iter()
1887 .filter(|v| v.is_finite())
1888 .fold(0f32, |a, v| a.max(v.abs()))
1889 );
1890 }
1891 w.matmat(&hn, n, dst, pool);
1892 if probe {
1893 let bad_out = dst.iter().filter(|v| !v.is_finite()).count();
1894 eprintln!(
1895 " nanprobe dim={dim} out_bad={bad_out}/{} out_absmax={:.4}",
1896 dst.len(),
1897 dst.iter()
1898 .filter(|v| v.is_finite())
1899 .fold(0f32, |a, v| a.max(v.abs()))
1900 );
1901 }
1902 for r in dst.chunks_exact_mut(dim) {
1903 for (v, &bv) in r.iter_mut().zip(b.iter()) {
1904 *v += bv;
1905 }
1906 }
1907 }
1908
1909 let out_t = (vseg.stop - vseg.start) / layout.frame_rows;
1918 let out_at = (aseg.stop - aseg.start) / 2;
1919 let video = unpatchify_video(
1920 &video_out,
1921 self.latents_dim,
1922 out_t,
1923 layout.lat_h,
1924 layout.lat_w,
1925 );
1926 let audio = unpack_audio(&audio_out, self.audio_dim, out_at);
1927 (
1928 video.iter().map(|&v| -v).collect(),
1929 audio.iter().map(|&v| -v).collect(),
1930 )
1931 }
1932}
1933
1934fn pool_rows(pool: Option<&Pool>, n: usize, f: &(dyn Fn(usize, usize) + Sync)) {
1938 match pool {
1939 Some(p) => p.run_rows(n, f),
1940 None => f(0, n),
1941 }
1942}
1943
1944pub fn patchify_video(x: &[f32], c: usize, t: usize, h: usize, w: usize) -> Vec<f32> {
1949 let (ph, pw) = (h / 2, w / 2);
1950 let mut out = vec![0f32; t * ph * pw * c * 4];
1951 let mut i = 0;
1952 for ti in 0..t {
1953 for hi in 0..ph {
1954 for wi in 0..pw {
1955 for ci in 0..c {
1956 for p in 0..2 {
1957 for q in 0..2 {
1958 out[i] = x[((ci * t + ti) * h + hi * 2 + p) * w + wi * 2 + q];
1959 i += 1;
1960 }
1961 }
1962 }
1963 }
1964 }
1965 }
1966 out
1967}
1968
1969pub fn unpatchify_video(rows: &[f32], c: usize, t: usize, h: usize, w: usize) -> Vec<f32> {
1971 let (ph, pw) = (h / 2, w / 2);
1972 let mut out = vec![0f32; c * t * h * w];
1973 let mut i = 0;
1974 for ti in 0..t {
1975 for hi in 0..ph {
1976 for wi in 0..pw {
1977 for ci in 0..c {
1978 for p in 0..2 {
1979 for q in 0..2 {
1980 out[((ci * t + ti) * h + hi * 2 + p) * w + wi * 2 + q] = rows[i];
1981 i += 1;
1982 }
1983 }
1984 }
1985 }
1986 }
1987 }
1988 out
1989}
1990
1991pub fn pack_audio(x: &[f32], c: usize, t: usize) -> Vec<f32> {
1994 let mut out = vec![0f32; 2 * t * c];
1995 for ch in 0..2 {
1996 for ti in 0..t {
1997 for ci in 0..c {
1998 out[(ch * t + ti) * c + ci] = x[(ci * 2 + ch) * t + ti];
1999 }
2000 }
2001 }
2002 out
2003}
2004
2005pub fn unpack_audio(rows: &[f32], c: usize, t: usize) -> Vec<f32> {
2007 let mut out = vec![0f32; c * 2 * t];
2008 for ch in 0..2 {
2009 for ti in 0..t {
2010 for ci in 0..c {
2011 out[(ci * 2 + ch) * t + ti] = rows[(ch * t + ti) * c + ci];
2012 }
2013 }
2014 }
2015 out
2016}
2017
2018struct SendPtr(*mut f32);
2021unsafe impl Send for SendPtr {}
2022unsafe impl Sync for SendPtr {}
2023impl SendPtr {
2024 #[allow(clippy::mut_from_ref)]
2026 unsafe fn row(&self, off: usize, len: usize) -> &mut [f32] {
2027 unsafe { std::slice::from_raw_parts_mut(self.0.add(off), len) }
2028 }
2029}
2030
2031fn softmax_inplace(row: &mut [f32]) {
2032 let mx = row.iter().cloned().fold(f32::MIN, f32::max);
2033 let mut den = 0f32;
2034 for r in row.iter_mut() {
2035 *r = (*r - mx).exp();
2036 den += *r;
2037 }
2038 if den > 0.0 {
2039 let inv = 1.0 / den;
2040 for r in row.iter_mut() {
2041 *r *= inv;
2042 }
2043 }
2044}
2045
2046#[cfg(test)]
2047mod tests {
2048 #[test]
2054 fn streaming_layout_degenerates_to_the_bidirectional_one() {
2055 let (lat_h, lat_w, latent_t, audio_t, text_len) = (16, 24, 7, 12, 5);
2056 let base = Layout::t2va(text_len, latent_t, lat_h, lat_w, audio_t);
2057 let cur_v: Vec<usize> = (0..latent_t).collect();
2058 let cur_a: Vec<usize> = (0..audio_t).collect();
2059 let stream = Layout::streaming(text_len, &[], lat_h, lat_w, &[], &cur_v, &[], &cur_a);
2060 assert_eq!(stream.seq_len, base.seq_len, "row count");
2061 assert_eq!(stream.latent_t, base.latent_t);
2062 assert_eq!(stream.audio_t, base.audio_t);
2063 for (i, (a, b)) in stream.pos.iter().zip(&base.pos).enumerate() {
2064 for axis in 0..3 {
2065 assert!(
2066 (a[axis] - b[axis]).abs() < 1e-9,
2067 "row {i} axis {axis}: {a:?} vs {b:?}"
2068 );
2069 }
2070 }
2071 let seg = |l: &Layout, k: Kind| {
2072 l.segments
2073 .iter()
2074 .find(|s| s.kind == k)
2075 .map(|s| (s.start, s.stop))
2076 };
2077 assert_eq!(seg(&stream, Kind::Video), seg(&base, Kind::Video));
2078 assert_eq!(seg(&stream, Kind::Audio), seg(&base, Kind::Audio));
2079 }
2080
2081 #[test]
2084 fn streaming_positions_are_absolute() {
2085 let (lat_h, lat_w) = (16, 24);
2086 let whole = Layout::streaming(
2087 3,
2088 &[],
2089 lat_h,
2090 lat_w,
2091 &[],
2092 &(0..9).collect::<Vec<_>>(),
2093 &[],
2094 &[],
2095 );
2096 let tail = Layout::streaming(
2097 3,
2098 &[],
2099 lat_h,
2100 lat_w,
2101 &[],
2102 &(6..9).collect::<Vec<_>>(),
2103 &[],
2104 &[],
2105 );
2106 let rows = whole.frame_rows;
2107 let (ws, _) = whole.video_block();
2108 let (ts_, _) = tail.video_block();
2109 for k in 0..3 {
2110 let a = whole.pos[ws + (6 + k) * rows];
2111 let b = tail.pos[ts_ + k * rows];
2112 assert!((a[0] - b[0]).abs() < 1e-9, "frame {k}: {a:?} vs {b:?}");
2113 }
2114 }
2115
2116 use super::*;
2117
2118 #[test]
2119 fn schedule_remap_is_an_involution() {
2120 for &s in &[1e-3, 0.25, 0.5, 0.8, 0.972_973, 1.0] {
2121 let a = time_shift_sigma(s, 12.0, 3.0);
2122 let back = time_shift_sigma(a, 3.0, 12.0);
2123 assert!((back - s).abs() < 1e-9, "{s} -> {a} -> {back}");
2124 }
2125 }
2126
2127 #[test]
2128 fn slope_matches_a_finite_difference() {
2129 let h = 1e-6;
2130 for &s in &[0.2, 0.5, 0.9] {
2131 let num = (time_shift_sigma(s + h, 12.0, 3.0) - time_shift_sigma(s - h, 12.0, 3.0))
2132 / (2.0 * h);
2133 let got = time_shift_slope(s, 12.0, 3.0);
2134 assert!((num - got).abs() < 1e-5, "{s}: {num} vs {got}");
2135 }
2136 }
2137
2138 #[test]
2139 fn patchify_round_trips() {
2140 let (c, t, h, w) = (3usize, 2usize, 4usize, 6usize);
2141 let x: Vec<f32> = (0..c * t * h * w).map(|i| i as f32).collect();
2142 let rows = patchify_video(&x, c, t, h, w);
2143 assert_eq!(rows.len(), t * (h / 2) * (w / 2) * c * 4);
2144 assert_eq!(unpatchify_video(&rows, c, t, h, w), x);
2145 }
2146
2147 #[test]
2148 fn audio_pack_round_trips() {
2149 let (c, t) = (5usize, 7usize);
2150 let x: Vec<f32> = (0..c * 2 * t).map(|i| i as f32).collect();
2151 let rows = pack_audio(&x, c, t);
2152 assert_eq!(unpack_audio(&rows, c, t), x);
2153 }
2154
2155 #[test]
2156 fn keyframes_sit_between_the_text_and_the_audio() {
2157 let (tl, lt, lh, lw, at) = (8usize, 3usize, 8usize, 12usize, 5usize);
2158 let base = Layout::t2va(tl, lt, lh, lw, at);
2159 let l = Layout::fl2va(tl, lt, lh, lw, at, &[(0, 39), (38, 39)], &[]);
2161 assert_eq!(l.cond_rows(), 2 * l.frame_rows);
2162 assert_eq!(l.seq_len, base.seq_len + 2 * l.frame_rows);
2163 let kinds: Vec<_> = l.segments.iter().map(|s| s.kind).collect();
2164 assert_eq!(
2165 kinds,
2166 vec![Kind::Text, Kind::Cond, Kind::Cond, Kind::Audio, Kind::Video]
2167 );
2168 let (a0, v0) = (l.segment(Kind::Audio), l.segment(Kind::Video));
2171 let (ba, bv) = (base.segment(Kind::Audio), base.segment(Kind::Video));
2172 assert_eq!(l.pos[a0.start][0], base.pos[ba.start][0]);
2173 assert_eq!(l.pos[v0.start][0], base.pos[bv.start][0]);
2174 let c: Vec<_> = l.segments.iter().filter(|s| s.kind == Kind::Cond).collect();
2177 assert_eq!(l.pos[c[0].start][0], tl as f64);
2178 let spans: f64 = (0..lt)
2179 .map(|k| FRAME_RESCALE * FRAME_PER_TOKEN[k % 5])
2180 .sum();
2181 assert!((l.pos[c[1].start][0] - (tl as f64 + spans - FRAME_RESCALE)).abs() < 1e-12);
2182 assert_eq!(l.pos[c[0].start][1], l.pos[v0.start][1]);
2184 assert_eq!(l.pos[c[0].start][2], l.pos[v0.start][2]);
2185 }
2186
2187 #[test]
2188 fn layout_places_the_target_streams_last() {
2189 let l = Layout::t2va(8, 3, 8, 12, 5);
2190 assert_eq!(l.frame_rows, 4 * 6);
2191 assert_eq!(l.seq_len, 8 + 2 * 5 + 3 * 24);
2192 assert_eq!(l.segments[0].kind, Kind::Text);
2193 assert_eq!(l.segments[1].kind, Kind::Audio);
2194 assert_eq!(l.segments[2].kind, Kind::Video);
2195 let v = l.segment(Kind::Video);
2197 let t0 = l.pos[v.start][0];
2198 let t1 = l.pos[v.start + l.frame_rows][0];
2199 assert!((t1 - t0 - FRAME_RESCALE).abs() < 1e-12);
2200 }
2201}