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;
38
39/// Per-phase microseconds of the DiT block, under `CMF_MMH3_PROF=1`:
40/// 0 norm+modulate · 1 qkv GEMM · 2 qk-norm+RoPE · 3 attention ·
41/// 4 out GEMM+residual · 5 fc1 GEMM · 6 SwiGLU · 7 fc2 GEMM.
42pub 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
59/// One line per phase, sorted by cost — the map that says which kernel
60/// to write next.
61pub 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
91/// Frames a video latent token spans, cycling with period 5 — the
92/// checkpoint's temporal grid, which is NOT uniform.
93const FRAME_PER_TOKEN: [f64; 5] = [1.0, 4.0, 4.0, 4.0, 4.0];
94const FRAME_RESCALE: f64 = 5.0 / 3.0;
95
96/// Modality tags, as the adaLN row layout orders them.
97const TAG_VIDEO: usize = 0;
98const TAG_TEXT: usize = 1;
99const TAG_AUDIO: usize = 2;
100const MODALITIES: usize = 3;
101/// shift/scale/gate for attention, then the same three for the MLP.
102const EXPAND: usize = 6;
103/// Where a visual condition row sits on the schedule: all but arrived.
104const VISUAL_COND_TIMESTEP: f64 = 0.999;
105/// An audio condition is not noised at all.
106const AUDIO_COND_TIMESTEP: f64 = 1.0;
107
108// ── the flow-schedule remap ─────────────────────────────────────────
109
110/// σ on `to`'s schedule at the same point of the shared base grid.
111pub 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
116/// d(σ_to)/d(σ_from) at the same base-grid point.
117pub 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// ── the packed layout ───────────────────────────────────────────────
124
125#[derive(Clone, Copy, PartialEq, Debug)]
126pub enum Kind {
127    Text,
128    /// A keyframe's latent, re-injected every step and never denoised.
129    Cond,
130    /// A reference block's video rows — an image, or a clip's frames.
131    RefImg,
132    /// A reference block's audio rows.
133    RefAudio,
134    Audio,
135    Video,
136}
137
138/// One contiguous run of rows sharing a modality tag and a timestep.
139#[derive(Clone, Copy, Debug)]
140pub struct Segment {
141    pub start: usize,
142    pub stop: usize,
143    pub kind: Kind,
144}
145
146/// The static structure of one shape signature: where each stream sits
147/// in the sequence and what 3-D position every row carries.
148pub struct Layout {
149    pub seq_len: usize,
150    pub segments: Vec<Segment>,
151    /// [seq_len, 3] — (t, h, w), f64 because the axes are fractional.
152    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    /// Rows per latent frame after the 2×2 patch.
159    pub frame_rows: usize,
160    /// Per-token modality tag for the text span. A vision block inside
161    /// the prompt carries the VIDEO tag, not the text one.
162    pub text_tags: Vec<u8>,
163}
164
165/// One reference block, in the order it was given.
166#[derive(Clone, Debug)]
167pub enum Ref {
168    /// A still, at its own latent size.
169    Image { lat_h: usize, lat_w: usize },
170    /// Standalone audio, `t` latent frames of it.
171    Audio { t: usize },
172    /// Frames, with an optional soundtrack that packs immediately
173    /// before them and shares their origin.
174    Video {
175        latent_t: usize,
176        lat_h: usize,
177        lat_w: usize,
178        audio_t: usize,
179    },
180}
181
182/// Channel-major stereo rows: `t` frames per channel, the two channels
183/// pinned to the grid's extreme w coordinates so RoPE can tell them
184/// apart, h flat at zero.
185fn 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
194/// `linspace((1 − ratio)/2, (1 + ratio)/2, dim/patch, endpoint=False) · 32`
195fn 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    /// t2va: `[text | audio | video]`, the target streams last and in
205    /// that order. Keyframe and reference blocks would slot between the
206    /// text and the audio; this port does text-to-video only.
207    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    /// `fl2va`: keyframe condition rows sit between the text and the
212    /// audio, sharing the TARGET spatial grid, each pinned to the time
213    /// coordinate of the frame it stands for — the first frame at the
214    /// text's end, the last one a whole clip further on, minus one
215    /// span. They never advance the cursor, so audio and video still
216    /// start where they would have.
217    ///
218    /// `frames` gives each keyframe's pixel index and the clip's total,
219    /// and `text_tags` the per-token modality of the prompt span.
220    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    /// `ref2va`: reference images, audio and clips ahead of the target
233    /// streams. Unlike a keyframe, a reference ADVANCES the cursor —
234    /// each block occupies its own stretch of the time axis, and the
235    /// target audio and video begin after the last of them.
236    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        // Both target streams share this origin: the text runs out at
285        // `text_len` and audio and video start together from there —
286        // unless references push it along.
287        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                    // The block's audio packs immediately BEFORE its
319                    // frames, both from the same origin, and takes its
320                    // w extremes from the block's own grid.
321                    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        // Keyframes, in the order given.
347        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        // Audio is channel-major stereo: every latent frame once per
368        // channel, the two channels pinned to the frame grid's extreme
369        // w coordinates so they are distinguishable under RoPE.
370        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        // Video: the t axis advances by the per-token frame spans, not
375        // by one; h/w come from the shared frame grid.
376        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    /// How many keyframe condition rows the layout carries.
407    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
424// ── weights ─────────────────────────────────────────────────────────
425
426struct Adaln {
427    /// [out, rank] — the curve basis, already carrying the LoRA.
428    w: Proj,
429    b: Vec<f32>,
430    /// [grid, rank]
431    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    /// The modulation vectors at `ts`: `[ts.len() · MODALITIES, expand ·
447    /// hidden]` laid out so row `t·MODALITIES + tag`, chunk `e`, starts
448    /// at `(t·MODALITIES + tag)·expand·hidden + e·hidden`.
449    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            // t → fractional grid index; out-of-range clamps to the ends,
454            // and the last interval is kept whole so t = 1 does not read
455            // past the table.
456            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,   // [3·heads·hd, hidden]
479    out: Proj,   // [hidden, heads·hd]
480    q_norm: Vec<f32>,
481    k_norm: Vec<f32>,
482    fc1: Proj,   // [2·ffn, hidden]
483    fc2: Proj,   // [hidden, ffn]
484    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    /// How much of a keyframe latent survives the noise blend, and
520    /// therefore where its rows sit on the schedule. `VISUAL_COND_
521    /// TIMESTEP` is the reference's default; 1.0 turns the blend off.
522    pub cond_aug: f64,
523    /// The same, for a reference soundtrack. The reference's default is
524    /// 1.0 — an audio condition is not noised at all.
525    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    /// Qwen3-VL states `[n, text_dim]` → refined text embeds
612    /// `[n, hidden]`. Prompt-only, so the caller does this once per
613    /// generation rather than once per step.
614    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    /// The rotation angles of every row: `[n, 48]`. Each of the three
635    /// position axes contributes `inv_freq.len()` angles, and the pair
636    /// (j, j+48) of the first 96 head dims rotates by angle j.
637    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    /// Per-head RMSNorm then the partial split-half rotation, in place.
651    /// `angles` carries 48 angles a row — three axes × 16 frequencies —
652    /// and pair (j, j+48) of the head's first 96 dims turns by angle j.
653    /// Dims 96..128 are not rotated at all, which is the checkpoint's
654    /// own `rope_inv_freq_len` arithmetic and not a truncation.
655    /// In place on a STRIDED view of the fused qkv buffer: `stride` is
656    /// the row pitch and `off` the plane's start. Normalizing q and k
657    /// through a scratch copy cost four full passes over `n·heads·hd`
658    /// per block — 10 G element copies over a render — for arithmetic
659    /// that touches each element once.
660    #[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                    // SAFETY: workers own disjoint token ranges, and the
679                    // heads of one token are disjoint within it.
680                    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    /// Full bidirectional attention over the packed sequence.
703    /// `nr` = (rope angles, q norm weights, k norm weights, eps) when the
704    /// DEVICE should apply qk-norm and RoPE. Passing it means the caller
705    /// skipped `norm_rope_w`, which is the only reason the qkv panel had
706    /// to come back to the host at all.
707    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        // Device path: scores, softmax and the PV product all stay in
720        // device buffers (`dit_qk` → `dit_softmax` → `dit_pv`), so the
721        // n×n score plane — 144 MB at render size — never crosses the
722        // bus. Measured share of a denoise step before this: 41.5%.
723        // The kernels exist and Lumina's DiT already rides them; this
724        // block is what puts MiniMax on the same road. CMF_MMH3_ATTN=cpu
725        // forces the host loop (the A/B that proved the parity).
726        if std::env::var("CMF_MMH3_ATTN").as_deref() != Ok("cpu")
727            && crate::gpu::enabled_here()
728            && n >= 256
729        {
730            // The qkv panel goes up in one piece and is split into
731            // head-major planes ON the card. The host repack below cost
732            // 4.4 s of a 7.3 s attention phase — more than the device
733            // work it fed. Measured at render size: repack 4.4 → 1.5 s,
734            // render 130.0 → 111.8 s, frames bit-identical.
735            // `CMF_MMH3_ATTN=repack` forces the host form.
736            // The device can do qk-norm and RoPE itself (kernel and
737            // plumbing are in; pass Some((angles, q_norm, k_norm, eps))).
738            // `attention` cannot reach them: they live in the caller,
739            // and handing them over means changing this signature — the
740            // last step of task #33, and the point where the panel stops
741            // coming home at all.
742            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                // No stamp: the caller's slot-3 stopwatch already spans
746                // this whole call, and CMF_DIT_ATTN_PROF breaks the
747                // device half into its three walls. Two overlapping
748                // stopwatches on the same work is how a 19.2 s step
749                // came to read as 26.1.
750                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                            // SAFETY: workers own disjoint token ranges,
772                            // and each token writes its own head slots.
773                            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            // Split the phase: the repack above is host work, this call
790            // is device work. The last round wrote a tensor-core QK on
791            // the assumption that the GEMMs dominate and the step did
792            // not move — so measure the halves before writing anything
793            // else. Slot 2 (qknorm+rope, unused on this path) takes the
794            // repack; slot 3 keeps the device call.
795            Self::prof(2, t_repack);
796            // No stamp on the device call: the caller's slot-3 stopwatch
797            // already spans it, and stamping both double-counts.
798            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                    // SAFETY: workers own disjoint score rows.
826                    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    /// Where a denoise step's wall actually goes, in microseconds, under
842    /// `CMF_MMH3_PROF=1`. Optimizing a 60-second step without this is
843    /// guesswork, and guesswork on a rented card is expensive.
844    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    /// One block. `mods` is the block's modulation buffer and `rows` the
852    /// per-token row index into it; both empty for the refiner, which is
853    /// unmodulated and unrotated.
854    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        // The projection writes its panel into a device buffer and the
881        // split reads it THERE — with qk-norm on the device too, nothing
882        // touches that panel on the host, so 160 MB down and the same
883        // back up per block simply stop happening. Measured at render
884        // size: 104.9 → 98.1 s, frames bit-identical.
885        // `CMF_MMH3_FUSEQKV=0` sends the panel home again.
886        let t_qkv = std::time::Instant::now();
887        // Ask whether the device can TAKE the norm, not merely whether a
888        // device exists. Skipping the host loop for a backend with no
889        // packed kernel hands the attention unnormalized q/k and there is
890        // no way back from there — which is why the refusal below used to
891        // be an assert.
892        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            // Best case first: qkv, attention and the output projection
898            // with nothing crossing the bus between them. It refuses at
899            // the door when anything is missing, so falling through to
900            // the chain below never repeats work.
901            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                        // Stamp the same slots the unfused path does, or
965                        // the profile reads 5.5 s for a step the device
966                        // spends 6+ in: an instrument with a blind spot
967                        // is worse than none, and this one has cost two
968                        // wrong conclusions already.
969                        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        // q and k are the first two thirds of every row; normalize and
987        // rotate them where they lie, leaving v alone.
988        // qk-norm and RoPE ride the same device pass that scatters the
989        // panel head-major, so the host neither walks the panel nor
990        // needs it back. Parity holds over a whole render: 1, 2 and 4
991        // steps all match this loop's output exactly (delta 0.000).
992        //
993        // A 7.7% "drift" was measured first and was an artefact — the
994        // reference had been rendered by an EARLIER binary, so the
995        // comparison carried every change since, not this one. Same
996        // binary, both arms, or the number means nothing.
997        // `CMF_MMH3_QKNORM=cpu` restores the host loop.
998        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        // Own slot: the residual is host-side elementwise work with
1025        // modulation, and billing it to the projection hid which of the
1026        // two actually costs (they were 9.8 s together at 512×288).
1027        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    /// The FFN half of a block: norm, modulate, SwiGLU, residual. Its
1035    /// own function so the attention half can return early — the fused
1036    /// path (`dit_qkv_attention`) finishes attention on the device and
1037    /// has nowhere to jump to otherwise.
1038    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            // Device-resident FFN: fc1 → SwiGLU → fc2 without the
1058            // intermediate crossing the bus. At render size that panel is
1059            // hundreds of megabytes each way, per block, per step.
1060            // CMF_MMH3_FFN=cpu forces the host chain below.
1061            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            // SwiGLU: fc1's output is [gate | up] per row.
1085            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                    // SAFETY: workers own disjoint token ranges.
1093                    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    /// RMSNorm every row of `src` into `dst`, across the pool. One
1110    /// block does this four times over `n·hidden`; on a 1 879-token
1111    /// pack that is 40 M elements a block, and it was running on one
1112    /// thread while forty-seven sat idle.
1113    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                // SAFETY: workers own disjoint token ranges.
1118                rms_norm_into(&src[p * hs..(p + 1) * hs], w, eps, unsafe {
1119                    ptr.row(p * hs, hs)
1120                });
1121            }
1122        });
1123    }
1124
1125    /// `x = x·(1 + scale[row]) + shift[row]`, per token, across the pool.
1126    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                // SAFETY: workers own disjoint token ranges.
1143                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    /// The gated residual — or a plain one where there is no modulation
1155    /// (the token refiner).
1156    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                // SAFETY: workers own disjoint token ranges.
1172                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    /// One denoise evaluation.
1193    ///
1194    /// `video` is `[latents_dim, latent_t, lat_h, lat_w]` and `audio` is
1195    /// `[audio_dim, 2, audio_t]`, both in the reference's channel-major
1196    /// order. `text` is the refined `[n, hidden]` stream. Returns the
1197    /// two velocities, EACH ON ITS OWN SCHEDULE and unscaled.
1198    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        // Distinct timesteps, sorted — the adaLN row index is a position
1214        // in this list, so the order is part of the contract. A keyframe
1215        // pins its rows near 1: they are conditions, not noise being
1216        // removed.
1217        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        // A reference soundtrack pins to the AUDIO clock's condition
1223        // timestep, which is its own number.
1224        let t_cond_a = t_a.max(self.cond_aug_audio);
1225        // The condition rows' timestep IS the noise-augmentation figure:
1226        // the reference blends `aug` of the latent with `1 − aug` of
1227        // noise and then tells the block the row sits at `aug`. Turning
1228        // the blend off means aug = 1, and the timestep moves with it.
1229        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        // Per-token modulation row: t_row · MODALITIES + tag. The text
1245        // span is not uniform once a vision block is in it — those
1246        // positions carry the VIDEO tag.
1247        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                // A reference's rows are conditions too: same timestep
1257                // near 1, and the modality of whichever stream they
1258                // belong to.
1259                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        // ── embed ──
1283        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        // Condition rows go through the SAME patch projection as the
1287        // target, so they are patchified the same way — one frame each.
1288        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                    // The reference draws this from a torch generator
1296                    // reseeded per condition; ours is its own stream, so
1297                    // the 0.1% it contributes differs — deliberately, and
1298                    // it is 0.1% of a unit normal.
1299                    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        // ── blocks ──
1348        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            // Per-block watch on the row that dies: growing magnitude is
1352            // an overflow, a sudden jump from finite to NaN is a kernel.
1353            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        // ── heads ──
1369        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            // The final layer's adaLN has one modality, so the row IS
1399            // the timestep index.
1400            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            // `CMF_MMH3_NANPROBE=1`: which side of the head's GEMM a NaN
1408            // is on. The stream that dies is the one to instrument, and
1409            // "the input was already bad" and "this GEMM made it bad" are
1410            // different bugs in different files.
1411            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        // The reference predicts toward the data and the sampler steps
1436        // σ down, hence the sign; the audio velocity is returned on its
1437        // OWN clock rather than pre-scaled by d(σ_a)/d(σ_v).
1438        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
1453// ── modulation helpers ──────────────────────────────────────────────
1454
1455/// Rows of `n` items split across pool workers (serial without a pool).
1456fn 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
1463// ── stream (un)packing ──────────────────────────────────────────────
1464
1465/// `[C, T, H, W]` → `[T·(H/2)·(W/2), C·4]`, the 2×2 spatial patch
1466/// flattened channel-major-outer as `einsum("nctrhpwq->nthwcrpq")`.
1467pub 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
1488/// The inverse of `patchify_video`.
1489pub 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
1510/// `[C, 2, T]` → `[2·T, C]`, channel-major: channel 0's frames then
1511/// channel 1's.
1512pub 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
1524/// The inverse of `pack_audio`.
1525pub 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
1537// ── small shared bits ───────────────────────────────────────────────
1538
1539struct SendPtr(*mut f32);
1540unsafe impl Send for SendPtr {}
1541unsafe impl Sync for SendPtr {}
1542impl SendPtr {
1543    /// SAFETY: caller guarantees disjoint `[off, off+len)` per worker.
1544    #[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        // First and last frame of a 39-frame clip.
1611        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        // A keyframe never advances the cursor: audio and video start
1620        // where they would have without it.
1621        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        // The first frame's rows sit at the text's end; the last one's a
1626        // whole clip further on, minus one span.
1627        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        // Both share the TARGET spatial grid.
1632        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        // The video t axis advances by the 1,4,4,4,4 span pattern.
1645        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}