Skip to main content

cortiq_engine/
mmh3.rs

1//! MiniMax-H3: the packed-token audio-video DiT.
2//!
3//! One stream of tokens — `[text | audio | video]` — denoised by fifty
4//! blocks of full self-attention, 3-axis RoPE and adaLN modulation, with
5//! the video and audio latents riding different flow schedules and a
6//! separate output head each.
7//!
8//! Three things make it unlike the image DiT next door:
9//!
10//! * **The sequence is packed, not batched.** Text, audio rows and video
11//!   rows sit in one sequence and attend to each other; there is no
12//!   cross-attention. Modulation is what tells a token which modality it
13//!   is: every block emits six modulation vectors for each of three
14//!   modality tags at each of the (at most two, for t2va) distinct
15//!   timesteps, and a segment table says which row each token reads.
16//!
17//! * **adaLN arrives as a curve, not a matrix.** The released weight is
18//!   [96768, 2688] per block — 13 B parameters, 40% of the model — for a
19//!   map whose input is one number. `cortiq animate-pack` collapses it
20//!   onto a rank-24 basis of the timestep curve that already carries the
21//!   Turbo LoRA, so what lands here is [96768, 24] and a [1025, 24]
22//!   table to interpolate. Measured against the full matrix: rms 8.7e-5
23//!   on a signal of rms 0.46.
24//!
25//! * **Two clocks.** The sampler hands in the video sigma; the audio
26//!   stream's own sigma is a closed-form remap of it (shift 12 → 3), and
27//!   the two are integrated separately. `forward` returns both
28//!   velocities unscaled, each on its own schedule — the reference
29//!   returns the audio one pre-multiplied by d(σ_a)/d(σ_v) so that a
30//!   single-schedule sampler is approximately right, which at four steps
31//!   it is not.
32//!
33//! Parity: `tools/mk_mmh3_toy.py` builds a toy checkpoint carrying the
34//! release's real tensor names and a golden forward from ComfyUI's own
35//! module; `tools/mmh3_toy_gate.sh` diffs this port against it.
36
37use crate::dit::Proj;
38use crate::ltxlora::{LoraBank, LoraBranch};
39
40/// Per-phase microseconds of the DiT block, under `CMF_MMH3_PROF=1`:
41/// 0 norm+modulate · 1 qkv GEMM · 2 qk-norm+RoPE · 3 attention ·
42/// 4 out GEMM+residual · 5 fc1 GEMM · 6 SwiGLU · 7 fc2 GEMM.
43pub static MMH3_PROF: [std::sync::atomic::AtomicU64; 9] = [
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    std::sync::atomic::AtomicU64::new(0),
53];
54
55pub(crate) fn mmh3_prof_on() -> bool {
56    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
57    *ON.get_or_init(|| std::env::var("CMF_MMH3_PROF").is_ok())
58}
59
60/// One line per phase, sorted by cost — the map that says which kernel
61/// to write next.
62pub fn mmh3_prof_report() -> Option<String> {
63    if !mmh3_prof_on() {
64        return None;
65    }
66    const NAMES: [&str; 9] = [
67        "norm+mod",
68        "qkv gemm",
69        "qknorm+rope",
70        "attention",
71        "out gemm",
72        "fc1 gemm",
73        "swiglu",
74        "fc2 gemm",
75        "residual",
76    ];
77    let mut v: Vec<(u64, &str)> = MMH3_PROF
78        .iter()
79        .map(|a| a.load(std::sync::atomic::Ordering::Relaxed))
80        .zip(NAMES)
81        .collect();
82    let total: u64 = v.iter().map(|(us, _)| *us).sum();
83    v.sort_by(|a, b| b.0.cmp(&a.0));
84    let mut out = format!("mmh3 phases (total {:.1} s):", total as f64 / 1e6);
85    for (us, name) in v {
86        out.push_str(&format!(
87            "\n  {name:<14} {:>7.1} s  {:>5.1}%",
88            us as f64 / 1e6,
89            100.0 * us as f64 / total.max(1) as f64
90        ));
91    }
92    Some(out)
93}
94
95use crate::pool::Pool;
96use cortiq_core::CmfModel;
97use std::sync::Arc;
98
99/// Frames a video latent token spans, cycling with period 5 — the
100/// checkpoint's temporal grid, which is NOT uniform.
101const FRAME_PER_TOKEN: [f64; 5] = [1.0, 4.0, 4.0, 4.0, 4.0];
102const FRAME_RESCALE: f64 = 5.0 / 3.0;
103
104/// Modality tags, as the adaLN row layout orders them.
105const TAG_VIDEO: usize = 0;
106const TAG_TEXT: usize = 1;
107const TAG_AUDIO: usize = 2;
108const MODALITIES: usize = 3;
109/// shift/scale/gate for attention, then the same three for the MLP.
110const EXPAND: usize = 6;
111/// Where a visual condition row sits on the schedule: all but arrived.
112const VISUAL_COND_TIMESTEP: f64 = 0.999;
113/// An audio condition is not noised at all.
114const AUDIO_COND_TIMESTEP: f64 = 1.0;
115
116// ── the flow-schedule remap ─────────────────────────────────────────
117
118/// σ on `to`'s schedule at the same point of the shared base grid.
119pub fn time_shift_sigma(sigma: f64, from_shift: f64, to_shift: f64) -> f64 {
120    let base = sigma / (from_shift + sigma * (1.0 - from_shift));
121    to_shift * base / (1.0 + (to_shift - 1.0) * base)
122}
123
124/// d(σ_to)/d(σ_from) at the same base-grid point.
125pub fn time_shift_slope(sigma: f64, from_shift: f64, to_shift: f64) -> f64 {
126    let base = sigma / (from_shift + sigma * (1.0 - from_shift));
127    (to_shift * (1.0 + (from_shift - 1.0) * base).powi(2))
128        / (from_shift * (1.0 + (to_shift - 1.0) * base).powi(2))
129}
130
131// ── the packed layout ───────────────────────────────────────────────
132
133#[derive(Clone, Copy, PartialEq, Debug)]
134pub enum Kind {
135    Text,
136    /// A keyframe's latent, re-injected every step and never denoised.
137    Cond,
138    /// A reference block's video rows — an image, or a clip's frames.
139    RefImg,
140    /// A reference block's audio rows.
141    RefAudio,
142    Audio,
143    Video,
144    /// Streaming context: video rows of an already-generated chunk, fed
145    /// back as the student's own x0 at timestep 0. Not denoised, not
146    /// decoded — they exist so the current chunk can attend to them.
147    CtxVideo,
148    /// The same for audio.
149    CtxAudio,
150}
151
152/// One contiguous run of rows sharing a modality tag and a timestep.
153#[derive(Clone, Copy, Debug)]
154pub struct Segment {
155    pub start: usize,
156    pub stop: usize,
157    pub kind: Kind,
158}
159
160/// The static structure of one shape signature: where each stream sits
161/// in the sequence and what 3-D position every row carries.
162pub struct Layout {
163    pub seq_len: usize,
164    pub segments: Vec<Segment>,
165    /// [seq_len, 3] — (t, h, w), f64 because the axes are fractional.
166    pub pos: Vec<[f64; 3]>,
167    pub text_len: usize,
168    pub audio_t: usize,
169    pub latent_t: usize,
170    pub lat_h: usize,
171    pub lat_w: usize,
172    /// Rows per latent frame after the 2×2 patch.
173    pub frame_rows: usize,
174    /// Per-token modality tag for the text span. A vision block inside
175    /// the prompt carries the VIDEO tag, not the text one.
176    pub text_tags: Vec<u8>,
177}
178
179/// One reference block, in the order it was given.
180#[derive(Clone, Debug)]
181pub enum Ref {
182    /// A still, at its own latent size.
183    Image { lat_h: usize, lat_w: usize },
184    /// Standalone audio, `t` latent frames of it.
185    Audio { t: usize },
186    /// Frames, with an optional soundtrack that packs immediately
187    /// before them and shares their origin.
188    Video {
189        latent_t: usize,
190        lat_h: usize,
191        lat_w: usize,
192        audio_t: usize,
193    },
194}
195
196/// Channel-major stereo rows: `t` frames per channel, the two channels
197/// pinned to the grid's extreme w coordinates so RoPE can tell them
198/// apart, h flat at zero.
199fn push_audio_grid(pos: &mut Vec<[f64; 3]>, cursor: f64, t: usize, w_low: f64, w_high: f64) {
200    for ch in 0..2 {
201        let w = if ch == 0 { w_low } else { w_high };
202        for i in 0..t {
203            pos.push([cursor + i as f64, 0.0, w]);
204        }
205    }
206}
207
208/// `linspace((1 − ratio)/2, (1 + ratio)/2, dim/patch, endpoint=False) · 32`
209fn axis_from_sqrt_area(dim: usize, patch: usize, sqrt_area: f64) -> Vec<f64> {
210    let ratio = dim as f64 / sqrt_area;
211    let n = dim / patch;
212    (0..n)
213        .map(|i| (i as f64 * (ratio / n as f64) + (1.0 - ratio) / 2.0) * 32.0)
214        .collect()
215}
216
217impl Layout {
218    /// t2va: `[text | audio | video]`, the target streams last and in
219    /// that order. Keyframe and reference blocks would slot between the
220    /// text and the audio; this port does text-to-video only.
221    pub fn t2va(
222        text_len: usize,
223        latent_t: usize,
224        lat_h: usize,
225        lat_w: usize,
226        audio_t: usize,
227    ) -> Self {
228        Self::build(text_len, latent_t, lat_h, lat_w, audio_t, &[], &[])
229    }
230
231    /// `fl2va`: keyframe condition rows sit between the text and the
232    /// audio, sharing the TARGET spatial grid, each pinned to the time
233    /// coordinate of the frame it stands for — the first frame at the
234    /// text's end, the last one a whole clip further on, minus one
235    /// span. They never advance the cursor, so audio and video still
236    /// start where they would have.
237    ///
238    /// `frames` gives each keyframe's pixel index and the clip's total,
239    /// and `text_tags` the per-token modality of the prompt span.
240    pub fn fl2va(
241        text_len: usize,
242        latent_t: usize,
243        lat_h: usize,
244        lat_w: usize,
245        audio_t: usize,
246        frames: &[(usize, usize)],
247        text_tags: &[u8],
248    ) -> Self {
249        Self::build(text_len, latent_t, lat_h, lat_w, audio_t, frames, text_tags)
250    }
251
252    /// `ref2va`: reference images, audio and clips ahead of the target
253    /// streams. Unlike a keyframe, a reference ADVANCES the cursor —
254    /// each block occupies its own stretch of the time axis, and the
255    /// target audio and video begin after the last of them.
256    pub fn ref2va(
257        text_len: usize,
258        latent_t: usize,
259        lat_h: usize,
260        lat_w: usize,
261        audio_t: usize,
262        refs: &[Ref],
263        text_tags: &[u8],
264    ) -> Self {
265        Self::build_full(
266            text_len,
267            latent_t,
268            lat_h,
269            lat_w,
270            audio_t,
271            &[],
272            refs,
273            text_tags,
274        )
275    }
276
277    fn build(
278        text_len: usize,
279        latent_t: usize,
280        lat_h: usize,
281        lat_w: usize,
282        audio_t: usize,
283        frames: &[(usize, usize)],
284        text_tags: &[u8],
285    ) -> Self {
286        Self::build_full(
287            text_len,
288            latent_t,
289            lat_h,
290            lat_w,
291            audio_t,
292            frames,
293            &[],
294            text_tags,
295        )
296    }
297
298    #[allow(clippy::too_many_arguments)]
299    fn build_full(
300        text_len: usize,
301        latent_t: usize,
302        lat_h: usize,
303        lat_w: usize,
304        audio_t: usize,
305        frames: &[(usize, usize)],
306        refs: &[Ref],
307        text_tags: &[u8],
308    ) -> Self {
309        let area = ((lat_h * lat_w) as f64).sqrt();
310        let h_axis = axis_from_sqrt_area(lat_h, 2, area);
311        let w_axis = axis_from_sqrt_area(lat_w, 2, area);
312        let frame_rows = h_axis.len() * w_axis.len();
313
314        let mut pos: Vec<[f64; 3]> = Vec::new();
315        let mut segments = Vec::new();
316
317        segments.push(Segment {
318            start: 0,
319            stop: text_len,
320            kind: Kind::Text,
321        });
322        for i in 0..text_len {
323            pos.push([i as f64, 0.0, 0.0]);
324        }
325
326        // Both target streams share this origin: the text runs out at
327        // `text_len` and audio and video start together from there —
328        // unless references push it along.
329        let mut cursor = text_len as f64;
330        let (w_low_t, w_high_t) = (w_axis[0], w_axis[w_axis.len() - 1]);
331
332        for r in refs {
333            match r {
334                Ref::Image { lat_h, lat_w } => {
335                    let (rh, rw) = (
336                        axis_from_sqrt_area(*lat_h, 2, ((lat_h * lat_w) as f64).sqrt()),
337                        axis_from_sqrt_area(*lat_w, 2, ((lat_h * lat_w) as f64).sqrt()),
338                    );
339                    let start = pos.len();
340                    for &h in &rh {
341                        for &w in &rw {
342                            pos.push([cursor, h, w]);
343                        }
344                    }
345                    segments.push(Segment {
346                        start,
347                        stop: pos.len(),
348                        kind: Kind::RefImg,
349                    });
350                    cursor += 1.0;
351                }
352                Ref::Audio { t } => {
353                    if *t > 0 {
354                        let start = pos.len();
355                        push_audio_grid(&mut pos, cursor, *t, w_low_t, w_high_t);
356                        segments.push(Segment {
357                            start,
358                            stop: pos.len(),
359                            kind: Kind::RefAudio,
360                        });
361                    }
362                    cursor += *t as f64;
363                }
364                Ref::Video {
365                    latent_t: vt,
366                    lat_h: rh_,
367                    lat_w: rw_,
368                    audio_t: rt,
369                } => {
370                    let area = ((rh_ * rw_) as f64).sqrt();
371                    let rh = axis_from_sqrt_area(*rh_, 2, area);
372                    let rw = axis_from_sqrt_area(*rw_, 2, area);
373                    // The block's audio packs immediately BEFORE its
374                    // frames, both from the same origin, and takes its
375                    // w extremes from the block's own grid.
376                    if *rt > 0 {
377                        let start = pos.len();
378                        push_audio_grid(&mut pos, cursor, *rt, rw[0], rw[rw.len() - 1]);
379                        segments.push(Segment {
380                            start,
381                            stop: pos.len(),
382                            kind: Kind::RefAudio,
383                        });
384                    }
385                    let start = pos.len();
386                    let mut t_coord = cursor;
387                    for k in 0..*vt {
388                        for &h in &rh {
389                            for &w in &rw {
390                                pos.push([t_coord, h, w]);
391                            }
392                        }
393                        t_coord += FRAME_RESCALE * FRAME_PER_TOKEN[k % 5];
394                    }
395                    segments.push(Segment {
396                        start,
397                        stop: pos.len(),
398                        kind: Kind::RefImg,
399                    });
400                    let spans: f64 = (0..*vt)
401                        .map(|k| FRAME_RESCALE * FRAME_PER_TOKEN[k % 5])
402                        .sum();
403                    cursor += (*rt as f64).max(spans);
404                }
405            }
406        }
407        let cursor = cursor;
408
409        // Keyframes, in the order given.
410        let spans: f64 = (0..latent_t)
411            .map(|k| FRAME_RESCALE * FRAME_PER_TOKEN[k % 5])
412            .sum();
413        for &(pixel_index, frame_count) in frames {
414            let cond_t = if pixel_index == 0 {
415                cursor
416            } else if frame_count > 0 && pixel_index == frame_count - 1 {
417                cursor + spans - FRAME_RESCALE
418            } else {
419                panic!("only the first and last frame can anchor a keyframe");
420            };
421            let start = pos.len();
422            for &h in &h_axis {
423                for &w in &w_axis {
424                    pos.push([cond_t, h, w]);
425                }
426            }
427            segments.push(Segment {
428                start,
429                stop: pos.len(),
430                kind: Kind::Cond,
431            });
432        }
433
434        // Audio is channel-major stereo: every latent frame once per
435        // channel, the two channels pinned to the frame grid's extreme
436        // w coordinates so they are distinguishable under RoPE.
437        let a_start = pos.len();
438        push_audio_grid(&mut pos, cursor, audio_t, w_low_t, w_high_t);
439        segments.push(Segment {
440            start: a_start,
441            stop: pos.len(),
442            kind: Kind::Audio,
443        });
444
445        // Video: the t axis advances by the per-token frame spans, not
446        // by one; h/w come from the shared frame grid.
447        let v_start = pos.len();
448        let mut t_coord = cursor;
449        for k in 0..latent_t {
450            for &h in &h_axis {
451                for &w in &w_axis {
452                    pos.push([t_coord, h, w]);
453                }
454            }
455            t_coord += FRAME_RESCALE * FRAME_PER_TOKEN[k % 5];
456        }
457        segments.push(Segment {
458            start: v_start,
459            stop: pos.len(),
460            kind: Kind::Video,
461        });
462
463        Self {
464            seq_len: pos.len(),
465            segments,
466            pos,
467            text_len,
468            audio_t,
469            latent_t,
470            lat_h,
471            lat_w,
472            frame_rows,
473            text_tags: if text_tags.is_empty() {
474                vec![TAG_TEXT as u8; text_len]
475            } else {
476                text_tags.to_vec()
477            },
478        }
479    }
480
481    /// The chunk-causal layout: one chunk being denoised, and the chunks
482    /// it is allowed to see.
483    ///
484    /// RAVEN generates a clip chunk by chunk, each one extrapolated from
485    /// what came before instead of denoised as one bidirectional clip.
486    /// Its attention pattern is `sink` chunks from the start plus a
487    /// sliding `window` of recent ones — the reference implements that
488    /// with a KV cache; the same *pattern* falls out of simply not
489    /// packing the rows a chunk may not see, which is what this builds.
490    /// The cache is then an optimization of this, not a prerequisite.
491    ///
492    /// Positions stay ABSOLUTE. A chunk five steps in must carry the RoPE
493    /// coordinates it would have had in the whole clip, or the model is
494    /// told it is generating the opening again.
495    ///
496    /// Rows come out as `[text | audio: ctx then current | video: ctx then
497    /// current]`: the audio and video blocks stay contiguous because the
498    /// patchifier hands them over that way, and audio is channel-major, so
499    /// its context is two runs — one per channel.
500    #[allow(clippy::too_many_arguments)]
501    pub fn streaming(
502        text_len: usize,
503        text_tags: &[u8],
504        lat_h: usize,
505        lat_w: usize,
506        ctx_video: &[usize],
507        cur_video: &[usize],
508        ctx_audio: &[usize],
509        cur_audio: &[usize],
510    ) -> Self {
511        let area = ((lat_h * lat_w) as f64).sqrt();
512        let h_axis = axis_from_sqrt_area(lat_h, 2, area);
513        let w_axis = axis_from_sqrt_area(lat_w, 2, area);
514        let frame_rows = h_axis.len() * w_axis.len();
515        let (w_low, w_high) = (w_axis[0], w_axis[w_axis.len() - 1]);
516
517        let mut pos: Vec<[f64; 3]> = Vec::new();
518        let mut segments = Vec::new();
519        segments.push(Segment {
520            start: 0,
521            stop: text_len,
522            kind: Kind::Text,
523        });
524        for i in 0..text_len {
525            pos.push([i as f64, 0.0, 0.0]);
526        }
527        let cursor = text_len as f64;
528
529        // The absolute t coordinate of latent frame k: the same running
530        // sum the bidirectional layout walks, evaluated at k.
531        let t_at = |k: usize| -> f64 {
532            cursor
533                + (0..k)
534                    .map(|j| FRAME_RESCALE * FRAME_PER_TOKEN[j % 5])
535                    .sum::<f64>()
536        };
537
538        // Audio, channel-major: for each channel, the context frames then
539        // the current ones, so each channel's half is [ctx | cur].
540        // Channel-major INSIDE each role, not across them: the packer
541        // hands over [ch0 | ch1] for a set of frames, so context and
542        // current each have to be one run of rows — the output side
543        // looks the current one up by kind and slices it whole.
544        for (idxs, kind) in [(ctx_audio, Kind::CtxAudio), (cur_audio, Kind::Audio)] {
545            if idxs.is_empty() {
546                continue;
547            }
548            let start = pos.len();
549            for ch in 0..2 {
550                let w = if ch == 0 { w_low } else { w_high };
551                for &i in idxs {
552                    pos.push([cursor + i as f64, 0.0, w]);
553                }
554            }
555            segments.push(Segment {
556                start,
557                stop: pos.len(),
558                kind,
559            });
560        }
561
562        for (idxs, kind) in [(ctx_video, Kind::CtxVideo), (cur_video, Kind::Video)] {
563            if idxs.is_empty() {
564                continue;
565            }
566            let start = pos.len();
567            for &k in idxs {
568                let t = t_at(k);
569                for &h in &h_axis {
570                    for &w in &w_axis {
571                        pos.push([t, h, w]);
572                    }
573                }
574            }
575            segments.push(Segment {
576                start,
577                stop: pos.len(),
578                kind,
579            });
580        }
581
582        Self {
583            seq_len: pos.len(),
584            segments,
585            pos,
586            text_len,
587            audio_t: ctx_audio.len() + cur_audio.len(),
588            latent_t: ctx_video.len() + cur_video.len(),
589            lat_h,
590            lat_w,
591            frame_rows,
592            text_tags: if text_tags.is_empty() {
593                vec![TAG_TEXT as u8; text_len]
594            } else {
595                text_tags.to_vec()
596            },
597        }
598    }
599
600    /// The contiguous span the video rows occupy — context and current
601    /// together, because the patchifier produces them as one block.
602    pub fn video_block(&self) -> (usize, usize) {
603        self.span(&[Kind::CtxVideo, Kind::Video])
604    }
605
606    /// The same for audio.
607    pub fn audio_block(&self) -> (usize, usize) {
608        self.span(&[Kind::CtxAudio, Kind::Audio])
609    }
610
611    fn span(&self, kinds: &[Kind]) -> (usize, usize) {
612        let mut lo = usize::MAX;
613        let mut hi = 0usize;
614        for s in self.segments.iter().filter(|s| kinds.contains(&s.kind)) {
615            lo = lo.min(s.start);
616            hi = hi.max(s.stop);
617        }
618        if lo == usize::MAX { (0, 0) } else { (lo, hi) }
619    }
620
621    /// How many keyframe condition rows the layout carries.
622    pub fn cond_rows(&self) -> usize {
623        self.segments
624            .iter()
625            .filter(|s| s.kind == Kind::Cond)
626            .map(|s| s.stop - s.start)
627            .sum()
628    }
629
630    fn segment(&self, kind: Kind) -> Segment {
631        *self
632            .segments
633            .iter()
634            .find(|s| s.kind == kind)
635            .expect("every layout carries all three streams")
636    }
637
638    /// MiniMax-H3-Image: `[text | reference images | target image]`, one
639    /// latent frame and no audio. Each reference is `(lat_h, lat_w)` of
640    /// its own latent; it sits at its own step of the time axis on its
641    /// own area-normalized grid, and the target follows the last of them.
642    pub fn image(
643        text_len: usize,
644        lat_h: usize,
645        lat_w: usize,
646        refs: &[(usize, usize)],
647        text_tags: &[u8],
648    ) -> Self {
649        let mut pos: Vec<[f64; 3]> = Vec::new();
650        let mut segments = vec![Segment {
651            start: 0,
652            stop: text_len,
653            kind: Kind::Text,
654        }];
655        for i in 0..text_len {
656            pos.push([i as f64, 0.0, 0.0]);
657        }
658        let mut cursor = text_len as f64;
659        let frame = |pos: &mut Vec<[f64; 3]>, h: usize, w: usize, t: f64| -> usize {
660            let area = ((h * w) as f64).sqrt();
661            let (ha, wa) = (
662                axis_from_sqrt_area(h, 2, area),
663                axis_from_sqrt_area(w, 2, area),
664            );
665            for &y in &ha {
666                for &x in &wa {
667                    pos.push([t, y, x]);
668                }
669            }
670            ha.len() * wa.len()
671        };
672        for &(rh, rw) in refs {
673            let start = pos.len();
674            let n = frame(&mut pos, rh, rw, cursor);
675            segments.push(Segment {
676                start,
677                stop: start + n,
678                kind: Kind::RefImg,
679            });
680            cursor += 1.0;
681        }
682        let start = pos.len();
683        let frame_rows = frame(&mut pos, lat_h, lat_w, cursor);
684        segments.push(Segment {
685            start,
686            stop: start + frame_rows,
687            kind: Kind::Video,
688        });
689        Self {
690            seq_len: pos.len(),
691            segments,
692            pos,
693            text_len,
694            audio_t: 0,
695            latent_t: 1,
696            lat_h,
697            lat_w,
698            frame_rows,
699            text_tags: if text_tags.is_empty() {
700                vec![TAG_TEXT as u8; text_len]
701            } else {
702                text_tags.to_vec()
703            },
704        }
705    }
706}
707
708// ── weights ─────────────────────────────────────────────────────────
709
710struct Adaln {
711    /// [out, rank] — the curve basis, already carrying the LoRA.
712    w: Proj,
713    b: Vec<f32>,
714    /// [grid, rank]
715    table: Vec<f32>,
716    rank: usize,
717    out: usize,
718}
719
720impl Adaln {
721    fn load(model: &Arc<CmfModel>, prefix: &str) -> Result<Self, String> {
722        let w = Proj::from_model(model, &format!("{prefix}.weight"))?;
723        let table = crate::dit::cmf_f32(model, &format!("{prefix}.table"))?;
724        let b = crate::dit::cmf_f32(model, &format!("{prefix}.bias"))?;
725        let out = w.rows();
726        let rank = table.len() / CURVE_GRID;
727        Ok(Self {
728            w,
729            b,
730            table,
731            rank,
732            out,
733        })
734    }
735
736    /// The modulation vectors at `ts`: `[ts.len() · MODALITIES, expand ·
737    /// hidden]` laid out so row `t·MODALITIES + tag`, chunk `e`, starts
738    /// at `(t·MODALITIES + tag)·expand·hidden + e·hidden`.
739    fn eval(&self, ts: &[f64], pool: Option<&Pool>) -> Vec<f32> {
740        let g = CURVE_GRID;
741        let mut coords = vec![0f32; ts.len() * self.rank];
742        for (i, &t) in ts.iter().enumerate() {
743            // t → fractional grid index; out-of-range clamps to the ends,
744            // and the last interval is kept whole so t = 1 does not read
745            // past the table.
746            let p = (t.clamp(0.0, 1.0) * (g - 1) as f64) as f32;
747            let i0 = (p.floor() as usize).min(g - 2);
748            let f = p - i0 as f32;
749            for k in 0..self.rank {
750                let (a, b) = (
751                    self.table[i0 * self.rank + k],
752                    self.table[(i0 + 1) * self.rank + k],
753                );
754                coords[i * self.rank + k] = a + (b - a) * f;
755            }
756        }
757        let mut out = vec![0f32; ts.len() * self.out];
758        self.w.matmat(&coords, ts.len(), &mut out, pool);
759        for row in out.chunks_exact_mut(self.out) {
760            for (v, &bv) in row.iter_mut().zip(&self.b) {
761                *v += bv;
762            }
763        }
764        out
765    }
766}
767
768struct Block {
769    norm1: Vec<f32>,
770    norm2: Vec<f32>,
771    qkv: Proj, // [3·heads·hd, hidden]
772    out: Proj, // [hidden, heads·hd]
773    q_norm: Vec<f32>,
774    k_norm: Vec<f32>,
775    fc1: Proj, // [2·ffn, hidden]
776    fc2: Proj, // [hidden, ffn]
777    adaln: Option<Adaln>,
778    /// Runtime low-rank branches, one per projection an adapter names.
779    /// A branch is why the fused device paths below stand down: they
780    /// keep qkv, the attention output and the FFN's middle on the card,
781    /// and the branch has to read exactly those.
782    lora: BlockLora,
783}
784
785/// The four projections an adapter can reach in a packed block. `adaln`
786/// is deliberately absent: this container carries the modulation as a
787/// rank-24 curve, and folding an adaLN update into it needs the time
788/// embedding the packer had (`animate-pack --lora --time-embedder`).
789#[derive(Default)]
790struct BlockLora {
791    qkv: Option<LoraBranch>,
792    out: Option<LoraBranch>,
793    fc1: Option<LoraBranch>,
794    fc2: Option<LoraBranch>,
795}
796
797/// A projection and its branch, in one device submission where the platform
798/// has that kernel and the shapes fit — the branch reads the activation the
799/// base GEMM already uploaded and accumulates into the output it just wrote,
800/// so it costs no transfer of its own. Falls back to `matmat` + `add`, which
801/// is correct everywhere and pays a round trip for the branch.
802///
803/// The router and the probe both need the branch and the base separated to
804/// measure one against the other, which the fused kernel does not do — so
805/// when either is asked for, a branch takes the split path until it HAS been
806/// measured, and the fused one from the next step onward. Without that the
807/// probe reports 0.0000 for every branch and reads as "this adapter does
808/// nothing", which is what it did in the first run of it.
809fn proj_with_lora(
810    p: &Proj,
811    br: &Option<LoraBranch>,
812    x: &[f32],
813    n: usize,
814    out: &mut [f32],
815    pool: Option<&Pool>,
816) {
817    #[cfg(target_os = "macos")]
818    if let Some(l) = br.as_ref().filter(|l| l.live()) {
819        if let Some((model, idx)) = p.q4tp_mapped() {
820            let (rows, cols) = (p.rows(), p.cols());
821            if n >= 32
822                && cols % 32 == 0
823                && crate::gpu::enabled_here()
824                && !crate::gpu::mm_killed()
825                && (!crate::ltxlora::wants_measurement() || l.resonance() != 0.0)
826                && crate::gpu_metal::q4tp_matmat_lora(model, idx, x, n, rows, cols, out, &l.side())
827            {
828                return;
829            }
830        }
831    }
832    p.matmat(x, n, out, pool);
833    if let Some(l) = br {
834        l.add(x, n, out, pool);
835    }
836}
837
838impl BlockLora {
839    /// Live, not merely present: a branch the router has switched off
840    /// gives the fused device path back for the rest of the render.
841    fn live(br: &Option<LoraBranch>) -> bool {
842        br.as_ref().is_some_and(|l| l.live())
843    }
844    fn ffn_any(&self) -> bool {
845        Self::live(&self.fc1) || Self::live(&self.fc2)
846    }
847}
848
849pub(crate) const CURVE_GRID: usize = 1025;
850
851pub struct MiniMaxH3 {
852    video_patch: Proj,
853    video_patch_b: Vec<f32>,
854    /// `None` on MiniMax-H3-Image, which has no audio stream.
855    audio_patch: Option<Proj>,
856    audio_patch_b: Vec<f32>,
857    condition: Proj,
858    condition_b: Vec<f32>,
859    refiner: Vec<Block>,
860    refiner_norm: Vec<f32>,
861    blocks: Vec<Block>,
862    final_norm: Vec<f32>,
863    final_adaln: Adaln,
864    video_out: Proj,
865    video_out_b: Vec<f32>,
866    audio_out: Option<Proj>,
867    audio_out_b: Vec<f32>,
868    /// MiniMax-H3-Image's output layer as plain f32 `[pdd_steps · out, hidden]`:
869    /// a bank of per-step heads, block 0 a full head and the rest offsets
870    /// from it. Empty on the video model.
871    pdd_w: Vec<f32>,
872    /// How many heads the bank stacks (1: a plain output layer) and the
873    /// sigma grid they were distilled on.
874    pub pdd_steps: usize,
875    pub pdd_sigmas: Vec<f64>,
876    /// MiniMax-H3-Image: `[text | reference images | target image]`.
877    pub image: bool,
878    /// The container, for the device chain's weight lookups.
879    model: Arc<CmfModel>,
880    inv_freq: Vec<f32>,
881    pool: Option<Arc<Pool>>,
882    pub hidden: usize,
883    pub heads: usize,
884    pub head_dim: usize,
885    pub ffn: usize,
886    pub latents_dim: usize,
887    pub audio_dim: usize,
888    pub text_dim: usize,
889    pub shift_video: f64,
890    pub shift_audio: f64,
891    eps: f64,
892    qk_eps: f64,
893    final_eps: f64,
894    /// How much of a keyframe latent survives the noise blend, and
895    /// therefore where its rows sit on the schedule. `VISUAL_COND_
896    /// TIMESTEP` is the reference's default; 1.0 turns the blend off.
897    pub cond_aug: f64,
898    /// The same, for a reference soundtrack. The reference's default is
899    /// 1.0 — an audio condition is not noised at all.
900    pub cond_aug_audio: f64,
901    /// Adapter keys that found a projection, for the caller's report.
902    lora_bound: std::collections::BTreeSet<String>,
903}
904
905fn rms_norm_into(x: &[f32], w: &[f32], eps: f64, dst: &mut [f32]) {
906    let ss = x.iter().map(|&v| (v as f64) * (v as f64)).sum::<f64>() / x.len() as f64;
907    let inv = 1.0 / (ss + eps).sqrt();
908    for ((d, &v), &g) in dst.iter_mut().zip(x).zip(w) {
909        *d = (v as f64 * inv) as f32 * g;
910    }
911}
912
913fn silu(v: f32) -> f32 {
914    v / (1.0 + (-v).exp())
915}
916
917impl MiniMaxH3 {
918    pub fn from_cmf(model: &Arc<CmfModel>) -> Result<Self, String> {
919        Self::from_cmf_lora(model, None)
920    }
921
922    /// The same, with an adapter consulted for every projection.
923    ///
924    /// The base weights are q4tp and stay untouched: a rank-32 update
925    /// cannot be folded into a four-bit ladder without dequantizing the
926    /// whole DiT, so the branch rides beside the base GEMM the way
927    /// [`crate::ltxlora`] does it for LTX.
928    pub fn from_cmf_lora(model: &Arc<CmfModel>, bank: Option<&LoraBank>) -> Result<Self, String> {
929        let cfg: serde_json::Value = serde_json::from_slice(
930            model
931                .tensor_bytes("dit.config_json")
932                .map_err(|e| e.to_string())?,
933        )
934        .map_err(|e| format!("dit.config_json: {e}"))?;
935        let u = |k: &str| cfg[k].as_u64().unwrap_or(0) as usize;
936        let f = |k: &str, d: f64| cfg[k].as_f64().unwrap_or(d);
937        let n_blocks = u("num_layers");
938        let n_refiner = u("token_refiner_num_layers");
939        let f32v = |n: &str| crate::dit::cmf_f32(model, n);
940        let image = cfg["image"].as_bool() == Some(true);
941        let pdd_steps = u("pdd_steps").max(1);
942        let pdd_sigmas: Vec<f64> = cfg["pdd_sigmas"]
943            .as_array()
944            .map(|a| a.iter().filter_map(|v| v.as_f64()).collect())
945            .unwrap_or_default();
946        if pdd_steps > 1 && pdd_sigmas.len() != pdd_steps + 1 {
947            return Err(format!(
948                "dit.config_json: {pdd_steps} PDD heads need {} sigmas, got {}",
949                pdd_steps + 1,
950                pdd_sigmas.len()
951            ));
952        }
953
954        // Which of the adapter's names actually found a projection here.
955        let bound: std::cell::RefCell<std::collections::BTreeSet<String>> = Default::default();
956        let load_block = |prefix: &str, with_adaln: bool| -> Result<Block, String> {
957            let qkv = Proj::from_model(model, &format!("dit.{prefix}.attn.qkv_proj.weight"))?;
958            let out = Proj::from_model(model, &format!("dit.{prefix}.attn.out_proj.weight"))?;
959            let fc1 = Proj::from_model(model, &format!("dit.{prefix}.mlp.fc1.weight"))?;
960            let fc2 = Proj::from_model(model, &format!("dit.{prefix}.mlp.fc2.weight"))?;
961            // A branch is bound against the SHAPE of the projection it will
962            // ride: `add` writes n·out floats through a raw pointer, so an
963            // adapter for another model has to be refused by name, not
964            // discovered as a corrupted panel.
965            let lora = match bank {
966                None => BlockLora::default(),
967                Some(k) => {
968                    let mut take = |suffix: &str, p: &Proj| -> Result<Option<LoraBranch>, String> {
969                        let key = format!("{prefix}.{suffix}");
970                        let br = k.branch_for(&format!("dit.{key}"), p.rows(), p.cols())?;
971                        if br.is_some() {
972                            bound.borrow_mut().insert(key);
973                        }
974                        Ok(br)
975                    };
976                    BlockLora {
977                        qkv: take("attn.qkv_proj", &qkv)?,
978                        out: take("attn.out_proj", &out)?,
979                        fc1: take("mlp.fc1", &fc1)?,
980                        fc2: take("mlp.fc2", &fc2)?,
981                    }
982                }
983            };
984            Ok(Block {
985                norm1: f32v(&format!("dit.{prefix}.norm1.weight"))?,
986                norm2: f32v(&format!("dit.{prefix}.norm2.weight"))?,
987                qkv,
988                out,
989                q_norm: f32v(&format!("dit.{prefix}.attn.q_norm.weight"))?,
990                k_norm: f32v(&format!("dit.{prefix}.attn.k_norm.weight"))?,
991                fc1,
992                fc2,
993                adaln: if with_adaln {
994                    Some(Adaln::load(model, &format!("dit.{prefix}.adaln"))?)
995                } else {
996                    None
997                },
998                lora,
999            })
1000        };
1001        let blocks = (0..n_blocks)
1002            .map(|i| load_block(&format!("blocks.{i}"), true))
1003            .collect::<Result<Vec<_>, _>>()?;
1004        let refiner = (0..n_refiner)
1005            .map(|i| load_block(&format!("token_refiner.blocks.{i}"), false))
1006            .collect::<Result<Vec<_>, _>>()?;
1007
1008        Ok(Self {
1009            video_patch: Proj::from_model(model, "dit.video_patch_proj.weight")?,
1010            video_patch_b: f32v("dit.video_patch_proj.bias")?,
1011            audio_patch: if image {
1012                None
1013            } else {
1014                Some(Proj::from_model(model, "dit.audio_patch_proj.weight")?)
1015            },
1016            audio_patch_b: if image {
1017                Vec::new()
1018            } else {
1019                f32v("dit.audio_patch_proj.bias")?
1020            },
1021            condition: Proj::from_model(model, "dit.condition_proj.weight")?,
1022            condition_b: f32v("dit.condition_proj.bias")?,
1023            refiner,
1024            refiner_norm: f32v("dit.token_refiner.final_norm.weight")?,
1025            blocks,
1026            final_norm: f32v("dit.final_layer.norm.weight")?,
1027            final_adaln: Adaln::load(model, "dit.final_layer.adaln")?,
1028            video_out: Proj::from_model(model, "dit.final_layer.video_out.weight")?,
1029            video_out_b: f32v("dit.final_layer.video_out.bias")?,
1030            audio_out: if image {
1031                None
1032            } else {
1033                Some(Proj::from_model(model, "dit.final_layer.audio_out.weight")?)
1034            },
1035            audio_out_b: if image {
1036                Vec::new()
1037            } else {
1038                f32v("dit.final_layer.audio_out.bias")?
1039            },
1040            pdd_w: if pdd_steps > 1 {
1041                f32v("dit.final_layer.video_out.weight")?
1042            } else {
1043                Vec::new()
1044            },
1045            pdd_steps,
1046            pdd_sigmas,
1047            image,
1048            model: model.clone(),
1049            inv_freq: f32v("dit.rope_inv_freq")?,
1050            pool: Pool::from_env(),
1051            hidden: u("hidden_size"),
1052            heads: u("num_attention_heads"),
1053            head_dim: u("attention_head_dim"),
1054            ffn: u("ffn_hidden_size"),
1055            latents_dim: u("latents_dim"),
1056            audio_dim: u("audio_latents_dim"),
1057            text_dim: u("text_dim"),
1058            shift_video: f("sigma_shift_video", 12.0),
1059            shift_audio: f("sigma_shift_audio", 3.0),
1060            eps: f("norm_eps", 1e-5),
1061            qk_eps: f("qk_norm_eps", 1e-5),
1062            final_eps: f("final_norm_eps", 1e-5),
1063            cond_aug: VISUAL_COND_TIMESTEP,
1064            cond_aug_audio: AUDIO_COND_TIMESTEP,
1065            lora_bound: bound.into_inner(),
1066        })
1067    }
1068
1069    /// How many of the adapter's branches found a projection.
1070    pub fn lora_bound(&self) -> usize {
1071        self.lora_bound.len()
1072    }
1073
1074    /// Whether this adapter key landed on a projection of ours.
1075    pub fn lora_binds(&self, key: &str) -> bool {
1076        self.lora_bound.contains(key)
1077    }
1078
1079    /// Under `CMF_LORA_PROBE=1`: every branch by measured contribution,
1080    /// loudest first, and which ones the router switched off. This is the
1081    /// map that says where an adapter actually lives — for the Realism
1082    /// adapter it is not uniform across the fifty blocks.
1083    pub fn lora_report(&self) -> Option<String> {
1084        if !crate::ltxlora::probe_report_on() {
1085            return None;
1086        }
1087        let mut rows: Vec<(f32, bool, String)> = Vec::new();
1088        let mut walk = |blocks: &[Block], pre: &str| {
1089            for (i, b) in blocks.iter().enumerate() {
1090                for (name, br) in [
1091                    ("attn.qkv_proj", &b.lora.qkv),
1092                    ("attn.out_proj", &b.lora.out),
1093                    ("mlp.fc1", &b.lora.fc1),
1094                    ("mlp.fc2", &b.lora.fc2),
1095                ] {
1096                    if let Some(l) = br {
1097                        rows.push((l.resonance(), l.live(), format!("{pre}{i}.{name}")));
1098                    }
1099                }
1100            }
1101        };
1102        walk(&self.blocks, "blocks.");
1103        walk(&self.refiner, "token_refiner.blocks.");
1104        if rows.is_empty() {
1105            return None;
1106        }
1107        rows.sort_by(|a, b| b.0.total_cmp(&a.0));
1108        let live = rows.iter().filter(|r| r.1).count();
1109        let mut s = format!(
1110            "lora branches by contribution ‖sΔY‖/‖Y‖ ({} of {} live):\n",
1111            live,
1112            rows.len()
1113        );
1114        for (r, on, name) in &rows {
1115            s.push_str(&format!(
1116                "  {:>8.4}  {}  {name}\n",
1117                r,
1118                if *on { "on " } else { "off" }
1119            ));
1120        }
1121        Some(s)
1122    }
1123
1124    /// Qwen3-VL states `[n, text_dim]` → refined text embeds
1125    /// `[n, hidden]`. Prompt-only, so the caller does this once per
1126    /// generation rather than once per step.
1127    pub fn refine_text(&self, states: &[f32], n: usize) -> Vec<f32> {
1128        let pool = self.pool.as_deref();
1129        let mut h = vec![0f32; n * self.hidden];
1130        self.condition.matmat(states, n, &mut h, pool);
1131        for row in h.chunks_exact_mut(self.hidden) {
1132            for (v, &b) in row.iter_mut().zip(&self.condition_b) {
1133                *v += b;
1134            }
1135        }
1136        self.run_blocks(&self.refiner, &mut h, n, &[], &[], &[]);
1137        let mut out = vec![0f32; n * self.hidden];
1138        for (o, x) in out
1139            .chunks_exact_mut(self.hidden)
1140            .zip(h.chunks_exact(self.hidden))
1141        {
1142            rms_norm_into(x, &self.refiner_norm, self.final_eps, o);
1143        }
1144        out
1145    }
1146
1147    /// The rotation angles of every row: `[n, 48]`. Each of the three
1148    /// position axes contributes `inv_freq.len()` angles, and the pair
1149    /// (j, j+48) of the first 96 head dims rotates by angle j.
1150    fn rope_angles(&self, pos: &[[f64; 3]]) -> Vec<f32> {
1151        let k = self.inv_freq.len();
1152        let mut out = Vec::with_capacity(pos.len() * 3 * k);
1153        for p in pos {
1154            for axis in 0..3 {
1155                for j in 0..k {
1156                    out.push((p[axis] * self.inv_freq[j] as f64) as f32);
1157                }
1158            }
1159        }
1160        out
1161    }
1162
1163    /// Per-head RMSNorm then the partial split-half rotation, in place.
1164    /// `angles` carries 48 angles a row — three axes × 16 frequencies —
1165    /// and pair (j, j+48) of the head's first 96 dims turns by angle j.
1166    /// Dims 96..128 are not rotated at all, which is the checkpoint's
1167    /// own `rope_inv_freq_len` arithmetic and not a truncation.
1168    /// In place on a STRIDED view of the fused qkv buffer: `stride` is
1169    /// the row pitch and `off` the plane's start. Normalizing q and k
1170    /// through a scratch copy cost four full passes over `n·heads·hd`
1171    /// per block — 10 G element copies over a render — for arithmetic
1172    /// that touches each element once.
1173    #[allow(clippy::too_many_arguments)]
1174    fn norm_rope_w(
1175        &self,
1176        v: &mut [f32],
1177        n: usize,
1178        heads: usize,
1179        w: &[f32],
1180        angles: &[f32],
1181        stride: usize,
1182        off: usize,
1183    ) {
1184        let hd = self.head_dim;
1185        let pairs = if angles.is_empty() {
1186            0
1187        } else {
1188            angles.len() / n
1189        };
1190        let pool = self.pool.as_deref();
1191        let ptr = SendPtr(v.as_mut_ptr());
1192        let work = |lo: usize, hi: usize| {
1193            for p in lo..hi {
1194                for h in 0..heads {
1195                    // SAFETY: workers own disjoint token ranges, and the
1196                    // heads of one token are disjoint within it.
1197                    let x = unsafe { ptr.row(p * stride + off + h * hd, hd) };
1198                    let ss = x.iter().map(|&a| (a as f64) * (a as f64)).sum::<f64>() / hd as f64;
1199                    let inv = 1.0 / (ss + self.qk_eps).sqrt();
1200                    for (d, &g) in x.iter_mut().zip(w) {
1201                        *d = (*d as f64 * inv) as f32 * g;
1202                    }
1203                    for j in 0..pairs {
1204                        let a = angles[p * pairs + j];
1205                        let (s, c) = a.sin_cos();
1206                        let (lo_v, hi_v) = (x[j], x[j + pairs]);
1207                        x[j] = lo_v * c - hi_v * s;
1208                        x[j + pairs] = lo_v * s + hi_v * c;
1209                    }
1210                }
1211            }
1212        };
1213        match pool {
1214            Some(pl) => pl.run_rows(n, &work),
1215            None => work(0, n),
1216        }
1217    }
1218
1219    /// Full bidirectional attention over the packed sequence.
1220    /// `nr` = (rope angles, q norm weights, k norm weights, eps) when the
1221    /// DEVICE should apply qk-norm and RoPE. Passing it means the caller
1222    /// skipped `norm_rope_w`, which is the only reason the qkv panel had
1223    /// to come back to the host at all.
1224    fn attention(
1225        &self,
1226        qkv: &[f32],
1227        n: usize,
1228        attn: &mut [f32],
1229        nr: Option<(&[f32], &[f32], &[f32], f32)>,
1230    ) {
1231        let t_repack = std::time::Instant::now();
1232        let (nh, hd) = (self.heads, self.head_dim);
1233        let inner = nh * hd;
1234        let scale = 1.0 / (hd as f32).sqrt();
1235        let pool = self.pool.as_deref();
1236        // Device path: scores, softmax and the PV product all stay in
1237        // device buffers (`dit_qk` → `dit_softmax` → `dit_pv`), so the
1238        // n×n score plane — 144 MB at render size — never crosses the
1239        // bus. Measured share of a denoise step before this: 41.5%.
1240        // The kernels exist and Lumina's DiT already rides them; this
1241        // block is what puts MiniMax on the same road. CMF_MMH3_ATTN=cpu
1242        // forces the host loop (the A/B that proved the parity).
1243        if std::env::var("CMF_MMH3_ATTN").as_deref() != Ok("cpu")
1244            && crate::gpu::enabled_here()
1245            && n >= 256
1246        {
1247            // The qkv panel goes up in one piece and is split into
1248            // head-major planes ON the card. The host repack below cost
1249            // 4.4 s of a 7.3 s attention phase — more than the device
1250            // work it fed. Measured at render size: repack 4.4 → 1.5 s,
1251            // render 130.0 → 111.8 s, frames bit-identical.
1252            // `CMF_MMH3_ATTN=repack` forces the host form.
1253            // The device can do qk-norm and RoPE itself (kernel and
1254            // plumbing are in; pass Some((angles, q_norm, k_norm, eps))).
1255            // `attention` cannot reach them: they live in the caller,
1256            // and handing them over means changing this signature — the
1257            // last step of task #33, and the point where the panel stops
1258            // coming home at all.
1259            if std::env::var("CMF_MMH3_ATTN").as_deref() != Ok("repack")
1260                && crate::gpu::dit_attention_packed(qkv, nh, n, hd, scale, nr, attn)
1261            {
1262                // No stamp: the caller's slot-3 stopwatch already spans
1263                // this whole call, and CMF_DIT_ATTN_PROF breaks the
1264                // device half into its three walls. Two overlapping
1265                // stopwatches on the same work is how a 19.2 s step
1266                // came to read as 26.1.
1267                return;
1268            }
1269            assert!(
1270                nr.is_none(),
1271                "device qk-norm was requested but the device path refused; \
1272                 the host loops below expect q/k already normalized"
1273            );
1274            let mut qh = vec![0f32; nh * n * hd];
1275            let mut kh = vec![0f32; nh * n * hd];
1276            let mut vh = vec![0f32; nh * n * hd];
1277            {
1278                let (pq, pk, pv) = (
1279                    SendPtr(qh.as_mut_ptr()),
1280                    SendPtr(kh.as_mut_ptr()),
1281                    SendPtr(vh.as_mut_ptr()),
1282                );
1283                pool_rows(pool, n, &|lo, hi| {
1284                    for p in lo..hi {
1285                        let base = p * 3 * inner;
1286                        for h in 0..nh {
1287                            let dst = (h * n + p) * hd;
1288                            // SAFETY: workers own disjoint token ranges,
1289                            // and each token writes its own head slots.
1290                            unsafe {
1291                                pq.row(dst, hd)
1292                                    .copy_from_slice(&qkv[base + h * hd..base + (h + 1) * hd]);
1293                                pk.row(dst, hd).copy_from_slice(
1294                                    &qkv[base + inner + h * hd..base + inner + (h + 1) * hd],
1295                                );
1296                                pv.row(dst, hd).copy_from_slice(
1297                                    &qkv[base + 2 * inner + h * hd
1298                                        ..base + 2 * inner + (h + 1) * hd],
1299                                );
1300                            }
1301                        }
1302                    }
1303                });
1304            }
1305            // Split the phase: the repack above is host work, this call
1306            // is device work. The last round wrote a tensor-core QK on
1307            // the assumption that the GEMMs dominate and the step did
1308            // not move — so measure the halves before writing anything
1309            // else. Slot 2 (qknorm+rope, unused on this path) takes the
1310            // repack; slot 3 keeps the device call.
1311            Self::prof(2, t_repack);
1312            // No stamp on the device call: the caller's slot-3 stopwatch
1313            // already spans it, and stamping both double-counts.
1314            if crate::gpu::dit_attention(&qh, &kh, &vh, nh, nh, n, hd, scale, attn) {
1315                return;
1316            }
1317        }
1318        let mut qh = vec![0f32; n * hd];
1319        let mut kh = vec![0f32; n * hd];
1320        let mut vt = vec![0f32; hd * n];
1321        let mut scores = vec![0f32; n * n];
1322        let mut oh = vec![0f32; n * hd];
1323        for h in 0..nh {
1324            for p in 0..n {
1325                let base = p * 3 * inner;
1326                let qs = &qkv[base + h * hd..base + (h + 1) * hd];
1327                for (d, &val) in qs.iter().enumerate() {
1328                    qh[p * hd + d] = val * scale;
1329                }
1330                kh[p * hd..(p + 1) * hd]
1331                    .copy_from_slice(&qkv[base + inner + h * hd..base + inner + (h + 1) * hd]);
1332                let vs = &qkv[base + 2 * inner + h * hd..base + 2 * inner + (h + 1) * hd];
1333                for (d, &val) in vs.iter().enumerate() {
1334                    vt[d * n + p] = val;
1335                }
1336            }
1337            crate::fcd_ops::gemm_nt(&qh, &kh, &mut scores, n, hd, n, pool);
1338            let sp = SendPtr(scores.as_mut_ptr());
1339            let soft = |lo: usize, hi: usize| {
1340                for r in lo..hi {
1341                    // SAFETY: workers own disjoint score rows.
1342                    softmax_inplace(unsafe { sp.row(r * n, n) });
1343                }
1344            };
1345            match pool {
1346                Some(pl) => pl.run_rows(n, &soft),
1347                None => soft(0, n),
1348            }
1349            crate::fcd_ops::gemm_nt(&scores, &vt, &mut oh, n, n, hd, pool);
1350            for p in 0..n {
1351                attn[p * inner + h * hd..p * inner + (h + 1) * hd]
1352                    .copy_from_slice(&oh[p * hd..(p + 1) * hd]);
1353            }
1354        }
1355    }
1356
1357    /// Where a denoise step's wall actually goes, in microseconds, under
1358    /// `CMF_MMH3_PROF=1`. Optimizing a 60-second step without this is
1359    /// guesswork, and guesswork on a rented card is expensive.
1360    fn prof(slot: usize, t: std::time::Instant) {
1361        if !mmh3_prof_on() {
1362            return;
1363        }
1364        MMH3_PROF[slot].fetch_add(
1365            t.elapsed().as_micros() as u64,
1366            std::sync::atomic::Ordering::Relaxed,
1367        );
1368    }
1369
1370    /// One block. `mods` is the block's modulation buffer and `rows` the
1371    /// per-token row index into it; both empty for the refiner, which is
1372    /// unmodulated and unrotated.
1373    fn block_forward(
1374        &self,
1375        blk: &Block,
1376        x: &mut [f32],
1377        n: usize,
1378        mods: Option<&[f32]>,
1379        pos: &[[f64; 3]],
1380        rows: &[u32],
1381    ) {
1382        let hs = self.hidden;
1383        let pool = self.pool.as_deref();
1384        let inner = self.heads * self.head_dim;
1385        let angles = if pos.is_empty() {
1386            Vec::new()
1387        } else {
1388            self.rope_angles(pos)
1389        };
1390
1391        let t = std::time::Instant::now();
1392        let mut xn = vec![0f32; n * hs];
1393        self.norm_rows(&mut xn, x, n, hs, &blk.norm1, self.eps);
1394        if let (Some(m), false) = (mods, rows.is_empty()) {
1395            self.modulate(&mut xn, hs, m, rows, 0, 1);
1396        }
1397        Self::prof(0, t);
1398        let t = std::time::Instant::now();
1399        // The projection writes its panel into a device buffer and the
1400        // split reads it THERE — with qk-norm on the device too, nothing
1401        // touches that panel on the host, so 160 MB down and the same
1402        // back up per block simply stop happening. Measured at render
1403        // size: 104.9 → 98.1 s, frames bit-identical.
1404        // `CMF_MMH3_FUSEQKV=0` sends the panel home again.
1405        let t_qkv = std::time::Instant::now();
1406        // Ask whether the device can TAKE the norm, not merely whether a
1407        // device exists. Skipping the host loop for a backend with no
1408        // packed kernel hands the attention unnormalized q/k and there is
1409        // no way back from there — which is why the refusal below used to
1410        // be an assert.
1411        let qk_gpu = std::env::var("CMF_MMH3_QKNORM").as_deref() != Ok("cpu")
1412            && crate::gpu::enabled_here()
1413            && crate::gpu::dit_attention_packed_available()
1414            && n >= 256;
1415        // An adapter on qkv needs the panel the fused paths never let
1416        // out of the card; one on `out` needs the attention output. Each
1417        // stands down only for the projection it owns — a qkv-only
1418        // adapter still gets device attention through the chain below.
1419        if qk_gpu
1420            && !BlockLora::live(&blk.lora.qkv)
1421            && std::env::var("CMF_MMH3_FUSEQKV").as_deref() != Ok("0")
1422        {
1423            // Best case first: qkv, attention and the output projection
1424            // with nothing crossing the bus between them. It refuses at
1425            // the door when anything is missing, so falling through to
1426            // the chain below never repeats work.
1427            let fuse_out = !BlockLora::live(&blk.lora.out)
1428                && std::env::var("CMF_MMH3_FUSEOUT").as_deref() != Ok("0");
1429            if std::env::var("CMF_GPU_DEBUG").is_ok() {
1430                static ONCE: std::sync::Once = std::sync::Once::new();
1431                ONCE.call_once(|| {
1432                    eprintln!(
1433                        "mmh3 fuse-out gate: qkv_q={} out_q={} qkv_map={} out_map={}",
1434                        matches!(&blk.qkv, Proj::Q(_)),
1435                        matches!(&blk.out, Proj::Q(_)),
1436                        matches!(&blk.qkv, Proj::Q(q) if q.mapped_device_gemm().is_some()),
1437                        matches!(&blk.out, Proj::Q(o) if o.mapped_device_gemm().is_some()),
1438                    )
1439                });
1440            }
1441            if let (true, Proj::Q(q), Proj::Q(o)) = (fuse_out, &blk.qkv, &blk.out) {
1442                if let (Some((m, i)), Some((_, oi))) =
1443                    (q.mapped_device_gemm(), o.mapped_device_gemm())
1444                {
1445                    let mut proj = vec![0f32; n * hs];
1446                    if crate::gpu::dit_qkv_attn_out(
1447                        m,
1448                        i,
1449                        oi,
1450                        &xn,
1451                        n,
1452                        hs,
1453                        self.heads,
1454                        self.head_dim,
1455                        1.0 / (self.head_dim as f32).sqrt(),
1456                        (
1457                            &angles[..],
1458                            &blk.q_norm[..],
1459                            &blk.k_norm[..],
1460                            self.qk_eps as f32,
1461                        ),
1462                        &mut proj,
1463                    ) {
1464                        Self::prof(1, t_qkv);
1465                        let t_res = std::time::Instant::now();
1466                        self.residual(x, hs, &proj, mods, rows, 2);
1467                        Self::prof(8, t_res);
1468                        self.ffn_tail(blk, x, n, mods, rows);
1469                        return;
1470                    }
1471                }
1472            }
1473            if let Proj::Q(q) = &blk.qkv {
1474                if let Some((m, i)) = q.mapped_device_gemm() {
1475                    let mut attn = vec![0f32; n * inner];
1476                    if crate::gpu::dit_qkv_attention(
1477                        m,
1478                        i,
1479                        &xn,
1480                        n,
1481                        hs,
1482                        self.heads,
1483                        self.head_dim,
1484                        1.0 / (self.head_dim as f32).sqrt(),
1485                        (
1486                            &angles[..],
1487                            &blk.q_norm[..],
1488                            &blk.k_norm[..],
1489                            self.qk_eps as f32,
1490                        ),
1491                        &mut attn,
1492                    ) {
1493                        // Stamp the same slots the unfused path does, or
1494                        // the profile reads 5.5 s for a step the device
1495                        // spends 6+ in: an instrument with a blind spot
1496                        // is worse than none, and this one has cost two
1497                        // wrong conclusions already.
1498                        Self::prof(1, t_qkv);
1499                        let t_out = std::time::Instant::now();
1500                        let mut proj = vec![0f32; n * hs];
1501                        proj_with_lora(&blk.out, &blk.lora.out, &attn, n, &mut proj, pool);
1502                        Self::prof(4, t_out);
1503                        let t_res = std::time::Instant::now();
1504                        self.residual(x, hs, &proj, mods, rows, 2);
1505                        Self::prof(8, t_res);
1506                        self.ffn_tail(blk, x, n, mods, rows);
1507                        return;
1508                    }
1509                }
1510            }
1511        }
1512        let mut qkv = vec![0f32; n * 3 * inner];
1513        proj_with_lora(&blk.qkv, &blk.lora.qkv, &xn, n, &mut qkv, pool);
1514        Self::prof(1, t);
1515        // q and k are the first two thirds of every row; normalize and
1516        // rotate them where they lie, leaving v alone.
1517        // qk-norm and RoPE ride the same device pass that scatters the
1518        // panel head-major, so the host neither walks the panel nor
1519        // needs it back. Parity holds over a whole render: 1, 2 and 4
1520        // steps all match this loop's output exactly (delta 0.000).
1521        //
1522        // A 7.7% "drift" was measured first and was an artefact — the
1523        // reference had been rendered by an EARLIER binary, so the
1524        // comparison carried every change since, not this one. Same
1525        // binary, both arms, or the number means nothing.
1526        // `CMF_MMH3_QKNORM=cpu` restores the host loop.
1527        let qk_on_gpu = qk_gpu;
1528        let t = std::time::Instant::now();
1529        if !qk_on_gpu {
1530            for (which, w) in [(0usize, &blk.q_norm), (1usize, &blk.k_norm)] {
1531                self.norm_rope_w(
1532                    &mut qkv,
1533                    n,
1534                    self.heads,
1535                    w,
1536                    &angles,
1537                    3 * inner,
1538                    which * inner,
1539                );
1540            }
1541        }
1542        Self::prof(2, t);
1543        let t = std::time::Instant::now();
1544        let mut attn = vec![0f32; n * inner];
1545        self.attention(
1546            &qkv,
1547            n,
1548            &mut attn,
1549            qk_on_gpu.then_some((
1550                &angles[..],
1551                &blk.q_norm[..],
1552                &blk.k_norm[..],
1553                self.qk_eps as f32,
1554            )),
1555        );
1556        Self::prof(3, t);
1557        let t = std::time::Instant::now();
1558        let mut proj = vec![0f32; n * hs];
1559        proj_with_lora(&blk.out, &blk.lora.out, &attn, n, &mut proj, pool);
1560        Self::prof(4, t);
1561        // Own slot: the residual is host-side elementwise work with
1562        // modulation, and billing it to the projection hid which of the
1563        // two actually costs (they were 9.8 s together at 512×288).
1564        let t = std::time::Instant::now();
1565        self.residual(x, hs, &proj, mods, rows, 2);
1566        Self::prof(8, t);
1567
1568        self.ffn_tail(blk, x, n, mods, rows);
1569    }
1570
1571    /// The FFN half of a block: norm, modulate, SwiGLU, residual. Its
1572    /// own function so the attention half can return early — the fused
1573    /// path (`dit_qkv_attention`) finishes attention on the device and
1574    /// has nowhere to jump to otherwise.
1575    fn ffn_tail(&self, blk: &Block, x: &mut [f32], n: usize, mods: Option<&[f32]>, rows: &[u32]) {
1576        let hs = self.hidden;
1577        let pool = self.pool.as_deref();
1578        let mut xn = vec![0f32; n * hs];
1579        let mut proj = vec![0f32; n * hs];
1580        let t = std::time::Instant::now();
1581        self.norm_rows(&mut xn, x, n, hs, &blk.norm2, self.eps);
1582        if let (Some(m), false) = (mods, rows.is_empty()) {
1583            self.modulate(&mut xn, hs, m, rows, 3, 4);
1584        }
1585        Self::prof(0, t);
1586        let t = std::time::Instant::now();
1587        // Device-resident FFN: fc1 → SwiGLU → fc2 without the
1588        // intermediate crossing the bus. At render size that panel is
1589        // hundreds of megabytes each way, per block, per step.
1590        // CMF_MMH3_FFN=cpu forces the host chain below.
1591        if std::env::var("CMF_MMH3_FFN").as_deref() != Ok("cpu")
1592            && !blk.lora.ffn_any()
1593            && crate::gpu::enabled_here()
1594            && n >= 64
1595        {
1596            if let (Proj::Q(q1), Proj::Q(q2)) = (&blk.fc1, &blk.fc2) {
1597                if let (Some((m, i1)), Some((_, i2))) =
1598                    (q1.mapped_device_gemm(), q2.mapped_device_gemm())
1599                {
1600                    let mut fout = vec![0f32; n * hs];
1601                    if crate::gpu::q4tp_ffn_packed(m, i1, i2, &xn, n, hs, self.ffn, None, &mut fout)
1602                    {
1603                        Self::prof(5, t);
1604                        self.residual(x, hs, &fout, mods, rows, 5);
1605                        return;
1606                    }
1607                }
1608            }
1609        }
1610        let mut gu = vec![0f32; n * 2 * self.ffn];
1611        proj_with_lora(&blk.fc1, &blk.lora.fc1, &xn, n, &mut gu, pool);
1612        Self::prof(5, t);
1613        let t = std::time::Instant::now();
1614        // SwiGLU: fc1's output is [gate | up] per row.
1615        let ffn = self.ffn;
1616        let mut act = vec![0f32; n * ffn];
1617        let ap = SendPtr(act.as_mut_ptr());
1618        pool_rows(pool, n, &|lo, hi| {
1619            for p in lo..hi {
1620                let row = &gu[p * 2 * ffn..(p + 1) * 2 * ffn];
1621                let (g, up) = row.split_at(ffn);
1622                // SAFETY: workers own disjoint token ranges.
1623                for (o, (&a, &b)) in unsafe { ap.row(p * ffn, ffn) }
1624                    .iter_mut()
1625                    .zip(g.iter().zip(up))
1626                {
1627                    *o = silu(a) * b;
1628                }
1629            }
1630        });
1631        Self::prof(6, t);
1632        let t = std::time::Instant::now();
1633        proj_with_lora(&blk.fc2, &blk.lora.fc2, &act, n, &mut proj, pool);
1634        Self::prof(7, t);
1635        self.residual(x, hs, &proj, mods, rows, 5);
1636    }
1637
1638    /// RMSNorm every row of `src` into `dst`, across the pool. One
1639    /// block does this four times over `n·hidden`; on a 1 879-token
1640    /// pack that is 40 M elements a block, and it was running on one
1641    /// thread while forty-seven sat idle.
1642    fn norm_rows(&self, dst: &mut [f32], src: &[f32], n: usize, hs: usize, w: &[f32], eps: f64) {
1643        let ptr = SendPtr(dst.as_mut_ptr());
1644        pool_rows(self.pool.as_deref(), n, &|lo, hi| {
1645            for p in lo..hi {
1646                // SAFETY: workers own disjoint token ranges.
1647                rms_norm_into(&src[p * hs..(p + 1) * hs], w, eps, unsafe {
1648                    ptr.row(p * hs, hs)
1649                });
1650            }
1651        });
1652    }
1653
1654    /// `x = x·(1 + scale[row]) + shift[row]`, per token, across the pool.
1655    fn modulate(
1656        &self,
1657        x: &mut [f32],
1658        hs: usize,
1659        mods: &[f32],
1660        rows: &[u32],
1661        shift_e: usize,
1662        scale_e: usize,
1663    ) {
1664        let stride = EXPAND * hs;
1665        let ptr = SendPtr(x.as_mut_ptr());
1666        pool_rows(self.pool.as_deref(), rows.len(), &|lo, hi| {
1667            for p in lo..hi {
1668                let base = rows[p] as usize * stride;
1669                let shift = &mods[base + shift_e * hs..base + (shift_e + 1) * hs];
1670                let scale = &mods[base + scale_e * hs..base + (scale_e + 1) * hs];
1671                // SAFETY: workers own disjoint token ranges.
1672                for ((v, &sc), &sh) in unsafe { ptr.row(p * hs, hs) }
1673                    .iter_mut()
1674                    .zip(scale)
1675                    .zip(shift)
1676                {
1677                    *v = *v * (1.0 + sc) + sh;
1678                }
1679            }
1680        });
1681    }
1682
1683    /// The gated residual — or a plain one where there is no modulation
1684    /// (the token refiner).
1685    fn residual(
1686        &self,
1687        x: &mut [f32],
1688        hs: usize,
1689        other: &[f32],
1690        mods: Option<&[f32]>,
1691        rows: &[u32],
1692        gate_e: usize,
1693    ) {
1694        let n = x.len() / hs;
1695        let stride = EXPAND * hs;
1696        let ptr = SendPtr(x.as_mut_ptr());
1697        let gated = mods.filter(|_| !rows.is_empty());
1698        pool_rows(self.pool.as_deref(), n, &|lo, hi| {
1699            for p in lo..hi {
1700                // SAFETY: workers own disjoint token ranges.
1701                let row = unsafe { ptr.row(p * hs, hs) };
1702                let src = &other[p * hs..(p + 1) * hs];
1703                match gated {
1704                    Some(m) => {
1705                        let base = rows[p] as usize * stride;
1706                        let gate = &m[base + gate_e * hs..base + (gate_e + 1) * hs];
1707                        for ((v, &g), &o) in row.iter_mut().zip(gate).zip(src) {
1708                            *v += g * o;
1709                        }
1710                    }
1711                    None => {
1712                        for (v, &o) in row.iter_mut().zip(src) {
1713                            *v += o;
1714                        }
1715                    }
1716                }
1717            }
1718        });
1719    }
1720
1721    /// One denoise evaluation.
1722    ///
1723    /// `video` is `[latents_dim, latent_t, lat_h, lat_w]` and `audio` is
1724    /// `[audio_dim, 2, audio_t]`, both in the reference's channel-major
1725    /// order. `text` is the refined `[n, hidden]` stream. Returns the
1726    /// two velocities, EACH ON ITS OWN SCHEDULE and unscaled.
1727    pub fn forward(
1728        &self,
1729        layout: &Layout,
1730        text: &[f32],
1731        video: &[f32],
1732        audio: &[f32],
1733        sigma_v: f64,
1734        cond: &[Vec<f32>],
1735    ) -> (Vec<f32>, Vec<f32>) {
1736        let hs = self.hidden;
1737        let pool = self.pool.as_deref();
1738        let sigma_v = sigma_v.max(1e-6);
1739        let t_v = 1.0 - sigma_v;
1740        let t_a = 1.0 - time_shift_sigma(sigma_v, self.shift_video, self.shift_audio);
1741
1742        // Distinct timesteps, sorted — the adaLN row index is a position
1743        // in this list, so the order is part of the contract. A keyframe
1744        // pins its rows near 1: they are conditions, not noise being
1745        // removed.
1746        let has_cond = layout
1747            .segments
1748            .iter()
1749            .any(|s| matches!(s.kind, Kind::Cond | Kind::RefImg));
1750        let has_ref_audio = layout.segments.iter().any(|s| s.kind == Kind::RefAudio);
1751        // A reference soundtrack pins to the AUDIO clock's condition
1752        // timestep, which is its own number.
1753        let t_cond_a = t_a.max(self.cond_aug_audio);
1754        // The condition rows' timestep IS the noise-augmentation figure:
1755        // the reference blends `aug` of the latent with `1 − aug` of
1756        // noise and then tells the block the row sits at `aug`. Turning
1757        // the blend off means aug = 1, and the timestep moves with it.
1758        let t_cond = t_v.max(self.cond_aug);
1759        // Streaming context is the student's OWN x0, handed back at
1760        // timestep 0 — the reference passes literal zeros for the clean
1761        // role, and a chunk conditioned on anything else is conditioned
1762        // on a frame the previous chunk never produced.
1763        let has_ctx = layout
1764            .segments
1765            .iter()
1766            .any(|s| matches!(s.kind, Kind::CtxVideo | Kind::CtxAudio));
1767        let mut ts = vec![t_v, t_a];
1768        if has_ctx {
1769            ts.push(0.0);
1770        }
1771        if has_cond {
1772            ts.push(t_cond);
1773        }
1774        if has_ref_audio {
1775            ts.push(t_cond_a);
1776        }
1777        ts.sort_by(|a, b| a.partial_cmp(b).unwrap());
1778        ts.dedup();
1779        let row_of = |t: f64| ts.iter().position(|&x| x == t).unwrap();
1780        let (row_v, row_a) = (row_of(t_v), row_of(t_a));
1781        let row_c = if has_cond { row_of(t_cond) } else { 0 };
1782        let row_ctx = if has_ctx { row_of(0.0) } else { 0 };
1783        let row_ca = if has_ref_audio { row_of(t_cond_a) } else { 0 };
1784
1785        // Per-token modulation row: t_row · MODALITIES + tag. The text
1786        // span is not uniform once a vision block is in it — those
1787        // positions carry the VIDEO tag.
1788        let mut rows = vec![0u32; layout.seq_len];
1789        for s in &layout.segments {
1790            match s.kind {
1791                Kind::Text => {
1792                    for (i, v) in rows[s.start..s.stop].iter_mut().enumerate() {
1793                        let tag = *layout.text_tags.get(i).unwrap_or(&(TAG_TEXT as u8));
1794                        *v = (row_v * MODALITIES + tag as usize) as u32;
1795                    }
1796                }
1797                // A reference's rows are conditions too: same timestep
1798                // near 1, and the modality of whichever stream they
1799                // belong to.
1800                Kind::Cond | Kind::RefImg => {
1801                    for v in rows[s.start..s.stop].iter_mut() {
1802                        *v = (row_c * MODALITIES + TAG_VIDEO) as u32;
1803                    }
1804                }
1805                Kind::RefAudio => {
1806                    for v in rows[s.start..s.stop].iter_mut() {
1807                        *v = (row_ca * MODALITIES + TAG_AUDIO) as u32;
1808                    }
1809                }
1810                Kind::Video => {
1811                    for v in rows[s.start..s.stop].iter_mut() {
1812                        *v = (row_v * MODALITIES + TAG_VIDEO) as u32;
1813                    }
1814                }
1815                Kind::Audio => {
1816                    for v in rows[s.start..s.stop].iter_mut() {
1817                        *v = (row_a * MODALITIES + TAG_AUDIO) as u32;
1818                    }
1819                }
1820                Kind::CtxVideo => {
1821                    for v in rows[s.start..s.stop].iter_mut() {
1822                        *v = (row_ctx * MODALITIES + TAG_VIDEO) as u32;
1823                    }
1824                }
1825                Kind::CtxAudio => {
1826                    for v in rows[s.start..s.stop].iter_mut() {
1827                        *v = (row_ctx * MODALITIES + TAG_AUDIO) as u32;
1828                    }
1829                }
1830            }
1831        }
1832
1833        // ── embed ──
1834        let v_rows = patchify_video(
1835            video,
1836            self.latents_dim,
1837            layout.latent_t,
1838            layout.lat_h,
1839            layout.lat_w,
1840        );
1841        let v_n = v_rows.len() / (self.latents_dim * 4);
1842        let a_rows = pack_audio(audio, self.audio_dim, layout.audio_t);
1843        // Condition rows go through the SAME patch projection as the
1844        // target, so they are patchified the same way — one frame each.
1845        let vd = self.latents_dim * 4;
1846        let cond_rows: Vec<Vec<f32>> = cond
1847            .iter()
1848            .enumerate()
1849            .map(|(i, z)| {
1850                let mut r = patchify_video(z, self.latents_dim, 1, layout.lat_h, layout.lat_w);
1851                if self.cond_aug < 1.0 {
1852                    // The reference draws this from a torch generator
1853                    // reseeded per condition; ours is its own stream, so
1854                    // the 0.1% it contributes differs — deliberately, and
1855                    // it is 0.1% of a unit normal.
1856                    let noise = crate::videogen::gauss_pub(r.len(), 0x5EED ^ i as u64);
1857                    let a = self.cond_aug as f32;
1858                    for (v, n) in r.iter_mut().zip(&noise) {
1859                        *v = a * *v + (1.0 - a) * n;
1860                    }
1861                }
1862                r
1863            })
1864            .collect();
1865
1866        let mut h = vec![0f32; layout.seq_len * hs];
1867        let mut ci = 0usize;
1868        for s in layout.segments.iter().filter(|s| s.kind == Kind::Cond) {
1869            let n = s.stop - s.start;
1870            let r = cond_rows.get(ci).unwrap_or_else(|| {
1871                panic!(
1872                    "layout has {} cond segments, {} latents given",
1873                    ci + 1,
1874                    cond_rows.len()
1875                )
1876            });
1877            self.video_patch
1878                .matmat(r, n, &mut h[s.start * hs..s.stop * hs], pool);
1879            for row in h[s.start * hs..s.stop * hs].chunks_exact_mut(hs) {
1880                for (v, &b) in row.iter_mut().zip(&self.video_patch_b) {
1881                    *v += b;
1882                }
1883            }
1884            ci += 1;
1885        }
1886        // The embed writes the whole block — context and current — while
1887        // the OUTPUT is only the current chunk's rows, which is why these
1888        // are two different spans in streaming and the same one otherwise.
1889        let (vblk_start, vblk_stop) = layout.video_block();
1890        let (ablk_start, ablk_stop) = layout.audio_block();
1891        let vseg = layout.segment(Kind::Video);
1892        let aseg = layout.segment(Kind::Audio);
1893        let tseg = layout.segment(Kind::Text);
1894        h[tseg.start * hs..tseg.stop * hs].copy_from_slice(&text[..(tseg.stop - tseg.start) * hs]);
1895        self.video_patch
1896            .matmat(&v_rows, v_n, &mut h[vblk_start * hs..vblk_stop * hs], pool);
1897        let audio_patch = self
1898            .audio_patch
1899            .as_ref()
1900            .expect("the audio-video forward on a model without an audio stream");
1901        audio_patch.matmat(
1902            &a_rows,
1903            ablk_stop - ablk_start,
1904            &mut h[ablk_start * hs..ablk_stop * hs],
1905            pool,
1906        );
1907        for row in h[vblk_start * hs..vblk_stop * hs].chunks_exact_mut(hs) {
1908            for (v, &b) in row.iter_mut().zip(&self.video_patch_b) {
1909                *v += b;
1910            }
1911        }
1912        for row in h[ablk_start * hs..ablk_stop * hs].chunks_exact_mut(hs) {
1913            for (v, &b) in row.iter_mut().zip(&self.audio_patch_b) {
1914                *v += b;
1915            }
1916        }
1917
1918        // ── blocks ──
1919        // One resident chain where the backend has one (`run_blocks`). The
1920        // per-block watch reads the stream on the host between blocks, so
1921        // it keeps the per-op loop.
1922        let watch = std::env::var("CMF_MMH3_WATCHROW")
1923            .ok()
1924            .and_then(|w| w.parse::<usize>().ok());
1925        if let Some(r) = watch {
1926            for (i, blk) in self.blocks.iter().enumerate() {
1927                let mods = blk.adaln.as_ref().unwrap().eval(&ts, pool);
1928                self.block_forward(blk, &mut h, layout.seq_len, Some(&mods), &layout.pos, &rows);
1929                // Growing magnitude is an overflow, a sudden jump from
1930                // finite to NaN is a kernel.
1931                let row = &h[r * hs..(r + 1) * hs];
1932                let amax = row
1933                    .iter()
1934                    .filter(|v| v.is_finite())
1935                    .fold(0f32, |a, v| a.max(v.abs()));
1936                let bad = row.iter().filter(|v| !v.is_finite()).count();
1937                if bad > 0 || i == 0 || amax > 1e3 {
1938                    eprintln!("  watch blk {i}: row {r} absmax {amax:.3e} nonfinite {bad}");
1939                }
1940                if std::env::var_os("CMF_DIT_PROGRESS").is_some() {
1941                    eprint!("\r  block {}/{}", i + 1, self.blocks.len());
1942                }
1943            }
1944        } else {
1945            self.run_blocks(&self.blocks, &mut h, layout.seq_len, &ts, &layout.pos, &rows);
1946        }
1947
1948        // ── heads ──
1949        if std::env::var("CMF_MMH3_NANPROBE").is_ok() {
1950            let bad: Vec<usize> = (0..h.len() / hs)
1951                .filter(|r| h[r * hs..(r + 1) * hs].iter().any(|v| !v.is_finite()))
1952                .collect();
1953            if !bad.is_empty() {
1954                eprintln!(
1955                    "  nanprobe rows: {} of {} bad, first 8 {:?}, video seg {}..{}, audio seg {}..{}",
1956                    bad.len(),
1957                    h.len() / hs,
1958                    &bad[..bad.len().min(8)],
1959                    vseg.start,
1960                    vseg.stop,
1961                    aseg.start,
1962                    aseg.stop
1963                );
1964            }
1965        }
1966        let fm = self.final_adaln.eval(&ts, pool);
1967        let mut video_out = vec![0f32; (vseg.stop - vseg.start) * vd];
1968        let mut audio_out = vec![0f32; (aseg.stop - aseg.start) * self.audio_dim];
1969        for (seg, row, w, b, dst, dim) in [
1970            (
1971                vseg,
1972                row_v,
1973                &self.video_out,
1974                &self.video_out_b,
1975                &mut video_out,
1976                vd,
1977            ),
1978            (
1979                aseg,
1980                row_a,
1981                self.audio_out
1982                    .as_ref()
1983                    .expect("the audio-video forward on a model without an audio stream"),
1984                &self.audio_out_b,
1985                &mut audio_out,
1986                self.audio_dim,
1987            ),
1988        ] {
1989            let n = seg.stop - seg.start;
1990            let mut hn = vec![0f32; n * hs];
1991            for (o, src) in hn
1992                .chunks_exact_mut(hs)
1993                .zip(h[seg.start * hs..seg.stop * hs].chunks_exact(hs))
1994            {
1995                rms_norm_into(src, &self.final_norm, self.final_eps, o);
1996            }
1997            // The final layer's adaLN has one modality, so the row IS
1998            // the timestep index.
1999            let shift = &fm[row * 2 * hs..row * 2 * hs + hs];
2000            let scale = &fm[row * 2 * hs + hs..(row + 1) * 2 * hs];
2001            for r in hn.chunks_exact_mut(hs) {
2002                for ((v, &sc), &sh) in r.iter_mut().zip(scale).zip(shift) {
2003                    *v = *v * (1.0 + sc) + sh;
2004                }
2005            }
2006            // `CMF_MMH3_NANPROBE=1`: which side of the head's GEMM a NaN
2007            // is on. The stream that dies is the one to instrument, and
2008            // "the input was already bad" and "this GEMM made it bad" are
2009            // different bugs in different files.
2010            let probe = std::env::var("CMF_MMH3_NANPROBE").is_ok();
2011            if probe {
2012                let bad_in = hn.iter().filter(|v| !v.is_finite()).count();
2013                eprintln!(
2014                    "  nanprobe dim={dim} n={n} in_bad={bad_in} in_absmax={:.4}",
2015                    hn.iter()
2016                        .filter(|v| v.is_finite())
2017                        .fold(0f32, |a, v| a.max(v.abs()))
2018                );
2019            }
2020            w.matmat(&hn, n, dst, pool);
2021            if probe {
2022                let bad_out = dst.iter().filter(|v| !v.is_finite()).count();
2023                eprintln!(
2024                    "  nanprobe dim={dim} out_bad={bad_out}/{} out_absmax={:.4}",
2025                    dst.len(),
2026                    dst.iter()
2027                        .filter(|v| v.is_finite())
2028                        .fold(0f32, |a, v| a.max(v.abs()))
2029                );
2030            }
2031            for r in dst.chunks_exact_mut(dim) {
2032                for (v, &bv) in r.iter_mut().zip(b.iter()) {
2033                    *v += bv;
2034                }
2035            }
2036        }
2037
2038        // The reference predicts toward the data and the sampler steps
2039        // σ down, hence the sign; the audio velocity is returned on its
2040        // OWN clock rather than pre-scaled by d(σ_a)/d(σ_v).
2041        // The output covers the rows the sampler owns — which in the
2042        // chunk-causal layout is the CURRENT chunk, not every visible
2043        // frame. Sizing this by `layout.latent_t` (context included) fed
2044        // one chunk's rows into a buffer shaped for five and walked off
2045        // the end on the first chunk that had any context.
2046        let out_t = (vseg.stop - vseg.start) / layout.frame_rows;
2047        let out_at = (aseg.stop - aseg.start) / 2;
2048        let video = unpatchify_video(
2049            &video_out,
2050            self.latents_dim,
2051            out_t,
2052            layout.lat_h,
2053            layout.lat_w,
2054        );
2055        let audio = unpack_audio(&audio_out, self.audio_dim, out_at);
2056        (
2057            video.iter().map(|&v| -v).collect(),
2058            audio.iter().map(|&v| -v).collect(),
2059        )
2060    }
2061}
2062
2063// ── MiniMax-H3-Image ────────────────────────────────────────────────
2064
2065impl MiniMaxH3 {
2066    /// `blocks` over `h` `[n, hidden]`, in place. On a backend with a
2067    /// resident chain the whole stack runs on the card — the stream goes
2068    /// up once and comes back once; otherwise block by block through the
2069    /// per-op paths. `ts` are the distinct timesteps (empty for the
2070    /// unmodulated token refiner), `pos` the rows' positions (empty: no
2071    /// rotation), `rows` each row's modulation row.
2072    /// `CMF_MMH3_CHAIN=0` keeps the per-op path.
2073    fn run_blocks(
2074        &self,
2075        blocks: &[Block],
2076        h: &mut [f32],
2077        n: usize,
2078        ts: &[f64],
2079        pos: &[[f64; 3]],
2080        rows: &[u32],
2081    ) {
2082        let pool = self.pool.as_deref();
2083        if std::env::var("CMF_MMH3_CHAIN").as_deref() != Ok("0")
2084            && crate::gpu::enabled_here()
2085            && crate::gpu::h3_chain_available()
2086        {
2087            let mods: Vec<Vec<f32>> = blocks
2088                .iter()
2089                .map(|b| b.adaln.as_ref().map(|a| a.eval(ts, pool)).unwrap_or_default())
2090                .collect();
2091            let angles = if pos.is_empty() {
2092                Vec::new()
2093            } else {
2094                self.rope_angles(pos)
2095            };
2096            let idx = |p: &Proj| match p {
2097                Proj::Q(q) => q.mapped_device_gemm().map(|(_, i)| i),
2098                Proj::F32 { .. } => None,
2099            };
2100            let chain: Option<Vec<crate::gpu::H3ChainBlock>> = blocks
2101                .iter()
2102                .zip(&mods)
2103                .map(|(b, m)| {
2104                    if BlockLora::live(&b.lora.qkv)
2105                        || BlockLora::live(&b.lora.out)
2106                        || b.lora.ffn_any()
2107                    {
2108                        return None;
2109                    }
2110                    Some(crate::gpu::H3ChainBlock {
2111                        qkv: idx(&b.qkv)?,
2112                        out: idx(&b.out)?,
2113                        fc1: idx(&b.fc1)?,
2114                        fc2: idx(&b.fc2)?,
2115                        norm1: &b.norm1,
2116                        norm2: &b.norm2,
2117                        q_norm: &b.q_norm,
2118                        k_norm: &b.k_norm,
2119                        mods: m,
2120                        qkv_bias: &[],
2121                        out_bias: &[],
2122                        fc1_bias: &[],
2123                        fc2_bias: &[],
2124                        scale1: &[],
2125                        scale2: &[],
2126                    })
2127                })
2128                .collect();
2129            if let Some(chain) = chain {
2130                let job = crate::gpu::H3Chain {
2131                    model: &self.model,
2132                    blocks: &chain,
2133                    n,
2134                    hidden: self.hidden,
2135                    heads: self.heads,
2136                    head_dim: self.head_dim,
2137                    ffn: self.ffn,
2138                    eps: self.eps as f32,
2139                    qk_eps: self.qk_eps as f32,
2140                    angles: &angles,
2141                    rows,
2142                    groups: 1,
2143                    qkv_interleaved: false,
2144                };
2145                if crate::gpu::h3_chain(&job, h) {
2146                    return;
2147                }
2148            }
2149        }
2150        for (i, blk) in blocks.iter().enumerate() {
2151            let mods = blk.adaln.as_ref().map(|a| a.eval(ts, pool));
2152            self.block_forward(blk, h, n, mods.as_deref(), pos, rows);
2153            if std::env::var_os("CMF_DIT_PROGRESS").is_some() {
2154                eprint!("\r  block {}/{}", i + 1, blocks.len());
2155            }
2156        }
2157    }
2158
2159    /// One denoise evaluation of MiniMax-H3-Image.
2160    ///
2161    /// `latent` is `[latents_dim, lat_h, lat_w]`, channel-major; `text`
2162    /// the refined `[n, hidden]` stream; `refs` each reference picture's
2163    /// latent with its `(lat_h, lat_w)`, in the layout's order. The step
2164    /// runs from `sigma` to `sigma_next` — the output layer is a bank of
2165    /// per-step heads, and the step takes the heads of the grid intervals
2166    /// it spans. Returns the velocity the sampler steps with
2167    /// (`x += (sigma_next − sigma) · v`).
2168    pub fn forward_image(
2169        &self,
2170        layout: &Layout,
2171        text: &[f32],
2172        latent: &[f32],
2173        refs: &[(&[f32], usize, usize)],
2174        sigma: f64,
2175        sigma_next: f64,
2176    ) -> Vec<f32> {
2177        let hs = self.hidden;
2178        let pool = self.pool.as_deref();
2179        let t = 1.0 - sigma;
2180        // Text follows the target; a reference's rows are conditions and
2181        // pin near 1.
2182        let has_ref = layout.segments.iter().any(|s| s.kind == Kind::RefImg);
2183        let t_ref = t.max(self.cond_aug);
2184        let mut ts = vec![t];
2185        if has_ref && t_ref != t {
2186            ts.push(t_ref);
2187        }
2188        ts.sort_by(|a, b| a.partial_cmp(b).unwrap());
2189        let row_of = |x: f64| ts.iter().position(|&v| v == x).unwrap();
2190        let (row_t, row_r) = (row_of(t), if has_ref { row_of(t_ref) } else { 0 });
2191
2192        let mut rows = vec![0u32; layout.seq_len];
2193        for s in &layout.segments {
2194            match s.kind {
2195                Kind::Text => {
2196                    for (i, v) in rows[s.start..s.stop].iter_mut().enumerate() {
2197                        let tag = *layout.text_tags.get(i).unwrap_or(&(TAG_TEXT as u8));
2198                        *v = (row_t * MODALITIES + tag as usize) as u32;
2199                    }
2200                }
2201                Kind::RefImg => {
2202                    for v in rows[s.start..s.stop].iter_mut() {
2203                        *v = (row_r * MODALITIES + TAG_VIDEO) as u32;
2204                    }
2205                }
2206                _ => {
2207                    for v in rows[s.start..s.stop].iter_mut() {
2208                        *v = (row_t * MODALITIES + TAG_VIDEO) as u32;
2209                    }
2210                }
2211            }
2212        }
2213
2214        // ── embed ──
2215        let vd = self.latents_dim * 4;
2216        let mut h = vec![0f32; layout.seq_len * hs];
2217        let tseg = layout.segment(Kind::Text);
2218        h[tseg.start * hs..tseg.stop * hs].copy_from_slice(&text[..(tseg.stop - tseg.start) * hs]);
2219        let embed = |r: &[f32], dst: &mut [f32]| {
2220            self.video_patch.matmat(r, r.len() / vd, dst, pool);
2221            for row in dst.chunks_exact_mut(hs) {
2222                for (v, &b) in row.iter_mut().zip(&self.video_patch_b) {
2223                    *v += b;
2224                }
2225            }
2226        };
2227        for (i, s) in layout
2228            .segments
2229            .iter()
2230            .filter(|s| s.kind == Kind::RefImg)
2231            .enumerate()
2232        {
2233            let (z, rh, rw) = refs[i];
2234            let mut r = patchify_video(z, self.latents_dim, 1, rh, rw);
2235            if self.cond_aug < 1.0 {
2236                // The reference blends 0.1% of seeded noise into a
2237                // condition row; ours is its own stream of the same law.
2238                let noise = crate::videogen::gauss_pub(r.len(), 0x5EED ^ i as u64);
2239                let a = self.cond_aug as f32;
2240                for (v, n) in r.iter_mut().zip(&noise) {
2241                    *v = a * *v + (1.0 - a) * n;
2242                }
2243            }
2244            embed(&r, &mut h[s.start * hs..s.stop * hs]);
2245        }
2246        let vseg = layout.segment(Kind::Video);
2247        let v_rows = patchify_video(latent, self.latents_dim, 1, layout.lat_h, layout.lat_w);
2248        embed(&v_rows, &mut h[vseg.start * hs..vseg.stop * hs]);
2249
2250        // ── blocks ──
2251        self.run_blocks(&self.blocks, &mut h, layout.seq_len, &ts, &layout.pos, &rows);
2252
2253        // ── the output layer ──
2254        let n = vseg.stop - vseg.start;
2255        let fm = self.final_adaln.eval(&ts, pool);
2256        let shift = &fm[row_t * 2 * hs..row_t * 2 * hs + hs];
2257        let scale = &fm[row_t * 2 * hs + hs..(row_t + 1) * 2 * hs];
2258        let mut hn = vec![0f32; n * hs];
2259        for (o, src) in hn
2260            .chunks_exact_mut(hs)
2261            .zip(h[vseg.start * hs..vseg.stop * hs].chunks_exact(hs))
2262        {
2263            rms_norm_into(src, &self.final_norm, self.final_eps, o);
2264            for ((v, &sc), &sh) in o.iter_mut().zip(scale).zip(shift) {
2265                *v = *v * (1.0 + sc) + sh;
2266            }
2267        }
2268        let mut out = vec![0f32; n * vd];
2269        if self.pdd_steps > 1 {
2270            let (w, b) = self.pdd_head(sigma, sigma_next);
2271            crate::fcd_ops::gemm_nt(&hn, &w, &mut out, n, hs, vd, pool);
2272            for r in out.chunks_exact_mut(vd) {
2273                for (v, &bv) in r.iter_mut().zip(&b) {
2274                    *v += bv;
2275                }
2276            }
2277        } else {
2278            self.video_out.matmat(&hn, n, &mut out, pool);
2279            for r in out.chunks_exact_mut(vd) {
2280                for (v, &bv) in r.iter_mut().zip(&self.video_out_b) {
2281                    *v += bv;
2282                }
2283            }
2284        }
2285        let img = unpatchify_video(&out, self.latents_dim, 1, layout.lat_h, layout.lat_w);
2286        img.iter().map(|&v| -v).collect()
2287    }
2288
2289    /// The output head of the step `sigma → sigma_next`: block 0 of the
2290    /// bank is a full head and the later blocks are offsets from it; a
2291    /// step takes the Δσ-weighted mean of the heads of the grid intervals
2292    /// it spans (one interval, on the grid the bank was distilled for).
2293    fn pdd_head(&self, sigma: f64, sigma_next: f64) -> (Vec<f32>, Vec<f32>) {
2294        let n = self.pdd_steps;
2295        let grid = &self.pdd_sigmas;
2296        let nearest = |s: f64| {
2297            (0..=n)
2298                .min_by(|&a, &b| {
2299                    (grid[a] - s)
2300                        .abs()
2301                        .partial_cmp(&(grid[b] - s).abs())
2302                        .unwrap()
2303                })
2304                .unwrap()
2305        };
2306        let start = nearest(sigma).min(n - 1);
2307        let stop = nearest(sigma_next).max(start + 1);
2308        let dt: Vec<f64> = (start..stop).map(|k| grid[k] - grid[k + 1]).collect();
2309        let total: f64 = dt.iter().sum();
2310        let (hs, vd) = (self.hidden, self.latents_dim * 4);
2311        let mut w = self.pdd_w[..vd * hs].to_vec();
2312        let mut b = self.video_out_b[..vd].to_vec();
2313        let first = start.max(1);
2314        for k in first..stop {
2315            let wk = (dt[k - start] / total) as f32;
2316            for (d, &s) in w.iter_mut().zip(&self.pdd_w[k * vd * hs..(k + 1) * vd * hs]) {
2317                *d += wk * s;
2318            }
2319            for (d, &s) in b.iter_mut().zip(&self.video_out_b[k * vd..(k + 1) * vd]) {
2320                *d += wk * s;
2321            }
2322        }
2323        (w, b)
2324    }
2325}
2326
2327// ── modulation helpers ──────────────────────────────────────────────
2328
2329/// Rows of `n` items split across pool workers (serial without a pool).
2330fn pool_rows(pool: Option<&Pool>, n: usize, f: &(dyn Fn(usize, usize) + Sync)) {
2331    match pool {
2332        Some(p) => p.run_rows(n, f),
2333        None => f(0, n),
2334    }
2335}
2336
2337// ── stream (un)packing ──────────────────────────────────────────────
2338
2339/// `[C, T, H, W]` → `[T·(H/2)·(W/2), C·4]`, the 2×2 spatial patch
2340/// flattened channel-major-outer as `einsum("nctrhpwq->nthwcrpq")`.
2341pub fn patchify_video(x: &[f32], c: usize, t: usize, h: usize, w: usize) -> Vec<f32> {
2342    let (ph, pw) = (h / 2, w / 2);
2343    let mut out = vec![0f32; t * ph * pw * c * 4];
2344    let mut i = 0;
2345    for ti in 0..t {
2346        for hi in 0..ph {
2347            for wi in 0..pw {
2348                for ci in 0..c {
2349                    for p in 0..2 {
2350                        for q in 0..2 {
2351                            out[i] = x[((ci * t + ti) * h + hi * 2 + p) * w + wi * 2 + q];
2352                            i += 1;
2353                        }
2354                    }
2355                }
2356            }
2357        }
2358    }
2359    out
2360}
2361
2362/// The inverse of `patchify_video`.
2363pub fn unpatchify_video(rows: &[f32], c: usize, t: usize, h: usize, w: usize) -> Vec<f32> {
2364    let (ph, pw) = (h / 2, w / 2);
2365    let mut out = vec![0f32; c * t * h * w];
2366    let mut i = 0;
2367    for ti in 0..t {
2368        for hi in 0..ph {
2369            for wi in 0..pw {
2370                for ci in 0..c {
2371                    for p in 0..2 {
2372                        for q in 0..2 {
2373                            out[((ci * t + ti) * h + hi * 2 + p) * w + wi * 2 + q] = rows[i];
2374                            i += 1;
2375                        }
2376                    }
2377                }
2378            }
2379        }
2380    }
2381    out
2382}
2383
2384/// `[C, 2, T]` → `[2·T, C]`, channel-major: channel 0's frames then
2385/// channel 1's.
2386pub fn pack_audio(x: &[f32], c: usize, t: usize) -> Vec<f32> {
2387    let mut out = vec![0f32; 2 * t * c];
2388    for ch in 0..2 {
2389        for ti in 0..t {
2390            for ci in 0..c {
2391                out[(ch * t + ti) * c + ci] = x[(ci * 2 + ch) * t + ti];
2392            }
2393        }
2394    }
2395    out
2396}
2397
2398/// The inverse of `pack_audio`.
2399pub fn unpack_audio(rows: &[f32], c: usize, t: usize) -> Vec<f32> {
2400    let mut out = vec![0f32; c * 2 * t];
2401    for ch in 0..2 {
2402        for ti in 0..t {
2403            for ci in 0..c {
2404                out[(ci * 2 + ch) * t + ti] = rows[(ch * t + ti) * c + ci];
2405            }
2406        }
2407    }
2408    out
2409}
2410
2411// ── small shared bits ───────────────────────────────────────────────
2412
2413struct SendPtr(*mut f32);
2414unsafe impl Send for SendPtr {}
2415unsafe impl Sync for SendPtr {}
2416impl SendPtr {
2417    /// SAFETY: caller guarantees disjoint `[off, off+len)` per worker.
2418    #[allow(clippy::mut_from_ref)]
2419    unsafe fn row(&self, off: usize, len: usize) -> &mut [f32] {
2420        unsafe { std::slice::from_raw_parts_mut(self.0.add(off), len) }
2421    }
2422}
2423
2424fn softmax_inplace(row: &mut [f32]) {
2425    let mx = row.iter().cloned().fold(f32::MIN, f32::max);
2426    let mut den = 0f32;
2427    for r in row.iter_mut() {
2428        *r = (*r - mx).exp();
2429        den += *r;
2430    }
2431    if den > 0.0 {
2432        let inv = 1.0 / den;
2433        for r in row.iter_mut() {
2434            *r *= inv;
2435        }
2436    }
2437}
2438
2439#[cfg(test)]
2440mod tests {
2441    /// A streaming layout whose "current chunk" is the whole clip and
2442    /// which sees no context must be the bidirectional layout, row for
2443    /// row. Absolute positions are the whole point of the chunked path:
2444    /// if this drifts, every chunk after the first is told it is
2445    /// generating the opening again.
2446    #[test]
2447    fn an_image_layout_puts_references_between_the_text_and_the_target() {
2448        // 5 text rows, one 4x8 reference latent (2x4 rows), an 8x8 target
2449        // latent (4x4 rows)
2450        let l = Layout::image(5, 8, 8, &[(4, 8)], &[]);
2451        assert_eq!(l.seq_len, 5 + 8 + 16);
2452        assert_eq!((l.latent_t, l.audio_t, l.frame_rows), (1, 0, 16));
2453        let kinds: Vec<Kind> = l.segments.iter().map(|s| s.kind).collect();
2454        assert_eq!(kinds, vec![Kind::Text, Kind::RefImg, Kind::Video]);
2455        // text rows count along the time axis only
2456        assert_eq!(l.pos[3], [3.0, 0.0, 0.0]);
2457        // the reference sits at the text's end, on its own area-normalized
2458        // grid: sqrt(4·8) = 5.657, so h spans 4/5.657 and w 8/5.657 of 32
2459        let r = l.pos[5];
2460        assert_eq!(r[0], 5.0);
2461        let (ratio_h, ratio_w) = (4.0 / 32f64.sqrt(), 8.0 / 32f64.sqrt());
2462        assert!((r[1] - (1.0 - ratio_h) / 2.0 * 32.0).abs() < 1e-9);
2463        assert!((r[2] - (1.0 - ratio_w) / 2.0 * 32.0).abs() < 1e-9);
2464        // the target follows the reference by one step, a square grid
2465        // from 0 in steps of 32 / 4
2466        assert_eq!(l.pos[13], [6.0, 0.0, 0.0]);
2467        assert_eq!(l.pos[14], [6.0, 0.0, 8.0]);
2468        assert_eq!(l.pos[13 + 4], [6.0, 8.0, 0.0]);
2469        // without references the target starts where the text ends
2470        let t = Layout::image(5, 8, 8, &[], &[]);
2471        assert_eq!(t.pos[5], [5.0, 0.0, 0.0]);
2472        assert_eq!(t.seq_len, 21);
2473    }
2474
2475    #[test]
2476    fn streaming_layout_degenerates_to_the_bidirectional_one() {
2477        let (lat_h, lat_w, latent_t, audio_t, text_len) = (16, 24, 7, 12, 5);
2478        let base = Layout::t2va(text_len, latent_t, lat_h, lat_w, audio_t);
2479        let cur_v: Vec<usize> = (0..latent_t).collect();
2480        let cur_a: Vec<usize> = (0..audio_t).collect();
2481        let stream = Layout::streaming(text_len, &[], lat_h, lat_w, &[], &cur_v, &[], &cur_a);
2482        assert_eq!(stream.seq_len, base.seq_len, "row count");
2483        assert_eq!(stream.latent_t, base.latent_t);
2484        assert_eq!(stream.audio_t, base.audio_t);
2485        for (i, (a, b)) in stream.pos.iter().zip(&base.pos).enumerate() {
2486            for axis in 0..3 {
2487                assert!(
2488                    (a[axis] - b[axis]).abs() < 1e-9,
2489                    "row {i} axis {axis}: {a:?} vs {b:?}"
2490                );
2491            }
2492        }
2493        let seg = |l: &Layout, k: Kind| {
2494            l.segments
2495                .iter()
2496                .find(|s| s.kind == k)
2497                .map(|s| (s.start, s.stop))
2498        };
2499        assert_eq!(seg(&stream, Kind::Video), seg(&base, Kind::Video));
2500        assert_eq!(seg(&stream, Kind::Audio), seg(&base, Kind::Audio));
2501    }
2502
2503    /// A later chunk keeps the coordinates it would have had in the whole
2504    /// clip — that is what makes the chunks line up into one video.
2505    #[test]
2506    fn streaming_positions_are_absolute() {
2507        let (lat_h, lat_w) = (16, 24);
2508        let whole = Layout::streaming(
2509            3,
2510            &[],
2511            lat_h,
2512            lat_w,
2513            &[],
2514            &(0..9).collect::<Vec<_>>(),
2515            &[],
2516            &[],
2517        );
2518        let tail = Layout::streaming(
2519            3,
2520            &[],
2521            lat_h,
2522            lat_w,
2523            &[],
2524            &(6..9).collect::<Vec<_>>(),
2525            &[],
2526            &[],
2527        );
2528        let rows = whole.frame_rows;
2529        let (ws, _) = whole.video_block();
2530        let (ts_, _) = tail.video_block();
2531        for k in 0..3 {
2532            let a = whole.pos[ws + (6 + k) * rows];
2533            let b = tail.pos[ts_ + k * rows];
2534            assert!((a[0] - b[0]).abs() < 1e-9, "frame {k}: {a:?} vs {b:?}");
2535        }
2536    }
2537
2538    use super::*;
2539
2540    #[test]
2541    fn schedule_remap_is_an_involution() {
2542        for &s in &[1e-3, 0.25, 0.5, 0.8, 0.972_973, 1.0] {
2543            let a = time_shift_sigma(s, 12.0, 3.0);
2544            let back = time_shift_sigma(a, 3.0, 12.0);
2545            assert!((back - s).abs() < 1e-9, "{s} -> {a} -> {back}");
2546        }
2547    }
2548
2549    #[test]
2550    fn slope_matches_a_finite_difference() {
2551        let h = 1e-6;
2552        for &s in &[0.2, 0.5, 0.9] {
2553            let num = (time_shift_sigma(s + h, 12.0, 3.0) - time_shift_sigma(s - h, 12.0, 3.0))
2554                / (2.0 * h);
2555            let got = time_shift_slope(s, 12.0, 3.0);
2556            assert!((num - got).abs() < 1e-5, "{s}: {num} vs {got}");
2557        }
2558    }
2559
2560    #[test]
2561    fn patchify_round_trips() {
2562        let (c, t, h, w) = (3usize, 2usize, 4usize, 6usize);
2563        let x: Vec<f32> = (0..c * t * h * w).map(|i| i as f32).collect();
2564        let rows = patchify_video(&x, c, t, h, w);
2565        assert_eq!(rows.len(), t * (h / 2) * (w / 2) * c * 4);
2566        assert_eq!(unpatchify_video(&rows, c, t, h, w), x);
2567    }
2568
2569    #[test]
2570    fn audio_pack_round_trips() {
2571        let (c, t) = (5usize, 7usize);
2572        let x: Vec<f32> = (0..c * 2 * t).map(|i| i as f32).collect();
2573        let rows = pack_audio(&x, c, t);
2574        assert_eq!(unpack_audio(&rows, c, t), x);
2575    }
2576
2577    #[test]
2578    fn keyframes_sit_between_the_text_and_the_audio() {
2579        let (tl, lt, lh, lw, at) = (8usize, 3usize, 8usize, 12usize, 5usize);
2580        let base = Layout::t2va(tl, lt, lh, lw, at);
2581        // First and last frame of a 39-frame clip.
2582        let l = Layout::fl2va(tl, lt, lh, lw, at, &[(0, 39), (38, 39)], &[]);
2583        assert_eq!(l.cond_rows(), 2 * l.frame_rows);
2584        assert_eq!(l.seq_len, base.seq_len + 2 * l.frame_rows);
2585        let kinds: Vec<_> = l.segments.iter().map(|s| s.kind).collect();
2586        assert_eq!(
2587            kinds,
2588            vec![Kind::Text, Kind::Cond, Kind::Cond, Kind::Audio, Kind::Video]
2589        );
2590        // A keyframe never advances the cursor: audio and video start
2591        // where they would have without it.
2592        let (a0, v0) = (l.segment(Kind::Audio), l.segment(Kind::Video));
2593        let (ba, bv) = (base.segment(Kind::Audio), base.segment(Kind::Video));
2594        assert_eq!(l.pos[a0.start][0], base.pos[ba.start][0]);
2595        assert_eq!(l.pos[v0.start][0], base.pos[bv.start][0]);
2596        // The first frame's rows sit at the text's end; the last one's a
2597        // whole clip further on, minus one span.
2598        let c: Vec<_> = l.segments.iter().filter(|s| s.kind == Kind::Cond).collect();
2599        assert_eq!(l.pos[c[0].start][0], tl as f64);
2600        let spans: f64 = (0..lt)
2601            .map(|k| FRAME_RESCALE * FRAME_PER_TOKEN[k % 5])
2602            .sum();
2603        assert!((l.pos[c[1].start][0] - (tl as f64 + spans - FRAME_RESCALE)).abs() < 1e-12);
2604        // Both share the TARGET spatial grid.
2605        assert_eq!(l.pos[c[0].start][1], l.pos[v0.start][1]);
2606        assert_eq!(l.pos[c[0].start][2], l.pos[v0.start][2]);
2607    }
2608
2609    #[test]
2610    fn layout_places_the_target_streams_last() {
2611        let l = Layout::t2va(8, 3, 8, 12, 5);
2612        assert_eq!(l.frame_rows, 4 * 6);
2613        assert_eq!(l.seq_len, 8 + 2 * 5 + 3 * 24);
2614        assert_eq!(l.segments[0].kind, Kind::Text);
2615        assert_eq!(l.segments[1].kind, Kind::Audio);
2616        assert_eq!(l.segments[2].kind, Kind::Video);
2617        // The video t axis advances by the 1,4,4,4,4 span pattern.
2618        let v = l.segment(Kind::Video);
2619        let t0 = l.pos[v.start][0];
2620        let t1 = l.pos[v.start + l.frame_rows][0];
2621        assert!((t1 - t0 - FRAME_RESCALE).abs() < 1e-12);
2622    }
2623}