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 pub fn image(
643 text_len: usize,
644 lat_h: usize,
645 lat_w: usize,
646 refs: &[(usize, usize)],
647 text_tags: &[u8],
648 ) -> Self {
649 let mut pos: Vec<[f64; 3]> = Vec::new();
650 let mut segments = vec![Segment {
651 start: 0,
652 stop: text_len,
653 kind: Kind::Text,
654 }];
655 for i in 0..text_len {
656 pos.push([i as f64, 0.0, 0.0]);
657 }
658 let mut cursor = text_len as f64;
659 let frame = |pos: &mut Vec<[f64; 3]>, h: usize, w: usize, t: f64| -> usize {
660 let area = ((h * w) as f64).sqrt();
661 let (ha, wa) = (
662 axis_from_sqrt_area(h, 2, area),
663 axis_from_sqrt_area(w, 2, area),
664 );
665 for &y in &ha {
666 for &x in &wa {
667 pos.push([t, y, x]);
668 }
669 }
670 ha.len() * wa.len()
671 };
672 for &(rh, rw) in refs {
673 let start = pos.len();
674 let n = frame(&mut pos, rh, rw, cursor);
675 segments.push(Segment {
676 start,
677 stop: start + n,
678 kind: Kind::RefImg,
679 });
680 cursor += 1.0;
681 }
682 let start = pos.len();
683 let frame_rows = frame(&mut pos, lat_h, lat_w, cursor);
684 segments.push(Segment {
685 start,
686 stop: start + frame_rows,
687 kind: Kind::Video,
688 });
689 Self {
690 seq_len: pos.len(),
691 segments,
692 pos,
693 text_len,
694 audio_t: 0,
695 latent_t: 1,
696 lat_h,
697 lat_w,
698 frame_rows,
699 text_tags: if text_tags.is_empty() {
700 vec![TAG_TEXT as u8; text_len]
701 } else {
702 text_tags.to_vec()
703 },
704 }
705 }
706}
707
708struct Adaln {
711 w: Proj,
713 b: Vec<f32>,
714 table: Vec<f32>,
716 rank: usize,
717 out: usize,
718}
719
720impl Adaln {
721 fn load(model: &Arc<CmfModel>, prefix: &str) -> Result<Self, String> {
722 let w = Proj::from_model(model, &format!("{prefix}.weight"))?;
723 let table = crate::dit::cmf_f32(model, &format!("{prefix}.table"))?;
724 let b = crate::dit::cmf_f32(model, &format!("{prefix}.bias"))?;
725 let out = w.rows();
726 let rank = table.len() / CURVE_GRID;
727 Ok(Self {
728 w,
729 b,
730 table,
731 rank,
732 out,
733 })
734 }
735
736 fn eval(&self, ts: &[f64], pool: Option<&Pool>) -> Vec<f32> {
740 let g = CURVE_GRID;
741 let mut coords = vec![0f32; ts.len() * self.rank];
742 for (i, &t) in ts.iter().enumerate() {
743 let p = (t.clamp(0.0, 1.0) * (g - 1) as f64) as f32;
747 let i0 = (p.floor() as usize).min(g - 2);
748 let f = p - i0 as f32;
749 for k in 0..self.rank {
750 let (a, b) = (
751 self.table[i0 * self.rank + k],
752 self.table[(i0 + 1) * self.rank + k],
753 );
754 coords[i * self.rank + k] = a + (b - a) * f;
755 }
756 }
757 let mut out = vec![0f32; ts.len() * self.out];
758 self.w.matmat(&coords, ts.len(), &mut out, pool);
759 for row in out.chunks_exact_mut(self.out) {
760 for (v, &bv) in row.iter_mut().zip(&self.b) {
761 *v += bv;
762 }
763 }
764 out
765 }
766}
767
768struct Block {
769 norm1: Vec<f32>,
770 norm2: Vec<f32>,
771 qkv: Proj, out: Proj, q_norm: Vec<f32>,
774 k_norm: Vec<f32>,
775 fc1: Proj, fc2: Proj, adaln: Option<Adaln>,
778 lora: BlockLora,
783}
784
785#[derive(Default)]
790struct BlockLora {
791 qkv: Option<LoraBranch>,
792 out: Option<LoraBranch>,
793 fc1: Option<LoraBranch>,
794 fc2: Option<LoraBranch>,
795}
796
797fn proj_with_lora(
810 p: &Proj,
811 br: &Option<LoraBranch>,
812 x: &[f32],
813 n: usize,
814 out: &mut [f32],
815 pool: Option<&Pool>,
816) {
817 #[cfg(target_os = "macos")]
818 if let Some(l) = br.as_ref().filter(|l| l.live()) {
819 if let Some((model, idx)) = p.q4tp_mapped() {
820 let (rows, cols) = (p.rows(), p.cols());
821 if n >= 32
822 && cols % 32 == 0
823 && crate::gpu::enabled_here()
824 && !crate::gpu::mm_killed()
825 && (!crate::ltxlora::wants_measurement() || l.resonance() != 0.0)
826 && crate::gpu_metal::q4tp_matmat_lora(model, idx, x, n, rows, cols, out, &l.side())
827 {
828 return;
829 }
830 }
831 }
832 p.matmat(x, n, out, pool);
833 if let Some(l) = br {
834 l.add(x, n, out, pool);
835 }
836}
837
838impl BlockLora {
839 fn live(br: &Option<LoraBranch>) -> bool {
842 br.as_ref().is_some_and(|l| l.live())
843 }
844 fn ffn_any(&self) -> bool {
845 Self::live(&self.fc1) || Self::live(&self.fc2)
846 }
847}
848
849pub(crate) const CURVE_GRID: usize = 1025;
850
851pub struct MiniMaxH3 {
852 video_patch: Proj,
853 video_patch_b: Vec<f32>,
854 audio_patch: Option<Proj>,
856 audio_patch_b: Vec<f32>,
857 condition: Proj,
858 condition_b: Vec<f32>,
859 refiner: Vec<Block>,
860 refiner_norm: Vec<f32>,
861 blocks: Vec<Block>,
862 final_norm: Vec<f32>,
863 final_adaln: Adaln,
864 video_out: Proj,
865 video_out_b: Vec<f32>,
866 audio_out: Option<Proj>,
867 audio_out_b: Vec<f32>,
868 pdd_w: Vec<f32>,
872 pub pdd_steps: usize,
875 pub pdd_sigmas: Vec<f64>,
876 pub image: bool,
878 model: Arc<CmfModel>,
880 inv_freq: Vec<f32>,
881 pool: Option<Arc<Pool>>,
882 pub hidden: usize,
883 pub heads: usize,
884 pub head_dim: usize,
885 pub ffn: usize,
886 pub latents_dim: usize,
887 pub audio_dim: usize,
888 pub text_dim: usize,
889 pub shift_video: f64,
890 pub shift_audio: f64,
891 eps: f64,
892 qk_eps: f64,
893 final_eps: f64,
894 pub cond_aug: f64,
898 pub cond_aug_audio: f64,
901 lora_bound: std::collections::BTreeSet<String>,
903}
904
905fn rms_norm_into(x: &[f32], w: &[f32], eps: f64, dst: &mut [f32]) {
906 let ss = x.iter().map(|&v| (v as f64) * (v as f64)).sum::<f64>() / x.len() as f64;
907 let inv = 1.0 / (ss + eps).sqrt();
908 for ((d, &v), &g) in dst.iter_mut().zip(x).zip(w) {
909 *d = (v as f64 * inv) as f32 * g;
910 }
911}
912
913fn silu(v: f32) -> f32 {
914 v / (1.0 + (-v).exp())
915}
916
917impl MiniMaxH3 {
918 pub fn from_cmf(model: &Arc<CmfModel>) -> Result<Self, String> {
919 Self::from_cmf_lora(model, None)
920 }
921
922 pub fn from_cmf_lora(model: &Arc<CmfModel>, bank: Option<&LoraBank>) -> Result<Self, String> {
929 let cfg: serde_json::Value = serde_json::from_slice(
930 model
931 .tensor_bytes("dit.config_json")
932 .map_err(|e| e.to_string())?,
933 )
934 .map_err(|e| format!("dit.config_json: {e}"))?;
935 let u = |k: &str| cfg[k].as_u64().unwrap_or(0) as usize;
936 let f = |k: &str, d: f64| cfg[k].as_f64().unwrap_or(d);
937 let n_blocks = u("num_layers");
938 let n_refiner = u("token_refiner_num_layers");
939 let f32v = |n: &str| crate::dit::cmf_f32(model, n);
940 let image = cfg["image"].as_bool() == Some(true);
941 let pdd_steps = u("pdd_steps").max(1);
942 let pdd_sigmas: Vec<f64> = cfg["pdd_sigmas"]
943 .as_array()
944 .map(|a| a.iter().filter_map(|v| v.as_f64()).collect())
945 .unwrap_or_default();
946 if pdd_steps > 1 && pdd_sigmas.len() != pdd_steps + 1 {
947 return Err(format!(
948 "dit.config_json: {pdd_steps} PDD heads need {} sigmas, got {}",
949 pdd_steps + 1,
950 pdd_sigmas.len()
951 ));
952 }
953
954 let bound: std::cell::RefCell<std::collections::BTreeSet<String>> = Default::default();
956 let load_block = |prefix: &str, with_adaln: bool| -> Result<Block, String> {
957 let qkv = Proj::from_model(model, &format!("dit.{prefix}.attn.qkv_proj.weight"))?;
958 let out = Proj::from_model(model, &format!("dit.{prefix}.attn.out_proj.weight"))?;
959 let fc1 = Proj::from_model(model, &format!("dit.{prefix}.mlp.fc1.weight"))?;
960 let fc2 = Proj::from_model(model, &format!("dit.{prefix}.mlp.fc2.weight"))?;
961 let lora = match bank {
966 None => BlockLora::default(),
967 Some(k) => {
968 let mut take = |suffix: &str, p: &Proj| -> Result<Option<LoraBranch>, String> {
969 let key = format!("{prefix}.{suffix}");
970 let br = k.branch_for(&format!("dit.{key}"), p.rows(), p.cols())?;
971 if br.is_some() {
972 bound.borrow_mut().insert(key);
973 }
974 Ok(br)
975 };
976 BlockLora {
977 qkv: take("attn.qkv_proj", &qkv)?,
978 out: take("attn.out_proj", &out)?,
979 fc1: take("mlp.fc1", &fc1)?,
980 fc2: take("mlp.fc2", &fc2)?,
981 }
982 }
983 };
984 Ok(Block {
985 norm1: f32v(&format!("dit.{prefix}.norm1.weight"))?,
986 norm2: f32v(&format!("dit.{prefix}.norm2.weight"))?,
987 qkv,
988 out,
989 q_norm: f32v(&format!("dit.{prefix}.attn.q_norm.weight"))?,
990 k_norm: f32v(&format!("dit.{prefix}.attn.k_norm.weight"))?,
991 fc1,
992 fc2,
993 adaln: if with_adaln {
994 Some(Adaln::load(model, &format!("dit.{prefix}.adaln"))?)
995 } else {
996 None
997 },
998 lora,
999 })
1000 };
1001 let blocks = (0..n_blocks)
1002 .map(|i| load_block(&format!("blocks.{i}"), true))
1003 .collect::<Result<Vec<_>, _>>()?;
1004 let refiner = (0..n_refiner)
1005 .map(|i| load_block(&format!("token_refiner.blocks.{i}"), false))
1006 .collect::<Result<Vec<_>, _>>()?;
1007
1008 Ok(Self {
1009 video_patch: Proj::from_model(model, "dit.video_patch_proj.weight")?,
1010 video_patch_b: f32v("dit.video_patch_proj.bias")?,
1011 audio_patch: if image {
1012 None
1013 } else {
1014 Some(Proj::from_model(model, "dit.audio_patch_proj.weight")?)
1015 },
1016 audio_patch_b: if image {
1017 Vec::new()
1018 } else {
1019 f32v("dit.audio_patch_proj.bias")?
1020 },
1021 condition: Proj::from_model(model, "dit.condition_proj.weight")?,
1022 condition_b: f32v("dit.condition_proj.bias")?,
1023 refiner,
1024 refiner_norm: f32v("dit.token_refiner.final_norm.weight")?,
1025 blocks,
1026 final_norm: f32v("dit.final_layer.norm.weight")?,
1027 final_adaln: Adaln::load(model, "dit.final_layer.adaln")?,
1028 video_out: Proj::from_model(model, "dit.final_layer.video_out.weight")?,
1029 video_out_b: f32v("dit.final_layer.video_out.bias")?,
1030 audio_out: if image {
1031 None
1032 } else {
1033 Some(Proj::from_model(model, "dit.final_layer.audio_out.weight")?)
1034 },
1035 audio_out_b: if image {
1036 Vec::new()
1037 } else {
1038 f32v("dit.final_layer.audio_out.bias")?
1039 },
1040 pdd_w: if pdd_steps > 1 {
1041 f32v("dit.final_layer.video_out.weight")?
1042 } else {
1043 Vec::new()
1044 },
1045 pdd_steps,
1046 pdd_sigmas,
1047 image,
1048 model: model.clone(),
1049 inv_freq: f32v("dit.rope_inv_freq")?,
1050 pool: Pool::from_env(),
1051 hidden: u("hidden_size"),
1052 heads: u("num_attention_heads"),
1053 head_dim: u("attention_head_dim"),
1054 ffn: u("ffn_hidden_size"),
1055 latents_dim: u("latents_dim"),
1056 audio_dim: u("audio_latents_dim"),
1057 text_dim: u("text_dim"),
1058 shift_video: f("sigma_shift_video", 12.0),
1059 shift_audio: f("sigma_shift_audio", 3.0),
1060 eps: f("norm_eps", 1e-5),
1061 qk_eps: f("qk_norm_eps", 1e-5),
1062 final_eps: f("final_norm_eps", 1e-5),
1063 cond_aug: VISUAL_COND_TIMESTEP,
1064 cond_aug_audio: AUDIO_COND_TIMESTEP,
1065 lora_bound: bound.into_inner(),
1066 })
1067 }
1068
1069 pub fn lora_bound(&self) -> usize {
1071 self.lora_bound.len()
1072 }
1073
1074 pub fn lora_binds(&self, key: &str) -> bool {
1076 self.lora_bound.contains(key)
1077 }
1078
1079 pub fn lora_report(&self) -> Option<String> {
1084 if !crate::ltxlora::probe_report_on() {
1085 return None;
1086 }
1087 let mut rows: Vec<(f32, bool, String)> = Vec::new();
1088 let mut walk = |blocks: &[Block], pre: &str| {
1089 for (i, b) in blocks.iter().enumerate() {
1090 for (name, br) in [
1091 ("attn.qkv_proj", &b.lora.qkv),
1092 ("attn.out_proj", &b.lora.out),
1093 ("mlp.fc1", &b.lora.fc1),
1094 ("mlp.fc2", &b.lora.fc2),
1095 ] {
1096 if let Some(l) = br {
1097 rows.push((l.resonance(), l.live(), format!("{pre}{i}.{name}")));
1098 }
1099 }
1100 }
1101 };
1102 walk(&self.blocks, "blocks.");
1103 walk(&self.refiner, "token_refiner.blocks.");
1104 if rows.is_empty() {
1105 return None;
1106 }
1107 rows.sort_by(|a, b| b.0.total_cmp(&a.0));
1108 let live = rows.iter().filter(|r| r.1).count();
1109 let mut s = format!(
1110 "lora branches by contribution ‖sΔY‖/‖Y‖ ({} of {} live):\n",
1111 live,
1112 rows.len()
1113 );
1114 for (r, on, name) in &rows {
1115 s.push_str(&format!(
1116 " {:>8.4} {} {name}\n",
1117 r,
1118 if *on { "on " } else { "off" }
1119 ));
1120 }
1121 Some(s)
1122 }
1123
1124 pub fn refine_text(&self, states: &[f32], n: usize) -> Vec<f32> {
1128 let pool = self.pool.as_deref();
1129 let mut h = vec![0f32; n * self.hidden];
1130 self.condition.matmat(states, n, &mut h, pool);
1131 for row in h.chunks_exact_mut(self.hidden) {
1132 for (v, &b) in row.iter_mut().zip(&self.condition_b) {
1133 *v += b;
1134 }
1135 }
1136 self.run_blocks(&self.refiner, &mut h, n, &[], &[], &[]);
1137 let mut out = vec![0f32; n * self.hidden];
1138 for (o, x) in out
1139 .chunks_exact_mut(self.hidden)
1140 .zip(h.chunks_exact(self.hidden))
1141 {
1142 rms_norm_into(x, &self.refiner_norm, self.final_eps, o);
1143 }
1144 out
1145 }
1146
1147 fn rope_angles(&self, pos: &[[f64; 3]]) -> Vec<f32> {
1151 let k = self.inv_freq.len();
1152 let mut out = Vec::with_capacity(pos.len() * 3 * k);
1153 for p in pos {
1154 for axis in 0..3 {
1155 for j in 0..k {
1156 out.push((p[axis] * self.inv_freq[j] as f64) as f32);
1157 }
1158 }
1159 }
1160 out
1161 }
1162
1163 #[allow(clippy::too_many_arguments)]
1174 fn norm_rope_w(
1175 &self,
1176 v: &mut [f32],
1177 n: usize,
1178 heads: usize,
1179 w: &[f32],
1180 angles: &[f32],
1181 stride: usize,
1182 off: usize,
1183 ) {
1184 let hd = self.head_dim;
1185 let pairs = if angles.is_empty() {
1186 0
1187 } else {
1188 angles.len() / n
1189 };
1190 let pool = self.pool.as_deref();
1191 let ptr = SendPtr(v.as_mut_ptr());
1192 let work = |lo: usize, hi: usize| {
1193 for p in lo..hi {
1194 for h in 0..heads {
1195 let x = unsafe { ptr.row(p * stride + off + h * hd, hd) };
1198 let ss = x.iter().map(|&a| (a as f64) * (a as f64)).sum::<f64>() / hd as f64;
1199 let inv = 1.0 / (ss + self.qk_eps).sqrt();
1200 for (d, &g) in x.iter_mut().zip(w) {
1201 *d = (*d as f64 * inv) as f32 * g;
1202 }
1203 for j in 0..pairs {
1204 let a = angles[p * pairs + j];
1205 let (s, c) = a.sin_cos();
1206 let (lo_v, hi_v) = (x[j], x[j + pairs]);
1207 x[j] = lo_v * c - hi_v * s;
1208 x[j + pairs] = lo_v * s + hi_v * c;
1209 }
1210 }
1211 }
1212 };
1213 match pool {
1214 Some(pl) => pl.run_rows(n, &work),
1215 None => work(0, n),
1216 }
1217 }
1218
1219 fn attention(
1225 &self,
1226 qkv: &[f32],
1227 n: usize,
1228 attn: &mut [f32],
1229 nr: Option<(&[f32], &[f32], &[f32], f32)>,
1230 ) {
1231 let t_repack = std::time::Instant::now();
1232 let (nh, hd) = (self.heads, self.head_dim);
1233 let inner = nh * hd;
1234 let scale = 1.0 / (hd as f32).sqrt();
1235 let pool = self.pool.as_deref();
1236 if std::env::var("CMF_MMH3_ATTN").as_deref() != Ok("cpu")
1244 && crate::gpu::enabled_here()
1245 && n >= 256
1246 {
1247 if std::env::var("CMF_MMH3_ATTN").as_deref() != Ok("repack")
1260 && crate::gpu::dit_attention_packed(qkv, nh, n, hd, scale, nr, attn)
1261 {
1262 return;
1268 }
1269 assert!(
1270 nr.is_none(),
1271 "device qk-norm was requested but the device path refused; \
1272 the host loops below expect q/k already normalized"
1273 );
1274 let mut qh = vec![0f32; nh * n * hd];
1275 let mut kh = vec![0f32; nh * n * hd];
1276 let mut vh = vec![0f32; nh * n * hd];
1277 {
1278 let (pq, pk, pv) = (
1279 SendPtr(qh.as_mut_ptr()),
1280 SendPtr(kh.as_mut_ptr()),
1281 SendPtr(vh.as_mut_ptr()),
1282 );
1283 pool_rows(pool, n, &|lo, hi| {
1284 for p in lo..hi {
1285 let base = p * 3 * inner;
1286 for h in 0..nh {
1287 let dst = (h * n + p) * hd;
1288 unsafe {
1291 pq.row(dst, hd)
1292 .copy_from_slice(&qkv[base + h * hd..base + (h + 1) * hd]);
1293 pk.row(dst, hd).copy_from_slice(
1294 &qkv[base + inner + h * hd..base + inner + (h + 1) * hd],
1295 );
1296 pv.row(dst, hd).copy_from_slice(
1297 &qkv[base + 2 * inner + h * hd
1298 ..base + 2 * inner + (h + 1) * hd],
1299 );
1300 }
1301 }
1302 }
1303 });
1304 }
1305 Self::prof(2, t_repack);
1312 if crate::gpu::dit_attention(&qh, &kh, &vh, nh, nh, n, hd, scale, attn) {
1315 return;
1316 }
1317 }
1318 let mut qh = vec![0f32; n * hd];
1319 let mut kh = vec![0f32; n * hd];
1320 let mut vt = vec![0f32; hd * n];
1321 let mut scores = vec![0f32; n * n];
1322 let mut oh = vec![0f32; n * hd];
1323 for h in 0..nh {
1324 for p in 0..n {
1325 let base = p * 3 * inner;
1326 let qs = &qkv[base + h * hd..base + (h + 1) * hd];
1327 for (d, &val) in qs.iter().enumerate() {
1328 qh[p * hd + d] = val * scale;
1329 }
1330 kh[p * hd..(p + 1) * hd]
1331 .copy_from_slice(&qkv[base + inner + h * hd..base + inner + (h + 1) * hd]);
1332 let vs = &qkv[base + 2 * inner + h * hd..base + 2 * inner + (h + 1) * hd];
1333 for (d, &val) in vs.iter().enumerate() {
1334 vt[d * n + p] = val;
1335 }
1336 }
1337 crate::fcd_ops::gemm_nt(&qh, &kh, &mut scores, n, hd, n, pool);
1338 let sp = SendPtr(scores.as_mut_ptr());
1339 let soft = |lo: usize, hi: usize| {
1340 for r in lo..hi {
1341 softmax_inplace(unsafe { sp.row(r * n, n) });
1343 }
1344 };
1345 match pool {
1346 Some(pl) => pl.run_rows(n, &soft),
1347 None => soft(0, n),
1348 }
1349 crate::fcd_ops::gemm_nt(&scores, &vt, &mut oh, n, n, hd, pool);
1350 for p in 0..n {
1351 attn[p * inner + h * hd..p * inner + (h + 1) * hd]
1352 .copy_from_slice(&oh[p * hd..(p + 1) * hd]);
1353 }
1354 }
1355 }
1356
1357 fn prof(slot: usize, t: std::time::Instant) {
1361 if !mmh3_prof_on() {
1362 return;
1363 }
1364 MMH3_PROF[slot].fetch_add(
1365 t.elapsed().as_micros() as u64,
1366 std::sync::atomic::Ordering::Relaxed,
1367 );
1368 }
1369
1370 fn block_forward(
1374 &self,
1375 blk: &Block,
1376 x: &mut [f32],
1377 n: usize,
1378 mods: Option<&[f32]>,
1379 pos: &[[f64; 3]],
1380 rows: &[u32],
1381 ) {
1382 let hs = self.hidden;
1383 let pool = self.pool.as_deref();
1384 let inner = self.heads * self.head_dim;
1385 let angles = if pos.is_empty() {
1386 Vec::new()
1387 } else {
1388 self.rope_angles(pos)
1389 };
1390
1391 let t = std::time::Instant::now();
1392 let mut xn = vec![0f32; n * hs];
1393 self.norm_rows(&mut xn, x, n, hs, &blk.norm1, self.eps);
1394 if let (Some(m), false) = (mods, rows.is_empty()) {
1395 self.modulate(&mut xn, hs, m, rows, 0, 1);
1396 }
1397 Self::prof(0, t);
1398 let t = std::time::Instant::now();
1399 let t_qkv = std::time::Instant::now();
1406 let qk_gpu = std::env::var("CMF_MMH3_QKNORM").as_deref() != Ok("cpu")
1412 && crate::gpu::enabled_here()
1413 && crate::gpu::dit_attention_packed_available()
1414 && n >= 256;
1415 if qk_gpu
1420 && !BlockLora::live(&blk.lora.qkv)
1421 && std::env::var("CMF_MMH3_FUSEQKV").as_deref() != Ok("0")
1422 {
1423 let fuse_out = !BlockLora::live(&blk.lora.out)
1428 && std::env::var("CMF_MMH3_FUSEOUT").as_deref() != Ok("0");
1429 if std::env::var("CMF_GPU_DEBUG").is_ok() {
1430 static ONCE: std::sync::Once = std::sync::Once::new();
1431 ONCE.call_once(|| {
1432 eprintln!(
1433 "mmh3 fuse-out gate: qkv_q={} out_q={} qkv_map={} out_map={}",
1434 matches!(&blk.qkv, Proj::Q(_)),
1435 matches!(&blk.out, Proj::Q(_)),
1436 matches!(&blk.qkv, Proj::Q(q) if q.mapped_device_gemm().is_some()),
1437 matches!(&blk.out, Proj::Q(o) if o.mapped_device_gemm().is_some()),
1438 )
1439 });
1440 }
1441 if let (true, Proj::Q(q), Proj::Q(o)) = (fuse_out, &blk.qkv, &blk.out) {
1442 if let (Some((m, i)), Some((_, oi))) =
1443 (q.mapped_device_gemm(), o.mapped_device_gemm())
1444 {
1445 let mut proj = vec![0f32; n * hs];
1446 if crate::gpu::dit_qkv_attn_out(
1447 m,
1448 i,
1449 oi,
1450 &xn,
1451 n,
1452 hs,
1453 self.heads,
1454 self.head_dim,
1455 1.0 / (self.head_dim as f32).sqrt(),
1456 (
1457 &angles[..],
1458 &blk.q_norm[..],
1459 &blk.k_norm[..],
1460 self.qk_eps as f32,
1461 ),
1462 &mut proj,
1463 ) {
1464 Self::prof(1, t_qkv);
1465 let t_res = std::time::Instant::now();
1466 self.residual(x, hs, &proj, mods, rows, 2);
1467 Self::prof(8, t_res);
1468 self.ffn_tail(blk, x, n, mods, rows);
1469 return;
1470 }
1471 }
1472 }
1473 if let Proj::Q(q) = &blk.qkv {
1474 if let Some((m, i)) = q.mapped_device_gemm() {
1475 let mut attn = vec![0f32; n * inner];
1476 if crate::gpu::dit_qkv_attention(
1477 m,
1478 i,
1479 &xn,
1480 n,
1481 hs,
1482 self.heads,
1483 self.head_dim,
1484 1.0 / (self.head_dim as f32).sqrt(),
1485 (
1486 &angles[..],
1487 &blk.q_norm[..],
1488 &blk.k_norm[..],
1489 self.qk_eps as f32,
1490 ),
1491 &mut attn,
1492 ) {
1493 Self::prof(1, t_qkv);
1499 let t_out = std::time::Instant::now();
1500 let mut proj = vec![0f32; n * hs];
1501 proj_with_lora(&blk.out, &blk.lora.out, &attn, n, &mut proj, pool);
1502 Self::prof(4, t_out);
1503 let t_res = std::time::Instant::now();
1504 self.residual(x, hs, &proj, mods, rows, 2);
1505 Self::prof(8, t_res);
1506 self.ffn_tail(blk, x, n, mods, rows);
1507 return;
1508 }
1509 }
1510 }
1511 }
1512 let mut qkv = vec![0f32; n * 3 * inner];
1513 proj_with_lora(&blk.qkv, &blk.lora.qkv, &xn, n, &mut qkv, pool);
1514 Self::prof(1, t);
1515 let qk_on_gpu = qk_gpu;
1528 let t = std::time::Instant::now();
1529 if !qk_on_gpu {
1530 for (which, w) in [(0usize, &blk.q_norm), (1usize, &blk.k_norm)] {
1531 self.norm_rope_w(
1532 &mut qkv,
1533 n,
1534 self.heads,
1535 w,
1536 &angles,
1537 3 * inner,
1538 which * inner,
1539 );
1540 }
1541 }
1542 Self::prof(2, t);
1543 let t = std::time::Instant::now();
1544 let mut attn = vec![0f32; n * inner];
1545 self.attention(
1546 &qkv,
1547 n,
1548 &mut attn,
1549 qk_on_gpu.then_some((
1550 &angles[..],
1551 &blk.q_norm[..],
1552 &blk.k_norm[..],
1553 self.qk_eps as f32,
1554 )),
1555 );
1556 Self::prof(3, t);
1557 let t = std::time::Instant::now();
1558 let mut proj = vec![0f32; n * hs];
1559 proj_with_lora(&blk.out, &blk.lora.out, &attn, n, &mut proj, pool);
1560 Self::prof(4, t);
1561 let t = std::time::Instant::now();
1565 self.residual(x, hs, &proj, mods, rows, 2);
1566 Self::prof(8, t);
1567
1568 self.ffn_tail(blk, x, n, mods, rows);
1569 }
1570
1571 fn ffn_tail(&self, blk: &Block, x: &mut [f32], n: usize, mods: Option<&[f32]>, rows: &[u32]) {
1576 let hs = self.hidden;
1577 let pool = self.pool.as_deref();
1578 let mut xn = vec![0f32; n * hs];
1579 let mut proj = vec![0f32; n * hs];
1580 let t = std::time::Instant::now();
1581 self.norm_rows(&mut xn, x, n, hs, &blk.norm2, self.eps);
1582 if let (Some(m), false) = (mods, rows.is_empty()) {
1583 self.modulate(&mut xn, hs, m, rows, 3, 4);
1584 }
1585 Self::prof(0, t);
1586 let t = std::time::Instant::now();
1587 if std::env::var("CMF_MMH3_FFN").as_deref() != Ok("cpu")
1592 && !blk.lora.ffn_any()
1593 && crate::gpu::enabled_here()
1594 && n >= 64
1595 {
1596 if let (Proj::Q(q1), Proj::Q(q2)) = (&blk.fc1, &blk.fc2) {
1597 if let (Some((m, i1)), Some((_, i2))) =
1598 (q1.mapped_device_gemm(), q2.mapped_device_gemm())
1599 {
1600 let mut fout = vec![0f32; n * hs];
1601 if crate::gpu::q4tp_ffn_packed(m, i1, i2, &xn, n, hs, self.ffn, None, &mut fout)
1602 {
1603 Self::prof(5, t);
1604 self.residual(x, hs, &fout, mods, rows, 5);
1605 return;
1606 }
1607 }
1608 }
1609 }
1610 let mut gu = vec![0f32; n * 2 * self.ffn];
1611 proj_with_lora(&blk.fc1, &blk.lora.fc1, &xn, n, &mut gu, pool);
1612 Self::prof(5, t);
1613 let t = std::time::Instant::now();
1614 let ffn = self.ffn;
1616 let mut act = vec![0f32; n * ffn];
1617 let ap = SendPtr(act.as_mut_ptr());
1618 pool_rows(pool, n, &|lo, hi| {
1619 for p in lo..hi {
1620 let row = &gu[p * 2 * ffn..(p + 1) * 2 * ffn];
1621 let (g, up) = row.split_at(ffn);
1622 for (o, (&a, &b)) in unsafe { ap.row(p * ffn, ffn) }
1624 .iter_mut()
1625 .zip(g.iter().zip(up))
1626 {
1627 *o = silu(a) * b;
1628 }
1629 }
1630 });
1631 Self::prof(6, t);
1632 let t = std::time::Instant::now();
1633 proj_with_lora(&blk.fc2, &blk.lora.fc2, &act, n, &mut proj, pool);
1634 Self::prof(7, t);
1635 self.residual(x, hs, &proj, mods, rows, 5);
1636 }
1637
1638 fn norm_rows(&self, dst: &mut [f32], src: &[f32], n: usize, hs: usize, w: &[f32], eps: f64) {
1643 let ptr = SendPtr(dst.as_mut_ptr());
1644 pool_rows(self.pool.as_deref(), n, &|lo, hi| {
1645 for p in lo..hi {
1646 rms_norm_into(&src[p * hs..(p + 1) * hs], w, eps, unsafe {
1648 ptr.row(p * hs, hs)
1649 });
1650 }
1651 });
1652 }
1653
1654 fn modulate(
1656 &self,
1657 x: &mut [f32],
1658 hs: usize,
1659 mods: &[f32],
1660 rows: &[u32],
1661 shift_e: usize,
1662 scale_e: usize,
1663 ) {
1664 let stride = EXPAND * hs;
1665 let ptr = SendPtr(x.as_mut_ptr());
1666 pool_rows(self.pool.as_deref(), rows.len(), &|lo, hi| {
1667 for p in lo..hi {
1668 let base = rows[p] as usize * stride;
1669 let shift = &mods[base + shift_e * hs..base + (shift_e + 1) * hs];
1670 let scale = &mods[base + scale_e * hs..base + (scale_e + 1) * hs];
1671 for ((v, &sc), &sh) in unsafe { ptr.row(p * hs, hs) }
1673 .iter_mut()
1674 .zip(scale)
1675 .zip(shift)
1676 {
1677 *v = *v * (1.0 + sc) + sh;
1678 }
1679 }
1680 });
1681 }
1682
1683 fn residual(
1686 &self,
1687 x: &mut [f32],
1688 hs: usize,
1689 other: &[f32],
1690 mods: Option<&[f32]>,
1691 rows: &[u32],
1692 gate_e: usize,
1693 ) {
1694 let n = x.len() / hs;
1695 let stride = EXPAND * hs;
1696 let ptr = SendPtr(x.as_mut_ptr());
1697 let gated = mods.filter(|_| !rows.is_empty());
1698 pool_rows(self.pool.as_deref(), n, &|lo, hi| {
1699 for p in lo..hi {
1700 let row = unsafe { ptr.row(p * hs, hs) };
1702 let src = &other[p * hs..(p + 1) * hs];
1703 match gated {
1704 Some(m) => {
1705 let base = rows[p] as usize * stride;
1706 let gate = &m[base + gate_e * hs..base + (gate_e + 1) * hs];
1707 for ((v, &g), &o) in row.iter_mut().zip(gate).zip(src) {
1708 *v += g * o;
1709 }
1710 }
1711 None => {
1712 for (v, &o) in row.iter_mut().zip(src) {
1713 *v += o;
1714 }
1715 }
1716 }
1717 }
1718 });
1719 }
1720
1721 pub fn forward(
1728 &self,
1729 layout: &Layout,
1730 text: &[f32],
1731 video: &[f32],
1732 audio: &[f32],
1733 sigma_v: f64,
1734 cond: &[Vec<f32>],
1735 ) -> (Vec<f32>, Vec<f32>) {
1736 let hs = self.hidden;
1737 let pool = self.pool.as_deref();
1738 let sigma_v = sigma_v.max(1e-6);
1739 let t_v = 1.0 - sigma_v;
1740 let t_a = 1.0 - time_shift_sigma(sigma_v, self.shift_video, self.shift_audio);
1741
1742 let has_cond = layout
1747 .segments
1748 .iter()
1749 .any(|s| matches!(s.kind, Kind::Cond | Kind::RefImg));
1750 let has_ref_audio = layout.segments.iter().any(|s| s.kind == Kind::RefAudio);
1751 let t_cond_a = t_a.max(self.cond_aug_audio);
1754 let t_cond = t_v.max(self.cond_aug);
1759 let has_ctx = layout
1764 .segments
1765 .iter()
1766 .any(|s| matches!(s.kind, Kind::CtxVideo | Kind::CtxAudio));
1767 let mut ts = vec![t_v, t_a];
1768 if has_ctx {
1769 ts.push(0.0);
1770 }
1771 if has_cond {
1772 ts.push(t_cond);
1773 }
1774 if has_ref_audio {
1775 ts.push(t_cond_a);
1776 }
1777 ts.sort_by(|a, b| a.partial_cmp(b).unwrap());
1778 ts.dedup();
1779 let row_of = |t: f64| ts.iter().position(|&x| x == t).unwrap();
1780 let (row_v, row_a) = (row_of(t_v), row_of(t_a));
1781 let row_c = if has_cond { row_of(t_cond) } else { 0 };
1782 let row_ctx = if has_ctx { row_of(0.0) } else { 0 };
1783 let row_ca = if has_ref_audio { row_of(t_cond_a) } else { 0 };
1784
1785 let mut rows = vec![0u32; layout.seq_len];
1789 for s in &layout.segments {
1790 match s.kind {
1791 Kind::Text => {
1792 for (i, v) in rows[s.start..s.stop].iter_mut().enumerate() {
1793 let tag = *layout.text_tags.get(i).unwrap_or(&(TAG_TEXT as u8));
1794 *v = (row_v * MODALITIES + tag as usize) as u32;
1795 }
1796 }
1797 Kind::Cond | Kind::RefImg => {
1801 for v in rows[s.start..s.stop].iter_mut() {
1802 *v = (row_c * MODALITIES + TAG_VIDEO) as u32;
1803 }
1804 }
1805 Kind::RefAudio => {
1806 for v in rows[s.start..s.stop].iter_mut() {
1807 *v = (row_ca * MODALITIES + TAG_AUDIO) as u32;
1808 }
1809 }
1810 Kind::Video => {
1811 for v in rows[s.start..s.stop].iter_mut() {
1812 *v = (row_v * MODALITIES + TAG_VIDEO) as u32;
1813 }
1814 }
1815 Kind::Audio => {
1816 for v in rows[s.start..s.stop].iter_mut() {
1817 *v = (row_a * MODALITIES + TAG_AUDIO) as u32;
1818 }
1819 }
1820 Kind::CtxVideo => {
1821 for v in rows[s.start..s.stop].iter_mut() {
1822 *v = (row_ctx * MODALITIES + TAG_VIDEO) as u32;
1823 }
1824 }
1825 Kind::CtxAudio => {
1826 for v in rows[s.start..s.stop].iter_mut() {
1827 *v = (row_ctx * MODALITIES + TAG_AUDIO) as u32;
1828 }
1829 }
1830 }
1831 }
1832
1833 let v_rows = patchify_video(
1835 video,
1836 self.latents_dim,
1837 layout.latent_t,
1838 layout.lat_h,
1839 layout.lat_w,
1840 );
1841 let v_n = v_rows.len() / (self.latents_dim * 4);
1842 let a_rows = pack_audio(audio, self.audio_dim, layout.audio_t);
1843 let vd = self.latents_dim * 4;
1846 let cond_rows: Vec<Vec<f32>> = cond
1847 .iter()
1848 .enumerate()
1849 .map(|(i, z)| {
1850 let mut r = patchify_video(z, self.latents_dim, 1, layout.lat_h, layout.lat_w);
1851 if self.cond_aug < 1.0 {
1852 let noise = crate::videogen::gauss_pub(r.len(), 0x5EED ^ i as u64);
1857 let a = self.cond_aug as f32;
1858 for (v, n) in r.iter_mut().zip(&noise) {
1859 *v = a * *v + (1.0 - a) * n;
1860 }
1861 }
1862 r
1863 })
1864 .collect();
1865
1866 let mut h = vec![0f32; layout.seq_len * hs];
1867 let mut ci = 0usize;
1868 for s in layout.segments.iter().filter(|s| s.kind == Kind::Cond) {
1869 let n = s.stop - s.start;
1870 let r = cond_rows.get(ci).unwrap_or_else(|| {
1871 panic!(
1872 "layout has {} cond segments, {} latents given",
1873 ci + 1,
1874 cond_rows.len()
1875 )
1876 });
1877 self.video_patch
1878 .matmat(r, n, &mut h[s.start * hs..s.stop * hs], pool);
1879 for row in h[s.start * hs..s.stop * hs].chunks_exact_mut(hs) {
1880 for (v, &b) in row.iter_mut().zip(&self.video_patch_b) {
1881 *v += b;
1882 }
1883 }
1884 ci += 1;
1885 }
1886 let (vblk_start, vblk_stop) = layout.video_block();
1890 let (ablk_start, ablk_stop) = layout.audio_block();
1891 let vseg = layout.segment(Kind::Video);
1892 let aseg = layout.segment(Kind::Audio);
1893 let tseg = layout.segment(Kind::Text);
1894 h[tseg.start * hs..tseg.stop * hs].copy_from_slice(&text[..(tseg.stop - tseg.start) * hs]);
1895 self.video_patch
1896 .matmat(&v_rows, v_n, &mut h[vblk_start * hs..vblk_stop * hs], pool);
1897 let audio_patch = self
1898 .audio_patch
1899 .as_ref()
1900 .expect("the audio-video forward on a model without an audio stream");
1901 audio_patch.matmat(
1902 &a_rows,
1903 ablk_stop - ablk_start,
1904 &mut h[ablk_start * hs..ablk_stop * hs],
1905 pool,
1906 );
1907 for row in h[vblk_start * hs..vblk_stop * hs].chunks_exact_mut(hs) {
1908 for (v, &b) in row.iter_mut().zip(&self.video_patch_b) {
1909 *v += b;
1910 }
1911 }
1912 for row in h[ablk_start * hs..ablk_stop * hs].chunks_exact_mut(hs) {
1913 for (v, &b) in row.iter_mut().zip(&self.audio_patch_b) {
1914 *v += b;
1915 }
1916 }
1917
1918 let watch = std::env::var("CMF_MMH3_WATCHROW")
1923 .ok()
1924 .and_then(|w| w.parse::<usize>().ok());
1925 if let Some(r) = watch {
1926 for (i, blk) in self.blocks.iter().enumerate() {
1927 let mods = blk.adaln.as_ref().unwrap().eval(&ts, pool);
1928 self.block_forward(blk, &mut h, layout.seq_len, Some(&mods), &layout.pos, &rows);
1929 let row = &h[r * hs..(r + 1) * hs];
1932 let amax = row
1933 .iter()
1934 .filter(|v| v.is_finite())
1935 .fold(0f32, |a, v| a.max(v.abs()));
1936 let bad = row.iter().filter(|v| !v.is_finite()).count();
1937 if bad > 0 || i == 0 || amax > 1e3 {
1938 eprintln!(" watch blk {i}: row {r} absmax {amax:.3e} nonfinite {bad}");
1939 }
1940 if std::env::var_os("CMF_DIT_PROGRESS").is_some() {
1941 eprint!("\r block {}/{}", i + 1, self.blocks.len());
1942 }
1943 }
1944 } else {
1945 self.run_blocks(&self.blocks, &mut h, layout.seq_len, &ts, &layout.pos, &rows);
1946 }
1947
1948 if std::env::var("CMF_MMH3_NANPROBE").is_ok() {
1950 let bad: Vec<usize> = (0..h.len() / hs)
1951 .filter(|r| h[r * hs..(r + 1) * hs].iter().any(|v| !v.is_finite()))
1952 .collect();
1953 if !bad.is_empty() {
1954 eprintln!(
1955 " nanprobe rows: {} of {} bad, first 8 {:?}, video seg {}..{}, audio seg {}..{}",
1956 bad.len(),
1957 h.len() / hs,
1958 &bad[..bad.len().min(8)],
1959 vseg.start,
1960 vseg.stop,
1961 aseg.start,
1962 aseg.stop
1963 );
1964 }
1965 }
1966 let fm = self.final_adaln.eval(&ts, pool);
1967 let mut video_out = vec![0f32; (vseg.stop - vseg.start) * vd];
1968 let mut audio_out = vec![0f32; (aseg.stop - aseg.start) * self.audio_dim];
1969 for (seg, row, w, b, dst, dim) in [
1970 (
1971 vseg,
1972 row_v,
1973 &self.video_out,
1974 &self.video_out_b,
1975 &mut video_out,
1976 vd,
1977 ),
1978 (
1979 aseg,
1980 row_a,
1981 self.audio_out
1982 .as_ref()
1983 .expect("the audio-video forward on a model without an audio stream"),
1984 &self.audio_out_b,
1985 &mut audio_out,
1986 self.audio_dim,
1987 ),
1988 ] {
1989 let n = seg.stop - seg.start;
1990 let mut hn = vec![0f32; n * hs];
1991 for (o, src) in hn
1992 .chunks_exact_mut(hs)
1993 .zip(h[seg.start * hs..seg.stop * hs].chunks_exact(hs))
1994 {
1995 rms_norm_into(src, &self.final_norm, self.final_eps, o);
1996 }
1997 let shift = &fm[row * 2 * hs..row * 2 * hs + hs];
2000 let scale = &fm[row * 2 * hs + hs..(row + 1) * 2 * hs];
2001 for r in hn.chunks_exact_mut(hs) {
2002 for ((v, &sc), &sh) in r.iter_mut().zip(scale).zip(shift) {
2003 *v = *v * (1.0 + sc) + sh;
2004 }
2005 }
2006 let probe = std::env::var("CMF_MMH3_NANPROBE").is_ok();
2011 if probe {
2012 let bad_in = hn.iter().filter(|v| !v.is_finite()).count();
2013 eprintln!(
2014 " nanprobe dim={dim} n={n} in_bad={bad_in} in_absmax={:.4}",
2015 hn.iter()
2016 .filter(|v| v.is_finite())
2017 .fold(0f32, |a, v| a.max(v.abs()))
2018 );
2019 }
2020 w.matmat(&hn, n, dst, pool);
2021 if probe {
2022 let bad_out = dst.iter().filter(|v| !v.is_finite()).count();
2023 eprintln!(
2024 " nanprobe dim={dim} out_bad={bad_out}/{} out_absmax={:.4}",
2025 dst.len(),
2026 dst.iter()
2027 .filter(|v| v.is_finite())
2028 .fold(0f32, |a, v| a.max(v.abs()))
2029 );
2030 }
2031 for r in dst.chunks_exact_mut(dim) {
2032 for (v, &bv) in r.iter_mut().zip(b.iter()) {
2033 *v += bv;
2034 }
2035 }
2036 }
2037
2038 let out_t = (vseg.stop - vseg.start) / layout.frame_rows;
2047 let out_at = (aseg.stop - aseg.start) / 2;
2048 let video = unpatchify_video(
2049 &video_out,
2050 self.latents_dim,
2051 out_t,
2052 layout.lat_h,
2053 layout.lat_w,
2054 );
2055 let audio = unpack_audio(&audio_out, self.audio_dim, out_at);
2056 (
2057 video.iter().map(|&v| -v).collect(),
2058 audio.iter().map(|&v| -v).collect(),
2059 )
2060 }
2061}
2062
2063impl MiniMaxH3 {
2066 fn run_blocks(
2074 &self,
2075 blocks: &[Block],
2076 h: &mut [f32],
2077 n: usize,
2078 ts: &[f64],
2079 pos: &[[f64; 3]],
2080 rows: &[u32],
2081 ) {
2082 let pool = self.pool.as_deref();
2083 if std::env::var("CMF_MMH3_CHAIN").as_deref() != Ok("0")
2084 && crate::gpu::enabled_here()
2085 && crate::gpu::h3_chain_available()
2086 {
2087 let mods: Vec<Vec<f32>> = blocks
2088 .iter()
2089 .map(|b| b.adaln.as_ref().map(|a| a.eval(ts, pool)).unwrap_or_default())
2090 .collect();
2091 let angles = if pos.is_empty() {
2092 Vec::new()
2093 } else {
2094 self.rope_angles(pos)
2095 };
2096 let idx = |p: &Proj| match p {
2097 Proj::Q(q) => q.mapped_device_gemm().map(|(_, i)| i),
2098 Proj::F32 { .. } => None,
2099 };
2100 let chain: Option<Vec<crate::gpu::H3ChainBlock>> = blocks
2101 .iter()
2102 .zip(&mods)
2103 .map(|(b, m)| {
2104 if BlockLora::live(&b.lora.qkv)
2105 || BlockLora::live(&b.lora.out)
2106 || b.lora.ffn_any()
2107 {
2108 return None;
2109 }
2110 Some(crate::gpu::H3ChainBlock {
2111 qkv: idx(&b.qkv)?,
2112 out: idx(&b.out)?,
2113 fc1: idx(&b.fc1)?,
2114 fc2: idx(&b.fc2)?,
2115 norm1: &b.norm1,
2116 norm2: &b.norm2,
2117 q_norm: &b.q_norm,
2118 k_norm: &b.k_norm,
2119 mods: m,
2120 qkv_bias: &[],
2121 out_bias: &[],
2122 fc1_bias: &[],
2123 fc2_bias: &[],
2124 scale1: &[],
2125 scale2: &[],
2126 })
2127 })
2128 .collect();
2129 if let Some(chain) = chain {
2130 let job = crate::gpu::H3Chain {
2131 model: &self.model,
2132 blocks: &chain,
2133 n,
2134 hidden: self.hidden,
2135 heads: self.heads,
2136 head_dim: self.head_dim,
2137 ffn: self.ffn,
2138 eps: self.eps as f32,
2139 qk_eps: self.qk_eps as f32,
2140 angles: &angles,
2141 rows,
2142 groups: 1,
2143 qkv_interleaved: false,
2144 };
2145 if crate::gpu::h3_chain(&job, h) {
2146 return;
2147 }
2148 }
2149 }
2150 for (i, blk) in blocks.iter().enumerate() {
2151 let mods = blk.adaln.as_ref().map(|a| a.eval(ts, pool));
2152 self.block_forward(blk, h, n, mods.as_deref(), pos, rows);
2153 if std::env::var_os("CMF_DIT_PROGRESS").is_some() {
2154 eprint!("\r block {}/{}", i + 1, blocks.len());
2155 }
2156 }
2157 }
2158
2159 pub fn forward_image(
2169 &self,
2170 layout: &Layout,
2171 text: &[f32],
2172 latent: &[f32],
2173 refs: &[(&[f32], usize, usize)],
2174 sigma: f64,
2175 sigma_next: f64,
2176 ) -> Vec<f32> {
2177 let hs = self.hidden;
2178 let pool = self.pool.as_deref();
2179 let t = 1.0 - sigma;
2180 let has_ref = layout.segments.iter().any(|s| s.kind == Kind::RefImg);
2183 let t_ref = t.max(self.cond_aug);
2184 let mut ts = vec![t];
2185 if has_ref && t_ref != t {
2186 ts.push(t_ref);
2187 }
2188 ts.sort_by(|a, b| a.partial_cmp(b).unwrap());
2189 let row_of = |x: f64| ts.iter().position(|&v| v == x).unwrap();
2190 let (row_t, row_r) = (row_of(t), if has_ref { row_of(t_ref) } else { 0 });
2191
2192 let mut rows = vec![0u32; layout.seq_len];
2193 for s in &layout.segments {
2194 match s.kind {
2195 Kind::Text => {
2196 for (i, v) in rows[s.start..s.stop].iter_mut().enumerate() {
2197 let tag = *layout.text_tags.get(i).unwrap_or(&(TAG_TEXT as u8));
2198 *v = (row_t * MODALITIES + tag as usize) as u32;
2199 }
2200 }
2201 Kind::RefImg => {
2202 for v in rows[s.start..s.stop].iter_mut() {
2203 *v = (row_r * MODALITIES + TAG_VIDEO) as u32;
2204 }
2205 }
2206 _ => {
2207 for v in rows[s.start..s.stop].iter_mut() {
2208 *v = (row_t * MODALITIES + TAG_VIDEO) as u32;
2209 }
2210 }
2211 }
2212 }
2213
2214 let vd = self.latents_dim * 4;
2216 let mut h = vec![0f32; layout.seq_len * hs];
2217 let tseg = layout.segment(Kind::Text);
2218 h[tseg.start * hs..tseg.stop * hs].copy_from_slice(&text[..(tseg.stop - tseg.start) * hs]);
2219 let embed = |r: &[f32], dst: &mut [f32]| {
2220 self.video_patch.matmat(r, r.len() / vd, dst, pool);
2221 for row in dst.chunks_exact_mut(hs) {
2222 for (v, &b) in row.iter_mut().zip(&self.video_patch_b) {
2223 *v += b;
2224 }
2225 }
2226 };
2227 for (i, s) in layout
2228 .segments
2229 .iter()
2230 .filter(|s| s.kind == Kind::RefImg)
2231 .enumerate()
2232 {
2233 let (z, rh, rw) = refs[i];
2234 let mut r = patchify_video(z, self.latents_dim, 1, rh, rw);
2235 if self.cond_aug < 1.0 {
2236 let noise = crate::videogen::gauss_pub(r.len(), 0x5EED ^ i as u64);
2239 let a = self.cond_aug as f32;
2240 for (v, n) in r.iter_mut().zip(&noise) {
2241 *v = a * *v + (1.0 - a) * n;
2242 }
2243 }
2244 embed(&r, &mut h[s.start * hs..s.stop * hs]);
2245 }
2246 let vseg = layout.segment(Kind::Video);
2247 let v_rows = patchify_video(latent, self.latents_dim, 1, layout.lat_h, layout.lat_w);
2248 embed(&v_rows, &mut h[vseg.start * hs..vseg.stop * hs]);
2249
2250 self.run_blocks(&self.blocks, &mut h, layout.seq_len, &ts, &layout.pos, &rows);
2252
2253 let n = vseg.stop - vseg.start;
2255 let fm = self.final_adaln.eval(&ts, pool);
2256 let shift = &fm[row_t * 2 * hs..row_t * 2 * hs + hs];
2257 let scale = &fm[row_t * 2 * hs + hs..(row_t + 1) * 2 * hs];
2258 let mut hn = vec![0f32; n * hs];
2259 for (o, src) in hn
2260 .chunks_exact_mut(hs)
2261 .zip(h[vseg.start * hs..vseg.stop * hs].chunks_exact(hs))
2262 {
2263 rms_norm_into(src, &self.final_norm, self.final_eps, o);
2264 for ((v, &sc), &sh) in o.iter_mut().zip(scale).zip(shift) {
2265 *v = *v * (1.0 + sc) + sh;
2266 }
2267 }
2268 let mut out = vec![0f32; n * vd];
2269 if self.pdd_steps > 1 {
2270 let (w, b) = self.pdd_head(sigma, sigma_next);
2271 crate::fcd_ops::gemm_nt(&hn, &w, &mut out, n, hs, vd, pool);
2272 for r in out.chunks_exact_mut(vd) {
2273 for (v, &bv) in r.iter_mut().zip(&b) {
2274 *v += bv;
2275 }
2276 }
2277 } else {
2278 self.video_out.matmat(&hn, n, &mut out, pool);
2279 for r in out.chunks_exact_mut(vd) {
2280 for (v, &bv) in r.iter_mut().zip(&self.video_out_b) {
2281 *v += bv;
2282 }
2283 }
2284 }
2285 let img = unpatchify_video(&out, self.latents_dim, 1, layout.lat_h, layout.lat_w);
2286 img.iter().map(|&v| -v).collect()
2287 }
2288
2289 fn pdd_head(&self, sigma: f64, sigma_next: f64) -> (Vec<f32>, Vec<f32>) {
2294 let n = self.pdd_steps;
2295 let grid = &self.pdd_sigmas;
2296 let nearest = |s: f64| {
2297 (0..=n)
2298 .min_by(|&a, &b| {
2299 (grid[a] - s)
2300 .abs()
2301 .partial_cmp(&(grid[b] - s).abs())
2302 .unwrap()
2303 })
2304 .unwrap()
2305 };
2306 let start = nearest(sigma).min(n - 1);
2307 let stop = nearest(sigma_next).max(start + 1);
2308 let dt: Vec<f64> = (start..stop).map(|k| grid[k] - grid[k + 1]).collect();
2309 let total: f64 = dt.iter().sum();
2310 let (hs, vd) = (self.hidden, self.latents_dim * 4);
2311 let mut w = self.pdd_w[..vd * hs].to_vec();
2312 let mut b = self.video_out_b[..vd].to_vec();
2313 let first = start.max(1);
2314 for k in first..stop {
2315 let wk = (dt[k - start] / total) as f32;
2316 for (d, &s) in w.iter_mut().zip(&self.pdd_w[k * vd * hs..(k + 1) * vd * hs]) {
2317 *d += wk * s;
2318 }
2319 for (d, &s) in b.iter_mut().zip(&self.video_out_b[k * vd..(k + 1) * vd]) {
2320 *d += wk * s;
2321 }
2322 }
2323 (w, b)
2324 }
2325}
2326
2327fn pool_rows(pool: Option<&Pool>, n: usize, f: &(dyn Fn(usize, usize) + Sync)) {
2331 match pool {
2332 Some(p) => p.run_rows(n, f),
2333 None => f(0, n),
2334 }
2335}
2336
2337pub fn patchify_video(x: &[f32], c: usize, t: usize, h: usize, w: usize) -> Vec<f32> {
2342 let (ph, pw) = (h / 2, w / 2);
2343 let mut out = vec![0f32; t * ph * pw * c * 4];
2344 let mut i = 0;
2345 for ti in 0..t {
2346 for hi in 0..ph {
2347 for wi in 0..pw {
2348 for ci in 0..c {
2349 for p in 0..2 {
2350 for q in 0..2 {
2351 out[i] = x[((ci * t + ti) * h + hi * 2 + p) * w + wi * 2 + q];
2352 i += 1;
2353 }
2354 }
2355 }
2356 }
2357 }
2358 }
2359 out
2360}
2361
2362pub fn unpatchify_video(rows: &[f32], c: usize, t: usize, h: usize, w: usize) -> Vec<f32> {
2364 let (ph, pw) = (h / 2, w / 2);
2365 let mut out = vec![0f32; c * t * h * w];
2366 let mut i = 0;
2367 for ti in 0..t {
2368 for hi in 0..ph {
2369 for wi in 0..pw {
2370 for ci in 0..c {
2371 for p in 0..2 {
2372 for q in 0..2 {
2373 out[((ci * t + ti) * h + hi * 2 + p) * w + wi * 2 + q] = rows[i];
2374 i += 1;
2375 }
2376 }
2377 }
2378 }
2379 }
2380 }
2381 out
2382}
2383
2384pub fn pack_audio(x: &[f32], c: usize, t: usize) -> Vec<f32> {
2387 let mut out = vec![0f32; 2 * t * c];
2388 for ch in 0..2 {
2389 for ti in 0..t {
2390 for ci in 0..c {
2391 out[(ch * t + ti) * c + ci] = x[(ci * 2 + ch) * t + ti];
2392 }
2393 }
2394 }
2395 out
2396}
2397
2398pub fn unpack_audio(rows: &[f32], c: usize, t: usize) -> Vec<f32> {
2400 let mut out = vec![0f32; c * 2 * t];
2401 for ch in 0..2 {
2402 for ti in 0..t {
2403 for ci in 0..c {
2404 out[(ci * 2 + ch) * t + ti] = rows[(ch * t + ti) * c + ci];
2405 }
2406 }
2407 }
2408 out
2409}
2410
2411struct SendPtr(*mut f32);
2414unsafe impl Send for SendPtr {}
2415unsafe impl Sync for SendPtr {}
2416impl SendPtr {
2417 #[allow(clippy::mut_from_ref)]
2419 unsafe fn row(&self, off: usize, len: usize) -> &mut [f32] {
2420 unsafe { std::slice::from_raw_parts_mut(self.0.add(off), len) }
2421 }
2422}
2423
2424fn softmax_inplace(row: &mut [f32]) {
2425 let mx = row.iter().cloned().fold(f32::MIN, f32::max);
2426 let mut den = 0f32;
2427 for r in row.iter_mut() {
2428 *r = (*r - mx).exp();
2429 den += *r;
2430 }
2431 if den > 0.0 {
2432 let inv = 1.0 / den;
2433 for r in row.iter_mut() {
2434 *r *= inv;
2435 }
2436 }
2437}
2438
2439#[cfg(test)]
2440mod tests {
2441 #[test]
2447 fn an_image_layout_puts_references_between_the_text_and_the_target() {
2448 let l = Layout::image(5, 8, 8, &[(4, 8)], &[]);
2451 assert_eq!(l.seq_len, 5 + 8 + 16);
2452 assert_eq!((l.latent_t, l.audio_t, l.frame_rows), (1, 0, 16));
2453 let kinds: Vec<Kind> = l.segments.iter().map(|s| s.kind).collect();
2454 assert_eq!(kinds, vec![Kind::Text, Kind::RefImg, Kind::Video]);
2455 assert_eq!(l.pos[3], [3.0, 0.0, 0.0]);
2457 let r = l.pos[5];
2460 assert_eq!(r[0], 5.0);
2461 let (ratio_h, ratio_w) = (4.0 / 32f64.sqrt(), 8.0 / 32f64.sqrt());
2462 assert!((r[1] - (1.0 - ratio_h) / 2.0 * 32.0).abs() < 1e-9);
2463 assert!((r[2] - (1.0 - ratio_w) / 2.0 * 32.0).abs() < 1e-9);
2464 assert_eq!(l.pos[13], [6.0, 0.0, 0.0]);
2467 assert_eq!(l.pos[14], [6.0, 0.0, 8.0]);
2468 assert_eq!(l.pos[13 + 4], [6.0, 8.0, 0.0]);
2469 let t = Layout::image(5, 8, 8, &[], &[]);
2471 assert_eq!(t.pos[5], [5.0, 0.0, 0.0]);
2472 assert_eq!(t.seq_len, 21);
2473 }
2474
2475 #[test]
2476 fn streaming_layout_degenerates_to_the_bidirectional_one() {
2477 let (lat_h, lat_w, latent_t, audio_t, text_len) = (16, 24, 7, 12, 5);
2478 let base = Layout::t2va(text_len, latent_t, lat_h, lat_w, audio_t);
2479 let cur_v: Vec<usize> = (0..latent_t).collect();
2480 let cur_a: Vec<usize> = (0..audio_t).collect();
2481 let stream = Layout::streaming(text_len, &[], lat_h, lat_w, &[], &cur_v, &[], &cur_a);
2482 assert_eq!(stream.seq_len, base.seq_len, "row count");
2483 assert_eq!(stream.latent_t, base.latent_t);
2484 assert_eq!(stream.audio_t, base.audio_t);
2485 for (i, (a, b)) in stream.pos.iter().zip(&base.pos).enumerate() {
2486 for axis in 0..3 {
2487 assert!(
2488 (a[axis] - b[axis]).abs() < 1e-9,
2489 "row {i} axis {axis}: {a:?} vs {b:?}"
2490 );
2491 }
2492 }
2493 let seg = |l: &Layout, k: Kind| {
2494 l.segments
2495 .iter()
2496 .find(|s| s.kind == k)
2497 .map(|s| (s.start, s.stop))
2498 };
2499 assert_eq!(seg(&stream, Kind::Video), seg(&base, Kind::Video));
2500 assert_eq!(seg(&stream, Kind::Audio), seg(&base, Kind::Audio));
2501 }
2502
2503 #[test]
2506 fn streaming_positions_are_absolute() {
2507 let (lat_h, lat_w) = (16, 24);
2508 let whole = Layout::streaming(
2509 3,
2510 &[],
2511 lat_h,
2512 lat_w,
2513 &[],
2514 &(0..9).collect::<Vec<_>>(),
2515 &[],
2516 &[],
2517 );
2518 let tail = Layout::streaming(
2519 3,
2520 &[],
2521 lat_h,
2522 lat_w,
2523 &[],
2524 &(6..9).collect::<Vec<_>>(),
2525 &[],
2526 &[],
2527 );
2528 let rows = whole.frame_rows;
2529 let (ws, _) = whole.video_block();
2530 let (ts_, _) = tail.video_block();
2531 for k in 0..3 {
2532 let a = whole.pos[ws + (6 + k) * rows];
2533 let b = tail.pos[ts_ + k * rows];
2534 assert!((a[0] - b[0]).abs() < 1e-9, "frame {k}: {a:?} vs {b:?}");
2535 }
2536 }
2537
2538 use super::*;
2539
2540 #[test]
2541 fn schedule_remap_is_an_involution() {
2542 for &s in &[1e-3, 0.25, 0.5, 0.8, 0.972_973, 1.0] {
2543 let a = time_shift_sigma(s, 12.0, 3.0);
2544 let back = time_shift_sigma(a, 3.0, 12.0);
2545 assert!((back - s).abs() < 1e-9, "{s} -> {a} -> {back}");
2546 }
2547 }
2548
2549 #[test]
2550 fn slope_matches_a_finite_difference() {
2551 let h = 1e-6;
2552 for &s in &[0.2, 0.5, 0.9] {
2553 let num = (time_shift_sigma(s + h, 12.0, 3.0) - time_shift_sigma(s - h, 12.0, 3.0))
2554 / (2.0 * h);
2555 let got = time_shift_slope(s, 12.0, 3.0);
2556 assert!((num - got).abs() < 1e-5, "{s}: {num} vs {got}");
2557 }
2558 }
2559
2560 #[test]
2561 fn patchify_round_trips() {
2562 let (c, t, h, w) = (3usize, 2usize, 4usize, 6usize);
2563 let x: Vec<f32> = (0..c * t * h * w).map(|i| i as f32).collect();
2564 let rows = patchify_video(&x, c, t, h, w);
2565 assert_eq!(rows.len(), t * (h / 2) * (w / 2) * c * 4);
2566 assert_eq!(unpatchify_video(&rows, c, t, h, w), x);
2567 }
2568
2569 #[test]
2570 fn audio_pack_round_trips() {
2571 let (c, t) = (5usize, 7usize);
2572 let x: Vec<f32> = (0..c * 2 * t).map(|i| i as f32).collect();
2573 let rows = pack_audio(&x, c, t);
2574 assert_eq!(unpack_audio(&rows, c, t), x);
2575 }
2576
2577 #[test]
2578 fn keyframes_sit_between_the_text_and_the_audio() {
2579 let (tl, lt, lh, lw, at) = (8usize, 3usize, 8usize, 12usize, 5usize);
2580 let base = Layout::t2va(tl, lt, lh, lw, at);
2581 let l = Layout::fl2va(tl, lt, lh, lw, at, &[(0, 39), (38, 39)], &[]);
2583 assert_eq!(l.cond_rows(), 2 * l.frame_rows);
2584 assert_eq!(l.seq_len, base.seq_len + 2 * l.frame_rows);
2585 let kinds: Vec<_> = l.segments.iter().map(|s| s.kind).collect();
2586 assert_eq!(
2587 kinds,
2588 vec![Kind::Text, Kind::Cond, Kind::Cond, Kind::Audio, Kind::Video]
2589 );
2590 let (a0, v0) = (l.segment(Kind::Audio), l.segment(Kind::Video));
2593 let (ba, bv) = (base.segment(Kind::Audio), base.segment(Kind::Video));
2594 assert_eq!(l.pos[a0.start][0], base.pos[ba.start][0]);
2595 assert_eq!(l.pos[v0.start][0], base.pos[bv.start][0]);
2596 let c: Vec<_> = l.segments.iter().filter(|s| s.kind == Kind::Cond).collect();
2599 assert_eq!(l.pos[c[0].start][0], tl as f64);
2600 let spans: f64 = (0..lt)
2601 .map(|k| FRAME_RESCALE * FRAME_PER_TOKEN[k % 5])
2602 .sum();
2603 assert!((l.pos[c[1].start][0] - (tl as f64 + spans - FRAME_RESCALE)).abs() < 1e-12);
2604 assert_eq!(l.pos[c[0].start][1], l.pos[v0.start][1]);
2606 assert_eq!(l.pos[c[0].start][2], l.pos[v0.start][2]);
2607 }
2608
2609 #[test]
2610 fn layout_places_the_target_streams_last() {
2611 let l = Layout::t2va(8, 3, 8, 12, 5);
2612 assert_eq!(l.frame_rows, 4 * 6);
2613 assert_eq!(l.seq_len, 8 + 2 * 5 + 3 * 24);
2614 assert_eq!(l.segments[0].kind, Kind::Text);
2615 assert_eq!(l.segments[1].kind, Kind::Audio);
2616 assert_eq!(l.segments[2].kind, Kind::Video);
2617 let v = l.segment(Kind::Video);
2619 let t0 = l.pos[v.start][0];
2620 let t1 = l.pos[v.start + l.frame_rows][0];
2621 assert!((t1 - t0 - FRAME_RESCALE).abs() < 1e-12);
2622 }
2623}