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",
67        "qkv gemm",
68        "qknorm+rope",
69        "attention",
70        "out gemm",
71        "fc1 gemm",
72        "swiglu",
73        "fc2 gemm",
74        "residual",
75    ];
76    let mut v: Vec<(u64, &str)> = MMH3_PROF
77        .iter()
78        .map(|a| a.load(std::sync::atomic::Ordering::Relaxed))
79        .zip(NAMES)
80        .collect();
81    let total: u64 = v.iter().map(|(us, _)| *us).sum();
82    v.sort_by(|a, b| b.0.cmp(&a.0));
83    let mut out = format!("mmh3 phases (total {:.1} s):", total as f64 / 1e6);
84    for (us, name) in v {
85        out.push_str(&format!(
86            "\n  {name:<14} {:>7.1} s  {:>5.1}%",
87            us as f64 / 1e6,
88            100.0 * us as f64 / total.max(1) as f64
89        ));
90    }
91    Some(out)
92}
93
94use crate::pool::Pool;
95use cortiq_core::CmfModel;
96use std::sync::Arc;
97
98/// Frames a video latent token spans, cycling with period 5 — the
99/// checkpoint's temporal grid, which is NOT uniform.
100const FRAME_PER_TOKEN: [f64; 5] = [1.0, 4.0, 4.0, 4.0, 4.0];
101const FRAME_RESCALE: f64 = 5.0 / 3.0;
102
103/// Modality tags, as the adaLN row layout orders them.
104const TAG_VIDEO: usize = 0;
105const TAG_TEXT: usize = 1;
106const TAG_AUDIO: usize = 2;
107const MODALITIES: usize = 3;
108/// shift/scale/gate for attention, then the same three for the MLP.
109const EXPAND: usize = 6;
110/// Where a visual condition row sits on the schedule: all but arrived.
111const VISUAL_COND_TIMESTEP: f64 = 0.999;
112/// An audio condition is not noised at all.
113const AUDIO_COND_TIMESTEP: f64 = 1.0;
114
115// ── the flow-schedule remap ─────────────────────────────────────────
116
117/// σ on `to`'s schedule at the same point of the shared base grid.
118pub fn time_shift_sigma(sigma: f64, from_shift: f64, to_shift: f64) -> f64 {
119    let base = sigma / (from_shift + sigma * (1.0 - from_shift));
120    to_shift * base / (1.0 + (to_shift - 1.0) * base)
121}
122
123/// d(σ_to)/d(σ_from) at the same base-grid point.
124pub fn time_shift_slope(sigma: f64, from_shift: f64, to_shift: f64) -> f64 {
125    let base = sigma / (from_shift + sigma * (1.0 - from_shift));
126    (to_shift * (1.0 + (from_shift - 1.0) * base).powi(2))
127        / (from_shift * (1.0 + (to_shift - 1.0) * base).powi(2))
128}
129
130// ── the packed layout ───────────────────────────────────────────────
131
132#[derive(Clone, Copy, PartialEq, Debug)]
133pub enum Kind {
134    Text,
135    /// A keyframe's latent, re-injected every step and never denoised.
136    Cond,
137    /// A reference block's video rows — an image, or a clip's frames.
138    RefImg,
139    /// A reference block's audio rows.
140    RefAudio,
141    Audio,
142    Video,
143}
144
145/// One contiguous run of rows sharing a modality tag and a timestep.
146#[derive(Clone, Copy, Debug)]
147pub struct Segment {
148    pub start: usize,
149    pub stop: usize,
150    pub kind: Kind,
151}
152
153/// The static structure of one shape signature: where each stream sits
154/// in the sequence and what 3-D position every row carries.
155pub struct Layout {
156    pub seq_len: usize,
157    pub segments: Vec<Segment>,
158    /// [seq_len, 3] — (t, h, w), f64 because the axes are fractional.
159    pub pos: Vec<[f64; 3]>,
160    pub text_len: usize,
161    pub audio_t: usize,
162    pub latent_t: usize,
163    pub lat_h: usize,
164    pub lat_w: usize,
165    /// Rows per latent frame after the 2×2 patch.
166    pub frame_rows: usize,
167    /// Per-token modality tag for the text span. A vision block inside
168    /// the prompt carries the VIDEO tag, not the text one.
169    pub text_tags: Vec<u8>,
170}
171
172/// One reference block, in the order it was given.
173#[derive(Clone, Debug)]
174pub enum Ref {
175    /// A still, at its own latent size.
176    Image { lat_h: usize, lat_w: usize },
177    /// Standalone audio, `t` latent frames of it.
178    Audio { t: usize },
179    /// Frames, with an optional soundtrack that packs immediately
180    /// before them and shares their origin.
181    Video {
182        latent_t: usize,
183        lat_h: usize,
184        lat_w: usize,
185        audio_t: usize,
186    },
187}
188
189/// Channel-major stereo rows: `t` frames per channel, the two channels
190/// pinned to the grid's extreme w coordinates so RoPE can tell them
191/// apart, h flat at zero.
192fn push_audio_grid(pos: &mut Vec<[f64; 3]>, cursor: f64, t: usize, w_low: f64, w_high: f64) {
193    for ch in 0..2 {
194        let w = if ch == 0 { w_low } else { w_high };
195        for i in 0..t {
196            pos.push([cursor + i as f64, 0.0, w]);
197        }
198    }
199}
200
201/// `linspace((1 − ratio)/2, (1 + ratio)/2, dim/patch, endpoint=False) · 32`
202fn axis_from_sqrt_area(dim: usize, patch: usize, sqrt_area: f64) -> Vec<f64> {
203    let ratio = dim as f64 / sqrt_area;
204    let n = dim / patch;
205    (0..n)
206        .map(|i| (i as f64 * (ratio / n as f64) + (1.0 - ratio) / 2.0) * 32.0)
207        .collect()
208}
209
210impl Layout {
211    /// t2va: `[text | audio | video]`, the target streams last and in
212    /// that order. Keyframe and reference blocks would slot between the
213    /// text and the audio; this port does text-to-video only.
214    pub fn t2va(
215        text_len: usize,
216        latent_t: usize,
217        lat_h: usize,
218        lat_w: usize,
219        audio_t: usize,
220    ) -> Self {
221        Self::build(text_len, latent_t, lat_h, lat_w, audio_t, &[], &[])
222    }
223
224    /// `fl2va`: keyframe condition rows sit between the text and the
225    /// audio, sharing the TARGET spatial grid, each pinned to the time
226    /// coordinate of the frame it stands for — the first frame at the
227    /// text's end, the last one a whole clip further on, minus one
228    /// span. They never advance the cursor, so audio and video still
229    /// start where they would have.
230    ///
231    /// `frames` gives each keyframe's pixel index and the clip's total,
232    /// and `text_tags` the per-token modality of the prompt span.
233    pub fn fl2va(
234        text_len: usize,
235        latent_t: usize,
236        lat_h: usize,
237        lat_w: usize,
238        audio_t: usize,
239        frames: &[(usize, usize)],
240        text_tags: &[u8],
241    ) -> Self {
242        Self::build(text_len, latent_t, lat_h, lat_w, audio_t, frames, text_tags)
243    }
244
245    /// `ref2va`: reference images, audio and clips ahead of the target
246    /// streams. Unlike a keyframe, a reference ADVANCES the cursor —
247    /// each block occupies its own stretch of the time axis, and the
248    /// target audio and video begin after the last of them.
249    pub fn ref2va(
250        text_len: usize,
251        latent_t: usize,
252        lat_h: usize,
253        lat_w: usize,
254        audio_t: usize,
255        refs: &[Ref],
256        text_tags: &[u8],
257    ) -> Self {
258        Self::build_full(
259            text_len,
260            latent_t,
261            lat_h,
262            lat_w,
263            audio_t,
264            &[],
265            refs,
266            text_tags,
267        )
268    }
269
270    fn build(
271        text_len: usize,
272        latent_t: usize,
273        lat_h: usize,
274        lat_w: usize,
275        audio_t: usize,
276        frames: &[(usize, usize)],
277        text_tags: &[u8],
278    ) -> Self {
279        Self::build_full(
280            text_len,
281            latent_t,
282            lat_h,
283            lat_w,
284            audio_t,
285            frames,
286            &[],
287            text_tags,
288        )
289    }
290
291    #[allow(clippy::too_many_arguments)]
292    fn build_full(
293        text_len: usize,
294        latent_t: usize,
295        lat_h: usize,
296        lat_w: usize,
297        audio_t: usize,
298        frames: &[(usize, usize)],
299        refs: &[Ref],
300        text_tags: &[u8],
301    ) -> Self {
302        let area = ((lat_h * lat_w) as f64).sqrt();
303        let h_axis = axis_from_sqrt_area(lat_h, 2, area);
304        let w_axis = axis_from_sqrt_area(lat_w, 2, area);
305        let frame_rows = h_axis.len() * w_axis.len();
306
307        let mut pos: Vec<[f64; 3]> = Vec::new();
308        let mut segments = Vec::new();
309
310        segments.push(Segment {
311            start: 0,
312            stop: text_len,
313            kind: Kind::Text,
314        });
315        for i in 0..text_len {
316            pos.push([i as f64, 0.0, 0.0]);
317        }
318
319        // Both target streams share this origin: the text runs out at
320        // `text_len` and audio and video start together from there —
321        // unless references push it along.
322        let mut cursor = text_len as f64;
323        let (w_low_t, w_high_t) = (w_axis[0], w_axis[w_axis.len() - 1]);
324
325        for r in refs {
326            match r {
327                Ref::Image { lat_h, lat_w } => {
328                    let (rh, rw) = (
329                        axis_from_sqrt_area(*lat_h, 2, ((lat_h * lat_w) as f64).sqrt()),
330                        axis_from_sqrt_area(*lat_w, 2, ((lat_h * lat_w) as f64).sqrt()),
331                    );
332                    let start = pos.len();
333                    for &h in &rh {
334                        for &w in &rw {
335                            pos.push([cursor, h, w]);
336                        }
337                    }
338                    segments.push(Segment {
339                        start,
340                        stop: pos.len(),
341                        kind: Kind::RefImg,
342                    });
343                    cursor += 1.0;
344                }
345                Ref::Audio { t } => {
346                    if *t > 0 {
347                        let start = pos.len();
348                        push_audio_grid(&mut pos, cursor, *t, w_low_t, w_high_t);
349                        segments.push(Segment {
350                            start,
351                            stop: pos.len(),
352                            kind: Kind::RefAudio,
353                        });
354                    }
355                    cursor += *t as f64;
356                }
357                Ref::Video {
358                    latent_t: vt,
359                    lat_h: rh_,
360                    lat_w: rw_,
361                    audio_t: rt,
362                } => {
363                    let area = ((rh_ * rw_) as f64).sqrt();
364                    let rh = axis_from_sqrt_area(*rh_, 2, area);
365                    let rw = axis_from_sqrt_area(*rw_, 2, area);
366                    // The block's audio packs immediately BEFORE its
367                    // frames, both from the same origin, and takes its
368                    // w extremes from the block's own grid.
369                    if *rt > 0 {
370                        let start = pos.len();
371                        push_audio_grid(&mut pos, cursor, *rt, rw[0], rw[rw.len() - 1]);
372                        segments.push(Segment {
373                            start,
374                            stop: pos.len(),
375                            kind: Kind::RefAudio,
376                        });
377                    }
378                    let start = pos.len();
379                    let mut t_coord = cursor;
380                    for k in 0..*vt {
381                        for &h in &rh {
382                            for &w in &rw {
383                                pos.push([t_coord, h, w]);
384                            }
385                        }
386                        t_coord += FRAME_RESCALE * FRAME_PER_TOKEN[k % 5];
387                    }
388                    segments.push(Segment {
389                        start,
390                        stop: pos.len(),
391                        kind: Kind::RefImg,
392                    });
393                    let spans: f64 = (0..*vt)
394                        .map(|k| FRAME_RESCALE * FRAME_PER_TOKEN[k % 5])
395                        .sum();
396                    cursor += (*rt as f64).max(spans);
397                }
398            }
399        }
400        let cursor = cursor;
401
402        // Keyframes, in the order given.
403        let spans: f64 = (0..latent_t)
404            .map(|k| FRAME_RESCALE * FRAME_PER_TOKEN[k % 5])
405            .sum();
406        for &(pixel_index, frame_count) in frames {
407            let cond_t = if pixel_index == 0 {
408                cursor
409            } else if frame_count > 0 && pixel_index == frame_count - 1 {
410                cursor + spans - FRAME_RESCALE
411            } else {
412                panic!("only the first and last frame can anchor a keyframe");
413            };
414            let start = pos.len();
415            for &h in &h_axis {
416                for &w in &w_axis {
417                    pos.push([cond_t, h, w]);
418                }
419            }
420            segments.push(Segment {
421                start,
422                stop: pos.len(),
423                kind: Kind::Cond,
424            });
425        }
426
427        // Audio is channel-major stereo: every latent frame once per
428        // channel, the two channels pinned to the frame grid's extreme
429        // w coordinates so they are distinguishable under RoPE.
430        let a_start = pos.len();
431        push_audio_grid(&mut pos, cursor, audio_t, w_low_t, w_high_t);
432        segments.push(Segment {
433            start: a_start,
434            stop: pos.len(),
435            kind: Kind::Audio,
436        });
437
438        // Video: the t axis advances by the per-token frame spans, not
439        // by one; h/w come from the shared frame grid.
440        let v_start = pos.len();
441        let mut t_coord = cursor;
442        for k in 0..latent_t {
443            for &h in &h_axis {
444                for &w in &w_axis {
445                    pos.push([t_coord, h, w]);
446                }
447            }
448            t_coord += FRAME_RESCALE * FRAME_PER_TOKEN[k % 5];
449        }
450        segments.push(Segment {
451            start: v_start,
452            stop: pos.len(),
453            kind: Kind::Video,
454        });
455
456        Self {
457            seq_len: pos.len(),
458            segments,
459            pos,
460            text_len,
461            audio_t,
462            latent_t,
463            lat_h,
464            lat_w,
465            frame_rows,
466            text_tags: if text_tags.is_empty() {
467                vec![TAG_TEXT as u8; text_len]
468            } else {
469                text_tags.to_vec()
470            },
471        }
472    }
473
474    /// How many keyframe condition rows the layout carries.
475    pub fn cond_rows(&self) -> usize {
476        self.segments
477            .iter()
478            .filter(|s| s.kind == Kind::Cond)
479            .map(|s| s.stop - s.start)
480            .sum()
481    }
482
483    fn segment(&self, kind: Kind) -> Segment {
484        *self
485            .segments
486            .iter()
487            .find(|s| s.kind == kind)
488            .expect("every layout carries all three streams")
489    }
490}
491
492// ── weights ─────────────────────────────────────────────────────────
493
494struct Adaln {
495    /// [out, rank] — the curve basis, already carrying the LoRA.
496    w: Proj,
497    b: Vec<f32>,
498    /// [grid, rank]
499    table: Vec<f32>,
500    rank: usize,
501    out: usize,
502}
503
504impl Adaln {
505    fn load(model: &Arc<CmfModel>, prefix: &str) -> Result<Self, String> {
506        let w = Proj::from_model(model, &format!("{prefix}.weight"))?;
507        let table = crate::dit::cmf_f32(model, &format!("{prefix}.table"))?;
508        let b = crate::dit::cmf_f32(model, &format!("{prefix}.bias"))?;
509        let out = w.rows();
510        let rank = table.len() / CURVE_GRID;
511        Ok(Self {
512            w,
513            b,
514            table,
515            rank,
516            out,
517        })
518    }
519
520    /// The modulation vectors at `ts`: `[ts.len() · MODALITIES, expand ·
521    /// hidden]` laid out so row `t·MODALITIES + tag`, chunk `e`, starts
522    /// at `(t·MODALITIES + tag)·expand·hidden + e·hidden`.
523    fn eval(&self, ts: &[f64], pool: Option<&Pool>) -> Vec<f32> {
524        let g = CURVE_GRID;
525        let mut coords = vec![0f32; ts.len() * self.rank];
526        for (i, &t) in ts.iter().enumerate() {
527            // t → fractional grid index; out-of-range clamps to the ends,
528            // and the last interval is kept whole so t = 1 does not read
529            // past the table.
530            let p = (t.clamp(0.0, 1.0) * (g - 1) as f64) as f32;
531            let i0 = (p.floor() as usize).min(g - 2);
532            let f = p - i0 as f32;
533            for k in 0..self.rank {
534                let (a, b) = (
535                    self.table[i0 * self.rank + k],
536                    self.table[(i0 + 1) * self.rank + k],
537                );
538                coords[i * self.rank + k] = a + (b - a) * f;
539            }
540        }
541        let mut out = vec![0f32; ts.len() * self.out];
542        self.w.matmat(&coords, ts.len(), &mut out, pool);
543        for row in out.chunks_exact_mut(self.out) {
544            for (v, &bv) in row.iter_mut().zip(&self.b) {
545                *v += bv;
546            }
547        }
548        out
549    }
550}
551
552struct Block {
553    norm1: Vec<f32>,
554    norm2: Vec<f32>,
555    qkv: Proj, // [3·heads·hd, hidden]
556    out: Proj, // [hidden, heads·hd]
557    q_norm: Vec<f32>,
558    k_norm: Vec<f32>,
559    fc1: Proj, // [2·ffn, hidden]
560    fc2: Proj, // [hidden, ffn]
561    adaln: Option<Adaln>,
562}
563
564pub(crate) const CURVE_GRID: usize = 1025;
565
566pub struct MiniMaxH3 {
567    video_patch: Proj,
568    video_patch_b: Vec<f32>,
569    audio_patch: Proj,
570    audio_patch_b: Vec<f32>,
571    condition: Proj,
572    condition_b: Vec<f32>,
573    refiner: Vec<Block>,
574    refiner_norm: Vec<f32>,
575    blocks: Vec<Block>,
576    final_norm: Vec<f32>,
577    final_adaln: Adaln,
578    video_out: Proj,
579    video_out_b: Vec<f32>,
580    audio_out: Proj,
581    audio_out_b: Vec<f32>,
582    inv_freq: Vec<f32>,
583    pool: Option<Arc<Pool>>,
584    pub hidden: usize,
585    pub heads: usize,
586    pub head_dim: usize,
587    pub ffn: usize,
588    pub latents_dim: usize,
589    pub audio_dim: usize,
590    pub text_dim: usize,
591    pub shift_video: f64,
592    pub shift_audio: f64,
593    eps: f64,
594    qk_eps: f64,
595    final_eps: f64,
596    /// How much of a keyframe latent survives the noise blend, and
597    /// therefore where its rows sit on the schedule. `VISUAL_COND_
598    /// TIMESTEP` is the reference's default; 1.0 turns the blend off.
599    pub cond_aug: f64,
600    /// The same, for a reference soundtrack. The reference's default is
601    /// 1.0 — an audio condition is not noised at all.
602    pub cond_aug_audio: f64,
603}
604
605fn rms_norm_into(x: &[f32], w: &[f32], eps: f64, dst: &mut [f32]) {
606    let ss = x.iter().map(|&v| (v as f64) * (v as f64)).sum::<f64>() / x.len() as f64;
607    let inv = 1.0 / (ss + eps).sqrt();
608    for ((d, &v), &g) in dst.iter_mut().zip(x).zip(w) {
609        *d = (v as f64 * inv) as f32 * g;
610    }
611}
612
613fn silu(v: f32) -> f32 {
614    v / (1.0 + (-v).exp())
615}
616
617impl MiniMaxH3 {
618    pub fn from_cmf(model: &Arc<CmfModel>) -> Result<Self, String> {
619        let cfg: serde_json::Value = serde_json::from_slice(
620            model
621                .tensor_bytes("dit.config_json")
622                .map_err(|e| e.to_string())?,
623        )
624        .map_err(|e| format!("dit.config_json: {e}"))?;
625        let u = |k: &str| cfg[k].as_u64().unwrap_or(0) as usize;
626        let f = |k: &str, d: f64| cfg[k].as_f64().unwrap_or(d);
627        let n_blocks = u("num_layers");
628        let n_refiner = u("token_refiner_num_layers");
629        let f32v = |n: &str| crate::dit::cmf_f32(model, n);
630
631        let load_block = |prefix: &str, with_adaln: bool| -> Result<Block, String> {
632            Ok(Block {
633                norm1: f32v(&format!("dit.{prefix}.norm1.weight"))?,
634                norm2: f32v(&format!("dit.{prefix}.norm2.weight"))?,
635                qkv: Proj::from_model(model, &format!("dit.{prefix}.attn.qkv_proj.weight"))?,
636                out: Proj::from_model(model, &format!("dit.{prefix}.attn.out_proj.weight"))?,
637                q_norm: f32v(&format!("dit.{prefix}.attn.q_norm.weight"))?,
638                k_norm: f32v(&format!("dit.{prefix}.attn.k_norm.weight"))?,
639                fc1: Proj::from_model(model, &format!("dit.{prefix}.mlp.fc1.weight"))?,
640                fc2: Proj::from_model(model, &format!("dit.{prefix}.mlp.fc2.weight"))?,
641                adaln: if with_adaln {
642                    Some(Adaln::load(model, &format!("dit.{prefix}.adaln"))?)
643                } else {
644                    None
645                },
646            })
647        };
648        let blocks = (0..n_blocks)
649            .map(|i| load_block(&format!("blocks.{i}"), true))
650            .collect::<Result<Vec<_>, _>>()?;
651        let refiner = (0..n_refiner)
652            .map(|i| load_block(&format!("token_refiner.blocks.{i}"), false))
653            .collect::<Result<Vec<_>, _>>()?;
654
655        Ok(Self {
656            video_patch: Proj::from_model(model, "dit.video_patch_proj.weight")?,
657            video_patch_b: f32v("dit.video_patch_proj.bias")?,
658            audio_patch: Proj::from_model(model, "dit.audio_patch_proj.weight")?,
659            audio_patch_b: f32v("dit.audio_patch_proj.bias")?,
660            condition: Proj::from_model(model, "dit.condition_proj.weight")?,
661            condition_b: f32v("dit.condition_proj.bias")?,
662            refiner,
663            refiner_norm: f32v("dit.token_refiner.final_norm.weight")?,
664            blocks,
665            final_norm: f32v("dit.final_layer.norm.weight")?,
666            final_adaln: Adaln::load(model, "dit.final_layer.adaln")?,
667            video_out: Proj::from_model(model, "dit.final_layer.video_out.weight")?,
668            video_out_b: f32v("dit.final_layer.video_out.bias")?,
669            audio_out: Proj::from_model(model, "dit.final_layer.audio_out.weight")?,
670            audio_out_b: f32v("dit.final_layer.audio_out.bias")?,
671            inv_freq: f32v("dit.rope_inv_freq")?,
672            pool: Pool::from_env(),
673            hidden: u("hidden_size"),
674            heads: u("num_attention_heads"),
675            head_dim: u("attention_head_dim"),
676            ffn: u("ffn_hidden_size"),
677            latents_dim: u("latents_dim"),
678            audio_dim: u("audio_latents_dim"),
679            text_dim: u("text_dim"),
680            shift_video: f("sigma_shift_video", 12.0),
681            shift_audio: f("sigma_shift_audio", 3.0),
682            eps: f("norm_eps", 1e-5),
683            qk_eps: f("qk_norm_eps", 1e-5),
684            final_eps: f("final_norm_eps", 1e-5),
685            cond_aug: VISUAL_COND_TIMESTEP,
686            cond_aug_audio: AUDIO_COND_TIMESTEP,
687        })
688    }
689
690    /// Qwen3-VL states `[n, text_dim]` → refined text embeds
691    /// `[n, hidden]`. Prompt-only, so the caller does this once per
692    /// generation rather than once per step.
693    pub fn refine_text(&self, states: &[f32], n: usize) -> Vec<f32> {
694        let pool = self.pool.as_deref();
695        let mut h = vec![0f32; n * self.hidden];
696        self.condition.matmat(states, n, &mut h, pool);
697        for row in h.chunks_exact_mut(self.hidden) {
698            for (v, &b) in row.iter_mut().zip(&self.condition_b) {
699                *v += b;
700            }
701        }
702        let ids: Vec<[f64; 3]> = Vec::new();
703        for blk in &self.refiner {
704            self.block_forward(blk, &mut h, n, None, &ids, &[]);
705        }
706        let mut out = vec![0f32; n * self.hidden];
707        for (o, x) in out
708            .chunks_exact_mut(self.hidden)
709            .zip(h.chunks_exact(self.hidden))
710        {
711            rms_norm_into(x, &self.refiner_norm, self.final_eps, o);
712        }
713        out
714    }
715
716    /// The rotation angles of every row: `[n, 48]`. Each of the three
717    /// position axes contributes `inv_freq.len()` angles, and the pair
718    /// (j, j+48) of the first 96 head dims rotates by angle j.
719    fn rope_angles(&self, pos: &[[f64; 3]]) -> Vec<f32> {
720        let k = self.inv_freq.len();
721        let mut out = Vec::with_capacity(pos.len() * 3 * k);
722        for p in pos {
723            for axis in 0..3 {
724                for j in 0..k {
725                    out.push((p[axis] * self.inv_freq[j] as f64) as f32);
726                }
727            }
728        }
729        out
730    }
731
732    /// Per-head RMSNorm then the partial split-half rotation, in place.
733    /// `angles` carries 48 angles a row — three axes × 16 frequencies —
734    /// and pair (j, j+48) of the head's first 96 dims turns by angle j.
735    /// Dims 96..128 are not rotated at all, which is the checkpoint's
736    /// own `rope_inv_freq_len` arithmetic and not a truncation.
737    /// In place on a STRIDED view of the fused qkv buffer: `stride` is
738    /// the row pitch and `off` the plane's start. Normalizing q and k
739    /// through a scratch copy cost four full passes over `n·heads·hd`
740    /// per block — 10 G element copies over a render — for arithmetic
741    /// that touches each element once.
742    #[allow(clippy::too_many_arguments)]
743    fn norm_rope_w(
744        &self,
745        v: &mut [f32],
746        n: usize,
747        heads: usize,
748        w: &[f32],
749        angles: &[f32],
750        stride: usize,
751        off: usize,
752    ) {
753        let hd = self.head_dim;
754        let pairs = if angles.is_empty() {
755            0
756        } else {
757            angles.len() / n
758        };
759        let pool = self.pool.as_deref();
760        let ptr = SendPtr(v.as_mut_ptr());
761        let work = |lo: usize, hi: usize| {
762            for p in lo..hi {
763                for h in 0..heads {
764                    // SAFETY: workers own disjoint token ranges, and the
765                    // heads of one token are disjoint within it.
766                    let x = unsafe { ptr.row(p * stride + off + h * hd, hd) };
767                    let ss = x.iter().map(|&a| (a as f64) * (a as f64)).sum::<f64>() / hd as f64;
768                    let inv = 1.0 / (ss + self.qk_eps).sqrt();
769                    for (d, &g) in x.iter_mut().zip(w) {
770                        *d = (*d as f64 * inv) as f32 * g;
771                    }
772                    for j in 0..pairs {
773                        let a = angles[p * pairs + j];
774                        let (s, c) = a.sin_cos();
775                        let (lo_v, hi_v) = (x[j], x[j + pairs]);
776                        x[j] = lo_v * c - hi_v * s;
777                        x[j + pairs] = lo_v * s + hi_v * c;
778                    }
779                }
780            }
781        };
782        match pool {
783            Some(pl) => pl.run_rows(n, &work),
784            None => work(0, n),
785        }
786    }
787
788    /// Full bidirectional attention over the packed sequence.
789    /// `nr` = (rope angles, q norm weights, k norm weights, eps) when the
790    /// DEVICE should apply qk-norm and RoPE. Passing it means the caller
791    /// skipped `norm_rope_w`, which is the only reason the qkv panel had
792    /// to come back to the host at all.
793    fn attention(
794        &self,
795        qkv: &[f32],
796        n: usize,
797        attn: &mut [f32],
798        nr: Option<(&[f32], &[f32], &[f32], f32)>,
799    ) {
800        let t_repack = std::time::Instant::now();
801        let (nh, hd) = (self.heads, self.head_dim);
802        let inner = nh * hd;
803        let scale = 1.0 / (hd as f32).sqrt();
804        let pool = self.pool.as_deref();
805        // Device path: scores, softmax and the PV product all stay in
806        // device buffers (`dit_qk` → `dit_softmax` → `dit_pv`), so the
807        // n×n score plane — 144 MB at render size — never crosses the
808        // bus. Measured share of a denoise step before this: 41.5%.
809        // The kernels exist and Lumina's DiT already rides them; this
810        // block is what puts MiniMax on the same road. CMF_MMH3_ATTN=cpu
811        // forces the host loop (the A/B that proved the parity).
812        if std::env::var("CMF_MMH3_ATTN").as_deref() != Ok("cpu")
813            && crate::gpu::enabled_here()
814            && n >= 256
815        {
816            // The qkv panel goes up in one piece and is split into
817            // head-major planes ON the card. The host repack below cost
818            // 4.4 s of a 7.3 s attention phase — more than the device
819            // work it fed. Measured at render size: repack 4.4 → 1.5 s,
820            // render 130.0 → 111.8 s, frames bit-identical.
821            // `CMF_MMH3_ATTN=repack` forces the host form.
822            // The device can do qk-norm and RoPE itself (kernel and
823            // plumbing are in; pass Some((angles, q_norm, k_norm, eps))).
824            // `attention` cannot reach them: they live in the caller,
825            // and handing them over means changing this signature — the
826            // last step of task #33, and the point where the panel stops
827            // coming home at all.
828            if std::env::var("CMF_MMH3_ATTN").as_deref() != Ok("repack")
829                && crate::gpu::dit_attention_packed(qkv, nh, n, hd, scale, nr, attn)
830            {
831                // No stamp: the caller's slot-3 stopwatch already spans
832                // this whole call, and CMF_DIT_ATTN_PROF breaks the
833                // device half into its three walls. Two overlapping
834                // stopwatches on the same work is how a 19.2 s step
835                // came to read as 26.1.
836                return;
837            }
838            assert!(
839                nr.is_none(),
840                "device qk-norm was requested but the device path refused; \
841                 the host loops below expect q/k already normalized"
842            );
843            let mut qh = vec![0f32; nh * n * hd];
844            let mut kh = vec![0f32; nh * n * hd];
845            let mut vh = vec![0f32; nh * n * hd];
846            {
847                let (pq, pk, pv) = (
848                    SendPtr(qh.as_mut_ptr()),
849                    SendPtr(kh.as_mut_ptr()),
850                    SendPtr(vh.as_mut_ptr()),
851                );
852                pool_rows(pool, n, &|lo, hi| {
853                    for p in lo..hi {
854                        let base = p * 3 * inner;
855                        for h in 0..nh {
856                            let dst = (h * n + p) * hd;
857                            // SAFETY: workers own disjoint token ranges,
858                            // and each token writes its own head slots.
859                            unsafe {
860                                pq.row(dst, hd)
861                                    .copy_from_slice(&qkv[base + h * hd..base + (h + 1) * hd]);
862                                pk.row(dst, hd).copy_from_slice(
863                                    &qkv[base + inner + h * hd..base + inner + (h + 1) * hd],
864                                );
865                                pv.row(dst, hd).copy_from_slice(
866                                    &qkv[base + 2 * inner + h * hd
867                                        ..base + 2 * inner + (h + 1) * hd],
868                                );
869                            }
870                        }
871                    }
872                });
873            }
874            // Split the phase: the repack above is host work, this call
875            // is device work. The last round wrote a tensor-core QK on
876            // the assumption that the GEMMs dominate and the step did
877            // not move — so measure the halves before writing anything
878            // else. Slot 2 (qknorm+rope, unused on this path) takes the
879            // repack; slot 3 keeps the device call.
880            Self::prof(2, t_repack);
881            // No stamp on the device call: the caller's slot-3 stopwatch
882            // already spans it, and stamping both double-counts.
883            if crate::gpu::dit_attention(&qh, &kh, &vh, nh, nh, n, hd, scale, attn) {
884                return;
885            }
886        }
887        let mut qh = vec![0f32; n * hd];
888        let mut kh = vec![0f32; n * hd];
889        let mut vt = vec![0f32; hd * n];
890        let mut scores = vec![0f32; n * n];
891        let mut oh = vec![0f32; n * hd];
892        for h in 0..nh {
893            for p in 0..n {
894                let base = p * 3 * inner;
895                let qs = &qkv[base + h * hd..base + (h + 1) * hd];
896                for (d, &val) in qs.iter().enumerate() {
897                    qh[p * hd + d] = val * scale;
898                }
899                kh[p * hd..(p + 1) * hd]
900                    .copy_from_slice(&qkv[base + inner + h * hd..base + inner + (h + 1) * hd]);
901                let vs = &qkv[base + 2 * inner + h * hd..base + 2 * inner + (h + 1) * hd];
902                for (d, &val) in vs.iter().enumerate() {
903                    vt[d * n + p] = val;
904                }
905            }
906            crate::fcd_ops::gemm_nt(&qh, &kh, &mut scores, n, hd, n, pool);
907            let sp = SendPtr(scores.as_mut_ptr());
908            let soft = |lo: usize, hi: usize| {
909                for r in lo..hi {
910                    // SAFETY: workers own disjoint score rows.
911                    softmax_inplace(unsafe { sp.row(r * n, n) });
912                }
913            };
914            match pool {
915                Some(pl) => pl.run_rows(n, &soft),
916                None => soft(0, n),
917            }
918            crate::fcd_ops::gemm_nt(&scores, &vt, &mut oh, n, n, hd, pool);
919            for p in 0..n {
920                attn[p * inner + h * hd..p * inner + (h + 1) * hd]
921                    .copy_from_slice(&oh[p * hd..(p + 1) * hd]);
922            }
923        }
924    }
925
926    /// Where a denoise step's wall actually goes, in microseconds, under
927    /// `CMF_MMH3_PROF=1`. Optimizing a 60-second step without this is
928    /// guesswork, and guesswork on a rented card is expensive.
929    fn prof(slot: usize, t: std::time::Instant) {
930        if !mmh3_prof_on() {
931            return;
932        }
933        MMH3_PROF[slot].fetch_add(
934            t.elapsed().as_micros() as u64,
935            std::sync::atomic::Ordering::Relaxed,
936        );
937    }
938
939    /// One block. `mods` is the block's modulation buffer and `rows` the
940    /// per-token row index into it; both empty for the refiner, which is
941    /// unmodulated and unrotated.
942    fn block_forward(
943        &self,
944        blk: &Block,
945        x: &mut [f32],
946        n: usize,
947        mods: Option<&[f32]>,
948        pos: &[[f64; 3]],
949        rows: &[u32],
950    ) {
951        let hs = self.hidden;
952        let pool = self.pool.as_deref();
953        let inner = self.heads * self.head_dim;
954        let angles = if pos.is_empty() {
955            Vec::new()
956        } else {
957            self.rope_angles(pos)
958        };
959
960        let t = std::time::Instant::now();
961        let mut xn = vec![0f32; n * hs];
962        self.norm_rows(&mut xn, x, n, hs, &blk.norm1, self.eps);
963        if let (Some(m), false) = (mods, rows.is_empty()) {
964            self.modulate(&mut xn, hs, m, rows, 0, 1);
965        }
966        Self::prof(0, t);
967        let t = std::time::Instant::now();
968        // The projection writes its panel into a device buffer and the
969        // split reads it THERE — with qk-norm on the device too, nothing
970        // touches that panel on the host, so 160 MB down and the same
971        // back up per block simply stop happening. Measured at render
972        // size: 104.9 → 98.1 s, frames bit-identical.
973        // `CMF_MMH3_FUSEQKV=0` sends the panel home again.
974        let t_qkv = std::time::Instant::now();
975        // Ask whether the device can TAKE the norm, not merely whether a
976        // device exists. Skipping the host loop for a backend with no
977        // packed kernel hands the attention unnormalized q/k and there is
978        // no way back from there — which is why the refusal below used to
979        // be an assert.
980        let qk_gpu = std::env::var("CMF_MMH3_QKNORM").as_deref() != Ok("cpu")
981            && crate::gpu::enabled_here()
982            && crate::gpu::dit_attention_packed_available()
983            && n >= 256;
984        if qk_gpu && std::env::var("CMF_MMH3_FUSEQKV").as_deref() != Ok("0") {
985            // Best case first: qkv, attention and the output projection
986            // with nothing crossing the bus between them. It refuses at
987            // the door when anything is missing, so falling through to
988            // the chain below never repeats work.
989            let fuse_out = std::env::var("CMF_MMH3_FUSEOUT").as_deref() != Ok("0");
990            if std::env::var("CMF_GPU_DEBUG").is_ok() {
991                static ONCE: std::sync::Once = std::sync::Once::new();
992                ONCE.call_once(|| {
993                    eprintln!(
994                        "mmh3 fuse-out gate: qkv_q={} out_q={} qkv_map={} out_map={}",
995                        matches!(&blk.qkv, Proj::Q(_)),
996                        matches!(&blk.out, Proj::Q(_)),
997                        matches!(&blk.qkv, Proj::Q(q) if q.mapped_q4tp().is_some()),
998                        matches!(&blk.out, Proj::Q(o) if o.mapped_q4tp().is_some()),
999                    )
1000                });
1001            }
1002            if let (true, Proj::Q(q), Proj::Q(o)) = (fuse_out, &blk.qkv, &blk.out) {
1003                if let (Some((m, i)), Some((_, oi))) = (q.mapped_q4tp(), o.mapped_q4tp()) {
1004                    let mut proj = vec![0f32; n * hs];
1005                    if crate::gpu::dit_qkv_attn_out(
1006                        m,
1007                        i,
1008                        oi,
1009                        &xn,
1010                        n,
1011                        hs,
1012                        self.heads,
1013                        self.head_dim,
1014                        1.0 / (self.head_dim as f32).sqrt(),
1015                        (
1016                            &angles[..],
1017                            &blk.q_norm[..],
1018                            &blk.k_norm[..],
1019                            self.qk_eps as f32,
1020                        ),
1021                        &mut proj,
1022                    ) {
1023                        Self::prof(1, t_qkv);
1024                        let t_res = std::time::Instant::now();
1025                        self.residual(x, hs, &proj, mods, rows, 2);
1026                        Self::prof(8, t_res);
1027                        self.ffn_tail(blk, x, n, mods, rows);
1028                        return;
1029                    }
1030                }
1031            }
1032            if let Proj::Q(q) = &blk.qkv {
1033                if let Some((m, i)) = q.mapped_q4tp() {
1034                    let mut attn = vec![0f32; n * inner];
1035                    if crate::gpu::dit_qkv_attention(
1036                        m,
1037                        i,
1038                        &xn,
1039                        n,
1040                        hs,
1041                        self.heads,
1042                        self.head_dim,
1043                        1.0 / (self.head_dim as f32).sqrt(),
1044                        (
1045                            &angles[..],
1046                            &blk.q_norm[..],
1047                            &blk.k_norm[..],
1048                            self.qk_eps as f32,
1049                        ),
1050                        &mut attn,
1051                    ) {
1052                        // Stamp the same slots the unfused path does, or
1053                        // the profile reads 5.5 s for a step the device
1054                        // spends 6+ in: an instrument with a blind spot
1055                        // is worse than none, and this one has cost two
1056                        // wrong conclusions already.
1057                        Self::prof(1, t_qkv);
1058                        let t_out = std::time::Instant::now();
1059                        let mut proj = vec![0f32; n * hs];
1060                        blk.out.matmat(&attn, n, &mut proj, pool);
1061                        Self::prof(4, t_out);
1062                        let t_res = std::time::Instant::now();
1063                        self.residual(x, hs, &proj, mods, rows, 2);
1064                        Self::prof(8, t_res);
1065                        self.ffn_tail(blk, x, n, mods, rows);
1066                        return;
1067                    }
1068                }
1069            }
1070        }
1071        let mut qkv = vec![0f32; n * 3 * inner];
1072        blk.qkv.matmat(&xn, n, &mut qkv, pool);
1073        Self::prof(1, t);
1074        // q and k are the first two thirds of every row; normalize and
1075        // rotate them where they lie, leaving v alone.
1076        // qk-norm and RoPE ride the same device pass that scatters the
1077        // panel head-major, so the host neither walks the panel nor
1078        // needs it back. Parity holds over a whole render: 1, 2 and 4
1079        // steps all match this loop's output exactly (delta 0.000).
1080        //
1081        // A 7.7% "drift" was measured first and was an artefact — the
1082        // reference had been rendered by an EARLIER binary, so the
1083        // comparison carried every change since, not this one. Same
1084        // binary, both arms, or the number means nothing.
1085        // `CMF_MMH3_QKNORM=cpu` restores the host loop.
1086        let qk_on_gpu = qk_gpu;
1087        let t = std::time::Instant::now();
1088        if !qk_on_gpu {
1089            for (which, w) in [(0usize, &blk.q_norm), (1usize, &blk.k_norm)] {
1090                self.norm_rope_w(
1091                    &mut qkv,
1092                    n,
1093                    self.heads,
1094                    w,
1095                    &angles,
1096                    3 * inner,
1097                    which * inner,
1098                );
1099            }
1100        }
1101        Self::prof(2, t);
1102        let t = std::time::Instant::now();
1103        let mut attn = vec![0f32; n * inner];
1104        self.attention(
1105            &qkv,
1106            n,
1107            &mut attn,
1108            qk_on_gpu.then_some((
1109                &angles[..],
1110                &blk.q_norm[..],
1111                &blk.k_norm[..],
1112                self.qk_eps as f32,
1113            )),
1114        );
1115        Self::prof(3, t);
1116        let t = std::time::Instant::now();
1117        let mut proj = vec![0f32; n * hs];
1118        blk.out.matmat(&attn, n, &mut proj, pool);
1119        Self::prof(4, t);
1120        // Own slot: the residual is host-side elementwise work with
1121        // modulation, and billing it to the projection hid which of the
1122        // two actually costs (they were 9.8 s together at 512×288).
1123        let t = std::time::Instant::now();
1124        self.residual(x, hs, &proj, mods, rows, 2);
1125        Self::prof(8, t);
1126
1127        self.ffn_tail(blk, x, n, mods, rows);
1128    }
1129
1130    /// The FFN half of a block: norm, modulate, SwiGLU, residual. Its
1131    /// own function so the attention half can return early — the fused
1132    /// path (`dit_qkv_attention`) finishes attention on the device and
1133    /// has nowhere to jump to otherwise.
1134    fn ffn_tail(&self, blk: &Block, x: &mut [f32], n: usize, mods: Option<&[f32]>, rows: &[u32]) {
1135        let hs = self.hidden;
1136        let pool = self.pool.as_deref();
1137        let mut xn = vec![0f32; n * hs];
1138        let mut proj = vec![0f32; n * hs];
1139        let t = std::time::Instant::now();
1140        self.norm_rows(&mut xn, x, n, hs, &blk.norm2, self.eps);
1141        if let (Some(m), false) = (mods, rows.is_empty()) {
1142            self.modulate(&mut xn, hs, m, rows, 3, 4);
1143        }
1144        Self::prof(0, t);
1145        let t = std::time::Instant::now();
1146        // Device-resident FFN: fc1 → SwiGLU → fc2 without the
1147        // intermediate crossing the bus. At render size that panel is
1148        // hundreds of megabytes each way, per block, per step.
1149        // CMF_MMH3_FFN=cpu forces the host chain below.
1150        if std::env::var("CMF_MMH3_FFN").as_deref() != Ok("cpu")
1151            && crate::gpu::enabled_here()
1152            && n >= 64
1153        {
1154            if let (Proj::Q(q1), Proj::Q(q2)) = (&blk.fc1, &blk.fc2) {
1155                if let (Some((m, i1)), Some((_, i2))) = (q1.mapped_q4tp(), q2.mapped_q4tp()) {
1156                    let mut fout = vec![0f32; n * hs];
1157                    if crate::gpu::q4tp_ffn_packed(m, i1, i2, &xn, n, hs, self.ffn, None, &mut fout)
1158                    {
1159                        Self::prof(5, t);
1160                        self.residual(x, hs, &fout, mods, rows, 5);
1161                        return;
1162                    }
1163                }
1164            }
1165        }
1166        let mut gu = vec![0f32; n * 2 * self.ffn];
1167        blk.fc1.matmat(&xn, n, &mut gu, pool);
1168        Self::prof(5, t);
1169        let t = std::time::Instant::now();
1170        // SwiGLU: fc1's output is [gate | up] per row.
1171        let ffn = self.ffn;
1172        let mut act = vec![0f32; n * ffn];
1173        let ap = SendPtr(act.as_mut_ptr());
1174        pool_rows(pool, n, &|lo, hi| {
1175            for p in lo..hi {
1176                let row = &gu[p * 2 * ffn..(p + 1) * 2 * ffn];
1177                let (g, up) = row.split_at(ffn);
1178                // SAFETY: workers own disjoint token ranges.
1179                for (o, (&a, &b)) in unsafe { ap.row(p * ffn, ffn) }
1180                    .iter_mut()
1181                    .zip(g.iter().zip(up))
1182                {
1183                    *o = silu(a) * b;
1184                }
1185            }
1186        });
1187        Self::prof(6, t);
1188        let t = std::time::Instant::now();
1189        blk.fc2.matmat(&act, n, &mut proj, pool);
1190        Self::prof(7, t);
1191        self.residual(x, hs, &proj, mods, rows, 5);
1192    }
1193
1194    /// RMSNorm every row of `src` into `dst`, across the pool. One
1195    /// block does this four times over `n·hidden`; on a 1 879-token
1196    /// pack that is 40 M elements a block, and it was running on one
1197    /// thread while forty-seven sat idle.
1198    fn norm_rows(&self, dst: &mut [f32], src: &[f32], n: usize, hs: usize, w: &[f32], eps: f64) {
1199        let ptr = SendPtr(dst.as_mut_ptr());
1200        pool_rows(self.pool.as_deref(), n, &|lo, hi| {
1201            for p in lo..hi {
1202                // SAFETY: workers own disjoint token ranges.
1203                rms_norm_into(&src[p * hs..(p + 1) * hs], w, eps, unsafe {
1204                    ptr.row(p * hs, hs)
1205                });
1206            }
1207        });
1208    }
1209
1210    /// `x = x·(1 + scale[row]) + shift[row]`, per token, across the pool.
1211    fn modulate(
1212        &self,
1213        x: &mut [f32],
1214        hs: usize,
1215        mods: &[f32],
1216        rows: &[u32],
1217        shift_e: usize,
1218        scale_e: usize,
1219    ) {
1220        let stride = EXPAND * hs;
1221        let ptr = SendPtr(x.as_mut_ptr());
1222        pool_rows(self.pool.as_deref(), rows.len(), &|lo, hi| {
1223            for p in lo..hi {
1224                let base = rows[p] as usize * stride;
1225                let shift = &mods[base + shift_e * hs..base + (shift_e + 1) * hs];
1226                let scale = &mods[base + scale_e * hs..base + (scale_e + 1) * hs];
1227                // SAFETY: workers own disjoint token ranges.
1228                for ((v, &sc), &sh) in unsafe { ptr.row(p * hs, hs) }
1229                    .iter_mut()
1230                    .zip(scale)
1231                    .zip(shift)
1232                {
1233                    *v = *v * (1.0 + sc) + sh;
1234                }
1235            }
1236        });
1237    }
1238
1239    /// The gated residual — or a plain one where there is no modulation
1240    /// (the token refiner).
1241    fn residual(
1242        &self,
1243        x: &mut [f32],
1244        hs: usize,
1245        other: &[f32],
1246        mods: Option<&[f32]>,
1247        rows: &[u32],
1248        gate_e: usize,
1249    ) {
1250        let n = x.len() / hs;
1251        let stride = EXPAND * hs;
1252        let ptr = SendPtr(x.as_mut_ptr());
1253        let gated = mods.filter(|_| !rows.is_empty());
1254        pool_rows(self.pool.as_deref(), n, &|lo, hi| {
1255            for p in lo..hi {
1256                // SAFETY: workers own disjoint token ranges.
1257                let row = unsafe { ptr.row(p * hs, hs) };
1258                let src = &other[p * hs..(p + 1) * hs];
1259                match gated {
1260                    Some(m) => {
1261                        let base = rows[p] as usize * stride;
1262                        let gate = &m[base + gate_e * hs..base + (gate_e + 1) * hs];
1263                        for ((v, &g), &o) in row.iter_mut().zip(gate).zip(src) {
1264                            *v += g * o;
1265                        }
1266                    }
1267                    None => {
1268                        for (v, &o) in row.iter_mut().zip(src) {
1269                            *v += o;
1270                        }
1271                    }
1272                }
1273            }
1274        });
1275    }
1276
1277    /// One denoise evaluation.
1278    ///
1279    /// `video` is `[latents_dim, latent_t, lat_h, lat_w]` and `audio` is
1280    /// `[audio_dim, 2, audio_t]`, both in the reference's channel-major
1281    /// order. `text` is the refined `[n, hidden]` stream. Returns the
1282    /// two velocities, EACH ON ITS OWN SCHEDULE and unscaled.
1283    pub fn forward(
1284        &self,
1285        layout: &Layout,
1286        text: &[f32],
1287        video: &[f32],
1288        audio: &[f32],
1289        sigma_v: f64,
1290        cond: &[Vec<f32>],
1291    ) -> (Vec<f32>, Vec<f32>) {
1292        let hs = self.hidden;
1293        let pool = self.pool.as_deref();
1294        let sigma_v = sigma_v.max(1e-6);
1295        let t_v = 1.0 - sigma_v;
1296        let t_a = 1.0 - time_shift_sigma(sigma_v, self.shift_video, self.shift_audio);
1297
1298        // Distinct timesteps, sorted — the adaLN row index is a position
1299        // in this list, so the order is part of the contract. A keyframe
1300        // pins its rows near 1: they are conditions, not noise being
1301        // removed.
1302        let has_cond = layout
1303            .segments
1304            .iter()
1305            .any(|s| matches!(s.kind, Kind::Cond | Kind::RefImg));
1306        let has_ref_audio = layout.segments.iter().any(|s| s.kind == Kind::RefAudio);
1307        // A reference soundtrack pins to the AUDIO clock's condition
1308        // timestep, which is its own number.
1309        let t_cond_a = t_a.max(self.cond_aug_audio);
1310        // The condition rows' timestep IS the noise-augmentation figure:
1311        // the reference blends `aug` of the latent with `1 − aug` of
1312        // noise and then tells the block the row sits at `aug`. Turning
1313        // the blend off means aug = 1, and the timestep moves with it.
1314        let t_cond = t_v.max(self.cond_aug);
1315        let mut ts = vec![t_v, t_a];
1316        if has_cond {
1317            ts.push(t_cond);
1318        }
1319        if has_ref_audio {
1320            ts.push(t_cond_a);
1321        }
1322        ts.sort_by(|a, b| a.partial_cmp(b).unwrap());
1323        ts.dedup();
1324        let row_of = |t: f64| ts.iter().position(|&x| x == t).unwrap();
1325        let (row_v, row_a) = (row_of(t_v), row_of(t_a));
1326        let row_c = if has_cond { row_of(t_cond) } else { 0 };
1327        let row_ca = if has_ref_audio { row_of(t_cond_a) } else { 0 };
1328
1329        // Per-token modulation row: t_row · MODALITIES + tag. The text
1330        // span is not uniform once a vision block is in it — those
1331        // positions carry the VIDEO tag.
1332        let mut rows = vec![0u32; layout.seq_len];
1333        for s in &layout.segments {
1334            match s.kind {
1335                Kind::Text => {
1336                    for (i, v) in rows[s.start..s.stop].iter_mut().enumerate() {
1337                        let tag = *layout.text_tags.get(i).unwrap_or(&(TAG_TEXT as u8));
1338                        *v = (row_v * MODALITIES + tag as usize) as u32;
1339                    }
1340                }
1341                // A reference's rows are conditions too: same timestep
1342                // near 1, and the modality of whichever stream they
1343                // belong to.
1344                Kind::Cond | Kind::RefImg => {
1345                    for v in rows[s.start..s.stop].iter_mut() {
1346                        *v = (row_c * MODALITIES + TAG_VIDEO) as u32;
1347                    }
1348                }
1349                Kind::RefAudio => {
1350                    for v in rows[s.start..s.stop].iter_mut() {
1351                        *v = (row_ca * MODALITIES + TAG_AUDIO) as u32;
1352                    }
1353                }
1354                Kind::Video => {
1355                    for v in rows[s.start..s.stop].iter_mut() {
1356                        *v = (row_v * MODALITIES + TAG_VIDEO) as u32;
1357                    }
1358                }
1359                Kind::Audio => {
1360                    for v in rows[s.start..s.stop].iter_mut() {
1361                        *v = (row_a * MODALITIES + TAG_AUDIO) as u32;
1362                    }
1363                }
1364            }
1365        }
1366
1367        // ── embed ──
1368        let v_rows = patchify_video(
1369            video,
1370            self.latents_dim,
1371            layout.latent_t,
1372            layout.lat_h,
1373            layout.lat_w,
1374        );
1375        let v_n = v_rows.len() / (self.latents_dim * 4);
1376        let a_rows = pack_audio(audio, self.audio_dim, layout.audio_t);
1377        // Condition rows go through the SAME patch projection as the
1378        // target, so they are patchified the same way — one frame each.
1379        let vd = self.latents_dim * 4;
1380        let cond_rows: Vec<Vec<f32>> = cond
1381            .iter()
1382            .enumerate()
1383            .map(|(i, z)| {
1384                let mut r = patchify_video(z, self.latents_dim, 1, layout.lat_h, layout.lat_w);
1385                if self.cond_aug < 1.0 {
1386                    // The reference draws this from a torch generator
1387                    // reseeded per condition; ours is its own stream, so
1388                    // the 0.1% it contributes differs — deliberately, and
1389                    // it is 0.1% of a unit normal.
1390                    let noise = crate::videogen::gauss_pub(r.len(), 0x5EED ^ i as u64);
1391                    let a = self.cond_aug as f32;
1392                    for (v, n) in r.iter_mut().zip(&noise) {
1393                        *v = a * *v + (1.0 - a) * n;
1394                    }
1395                }
1396                r
1397            })
1398            .collect();
1399
1400        let mut h = vec![0f32; layout.seq_len * hs];
1401        let mut ci = 0usize;
1402        for s in layout.segments.iter().filter(|s| s.kind == Kind::Cond) {
1403            let n = s.stop - s.start;
1404            let r = cond_rows.get(ci).unwrap_or_else(|| {
1405                panic!(
1406                    "layout has {} cond segments, {} latents given",
1407                    ci + 1,
1408                    cond_rows.len()
1409                )
1410            });
1411            self.video_patch
1412                .matmat(r, n, &mut h[s.start * hs..s.stop * hs], pool);
1413            for row in h[s.start * hs..s.stop * hs].chunks_exact_mut(hs) {
1414                for (v, &b) in row.iter_mut().zip(&self.video_patch_b) {
1415                    *v += b;
1416                }
1417            }
1418            ci += 1;
1419        }
1420        let vseg = layout.segment(Kind::Video);
1421        let aseg = layout.segment(Kind::Audio);
1422        let tseg = layout.segment(Kind::Text);
1423        h[tseg.start * hs..tseg.stop * hs].copy_from_slice(&text[..(tseg.stop - tseg.start) * hs]);
1424        self.video_patch
1425            .matmat(&v_rows, v_n, &mut h[vseg.start * hs..vseg.stop * hs], pool);
1426        self.audio_patch.matmat(
1427            &a_rows,
1428            aseg.stop - aseg.start,
1429            &mut h[aseg.start * hs..aseg.stop * hs],
1430            pool,
1431        );
1432        for row in h[vseg.start * hs..vseg.stop * hs].chunks_exact_mut(hs) {
1433            for (v, &b) in row.iter_mut().zip(&self.video_patch_b) {
1434                *v += b;
1435            }
1436        }
1437        for row in h[aseg.start * hs..aseg.stop * hs].chunks_exact_mut(hs) {
1438            for (v, &b) in row.iter_mut().zip(&self.audio_patch_b) {
1439                *v += b;
1440            }
1441        }
1442
1443        // ── blocks ──
1444        for (i, blk) in self.blocks.iter().enumerate() {
1445            let mods = blk.adaln.as_ref().unwrap().eval(&ts, pool);
1446            self.block_forward(blk, &mut h, layout.seq_len, Some(&mods), &layout.pos, &rows);
1447            // Per-block watch on the row that dies: growing magnitude is
1448            // an overflow, a sudden jump from finite to NaN is a kernel.
1449            if let Ok(w) = std::env::var("CMF_MMH3_WATCHROW") {
1450                if let Ok(r) = w.parse::<usize>() {
1451                    let row = &h[r * hs..(r + 1) * hs];
1452                    let amax = row
1453                        .iter()
1454                        .filter(|v| v.is_finite())
1455                        .fold(0f32, |a, v| a.max(v.abs()));
1456                    let bad = row.iter().filter(|v| !v.is_finite()).count();
1457                    if bad > 0 || i == 0 || amax > 1e3 {
1458                        eprintln!("  watch blk {i}: row {r} absmax {amax:.3e} nonfinite {bad}");
1459                    }
1460                }
1461            }
1462            if std::env::var_os("CMF_DIT_PROGRESS").is_some() {
1463                eprint!("\r  block {}/{}", i + 1, self.blocks.len());
1464            }
1465        }
1466
1467        // ── heads ──
1468        if std::env::var("CMF_MMH3_NANPROBE").is_ok() {
1469            let bad: Vec<usize> = (0..h.len() / hs)
1470                .filter(|r| h[r * hs..(r + 1) * hs].iter().any(|v| !v.is_finite()))
1471                .collect();
1472            if !bad.is_empty() {
1473                eprintln!(
1474                    "  nanprobe rows: {} of {} bad, first 8 {:?}, video seg {}..{}, audio seg {}..{}",
1475                    bad.len(),
1476                    h.len() / hs,
1477                    &bad[..bad.len().min(8)],
1478                    vseg.start,
1479                    vseg.stop,
1480                    aseg.start,
1481                    aseg.stop
1482                );
1483            }
1484        }
1485        let fm = self.final_adaln.eval(&ts, pool);
1486        let mut video_out = vec![0f32; (vseg.stop - vseg.start) * vd];
1487        let mut audio_out = vec![0f32; (aseg.stop - aseg.start) * self.audio_dim];
1488        for (seg, row, w, b, dst, dim) in [
1489            (
1490                vseg,
1491                row_v,
1492                &self.video_out,
1493                &self.video_out_b,
1494                &mut video_out,
1495                vd,
1496            ),
1497            (
1498                aseg,
1499                row_a,
1500                &self.audio_out,
1501                &self.audio_out_b,
1502                &mut audio_out,
1503                self.audio_dim,
1504            ),
1505        ] {
1506            let n = seg.stop - seg.start;
1507            let mut hn = vec![0f32; n * hs];
1508            for (o, src) in hn
1509                .chunks_exact_mut(hs)
1510                .zip(h[seg.start * hs..seg.stop * hs].chunks_exact(hs))
1511            {
1512                rms_norm_into(src, &self.final_norm, self.final_eps, o);
1513            }
1514            // The final layer's adaLN has one modality, so the row IS
1515            // the timestep index.
1516            let shift = &fm[row * 2 * hs..row * 2 * hs + hs];
1517            let scale = &fm[row * 2 * hs + hs..(row + 1) * 2 * hs];
1518            for r in hn.chunks_exact_mut(hs) {
1519                for ((v, &sc), &sh) in r.iter_mut().zip(scale).zip(shift) {
1520                    *v = *v * (1.0 + sc) + sh;
1521                }
1522            }
1523            // `CMF_MMH3_NANPROBE=1`: which side of the head's GEMM a NaN
1524            // is on. The stream that dies is the one to instrument, and
1525            // "the input was already bad" and "this GEMM made it bad" are
1526            // different bugs in different files.
1527            let probe = std::env::var("CMF_MMH3_NANPROBE").is_ok();
1528            if probe {
1529                let bad_in = hn.iter().filter(|v| !v.is_finite()).count();
1530                eprintln!(
1531                    "  nanprobe dim={dim} n={n} in_bad={bad_in} in_absmax={:.4}",
1532                    hn.iter()
1533                        .filter(|v| v.is_finite())
1534                        .fold(0f32, |a, v| a.max(v.abs()))
1535                );
1536            }
1537            w.matmat(&hn, n, dst, pool);
1538            if probe {
1539                let bad_out = dst.iter().filter(|v| !v.is_finite()).count();
1540                eprintln!(
1541                    "  nanprobe dim={dim} out_bad={bad_out}/{} out_absmax={:.4}",
1542                    dst.len(),
1543                    dst.iter()
1544                        .filter(|v| v.is_finite())
1545                        .fold(0f32, |a, v| a.max(v.abs()))
1546                );
1547            }
1548            for r in dst.chunks_exact_mut(dim) {
1549                for (v, &bv) in r.iter_mut().zip(b.iter()) {
1550                    *v += bv;
1551                }
1552            }
1553        }
1554
1555        // The reference predicts toward the data and the sampler steps
1556        // σ down, hence the sign; the audio velocity is returned on its
1557        // OWN clock rather than pre-scaled by d(σ_a)/d(σ_v).
1558        let video = unpatchify_video(
1559            &video_out,
1560            self.latents_dim,
1561            layout.latent_t,
1562            layout.lat_h,
1563            layout.lat_w,
1564        );
1565        let audio = unpack_audio(&audio_out, self.audio_dim, layout.audio_t);
1566        (
1567            video.iter().map(|&v| -v).collect(),
1568            audio.iter().map(|&v| -v).collect(),
1569        )
1570    }
1571}
1572
1573// ── modulation helpers ──────────────────────────────────────────────
1574
1575/// Rows of `n` items split across pool workers (serial without a pool).
1576fn pool_rows(pool: Option<&Pool>, n: usize, f: &(dyn Fn(usize, usize) + Sync)) {
1577    match pool {
1578        Some(p) => p.run_rows(n, f),
1579        None => f(0, n),
1580    }
1581}
1582
1583// ── stream (un)packing ──────────────────────────────────────────────
1584
1585/// `[C, T, H, W]` → `[T·(H/2)·(W/2), C·4]`, the 2×2 spatial patch
1586/// flattened channel-major-outer as `einsum("nctrhpwq->nthwcrpq")`.
1587pub fn patchify_video(x: &[f32], c: usize, t: usize, h: usize, w: usize) -> Vec<f32> {
1588    let (ph, pw) = (h / 2, w / 2);
1589    let mut out = vec![0f32; t * ph * pw * c * 4];
1590    let mut i = 0;
1591    for ti in 0..t {
1592        for hi in 0..ph {
1593            for wi in 0..pw {
1594                for ci in 0..c {
1595                    for p in 0..2 {
1596                        for q in 0..2 {
1597                            out[i] = x[((ci * t + ti) * h + hi * 2 + p) * w + wi * 2 + q];
1598                            i += 1;
1599                        }
1600                    }
1601                }
1602            }
1603        }
1604    }
1605    out
1606}
1607
1608/// The inverse of `patchify_video`.
1609pub fn unpatchify_video(rows: &[f32], c: usize, t: usize, h: usize, w: usize) -> Vec<f32> {
1610    let (ph, pw) = (h / 2, w / 2);
1611    let mut out = vec![0f32; c * t * h * w];
1612    let mut i = 0;
1613    for ti in 0..t {
1614        for hi in 0..ph {
1615            for wi in 0..pw {
1616                for ci in 0..c {
1617                    for p in 0..2 {
1618                        for q in 0..2 {
1619                            out[((ci * t + ti) * h + hi * 2 + p) * w + wi * 2 + q] = rows[i];
1620                            i += 1;
1621                        }
1622                    }
1623                }
1624            }
1625        }
1626    }
1627    out
1628}
1629
1630/// `[C, 2, T]` → `[2·T, C]`, channel-major: channel 0's frames then
1631/// channel 1's.
1632pub fn pack_audio(x: &[f32], c: usize, t: usize) -> Vec<f32> {
1633    let mut out = vec![0f32; 2 * t * c];
1634    for ch in 0..2 {
1635        for ti in 0..t {
1636            for ci in 0..c {
1637                out[(ch * t + ti) * c + ci] = x[(ci * 2 + ch) * t + ti];
1638            }
1639        }
1640    }
1641    out
1642}
1643
1644/// The inverse of `pack_audio`.
1645pub fn unpack_audio(rows: &[f32], c: usize, t: usize) -> Vec<f32> {
1646    let mut out = vec![0f32; c * 2 * t];
1647    for ch in 0..2 {
1648        for ti in 0..t {
1649            for ci in 0..c {
1650                out[(ci * 2 + ch) * t + ti] = rows[(ch * t + ti) * c + ci];
1651            }
1652        }
1653    }
1654    out
1655}
1656
1657// ── small shared bits ───────────────────────────────────────────────
1658
1659struct SendPtr(*mut f32);
1660unsafe impl Send for SendPtr {}
1661unsafe impl Sync for SendPtr {}
1662impl SendPtr {
1663    /// SAFETY: caller guarantees disjoint `[off, off+len)` per worker.
1664    #[allow(clippy::mut_from_ref)]
1665    unsafe fn row(&self, off: usize, len: usize) -> &mut [f32] {
1666        unsafe { std::slice::from_raw_parts_mut(self.0.add(off), len) }
1667    }
1668}
1669
1670fn softmax_inplace(row: &mut [f32]) {
1671    let mx = row.iter().cloned().fold(f32::MIN, f32::max);
1672    let mut den = 0f32;
1673    for r in row.iter_mut() {
1674        *r = (*r - mx).exp();
1675        den += *r;
1676    }
1677    if den > 0.0 {
1678        let inv = 1.0 / den;
1679        for r in row.iter_mut() {
1680            *r *= inv;
1681        }
1682    }
1683}
1684
1685#[cfg(test)]
1686mod tests {
1687    use super::*;
1688
1689    #[test]
1690    fn schedule_remap_is_an_involution() {
1691        for &s in &[1e-3, 0.25, 0.5, 0.8, 0.972_973, 1.0] {
1692            let a = time_shift_sigma(s, 12.0, 3.0);
1693            let back = time_shift_sigma(a, 3.0, 12.0);
1694            assert!((back - s).abs() < 1e-9, "{s} -> {a} -> {back}");
1695        }
1696    }
1697
1698    #[test]
1699    fn slope_matches_a_finite_difference() {
1700        let h = 1e-6;
1701        for &s in &[0.2, 0.5, 0.9] {
1702            let num = (time_shift_sigma(s + h, 12.0, 3.0) - time_shift_sigma(s - h, 12.0, 3.0))
1703                / (2.0 * h);
1704            let got = time_shift_slope(s, 12.0, 3.0);
1705            assert!((num - got).abs() < 1e-5, "{s}: {num} vs {got}");
1706        }
1707    }
1708
1709    #[test]
1710    fn patchify_round_trips() {
1711        let (c, t, h, w) = (3usize, 2usize, 4usize, 6usize);
1712        let x: Vec<f32> = (0..c * t * h * w).map(|i| i as f32).collect();
1713        let rows = patchify_video(&x, c, t, h, w);
1714        assert_eq!(rows.len(), t * (h / 2) * (w / 2) * c * 4);
1715        assert_eq!(unpatchify_video(&rows, c, t, h, w), x);
1716    }
1717
1718    #[test]
1719    fn audio_pack_round_trips() {
1720        let (c, t) = (5usize, 7usize);
1721        let x: Vec<f32> = (0..c * 2 * t).map(|i| i as f32).collect();
1722        let rows = pack_audio(&x, c, t);
1723        assert_eq!(unpack_audio(&rows, c, t), x);
1724    }
1725
1726    #[test]
1727    fn keyframes_sit_between_the_text_and_the_audio() {
1728        let (tl, lt, lh, lw, at) = (8usize, 3usize, 8usize, 12usize, 5usize);
1729        let base = Layout::t2va(tl, lt, lh, lw, at);
1730        // First and last frame of a 39-frame clip.
1731        let l = Layout::fl2va(tl, lt, lh, lw, at, &[(0, 39), (38, 39)], &[]);
1732        assert_eq!(l.cond_rows(), 2 * l.frame_rows);
1733        assert_eq!(l.seq_len, base.seq_len + 2 * l.frame_rows);
1734        let kinds: Vec<_> = l.segments.iter().map(|s| s.kind).collect();
1735        assert_eq!(
1736            kinds,
1737            vec![Kind::Text, Kind::Cond, Kind::Cond, Kind::Audio, Kind::Video]
1738        );
1739        // A keyframe never advances the cursor: audio and video start
1740        // where they would have without it.
1741        let (a0, v0) = (l.segment(Kind::Audio), l.segment(Kind::Video));
1742        let (ba, bv) = (base.segment(Kind::Audio), base.segment(Kind::Video));
1743        assert_eq!(l.pos[a0.start][0], base.pos[ba.start][0]);
1744        assert_eq!(l.pos[v0.start][0], base.pos[bv.start][0]);
1745        // The first frame's rows sit at the text's end; the last one's a
1746        // whole clip further on, minus one span.
1747        let c: Vec<_> = l.segments.iter().filter(|s| s.kind == Kind::Cond).collect();
1748        assert_eq!(l.pos[c[0].start][0], tl as f64);
1749        let spans: f64 = (0..lt)
1750            .map(|k| FRAME_RESCALE * FRAME_PER_TOKEN[k % 5])
1751            .sum();
1752        assert!((l.pos[c[1].start][0] - (tl as f64 + spans - FRAME_RESCALE)).abs() < 1e-12);
1753        // Both share the TARGET spatial grid.
1754        assert_eq!(l.pos[c[0].start][1], l.pos[v0.start][1]);
1755        assert_eq!(l.pos[c[0].start][2], l.pos[v0.start][2]);
1756    }
1757
1758    #[test]
1759    fn layout_places_the_target_streams_last() {
1760        let l = Layout::t2va(8, 3, 8, 12, 5);
1761        assert_eq!(l.frame_rows, 4 * 6);
1762        assert_eq!(l.seq_len, 8 + 2 * 5 + 3 * 24);
1763        assert_eq!(l.segments[0].kind, Kind::Text);
1764        assert_eq!(l.segments[1].kind, Kind::Audio);
1765        assert_eq!(l.segments[2].kind, Kind::Video);
1766        // The video t axis advances by the 1,4,4,4,4 span pattern.
1767        let v = l.segment(Kind::Video);
1768        let t0 = l.pos[v.start][0];
1769        let t1 = l.pos[v.start + l.frame_rows][0];
1770        assert!((t1 - t0 - FRAME_RESCALE).abs() < 1e-12);
1771    }
1772}