1use crate::dit::Proj;
38
39pub static MMH3_PROF: [std::sync::atomic::AtomicU64; 9] = [
43 std::sync::atomic::AtomicU64::new(0),
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];
53
54pub(crate) fn mmh3_prof_on() -> bool {
55 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
56 *ON.get_or_init(|| std::env::var("CMF_MMH3_PROF").is_ok())
57}
58
59pub fn mmh3_prof_report() -> Option<String> {
62 if !mmh3_prof_on() {
63 return None;
64 }
65 const NAMES: [&str; 9] = [
66 "norm+mod",
67 "qkv gemm",
68 "qknorm+rope",
69 "attention",
70 "out gemm",
71 "fc1 gemm",
72 "swiglu",
73 "fc2 gemm",
74 "residual",
75 ];
76 let mut v: Vec<(u64, &str)> = MMH3_PROF
77 .iter()
78 .map(|a| a.load(std::sync::atomic::Ordering::Relaxed))
79 .zip(NAMES)
80 .collect();
81 let total: u64 = v.iter().map(|(us, _)| *us).sum();
82 v.sort_by(|a, b| b.0.cmp(&a.0));
83 let mut out = format!("mmh3 phases (total {:.1} s):", total as f64 / 1e6);
84 for (us, name) in v {
85 out.push_str(&format!(
86 "\n {name:<14} {:>7.1} s {:>5.1}%",
87 us as f64 / 1e6,
88 100.0 * us as f64 / total.max(1) as f64
89 ));
90 }
91 Some(out)
92}
93
94use crate::pool::Pool;
95use cortiq_core::CmfModel;
96use std::sync::Arc;
97
98const FRAME_PER_TOKEN: [f64; 5] = [1.0, 4.0, 4.0, 4.0, 4.0];
101const FRAME_RESCALE: f64 = 5.0 / 3.0;
102
103const TAG_VIDEO: usize = 0;
105const TAG_TEXT: usize = 1;
106const TAG_AUDIO: usize = 2;
107const MODALITIES: usize = 3;
108const EXPAND: usize = 6;
110const VISUAL_COND_TIMESTEP: f64 = 0.999;
112const AUDIO_COND_TIMESTEP: f64 = 1.0;
114
115pub fn time_shift_sigma(sigma: f64, from_shift: f64, to_shift: f64) -> f64 {
119 let base = sigma / (from_shift + sigma * (1.0 - from_shift));
120 to_shift * base / (1.0 + (to_shift - 1.0) * base)
121}
122
123pub fn time_shift_slope(sigma: f64, from_shift: f64, to_shift: f64) -> f64 {
125 let base = sigma / (from_shift + sigma * (1.0 - from_shift));
126 (to_shift * (1.0 + (from_shift - 1.0) * base).powi(2))
127 / (from_shift * (1.0 + (to_shift - 1.0) * base).powi(2))
128}
129
130#[derive(Clone, Copy, PartialEq, Debug)]
133pub enum Kind {
134 Text,
135 Cond,
137 RefImg,
139 RefAudio,
141 Audio,
142 Video,
143}
144
145#[derive(Clone, Copy, Debug)]
147pub struct Segment {
148 pub start: usize,
149 pub stop: usize,
150 pub kind: Kind,
151}
152
153pub struct Layout {
156 pub seq_len: usize,
157 pub segments: Vec<Segment>,
158 pub pos: Vec<[f64; 3]>,
160 pub text_len: usize,
161 pub audio_t: usize,
162 pub latent_t: usize,
163 pub lat_h: usize,
164 pub lat_w: usize,
165 pub frame_rows: usize,
167 pub text_tags: Vec<u8>,
170}
171
172#[derive(Clone, Debug)]
174pub enum Ref {
175 Image { lat_h: usize, lat_w: usize },
177 Audio { t: usize },
179 Video {
182 latent_t: usize,
183 lat_h: usize,
184 lat_w: usize,
185 audio_t: usize,
186 },
187}
188
189fn push_audio_grid(pos: &mut Vec<[f64; 3]>, cursor: f64, t: usize, w_low: f64, w_high: f64) {
193 for ch in 0..2 {
194 let w = if ch == 0 { w_low } else { w_high };
195 for i in 0..t {
196 pos.push([cursor + i as f64, 0.0, w]);
197 }
198 }
199}
200
201fn axis_from_sqrt_area(dim: usize, patch: usize, sqrt_area: f64) -> Vec<f64> {
203 let ratio = dim as f64 / sqrt_area;
204 let n = dim / patch;
205 (0..n)
206 .map(|i| (i as f64 * (ratio / n as f64) + (1.0 - ratio) / 2.0) * 32.0)
207 .collect()
208}
209
210impl Layout {
211 pub fn t2va(
215 text_len: usize,
216 latent_t: usize,
217 lat_h: usize,
218 lat_w: usize,
219 audio_t: usize,
220 ) -> Self {
221 Self::build(text_len, latent_t, lat_h, lat_w, audio_t, &[], &[])
222 }
223
224 pub fn fl2va(
234 text_len: usize,
235 latent_t: usize,
236 lat_h: usize,
237 lat_w: usize,
238 audio_t: usize,
239 frames: &[(usize, usize)],
240 text_tags: &[u8],
241 ) -> Self {
242 Self::build(text_len, latent_t, lat_h, lat_w, audio_t, frames, text_tags)
243 }
244
245 pub fn ref2va(
250 text_len: usize,
251 latent_t: usize,
252 lat_h: usize,
253 lat_w: usize,
254 audio_t: usize,
255 refs: &[Ref],
256 text_tags: &[u8],
257 ) -> Self {
258 Self::build_full(
259 text_len,
260 latent_t,
261 lat_h,
262 lat_w,
263 audio_t,
264 &[],
265 refs,
266 text_tags,
267 )
268 }
269
270 fn build(
271 text_len: usize,
272 latent_t: usize,
273 lat_h: usize,
274 lat_w: usize,
275 audio_t: usize,
276 frames: &[(usize, usize)],
277 text_tags: &[u8],
278 ) -> Self {
279 Self::build_full(
280 text_len,
281 latent_t,
282 lat_h,
283 lat_w,
284 audio_t,
285 frames,
286 &[],
287 text_tags,
288 )
289 }
290
291 #[allow(clippy::too_many_arguments)]
292 fn build_full(
293 text_len: usize,
294 latent_t: usize,
295 lat_h: usize,
296 lat_w: usize,
297 audio_t: usize,
298 frames: &[(usize, usize)],
299 refs: &[Ref],
300 text_tags: &[u8],
301 ) -> Self {
302 let area = ((lat_h * lat_w) as f64).sqrt();
303 let h_axis = axis_from_sqrt_area(lat_h, 2, area);
304 let w_axis = axis_from_sqrt_area(lat_w, 2, area);
305 let frame_rows = h_axis.len() * w_axis.len();
306
307 let mut pos: Vec<[f64; 3]> = Vec::new();
308 let mut segments = Vec::new();
309
310 segments.push(Segment {
311 start: 0,
312 stop: text_len,
313 kind: Kind::Text,
314 });
315 for i in 0..text_len {
316 pos.push([i as f64, 0.0, 0.0]);
317 }
318
319 let mut cursor = text_len as f64;
323 let (w_low_t, w_high_t) = (w_axis[0], w_axis[w_axis.len() - 1]);
324
325 for r in refs {
326 match r {
327 Ref::Image { lat_h, lat_w } => {
328 let (rh, rw) = (
329 axis_from_sqrt_area(*lat_h, 2, ((lat_h * lat_w) as f64).sqrt()),
330 axis_from_sqrt_area(*lat_w, 2, ((lat_h * lat_w) as f64).sqrt()),
331 );
332 let start = pos.len();
333 for &h in &rh {
334 for &w in &rw {
335 pos.push([cursor, h, w]);
336 }
337 }
338 segments.push(Segment {
339 start,
340 stop: pos.len(),
341 kind: Kind::RefImg,
342 });
343 cursor += 1.0;
344 }
345 Ref::Audio { t } => {
346 if *t > 0 {
347 let start = pos.len();
348 push_audio_grid(&mut pos, cursor, *t, w_low_t, w_high_t);
349 segments.push(Segment {
350 start,
351 stop: pos.len(),
352 kind: Kind::RefAudio,
353 });
354 }
355 cursor += *t as f64;
356 }
357 Ref::Video {
358 latent_t: vt,
359 lat_h: rh_,
360 lat_w: rw_,
361 audio_t: rt,
362 } => {
363 let area = ((rh_ * rw_) as f64).sqrt();
364 let rh = axis_from_sqrt_area(*rh_, 2, area);
365 let rw = axis_from_sqrt_area(*rw_, 2, area);
366 if *rt > 0 {
370 let start = pos.len();
371 push_audio_grid(&mut pos, cursor, *rt, rw[0], rw[rw.len() - 1]);
372 segments.push(Segment {
373 start,
374 stop: pos.len(),
375 kind: Kind::RefAudio,
376 });
377 }
378 let start = pos.len();
379 let mut t_coord = cursor;
380 for k in 0..*vt {
381 for &h in &rh {
382 for &w in &rw {
383 pos.push([t_coord, h, w]);
384 }
385 }
386 t_coord += FRAME_RESCALE * FRAME_PER_TOKEN[k % 5];
387 }
388 segments.push(Segment {
389 start,
390 stop: pos.len(),
391 kind: Kind::RefImg,
392 });
393 let spans: f64 = (0..*vt)
394 .map(|k| FRAME_RESCALE * FRAME_PER_TOKEN[k % 5])
395 .sum();
396 cursor += (*rt as f64).max(spans);
397 }
398 }
399 }
400 let cursor = cursor;
401
402 let spans: f64 = (0..latent_t)
404 .map(|k| FRAME_RESCALE * FRAME_PER_TOKEN[k % 5])
405 .sum();
406 for &(pixel_index, frame_count) in frames {
407 let cond_t = if pixel_index == 0 {
408 cursor
409 } else if frame_count > 0 && pixel_index == frame_count - 1 {
410 cursor + spans - FRAME_RESCALE
411 } else {
412 panic!("only the first and last frame can anchor a keyframe");
413 };
414 let start = pos.len();
415 for &h in &h_axis {
416 for &w in &w_axis {
417 pos.push([cond_t, h, w]);
418 }
419 }
420 segments.push(Segment {
421 start,
422 stop: pos.len(),
423 kind: Kind::Cond,
424 });
425 }
426
427 let a_start = pos.len();
431 push_audio_grid(&mut pos, cursor, audio_t, w_low_t, w_high_t);
432 segments.push(Segment {
433 start: a_start,
434 stop: pos.len(),
435 kind: Kind::Audio,
436 });
437
438 let v_start = pos.len();
441 let mut t_coord = cursor;
442 for k in 0..latent_t {
443 for &h in &h_axis {
444 for &w in &w_axis {
445 pos.push([t_coord, h, w]);
446 }
447 }
448 t_coord += FRAME_RESCALE * FRAME_PER_TOKEN[k % 5];
449 }
450 segments.push(Segment {
451 start: v_start,
452 stop: pos.len(),
453 kind: Kind::Video,
454 });
455
456 Self {
457 seq_len: pos.len(),
458 segments,
459 pos,
460 text_len,
461 audio_t,
462 latent_t,
463 lat_h,
464 lat_w,
465 frame_rows,
466 text_tags: if text_tags.is_empty() {
467 vec![TAG_TEXT as u8; text_len]
468 } else {
469 text_tags.to_vec()
470 },
471 }
472 }
473
474 pub fn cond_rows(&self) -> usize {
476 self.segments
477 .iter()
478 .filter(|s| s.kind == Kind::Cond)
479 .map(|s| s.stop - s.start)
480 .sum()
481 }
482
483 fn segment(&self, kind: Kind) -> Segment {
484 *self
485 .segments
486 .iter()
487 .find(|s| s.kind == kind)
488 .expect("every layout carries all three streams")
489 }
490}
491
492struct Adaln {
495 w: Proj,
497 b: Vec<f32>,
498 table: Vec<f32>,
500 rank: usize,
501 out: usize,
502}
503
504impl Adaln {
505 fn load(model: &Arc<CmfModel>, prefix: &str) -> Result<Self, String> {
506 let w = Proj::from_model(model, &format!("{prefix}.weight"))?;
507 let table = crate::dit::cmf_f32(model, &format!("{prefix}.table"))?;
508 let b = crate::dit::cmf_f32(model, &format!("{prefix}.bias"))?;
509 let out = w.rows();
510 let rank = table.len() / CURVE_GRID;
511 Ok(Self {
512 w,
513 b,
514 table,
515 rank,
516 out,
517 })
518 }
519
520 fn eval(&self, ts: &[f64], pool: Option<&Pool>) -> Vec<f32> {
524 let g = CURVE_GRID;
525 let mut coords = vec![0f32; ts.len() * self.rank];
526 for (i, &t) in ts.iter().enumerate() {
527 let p = (t.clamp(0.0, 1.0) * (g - 1) as f64) as f32;
531 let i0 = (p.floor() as usize).min(g - 2);
532 let f = p - i0 as f32;
533 for k in 0..self.rank {
534 let (a, b) = (
535 self.table[i0 * self.rank + k],
536 self.table[(i0 + 1) * self.rank + k],
537 );
538 coords[i * self.rank + k] = a + (b - a) * f;
539 }
540 }
541 let mut out = vec![0f32; ts.len() * self.out];
542 self.w.matmat(&coords, ts.len(), &mut out, pool);
543 for row in out.chunks_exact_mut(self.out) {
544 for (v, &bv) in row.iter_mut().zip(&self.b) {
545 *v += bv;
546 }
547 }
548 out
549 }
550}
551
552struct Block {
553 norm1: Vec<f32>,
554 norm2: Vec<f32>,
555 qkv: Proj, out: Proj, q_norm: Vec<f32>,
558 k_norm: Vec<f32>,
559 fc1: Proj, fc2: Proj, adaln: Option<Adaln>,
562}
563
564pub(crate) const CURVE_GRID: usize = 1025;
565
566pub struct MiniMaxH3 {
567 video_patch: Proj,
568 video_patch_b: Vec<f32>,
569 audio_patch: Proj,
570 audio_patch_b: Vec<f32>,
571 condition: Proj,
572 condition_b: Vec<f32>,
573 refiner: Vec<Block>,
574 refiner_norm: Vec<f32>,
575 blocks: Vec<Block>,
576 final_norm: Vec<f32>,
577 final_adaln: Adaln,
578 video_out: Proj,
579 video_out_b: Vec<f32>,
580 audio_out: Proj,
581 audio_out_b: Vec<f32>,
582 inv_freq: Vec<f32>,
583 pool: Option<Arc<Pool>>,
584 pub hidden: usize,
585 pub heads: usize,
586 pub head_dim: usize,
587 pub ffn: usize,
588 pub latents_dim: usize,
589 pub audio_dim: usize,
590 pub text_dim: usize,
591 pub shift_video: f64,
592 pub shift_audio: f64,
593 eps: f64,
594 qk_eps: f64,
595 final_eps: f64,
596 pub cond_aug: f64,
600 pub cond_aug_audio: f64,
603}
604
605fn rms_norm_into(x: &[f32], w: &[f32], eps: f64, dst: &mut [f32]) {
606 let ss = x.iter().map(|&v| (v as f64) * (v as f64)).sum::<f64>() / x.len() as f64;
607 let inv = 1.0 / (ss + eps).sqrt();
608 for ((d, &v), &g) in dst.iter_mut().zip(x).zip(w) {
609 *d = (v as f64 * inv) as f32 * g;
610 }
611}
612
613fn silu(v: f32) -> f32 {
614 v / (1.0 + (-v).exp())
615}
616
617impl MiniMaxH3 {
618 pub fn from_cmf(model: &Arc<CmfModel>) -> Result<Self, String> {
619 let cfg: serde_json::Value = serde_json::from_slice(
620 model
621 .tensor_bytes("dit.config_json")
622 .map_err(|e| e.to_string())?,
623 )
624 .map_err(|e| format!("dit.config_json: {e}"))?;
625 let u = |k: &str| cfg[k].as_u64().unwrap_or(0) as usize;
626 let f = |k: &str, d: f64| cfg[k].as_f64().unwrap_or(d);
627 let n_blocks = u("num_layers");
628 let n_refiner = u("token_refiner_num_layers");
629 let f32v = |n: &str| crate::dit::cmf_f32(model, n);
630
631 let load_block = |prefix: &str, with_adaln: bool| -> Result<Block, String> {
632 Ok(Block {
633 norm1: f32v(&format!("dit.{prefix}.norm1.weight"))?,
634 norm2: f32v(&format!("dit.{prefix}.norm2.weight"))?,
635 qkv: Proj::from_model(model, &format!("dit.{prefix}.attn.qkv_proj.weight"))?,
636 out: Proj::from_model(model, &format!("dit.{prefix}.attn.out_proj.weight"))?,
637 q_norm: f32v(&format!("dit.{prefix}.attn.q_norm.weight"))?,
638 k_norm: f32v(&format!("dit.{prefix}.attn.k_norm.weight"))?,
639 fc1: Proj::from_model(model, &format!("dit.{prefix}.mlp.fc1.weight"))?,
640 fc2: Proj::from_model(model, &format!("dit.{prefix}.mlp.fc2.weight"))?,
641 adaln: if with_adaln {
642 Some(Adaln::load(model, &format!("dit.{prefix}.adaln"))?)
643 } else {
644 None
645 },
646 })
647 };
648 let blocks = (0..n_blocks)
649 .map(|i| load_block(&format!("blocks.{i}"), true))
650 .collect::<Result<Vec<_>, _>>()?;
651 let refiner = (0..n_refiner)
652 .map(|i| load_block(&format!("token_refiner.blocks.{i}"), false))
653 .collect::<Result<Vec<_>, _>>()?;
654
655 Ok(Self {
656 video_patch: Proj::from_model(model, "dit.video_patch_proj.weight")?,
657 video_patch_b: f32v("dit.video_patch_proj.bias")?,
658 audio_patch: Proj::from_model(model, "dit.audio_patch_proj.weight")?,
659 audio_patch_b: f32v("dit.audio_patch_proj.bias")?,
660 condition: Proj::from_model(model, "dit.condition_proj.weight")?,
661 condition_b: f32v("dit.condition_proj.bias")?,
662 refiner,
663 refiner_norm: f32v("dit.token_refiner.final_norm.weight")?,
664 blocks,
665 final_norm: f32v("dit.final_layer.norm.weight")?,
666 final_adaln: Adaln::load(model, "dit.final_layer.adaln")?,
667 video_out: Proj::from_model(model, "dit.final_layer.video_out.weight")?,
668 video_out_b: f32v("dit.final_layer.video_out.bias")?,
669 audio_out: Proj::from_model(model, "dit.final_layer.audio_out.weight")?,
670 audio_out_b: f32v("dit.final_layer.audio_out.bias")?,
671 inv_freq: f32v("dit.rope_inv_freq")?,
672 pool: Pool::from_env(),
673 hidden: u("hidden_size"),
674 heads: u("num_attention_heads"),
675 head_dim: u("attention_head_dim"),
676 ffn: u("ffn_hidden_size"),
677 latents_dim: u("latents_dim"),
678 audio_dim: u("audio_latents_dim"),
679 text_dim: u("text_dim"),
680 shift_video: f("sigma_shift_video", 12.0),
681 shift_audio: f("sigma_shift_audio", 3.0),
682 eps: f("norm_eps", 1e-5),
683 qk_eps: f("qk_norm_eps", 1e-5),
684 final_eps: f("final_norm_eps", 1e-5),
685 cond_aug: VISUAL_COND_TIMESTEP,
686 cond_aug_audio: AUDIO_COND_TIMESTEP,
687 })
688 }
689
690 pub fn refine_text(&self, states: &[f32], n: usize) -> Vec<f32> {
694 let pool = self.pool.as_deref();
695 let mut h = vec![0f32; n * self.hidden];
696 self.condition.matmat(states, n, &mut h, pool);
697 for row in h.chunks_exact_mut(self.hidden) {
698 for (v, &b) in row.iter_mut().zip(&self.condition_b) {
699 *v += b;
700 }
701 }
702 let ids: Vec<[f64; 3]> = Vec::new();
703 for blk in &self.refiner {
704 self.block_forward(blk, &mut h, n, None, &ids, &[]);
705 }
706 let mut out = vec![0f32; n * self.hidden];
707 for (o, x) in out
708 .chunks_exact_mut(self.hidden)
709 .zip(h.chunks_exact(self.hidden))
710 {
711 rms_norm_into(x, &self.refiner_norm, self.final_eps, o);
712 }
713 out
714 }
715
716 fn rope_angles(&self, pos: &[[f64; 3]]) -> Vec<f32> {
720 let k = self.inv_freq.len();
721 let mut out = Vec::with_capacity(pos.len() * 3 * k);
722 for p in pos {
723 for axis in 0..3 {
724 for j in 0..k {
725 out.push((p[axis] * self.inv_freq[j] as f64) as f32);
726 }
727 }
728 }
729 out
730 }
731
732 #[allow(clippy::too_many_arguments)]
743 fn norm_rope_w(
744 &self,
745 v: &mut [f32],
746 n: usize,
747 heads: usize,
748 w: &[f32],
749 angles: &[f32],
750 stride: usize,
751 off: usize,
752 ) {
753 let hd = self.head_dim;
754 let pairs = if angles.is_empty() {
755 0
756 } else {
757 angles.len() / n
758 };
759 let pool = self.pool.as_deref();
760 let ptr = SendPtr(v.as_mut_ptr());
761 let work = |lo: usize, hi: usize| {
762 for p in lo..hi {
763 for h in 0..heads {
764 let x = unsafe { ptr.row(p * stride + off + h * hd, hd) };
767 let ss = x.iter().map(|&a| (a as f64) * (a as f64)).sum::<f64>() / hd as f64;
768 let inv = 1.0 / (ss + self.qk_eps).sqrt();
769 for (d, &g) in x.iter_mut().zip(w) {
770 *d = (*d as f64 * inv) as f32 * g;
771 }
772 for j in 0..pairs {
773 let a = angles[p * pairs + j];
774 let (s, c) = a.sin_cos();
775 let (lo_v, hi_v) = (x[j], x[j + pairs]);
776 x[j] = lo_v * c - hi_v * s;
777 x[j + pairs] = lo_v * s + hi_v * c;
778 }
779 }
780 }
781 };
782 match pool {
783 Some(pl) => pl.run_rows(n, &work),
784 None => work(0, n),
785 }
786 }
787
788 fn attention(
794 &self,
795 qkv: &[f32],
796 n: usize,
797 attn: &mut [f32],
798 nr: Option<(&[f32], &[f32], &[f32], f32)>,
799 ) {
800 let t_repack = std::time::Instant::now();
801 let (nh, hd) = (self.heads, self.head_dim);
802 let inner = nh * hd;
803 let scale = 1.0 / (hd as f32).sqrt();
804 let pool = self.pool.as_deref();
805 if std::env::var("CMF_MMH3_ATTN").as_deref() != Ok("cpu")
813 && crate::gpu::enabled_here()
814 && n >= 256
815 {
816 if std::env::var("CMF_MMH3_ATTN").as_deref() != Ok("repack")
829 && crate::gpu::dit_attention_packed(qkv, nh, n, hd, scale, nr, attn)
830 {
831 return;
837 }
838 assert!(
839 nr.is_none(),
840 "device qk-norm was requested but the device path refused; \
841 the host loops below expect q/k already normalized"
842 );
843 let mut qh = vec![0f32; nh * n * hd];
844 let mut kh = vec![0f32; nh * n * hd];
845 let mut vh = vec![0f32; nh * n * hd];
846 {
847 let (pq, pk, pv) = (
848 SendPtr(qh.as_mut_ptr()),
849 SendPtr(kh.as_mut_ptr()),
850 SendPtr(vh.as_mut_ptr()),
851 );
852 pool_rows(pool, n, &|lo, hi| {
853 for p in lo..hi {
854 let base = p * 3 * inner;
855 for h in 0..nh {
856 let dst = (h * n + p) * hd;
857 unsafe {
860 pq.row(dst, hd)
861 .copy_from_slice(&qkv[base + h * hd..base + (h + 1) * hd]);
862 pk.row(dst, hd).copy_from_slice(
863 &qkv[base + inner + h * hd..base + inner + (h + 1) * hd],
864 );
865 pv.row(dst, hd).copy_from_slice(
866 &qkv[base + 2 * inner + h * hd
867 ..base + 2 * inner + (h + 1) * hd],
868 );
869 }
870 }
871 }
872 });
873 }
874 Self::prof(2, t_repack);
881 if crate::gpu::dit_attention(&qh, &kh, &vh, nh, nh, n, hd, scale, attn) {
884 return;
885 }
886 }
887 let mut qh = vec![0f32; n * hd];
888 let mut kh = vec![0f32; n * hd];
889 let mut vt = vec![0f32; hd * n];
890 let mut scores = vec![0f32; n * n];
891 let mut oh = vec![0f32; n * hd];
892 for h in 0..nh {
893 for p in 0..n {
894 let base = p * 3 * inner;
895 let qs = &qkv[base + h * hd..base + (h + 1) * hd];
896 for (d, &val) in qs.iter().enumerate() {
897 qh[p * hd + d] = val * scale;
898 }
899 kh[p * hd..(p + 1) * hd]
900 .copy_from_slice(&qkv[base + inner + h * hd..base + inner + (h + 1) * hd]);
901 let vs = &qkv[base + 2 * inner + h * hd..base + 2 * inner + (h + 1) * hd];
902 for (d, &val) in vs.iter().enumerate() {
903 vt[d * n + p] = val;
904 }
905 }
906 crate::fcd_ops::gemm_nt(&qh, &kh, &mut scores, n, hd, n, pool);
907 let sp = SendPtr(scores.as_mut_ptr());
908 let soft = |lo: usize, hi: usize| {
909 for r in lo..hi {
910 softmax_inplace(unsafe { sp.row(r * n, n) });
912 }
913 };
914 match pool {
915 Some(pl) => pl.run_rows(n, &soft),
916 None => soft(0, n),
917 }
918 crate::fcd_ops::gemm_nt(&scores, &vt, &mut oh, n, n, hd, pool);
919 for p in 0..n {
920 attn[p * inner + h * hd..p * inner + (h + 1) * hd]
921 .copy_from_slice(&oh[p * hd..(p + 1) * hd]);
922 }
923 }
924 }
925
926 fn prof(slot: usize, t: std::time::Instant) {
930 if !mmh3_prof_on() {
931 return;
932 }
933 MMH3_PROF[slot].fetch_add(
934 t.elapsed().as_micros() as u64,
935 std::sync::atomic::Ordering::Relaxed,
936 );
937 }
938
939 fn block_forward(
943 &self,
944 blk: &Block,
945 x: &mut [f32],
946 n: usize,
947 mods: Option<&[f32]>,
948 pos: &[[f64; 3]],
949 rows: &[u32],
950 ) {
951 let hs = self.hidden;
952 let pool = self.pool.as_deref();
953 let inner = self.heads * self.head_dim;
954 let angles = if pos.is_empty() {
955 Vec::new()
956 } else {
957 self.rope_angles(pos)
958 };
959
960 let t = std::time::Instant::now();
961 let mut xn = vec![0f32; n * hs];
962 self.norm_rows(&mut xn, x, n, hs, &blk.norm1, self.eps);
963 if let (Some(m), false) = (mods, rows.is_empty()) {
964 self.modulate(&mut xn, hs, m, rows, 0, 1);
965 }
966 Self::prof(0, t);
967 let t = std::time::Instant::now();
968 let t_qkv = std::time::Instant::now();
975 let qk_gpu = std::env::var("CMF_MMH3_QKNORM").as_deref() != Ok("cpu")
981 && crate::gpu::enabled_here()
982 && crate::gpu::dit_attention_packed_available()
983 && n >= 256;
984 if qk_gpu && std::env::var("CMF_MMH3_FUSEQKV").as_deref() != Ok("0") {
985 let fuse_out = std::env::var("CMF_MMH3_FUSEOUT").as_deref() != Ok("0");
990 if std::env::var("CMF_GPU_DEBUG").is_ok() {
991 static ONCE: std::sync::Once = std::sync::Once::new();
992 ONCE.call_once(|| {
993 eprintln!(
994 "mmh3 fuse-out gate: qkv_q={} out_q={} qkv_map={} out_map={}",
995 matches!(&blk.qkv, Proj::Q(_)),
996 matches!(&blk.out, Proj::Q(_)),
997 matches!(&blk.qkv, Proj::Q(q) if q.mapped_q4tp().is_some()),
998 matches!(&blk.out, Proj::Q(o) if o.mapped_q4tp().is_some()),
999 )
1000 });
1001 }
1002 if let (true, Proj::Q(q), Proj::Q(o)) = (fuse_out, &blk.qkv, &blk.out) {
1003 if let (Some((m, i)), Some((_, oi))) = (q.mapped_q4tp(), o.mapped_q4tp()) {
1004 let mut proj = vec![0f32; n * hs];
1005 if crate::gpu::dit_qkv_attn_out(
1006 m,
1007 i,
1008 oi,
1009 &xn,
1010 n,
1011 hs,
1012 self.heads,
1013 self.head_dim,
1014 1.0 / (self.head_dim as f32).sqrt(),
1015 (
1016 &angles[..],
1017 &blk.q_norm[..],
1018 &blk.k_norm[..],
1019 self.qk_eps as f32,
1020 ),
1021 &mut proj,
1022 ) {
1023 Self::prof(1, t_qkv);
1024 let t_res = std::time::Instant::now();
1025 self.residual(x, hs, &proj, mods, rows, 2);
1026 Self::prof(8, t_res);
1027 self.ffn_tail(blk, x, n, mods, rows);
1028 return;
1029 }
1030 }
1031 }
1032 if let Proj::Q(q) = &blk.qkv {
1033 if let Some((m, i)) = q.mapped_q4tp() {
1034 let mut attn = vec![0f32; n * inner];
1035 if crate::gpu::dit_qkv_attention(
1036 m,
1037 i,
1038 &xn,
1039 n,
1040 hs,
1041 self.heads,
1042 self.head_dim,
1043 1.0 / (self.head_dim as f32).sqrt(),
1044 (
1045 &angles[..],
1046 &blk.q_norm[..],
1047 &blk.k_norm[..],
1048 self.qk_eps as f32,
1049 ),
1050 &mut attn,
1051 ) {
1052 Self::prof(1, t_qkv);
1058 let t_out = std::time::Instant::now();
1059 let mut proj = vec![0f32; n * hs];
1060 blk.out.matmat(&attn, n, &mut proj, pool);
1061 Self::prof(4, t_out);
1062 let t_res = std::time::Instant::now();
1063 self.residual(x, hs, &proj, mods, rows, 2);
1064 Self::prof(8, t_res);
1065 self.ffn_tail(blk, x, n, mods, rows);
1066 return;
1067 }
1068 }
1069 }
1070 }
1071 let mut qkv = vec![0f32; n * 3 * inner];
1072 blk.qkv.matmat(&xn, n, &mut qkv, pool);
1073 Self::prof(1, t);
1074 let qk_on_gpu = qk_gpu;
1087 let t = std::time::Instant::now();
1088 if !qk_on_gpu {
1089 for (which, w) in [(0usize, &blk.q_norm), (1usize, &blk.k_norm)] {
1090 self.norm_rope_w(
1091 &mut qkv,
1092 n,
1093 self.heads,
1094 w,
1095 &angles,
1096 3 * inner,
1097 which * inner,
1098 );
1099 }
1100 }
1101 Self::prof(2, t);
1102 let t = std::time::Instant::now();
1103 let mut attn = vec![0f32; n * inner];
1104 self.attention(
1105 &qkv,
1106 n,
1107 &mut attn,
1108 qk_on_gpu.then_some((
1109 &angles[..],
1110 &blk.q_norm[..],
1111 &blk.k_norm[..],
1112 self.qk_eps as f32,
1113 )),
1114 );
1115 Self::prof(3, t);
1116 let t = std::time::Instant::now();
1117 let mut proj = vec![0f32; n * hs];
1118 blk.out.matmat(&attn, n, &mut proj, pool);
1119 Self::prof(4, t);
1120 let t = std::time::Instant::now();
1124 self.residual(x, hs, &proj, mods, rows, 2);
1125 Self::prof(8, t);
1126
1127 self.ffn_tail(blk, x, n, mods, rows);
1128 }
1129
1130 fn ffn_tail(&self, blk: &Block, x: &mut [f32], n: usize, mods: Option<&[f32]>, rows: &[u32]) {
1135 let hs = self.hidden;
1136 let pool = self.pool.as_deref();
1137 let mut xn = vec![0f32; n * hs];
1138 let mut proj = vec![0f32; n * hs];
1139 let t = std::time::Instant::now();
1140 self.norm_rows(&mut xn, x, n, hs, &blk.norm2, self.eps);
1141 if let (Some(m), false) = (mods, rows.is_empty()) {
1142 self.modulate(&mut xn, hs, m, rows, 3, 4);
1143 }
1144 Self::prof(0, t);
1145 let t = std::time::Instant::now();
1146 if std::env::var("CMF_MMH3_FFN").as_deref() != Ok("cpu")
1151 && crate::gpu::enabled_here()
1152 && n >= 64
1153 {
1154 if let (Proj::Q(q1), Proj::Q(q2)) = (&blk.fc1, &blk.fc2) {
1155 if let (Some((m, i1)), Some((_, i2))) = (q1.mapped_q4tp(), q2.mapped_q4tp()) {
1156 let mut fout = vec![0f32; n * hs];
1157 if crate::gpu::q4tp_ffn_packed(m, i1, i2, &xn, n, hs, self.ffn, None, &mut fout)
1158 {
1159 Self::prof(5, t);
1160 self.residual(x, hs, &fout, mods, rows, 5);
1161 return;
1162 }
1163 }
1164 }
1165 }
1166 let mut gu = vec![0f32; n * 2 * self.ffn];
1167 blk.fc1.matmat(&xn, n, &mut gu, pool);
1168 Self::prof(5, t);
1169 let t = std::time::Instant::now();
1170 let ffn = self.ffn;
1172 let mut act = vec![0f32; n * ffn];
1173 let ap = SendPtr(act.as_mut_ptr());
1174 pool_rows(pool, n, &|lo, hi| {
1175 for p in lo..hi {
1176 let row = &gu[p * 2 * ffn..(p + 1) * 2 * ffn];
1177 let (g, up) = row.split_at(ffn);
1178 for (o, (&a, &b)) in unsafe { ap.row(p * ffn, ffn) }
1180 .iter_mut()
1181 .zip(g.iter().zip(up))
1182 {
1183 *o = silu(a) * b;
1184 }
1185 }
1186 });
1187 Self::prof(6, t);
1188 let t = std::time::Instant::now();
1189 blk.fc2.matmat(&act, n, &mut proj, pool);
1190 Self::prof(7, t);
1191 self.residual(x, hs, &proj, mods, rows, 5);
1192 }
1193
1194 fn norm_rows(&self, dst: &mut [f32], src: &[f32], n: usize, hs: usize, w: &[f32], eps: f64) {
1199 let ptr = SendPtr(dst.as_mut_ptr());
1200 pool_rows(self.pool.as_deref(), n, &|lo, hi| {
1201 for p in lo..hi {
1202 rms_norm_into(&src[p * hs..(p + 1) * hs], w, eps, unsafe {
1204 ptr.row(p * hs, hs)
1205 });
1206 }
1207 });
1208 }
1209
1210 fn modulate(
1212 &self,
1213 x: &mut [f32],
1214 hs: usize,
1215 mods: &[f32],
1216 rows: &[u32],
1217 shift_e: usize,
1218 scale_e: usize,
1219 ) {
1220 let stride = EXPAND * hs;
1221 let ptr = SendPtr(x.as_mut_ptr());
1222 pool_rows(self.pool.as_deref(), rows.len(), &|lo, hi| {
1223 for p in lo..hi {
1224 let base = rows[p] as usize * stride;
1225 let shift = &mods[base + shift_e * hs..base + (shift_e + 1) * hs];
1226 let scale = &mods[base + scale_e * hs..base + (scale_e + 1) * hs];
1227 for ((v, &sc), &sh) in unsafe { ptr.row(p * hs, hs) }
1229 .iter_mut()
1230 .zip(scale)
1231 .zip(shift)
1232 {
1233 *v = *v * (1.0 + sc) + sh;
1234 }
1235 }
1236 });
1237 }
1238
1239 fn residual(
1242 &self,
1243 x: &mut [f32],
1244 hs: usize,
1245 other: &[f32],
1246 mods: Option<&[f32]>,
1247 rows: &[u32],
1248 gate_e: usize,
1249 ) {
1250 let n = x.len() / hs;
1251 let stride = EXPAND * hs;
1252 let ptr = SendPtr(x.as_mut_ptr());
1253 let gated = mods.filter(|_| !rows.is_empty());
1254 pool_rows(self.pool.as_deref(), n, &|lo, hi| {
1255 for p in lo..hi {
1256 let row = unsafe { ptr.row(p * hs, hs) };
1258 let src = &other[p * hs..(p + 1) * hs];
1259 match gated {
1260 Some(m) => {
1261 let base = rows[p] as usize * stride;
1262 let gate = &m[base + gate_e * hs..base + (gate_e + 1) * hs];
1263 for ((v, &g), &o) in row.iter_mut().zip(gate).zip(src) {
1264 *v += g * o;
1265 }
1266 }
1267 None => {
1268 for (v, &o) in row.iter_mut().zip(src) {
1269 *v += o;
1270 }
1271 }
1272 }
1273 }
1274 });
1275 }
1276
1277 pub fn forward(
1284 &self,
1285 layout: &Layout,
1286 text: &[f32],
1287 video: &[f32],
1288 audio: &[f32],
1289 sigma_v: f64,
1290 cond: &[Vec<f32>],
1291 ) -> (Vec<f32>, Vec<f32>) {
1292 let hs = self.hidden;
1293 let pool = self.pool.as_deref();
1294 let sigma_v = sigma_v.max(1e-6);
1295 let t_v = 1.0 - sigma_v;
1296 let t_a = 1.0 - time_shift_sigma(sigma_v, self.shift_video, self.shift_audio);
1297
1298 let has_cond = layout
1303 .segments
1304 .iter()
1305 .any(|s| matches!(s.kind, Kind::Cond | Kind::RefImg));
1306 let has_ref_audio = layout.segments.iter().any(|s| s.kind == Kind::RefAudio);
1307 let t_cond_a = t_a.max(self.cond_aug_audio);
1310 let t_cond = t_v.max(self.cond_aug);
1315 let mut ts = vec![t_v, t_a];
1316 if has_cond {
1317 ts.push(t_cond);
1318 }
1319 if has_ref_audio {
1320 ts.push(t_cond_a);
1321 }
1322 ts.sort_by(|a, b| a.partial_cmp(b).unwrap());
1323 ts.dedup();
1324 let row_of = |t: f64| ts.iter().position(|&x| x == t).unwrap();
1325 let (row_v, row_a) = (row_of(t_v), row_of(t_a));
1326 let row_c = if has_cond { row_of(t_cond) } else { 0 };
1327 let row_ca = if has_ref_audio { row_of(t_cond_a) } else { 0 };
1328
1329 let mut rows = vec![0u32; layout.seq_len];
1333 for s in &layout.segments {
1334 match s.kind {
1335 Kind::Text => {
1336 for (i, v) in rows[s.start..s.stop].iter_mut().enumerate() {
1337 let tag = *layout.text_tags.get(i).unwrap_or(&(TAG_TEXT as u8));
1338 *v = (row_v * MODALITIES + tag as usize) as u32;
1339 }
1340 }
1341 Kind::Cond | Kind::RefImg => {
1345 for v in rows[s.start..s.stop].iter_mut() {
1346 *v = (row_c * MODALITIES + TAG_VIDEO) as u32;
1347 }
1348 }
1349 Kind::RefAudio => {
1350 for v in rows[s.start..s.stop].iter_mut() {
1351 *v = (row_ca * MODALITIES + TAG_AUDIO) as u32;
1352 }
1353 }
1354 Kind::Video => {
1355 for v in rows[s.start..s.stop].iter_mut() {
1356 *v = (row_v * MODALITIES + TAG_VIDEO) as u32;
1357 }
1358 }
1359 Kind::Audio => {
1360 for v in rows[s.start..s.stop].iter_mut() {
1361 *v = (row_a * MODALITIES + TAG_AUDIO) as u32;
1362 }
1363 }
1364 }
1365 }
1366
1367 let v_rows = patchify_video(
1369 video,
1370 self.latents_dim,
1371 layout.latent_t,
1372 layout.lat_h,
1373 layout.lat_w,
1374 );
1375 let v_n = v_rows.len() / (self.latents_dim * 4);
1376 let a_rows = pack_audio(audio, self.audio_dim, layout.audio_t);
1377 let vd = self.latents_dim * 4;
1380 let cond_rows: Vec<Vec<f32>> = cond
1381 .iter()
1382 .enumerate()
1383 .map(|(i, z)| {
1384 let mut r = patchify_video(z, self.latents_dim, 1, layout.lat_h, layout.lat_w);
1385 if self.cond_aug < 1.0 {
1386 let noise = crate::videogen::gauss_pub(r.len(), 0x5EED ^ i as u64);
1391 let a = self.cond_aug as f32;
1392 for (v, n) in r.iter_mut().zip(&noise) {
1393 *v = a * *v + (1.0 - a) * n;
1394 }
1395 }
1396 r
1397 })
1398 .collect();
1399
1400 let mut h = vec![0f32; layout.seq_len * hs];
1401 let mut ci = 0usize;
1402 for s in layout.segments.iter().filter(|s| s.kind == Kind::Cond) {
1403 let n = s.stop - s.start;
1404 let r = cond_rows.get(ci).unwrap_or_else(|| {
1405 panic!(
1406 "layout has {} cond segments, {} latents given",
1407 ci + 1,
1408 cond_rows.len()
1409 )
1410 });
1411 self.video_patch
1412 .matmat(r, n, &mut h[s.start * hs..s.stop * hs], pool);
1413 for row in h[s.start * hs..s.stop * hs].chunks_exact_mut(hs) {
1414 for (v, &b) in row.iter_mut().zip(&self.video_patch_b) {
1415 *v += b;
1416 }
1417 }
1418 ci += 1;
1419 }
1420 let vseg = layout.segment(Kind::Video);
1421 let aseg = layout.segment(Kind::Audio);
1422 let tseg = layout.segment(Kind::Text);
1423 h[tseg.start * hs..tseg.stop * hs].copy_from_slice(&text[..(tseg.stop - tseg.start) * hs]);
1424 self.video_patch
1425 .matmat(&v_rows, v_n, &mut h[vseg.start * hs..vseg.stop * hs], pool);
1426 self.audio_patch.matmat(
1427 &a_rows,
1428 aseg.stop - aseg.start,
1429 &mut h[aseg.start * hs..aseg.stop * hs],
1430 pool,
1431 );
1432 for row in h[vseg.start * hs..vseg.stop * hs].chunks_exact_mut(hs) {
1433 for (v, &b) in row.iter_mut().zip(&self.video_patch_b) {
1434 *v += b;
1435 }
1436 }
1437 for row in h[aseg.start * hs..aseg.stop * hs].chunks_exact_mut(hs) {
1438 for (v, &b) in row.iter_mut().zip(&self.audio_patch_b) {
1439 *v += b;
1440 }
1441 }
1442
1443 for (i, blk) in self.blocks.iter().enumerate() {
1445 let mods = blk.adaln.as_ref().unwrap().eval(&ts, pool);
1446 self.block_forward(blk, &mut h, layout.seq_len, Some(&mods), &layout.pos, &rows);
1447 if let Ok(w) = std::env::var("CMF_MMH3_WATCHROW") {
1450 if let Ok(r) = w.parse::<usize>() {
1451 let row = &h[r * hs..(r + 1) * hs];
1452 let amax = row
1453 .iter()
1454 .filter(|v| v.is_finite())
1455 .fold(0f32, |a, v| a.max(v.abs()));
1456 let bad = row.iter().filter(|v| !v.is_finite()).count();
1457 if bad > 0 || i == 0 || amax > 1e3 {
1458 eprintln!(" watch blk {i}: row {r} absmax {amax:.3e} nonfinite {bad}");
1459 }
1460 }
1461 }
1462 if std::env::var_os("CMF_DIT_PROGRESS").is_some() {
1463 eprint!("\r block {}/{}", i + 1, self.blocks.len());
1464 }
1465 }
1466
1467 if std::env::var("CMF_MMH3_NANPROBE").is_ok() {
1469 let bad: Vec<usize> = (0..h.len() / hs)
1470 .filter(|r| h[r * hs..(r + 1) * hs].iter().any(|v| !v.is_finite()))
1471 .collect();
1472 if !bad.is_empty() {
1473 eprintln!(
1474 " nanprobe rows: {} of {} bad, first 8 {:?}, video seg {}..{}, audio seg {}..{}",
1475 bad.len(),
1476 h.len() / hs,
1477 &bad[..bad.len().min(8)],
1478 vseg.start,
1479 vseg.stop,
1480 aseg.start,
1481 aseg.stop
1482 );
1483 }
1484 }
1485 let fm = self.final_adaln.eval(&ts, pool);
1486 let mut video_out = vec![0f32; (vseg.stop - vseg.start) * vd];
1487 let mut audio_out = vec![0f32; (aseg.stop - aseg.start) * self.audio_dim];
1488 for (seg, row, w, b, dst, dim) in [
1489 (
1490 vseg,
1491 row_v,
1492 &self.video_out,
1493 &self.video_out_b,
1494 &mut video_out,
1495 vd,
1496 ),
1497 (
1498 aseg,
1499 row_a,
1500 &self.audio_out,
1501 &self.audio_out_b,
1502 &mut audio_out,
1503 self.audio_dim,
1504 ),
1505 ] {
1506 let n = seg.stop - seg.start;
1507 let mut hn = vec![0f32; n * hs];
1508 for (o, src) in hn
1509 .chunks_exact_mut(hs)
1510 .zip(h[seg.start * hs..seg.stop * hs].chunks_exact(hs))
1511 {
1512 rms_norm_into(src, &self.final_norm, self.final_eps, o);
1513 }
1514 let shift = &fm[row * 2 * hs..row * 2 * hs + hs];
1517 let scale = &fm[row * 2 * hs + hs..(row + 1) * 2 * hs];
1518 for r in hn.chunks_exact_mut(hs) {
1519 for ((v, &sc), &sh) in r.iter_mut().zip(scale).zip(shift) {
1520 *v = *v * (1.0 + sc) + sh;
1521 }
1522 }
1523 let probe = std::env::var("CMF_MMH3_NANPROBE").is_ok();
1528 if probe {
1529 let bad_in = hn.iter().filter(|v| !v.is_finite()).count();
1530 eprintln!(
1531 " nanprobe dim={dim} n={n} in_bad={bad_in} in_absmax={:.4}",
1532 hn.iter()
1533 .filter(|v| v.is_finite())
1534 .fold(0f32, |a, v| a.max(v.abs()))
1535 );
1536 }
1537 w.matmat(&hn, n, dst, pool);
1538 if probe {
1539 let bad_out = dst.iter().filter(|v| !v.is_finite()).count();
1540 eprintln!(
1541 " nanprobe dim={dim} out_bad={bad_out}/{} out_absmax={:.4}",
1542 dst.len(),
1543 dst.iter()
1544 .filter(|v| v.is_finite())
1545 .fold(0f32, |a, v| a.max(v.abs()))
1546 );
1547 }
1548 for r in dst.chunks_exact_mut(dim) {
1549 for (v, &bv) in r.iter_mut().zip(b.iter()) {
1550 *v += bv;
1551 }
1552 }
1553 }
1554
1555 let video = unpatchify_video(
1559 &video_out,
1560 self.latents_dim,
1561 layout.latent_t,
1562 layout.lat_h,
1563 layout.lat_w,
1564 );
1565 let audio = unpack_audio(&audio_out, self.audio_dim, layout.audio_t);
1566 (
1567 video.iter().map(|&v| -v).collect(),
1568 audio.iter().map(|&v| -v).collect(),
1569 )
1570 }
1571}
1572
1573fn pool_rows(pool: Option<&Pool>, n: usize, f: &(dyn Fn(usize, usize) + Sync)) {
1577 match pool {
1578 Some(p) => p.run_rows(n, f),
1579 None => f(0, n),
1580 }
1581}
1582
1583pub fn patchify_video(x: &[f32], c: usize, t: usize, h: usize, w: usize) -> Vec<f32> {
1588 let (ph, pw) = (h / 2, w / 2);
1589 let mut out = vec![0f32; t * ph * pw * c * 4];
1590 let mut i = 0;
1591 for ti in 0..t {
1592 for hi in 0..ph {
1593 for wi in 0..pw {
1594 for ci in 0..c {
1595 for p in 0..2 {
1596 for q in 0..2 {
1597 out[i] = x[((ci * t + ti) * h + hi * 2 + p) * w + wi * 2 + q];
1598 i += 1;
1599 }
1600 }
1601 }
1602 }
1603 }
1604 }
1605 out
1606}
1607
1608pub fn unpatchify_video(rows: &[f32], c: usize, t: usize, h: usize, w: usize) -> Vec<f32> {
1610 let (ph, pw) = (h / 2, w / 2);
1611 let mut out = vec![0f32; c * t * h * w];
1612 let mut i = 0;
1613 for ti in 0..t {
1614 for hi in 0..ph {
1615 for wi in 0..pw {
1616 for ci in 0..c {
1617 for p in 0..2 {
1618 for q in 0..2 {
1619 out[((ci * t + ti) * h + hi * 2 + p) * w + wi * 2 + q] = rows[i];
1620 i += 1;
1621 }
1622 }
1623 }
1624 }
1625 }
1626 }
1627 out
1628}
1629
1630pub fn pack_audio(x: &[f32], c: usize, t: usize) -> Vec<f32> {
1633 let mut out = vec![0f32; 2 * t * c];
1634 for ch in 0..2 {
1635 for ti in 0..t {
1636 for ci in 0..c {
1637 out[(ch * t + ti) * c + ci] = x[(ci * 2 + ch) * t + ti];
1638 }
1639 }
1640 }
1641 out
1642}
1643
1644pub fn unpack_audio(rows: &[f32], c: usize, t: usize) -> Vec<f32> {
1646 let mut out = vec![0f32; c * 2 * t];
1647 for ch in 0..2 {
1648 for ti in 0..t {
1649 for ci in 0..c {
1650 out[(ci * 2 + ch) * t + ti] = rows[(ch * t + ti) * c + ci];
1651 }
1652 }
1653 }
1654 out
1655}
1656
1657struct SendPtr(*mut f32);
1660unsafe impl Send for SendPtr {}
1661unsafe impl Sync for SendPtr {}
1662impl SendPtr {
1663 #[allow(clippy::mut_from_ref)]
1665 unsafe fn row(&self, off: usize, len: usize) -> &mut [f32] {
1666 unsafe { std::slice::from_raw_parts_mut(self.0.add(off), len) }
1667 }
1668}
1669
1670fn softmax_inplace(row: &mut [f32]) {
1671 let mx = row.iter().cloned().fold(f32::MIN, f32::max);
1672 let mut den = 0f32;
1673 for r in row.iter_mut() {
1674 *r = (*r - mx).exp();
1675 den += *r;
1676 }
1677 if den > 0.0 {
1678 let inv = 1.0 / den;
1679 for r in row.iter_mut() {
1680 *r *= inv;
1681 }
1682 }
1683}
1684
1685#[cfg(test)]
1686mod tests {
1687 use super::*;
1688
1689 #[test]
1690 fn schedule_remap_is_an_involution() {
1691 for &s in &[1e-3, 0.25, 0.5, 0.8, 0.972_973, 1.0] {
1692 let a = time_shift_sigma(s, 12.0, 3.0);
1693 let back = time_shift_sigma(a, 3.0, 12.0);
1694 assert!((back - s).abs() < 1e-9, "{s} -> {a} -> {back}");
1695 }
1696 }
1697
1698 #[test]
1699 fn slope_matches_a_finite_difference() {
1700 let h = 1e-6;
1701 for &s in &[0.2, 0.5, 0.9] {
1702 let num = (time_shift_sigma(s + h, 12.0, 3.0) - time_shift_sigma(s - h, 12.0, 3.0))
1703 / (2.0 * h);
1704 let got = time_shift_slope(s, 12.0, 3.0);
1705 assert!((num - got).abs() < 1e-5, "{s}: {num} vs {got}");
1706 }
1707 }
1708
1709 #[test]
1710 fn patchify_round_trips() {
1711 let (c, t, h, w) = (3usize, 2usize, 4usize, 6usize);
1712 let x: Vec<f32> = (0..c * t * h * w).map(|i| i as f32).collect();
1713 let rows = patchify_video(&x, c, t, h, w);
1714 assert_eq!(rows.len(), t * (h / 2) * (w / 2) * c * 4);
1715 assert_eq!(unpatchify_video(&rows, c, t, h, w), x);
1716 }
1717
1718 #[test]
1719 fn audio_pack_round_trips() {
1720 let (c, t) = (5usize, 7usize);
1721 let x: Vec<f32> = (0..c * 2 * t).map(|i| i as f32).collect();
1722 let rows = pack_audio(&x, c, t);
1723 assert_eq!(unpack_audio(&rows, c, t), x);
1724 }
1725
1726 #[test]
1727 fn keyframes_sit_between_the_text_and_the_audio() {
1728 let (tl, lt, lh, lw, at) = (8usize, 3usize, 8usize, 12usize, 5usize);
1729 let base = Layout::t2va(tl, lt, lh, lw, at);
1730 let l = Layout::fl2va(tl, lt, lh, lw, at, &[(0, 39), (38, 39)], &[]);
1732 assert_eq!(l.cond_rows(), 2 * l.frame_rows);
1733 assert_eq!(l.seq_len, base.seq_len + 2 * l.frame_rows);
1734 let kinds: Vec<_> = l.segments.iter().map(|s| s.kind).collect();
1735 assert_eq!(
1736 kinds,
1737 vec![Kind::Text, Kind::Cond, Kind::Cond, Kind::Audio, Kind::Video]
1738 );
1739 let (a0, v0) = (l.segment(Kind::Audio), l.segment(Kind::Video));
1742 let (ba, bv) = (base.segment(Kind::Audio), base.segment(Kind::Video));
1743 assert_eq!(l.pos[a0.start][0], base.pos[ba.start][0]);
1744 assert_eq!(l.pos[v0.start][0], base.pos[bv.start][0]);
1745 let c: Vec<_> = l.segments.iter().filter(|s| s.kind == Kind::Cond).collect();
1748 assert_eq!(l.pos[c[0].start][0], tl as f64);
1749 let spans: f64 = (0..lt)
1750 .map(|k| FRAME_RESCALE * FRAME_PER_TOKEN[k % 5])
1751 .sum();
1752 assert!((l.pos[c[1].start][0] - (tl as f64 + spans - FRAME_RESCALE)).abs() < 1e-12);
1753 assert_eq!(l.pos[c[0].start][1], l.pos[v0.start][1]);
1755 assert_eq!(l.pos[c[0].start][2], l.pos[v0.start][2]);
1756 }
1757
1758 #[test]
1759 fn layout_places_the_target_streams_last() {
1760 let l = Layout::t2va(8, 3, 8, 12, 5);
1761 assert_eq!(l.frame_rows, 4 * 6);
1762 assert_eq!(l.seq_len, 8 + 2 * 5 + 3 * 24);
1763 assert_eq!(l.segments[0].kind, Kind::Text);
1764 assert_eq!(l.segments[1].kind, Kind::Audio);
1765 assert_eq!(l.segments[2].kind, Kind::Video);
1766 let v = l.segment(Kind::Video);
1768 let t0 = l.pos[v.start][0];
1769 let t1 = l.pos[v.start + l.frame_rows][0];
1770 assert!((t1 - t0 - FRAME_RESCALE).abs() < 1e-12);
1771 }
1772}