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 { start: 0, stop: text_len, kind: Kind::Text });
520 for i in 0..text_len {
521 pos.push([i as f64, 0.0, 0.0]);
522 }
523 let cursor = text_len as f64;
524
525 let t_at = |k: usize| -> f64 {
528 cursor + (0..k).map(|j| FRAME_RESCALE * FRAME_PER_TOKEN[j % 5]).sum::<f64>()
529 };
530
531 for (idxs, kind) in [(ctx_audio, Kind::CtxAudio), (cur_audio, Kind::Audio)] {
538 if idxs.is_empty() {
539 continue;
540 }
541 let start = pos.len();
542 for ch in 0..2 {
543 let w = if ch == 0 { w_low } else { w_high };
544 for &i in idxs {
545 pos.push([cursor + i as f64, 0.0, w]);
546 }
547 }
548 segments.push(Segment { start, stop: pos.len(), kind });
549 }
550
551 for (idxs, kind) in [(ctx_video, Kind::CtxVideo), (cur_video, Kind::Video)] {
552 if idxs.is_empty() {
553 continue;
554 }
555 let start = pos.len();
556 for &k in idxs {
557 let t = t_at(k);
558 for &h in &h_axis {
559 for &w in &w_axis {
560 pos.push([t, h, w]);
561 }
562 }
563 }
564 segments.push(Segment { start, stop: pos.len(), kind });
565 }
566
567 Self {
568 seq_len: pos.len(),
569 segments,
570 pos,
571 text_len,
572 audio_t: ctx_audio.len() + cur_audio.len(),
573 latent_t: ctx_video.len() + cur_video.len(),
574 lat_h,
575 lat_w,
576 frame_rows,
577 text_tags: if text_tags.is_empty() {
578 vec![TAG_TEXT as u8; text_len]
579 } else {
580 text_tags.to_vec()
581 },
582 }
583 }
584
585 pub fn video_block(&self) -> (usize, usize) {
588 self.span(&[Kind::CtxVideo, Kind::Video])
589 }
590
591 pub fn audio_block(&self) -> (usize, usize) {
593 self.span(&[Kind::CtxAudio, Kind::Audio])
594 }
595
596 fn span(&self, kinds: &[Kind]) -> (usize, usize) {
597 let mut lo = usize::MAX;
598 let mut hi = 0usize;
599 for s in self.segments.iter().filter(|s| kinds.contains(&s.kind)) {
600 lo = lo.min(s.start);
601 hi = hi.max(s.stop);
602 }
603 if lo == usize::MAX { (0, 0) } else { (lo, hi) }
604 }
605
606 pub fn cond_rows(&self) -> usize {
608 self.segments
609 .iter()
610 .filter(|s| s.kind == Kind::Cond)
611 .map(|s| s.stop - s.start)
612 .sum()
613 }
614
615 fn segment(&self, kind: Kind) -> Segment {
616 *self
617 .segments
618 .iter()
619 .find(|s| s.kind == kind)
620 .expect("every layout carries all three streams")
621 }
622}
623
624struct Adaln {
627 w: Proj,
629 b: Vec<f32>,
630 table: Vec<f32>,
632 rank: usize,
633 out: usize,
634}
635
636impl Adaln {
637 fn load(model: &Arc<CmfModel>, prefix: &str) -> Result<Self, String> {
638 let w = Proj::from_model(model, &format!("{prefix}.weight"))?;
639 let table = crate::dit::cmf_f32(model, &format!("{prefix}.table"))?;
640 let b = crate::dit::cmf_f32(model, &format!("{prefix}.bias"))?;
641 let out = w.rows();
642 let rank = table.len() / CURVE_GRID;
643 Ok(Self {
644 w,
645 b,
646 table,
647 rank,
648 out,
649 })
650 }
651
652 fn eval(&self, ts: &[f64], pool: Option<&Pool>) -> Vec<f32> {
656 let g = CURVE_GRID;
657 let mut coords = vec![0f32; ts.len() * self.rank];
658 for (i, &t) in ts.iter().enumerate() {
659 let p = (t.clamp(0.0, 1.0) * (g - 1) as f64) as f32;
663 let i0 = (p.floor() as usize).min(g - 2);
664 let f = p - i0 as f32;
665 for k in 0..self.rank {
666 let (a, b) = (
667 self.table[i0 * self.rank + k],
668 self.table[(i0 + 1) * self.rank + k],
669 );
670 coords[i * self.rank + k] = a + (b - a) * f;
671 }
672 }
673 let mut out = vec![0f32; ts.len() * self.out];
674 self.w.matmat(&coords, ts.len(), &mut out, pool);
675 for row in out.chunks_exact_mut(self.out) {
676 for (v, &bv) in row.iter_mut().zip(&self.b) {
677 *v += bv;
678 }
679 }
680 out
681 }
682}
683
684struct Block {
685 norm1: Vec<f32>,
686 norm2: Vec<f32>,
687 qkv: Proj, out: Proj, q_norm: Vec<f32>,
690 k_norm: Vec<f32>,
691 fc1: Proj, fc2: Proj, adaln: Option<Adaln>,
694 lora: BlockLora,
699}
700
701#[derive(Default)]
706struct BlockLora {
707 qkv: Option<LoraBranch>,
708 out: Option<LoraBranch>,
709 fc1: Option<LoraBranch>,
710 fc2: Option<LoraBranch>,
711}
712
713fn proj_with_lora(
726 p: &Proj,
727 br: &Option<LoraBranch>,
728 x: &[f32],
729 n: usize,
730 out: &mut [f32],
731 pool: Option<&Pool>,
732) {
733 #[cfg(target_os = "macos")]
734 if let Some(l) = br.as_ref().filter(|l| l.live()) {
735 if let Some((model, idx)) = p.q4tp_mapped() {
736 let (rows, cols) = (p.rows(), p.cols());
737 if n >= 32
738 && cols % 32 == 0
739 && crate::gpu::enabled_here()
740 && !crate::gpu::mm_killed()
741 && (!crate::ltxlora::wants_measurement() || l.resonance() != 0.0)
742 && crate::gpu_metal::q4tp_matmat_lora(model, idx, x, n, rows, cols, out, &l.side())
743 {
744 return;
745 }
746 }
747 }
748 p.matmat(x, n, out, pool);
749 if let Some(l) = br {
750 l.add(x, n, out, pool);
751 }
752}
753
754impl BlockLora {
755 fn live(br: &Option<LoraBranch>) -> bool {
758 br.as_ref().is_some_and(|l| l.live())
759 }
760 fn ffn_any(&self) -> bool {
761 Self::live(&self.fc1) || Self::live(&self.fc2)
762 }
763}
764
765pub(crate) const CURVE_GRID: usize = 1025;
766
767pub struct MiniMaxH3 {
768 video_patch: Proj,
769 video_patch_b: Vec<f32>,
770 audio_patch: Proj,
771 audio_patch_b: Vec<f32>,
772 condition: Proj,
773 condition_b: Vec<f32>,
774 refiner: Vec<Block>,
775 refiner_norm: Vec<f32>,
776 blocks: Vec<Block>,
777 final_norm: Vec<f32>,
778 final_adaln: Adaln,
779 video_out: Proj,
780 video_out_b: Vec<f32>,
781 audio_out: Proj,
782 audio_out_b: Vec<f32>,
783 inv_freq: Vec<f32>,
784 pool: Option<Arc<Pool>>,
785 pub hidden: usize,
786 pub heads: usize,
787 pub head_dim: usize,
788 pub ffn: usize,
789 pub latents_dim: usize,
790 pub audio_dim: usize,
791 pub text_dim: usize,
792 pub shift_video: f64,
793 pub shift_audio: f64,
794 eps: f64,
795 qk_eps: f64,
796 final_eps: f64,
797 pub cond_aug: f64,
801 pub cond_aug_audio: f64,
804 lora_bound: std::collections::BTreeSet<String>,
806}
807
808fn rms_norm_into(x: &[f32], w: &[f32], eps: f64, dst: &mut [f32]) {
809 let ss = x.iter().map(|&v| (v as f64) * (v as f64)).sum::<f64>() / x.len() as f64;
810 let inv = 1.0 / (ss + eps).sqrt();
811 for ((d, &v), &g) in dst.iter_mut().zip(x).zip(w) {
812 *d = (v as f64 * inv) as f32 * g;
813 }
814}
815
816fn silu(v: f32) -> f32 {
817 v / (1.0 + (-v).exp())
818}
819
820impl MiniMaxH3 {
821 pub fn from_cmf(model: &Arc<CmfModel>) -> Result<Self, String> {
822 Self::from_cmf_lora(model, None)
823 }
824
825 pub fn from_cmf_lora(
832 model: &Arc<CmfModel>,
833 bank: Option<&LoraBank>,
834 ) -> Result<Self, String> {
835 let cfg: serde_json::Value = serde_json::from_slice(
836 model
837 .tensor_bytes("dit.config_json")
838 .map_err(|e| e.to_string())?,
839 )
840 .map_err(|e| format!("dit.config_json: {e}"))?;
841 let u = |k: &str| cfg[k].as_u64().unwrap_or(0) as usize;
842 let f = |k: &str, d: f64| cfg[k].as_f64().unwrap_or(d);
843 let n_blocks = u("num_layers");
844 let n_refiner = u("token_refiner_num_layers");
845 let f32v = |n: &str| crate::dit::cmf_f32(model, n);
846
847 let bound: std::cell::RefCell<std::collections::BTreeSet<String>> = Default::default();
849 let load_block = |prefix: &str, with_adaln: bool| -> Result<Block, String> {
850 let qkv = Proj::from_model(model, &format!("dit.{prefix}.attn.qkv_proj.weight"))?;
851 let out = Proj::from_model(model, &format!("dit.{prefix}.attn.out_proj.weight"))?;
852 let fc1 = Proj::from_model(model, &format!("dit.{prefix}.mlp.fc1.weight"))?;
853 let fc2 = Proj::from_model(model, &format!("dit.{prefix}.mlp.fc2.weight"))?;
854 let lora = match bank {
859 None => BlockLora::default(),
860 Some(k) => {
861 let mut take = |suffix: &str, p: &Proj| -> Result<Option<LoraBranch>, String> {
862 let key = format!("{prefix}.{suffix}");
863 let br = k.branch_for(&format!("dit.{key}"), p.rows(), p.cols())?;
864 if br.is_some() {
865 bound.borrow_mut().insert(key);
866 }
867 Ok(br)
868 };
869 BlockLora {
870 qkv: take("attn.qkv_proj", &qkv)?,
871 out: take("attn.out_proj", &out)?,
872 fc1: take("mlp.fc1", &fc1)?,
873 fc2: take("mlp.fc2", &fc2)?,
874 }
875 }
876 };
877 Ok(Block {
878 norm1: f32v(&format!("dit.{prefix}.norm1.weight"))?,
879 norm2: f32v(&format!("dit.{prefix}.norm2.weight"))?,
880 qkv,
881 out,
882 q_norm: f32v(&format!("dit.{prefix}.attn.q_norm.weight"))?,
883 k_norm: f32v(&format!("dit.{prefix}.attn.k_norm.weight"))?,
884 fc1,
885 fc2,
886 adaln: if with_adaln {
887 Some(Adaln::load(model, &format!("dit.{prefix}.adaln"))?)
888 } else {
889 None
890 },
891 lora,
892 })
893 };
894 let blocks = (0..n_blocks)
895 .map(|i| load_block(&format!("blocks.{i}"), true))
896 .collect::<Result<Vec<_>, _>>()?;
897 let refiner = (0..n_refiner)
898 .map(|i| load_block(&format!("token_refiner.blocks.{i}"), false))
899 .collect::<Result<Vec<_>, _>>()?;
900
901 Ok(Self {
902 video_patch: Proj::from_model(model, "dit.video_patch_proj.weight")?,
903 video_patch_b: f32v("dit.video_patch_proj.bias")?,
904 audio_patch: Proj::from_model(model, "dit.audio_patch_proj.weight")?,
905 audio_patch_b: f32v("dit.audio_patch_proj.bias")?,
906 condition: Proj::from_model(model, "dit.condition_proj.weight")?,
907 condition_b: f32v("dit.condition_proj.bias")?,
908 refiner,
909 refiner_norm: f32v("dit.token_refiner.final_norm.weight")?,
910 blocks,
911 final_norm: f32v("dit.final_layer.norm.weight")?,
912 final_adaln: Adaln::load(model, "dit.final_layer.adaln")?,
913 video_out: Proj::from_model(model, "dit.final_layer.video_out.weight")?,
914 video_out_b: f32v("dit.final_layer.video_out.bias")?,
915 audio_out: Proj::from_model(model, "dit.final_layer.audio_out.weight")?,
916 audio_out_b: f32v("dit.final_layer.audio_out.bias")?,
917 inv_freq: f32v("dit.rope_inv_freq")?,
918 pool: Pool::from_env(),
919 hidden: u("hidden_size"),
920 heads: u("num_attention_heads"),
921 head_dim: u("attention_head_dim"),
922 ffn: u("ffn_hidden_size"),
923 latents_dim: u("latents_dim"),
924 audio_dim: u("audio_latents_dim"),
925 text_dim: u("text_dim"),
926 shift_video: f("sigma_shift_video", 12.0),
927 shift_audio: f("sigma_shift_audio", 3.0),
928 eps: f("norm_eps", 1e-5),
929 qk_eps: f("qk_norm_eps", 1e-5),
930 final_eps: f("final_norm_eps", 1e-5),
931 cond_aug: VISUAL_COND_TIMESTEP,
932 cond_aug_audio: AUDIO_COND_TIMESTEP,
933 lora_bound: bound.into_inner(),
934 })
935 }
936
937 pub fn lora_bound(&self) -> usize {
939 self.lora_bound.len()
940 }
941
942 pub fn lora_binds(&self, key: &str) -> bool {
944 self.lora_bound.contains(key)
945 }
946
947 pub fn lora_report(&self) -> Option<String> {
952 if !crate::ltxlora::probe_report_on() {
953 return None;
954 }
955 let mut rows: Vec<(f32, bool, String)> = Vec::new();
956 let mut walk = |blocks: &[Block], pre: &str| {
957 for (i, b) in blocks.iter().enumerate() {
958 for (name, br) in [
959 ("attn.qkv_proj", &b.lora.qkv),
960 ("attn.out_proj", &b.lora.out),
961 ("mlp.fc1", &b.lora.fc1),
962 ("mlp.fc2", &b.lora.fc2),
963 ] {
964 if let Some(l) = br {
965 rows.push((l.resonance(), l.live(), format!("{pre}{i}.{name}")));
966 }
967 }
968 }
969 };
970 walk(&self.blocks, "blocks.");
971 walk(&self.refiner, "token_refiner.blocks.");
972 if rows.is_empty() {
973 return None;
974 }
975 rows.sort_by(|a, b| b.0.total_cmp(&a.0));
976 let live = rows.iter().filter(|r| r.1).count();
977 let mut s = format!(
978 "lora branches by contribution ‖sΔY‖/‖Y‖ ({} of {} live):\n",
979 live,
980 rows.len()
981 );
982 for (r, on, name) in &rows {
983 s.push_str(&format!(
984 " {:>8.4} {} {name}\n",
985 r,
986 if *on { "on " } else { "off" }
987 ));
988 }
989 Some(s)
990 }
991
992 pub fn refine_text(&self, states: &[f32], n: usize) -> Vec<f32> {
996 let pool = self.pool.as_deref();
997 let mut h = vec![0f32; n * self.hidden];
998 self.condition.matmat(states, n, &mut h, pool);
999 for row in h.chunks_exact_mut(self.hidden) {
1000 for (v, &b) in row.iter_mut().zip(&self.condition_b) {
1001 *v += b;
1002 }
1003 }
1004 let ids: Vec<[f64; 3]> = Vec::new();
1005 for blk in &self.refiner {
1006 self.block_forward(blk, &mut h, n, None, &ids, &[]);
1007 }
1008 let mut out = vec![0f32; n * self.hidden];
1009 for (o, x) in out
1010 .chunks_exact_mut(self.hidden)
1011 .zip(h.chunks_exact(self.hidden))
1012 {
1013 rms_norm_into(x, &self.refiner_norm, self.final_eps, o);
1014 }
1015 out
1016 }
1017
1018 fn rope_angles(&self, pos: &[[f64; 3]]) -> Vec<f32> {
1022 let k = self.inv_freq.len();
1023 let mut out = Vec::with_capacity(pos.len() * 3 * k);
1024 for p in pos {
1025 for axis in 0..3 {
1026 for j in 0..k {
1027 out.push((p[axis] * self.inv_freq[j] as f64) as f32);
1028 }
1029 }
1030 }
1031 out
1032 }
1033
1034 #[allow(clippy::too_many_arguments)]
1045 fn norm_rope_w(
1046 &self,
1047 v: &mut [f32],
1048 n: usize,
1049 heads: usize,
1050 w: &[f32],
1051 angles: &[f32],
1052 stride: usize,
1053 off: usize,
1054 ) {
1055 let hd = self.head_dim;
1056 let pairs = if angles.is_empty() {
1057 0
1058 } else {
1059 angles.len() / n
1060 };
1061 let pool = self.pool.as_deref();
1062 let ptr = SendPtr(v.as_mut_ptr());
1063 let work = |lo: usize, hi: usize| {
1064 for p in lo..hi {
1065 for h in 0..heads {
1066 let x = unsafe { ptr.row(p * stride + off + h * hd, hd) };
1069 let ss = x.iter().map(|&a| (a as f64) * (a as f64)).sum::<f64>() / hd as f64;
1070 let inv = 1.0 / (ss + self.qk_eps).sqrt();
1071 for (d, &g) in x.iter_mut().zip(w) {
1072 *d = (*d as f64 * inv) as f32 * g;
1073 }
1074 for j in 0..pairs {
1075 let a = angles[p * pairs + j];
1076 let (s, c) = a.sin_cos();
1077 let (lo_v, hi_v) = (x[j], x[j + pairs]);
1078 x[j] = lo_v * c - hi_v * s;
1079 x[j + pairs] = lo_v * s + hi_v * c;
1080 }
1081 }
1082 }
1083 };
1084 match pool {
1085 Some(pl) => pl.run_rows(n, &work),
1086 None => work(0, n),
1087 }
1088 }
1089
1090 fn attention(
1096 &self,
1097 qkv: &[f32],
1098 n: usize,
1099 attn: &mut [f32],
1100 nr: Option<(&[f32], &[f32], &[f32], f32)>,
1101 ) {
1102 let t_repack = std::time::Instant::now();
1103 let (nh, hd) = (self.heads, self.head_dim);
1104 let inner = nh * hd;
1105 let scale = 1.0 / (hd as f32).sqrt();
1106 let pool = self.pool.as_deref();
1107 if std::env::var("CMF_MMH3_ATTN").as_deref() != Ok("cpu")
1115 && crate::gpu::enabled_here()
1116 && n >= 256
1117 {
1118 if std::env::var("CMF_MMH3_ATTN").as_deref() != Ok("repack")
1131 && crate::gpu::dit_attention_packed(qkv, nh, n, hd, scale, nr, attn)
1132 {
1133 return;
1139 }
1140 assert!(
1141 nr.is_none(),
1142 "device qk-norm was requested but the device path refused; \
1143 the host loops below expect q/k already normalized"
1144 );
1145 let mut qh = vec![0f32; nh * n * hd];
1146 let mut kh = vec![0f32; nh * n * hd];
1147 let mut vh = vec![0f32; nh * n * hd];
1148 {
1149 let (pq, pk, pv) = (
1150 SendPtr(qh.as_mut_ptr()),
1151 SendPtr(kh.as_mut_ptr()),
1152 SendPtr(vh.as_mut_ptr()),
1153 );
1154 pool_rows(pool, n, &|lo, hi| {
1155 for p in lo..hi {
1156 let base = p * 3 * inner;
1157 for h in 0..nh {
1158 let dst = (h * n + p) * hd;
1159 unsafe {
1162 pq.row(dst, hd)
1163 .copy_from_slice(&qkv[base + h * hd..base + (h + 1) * hd]);
1164 pk.row(dst, hd).copy_from_slice(
1165 &qkv[base + inner + h * hd..base + inner + (h + 1) * hd],
1166 );
1167 pv.row(dst, hd).copy_from_slice(
1168 &qkv[base + 2 * inner + h * hd
1169 ..base + 2 * inner + (h + 1) * hd],
1170 );
1171 }
1172 }
1173 }
1174 });
1175 }
1176 Self::prof(2, t_repack);
1183 if crate::gpu::dit_attention(&qh, &kh, &vh, nh, nh, n, hd, scale, attn) {
1186 return;
1187 }
1188 }
1189 let mut qh = vec![0f32; n * hd];
1190 let mut kh = vec![0f32; n * hd];
1191 let mut vt = vec![0f32; hd * n];
1192 let mut scores = vec![0f32; n * n];
1193 let mut oh = vec![0f32; n * hd];
1194 for h in 0..nh {
1195 for p in 0..n {
1196 let base = p * 3 * inner;
1197 let qs = &qkv[base + h * hd..base + (h + 1) * hd];
1198 for (d, &val) in qs.iter().enumerate() {
1199 qh[p * hd + d] = val * scale;
1200 }
1201 kh[p * hd..(p + 1) * hd]
1202 .copy_from_slice(&qkv[base + inner + h * hd..base + inner + (h + 1) * hd]);
1203 let vs = &qkv[base + 2 * inner + h * hd..base + 2 * inner + (h + 1) * hd];
1204 for (d, &val) in vs.iter().enumerate() {
1205 vt[d * n + p] = val;
1206 }
1207 }
1208 crate::fcd_ops::gemm_nt(&qh, &kh, &mut scores, n, hd, n, pool);
1209 let sp = SendPtr(scores.as_mut_ptr());
1210 let soft = |lo: usize, hi: usize| {
1211 for r in lo..hi {
1212 softmax_inplace(unsafe { sp.row(r * n, n) });
1214 }
1215 };
1216 match pool {
1217 Some(pl) => pl.run_rows(n, &soft),
1218 None => soft(0, n),
1219 }
1220 crate::fcd_ops::gemm_nt(&scores, &vt, &mut oh, n, n, hd, pool);
1221 for p in 0..n {
1222 attn[p * inner + h * hd..p * inner + (h + 1) * hd]
1223 .copy_from_slice(&oh[p * hd..(p + 1) * hd]);
1224 }
1225 }
1226 }
1227
1228 fn prof(slot: usize, t: std::time::Instant) {
1232 if !mmh3_prof_on() {
1233 return;
1234 }
1235 MMH3_PROF[slot].fetch_add(
1236 t.elapsed().as_micros() as u64,
1237 std::sync::atomic::Ordering::Relaxed,
1238 );
1239 }
1240
1241 fn block_forward(
1245 &self,
1246 blk: &Block,
1247 x: &mut [f32],
1248 n: usize,
1249 mods: Option<&[f32]>,
1250 pos: &[[f64; 3]],
1251 rows: &[u32],
1252 ) {
1253 let hs = self.hidden;
1254 let pool = self.pool.as_deref();
1255 let inner = self.heads * self.head_dim;
1256 let angles = if pos.is_empty() {
1257 Vec::new()
1258 } else {
1259 self.rope_angles(pos)
1260 };
1261
1262 let t = std::time::Instant::now();
1263 let mut xn = vec![0f32; n * hs];
1264 self.norm_rows(&mut xn, x, n, hs, &blk.norm1, self.eps);
1265 if let (Some(m), false) = (mods, rows.is_empty()) {
1266 self.modulate(&mut xn, hs, m, rows, 0, 1);
1267 }
1268 Self::prof(0, t);
1269 let t = std::time::Instant::now();
1270 let t_qkv = std::time::Instant::now();
1277 let qk_gpu = std::env::var("CMF_MMH3_QKNORM").as_deref() != Ok("cpu")
1283 && crate::gpu::enabled_here()
1284 && crate::gpu::dit_attention_packed_available()
1285 && n >= 256;
1286 if qk_gpu
1291 && !BlockLora::live(&blk.lora.qkv)
1292 && std::env::var("CMF_MMH3_FUSEQKV").as_deref() != Ok("0")
1293 {
1294 let fuse_out = !BlockLora::live(&blk.lora.out)
1299 && std::env::var("CMF_MMH3_FUSEOUT").as_deref() != Ok("0");
1300 if std::env::var("CMF_GPU_DEBUG").is_ok() {
1301 static ONCE: std::sync::Once = std::sync::Once::new();
1302 ONCE.call_once(|| {
1303 eprintln!(
1304 "mmh3 fuse-out gate: qkv_q={} out_q={} qkv_map={} out_map={}",
1305 matches!(&blk.qkv, Proj::Q(_)),
1306 matches!(&blk.out, Proj::Q(_)),
1307 matches!(&blk.qkv, Proj::Q(q) if q.mapped_device_gemm().is_some()),
1308 matches!(&blk.out, Proj::Q(o) if o.mapped_device_gemm().is_some()),
1309 )
1310 });
1311 }
1312 if let (true, Proj::Q(q), Proj::Q(o)) = (fuse_out, &blk.qkv, &blk.out) {
1313 if let (Some((m, i)), Some((_, oi))) = (q.mapped_device_gemm(), o.mapped_device_gemm()) {
1314 let mut proj = vec![0f32; n * hs];
1315 if crate::gpu::dit_qkv_attn_out(
1316 m,
1317 i,
1318 oi,
1319 &xn,
1320 n,
1321 hs,
1322 self.heads,
1323 self.head_dim,
1324 1.0 / (self.head_dim as f32).sqrt(),
1325 (
1326 &angles[..],
1327 &blk.q_norm[..],
1328 &blk.k_norm[..],
1329 self.qk_eps as f32,
1330 ),
1331 &mut proj,
1332 ) {
1333 Self::prof(1, t_qkv);
1334 let t_res = std::time::Instant::now();
1335 self.residual(x, hs, &proj, mods, rows, 2);
1336 Self::prof(8, t_res);
1337 self.ffn_tail(blk, x, n, mods, rows);
1338 return;
1339 }
1340 }
1341 }
1342 if let Proj::Q(q) = &blk.qkv {
1343 if let Some((m, i)) = q.mapped_device_gemm() {
1344 let mut attn = vec![0f32; n * inner];
1345 if crate::gpu::dit_qkv_attention(
1346 m,
1347 i,
1348 &xn,
1349 n,
1350 hs,
1351 self.heads,
1352 self.head_dim,
1353 1.0 / (self.head_dim as f32).sqrt(),
1354 (
1355 &angles[..],
1356 &blk.q_norm[..],
1357 &blk.k_norm[..],
1358 self.qk_eps as f32,
1359 ),
1360 &mut attn,
1361 ) {
1362 Self::prof(1, t_qkv);
1368 let t_out = std::time::Instant::now();
1369 let mut proj = vec![0f32; n * hs];
1370 proj_with_lora(&blk.out, &blk.lora.out, &attn, n, &mut proj, pool);
1371 Self::prof(4, t_out);
1372 let t_res = std::time::Instant::now();
1373 self.residual(x, hs, &proj, mods, rows, 2);
1374 Self::prof(8, t_res);
1375 self.ffn_tail(blk, x, n, mods, rows);
1376 return;
1377 }
1378 }
1379 }
1380 }
1381 let mut qkv = vec![0f32; n * 3 * inner];
1382 proj_with_lora(&blk.qkv, &blk.lora.qkv, &xn, n, &mut qkv, pool);
1383 Self::prof(1, t);
1384 let qk_on_gpu = qk_gpu;
1397 let t = std::time::Instant::now();
1398 if !qk_on_gpu {
1399 for (which, w) in [(0usize, &blk.q_norm), (1usize, &blk.k_norm)] {
1400 self.norm_rope_w(
1401 &mut qkv,
1402 n,
1403 self.heads,
1404 w,
1405 &angles,
1406 3 * inner,
1407 which * inner,
1408 );
1409 }
1410 }
1411 Self::prof(2, t);
1412 let t = std::time::Instant::now();
1413 let mut attn = vec![0f32; n * inner];
1414 self.attention(
1415 &qkv,
1416 n,
1417 &mut attn,
1418 qk_on_gpu.then_some((
1419 &angles[..],
1420 &blk.q_norm[..],
1421 &blk.k_norm[..],
1422 self.qk_eps as f32,
1423 )),
1424 );
1425 Self::prof(3, t);
1426 let t = std::time::Instant::now();
1427 let mut proj = vec![0f32; n * hs];
1428 proj_with_lora(&blk.out, &blk.lora.out, &attn, n, &mut proj, pool);
1429 Self::prof(4, t);
1430 let t = std::time::Instant::now();
1434 self.residual(x, hs, &proj, mods, rows, 2);
1435 Self::prof(8, t);
1436
1437 self.ffn_tail(blk, x, n, mods, rows);
1438 }
1439
1440 fn ffn_tail(&self, blk: &Block, x: &mut [f32], n: usize, mods: Option<&[f32]>, rows: &[u32]) {
1445 let hs = self.hidden;
1446 let pool = self.pool.as_deref();
1447 let mut xn = vec![0f32; n * hs];
1448 let mut proj = vec![0f32; n * hs];
1449 let t = std::time::Instant::now();
1450 self.norm_rows(&mut xn, x, n, hs, &blk.norm2, self.eps);
1451 if let (Some(m), false) = (mods, rows.is_empty()) {
1452 self.modulate(&mut xn, hs, m, rows, 3, 4);
1453 }
1454 Self::prof(0, t);
1455 let t = std::time::Instant::now();
1456 if std::env::var("CMF_MMH3_FFN").as_deref() != Ok("cpu")
1461 && !blk.lora.ffn_any()
1462 && crate::gpu::enabled_here()
1463 && n >= 64
1464 {
1465 if let (Proj::Q(q1), Proj::Q(q2)) = (&blk.fc1, &blk.fc2) {
1466 if let (Some((m, i1)), Some((_, i2))) = (q1.mapped_device_gemm(), q2.mapped_device_gemm()) {
1467 let mut fout = vec![0f32; n * hs];
1468 if crate::gpu::q4tp_ffn_packed(m, i1, i2, &xn, n, hs, self.ffn, None, &mut fout)
1469 {
1470 Self::prof(5, t);
1471 self.residual(x, hs, &fout, mods, rows, 5);
1472 return;
1473 }
1474 }
1475 }
1476 }
1477 let mut gu = vec![0f32; n * 2 * self.ffn];
1478 proj_with_lora(&blk.fc1, &blk.lora.fc1, &xn, n, &mut gu, pool);
1479 Self::prof(5, t);
1480 let t = std::time::Instant::now();
1481 let ffn = self.ffn;
1483 let mut act = vec![0f32; n * ffn];
1484 let ap = SendPtr(act.as_mut_ptr());
1485 pool_rows(pool, n, &|lo, hi| {
1486 for p in lo..hi {
1487 let row = &gu[p * 2 * ffn..(p + 1) * 2 * ffn];
1488 let (g, up) = row.split_at(ffn);
1489 for (o, (&a, &b)) in unsafe { ap.row(p * ffn, ffn) }
1491 .iter_mut()
1492 .zip(g.iter().zip(up))
1493 {
1494 *o = silu(a) * b;
1495 }
1496 }
1497 });
1498 Self::prof(6, t);
1499 let t = std::time::Instant::now();
1500 proj_with_lora(&blk.fc2, &blk.lora.fc2, &act, n, &mut proj, pool);
1501 Self::prof(7, t);
1502 self.residual(x, hs, &proj, mods, rows, 5);
1503 }
1504
1505 fn norm_rows(&self, dst: &mut [f32], src: &[f32], n: usize, hs: usize, w: &[f32], eps: f64) {
1510 let ptr = SendPtr(dst.as_mut_ptr());
1511 pool_rows(self.pool.as_deref(), n, &|lo, hi| {
1512 for p in lo..hi {
1513 rms_norm_into(&src[p * hs..(p + 1) * hs], w, eps, unsafe {
1515 ptr.row(p * hs, hs)
1516 });
1517 }
1518 });
1519 }
1520
1521 fn modulate(
1523 &self,
1524 x: &mut [f32],
1525 hs: usize,
1526 mods: &[f32],
1527 rows: &[u32],
1528 shift_e: usize,
1529 scale_e: usize,
1530 ) {
1531 let stride = EXPAND * hs;
1532 let ptr = SendPtr(x.as_mut_ptr());
1533 pool_rows(self.pool.as_deref(), rows.len(), &|lo, hi| {
1534 for p in lo..hi {
1535 let base = rows[p] as usize * stride;
1536 let shift = &mods[base + shift_e * hs..base + (shift_e + 1) * hs];
1537 let scale = &mods[base + scale_e * hs..base + (scale_e + 1) * hs];
1538 for ((v, &sc), &sh) in unsafe { ptr.row(p * hs, hs) }
1540 .iter_mut()
1541 .zip(scale)
1542 .zip(shift)
1543 {
1544 *v = *v * (1.0 + sc) + sh;
1545 }
1546 }
1547 });
1548 }
1549
1550 fn residual(
1553 &self,
1554 x: &mut [f32],
1555 hs: usize,
1556 other: &[f32],
1557 mods: Option<&[f32]>,
1558 rows: &[u32],
1559 gate_e: usize,
1560 ) {
1561 let n = x.len() / hs;
1562 let stride = EXPAND * hs;
1563 let ptr = SendPtr(x.as_mut_ptr());
1564 let gated = mods.filter(|_| !rows.is_empty());
1565 pool_rows(self.pool.as_deref(), n, &|lo, hi| {
1566 for p in lo..hi {
1567 let row = unsafe { ptr.row(p * hs, hs) };
1569 let src = &other[p * hs..(p + 1) * hs];
1570 match gated {
1571 Some(m) => {
1572 let base = rows[p] as usize * stride;
1573 let gate = &m[base + gate_e * hs..base + (gate_e + 1) * hs];
1574 for ((v, &g), &o) in row.iter_mut().zip(gate).zip(src) {
1575 *v += g * o;
1576 }
1577 }
1578 None => {
1579 for (v, &o) in row.iter_mut().zip(src) {
1580 *v += o;
1581 }
1582 }
1583 }
1584 }
1585 });
1586 }
1587
1588 pub fn forward(
1595 &self,
1596 layout: &Layout,
1597 text: &[f32],
1598 video: &[f32],
1599 audio: &[f32],
1600 sigma_v: f64,
1601 cond: &[Vec<f32>],
1602 ) -> (Vec<f32>, Vec<f32>) {
1603 let hs = self.hidden;
1604 let pool = self.pool.as_deref();
1605 let sigma_v = sigma_v.max(1e-6);
1606 let t_v = 1.0 - sigma_v;
1607 let t_a = 1.0 - time_shift_sigma(sigma_v, self.shift_video, self.shift_audio);
1608
1609 let has_cond = layout
1614 .segments
1615 .iter()
1616 .any(|s| matches!(s.kind, Kind::Cond | Kind::RefImg));
1617 let has_ref_audio = layout.segments.iter().any(|s| s.kind == Kind::RefAudio);
1618 let t_cond_a = t_a.max(self.cond_aug_audio);
1621 let t_cond = t_v.max(self.cond_aug);
1626 let has_ctx = layout
1631 .segments
1632 .iter()
1633 .any(|s| matches!(s.kind, Kind::CtxVideo | Kind::CtxAudio));
1634 let mut ts = vec![t_v, t_a];
1635 if has_ctx {
1636 ts.push(0.0);
1637 }
1638 if has_cond {
1639 ts.push(t_cond);
1640 }
1641 if has_ref_audio {
1642 ts.push(t_cond_a);
1643 }
1644 ts.sort_by(|a, b| a.partial_cmp(b).unwrap());
1645 ts.dedup();
1646 let row_of = |t: f64| ts.iter().position(|&x| x == t).unwrap();
1647 let (row_v, row_a) = (row_of(t_v), row_of(t_a));
1648 let row_c = if has_cond { row_of(t_cond) } else { 0 };
1649 let row_ctx = if has_ctx { row_of(0.0) } else { 0 };
1650 let row_ca = if has_ref_audio { row_of(t_cond_a) } else { 0 };
1651
1652 let mut rows = vec![0u32; layout.seq_len];
1656 for s in &layout.segments {
1657 match s.kind {
1658 Kind::Text => {
1659 for (i, v) in rows[s.start..s.stop].iter_mut().enumerate() {
1660 let tag = *layout.text_tags.get(i).unwrap_or(&(TAG_TEXT as u8));
1661 *v = (row_v * MODALITIES + tag as usize) as u32;
1662 }
1663 }
1664 Kind::Cond | Kind::RefImg => {
1668 for v in rows[s.start..s.stop].iter_mut() {
1669 *v = (row_c * MODALITIES + TAG_VIDEO) as u32;
1670 }
1671 }
1672 Kind::RefAudio => {
1673 for v in rows[s.start..s.stop].iter_mut() {
1674 *v = (row_ca * MODALITIES + TAG_AUDIO) as u32;
1675 }
1676 }
1677 Kind::Video => {
1678 for v in rows[s.start..s.stop].iter_mut() {
1679 *v = (row_v * MODALITIES + TAG_VIDEO) as u32;
1680 }
1681 }
1682 Kind::Audio => {
1683 for v in rows[s.start..s.stop].iter_mut() {
1684 *v = (row_a * MODALITIES + TAG_AUDIO) as u32;
1685 }
1686 }
1687 Kind::CtxVideo => {
1688 for v in rows[s.start..s.stop].iter_mut() {
1689 *v = (row_ctx * MODALITIES + TAG_VIDEO) as u32;
1690 }
1691 }
1692 Kind::CtxAudio => {
1693 for v in rows[s.start..s.stop].iter_mut() {
1694 *v = (row_ctx * MODALITIES + TAG_AUDIO) as u32;
1695 }
1696 }
1697 }
1698 }
1699
1700 let v_rows = patchify_video(
1702 video,
1703 self.latents_dim,
1704 layout.latent_t,
1705 layout.lat_h,
1706 layout.lat_w,
1707 );
1708 let v_n = v_rows.len() / (self.latents_dim * 4);
1709 let a_rows = pack_audio(audio, self.audio_dim, layout.audio_t);
1710 let vd = self.latents_dim * 4;
1713 let cond_rows: Vec<Vec<f32>> = cond
1714 .iter()
1715 .enumerate()
1716 .map(|(i, z)| {
1717 let mut r = patchify_video(z, self.latents_dim, 1, layout.lat_h, layout.lat_w);
1718 if self.cond_aug < 1.0 {
1719 let noise = crate::videogen::gauss_pub(r.len(), 0x5EED ^ i as u64);
1724 let a = self.cond_aug as f32;
1725 for (v, n) in r.iter_mut().zip(&noise) {
1726 *v = a * *v + (1.0 - a) * n;
1727 }
1728 }
1729 r
1730 })
1731 .collect();
1732
1733 let mut h = vec![0f32; layout.seq_len * hs];
1734 let mut ci = 0usize;
1735 for s in layout.segments.iter().filter(|s| s.kind == Kind::Cond) {
1736 let n = s.stop - s.start;
1737 let r = cond_rows.get(ci).unwrap_or_else(|| {
1738 panic!(
1739 "layout has {} cond segments, {} latents given",
1740 ci + 1,
1741 cond_rows.len()
1742 )
1743 });
1744 self.video_patch
1745 .matmat(r, n, &mut h[s.start * hs..s.stop * hs], pool);
1746 for row in h[s.start * hs..s.stop * hs].chunks_exact_mut(hs) {
1747 for (v, &b) in row.iter_mut().zip(&self.video_patch_b) {
1748 *v += b;
1749 }
1750 }
1751 ci += 1;
1752 }
1753 let (vblk_start, vblk_stop) = layout.video_block();
1757 let (ablk_start, ablk_stop) = layout.audio_block();
1758 let vseg = layout.segment(Kind::Video);
1759 let aseg = layout.segment(Kind::Audio);
1760 let tseg = layout.segment(Kind::Text);
1761 h[tseg.start * hs..tseg.stop * hs].copy_from_slice(&text[..(tseg.stop - tseg.start) * hs]);
1762 self.video_patch
1763 .matmat(&v_rows, v_n, &mut h[vblk_start * hs..vblk_stop * hs], pool);
1764 self.audio_patch.matmat(
1765 &a_rows,
1766 ablk_stop - ablk_start,
1767 &mut h[ablk_start * hs..ablk_stop * hs],
1768 pool,
1769 );
1770 for row in h[vblk_start * hs..vblk_stop * hs].chunks_exact_mut(hs) {
1771 for (v, &b) in row.iter_mut().zip(&self.video_patch_b) {
1772 *v += b;
1773 }
1774 }
1775 for row in h[ablk_start * hs..ablk_stop * hs].chunks_exact_mut(hs) {
1776 for (v, &b) in row.iter_mut().zip(&self.audio_patch_b) {
1777 *v += b;
1778 }
1779 }
1780
1781 for (i, blk) in self.blocks.iter().enumerate() {
1783 let mods = blk.adaln.as_ref().unwrap().eval(&ts, pool);
1784 self.block_forward(blk, &mut h, layout.seq_len, Some(&mods), &layout.pos, &rows);
1785 if let Ok(w) = std::env::var("CMF_MMH3_WATCHROW") {
1788 if let Ok(r) = w.parse::<usize>() {
1789 let row = &h[r * hs..(r + 1) * hs];
1790 let amax = row
1791 .iter()
1792 .filter(|v| v.is_finite())
1793 .fold(0f32, |a, v| a.max(v.abs()));
1794 let bad = row.iter().filter(|v| !v.is_finite()).count();
1795 if bad > 0 || i == 0 || amax > 1e3 {
1796 eprintln!(" watch blk {i}: row {r} absmax {amax:.3e} nonfinite {bad}");
1797 }
1798 }
1799 }
1800 if std::env::var_os("CMF_DIT_PROGRESS").is_some() {
1801 eprint!("\r block {}/{}", i + 1, self.blocks.len());
1802 }
1803 }
1804
1805 if std::env::var("CMF_MMH3_NANPROBE").is_ok() {
1807 let bad: Vec<usize> = (0..h.len() / hs)
1808 .filter(|r| h[r * hs..(r + 1) * hs].iter().any(|v| !v.is_finite()))
1809 .collect();
1810 if !bad.is_empty() {
1811 eprintln!(
1812 " nanprobe rows: {} of {} bad, first 8 {:?}, video seg {}..{}, audio seg {}..{}",
1813 bad.len(),
1814 h.len() / hs,
1815 &bad[..bad.len().min(8)],
1816 vseg.start,
1817 vseg.stop,
1818 aseg.start,
1819 aseg.stop
1820 );
1821 }
1822 }
1823 let fm = self.final_adaln.eval(&ts, pool);
1824 let mut video_out = vec![0f32; (vseg.stop - vseg.start) * vd];
1825 let mut audio_out = vec![0f32; (aseg.stop - aseg.start) * self.audio_dim];
1826 for (seg, row, w, b, dst, dim) in [
1827 (
1828 vseg,
1829 row_v,
1830 &self.video_out,
1831 &self.video_out_b,
1832 &mut video_out,
1833 vd,
1834 ),
1835 (
1836 aseg,
1837 row_a,
1838 &self.audio_out,
1839 &self.audio_out_b,
1840 &mut audio_out,
1841 self.audio_dim,
1842 ),
1843 ] {
1844 let n = seg.stop - seg.start;
1845 let mut hn = vec![0f32; n * hs];
1846 for (o, src) in hn
1847 .chunks_exact_mut(hs)
1848 .zip(h[seg.start * hs..seg.stop * hs].chunks_exact(hs))
1849 {
1850 rms_norm_into(src, &self.final_norm, self.final_eps, o);
1851 }
1852 let shift = &fm[row * 2 * hs..row * 2 * hs + hs];
1855 let scale = &fm[row * 2 * hs + hs..(row + 1) * 2 * hs];
1856 for r in hn.chunks_exact_mut(hs) {
1857 for ((v, &sc), &sh) in r.iter_mut().zip(scale).zip(shift) {
1858 *v = *v * (1.0 + sc) + sh;
1859 }
1860 }
1861 let probe = std::env::var("CMF_MMH3_NANPROBE").is_ok();
1866 if probe {
1867 let bad_in = hn.iter().filter(|v| !v.is_finite()).count();
1868 eprintln!(
1869 " nanprobe dim={dim} n={n} in_bad={bad_in} in_absmax={:.4}",
1870 hn.iter()
1871 .filter(|v| v.is_finite())
1872 .fold(0f32, |a, v| a.max(v.abs()))
1873 );
1874 }
1875 w.matmat(&hn, n, dst, pool);
1876 if probe {
1877 let bad_out = dst.iter().filter(|v| !v.is_finite()).count();
1878 eprintln!(
1879 " nanprobe dim={dim} out_bad={bad_out}/{} out_absmax={:.4}",
1880 dst.len(),
1881 dst.iter()
1882 .filter(|v| v.is_finite())
1883 .fold(0f32, |a, v| a.max(v.abs()))
1884 );
1885 }
1886 for r in dst.chunks_exact_mut(dim) {
1887 for (v, &bv) in r.iter_mut().zip(b.iter()) {
1888 *v += bv;
1889 }
1890 }
1891 }
1892
1893 let out_t = (vseg.stop - vseg.start) / layout.frame_rows;
1902 let out_at = (aseg.stop - aseg.start) / 2;
1903 let video = unpatchify_video(
1904 &video_out,
1905 self.latents_dim,
1906 out_t,
1907 layout.lat_h,
1908 layout.lat_w,
1909 );
1910 let audio = unpack_audio(&audio_out, self.audio_dim, out_at);
1911 (
1912 video.iter().map(|&v| -v).collect(),
1913 audio.iter().map(|&v| -v).collect(),
1914 )
1915 }
1916}
1917
1918fn pool_rows(pool: Option<&Pool>, n: usize, f: &(dyn Fn(usize, usize) + Sync)) {
1922 match pool {
1923 Some(p) => p.run_rows(n, f),
1924 None => f(0, n),
1925 }
1926}
1927
1928pub fn patchify_video(x: &[f32], c: usize, t: usize, h: usize, w: usize) -> Vec<f32> {
1933 let (ph, pw) = (h / 2, w / 2);
1934 let mut out = vec![0f32; t * ph * pw * c * 4];
1935 let mut i = 0;
1936 for ti in 0..t {
1937 for hi in 0..ph {
1938 for wi in 0..pw {
1939 for ci in 0..c {
1940 for p in 0..2 {
1941 for q in 0..2 {
1942 out[i] = x[((ci * t + ti) * h + hi * 2 + p) * w + wi * 2 + q];
1943 i += 1;
1944 }
1945 }
1946 }
1947 }
1948 }
1949 }
1950 out
1951}
1952
1953pub fn unpatchify_video(rows: &[f32], c: usize, t: usize, h: usize, w: usize) -> Vec<f32> {
1955 let (ph, pw) = (h / 2, w / 2);
1956 let mut out = vec![0f32; c * t * h * w];
1957 let mut i = 0;
1958 for ti in 0..t {
1959 for hi in 0..ph {
1960 for wi in 0..pw {
1961 for ci in 0..c {
1962 for p in 0..2 {
1963 for q in 0..2 {
1964 out[((ci * t + ti) * h + hi * 2 + p) * w + wi * 2 + q] = rows[i];
1965 i += 1;
1966 }
1967 }
1968 }
1969 }
1970 }
1971 }
1972 out
1973}
1974
1975pub fn pack_audio(x: &[f32], c: usize, t: usize) -> Vec<f32> {
1978 let mut out = vec![0f32; 2 * t * c];
1979 for ch in 0..2 {
1980 for ti in 0..t {
1981 for ci in 0..c {
1982 out[(ch * t + ti) * c + ci] = x[(ci * 2 + ch) * t + ti];
1983 }
1984 }
1985 }
1986 out
1987}
1988
1989pub fn unpack_audio(rows: &[f32], c: usize, t: usize) -> Vec<f32> {
1991 let mut out = vec![0f32; c * 2 * t];
1992 for ch in 0..2 {
1993 for ti in 0..t {
1994 for ci in 0..c {
1995 out[(ci * 2 + ch) * t + ti] = rows[(ch * t + ti) * c + ci];
1996 }
1997 }
1998 }
1999 out
2000}
2001
2002struct SendPtr(*mut f32);
2005unsafe impl Send for SendPtr {}
2006unsafe impl Sync for SendPtr {}
2007impl SendPtr {
2008 #[allow(clippy::mut_from_ref)]
2010 unsafe fn row(&self, off: usize, len: usize) -> &mut [f32] {
2011 unsafe { std::slice::from_raw_parts_mut(self.0.add(off), len) }
2012 }
2013}
2014
2015fn softmax_inplace(row: &mut [f32]) {
2016 let mx = row.iter().cloned().fold(f32::MIN, f32::max);
2017 let mut den = 0f32;
2018 for r in row.iter_mut() {
2019 *r = (*r - mx).exp();
2020 den += *r;
2021 }
2022 if den > 0.0 {
2023 let inv = 1.0 / den;
2024 for r in row.iter_mut() {
2025 *r *= inv;
2026 }
2027 }
2028}
2029
2030#[cfg(test)]
2031mod tests {
2032 #[test]
2038 fn streaming_layout_degenerates_to_the_bidirectional_one() {
2039 let (lat_h, lat_w, latent_t, audio_t, text_len) = (16, 24, 7, 12, 5);
2040 let base = Layout::t2va(text_len, latent_t, lat_h, lat_w, audio_t);
2041 let cur_v: Vec<usize> = (0..latent_t).collect();
2042 let cur_a: Vec<usize> = (0..audio_t).collect();
2043 let stream = Layout::streaming(text_len, &[], lat_h, lat_w, &[], &cur_v, &[], &cur_a);
2044 assert_eq!(stream.seq_len, base.seq_len, "row count");
2045 assert_eq!(stream.latent_t, base.latent_t);
2046 assert_eq!(stream.audio_t, base.audio_t);
2047 for (i, (a, b)) in stream.pos.iter().zip(&base.pos).enumerate() {
2048 for axis in 0..3 {
2049 assert!(
2050 (a[axis] - b[axis]).abs() < 1e-9,
2051 "row {i} axis {axis}: {a:?} vs {b:?}"
2052 );
2053 }
2054 }
2055 let seg = |l: &Layout, k: Kind| {
2056 l.segments
2057 .iter()
2058 .find(|s| s.kind == k)
2059 .map(|s| (s.start, s.stop))
2060 };
2061 assert_eq!(seg(&stream, Kind::Video), seg(&base, Kind::Video));
2062 assert_eq!(seg(&stream, Kind::Audio), seg(&base, Kind::Audio));
2063 }
2064
2065 #[test]
2068 fn streaming_positions_are_absolute() {
2069 let (lat_h, lat_w) = (16, 24);
2070 let whole = Layout::streaming(3, &[], lat_h, lat_w, &[], &(0..9).collect::<Vec<_>>(), &[], &[]);
2071 let tail = Layout::streaming(3, &[], lat_h, lat_w, &[], &(6..9).collect::<Vec<_>>(), &[], &[]);
2072 let rows = whole.frame_rows;
2073 let (ws, _) = whole.video_block();
2074 let (ts_, _) = tail.video_block();
2075 for k in 0..3 {
2076 let a = whole.pos[ws + (6 + k) * rows];
2077 let b = tail.pos[ts_ + k * rows];
2078 assert!((a[0] - b[0]).abs() < 1e-9, "frame {k}: {a:?} vs {b:?}");
2079 }
2080 }
2081
2082 use super::*;
2083
2084 #[test]
2085 fn schedule_remap_is_an_involution() {
2086 for &s in &[1e-3, 0.25, 0.5, 0.8, 0.972_973, 1.0] {
2087 let a = time_shift_sigma(s, 12.0, 3.0);
2088 let back = time_shift_sigma(a, 3.0, 12.0);
2089 assert!((back - s).abs() < 1e-9, "{s} -> {a} -> {back}");
2090 }
2091 }
2092
2093 #[test]
2094 fn slope_matches_a_finite_difference() {
2095 let h = 1e-6;
2096 for &s in &[0.2, 0.5, 0.9] {
2097 let num = (time_shift_sigma(s + h, 12.0, 3.0) - time_shift_sigma(s - h, 12.0, 3.0))
2098 / (2.0 * h);
2099 let got = time_shift_slope(s, 12.0, 3.0);
2100 assert!((num - got).abs() < 1e-5, "{s}: {num} vs {got}");
2101 }
2102 }
2103
2104 #[test]
2105 fn patchify_round_trips() {
2106 let (c, t, h, w) = (3usize, 2usize, 4usize, 6usize);
2107 let x: Vec<f32> = (0..c * t * h * w).map(|i| i as f32).collect();
2108 let rows = patchify_video(&x, c, t, h, w);
2109 assert_eq!(rows.len(), t * (h / 2) * (w / 2) * c * 4);
2110 assert_eq!(unpatchify_video(&rows, c, t, h, w), x);
2111 }
2112
2113 #[test]
2114 fn audio_pack_round_trips() {
2115 let (c, t) = (5usize, 7usize);
2116 let x: Vec<f32> = (0..c * 2 * t).map(|i| i as f32).collect();
2117 let rows = pack_audio(&x, c, t);
2118 assert_eq!(unpack_audio(&rows, c, t), x);
2119 }
2120
2121 #[test]
2122 fn keyframes_sit_between_the_text_and_the_audio() {
2123 let (tl, lt, lh, lw, at) = (8usize, 3usize, 8usize, 12usize, 5usize);
2124 let base = Layout::t2va(tl, lt, lh, lw, at);
2125 let l = Layout::fl2va(tl, lt, lh, lw, at, &[(0, 39), (38, 39)], &[]);
2127 assert_eq!(l.cond_rows(), 2 * l.frame_rows);
2128 assert_eq!(l.seq_len, base.seq_len + 2 * l.frame_rows);
2129 let kinds: Vec<_> = l.segments.iter().map(|s| s.kind).collect();
2130 assert_eq!(
2131 kinds,
2132 vec![Kind::Text, Kind::Cond, Kind::Cond, Kind::Audio, Kind::Video]
2133 );
2134 let (a0, v0) = (l.segment(Kind::Audio), l.segment(Kind::Video));
2137 let (ba, bv) = (base.segment(Kind::Audio), base.segment(Kind::Video));
2138 assert_eq!(l.pos[a0.start][0], base.pos[ba.start][0]);
2139 assert_eq!(l.pos[v0.start][0], base.pos[bv.start][0]);
2140 let c: Vec<_> = l.segments.iter().filter(|s| s.kind == Kind::Cond).collect();
2143 assert_eq!(l.pos[c[0].start][0], tl as f64);
2144 let spans: f64 = (0..lt)
2145 .map(|k| FRAME_RESCALE * FRAME_PER_TOKEN[k % 5])
2146 .sum();
2147 assert!((l.pos[c[1].start][0] - (tl as f64 + spans - FRAME_RESCALE)).abs() < 1e-12);
2148 assert_eq!(l.pos[c[0].start][1], l.pos[v0.start][1]);
2150 assert_eq!(l.pos[c[0].start][2], l.pos[v0.start][2]);
2151 }
2152
2153 #[test]
2154 fn layout_places_the_target_streams_last() {
2155 let l = Layout::t2va(8, 3, 8, 12, 5);
2156 assert_eq!(l.frame_rows, 4 * 6);
2157 assert_eq!(l.seq_len, 8 + 2 * 5 + 3 * 24);
2158 assert_eq!(l.segments[0].kind, Kind::Text);
2159 assert_eq!(l.segments[1].kind, Kind::Audio);
2160 assert_eq!(l.segments[2].kind, Kind::Video);
2161 let v = l.segment(Kind::Video);
2163 let t0 = l.pos[v.start][0];
2164 let t1 = l.pos[v.start + l.frame_rows][0];
2165 assert!((t1 - t0 - FRAME_RESCALE).abs() < 1e-12);
2166 }
2167}