Skip to main content

cortiq_engine/
mmh3.rs

1//! MiniMax-H3: the packed-token audio-video DiT.
2//!
3//! One stream of tokens — `[text | audio | video]` — denoised by fifty
4//! blocks of full self-attention, 3-axis RoPE and adaLN modulation, with
5//! the video and audio latents riding different flow schedules and a
6//! separate output head each.
7//!
8//! Three things make it unlike the image DiT next door:
9//!
10//! * **The sequence is packed, not batched.** Text, audio rows and video
11//!   rows sit in one sequence and attend to each other; there is no
12//!   cross-attention. Modulation is what tells a token which modality it
13//!   is: every block emits six modulation vectors for each of three
14//!   modality tags at each of the (at most two, for t2va) distinct
15//!   timesteps, and a segment table says which row each token reads.
16//!
17//! * **adaLN arrives as a curve, not a matrix.** The released weight is
18//!   [96768, 2688] per block — 13 B parameters, 40% of the model — for a
19//!   map whose input is one number. `cortiq animate-pack` collapses it
20//!   onto a rank-24 basis of the timestep curve that already carries the
21//!   Turbo LoRA, so what lands here is [96768, 24] and a [1025, 24]
22//!   table to interpolate. Measured against the full matrix: rms 8.7e-5
23//!   on a signal of rms 0.46.
24//!
25//! * **Two clocks.** The sampler hands in the video sigma; the audio
26//!   stream's own sigma is a closed-form remap of it (shift 12 → 3), and
27//!   the two are integrated separately. `forward` returns both
28//!   velocities unscaled, each on its own schedule — the reference
29//!   returns the audio one pre-multiplied by d(σ_a)/d(σ_v) so that a
30//!   single-schedule sampler is approximately right, which at four steps
31//!   it is not.
32//!
33//! Parity: `tools/mk_mmh3_toy.py` builds a toy checkpoint carrying the
34//! release's real tensor names and a golden forward from ComfyUI's own
35//! module; `tools/mmh3_toy_gate.sh` diffs this port against it.
36
37use crate::dit::Proj;
38use crate::pool::Pool;
39use cortiq_core::CmfModel;
40use std::sync::Arc;
41
42/// Frames a video latent token spans, cycling with period 5 — the
43/// checkpoint's temporal grid, which is NOT uniform.
44const FRAME_PER_TOKEN: [f64; 5] = [1.0, 4.0, 4.0, 4.0, 4.0];
45const FRAME_RESCALE: f64 = 5.0 / 3.0;
46
47/// Modality tags, as the adaLN row layout orders them.
48const TAG_VIDEO: usize = 0;
49const TAG_TEXT: usize = 1;
50const TAG_AUDIO: usize = 2;
51const MODALITIES: usize = 3;
52/// shift/scale/gate for attention, then the same three for the MLP.
53const EXPAND: usize = 6;
54/// Where a visual condition row sits on the schedule: all but arrived.
55const VISUAL_COND_TIMESTEP: f64 = 0.999;
56/// An audio condition is not noised at all.
57const AUDIO_COND_TIMESTEP: f64 = 1.0;
58
59// ── the flow-schedule remap ─────────────────────────────────────────
60
61/// σ on `to`'s schedule at the same point of the shared base grid.
62pub fn time_shift_sigma(sigma: f64, from_shift: f64, to_shift: f64) -> f64 {
63    let base = sigma / (from_shift + sigma * (1.0 - from_shift));
64    to_shift * base / (1.0 + (to_shift - 1.0) * base)
65}
66
67/// d(σ_to)/d(σ_from) at the same base-grid point.
68pub fn time_shift_slope(sigma: f64, from_shift: f64, to_shift: f64) -> f64 {
69    let base = sigma / (from_shift + sigma * (1.0 - from_shift));
70    (to_shift * (1.0 + (from_shift - 1.0) * base).powi(2))
71        / (from_shift * (1.0 + (to_shift - 1.0) * base).powi(2))
72}
73
74// ── the packed layout ───────────────────────────────────────────────
75
76#[derive(Clone, Copy, PartialEq, Debug)]
77pub enum Kind {
78    Text,
79    /// A keyframe's latent, re-injected every step and never denoised.
80    Cond,
81    /// A reference block's video rows — an image, or a clip's frames.
82    RefImg,
83    /// A reference block's audio rows.
84    RefAudio,
85    Audio,
86    Video,
87}
88
89/// One contiguous run of rows sharing a modality tag and a timestep.
90#[derive(Clone, Copy, Debug)]
91pub struct Segment {
92    pub start: usize,
93    pub stop: usize,
94    pub kind: Kind,
95}
96
97/// The static structure of one shape signature: where each stream sits
98/// in the sequence and what 3-D position every row carries.
99pub struct Layout {
100    pub seq_len: usize,
101    pub segments: Vec<Segment>,
102    /// [seq_len, 3] — (t, h, w), f64 because the axes are fractional.
103    pub pos: Vec<[f64; 3]>,
104    pub text_len: usize,
105    pub audio_t: usize,
106    pub latent_t: usize,
107    pub lat_h: usize,
108    pub lat_w: usize,
109    /// Rows per latent frame after the 2×2 patch.
110    pub frame_rows: usize,
111    /// Per-token modality tag for the text span. A vision block inside
112    /// the prompt carries the VIDEO tag, not the text one.
113    pub text_tags: Vec<u8>,
114}
115
116/// One reference block, in the order it was given.
117#[derive(Clone, Debug)]
118pub enum Ref {
119    /// A still, at its own latent size.
120    Image { lat_h: usize, lat_w: usize },
121    /// Standalone audio, `t` latent frames of it.
122    Audio { t: usize },
123    /// Frames, with an optional soundtrack that packs immediately
124    /// before them and shares their origin.
125    Video {
126        latent_t: usize,
127        lat_h: usize,
128        lat_w: usize,
129        audio_t: usize,
130    },
131}
132
133/// Channel-major stereo rows: `t` frames per channel, the two channels
134/// pinned to the grid's extreme w coordinates so RoPE can tell them
135/// apart, h flat at zero.
136fn push_audio_grid(pos: &mut Vec<[f64; 3]>, cursor: f64, t: usize, w_low: f64, w_high: f64) {
137    for ch in 0..2 {
138        let w = if ch == 0 { w_low } else { w_high };
139        for i in 0..t {
140            pos.push([cursor + i as f64, 0.0, w]);
141        }
142    }
143}
144
145/// `linspace((1 − ratio)/2, (1 + ratio)/2, dim/patch, endpoint=False) · 32`
146fn axis_from_sqrt_area(dim: usize, patch: usize, sqrt_area: f64) -> Vec<f64> {
147    let ratio = dim as f64 / sqrt_area;
148    let n = dim / patch;
149    (0..n)
150        .map(|i| (i as f64 * (ratio / n as f64) + (1.0 - ratio) / 2.0) * 32.0)
151        .collect()
152}
153
154impl Layout {
155    /// t2va: `[text | audio | video]`, the target streams last and in
156    /// that order. Keyframe and reference blocks would slot between the
157    /// text and the audio; this port does text-to-video only.
158    pub fn t2va(text_len: usize, latent_t: usize, lat_h: usize, lat_w: usize, audio_t: usize) -> Self {
159        Self::build(text_len, latent_t, lat_h, lat_w, audio_t, &[], &[])
160    }
161
162    /// `fl2va`: keyframe condition rows sit between the text and the
163    /// audio, sharing the TARGET spatial grid, each pinned to the time
164    /// coordinate of the frame it stands for — the first frame at the
165    /// text's end, the last one a whole clip further on, minus one
166    /// span. They never advance the cursor, so audio and video still
167    /// start where they would have.
168    ///
169    /// `frames` gives each keyframe's pixel index and the clip's total,
170    /// and `text_tags` the per-token modality of the prompt span.
171    pub fn fl2va(
172        text_len: usize,
173        latent_t: usize,
174        lat_h: usize,
175        lat_w: usize,
176        audio_t: usize,
177        frames: &[(usize, usize)],
178        text_tags: &[u8],
179    ) -> Self {
180        Self::build(text_len, latent_t, lat_h, lat_w, audio_t, frames, text_tags)
181    }
182
183    /// `ref2va`: reference images, audio and clips ahead of the target
184    /// streams. Unlike a keyframe, a reference ADVANCES the cursor —
185    /// each block occupies its own stretch of the time axis, and the
186    /// target audio and video begin after the last of them.
187    pub fn ref2va(
188        text_len: usize,
189        latent_t: usize,
190        lat_h: usize,
191        lat_w: usize,
192        audio_t: usize,
193        refs: &[Ref],
194        text_tags: &[u8],
195    ) -> Self {
196        Self::build_full(text_len, latent_t, lat_h, lat_w, audio_t, &[], refs, text_tags)
197    }
198
199    fn build(
200        text_len: usize,
201        latent_t: usize,
202        lat_h: usize,
203        lat_w: usize,
204        audio_t: usize,
205        frames: &[(usize, usize)],
206        text_tags: &[u8],
207    ) -> Self {
208        Self::build_full(text_len, latent_t, lat_h, lat_w, audio_t, frames, &[], text_tags)
209    }
210
211    #[allow(clippy::too_many_arguments)]
212    fn build_full(
213        text_len: usize,
214        latent_t: usize,
215        lat_h: usize,
216        lat_w: usize,
217        audio_t: usize,
218        frames: &[(usize, usize)],
219        refs: &[Ref],
220        text_tags: &[u8],
221    ) -> Self {
222        let area = ((lat_h * lat_w) as f64).sqrt();
223        let h_axis = axis_from_sqrt_area(lat_h, 2, area);
224        let w_axis = axis_from_sqrt_area(lat_w, 2, area);
225        let frame_rows = h_axis.len() * w_axis.len();
226
227        let mut pos: Vec<[f64; 3]> = Vec::new();
228        let mut segments = Vec::new();
229
230        segments.push(Segment { start: 0, stop: text_len, kind: Kind::Text });
231        for i in 0..text_len {
232            pos.push([i as f64, 0.0, 0.0]);
233        }
234
235        // Both target streams share this origin: the text runs out at
236        // `text_len` and audio and video start together from there —
237        // unless references push it along.
238        let mut cursor = text_len as f64;
239        let (w_low_t, w_high_t) = (w_axis[0], w_axis[w_axis.len() - 1]);
240
241        for r in refs {
242            match r {
243                Ref::Image { lat_h, lat_w } => {
244                    let (rh, rw) = (
245                        axis_from_sqrt_area(*lat_h, 2, ((lat_h * lat_w) as f64).sqrt()),
246                        axis_from_sqrt_area(*lat_w, 2, ((lat_h * lat_w) as f64).sqrt()),
247                    );
248                    let start = pos.len();
249                    for &h in &rh {
250                        for &w in &rw {
251                            pos.push([cursor, h, w]);
252                        }
253                    }
254                    segments.push(Segment { start, stop: pos.len(), kind: Kind::RefImg });
255                    cursor += 1.0;
256                }
257                Ref::Audio { t } => {
258                    if *t > 0 {
259                        let start = pos.len();
260                        push_audio_grid(&mut pos, cursor, *t, w_low_t, w_high_t);
261                        segments.push(Segment { start, stop: pos.len(), kind: Kind::RefAudio });
262                    }
263                    cursor += *t as f64;
264                }
265                Ref::Video { latent_t: vt, lat_h: rh_, lat_w: rw_, audio_t: rt } => {
266                    let area = ((rh_ * rw_) as f64).sqrt();
267                    let rh = axis_from_sqrt_area(*rh_, 2, area);
268                    let rw = axis_from_sqrt_area(*rw_, 2, area);
269                    // The block's audio packs immediately BEFORE its
270                    // frames, both from the same origin, and takes its
271                    // w extremes from the block's own grid.
272                    if *rt > 0 {
273                        let start = pos.len();
274                        push_audio_grid(&mut pos, cursor, *rt, rw[0], rw[rw.len() - 1]);
275                        segments.push(Segment { start, stop: pos.len(), kind: Kind::RefAudio });
276                    }
277                    let start = pos.len();
278                    let mut t_coord = cursor;
279                    for k in 0..*vt {
280                        for &h in &rh {
281                            for &w in &rw {
282                                pos.push([t_coord, h, w]);
283                            }
284                        }
285                        t_coord += FRAME_RESCALE * FRAME_PER_TOKEN[k % 5];
286                    }
287                    segments.push(Segment { start, stop: pos.len(), kind: Kind::RefImg });
288                    let spans: f64 = (0..*vt)
289                        .map(|k| FRAME_RESCALE * FRAME_PER_TOKEN[k % 5])
290                        .sum();
291                    cursor += (*rt as f64).max(spans);
292                }
293            }
294        }
295        let cursor = cursor;
296
297        // Keyframes, in the order given.
298        let spans: f64 = (0..latent_t)
299            .map(|k| FRAME_RESCALE * FRAME_PER_TOKEN[k % 5])
300            .sum();
301        for &(pixel_index, frame_count) in frames {
302            let cond_t = if pixel_index == 0 {
303                cursor
304            } else if frame_count > 0 && pixel_index == frame_count - 1 {
305                cursor + spans - FRAME_RESCALE
306            } else {
307                panic!("only the first and last frame can anchor a keyframe");
308            };
309            let start = pos.len();
310            for &h in &h_axis {
311                for &w in &w_axis {
312                    pos.push([cond_t, h, w]);
313                }
314            }
315            segments.push(Segment { start, stop: pos.len(), kind: Kind::Cond });
316        }
317
318        // Audio is channel-major stereo: every latent frame once per
319        // channel, the two channels pinned to the frame grid's extreme
320        // w coordinates so they are distinguishable under RoPE.
321        let a_start = pos.len();
322        push_audio_grid(&mut pos, cursor, audio_t, w_low_t, w_high_t);
323        segments.push(Segment { start: a_start, stop: pos.len(), kind: Kind::Audio });
324
325        // Video: the t axis advances by the per-token frame spans, not
326        // by one; h/w come from the shared frame grid.
327        let v_start = pos.len();
328        let mut t_coord = cursor;
329        for k in 0..latent_t {
330            for &h in &h_axis {
331                for &w in &w_axis {
332                    pos.push([t_coord, h, w]);
333                }
334            }
335            t_coord += FRAME_RESCALE * FRAME_PER_TOKEN[k % 5];
336        }
337        segments.push(Segment { start: v_start, stop: pos.len(), kind: Kind::Video });
338
339        Self {
340            seq_len: pos.len(),
341            segments,
342            pos,
343            text_len,
344            audio_t,
345            latent_t,
346            lat_h,
347            lat_w,
348            frame_rows,
349            text_tags: if text_tags.is_empty() {
350                vec![TAG_TEXT as u8; text_len]
351            } else {
352                text_tags.to_vec()
353            },
354        }
355    }
356
357    /// How many keyframe condition rows the layout carries.
358    pub fn cond_rows(&self) -> usize {
359        self.segments
360            .iter()
361            .filter(|s| s.kind == Kind::Cond)
362            .map(|s| s.stop - s.start)
363            .sum()
364    }
365
366    fn segment(&self, kind: Kind) -> Segment {
367        *self
368            .segments
369            .iter()
370            .find(|s| s.kind == kind)
371            .expect("every layout carries all three streams")
372    }
373}
374
375// ── weights ─────────────────────────────────────────────────────────
376
377struct Adaln {
378    /// [out, rank] — the curve basis, already carrying the LoRA.
379    w: Proj,
380    b: Vec<f32>,
381    /// [grid, rank]
382    table: Vec<f32>,
383    rank: usize,
384    out: usize,
385}
386
387impl Adaln {
388    fn load(model: &Arc<CmfModel>, prefix: &str) -> Result<Self, String> {
389        let w = Proj::from_model(model, &format!("{prefix}.weight"))?;
390        let table = crate::dit::cmf_f32(model, &format!("{prefix}.table"))?;
391        let b = crate::dit::cmf_f32(model, &format!("{prefix}.bias"))?;
392        let out = w.rows();
393        let rank = table.len() / CURVE_GRID;
394        Ok(Self { w, b, table, rank, out })
395    }
396
397    /// The modulation vectors at `ts`: `[ts.len() · MODALITIES, expand ·
398    /// hidden]` laid out so row `t·MODALITIES + tag`, chunk `e`, starts
399    /// at `(t·MODALITIES + tag)·expand·hidden + e·hidden`.
400    fn eval(&self, ts: &[f64], pool: Option<&Pool>) -> Vec<f32> {
401        let g = CURVE_GRID;
402        let mut coords = vec![0f32; ts.len() * self.rank];
403        for (i, &t) in ts.iter().enumerate() {
404            // t → fractional grid index; out-of-range clamps to the ends,
405            // and the last interval is kept whole so t = 1 does not read
406            // past the table.
407            let p = (t.clamp(0.0, 1.0) * (g - 1) as f64) as f32;
408            let i0 = (p.floor() as usize).min(g - 2);
409            let f = p - i0 as f32;
410            for k in 0..self.rank {
411                let (a, b) = (self.table[i0 * self.rank + k], self.table[(i0 + 1) * self.rank + k]);
412                coords[i * self.rank + k] = a + (b - a) * f;
413            }
414        }
415        let mut out = vec![0f32; ts.len() * self.out];
416        self.w.matmat(&coords, ts.len(), &mut out, pool);
417        for row in out.chunks_exact_mut(self.out) {
418            for (v, &bv) in row.iter_mut().zip(&self.b) {
419                *v += bv;
420            }
421        }
422        out
423    }
424}
425
426struct Block {
427    norm1: Vec<f32>,
428    norm2: Vec<f32>,
429    qkv: Proj,   // [3·heads·hd, hidden]
430    out: Proj,   // [hidden, heads·hd]
431    q_norm: Vec<f32>,
432    k_norm: Vec<f32>,
433    fc1: Proj,   // [2·ffn, hidden]
434    fc2: Proj,   // [hidden, ffn]
435    adaln: Option<Adaln>,
436}
437
438pub(crate) const CURVE_GRID: usize = 1025;
439
440pub struct MiniMaxH3 {
441    video_patch: Proj,
442    video_patch_b: Vec<f32>,
443    audio_patch: Proj,
444    audio_patch_b: Vec<f32>,
445    condition: Proj,
446    condition_b: Vec<f32>,
447    refiner: Vec<Block>,
448    refiner_norm: Vec<f32>,
449    blocks: Vec<Block>,
450    final_norm: Vec<f32>,
451    final_adaln: Adaln,
452    video_out: Proj,
453    video_out_b: Vec<f32>,
454    audio_out: Proj,
455    audio_out_b: Vec<f32>,
456    inv_freq: Vec<f32>,
457    pool: Option<Arc<Pool>>,
458    pub hidden: usize,
459    pub heads: usize,
460    pub head_dim: usize,
461    pub ffn: usize,
462    pub latents_dim: usize,
463    pub audio_dim: usize,
464    pub text_dim: usize,
465    pub shift_video: f64,
466    pub shift_audio: f64,
467    eps: f64,
468    qk_eps: f64,
469    final_eps: f64,
470    /// How much of a keyframe latent survives the noise blend, and
471    /// therefore where its rows sit on the schedule. `VISUAL_COND_
472    /// TIMESTEP` is the reference's default; 1.0 turns the blend off.
473    pub cond_aug: f64,
474    /// The same, for a reference soundtrack. The reference's default is
475    /// 1.0 — an audio condition is not noised at all.
476    pub cond_aug_audio: f64,
477}
478
479fn rms_norm_into(x: &[f32], w: &[f32], eps: f64, dst: &mut [f32]) {
480    let ss = x.iter().map(|&v| (v as f64) * (v as f64)).sum::<f64>() / x.len() as f64;
481    let inv = 1.0 / (ss + eps).sqrt();
482    for ((d, &v), &g) in dst.iter_mut().zip(x).zip(w) {
483        *d = (v as f64 * inv) as f32 * g;
484    }
485}
486
487fn silu(v: f32) -> f32 {
488    v / (1.0 + (-v).exp())
489}
490
491impl MiniMaxH3 {
492    pub fn from_cmf(model: &Arc<CmfModel>) -> Result<Self, String> {
493        let cfg: serde_json::Value = serde_json::from_slice(
494            model.tensor_bytes("dit.config_json").map_err(|e| e.to_string())?,
495        )
496        .map_err(|e| format!("dit.config_json: {e}"))?;
497        let u = |k: &str| cfg[k].as_u64().unwrap_or(0) as usize;
498        let f = |k: &str, d: f64| cfg[k].as_f64().unwrap_or(d);
499        let n_blocks = u("num_layers");
500        let n_refiner = u("token_refiner_num_layers");
501        let f32v = |n: &str| crate::dit::cmf_f32(model, n);
502
503        let load_block = |prefix: &str, with_adaln: bool| -> Result<Block, String> {
504            Ok(Block {
505                norm1: f32v(&format!("dit.{prefix}.norm1.weight"))?,
506                norm2: f32v(&format!("dit.{prefix}.norm2.weight"))?,
507                qkv: Proj::from_model(model, &format!("dit.{prefix}.attn.qkv_proj.weight"))?,
508                out: Proj::from_model(model, &format!("dit.{prefix}.attn.out_proj.weight"))?,
509                q_norm: f32v(&format!("dit.{prefix}.attn.q_norm.weight"))?,
510                k_norm: f32v(&format!("dit.{prefix}.attn.k_norm.weight"))?,
511                fc1: Proj::from_model(model, &format!("dit.{prefix}.mlp.fc1.weight"))?,
512                fc2: Proj::from_model(model, &format!("dit.{prefix}.mlp.fc2.weight"))?,
513                adaln: if with_adaln {
514                    Some(Adaln::load(model, &format!("dit.{prefix}.adaln"))?)
515                } else {
516                    None
517                },
518            })
519        };
520        let blocks = (0..n_blocks)
521            .map(|i| load_block(&format!("blocks.{i}"), true))
522            .collect::<Result<Vec<_>, _>>()?;
523        let refiner = (0..n_refiner)
524            .map(|i| load_block(&format!("token_refiner.blocks.{i}"), false))
525            .collect::<Result<Vec<_>, _>>()?;
526
527        Ok(Self {
528            video_patch: Proj::from_model(model, "dit.video_patch_proj.weight")?,
529            video_patch_b: f32v("dit.video_patch_proj.bias")?,
530            audio_patch: Proj::from_model(model, "dit.audio_patch_proj.weight")?,
531            audio_patch_b: f32v("dit.audio_patch_proj.bias")?,
532            condition: Proj::from_model(model, "dit.condition_proj.weight")?,
533            condition_b: f32v("dit.condition_proj.bias")?,
534            refiner,
535            refiner_norm: f32v("dit.token_refiner.final_norm.weight")?,
536            blocks,
537            final_norm: f32v("dit.final_layer.norm.weight")?,
538            final_adaln: Adaln::load(model, "dit.final_layer.adaln")?,
539            video_out: Proj::from_model(model, "dit.final_layer.video_out.weight")?,
540            video_out_b: f32v("dit.final_layer.video_out.bias")?,
541            audio_out: Proj::from_model(model, "dit.final_layer.audio_out.weight")?,
542            audio_out_b: f32v("dit.final_layer.audio_out.bias")?,
543            inv_freq: f32v("dit.rope_inv_freq")?,
544            pool: Pool::from_env(),
545            hidden: u("hidden_size"),
546            heads: u("num_attention_heads"),
547            head_dim: u("attention_head_dim"),
548            ffn: u("ffn_hidden_size"),
549            latents_dim: u("latents_dim"),
550            audio_dim: u("audio_latents_dim"),
551            text_dim: u("text_dim"),
552            shift_video: f("sigma_shift_video", 12.0),
553            shift_audio: f("sigma_shift_audio", 3.0),
554            eps: f("norm_eps", 1e-5),
555            qk_eps: f("qk_norm_eps", 1e-5),
556            final_eps: f("final_norm_eps", 1e-5),
557            cond_aug: VISUAL_COND_TIMESTEP,
558            cond_aug_audio: AUDIO_COND_TIMESTEP,
559        })
560    }
561
562    /// Qwen3-VL states `[n, text_dim]` → refined text embeds
563    /// `[n, hidden]`. Prompt-only, so the caller does this once per
564    /// generation rather than once per step.
565    pub fn refine_text(&self, states: &[f32], n: usize) -> Vec<f32> {
566        let pool = self.pool.as_deref();
567        let mut h = vec![0f32; n * self.hidden];
568        self.condition.matmat(states, n, &mut h, pool);
569        for row in h.chunks_exact_mut(self.hidden) {
570            for (v, &b) in row.iter_mut().zip(&self.condition_b) {
571                *v += b;
572            }
573        }
574        let ids: Vec<[f64; 3]> = Vec::new();
575        for blk in &self.refiner {
576            self.block_forward(blk, &mut h, n, None, &ids, &[]);
577        }
578        let mut out = vec![0f32; n * self.hidden];
579        for (o, x) in out.chunks_exact_mut(self.hidden).zip(h.chunks_exact(self.hidden)) {
580            rms_norm_into(x, &self.refiner_norm, self.final_eps, o);
581        }
582        out
583    }
584
585    /// The rotation angles of every row: `[n, 48]`. Each of the three
586    /// position axes contributes `inv_freq.len()` angles, and the pair
587    /// (j, j+48) of the first 96 head dims rotates by angle j.
588    fn rope_angles(&self, pos: &[[f64; 3]]) -> Vec<f32> {
589        let k = self.inv_freq.len();
590        let mut out = Vec::with_capacity(pos.len() * 3 * k);
591        for p in pos {
592            for axis in 0..3 {
593                for j in 0..k {
594                    out.push((p[axis] * self.inv_freq[j] as f64) as f32);
595                }
596            }
597        }
598        out
599    }
600
601    /// Per-head RMSNorm then the partial split-half rotation, in place.
602    /// `angles` carries 48 angles a row — three axes × 16 frequencies —
603    /// and pair (j, j+48) of the head's first 96 dims turns by angle j.
604    /// Dims 96..128 are not rotated at all, which is the checkpoint's
605    /// own `rope_inv_freq_len` arithmetic and not a truncation.
606    /// In place on a STRIDED view of the fused qkv buffer: `stride` is
607    /// the row pitch and `off` the plane's start. Normalizing q and k
608    /// through a scratch copy cost four full passes over `n·heads·hd`
609    /// per block — 10 G element copies over a render — for arithmetic
610    /// that touches each element once.
611    #[allow(clippy::too_many_arguments)]
612    fn norm_rope_w(
613        &self,
614        v: &mut [f32],
615        n: usize,
616        heads: usize,
617        w: &[f32],
618        angles: &[f32],
619        stride: usize,
620        off: usize,
621    ) {
622        let hd = self.head_dim;
623        let pairs = if angles.is_empty() { 0 } else { angles.len() / n };
624        let pool = self.pool.as_deref();
625        let ptr = SendPtr(v.as_mut_ptr());
626        let work = |lo: usize, hi: usize| {
627            for p in lo..hi {
628                for h in 0..heads {
629                    // SAFETY: workers own disjoint token ranges, and the
630                    // heads of one token are disjoint within it.
631                    let x = unsafe { ptr.row(p * stride + off + h * hd, hd) };
632                    let ss = x.iter().map(|&a| (a as f64) * (a as f64)).sum::<f64>() / hd as f64;
633                    let inv = 1.0 / (ss + self.qk_eps).sqrt();
634                    for (d, &g) in x.iter_mut().zip(w) {
635                        *d = (*d as f64 * inv) as f32 * g;
636                    }
637                    for j in 0..pairs {
638                        let a = angles[p * pairs + j];
639                        let (s, c) = a.sin_cos();
640                        let (lo_v, hi_v) = (x[j], x[j + pairs]);
641                        x[j] = lo_v * c - hi_v * s;
642                        x[j + pairs] = lo_v * s + hi_v * c;
643                    }
644                }
645            }
646        };
647        match pool {
648            Some(pl) => pl.run_rows(n, &work),
649            None => work(0, n),
650        }
651    }
652
653    /// Full bidirectional attention over the packed sequence.
654    fn attention(&self, qkv: &[f32], n: usize, attn: &mut [f32]) {
655        let (nh, hd) = (self.heads, self.head_dim);
656        let inner = nh * hd;
657        let scale = 1.0 / (hd as f32).sqrt();
658        let pool = self.pool.as_deref();
659        let mut qh = vec![0f32; n * hd];
660        let mut kh = vec![0f32; n * hd];
661        let mut vt = vec![0f32; hd * n];
662        let mut scores = vec![0f32; n * n];
663        let mut oh = vec![0f32; n * hd];
664        for h in 0..nh {
665            for p in 0..n {
666                let base = p * 3 * inner;
667                let qs = &qkv[base + h * hd..base + (h + 1) * hd];
668                for (d, &val) in qs.iter().enumerate() {
669                    qh[p * hd + d] = val * scale;
670                }
671                kh[p * hd..(p + 1) * hd]
672                    .copy_from_slice(&qkv[base + inner + h * hd..base + inner + (h + 1) * hd]);
673                let vs = &qkv[base + 2 * inner + h * hd..base + 2 * inner + (h + 1) * hd];
674                for (d, &val) in vs.iter().enumerate() {
675                    vt[d * n + p] = val;
676                }
677            }
678            crate::fcd_ops::gemm_nt(&qh, &kh, &mut scores, n, hd, n, pool);
679            let sp = SendPtr(scores.as_mut_ptr());
680            let soft = |lo: usize, hi: usize| {
681                for r in lo..hi {
682                    // SAFETY: workers own disjoint score rows.
683                    softmax_inplace(unsafe { sp.row(r * n, n) });
684                }
685            };
686            match pool {
687                Some(pl) => pl.run_rows(n, &soft),
688                None => soft(0, n),
689            }
690            crate::fcd_ops::gemm_nt(&scores, &vt, &mut oh, n, n, hd, pool);
691            for p in 0..n {
692                attn[p * inner + h * hd..p * inner + (h + 1) * hd]
693                    .copy_from_slice(&oh[p * hd..(p + 1) * hd]);
694            }
695        }
696    }
697
698    /// One block. `mods` is the block's modulation buffer and `rows` the
699    /// per-token row index into it; both empty for the refiner, which is
700    /// unmodulated and unrotated.
701    fn block_forward(
702        &self,
703        blk: &Block,
704        x: &mut [f32],
705        n: usize,
706        mods: Option<&[f32]>,
707        pos: &[[f64; 3]],
708        rows: &[u32],
709    ) {
710        let hs = self.hidden;
711        let pool = self.pool.as_deref();
712        let inner = self.heads * self.head_dim;
713        let angles = if pos.is_empty() {
714            Vec::new()
715        } else {
716            self.rope_angles(pos)
717        };
718
719        let mut xn = vec![0f32; n * hs];
720        self.norm_rows(&mut xn, x, n, hs, &blk.norm1, self.eps);
721        if let (Some(m), false) = (mods, rows.is_empty()) {
722            self.modulate(&mut xn, hs, m, rows, 0, 1);
723        }
724        let mut qkv = vec![0f32; n * 3 * inner];
725        blk.qkv.matmat(&xn, n, &mut qkv, pool);
726        // q and k are the first two thirds of every row; normalize and
727        // rotate them where they lie, leaving v alone.
728        for (which, w) in [(0usize, &blk.q_norm), (1usize, &blk.k_norm)] {
729            self.norm_rope_w(&mut qkv, n, self.heads, w, &angles, 3 * inner, which * inner);
730        }
731        let mut attn = vec![0f32; n * inner];
732        self.attention(&qkv, n, &mut attn);
733        let mut proj = vec![0f32; n * hs];
734        blk.out.matmat(&attn, n, &mut proj, pool);
735        self.residual(x, hs, &proj, mods, rows, 2);
736
737        self.norm_rows(&mut xn, x, n, hs, &blk.norm2, self.eps);
738        if let (Some(m), false) = (mods, rows.is_empty()) {
739            self.modulate(&mut xn, hs, m, rows, 3, 4);
740        }
741        let mut gu = vec![0f32; n * 2 * self.ffn];
742        blk.fc1.matmat(&xn, n, &mut gu, pool);
743        // SwiGLU: fc1's output is [gate | up] per row.
744        let ffn = self.ffn;
745        let mut act = vec![0f32; n * ffn];
746        let ap = SendPtr(act.as_mut_ptr());
747        pool_rows(pool, n, &|lo, hi| {
748            for p in lo..hi {
749                let row = &gu[p * 2 * ffn..(p + 1) * 2 * ffn];
750                let (g, up) = row.split_at(ffn);
751                // SAFETY: workers own disjoint token ranges.
752                for (o, (&a, &b)) in unsafe { ap.row(p * ffn, ffn) }
753                    .iter_mut()
754                    .zip(g.iter().zip(up))
755                {
756                    *o = silu(a) * b;
757                }
758            }
759        });
760        blk.fc2.matmat(&act, n, &mut proj, pool);
761        self.residual(x, hs, &proj, mods, rows, 5);
762    }
763
764    /// RMSNorm every row of `src` into `dst`, across the pool. One
765    /// block does this four times over `n·hidden`; on a 1 879-token
766    /// pack that is 40 M elements a block, and it was running on one
767    /// thread while forty-seven sat idle.
768    fn norm_rows(&self, dst: &mut [f32], src: &[f32], n: usize, hs: usize, w: &[f32], eps: f64) {
769        let ptr = SendPtr(dst.as_mut_ptr());
770        pool_rows(self.pool.as_deref(), n, &|lo, hi| {
771            for p in lo..hi {
772                // SAFETY: workers own disjoint token ranges.
773                rms_norm_into(&src[p * hs..(p + 1) * hs], w, eps, unsafe {
774                    ptr.row(p * hs, hs)
775                });
776            }
777        });
778    }
779
780    /// `x = x·(1 + scale[row]) + shift[row]`, per token, across the pool.
781    fn modulate(
782        &self,
783        x: &mut [f32],
784        hs: usize,
785        mods: &[f32],
786        rows: &[u32],
787        shift_e: usize,
788        scale_e: usize,
789    ) {
790        let stride = EXPAND * hs;
791        let ptr = SendPtr(x.as_mut_ptr());
792        pool_rows(self.pool.as_deref(), rows.len(), &|lo, hi| {
793            for p in lo..hi {
794                let base = rows[p] as usize * stride;
795                let shift = &mods[base + shift_e * hs..base + (shift_e + 1) * hs];
796                let scale = &mods[base + scale_e * hs..base + (scale_e + 1) * hs];
797                // SAFETY: workers own disjoint token ranges.
798                for ((v, &sc), &sh) in unsafe { ptr.row(p * hs, hs) }
799                    .iter_mut()
800                    .zip(scale)
801                    .zip(shift)
802                {
803                    *v = *v * (1.0 + sc) + sh;
804                }
805            }
806        });
807    }
808
809    /// The gated residual — or a plain one where there is no modulation
810    /// (the token refiner).
811    fn residual(
812        &self,
813        x: &mut [f32],
814        hs: usize,
815        other: &[f32],
816        mods: Option<&[f32]>,
817        rows: &[u32],
818        gate_e: usize,
819    ) {
820        let n = x.len() / hs;
821        let stride = EXPAND * hs;
822        let ptr = SendPtr(x.as_mut_ptr());
823        let gated = mods.filter(|_| !rows.is_empty());
824        pool_rows(self.pool.as_deref(), n, &|lo, hi| {
825            for p in lo..hi {
826                // SAFETY: workers own disjoint token ranges.
827                let row = unsafe { ptr.row(p * hs, hs) };
828                let src = &other[p * hs..(p + 1) * hs];
829                match gated {
830                    Some(m) => {
831                        let base = rows[p] as usize * stride;
832                        let gate = &m[base + gate_e * hs..base + (gate_e + 1) * hs];
833                        for ((v, &g), &o) in row.iter_mut().zip(gate).zip(src) {
834                            *v += g * o;
835                        }
836                    }
837                    None => {
838                        for (v, &o) in row.iter_mut().zip(src) {
839                            *v += o;
840                        }
841                    }
842                }
843            }
844        });
845    }
846
847    /// One denoise evaluation.
848    ///
849    /// `video` is `[latents_dim, latent_t, lat_h, lat_w]` and `audio` is
850    /// `[audio_dim, 2, audio_t]`, both in the reference's channel-major
851    /// order. `text` is the refined `[n, hidden]` stream. Returns the
852    /// two velocities, EACH ON ITS OWN SCHEDULE and unscaled.
853    pub fn forward(
854        &self,
855        layout: &Layout,
856        text: &[f32],
857        video: &[f32],
858        audio: &[f32],
859        sigma_v: f64,
860        cond: &[Vec<f32>],
861    ) -> (Vec<f32>, Vec<f32>) {
862        let hs = self.hidden;
863        let pool = self.pool.as_deref();
864        let sigma_v = sigma_v.max(1e-6);
865        let t_v = 1.0 - sigma_v;
866        let t_a = 1.0 - time_shift_sigma(sigma_v, self.shift_video, self.shift_audio);
867
868        // Distinct timesteps, sorted — the adaLN row index is a position
869        // in this list, so the order is part of the contract. A keyframe
870        // pins its rows near 1: they are conditions, not noise being
871        // removed.
872        let has_cond = layout
873            .segments
874            .iter()
875            .any(|s| matches!(s.kind, Kind::Cond | Kind::RefImg));
876        let has_ref_audio = layout.segments.iter().any(|s| s.kind == Kind::RefAudio);
877        // A reference soundtrack pins to the AUDIO clock's condition
878        // timestep, which is its own number.
879        let t_cond_a = t_a.max(self.cond_aug_audio);
880        // The condition rows' timestep IS the noise-augmentation figure:
881        // the reference blends `aug` of the latent with `1 − aug` of
882        // noise and then tells the block the row sits at `aug`. Turning
883        // the blend off means aug = 1, and the timestep moves with it.
884        let t_cond = t_v.max(self.cond_aug);
885        let mut ts = vec![t_v, t_a];
886        if has_cond {
887            ts.push(t_cond);
888        }
889        if has_ref_audio {
890            ts.push(t_cond_a);
891        }
892        ts.sort_by(|a, b| a.partial_cmp(b).unwrap());
893        ts.dedup();
894        let row_of = |t: f64| ts.iter().position(|&x| x == t).unwrap();
895        let (row_v, row_a) = (row_of(t_v), row_of(t_a));
896        let row_c = if has_cond { row_of(t_cond) } else { 0 };
897        let row_ca = if has_ref_audio { row_of(t_cond_a) } else { 0 };
898
899        // Per-token modulation row: t_row · MODALITIES + tag. The text
900        // span is not uniform once a vision block is in it — those
901        // positions carry the VIDEO tag.
902        let mut rows = vec![0u32; layout.seq_len];
903        for s in &layout.segments {
904            match s.kind {
905                Kind::Text => {
906                    for (i, v) in rows[s.start..s.stop].iter_mut().enumerate() {
907                        let tag = *layout.text_tags.get(i).unwrap_or(&(TAG_TEXT as u8));
908                        *v = (row_v * MODALITIES + tag as usize) as u32;
909                    }
910                }
911                // A reference's rows are conditions too: same timestep
912                // near 1, and the modality of whichever stream they
913                // belong to.
914                Kind::Cond | Kind::RefImg => {
915                    for v in rows[s.start..s.stop].iter_mut() {
916                        *v = (row_c * MODALITIES + TAG_VIDEO) as u32;
917                    }
918                }
919                Kind::RefAudio => {
920                    for v in rows[s.start..s.stop].iter_mut() {
921                        *v = (row_ca * MODALITIES + TAG_AUDIO) as u32;
922                    }
923                }
924                Kind::Video => {
925                    for v in rows[s.start..s.stop].iter_mut() {
926                        *v = (row_v * MODALITIES + TAG_VIDEO) as u32;
927                    }
928                }
929                Kind::Audio => {
930                    for v in rows[s.start..s.stop].iter_mut() {
931                        *v = (row_a * MODALITIES + TAG_AUDIO) as u32;
932                    }
933                }
934            }
935        }
936
937        // ── embed ──
938        let v_rows = patchify_video(video, self.latents_dim, layout.latent_t, layout.lat_h, layout.lat_w);
939        let v_n = v_rows.len() / (self.latents_dim * 4);
940        let a_rows = pack_audio(audio, self.audio_dim, layout.audio_t);
941        // Condition rows go through the SAME patch projection as the
942        // target, so they are patchified the same way — one frame each.
943        let vd = self.latents_dim * 4;
944        let cond_rows: Vec<Vec<f32>> = cond
945            .iter()
946            .enumerate()
947            .map(|(i, z)| {
948                let mut r = patchify_video(z, self.latents_dim, 1, layout.lat_h, layout.lat_w);
949                if self.cond_aug < 1.0 {
950                    // The reference draws this from a torch generator
951                    // reseeded per condition; ours is its own stream, so
952                    // the 0.1% it contributes differs — deliberately, and
953                    // it is 0.1% of a unit normal.
954                    let noise = crate::videogen::gauss_pub(r.len(), 0x5EED ^ i as u64);
955                    let a = self.cond_aug as f32;
956                    for (v, n) in r.iter_mut().zip(&noise) {
957                        *v = a * *v + (1.0 - a) * n;
958                    }
959                }
960                r
961            })
962            .collect();
963
964        let mut h = vec![0f32; layout.seq_len * hs];
965        let mut ci = 0usize;
966        for s in layout.segments.iter().filter(|s| s.kind == Kind::Cond) {
967            let n = s.stop - s.start;
968            let r = cond_rows
969                .get(ci)
970                .unwrap_or_else(|| panic!("layout has {} cond segments, {} latents given", ci + 1, cond_rows.len()));
971            self.video_patch
972                .matmat(r, n, &mut h[s.start * hs..s.stop * hs], pool);
973            for row in h[s.start * hs..s.stop * hs].chunks_exact_mut(hs) {
974                for (v, &b) in row.iter_mut().zip(&self.video_patch_b) {
975                    *v += b;
976                }
977            }
978            ci += 1;
979        }
980        let vseg = layout.segment(Kind::Video);
981        let aseg = layout.segment(Kind::Audio);
982        let tseg = layout.segment(Kind::Text);
983        h[tseg.start * hs..tseg.stop * hs].copy_from_slice(&text[..(tseg.stop - tseg.start) * hs]);
984        self.video_patch.matmat(&v_rows, v_n, &mut h[vseg.start * hs..vseg.stop * hs], pool);
985        self.audio_patch.matmat(
986            &a_rows,
987            aseg.stop - aseg.start,
988            &mut h[aseg.start * hs..aseg.stop * hs],
989            pool,
990        );
991        for row in h[vseg.start * hs..vseg.stop * hs].chunks_exact_mut(hs) {
992            for (v, &b) in row.iter_mut().zip(&self.video_patch_b) {
993                *v += b;
994            }
995        }
996        for row in h[aseg.start * hs..aseg.stop * hs].chunks_exact_mut(hs) {
997            for (v, &b) in row.iter_mut().zip(&self.audio_patch_b) {
998                *v += b;
999            }
1000        }
1001
1002        // ── blocks ──
1003        for (i, blk) in self.blocks.iter().enumerate() {
1004            let mods = blk.adaln.as_ref().unwrap().eval(&ts, pool);
1005            self.block_forward(blk, &mut h, layout.seq_len, Some(&mods), &layout.pos, &rows);
1006            if std::env::var_os("CMF_DIT_PROGRESS").is_some() {
1007                eprint!("\r  block {}/{}", i + 1, self.blocks.len());
1008            }
1009        }
1010
1011        // ── heads ──
1012        let fm = self.final_adaln.eval(&ts, pool);
1013        let mut video_out = vec![0f32; (vseg.stop - vseg.start) * vd];
1014        let mut audio_out = vec![0f32; (aseg.stop - aseg.start) * self.audio_dim];
1015        for (seg, row, w, b, dst, dim) in [
1016            (vseg, row_v, &self.video_out, &self.video_out_b, &mut video_out, vd),
1017            (aseg, row_a, &self.audio_out, &self.audio_out_b, &mut audio_out, self.audio_dim),
1018        ] {
1019            let n = seg.stop - seg.start;
1020            let mut hn = vec![0f32; n * hs];
1021            for (o, src) in hn.chunks_exact_mut(hs).zip(h[seg.start * hs..seg.stop * hs].chunks_exact(hs)) {
1022                rms_norm_into(src, &self.final_norm, self.final_eps, o);
1023            }
1024            // The final layer's adaLN has one modality, so the row IS
1025            // the timestep index.
1026            let shift = &fm[row * 2 * hs..row * 2 * hs + hs];
1027            let scale = &fm[row * 2 * hs + hs..(row + 1) * 2 * hs];
1028            for r in hn.chunks_exact_mut(hs) {
1029                for ((v, &sc), &sh) in r.iter_mut().zip(scale).zip(shift) {
1030                    *v = *v * (1.0 + sc) + sh;
1031                }
1032            }
1033            w.matmat(&hn, n, dst, pool);
1034            for r in dst.chunks_exact_mut(dim) {
1035                for (v, &bv) in r.iter_mut().zip(b.iter()) {
1036                    *v += bv;
1037                }
1038            }
1039        }
1040
1041        // The reference predicts toward the data and the sampler steps
1042        // σ down, hence the sign; the audio velocity is returned on its
1043        // OWN clock rather than pre-scaled by d(σ_a)/d(σ_v).
1044        let video = unpatchify_video(
1045            &video_out,
1046            self.latents_dim,
1047            layout.latent_t,
1048            layout.lat_h,
1049            layout.lat_w,
1050        );
1051        let audio = unpack_audio(&audio_out, self.audio_dim, layout.audio_t);
1052        (
1053            video.iter().map(|&v| -v).collect(),
1054            audio.iter().map(|&v| -v).collect(),
1055        )
1056    }
1057}
1058
1059// ── modulation helpers ──────────────────────────────────────────────
1060
1061/// Rows of `n` items split across pool workers (serial without a pool).
1062fn pool_rows(pool: Option<&Pool>, n: usize, f: &(dyn Fn(usize, usize) + Sync)) {
1063    match pool {
1064        Some(p) => p.run_rows(n, f),
1065        None => f(0, n),
1066    }
1067}
1068
1069// ── stream (un)packing ──────────────────────────────────────────────
1070
1071/// `[C, T, H, W]` → `[T·(H/2)·(W/2), C·4]`, the 2×2 spatial patch
1072/// flattened channel-major-outer as `einsum("nctrhpwq->nthwcrpq")`.
1073pub fn patchify_video(x: &[f32], c: usize, t: usize, h: usize, w: usize) -> Vec<f32> {
1074    let (ph, pw) = (h / 2, w / 2);
1075    let mut out = vec![0f32; t * ph * pw * c * 4];
1076    let mut i = 0;
1077    for ti in 0..t {
1078        for hi in 0..ph {
1079            for wi in 0..pw {
1080                for ci in 0..c {
1081                    for p in 0..2 {
1082                        for q in 0..2 {
1083                            out[i] = x[((ci * t + ti) * h + hi * 2 + p) * w + wi * 2 + q];
1084                            i += 1;
1085                        }
1086                    }
1087                }
1088            }
1089        }
1090    }
1091    out
1092}
1093
1094/// The inverse of `patchify_video`.
1095pub fn unpatchify_video(rows: &[f32], c: usize, t: usize, h: usize, w: usize) -> Vec<f32> {
1096    let (ph, pw) = (h / 2, w / 2);
1097    let mut out = vec![0f32; c * t * h * w];
1098    let mut i = 0;
1099    for ti in 0..t {
1100        for hi in 0..ph {
1101            for wi in 0..pw {
1102                for ci in 0..c {
1103                    for p in 0..2 {
1104                        for q in 0..2 {
1105                            out[((ci * t + ti) * h + hi * 2 + p) * w + wi * 2 + q] = rows[i];
1106                            i += 1;
1107                        }
1108                    }
1109                }
1110            }
1111        }
1112    }
1113    out
1114}
1115
1116/// `[C, 2, T]` → `[2·T, C]`, channel-major: channel 0's frames then
1117/// channel 1's.
1118pub fn pack_audio(x: &[f32], c: usize, t: usize) -> Vec<f32> {
1119    let mut out = vec![0f32; 2 * t * c];
1120    for ch in 0..2 {
1121        for ti in 0..t {
1122            for ci in 0..c {
1123                out[(ch * t + ti) * c + ci] = x[(ci * 2 + ch) * t + ti];
1124            }
1125        }
1126    }
1127    out
1128}
1129
1130/// The inverse of `pack_audio`.
1131pub fn unpack_audio(rows: &[f32], c: usize, t: usize) -> Vec<f32> {
1132    let mut out = vec![0f32; c * 2 * t];
1133    for ch in 0..2 {
1134        for ti in 0..t {
1135            for ci in 0..c {
1136                out[(ci * 2 + ch) * t + ti] = rows[(ch * t + ti) * c + ci];
1137            }
1138        }
1139    }
1140    out
1141}
1142
1143// ── small shared bits ───────────────────────────────────────────────
1144
1145struct SendPtr(*mut f32);
1146unsafe impl Send for SendPtr {}
1147unsafe impl Sync for SendPtr {}
1148impl SendPtr {
1149    /// SAFETY: caller guarantees disjoint `[off, off+len)` per worker.
1150    #[allow(clippy::mut_from_ref)]
1151    unsafe fn row(&self, off: usize, len: usize) -> &mut [f32] {
1152        unsafe { std::slice::from_raw_parts_mut(self.0.add(off), len) }
1153    }
1154}
1155
1156fn softmax_inplace(row: &mut [f32]) {
1157    let mx = row.iter().cloned().fold(f32::MIN, f32::max);
1158    let mut den = 0f32;
1159    for r in row.iter_mut() {
1160        *r = (*r - mx).exp();
1161        den += *r;
1162    }
1163    if den > 0.0 {
1164        let inv = 1.0 / den;
1165        for r in row.iter_mut() {
1166            *r *= inv;
1167        }
1168    }
1169}
1170
1171#[cfg(test)]
1172mod tests {
1173    use super::*;
1174
1175    #[test]
1176    fn schedule_remap_is_an_involution() {
1177        for &s in &[1e-3, 0.25, 0.5, 0.8, 0.972_973, 1.0] {
1178            let a = time_shift_sigma(s, 12.0, 3.0);
1179            let back = time_shift_sigma(a, 3.0, 12.0);
1180            assert!((back - s).abs() < 1e-9, "{s} -> {a} -> {back}");
1181        }
1182    }
1183
1184    #[test]
1185    fn slope_matches_a_finite_difference() {
1186        let h = 1e-6;
1187        for &s in &[0.2, 0.5, 0.9] {
1188            let num = (time_shift_sigma(s + h, 12.0, 3.0) - time_shift_sigma(s - h, 12.0, 3.0))
1189                / (2.0 * h);
1190            let got = time_shift_slope(s, 12.0, 3.0);
1191            assert!((num - got).abs() < 1e-5, "{s}: {num} vs {got}");
1192        }
1193    }
1194
1195    #[test]
1196    fn patchify_round_trips() {
1197        let (c, t, h, w) = (3usize, 2usize, 4usize, 6usize);
1198        let x: Vec<f32> = (0..c * t * h * w).map(|i| i as f32).collect();
1199        let rows = patchify_video(&x, c, t, h, w);
1200        assert_eq!(rows.len(), t * (h / 2) * (w / 2) * c * 4);
1201        assert_eq!(unpatchify_video(&rows, c, t, h, w), x);
1202    }
1203
1204    #[test]
1205    fn audio_pack_round_trips() {
1206        let (c, t) = (5usize, 7usize);
1207        let x: Vec<f32> = (0..c * 2 * t).map(|i| i as f32).collect();
1208        let rows = pack_audio(&x, c, t);
1209        assert_eq!(unpack_audio(&rows, c, t), x);
1210    }
1211
1212    #[test]
1213    fn keyframes_sit_between_the_text_and_the_audio() {
1214        let (tl, lt, lh, lw, at) = (8usize, 3usize, 8usize, 12usize, 5usize);
1215        let base = Layout::t2va(tl, lt, lh, lw, at);
1216        // First and last frame of a 39-frame clip.
1217        let l = Layout::fl2va(tl, lt, lh, lw, at, &[(0, 39), (38, 39)], &[]);
1218        assert_eq!(l.cond_rows(), 2 * l.frame_rows);
1219        assert_eq!(l.seq_len, base.seq_len + 2 * l.frame_rows);
1220        let kinds: Vec<_> = l.segments.iter().map(|s| s.kind).collect();
1221        assert_eq!(
1222            kinds,
1223            vec![Kind::Text, Kind::Cond, Kind::Cond, Kind::Audio, Kind::Video]
1224        );
1225        // A keyframe never advances the cursor: audio and video start
1226        // where they would have without it.
1227        let (a0, v0) = (l.segment(Kind::Audio), l.segment(Kind::Video));
1228        let (ba, bv) = (base.segment(Kind::Audio), base.segment(Kind::Video));
1229        assert_eq!(l.pos[a0.start][0], base.pos[ba.start][0]);
1230        assert_eq!(l.pos[v0.start][0], base.pos[bv.start][0]);
1231        // The first frame's rows sit at the text's end; the last one's a
1232        // whole clip further on, minus one span.
1233        let c: Vec<_> = l.segments.iter().filter(|s| s.kind == Kind::Cond).collect();
1234        assert_eq!(l.pos[c[0].start][0], tl as f64);
1235        let spans: f64 = (0..lt).map(|k| FRAME_RESCALE * FRAME_PER_TOKEN[k % 5]).sum();
1236        assert!((l.pos[c[1].start][0] - (tl as f64 + spans - FRAME_RESCALE)).abs() < 1e-12);
1237        // Both share the TARGET spatial grid.
1238        assert_eq!(l.pos[c[0].start][1], l.pos[v0.start][1]);
1239        assert_eq!(l.pos[c[0].start][2], l.pos[v0.start][2]);
1240    }
1241
1242    #[test]
1243    fn layout_places_the_target_streams_last() {
1244        let l = Layout::t2va(8, 3, 8, 12, 5);
1245        assert_eq!(l.frame_rows, 4 * 6);
1246        assert_eq!(l.seq_len, 8 + 2 * 5 + 3 * 24);
1247        assert_eq!(l.segments[0].kind, Kind::Text);
1248        assert_eq!(l.segments[1].kind, Kind::Audio);
1249        assert_eq!(l.segments[2].kind, Kind::Video);
1250        // The video t axis advances by the 1,4,4,4,4 span pattern.
1251        let v = l.segment(Kind::Video);
1252        let t0 = l.pos[v.start][0];
1253        let t1 = l.pos[v.start + l.frame_rows][0];
1254        assert!((t1 - t0 - FRAME_RESCALE).abs() < 1e-12);
1255    }
1256}