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", "qkv gemm", "qknorm+rope", "attention", "out gemm", "fc1 gemm",
67 "swiglu", "fc2 gemm", "residual",
68 ];
69 let mut v: Vec<(u64, &str)> = MMH3_PROF
70 .iter()
71 .map(|a| a.load(std::sync::atomic::Ordering::Relaxed))
72 .zip(NAMES)
73 .collect();
74 let total: u64 = v.iter().map(|(us, _)| *us).sum();
75 v.sort_by(|a, b| b.0.cmp(&a.0));
76 let mut out = format!("mmh3 phases (total {:.1} s):", total as f64 / 1e6);
77 for (us, name) in v {
78 out.push_str(&format!(
79 "\n {name:<14} {:>7.1} s {:>5.1}%",
80 us as f64 / 1e6,
81 100.0 * us as f64 / total.max(1) as f64
82 ));
83 }
84 Some(out)
85}
86
87use crate::pool::Pool;
88use cortiq_core::CmfModel;
89use std::sync::Arc;
90
91const FRAME_PER_TOKEN: [f64; 5] = [1.0, 4.0, 4.0, 4.0, 4.0];
94const FRAME_RESCALE: f64 = 5.0 / 3.0;
95
96const TAG_VIDEO: usize = 0;
98const TAG_TEXT: usize = 1;
99const TAG_AUDIO: usize = 2;
100const MODALITIES: usize = 3;
101const EXPAND: usize = 6;
103const VISUAL_COND_TIMESTEP: f64 = 0.999;
105const AUDIO_COND_TIMESTEP: f64 = 1.0;
107
108pub fn time_shift_sigma(sigma: f64, from_shift: f64, to_shift: f64) -> f64 {
112 let base = sigma / (from_shift + sigma * (1.0 - from_shift));
113 to_shift * base / (1.0 + (to_shift - 1.0) * base)
114}
115
116pub fn time_shift_slope(sigma: f64, from_shift: f64, to_shift: f64) -> f64 {
118 let base = sigma / (from_shift + sigma * (1.0 - from_shift));
119 (to_shift * (1.0 + (from_shift - 1.0) * base).powi(2))
120 / (from_shift * (1.0 + (to_shift - 1.0) * base).powi(2))
121}
122
123#[derive(Clone, Copy, PartialEq, Debug)]
126pub enum Kind {
127 Text,
128 Cond,
130 RefImg,
132 RefAudio,
134 Audio,
135 Video,
136}
137
138#[derive(Clone, Copy, Debug)]
140pub struct Segment {
141 pub start: usize,
142 pub stop: usize,
143 pub kind: Kind,
144}
145
146pub struct Layout {
149 pub seq_len: usize,
150 pub segments: Vec<Segment>,
151 pub pos: Vec<[f64; 3]>,
153 pub text_len: usize,
154 pub audio_t: usize,
155 pub latent_t: usize,
156 pub lat_h: usize,
157 pub lat_w: usize,
158 pub frame_rows: usize,
160 pub text_tags: Vec<u8>,
163}
164
165#[derive(Clone, Debug)]
167pub enum Ref {
168 Image { lat_h: usize, lat_w: usize },
170 Audio { t: usize },
172 Video {
175 latent_t: usize,
176 lat_h: usize,
177 lat_w: usize,
178 audio_t: usize,
179 },
180}
181
182fn push_audio_grid(pos: &mut Vec<[f64; 3]>, cursor: f64, t: usize, w_low: f64, w_high: f64) {
186 for ch in 0..2 {
187 let w = if ch == 0 { w_low } else { w_high };
188 for i in 0..t {
189 pos.push([cursor + i as f64, 0.0, w]);
190 }
191 }
192}
193
194fn axis_from_sqrt_area(dim: usize, patch: usize, sqrt_area: f64) -> Vec<f64> {
196 let ratio = dim as f64 / sqrt_area;
197 let n = dim / patch;
198 (0..n)
199 .map(|i| (i as f64 * (ratio / n as f64) + (1.0 - ratio) / 2.0) * 32.0)
200 .collect()
201}
202
203impl Layout {
204 pub fn t2va(text_len: usize, latent_t: usize, lat_h: usize, lat_w: usize, audio_t: usize) -> Self {
208 Self::build(text_len, latent_t, lat_h, lat_w, audio_t, &[], &[])
209 }
210
211 pub fn fl2va(
221 text_len: usize,
222 latent_t: usize,
223 lat_h: usize,
224 lat_w: usize,
225 audio_t: usize,
226 frames: &[(usize, usize)],
227 text_tags: &[u8],
228 ) -> Self {
229 Self::build(text_len, latent_t, lat_h, lat_w, audio_t, frames, text_tags)
230 }
231
232 pub fn ref2va(
237 text_len: usize,
238 latent_t: usize,
239 lat_h: usize,
240 lat_w: usize,
241 audio_t: usize,
242 refs: &[Ref],
243 text_tags: &[u8],
244 ) -> Self {
245 Self::build_full(text_len, latent_t, lat_h, lat_w, audio_t, &[], refs, text_tags)
246 }
247
248 fn build(
249 text_len: usize,
250 latent_t: usize,
251 lat_h: usize,
252 lat_w: usize,
253 audio_t: usize,
254 frames: &[(usize, usize)],
255 text_tags: &[u8],
256 ) -> Self {
257 Self::build_full(text_len, latent_t, lat_h, lat_w, audio_t, frames, &[], text_tags)
258 }
259
260 #[allow(clippy::too_many_arguments)]
261 fn build_full(
262 text_len: usize,
263 latent_t: usize,
264 lat_h: usize,
265 lat_w: usize,
266 audio_t: usize,
267 frames: &[(usize, usize)],
268 refs: &[Ref],
269 text_tags: &[u8],
270 ) -> Self {
271 let area = ((lat_h * lat_w) as f64).sqrt();
272 let h_axis = axis_from_sqrt_area(lat_h, 2, area);
273 let w_axis = axis_from_sqrt_area(lat_w, 2, area);
274 let frame_rows = h_axis.len() * w_axis.len();
275
276 let mut pos: Vec<[f64; 3]> = Vec::new();
277 let mut segments = Vec::new();
278
279 segments.push(Segment { start: 0, stop: text_len, kind: Kind::Text });
280 for i in 0..text_len {
281 pos.push([i as f64, 0.0, 0.0]);
282 }
283
284 let mut cursor = text_len as f64;
288 let (w_low_t, w_high_t) = (w_axis[0], w_axis[w_axis.len() - 1]);
289
290 for r in refs {
291 match r {
292 Ref::Image { lat_h, lat_w } => {
293 let (rh, rw) = (
294 axis_from_sqrt_area(*lat_h, 2, ((lat_h * lat_w) as f64).sqrt()),
295 axis_from_sqrt_area(*lat_w, 2, ((lat_h * lat_w) as f64).sqrt()),
296 );
297 let start = pos.len();
298 for &h in &rh {
299 for &w in &rw {
300 pos.push([cursor, h, w]);
301 }
302 }
303 segments.push(Segment { start, stop: pos.len(), kind: Kind::RefImg });
304 cursor += 1.0;
305 }
306 Ref::Audio { t } => {
307 if *t > 0 {
308 let start = pos.len();
309 push_audio_grid(&mut pos, cursor, *t, w_low_t, w_high_t);
310 segments.push(Segment { start, stop: pos.len(), kind: Kind::RefAudio });
311 }
312 cursor += *t as f64;
313 }
314 Ref::Video { latent_t: vt, lat_h: rh_, lat_w: rw_, audio_t: rt } => {
315 let area = ((rh_ * rw_) as f64).sqrt();
316 let rh = axis_from_sqrt_area(*rh_, 2, area);
317 let rw = axis_from_sqrt_area(*rw_, 2, area);
318 if *rt > 0 {
322 let start = pos.len();
323 push_audio_grid(&mut pos, cursor, *rt, rw[0], rw[rw.len() - 1]);
324 segments.push(Segment { start, stop: pos.len(), kind: Kind::RefAudio });
325 }
326 let start = pos.len();
327 let mut t_coord = cursor;
328 for k in 0..*vt {
329 for &h in &rh {
330 for &w in &rw {
331 pos.push([t_coord, h, w]);
332 }
333 }
334 t_coord += FRAME_RESCALE * FRAME_PER_TOKEN[k % 5];
335 }
336 segments.push(Segment { start, stop: pos.len(), kind: Kind::RefImg });
337 let spans: f64 = (0..*vt)
338 .map(|k| FRAME_RESCALE * FRAME_PER_TOKEN[k % 5])
339 .sum();
340 cursor += (*rt as f64).max(spans);
341 }
342 }
343 }
344 let cursor = cursor;
345
346 let spans: f64 = (0..latent_t)
348 .map(|k| FRAME_RESCALE * FRAME_PER_TOKEN[k % 5])
349 .sum();
350 for &(pixel_index, frame_count) in frames {
351 let cond_t = if pixel_index == 0 {
352 cursor
353 } else if frame_count > 0 && pixel_index == frame_count - 1 {
354 cursor + spans - FRAME_RESCALE
355 } else {
356 panic!("only the first and last frame can anchor a keyframe");
357 };
358 let start = pos.len();
359 for &h in &h_axis {
360 for &w in &w_axis {
361 pos.push([cond_t, h, w]);
362 }
363 }
364 segments.push(Segment { start, stop: pos.len(), kind: Kind::Cond });
365 }
366
367 let a_start = pos.len();
371 push_audio_grid(&mut pos, cursor, audio_t, w_low_t, w_high_t);
372 segments.push(Segment { start: a_start, stop: pos.len(), kind: Kind::Audio });
373
374 let v_start = pos.len();
377 let mut t_coord = cursor;
378 for k in 0..latent_t {
379 for &h in &h_axis {
380 for &w in &w_axis {
381 pos.push([t_coord, h, w]);
382 }
383 }
384 t_coord += FRAME_RESCALE * FRAME_PER_TOKEN[k % 5];
385 }
386 segments.push(Segment { start: v_start, stop: pos.len(), kind: Kind::Video });
387
388 Self {
389 seq_len: pos.len(),
390 segments,
391 pos,
392 text_len,
393 audio_t,
394 latent_t,
395 lat_h,
396 lat_w,
397 frame_rows,
398 text_tags: if text_tags.is_empty() {
399 vec![TAG_TEXT as u8; text_len]
400 } else {
401 text_tags.to_vec()
402 },
403 }
404 }
405
406 pub fn cond_rows(&self) -> usize {
408 self.segments
409 .iter()
410 .filter(|s| s.kind == Kind::Cond)
411 .map(|s| s.stop - s.start)
412 .sum()
413 }
414
415 fn segment(&self, kind: Kind) -> Segment {
416 *self
417 .segments
418 .iter()
419 .find(|s| s.kind == kind)
420 .expect("every layout carries all three streams")
421 }
422}
423
424struct Adaln {
427 w: Proj,
429 b: Vec<f32>,
430 table: Vec<f32>,
432 rank: usize,
433 out: usize,
434}
435
436impl Adaln {
437 fn load(model: &Arc<CmfModel>, prefix: &str) -> Result<Self, String> {
438 let w = Proj::from_model(model, &format!("{prefix}.weight"))?;
439 let table = crate::dit::cmf_f32(model, &format!("{prefix}.table"))?;
440 let b = crate::dit::cmf_f32(model, &format!("{prefix}.bias"))?;
441 let out = w.rows();
442 let rank = table.len() / CURVE_GRID;
443 Ok(Self { w, b, table, rank, out })
444 }
445
446 fn eval(&self, ts: &[f64], pool: Option<&Pool>) -> Vec<f32> {
450 let g = CURVE_GRID;
451 let mut coords = vec![0f32; ts.len() * self.rank];
452 for (i, &t) in ts.iter().enumerate() {
453 let p = (t.clamp(0.0, 1.0) * (g - 1) as f64) as f32;
457 let i0 = (p.floor() as usize).min(g - 2);
458 let f = p - i0 as f32;
459 for k in 0..self.rank {
460 let (a, b) = (self.table[i0 * self.rank + k], self.table[(i0 + 1) * self.rank + k]);
461 coords[i * self.rank + k] = a + (b - a) * f;
462 }
463 }
464 let mut out = vec![0f32; ts.len() * self.out];
465 self.w.matmat(&coords, ts.len(), &mut out, pool);
466 for row in out.chunks_exact_mut(self.out) {
467 for (v, &bv) in row.iter_mut().zip(&self.b) {
468 *v += bv;
469 }
470 }
471 out
472 }
473}
474
475struct Block {
476 norm1: Vec<f32>,
477 norm2: Vec<f32>,
478 qkv: Proj, out: Proj, q_norm: Vec<f32>,
481 k_norm: Vec<f32>,
482 fc1: Proj, fc2: Proj, adaln: Option<Adaln>,
485}
486
487pub(crate) const CURVE_GRID: usize = 1025;
488
489pub struct MiniMaxH3 {
490 video_patch: Proj,
491 video_patch_b: Vec<f32>,
492 audio_patch: Proj,
493 audio_patch_b: Vec<f32>,
494 condition: Proj,
495 condition_b: Vec<f32>,
496 refiner: Vec<Block>,
497 refiner_norm: Vec<f32>,
498 blocks: Vec<Block>,
499 final_norm: Vec<f32>,
500 final_adaln: Adaln,
501 video_out: Proj,
502 video_out_b: Vec<f32>,
503 audio_out: Proj,
504 audio_out_b: Vec<f32>,
505 inv_freq: Vec<f32>,
506 pool: Option<Arc<Pool>>,
507 pub hidden: usize,
508 pub heads: usize,
509 pub head_dim: usize,
510 pub ffn: usize,
511 pub latents_dim: usize,
512 pub audio_dim: usize,
513 pub text_dim: usize,
514 pub shift_video: f64,
515 pub shift_audio: f64,
516 eps: f64,
517 qk_eps: f64,
518 final_eps: f64,
519 pub cond_aug: f64,
523 pub cond_aug_audio: f64,
526}
527
528fn rms_norm_into(x: &[f32], w: &[f32], eps: f64, dst: &mut [f32]) {
529 let ss = x.iter().map(|&v| (v as f64) * (v as f64)).sum::<f64>() / x.len() as f64;
530 let inv = 1.0 / (ss + eps).sqrt();
531 for ((d, &v), &g) in dst.iter_mut().zip(x).zip(w) {
532 *d = (v as f64 * inv) as f32 * g;
533 }
534}
535
536fn silu(v: f32) -> f32 {
537 v / (1.0 + (-v).exp())
538}
539
540impl MiniMaxH3 {
541 pub fn from_cmf(model: &Arc<CmfModel>) -> Result<Self, String> {
542 let cfg: serde_json::Value = serde_json::from_slice(
543 model.tensor_bytes("dit.config_json").map_err(|e| e.to_string())?,
544 )
545 .map_err(|e| format!("dit.config_json: {e}"))?;
546 let u = |k: &str| cfg[k].as_u64().unwrap_or(0) as usize;
547 let f = |k: &str, d: f64| cfg[k].as_f64().unwrap_or(d);
548 let n_blocks = u("num_layers");
549 let n_refiner = u("token_refiner_num_layers");
550 let f32v = |n: &str| crate::dit::cmf_f32(model, n);
551
552 let load_block = |prefix: &str, with_adaln: bool| -> Result<Block, String> {
553 Ok(Block {
554 norm1: f32v(&format!("dit.{prefix}.norm1.weight"))?,
555 norm2: f32v(&format!("dit.{prefix}.norm2.weight"))?,
556 qkv: Proj::from_model(model, &format!("dit.{prefix}.attn.qkv_proj.weight"))?,
557 out: Proj::from_model(model, &format!("dit.{prefix}.attn.out_proj.weight"))?,
558 q_norm: f32v(&format!("dit.{prefix}.attn.q_norm.weight"))?,
559 k_norm: f32v(&format!("dit.{prefix}.attn.k_norm.weight"))?,
560 fc1: Proj::from_model(model, &format!("dit.{prefix}.mlp.fc1.weight"))?,
561 fc2: Proj::from_model(model, &format!("dit.{prefix}.mlp.fc2.weight"))?,
562 adaln: if with_adaln {
563 Some(Adaln::load(model, &format!("dit.{prefix}.adaln"))?)
564 } else {
565 None
566 },
567 })
568 };
569 let blocks = (0..n_blocks)
570 .map(|i| load_block(&format!("blocks.{i}"), true))
571 .collect::<Result<Vec<_>, _>>()?;
572 let refiner = (0..n_refiner)
573 .map(|i| load_block(&format!("token_refiner.blocks.{i}"), false))
574 .collect::<Result<Vec<_>, _>>()?;
575
576 Ok(Self {
577 video_patch: Proj::from_model(model, "dit.video_patch_proj.weight")?,
578 video_patch_b: f32v("dit.video_patch_proj.bias")?,
579 audio_patch: Proj::from_model(model, "dit.audio_patch_proj.weight")?,
580 audio_patch_b: f32v("dit.audio_patch_proj.bias")?,
581 condition: Proj::from_model(model, "dit.condition_proj.weight")?,
582 condition_b: f32v("dit.condition_proj.bias")?,
583 refiner,
584 refiner_norm: f32v("dit.token_refiner.final_norm.weight")?,
585 blocks,
586 final_norm: f32v("dit.final_layer.norm.weight")?,
587 final_adaln: Adaln::load(model, "dit.final_layer.adaln")?,
588 video_out: Proj::from_model(model, "dit.final_layer.video_out.weight")?,
589 video_out_b: f32v("dit.final_layer.video_out.bias")?,
590 audio_out: Proj::from_model(model, "dit.final_layer.audio_out.weight")?,
591 audio_out_b: f32v("dit.final_layer.audio_out.bias")?,
592 inv_freq: f32v("dit.rope_inv_freq")?,
593 pool: Pool::from_env(),
594 hidden: u("hidden_size"),
595 heads: u("num_attention_heads"),
596 head_dim: u("attention_head_dim"),
597 ffn: u("ffn_hidden_size"),
598 latents_dim: u("latents_dim"),
599 audio_dim: u("audio_latents_dim"),
600 text_dim: u("text_dim"),
601 shift_video: f("sigma_shift_video", 12.0),
602 shift_audio: f("sigma_shift_audio", 3.0),
603 eps: f("norm_eps", 1e-5),
604 qk_eps: f("qk_norm_eps", 1e-5),
605 final_eps: f("final_norm_eps", 1e-5),
606 cond_aug: VISUAL_COND_TIMESTEP,
607 cond_aug_audio: AUDIO_COND_TIMESTEP,
608 })
609 }
610
611 pub fn refine_text(&self, states: &[f32], n: usize) -> Vec<f32> {
615 let pool = self.pool.as_deref();
616 let mut h = vec![0f32; n * self.hidden];
617 self.condition.matmat(states, n, &mut h, pool);
618 for row in h.chunks_exact_mut(self.hidden) {
619 for (v, &b) in row.iter_mut().zip(&self.condition_b) {
620 *v += b;
621 }
622 }
623 let ids: Vec<[f64; 3]> = Vec::new();
624 for blk in &self.refiner {
625 self.block_forward(blk, &mut h, n, None, &ids, &[]);
626 }
627 let mut out = vec![0f32; n * self.hidden];
628 for (o, x) in out.chunks_exact_mut(self.hidden).zip(h.chunks_exact(self.hidden)) {
629 rms_norm_into(x, &self.refiner_norm, self.final_eps, o);
630 }
631 out
632 }
633
634 fn rope_angles(&self, pos: &[[f64; 3]]) -> Vec<f32> {
638 let k = self.inv_freq.len();
639 let mut out = Vec::with_capacity(pos.len() * 3 * k);
640 for p in pos {
641 for axis in 0..3 {
642 for j in 0..k {
643 out.push((p[axis] * self.inv_freq[j] as f64) as f32);
644 }
645 }
646 }
647 out
648 }
649
650 #[allow(clippy::too_many_arguments)]
661 fn norm_rope_w(
662 &self,
663 v: &mut [f32],
664 n: usize,
665 heads: usize,
666 w: &[f32],
667 angles: &[f32],
668 stride: usize,
669 off: usize,
670 ) {
671 let hd = self.head_dim;
672 let pairs = if angles.is_empty() { 0 } else { angles.len() / n };
673 let pool = self.pool.as_deref();
674 let ptr = SendPtr(v.as_mut_ptr());
675 let work = |lo: usize, hi: usize| {
676 for p in lo..hi {
677 for h in 0..heads {
678 let x = unsafe { ptr.row(p * stride + off + h * hd, hd) };
681 let ss = x.iter().map(|&a| (a as f64) * (a as f64)).sum::<f64>() / hd as f64;
682 let inv = 1.0 / (ss + self.qk_eps).sqrt();
683 for (d, &g) in x.iter_mut().zip(w) {
684 *d = (*d as f64 * inv) as f32 * g;
685 }
686 for j in 0..pairs {
687 let a = angles[p * pairs + j];
688 let (s, c) = a.sin_cos();
689 let (lo_v, hi_v) = (x[j], x[j + pairs]);
690 x[j] = lo_v * c - hi_v * s;
691 x[j + pairs] = lo_v * s + hi_v * c;
692 }
693 }
694 }
695 };
696 match pool {
697 Some(pl) => pl.run_rows(n, &work),
698 None => work(0, n),
699 }
700 }
701
702 fn attention(
708 &self,
709 qkv: &[f32],
710 n: usize,
711 attn: &mut [f32],
712 nr: Option<(&[f32], &[f32], &[f32], f32)>,
713 ) {
714 let t_repack = std::time::Instant::now();
715 let (nh, hd) = (self.heads, self.head_dim);
716 let inner = nh * hd;
717 let scale = 1.0 / (hd as f32).sqrt();
718 let pool = self.pool.as_deref();
719 if std::env::var("CMF_MMH3_ATTN").as_deref() != Ok("cpu")
727 && crate::gpu::enabled_here()
728 && n >= 256
729 {
730 if std::env::var("CMF_MMH3_ATTN").as_deref() != Ok("repack")
743 && crate::gpu::dit_attention_packed(qkv, nh, n, hd, scale, nr, attn)
744 {
745 return;
751 }
752 assert!(
753 nr.is_none(),
754 "device qk-norm was requested but the device path refused; \
755 the host loops below expect q/k already normalized"
756 );
757 let mut qh = vec![0f32; nh * n * hd];
758 let mut kh = vec![0f32; nh * n * hd];
759 let mut vh = vec![0f32; nh * n * hd];
760 {
761 let (pq, pk, pv) = (
762 SendPtr(qh.as_mut_ptr()),
763 SendPtr(kh.as_mut_ptr()),
764 SendPtr(vh.as_mut_ptr()),
765 );
766 pool_rows(pool, n, &|lo, hi| {
767 for p in lo..hi {
768 let base = p * 3 * inner;
769 for h in 0..nh {
770 let dst = (h * n + p) * hd;
771 unsafe {
774 pq.row(dst, hd).copy_from_slice(
775 &qkv[base + h * hd..base + (h + 1) * hd],
776 );
777 pk.row(dst, hd).copy_from_slice(
778 &qkv[base + inner + h * hd..base + inner + (h + 1) * hd],
779 );
780 pv.row(dst, hd).copy_from_slice(
781 &qkv[base + 2 * inner + h * hd
782 ..base + 2 * inner + (h + 1) * hd],
783 );
784 }
785 }
786 }
787 });
788 }
789 Self::prof(2, t_repack);
796 if crate::gpu::dit_attention(&qh, &kh, &vh, nh, nh, n, hd, scale, attn) {
799 return;
800 }
801 }
802 let mut qh = vec![0f32; n * hd];
803 let mut kh = vec![0f32; n * hd];
804 let mut vt = vec![0f32; hd * n];
805 let mut scores = vec![0f32; n * n];
806 let mut oh = vec![0f32; n * hd];
807 for h in 0..nh {
808 for p in 0..n {
809 let base = p * 3 * inner;
810 let qs = &qkv[base + h * hd..base + (h + 1) * hd];
811 for (d, &val) in qs.iter().enumerate() {
812 qh[p * hd + d] = val * scale;
813 }
814 kh[p * hd..(p + 1) * hd]
815 .copy_from_slice(&qkv[base + inner + h * hd..base + inner + (h + 1) * hd]);
816 let vs = &qkv[base + 2 * inner + h * hd..base + 2 * inner + (h + 1) * hd];
817 for (d, &val) in vs.iter().enumerate() {
818 vt[d * n + p] = val;
819 }
820 }
821 crate::fcd_ops::gemm_nt(&qh, &kh, &mut scores, n, hd, n, pool);
822 let sp = SendPtr(scores.as_mut_ptr());
823 let soft = |lo: usize, hi: usize| {
824 for r in lo..hi {
825 softmax_inplace(unsafe { sp.row(r * n, n) });
827 }
828 };
829 match pool {
830 Some(pl) => pl.run_rows(n, &soft),
831 None => soft(0, n),
832 }
833 crate::fcd_ops::gemm_nt(&scores, &vt, &mut oh, n, n, hd, pool);
834 for p in 0..n {
835 attn[p * inner + h * hd..p * inner + (h + 1) * hd]
836 .copy_from_slice(&oh[p * hd..(p + 1) * hd]);
837 }
838 }
839 }
840
841 fn prof(slot: usize, t: std::time::Instant) {
845 if !mmh3_prof_on() {
846 return;
847 }
848 MMH3_PROF[slot].fetch_add(t.elapsed().as_micros() as u64, std::sync::atomic::Ordering::Relaxed);
849 }
850
851 fn block_forward(
855 &self,
856 blk: &Block,
857 x: &mut [f32],
858 n: usize,
859 mods: Option<&[f32]>,
860 pos: &[[f64; 3]],
861 rows: &[u32],
862 ) {
863 let hs = self.hidden;
864 let pool = self.pool.as_deref();
865 let inner = self.heads * self.head_dim;
866 let angles = if pos.is_empty() {
867 Vec::new()
868 } else {
869 self.rope_angles(pos)
870 };
871
872 let t = std::time::Instant::now();
873 let mut xn = vec![0f32; n * hs];
874 self.norm_rows(&mut xn, x, n, hs, &blk.norm1, self.eps);
875 if let (Some(m), false) = (mods, rows.is_empty()) {
876 self.modulate(&mut xn, hs, m, rows, 0, 1);
877 }
878 Self::prof(0, t);
879 let t = std::time::Instant::now();
880 let t_qkv = std::time::Instant::now();
887 let qk_gpu = std::env::var("CMF_MMH3_QKNORM").as_deref() != Ok("cpu")
893 && crate::gpu::enabled_here()
894 && crate::gpu::dit_attention_packed_available()
895 && n >= 256;
896 if qk_gpu && std::env::var("CMF_MMH3_FUSEQKV").as_deref() != Ok("0") {
897 let fuse_out = std::env::var("CMF_MMH3_FUSEOUT").as_deref() != Ok("0");
902 if std::env::var("CMF_GPU_DEBUG").is_ok() {
903 static ONCE: std::sync::Once = std::sync::Once::new();
904 ONCE.call_once(|| {
905 eprintln!(
906 "mmh3 fuse-out gate: qkv_q={} out_q={} qkv_map={} out_map={}",
907 matches!(&blk.qkv, Proj::Q(_)),
908 matches!(&blk.out, Proj::Q(_)),
909 matches!(&blk.qkv, Proj::Q(q) if q.mapped_q4tp().is_some()),
910 matches!(&blk.out, Proj::Q(o) if o.mapped_q4tp().is_some()),
911 )
912 });
913 }
914 if let (true, Proj::Q(q), Proj::Q(o)) = (fuse_out, &blk.qkv, &blk.out) {
915 if let (Some((m, i)), Some((_, oi))) = (q.mapped_q4tp(), o.mapped_q4tp()) {
916 let mut proj = vec![0f32; n * hs];
917 if crate::gpu::dit_qkv_attn_out(
918 m,
919 i,
920 oi,
921 &xn,
922 n,
923 hs,
924 self.heads,
925 self.head_dim,
926 1.0 / (self.head_dim as f32).sqrt(),
927 (
928 &angles[..],
929 &blk.q_norm[..],
930 &blk.k_norm[..],
931 self.qk_eps as f32,
932 ),
933 &mut proj,
934 ) {
935 Self::prof(1, t_qkv);
936 let t_res = std::time::Instant::now();
937 self.residual(x, hs, &proj, mods, rows, 2);
938 Self::prof(8, t_res);
939 self.ffn_tail(blk, x, n, mods, rows);
940 return;
941 }
942 }
943 }
944 if let Proj::Q(q) = &blk.qkv {
945 if let Some((m, i)) = q.mapped_q4tp() {
946 let mut attn = vec![0f32; n * inner];
947 if crate::gpu::dit_qkv_attention(
948 m,
949 i,
950 &xn,
951 n,
952 hs,
953 self.heads,
954 self.head_dim,
955 1.0 / (self.head_dim as f32).sqrt(),
956 (
957 &angles[..],
958 &blk.q_norm[..],
959 &blk.k_norm[..],
960 self.qk_eps as f32,
961 ),
962 &mut attn,
963 ) {
964 Self::prof(1, t_qkv);
970 let t_out = std::time::Instant::now();
971 let mut proj = vec![0f32; n * hs];
972 blk.out.matmat(&attn, n, &mut proj, pool);
973 Self::prof(4, t_out);
974 let t_res = std::time::Instant::now();
975 self.residual(x, hs, &proj, mods, rows, 2);
976 Self::prof(8, t_res);
977 self.ffn_tail(blk, x, n, mods, rows);
978 return;
979 }
980 }
981 }
982 }
983 let mut qkv = vec![0f32; n * 3 * inner];
984 blk.qkv.matmat(&xn, n, &mut qkv, pool);
985 Self::prof(1, t);
986 let qk_on_gpu = qk_gpu;
999 let t = std::time::Instant::now();
1000 if !qk_on_gpu {
1001 for (which, w) in [(0usize, &blk.q_norm), (1usize, &blk.k_norm)] {
1002 self.norm_rope_w(&mut qkv, n, self.heads, w, &angles, 3 * inner, which * inner);
1003 }
1004 }
1005 Self::prof(2, t);
1006 let t = std::time::Instant::now();
1007 let mut attn = vec![0f32; n * inner];
1008 self.attention(
1009 &qkv,
1010 n,
1011 &mut attn,
1012 qk_on_gpu.then_some((
1013 &angles[..],
1014 &blk.q_norm[..],
1015 &blk.k_norm[..],
1016 self.qk_eps as f32,
1017 )),
1018 );
1019 Self::prof(3, t);
1020 let t = std::time::Instant::now();
1021 let mut proj = vec![0f32; n * hs];
1022 blk.out.matmat(&attn, n, &mut proj, pool);
1023 Self::prof(4, t);
1024 let t = std::time::Instant::now();
1028 self.residual(x, hs, &proj, mods, rows, 2);
1029 Self::prof(8, t);
1030
1031 self.ffn_tail(blk, x, n, mods, rows);
1032 }
1033
1034 fn ffn_tail(
1039 &self,
1040 blk: &Block,
1041 x: &mut [f32],
1042 n: usize,
1043 mods: Option<&[f32]>,
1044 rows: &[u32],
1045 ) {
1046 let hs = self.hidden;
1047 let pool = self.pool.as_deref();
1048 let mut xn = vec![0f32; n * hs];
1049 let mut proj = vec![0f32; n * hs];
1050 let t = std::time::Instant::now();
1051 self.norm_rows(&mut xn, x, n, hs, &blk.norm2, self.eps);
1052 if let (Some(m), false) = (mods, rows.is_empty()) {
1053 self.modulate(&mut xn, hs, m, rows, 3, 4);
1054 }
1055 Self::prof(0, t);
1056 let t = std::time::Instant::now();
1057 if std::env::var("CMF_MMH3_FFN").as_deref() != Ok("cpu")
1062 && crate::gpu::enabled_here()
1063 && n >= 64
1064 {
1065 if let (Proj::Q(q1), Proj::Q(q2)) = (&blk.fc1, &blk.fc2) {
1066 if let (Some((m, i1)), Some((_, i2))) =
1067 (q1.mapped_q4tp(), q2.mapped_q4tp())
1068 {
1069 let mut fout = vec![0f32; n * hs];
1070 if crate::gpu::q4tp_ffn_packed(
1071 m, i1, i2, &xn, n, hs, self.ffn, None, &mut fout,
1072 ) {
1073 Self::prof(5, t);
1074 self.residual(x, hs, &fout, mods, rows, 5);
1075 return;
1076 }
1077 }
1078 }
1079 }
1080 let mut gu = vec![0f32; n * 2 * self.ffn];
1081 blk.fc1.matmat(&xn, n, &mut gu, pool);
1082 Self::prof(5, t);
1083 let t = std::time::Instant::now();
1084 let ffn = self.ffn;
1086 let mut act = vec![0f32; n * ffn];
1087 let ap = SendPtr(act.as_mut_ptr());
1088 pool_rows(pool, n, &|lo, hi| {
1089 for p in lo..hi {
1090 let row = &gu[p * 2 * ffn..(p + 1) * 2 * ffn];
1091 let (g, up) = row.split_at(ffn);
1092 for (o, (&a, &b)) in unsafe { ap.row(p * ffn, ffn) }
1094 .iter_mut()
1095 .zip(g.iter().zip(up))
1096 {
1097 *o = silu(a) * b;
1098 }
1099 }
1100 });
1101 Self::prof(6, t);
1102 let t = std::time::Instant::now();
1103 blk.fc2.matmat(&act, n, &mut proj, pool);
1104 Self::prof(7, t);
1105 self.residual(x, hs, &proj, mods, rows, 5);
1106 }
1107
1108
1109 fn norm_rows(&self, dst: &mut [f32], src: &[f32], n: usize, hs: usize, w: &[f32], eps: f64) {
1114 let ptr = SendPtr(dst.as_mut_ptr());
1115 pool_rows(self.pool.as_deref(), n, &|lo, hi| {
1116 for p in lo..hi {
1117 rms_norm_into(&src[p * hs..(p + 1) * hs], w, eps, unsafe {
1119 ptr.row(p * hs, hs)
1120 });
1121 }
1122 });
1123 }
1124
1125 fn modulate(
1127 &self,
1128 x: &mut [f32],
1129 hs: usize,
1130 mods: &[f32],
1131 rows: &[u32],
1132 shift_e: usize,
1133 scale_e: usize,
1134 ) {
1135 let stride = EXPAND * hs;
1136 let ptr = SendPtr(x.as_mut_ptr());
1137 pool_rows(self.pool.as_deref(), rows.len(), &|lo, hi| {
1138 for p in lo..hi {
1139 let base = rows[p] as usize * stride;
1140 let shift = &mods[base + shift_e * hs..base + (shift_e + 1) * hs];
1141 let scale = &mods[base + scale_e * hs..base + (scale_e + 1) * hs];
1142 for ((v, &sc), &sh) in unsafe { ptr.row(p * hs, hs) }
1144 .iter_mut()
1145 .zip(scale)
1146 .zip(shift)
1147 {
1148 *v = *v * (1.0 + sc) + sh;
1149 }
1150 }
1151 });
1152 }
1153
1154 fn residual(
1157 &self,
1158 x: &mut [f32],
1159 hs: usize,
1160 other: &[f32],
1161 mods: Option<&[f32]>,
1162 rows: &[u32],
1163 gate_e: usize,
1164 ) {
1165 let n = x.len() / hs;
1166 let stride = EXPAND * hs;
1167 let ptr = SendPtr(x.as_mut_ptr());
1168 let gated = mods.filter(|_| !rows.is_empty());
1169 pool_rows(self.pool.as_deref(), n, &|lo, hi| {
1170 for p in lo..hi {
1171 let row = unsafe { ptr.row(p * hs, hs) };
1173 let src = &other[p * hs..(p + 1) * hs];
1174 match gated {
1175 Some(m) => {
1176 let base = rows[p] as usize * stride;
1177 let gate = &m[base + gate_e * hs..base + (gate_e + 1) * hs];
1178 for ((v, &g), &o) in row.iter_mut().zip(gate).zip(src) {
1179 *v += g * o;
1180 }
1181 }
1182 None => {
1183 for (v, &o) in row.iter_mut().zip(src) {
1184 *v += o;
1185 }
1186 }
1187 }
1188 }
1189 });
1190 }
1191
1192 pub fn forward(
1199 &self,
1200 layout: &Layout,
1201 text: &[f32],
1202 video: &[f32],
1203 audio: &[f32],
1204 sigma_v: f64,
1205 cond: &[Vec<f32>],
1206 ) -> (Vec<f32>, Vec<f32>) {
1207 let hs = self.hidden;
1208 let pool = self.pool.as_deref();
1209 let sigma_v = sigma_v.max(1e-6);
1210 let t_v = 1.0 - sigma_v;
1211 let t_a = 1.0 - time_shift_sigma(sigma_v, self.shift_video, self.shift_audio);
1212
1213 let has_cond = layout
1218 .segments
1219 .iter()
1220 .any(|s| matches!(s.kind, Kind::Cond | Kind::RefImg));
1221 let has_ref_audio = layout.segments.iter().any(|s| s.kind == Kind::RefAudio);
1222 let t_cond_a = t_a.max(self.cond_aug_audio);
1225 let t_cond = t_v.max(self.cond_aug);
1230 let mut ts = vec![t_v, t_a];
1231 if has_cond {
1232 ts.push(t_cond);
1233 }
1234 if has_ref_audio {
1235 ts.push(t_cond_a);
1236 }
1237 ts.sort_by(|a, b| a.partial_cmp(b).unwrap());
1238 ts.dedup();
1239 let row_of = |t: f64| ts.iter().position(|&x| x == t).unwrap();
1240 let (row_v, row_a) = (row_of(t_v), row_of(t_a));
1241 let row_c = if has_cond { row_of(t_cond) } else { 0 };
1242 let row_ca = if has_ref_audio { row_of(t_cond_a) } else { 0 };
1243
1244 let mut rows = vec![0u32; layout.seq_len];
1248 for s in &layout.segments {
1249 match s.kind {
1250 Kind::Text => {
1251 for (i, v) in rows[s.start..s.stop].iter_mut().enumerate() {
1252 let tag = *layout.text_tags.get(i).unwrap_or(&(TAG_TEXT as u8));
1253 *v = (row_v * MODALITIES + tag as usize) as u32;
1254 }
1255 }
1256 Kind::Cond | Kind::RefImg => {
1260 for v in rows[s.start..s.stop].iter_mut() {
1261 *v = (row_c * MODALITIES + TAG_VIDEO) as u32;
1262 }
1263 }
1264 Kind::RefAudio => {
1265 for v in rows[s.start..s.stop].iter_mut() {
1266 *v = (row_ca * MODALITIES + TAG_AUDIO) as u32;
1267 }
1268 }
1269 Kind::Video => {
1270 for v in rows[s.start..s.stop].iter_mut() {
1271 *v = (row_v * MODALITIES + TAG_VIDEO) as u32;
1272 }
1273 }
1274 Kind::Audio => {
1275 for v in rows[s.start..s.stop].iter_mut() {
1276 *v = (row_a * MODALITIES + TAG_AUDIO) as u32;
1277 }
1278 }
1279 }
1280 }
1281
1282 let v_rows = patchify_video(video, self.latents_dim, layout.latent_t, layout.lat_h, layout.lat_w);
1284 let v_n = v_rows.len() / (self.latents_dim * 4);
1285 let a_rows = pack_audio(audio, self.audio_dim, layout.audio_t);
1286 let vd = self.latents_dim * 4;
1289 let cond_rows: Vec<Vec<f32>> = cond
1290 .iter()
1291 .enumerate()
1292 .map(|(i, z)| {
1293 let mut r = patchify_video(z, self.latents_dim, 1, layout.lat_h, layout.lat_w);
1294 if self.cond_aug < 1.0 {
1295 let noise = crate::videogen::gauss_pub(r.len(), 0x5EED ^ i as u64);
1300 let a = self.cond_aug as f32;
1301 for (v, n) in r.iter_mut().zip(&noise) {
1302 *v = a * *v + (1.0 - a) * n;
1303 }
1304 }
1305 r
1306 })
1307 .collect();
1308
1309 let mut h = vec![0f32; layout.seq_len * hs];
1310 let mut ci = 0usize;
1311 for s in layout.segments.iter().filter(|s| s.kind == Kind::Cond) {
1312 let n = s.stop - s.start;
1313 let r = cond_rows
1314 .get(ci)
1315 .unwrap_or_else(|| panic!("layout has {} cond segments, {} latents given", ci + 1, cond_rows.len()));
1316 self.video_patch
1317 .matmat(r, n, &mut h[s.start * hs..s.stop * hs], pool);
1318 for row in h[s.start * hs..s.stop * hs].chunks_exact_mut(hs) {
1319 for (v, &b) in row.iter_mut().zip(&self.video_patch_b) {
1320 *v += b;
1321 }
1322 }
1323 ci += 1;
1324 }
1325 let vseg = layout.segment(Kind::Video);
1326 let aseg = layout.segment(Kind::Audio);
1327 let tseg = layout.segment(Kind::Text);
1328 h[tseg.start * hs..tseg.stop * hs].copy_from_slice(&text[..(tseg.stop - tseg.start) * hs]);
1329 self.video_patch.matmat(&v_rows, v_n, &mut h[vseg.start * hs..vseg.stop * hs], pool);
1330 self.audio_patch.matmat(
1331 &a_rows,
1332 aseg.stop - aseg.start,
1333 &mut h[aseg.start * hs..aseg.stop * hs],
1334 pool,
1335 );
1336 for row in h[vseg.start * hs..vseg.stop * hs].chunks_exact_mut(hs) {
1337 for (v, &b) in row.iter_mut().zip(&self.video_patch_b) {
1338 *v += b;
1339 }
1340 }
1341 for row in h[aseg.start * hs..aseg.stop * hs].chunks_exact_mut(hs) {
1342 for (v, &b) in row.iter_mut().zip(&self.audio_patch_b) {
1343 *v += b;
1344 }
1345 }
1346
1347 for (i, blk) in self.blocks.iter().enumerate() {
1349 let mods = blk.adaln.as_ref().unwrap().eval(&ts, pool);
1350 self.block_forward(blk, &mut h, layout.seq_len, Some(&mods), &layout.pos, &rows);
1351 if let Ok(w) = std::env::var("CMF_MMH3_WATCHROW") {
1354 if let Ok(r) = w.parse::<usize>() {
1355 let row = &h[r * hs..(r + 1) * hs];
1356 let amax = row.iter().filter(|v| v.is_finite()).fold(0f32, |a, v| a.max(v.abs()));
1357 let bad = row.iter().filter(|v| !v.is_finite()).count();
1358 if bad > 0 || i == 0 || amax > 1e3 {
1359 eprintln!(" watch blk {i}: row {r} absmax {amax:.3e} nonfinite {bad}");
1360 }
1361 }
1362 }
1363 if std::env::var_os("CMF_DIT_PROGRESS").is_some() {
1364 eprint!("\r block {}/{}", i + 1, self.blocks.len());
1365 }
1366 }
1367
1368 if std::env::var("CMF_MMH3_NANPROBE").is_ok() {
1370 let bad: Vec<usize> = (0..h.len() / hs)
1371 .filter(|r| h[r * hs..(r + 1) * hs].iter().any(|v| !v.is_finite()))
1372 .collect();
1373 if !bad.is_empty() {
1374 eprintln!(
1375 " nanprobe rows: {} of {} bad, first 8 {:?}, video seg {}..{}, audio seg {}..{}",
1376 bad.len(),
1377 h.len() / hs,
1378 &bad[..bad.len().min(8)],
1379 vseg.start,
1380 vseg.stop,
1381 aseg.start,
1382 aseg.stop
1383 );
1384 }
1385 }
1386 let fm = self.final_adaln.eval(&ts, pool);
1387 let mut video_out = vec![0f32; (vseg.stop - vseg.start) * vd];
1388 let mut audio_out = vec![0f32; (aseg.stop - aseg.start) * self.audio_dim];
1389 for (seg, row, w, b, dst, dim) in [
1390 (vseg, row_v, &self.video_out, &self.video_out_b, &mut video_out, vd),
1391 (aseg, row_a, &self.audio_out, &self.audio_out_b, &mut audio_out, self.audio_dim),
1392 ] {
1393 let n = seg.stop - seg.start;
1394 let mut hn = vec![0f32; n * hs];
1395 for (o, src) in hn.chunks_exact_mut(hs).zip(h[seg.start * hs..seg.stop * hs].chunks_exact(hs)) {
1396 rms_norm_into(src, &self.final_norm, self.final_eps, o);
1397 }
1398 let shift = &fm[row * 2 * hs..row * 2 * hs + hs];
1401 let scale = &fm[row * 2 * hs + hs..(row + 1) * 2 * hs];
1402 for r in hn.chunks_exact_mut(hs) {
1403 for ((v, &sc), &sh) in r.iter_mut().zip(scale).zip(shift) {
1404 *v = *v * (1.0 + sc) + sh;
1405 }
1406 }
1407 let probe = std::env::var("CMF_MMH3_NANPROBE").is_ok();
1412 if probe {
1413 let bad_in = hn.iter().filter(|v| !v.is_finite()).count();
1414 eprintln!(
1415 " nanprobe dim={dim} n={n} in_bad={bad_in} in_absmax={:.4}",
1416 hn.iter().filter(|v| v.is_finite()).fold(0f32, |a, v| a.max(v.abs()))
1417 );
1418 }
1419 w.matmat(&hn, n, dst, pool);
1420 if probe {
1421 let bad_out = dst.iter().filter(|v| !v.is_finite()).count();
1422 eprintln!(
1423 " nanprobe dim={dim} out_bad={bad_out}/{} out_absmax={:.4}",
1424 dst.len(),
1425 dst.iter().filter(|v| v.is_finite()).fold(0f32, |a, v| a.max(v.abs()))
1426 );
1427 }
1428 for r in dst.chunks_exact_mut(dim) {
1429 for (v, &bv) in r.iter_mut().zip(b.iter()) {
1430 *v += bv;
1431 }
1432 }
1433 }
1434
1435 let video = unpatchify_video(
1439 &video_out,
1440 self.latents_dim,
1441 layout.latent_t,
1442 layout.lat_h,
1443 layout.lat_w,
1444 );
1445 let audio = unpack_audio(&audio_out, self.audio_dim, layout.audio_t);
1446 (
1447 video.iter().map(|&v| -v).collect(),
1448 audio.iter().map(|&v| -v).collect(),
1449 )
1450 }
1451}
1452
1453fn pool_rows(pool: Option<&Pool>, n: usize, f: &(dyn Fn(usize, usize) + Sync)) {
1457 match pool {
1458 Some(p) => p.run_rows(n, f),
1459 None => f(0, n),
1460 }
1461}
1462
1463pub fn patchify_video(x: &[f32], c: usize, t: usize, h: usize, w: usize) -> Vec<f32> {
1468 let (ph, pw) = (h / 2, w / 2);
1469 let mut out = vec![0f32; t * ph * pw * c * 4];
1470 let mut i = 0;
1471 for ti in 0..t {
1472 for hi in 0..ph {
1473 for wi in 0..pw {
1474 for ci in 0..c {
1475 for p in 0..2 {
1476 for q in 0..2 {
1477 out[i] = x[((ci * t + ti) * h + hi * 2 + p) * w + wi * 2 + q];
1478 i += 1;
1479 }
1480 }
1481 }
1482 }
1483 }
1484 }
1485 out
1486}
1487
1488pub fn unpatchify_video(rows: &[f32], c: usize, t: usize, h: usize, w: usize) -> Vec<f32> {
1490 let (ph, pw) = (h / 2, w / 2);
1491 let mut out = vec![0f32; c * t * h * w];
1492 let mut i = 0;
1493 for ti in 0..t {
1494 for hi in 0..ph {
1495 for wi in 0..pw {
1496 for ci in 0..c {
1497 for p in 0..2 {
1498 for q in 0..2 {
1499 out[((ci * t + ti) * h + hi * 2 + p) * w + wi * 2 + q] = rows[i];
1500 i += 1;
1501 }
1502 }
1503 }
1504 }
1505 }
1506 }
1507 out
1508}
1509
1510pub fn pack_audio(x: &[f32], c: usize, t: usize) -> Vec<f32> {
1513 let mut out = vec![0f32; 2 * t * c];
1514 for ch in 0..2 {
1515 for ti in 0..t {
1516 for ci in 0..c {
1517 out[(ch * t + ti) * c + ci] = x[(ci * 2 + ch) * t + ti];
1518 }
1519 }
1520 }
1521 out
1522}
1523
1524pub fn unpack_audio(rows: &[f32], c: usize, t: usize) -> Vec<f32> {
1526 let mut out = vec![0f32; c * 2 * t];
1527 for ch in 0..2 {
1528 for ti in 0..t {
1529 for ci in 0..c {
1530 out[(ci * 2 + ch) * t + ti] = rows[(ch * t + ti) * c + ci];
1531 }
1532 }
1533 }
1534 out
1535}
1536
1537struct SendPtr(*mut f32);
1540unsafe impl Send for SendPtr {}
1541unsafe impl Sync for SendPtr {}
1542impl SendPtr {
1543 #[allow(clippy::mut_from_ref)]
1545 unsafe fn row(&self, off: usize, len: usize) -> &mut [f32] {
1546 unsafe { std::slice::from_raw_parts_mut(self.0.add(off), len) }
1547 }
1548}
1549
1550fn softmax_inplace(row: &mut [f32]) {
1551 let mx = row.iter().cloned().fold(f32::MIN, f32::max);
1552 let mut den = 0f32;
1553 for r in row.iter_mut() {
1554 *r = (*r - mx).exp();
1555 den += *r;
1556 }
1557 if den > 0.0 {
1558 let inv = 1.0 / den;
1559 for r in row.iter_mut() {
1560 *r *= inv;
1561 }
1562 }
1563}
1564
1565#[cfg(test)]
1566mod tests {
1567 use super::*;
1568
1569 #[test]
1570 fn schedule_remap_is_an_involution() {
1571 for &s in &[1e-3, 0.25, 0.5, 0.8, 0.972_973, 1.0] {
1572 let a = time_shift_sigma(s, 12.0, 3.0);
1573 let back = time_shift_sigma(a, 3.0, 12.0);
1574 assert!((back - s).abs() < 1e-9, "{s} -> {a} -> {back}");
1575 }
1576 }
1577
1578 #[test]
1579 fn slope_matches_a_finite_difference() {
1580 let h = 1e-6;
1581 for &s in &[0.2, 0.5, 0.9] {
1582 let num = (time_shift_sigma(s + h, 12.0, 3.0) - time_shift_sigma(s - h, 12.0, 3.0))
1583 / (2.0 * h);
1584 let got = time_shift_slope(s, 12.0, 3.0);
1585 assert!((num - got).abs() < 1e-5, "{s}: {num} vs {got}");
1586 }
1587 }
1588
1589 #[test]
1590 fn patchify_round_trips() {
1591 let (c, t, h, w) = (3usize, 2usize, 4usize, 6usize);
1592 let x: Vec<f32> = (0..c * t * h * w).map(|i| i as f32).collect();
1593 let rows = patchify_video(&x, c, t, h, w);
1594 assert_eq!(rows.len(), t * (h / 2) * (w / 2) * c * 4);
1595 assert_eq!(unpatchify_video(&rows, c, t, h, w), x);
1596 }
1597
1598 #[test]
1599 fn audio_pack_round_trips() {
1600 let (c, t) = (5usize, 7usize);
1601 let x: Vec<f32> = (0..c * 2 * t).map(|i| i as f32).collect();
1602 let rows = pack_audio(&x, c, t);
1603 assert_eq!(unpack_audio(&rows, c, t), x);
1604 }
1605
1606 #[test]
1607 fn keyframes_sit_between_the_text_and_the_audio() {
1608 let (tl, lt, lh, lw, at) = (8usize, 3usize, 8usize, 12usize, 5usize);
1609 let base = Layout::t2va(tl, lt, lh, lw, at);
1610 let l = Layout::fl2va(tl, lt, lh, lw, at, &[(0, 39), (38, 39)], &[]);
1612 assert_eq!(l.cond_rows(), 2 * l.frame_rows);
1613 assert_eq!(l.seq_len, base.seq_len + 2 * l.frame_rows);
1614 let kinds: Vec<_> = l.segments.iter().map(|s| s.kind).collect();
1615 assert_eq!(
1616 kinds,
1617 vec![Kind::Text, Kind::Cond, Kind::Cond, Kind::Audio, Kind::Video]
1618 );
1619 let (a0, v0) = (l.segment(Kind::Audio), l.segment(Kind::Video));
1622 let (ba, bv) = (base.segment(Kind::Audio), base.segment(Kind::Video));
1623 assert_eq!(l.pos[a0.start][0], base.pos[ba.start][0]);
1624 assert_eq!(l.pos[v0.start][0], base.pos[bv.start][0]);
1625 let c: Vec<_> = l.segments.iter().filter(|s| s.kind == Kind::Cond).collect();
1628 assert_eq!(l.pos[c[0].start][0], tl as f64);
1629 let spans: f64 = (0..lt).map(|k| FRAME_RESCALE * FRAME_PER_TOKEN[k % 5]).sum();
1630 assert!((l.pos[c[1].start][0] - (tl as f64 + spans - FRAME_RESCALE)).abs() < 1e-12);
1631 assert_eq!(l.pos[c[0].start][1], l.pos[v0.start][1]);
1633 assert_eq!(l.pos[c[0].start][2], l.pos[v0.start][2]);
1634 }
1635
1636 #[test]
1637 fn layout_places_the_target_streams_last() {
1638 let l = Layout::t2va(8, 3, 8, 12, 5);
1639 assert_eq!(l.frame_rows, 4 * 6);
1640 assert_eq!(l.seq_len, 8 + 2 * 5 + 3 * 24);
1641 assert_eq!(l.segments[0].kind, Kind::Text);
1642 assert_eq!(l.segments[1].kind, Kind::Audio);
1643 assert_eq!(l.segments[2].kind, Kind::Video);
1644 let v = l.segment(Kind::Video);
1646 let t0 = l.pos[v.start][0];
1647 let t1 = l.pos[v.start + l.frame_rows][0];
1648 assert!((t1 - t0 - FRAME_RESCALE).abs() < 1e-12);
1649 }
1650}