Skip to main content

cortiq_engine/
vae3d.rs

1//! MiniMax-H3's video VAE decoder: a ViT3D, not a conv stack.
2//!
3//! 36 transformer blocks over the latent grid, each latent cell one
4//! token, and a single linear that expands every token into a
5//! 4×16×16×3 block of pixels. The encoder half is a 3-D causal CNN and
6//! is not packed — text-to-video never runs it.
7//!
8//! ## Tiling is not an optimization here
9//!
10//! The reference decodes in 256-pixel spatial tiles and 17-frame
11//! temporal clips ALWAYS — `tiling=True` is the constructor default and
12//! `decode_tiled` just forwards to `decode`. Because the decoder is
13//! global attention, a tile sees a different context than the whole
14//! frame would, so the tiling is part of the model's output, not a
15//! memory strategy layered on top of it. Both schedules are reproduced
16//! exactly: `split_tiles` down to the last overlap unit, and the
17//! clip/token-drop bookkeeping that makes a chunk emit 17 frames and
18//! carry 5 over.
19
20use std::sync::atomic::{AtomicU64, Ordering};
21
22/// Where a VAE decode actually goes. The DiT had a profiler for months
23/// while this stage — the LARGER half of a render at low step counts,
24/// 37.8 s against the denoiser's 19.2 — had none, so every hour of
25/// tuning went to the half that was already instrumented.
26pub static VAE3D_PROF: [AtomicU64; 8] = [
27    AtomicU64::new(0),
28    AtomicU64::new(0),
29    AtomicU64::new(0),
30    AtomicU64::new(0),
31    AtomicU64::new(0),
32    AtomicU64::new(0),
33    AtomicU64::new(0),
34    AtomicU64::new(0),
35];
36
37pub(crate) fn vae3d_prof_on() -> bool {
38    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
39    *ON.get_or_init(|| std::env::var("CMF_VAE3D_PROF").is_ok())
40}
41
42fn vprof(slot: usize, t: std::time::Instant) {
43    if vae3d_prof_on() {
44        VAE3D_PROF[slot].fetch_add(t.elapsed().as_micros() as u64, Ordering::Relaxed);
45    }
46}
47
48/// One line per phase, sorted by cost.
49pub fn vae3d_prof_report() -> Option<String> {
50    if !vae3d_prof_on() {
51        return None;
52    }
53    const NAMES: [&str; 8] = [
54        "norm rows",
55        "qkv gemm",
56        "repack+rope",
57        "attention",
58        "out gemm",
59        "ffn",
60        "head+proj",
61        "pixel shuffle",
62    ];
63    let mut v: Vec<(u64, &str)> = VAE3D_PROF
64        .iter()
65        .map(|a| a.load(Ordering::Relaxed))
66        .zip(NAMES)
67        .collect();
68    let total: u64 = v.iter().map(|(u, _)| u).sum();
69    if total == 0 {
70        return None;
71    }
72    v.sort_by(|a, b| b.0.cmp(&a.0));
73    let mut out = format!("vae3d phases (total {:.1} s):\n", total as f64 / 1e6);
74    for (us, name) in v {
75        out.push_str(&format!(
76            "  {name:<16} {:>6.1} s  {:>5.1}%\n",
77            us as f64 / 1e6,
78            100.0 * us as f64 / total as f64
79        ));
80    }
81    Some(out)
82}
83
84use crate::dit::Proj;
85use crate::pool::Pool;
86use cortiq_core::CmfModel;
87use std::sync::Arc;
88
89const IMAGENET_MEAN: [f32; 3] = [0.485, 0.456, 0.406];
90const IMAGENET_STD: [f32; 3] = [0.229, 0.224, 0.225];
91
92/// Pixel tile edge and the smallest overlap `split_tiles` will accept.
93const TILE: usize = 256;
94const TILE_OVERLAP_MIN: usize = 64;
95/// Temporal clip in frames, and the tokens dropped off the encoder's
96/// tail that the decoder must therefore re-manufacture.
97const CLIP_LENGTH: usize = 17;
98const TOKEN_DROP: usize = 3;
99
100struct Block {
101    norm1: Vec<f32>,
102    norm2: Vec<f32>,
103    scale1: Vec<f32>,
104    scale2: Vec<f32>,
105    qkv: Proj, // [3·dim, dim], PER HEAD interleaved
106    qkv_b: Vec<f32>,
107    out: Proj,
108    out_b: Vec<f32>,
109    w1: Proj, // [2·4·dim, dim]
110    w1_b: Vec<f32>,
111    w2: Proj, // [dim, 4·dim]
112    w2_b: Vec<f32>,
113}
114
115pub struct VideoVae {
116    post_quant: Proj,
117    post_quant_b: Vec<f32>,
118    x_embed: Proj,
119    x_embed_b: Vec<f32>,
120    registers: Vec<f32>, // [n_reg, dim]
121    blocks: Vec<Block>,
122    norm_out_w: Vec<f32>,
123    norm_out_b: Vec<f32>,
124    proj_out: Proj,
125    proj_out_b: Vec<f32>,
126    latents_mean: Vec<f32>,
127    latents_std: Vec<f32>,
128    pool: Option<Arc<Pool>>,
129    dim: usize,
130    heads: usize,
131    head_dim: usize,
132    z_channels: usize,
133    patch: usize,
134    patch_t: usize,
135    n_reg: usize,
136    rope_theta: f32,
137    rope_dim: usize,
138    eps: f64,
139}
140
141fn layer_norm(x: &[f32], w: &[f32], b: &[f32], eps: f64, dst: &mut [f32]) {
142    let n = x.len() as f64;
143    let mean = x.iter().map(|&v| v as f64).sum::<f64>() / n;
144    let var = x.iter().map(|&v| (v as f64 - mean).powi(2)).sum::<f64>() / n;
145    let inv = 1.0 / (var + eps).sqrt();
146    for (((d, &v), &g), &bb) in dst.iter_mut().zip(x).zip(w).zip(b) {
147        *d = ((v as f64 - mean) * inv) as f32 * g + bb;
148    }
149}
150
151/// RMSNorm with a weight, no bias.
152fn rms_norm_into(x: &[f32], w: &[f32], eps: f64, dst: &mut [f32]) {
153    let ss = x.iter().map(|&v| (v as f64) * (v as f64)).sum::<f64>() / x.len() as f64;
154    let inv = 1.0 / (ss + eps).sqrt();
155    for ((d, &v), &g) in dst.iter_mut().zip(x).zip(w) {
156        *d = (v as f64 * inv) as f32 * g;
157    }
158}
159
160/// RMSNorm with NO affine — the decoder's q/k norms are weightless.
161thread_local! {
162    /// The check's reference arm re-enters `attention`; without this it
163    /// checks its own check, forever.
164    static CHECKING: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
165}
166
167fn rms_norm_plain(x: &mut [f32], eps: f64) {
168    let ss = x.iter().map(|&v| (v as f64) * (v as f64)).sum::<f64>() / x.len() as f64;
169    let inv = 1.0 / (ss + eps).sqrt();
170    for v in x.iter_mut() {
171        *v = (*v as f64 * inv) as f32;
172    }
173}
174
175fn silu(v: f32) -> f32 {
176    v / (1.0 + (-v).exp())
177}
178
179/// Rows across the pool, or straight through when there is none. The
180/// DiT has had this since the start; this decoder was still walking its
181/// norms and residuals on one thread — 3.3 s of a 38 s stage.
182fn rows_par(pool: Option<&crate::pool::Pool>, n: usize, f: &(dyn Fn(usize, usize) + Sync)) {
183    match pool {
184        Some(pl) => pl.run_rows(n, f),
185        None => f(0, n),
186    }
187}
188
189struct SendPtr(*mut f32);
190unsafe impl Send for SendPtr {}
191unsafe impl Sync for SendPtr {}
192impl SendPtr {
193    /// SAFETY: caller guarantees disjoint `[off, off+len)` per worker.
194    #[allow(clippy::mut_from_ref)]
195    unsafe fn row(&self, off: usize, len: usize) -> &mut [f32] {
196        unsafe { std::slice::from_raw_parts_mut(self.0.add(off), len) }
197    }
198}
199
200fn softmax_inplace(row: &mut [f32]) {
201    let mx = row.iter().cloned().fold(f32::MIN, f32::max);
202    let mut den = 0f32;
203    for r in row.iter_mut() {
204        *r = (*r - mx).exp();
205        den += *r;
206    }
207    if den > 0.0 {
208        let inv = 1.0 / den;
209        for r in row.iter_mut() {
210            *r *= inv;
211        }
212    }
213}
214
215/// `(starts, lens, overlaps)` for one axis — the reference's schedule,
216/// which grows the overlaps rather than the tile count so every tile is
217/// exactly `TILE` wide.
218fn split_tiles(input_len: usize, ratio: usize) -> (Vec<usize>, Vec<usize>, Vec<usize>) {
219    if TILE >= input_len {
220        return (vec![0], vec![input_len], Vec::new());
221    }
222    let mut n = input_len.div_ceil(TILE);
223    let (mut overlaps, remaining);
224    loop {
225        overlaps = vec![TILE_OVERLAP_MIN; n - 1];
226        let total: usize = overlaps.iter().sum();
227        if TILE * n < total + input_len {
228            n += 1;
229            continue;
230        }
231        remaining = TILE * n - total - input_len;
232        break;
233    }
234    for i in 0..remaining / ratio {
235        overlaps[i % (n - 1)] += ratio;
236    }
237    let mut starts = vec![0usize];
238    for i in 0..n - 1 {
239        starts.push(starts[i] + TILE - overlaps[i]);
240    }
241    (starts, vec![TILE; n], overlaps)
242}
243
244impl VideoVae {
245    pub fn from_cmf(model: &Arc<CmfModel>) -> Result<Self, String> {
246        let cfg: serde_json::Value = serde_json::from_slice(
247            model
248                .tensor_bytes("vvae.config_json")
249                .map_err(|e| e.to_string())?,
250        )
251        .map_err(|e| format!("vvae.config_json: {e}"))?;
252        let u = |k: &str, d: usize| cfg[k].as_u64().map(|v| v as usize).unwrap_or(d);
253        let f32v = |n: &str| crate::dit::cmf_f32(model, n);
254        let n = u("num_layers", 36);
255        let mut blocks = Vec::with_capacity(n);
256        for l in 0..n {
257            let p = format!("vvae.blocks.{l}");
258            blocks.push(Block {
259                norm1: f32v(&format!("{p}.norm1"))?,
260                norm2: f32v(&format!("{p}.norm2"))?,
261                scale1: f32v(&format!("{p}.scale1"))?,
262                scale2: f32v(&format!("{p}.scale2"))?,
263                qkv: Proj::from_model(model, &format!("{p}.attn.to_qkv.weight"))?,
264                qkv_b: f32v(&format!("{p}.attn.to_qkv.bias"))?,
265                out: Proj::from_model(model, &format!("{p}.attn.to_out.weight"))?,
266                out_b: f32v(&format!("{p}.attn.to_out.bias"))?,
267                w1: Proj::from_model(model, &format!("{p}.ff.w1.weight"))?,
268                w1_b: f32v(&format!("{p}.ff.w1.bias"))?,
269                w2: Proj::from_model(model, &format!("{p}.ff.w2.weight"))?,
270                w2_b: f32v(&format!("{p}.ff.w2.bias"))?,
271            });
272        }
273        let dim = u("dim", 2048);
274        let heads = u("heads", 32);
275        Ok(Self {
276            post_quant: Proj::from_model(model, "vvae.post_quant_conv.weight")?,
277            post_quant_b: f32v("vvae.post_quant_conv.bias")?,
278            x_embed: Proj::from_model(model, "vvae.x_embedder.weight")?,
279            x_embed_b: f32v("vvae.x_embedder.bias")?,
280            registers: f32v("vvae.register_tokens")?,
281            blocks,
282            norm_out_w: f32v("vvae.norm_out.weight")?,
283            norm_out_b: f32v("vvae.norm_out.bias")?,
284            proj_out: Proj::from_model(model, "vvae.proj_out.weight")?,
285            proj_out_b: f32v("vvae.proj_out.bias")?,
286            latents_mean: f32v("vvae.latents_mean")?,
287            latents_std: f32v("vvae.latents_std")?,
288            pool: Pool::from_env(),
289            dim,
290            heads,
291            head_dim: dim / heads,
292            z_channels: u("z_channels", 24),
293            patch: u("patch_size", 16),
294            patch_t: u("patch_size_t", 4),
295            n_reg: u("num_register_tokens", 4),
296            rope_theta: cfg["rope_theta"].as_f64().unwrap_or(100.0) as f32,
297            rope_dim: ((dim / heads) as f64 * cfg["rope_dim_ratio"].as_f64().unwrap_or(0.75))
298                as usize,
299            eps: cfg["eps"].as_f64().unwrap_or(1e-5),
300        })
301    }
302
303    /// `arange(0.5, n)/n · 2 − 1` — the normalized cell centre of one
304    /// axis, so a tile's coordinates depend on the TILE's extent and
305    /// not on the frame's.
306    fn axis(n: usize) -> Vec<f32> {
307        (0..n)
308            .map(|i| 2.0 * ((i as f32 + 0.5) / n as f32) - 1.0)
309            .collect()
310    }
311
312    /// `[S, rope_dim/2]` angles: three axes × `rope_dim/6` frequencies,
313    /// scaled by 2π. Suffix tokens sit at the origin and get zeros.
314    fn rope_angles(&self, t: usize, h: usize, w: usize, suffix: usize) -> Vec<f32> {
315        let k = self.rope_dim / 6; // frequencies per axis
316        let inv: Vec<f32> = (0..k)
317            .map(|i| {
318                1.0 / self
319                    .rope_theta
320                    .powf(i as f32 * 2.0 * 3.0 / self.rope_dim as f32)
321            })
322            .collect();
323        let (ta, ha, wa) = (Self::axis(t), Self::axis(h), Self::axis(w));
324        let tau = 2.0 * std::f32::consts::PI;
325        let mut out = Vec::with_capacity((t * h * w + suffix) * 3 * k);
326        for ti in 0..t {
327            for hi in 0..h {
328                for wi in 0..w {
329                    for &c in &[ta[ti], ha[hi], wa[wi]] {
330                        for &f in &inv {
331                            out.push(tau * c * f);
332                        }
333                    }
334                }
335            }
336        }
337        out.extend(std::iter::repeat_n(0.0, suffix * 3 * k));
338        out
339    }
340
341    fn attention(&self, qkv: &[f32], n: usize, attn: &mut [f32], angles: &[f32]) {
342        let (nh, hd, dim) = (self.heads, self.head_dim, self.dim);
343        let pairs = angles.len() / n; // = rope_dim / 2
344        let scale = 1.0 / (hd as f32).sqrt();
345        let pool = self.pool.as_deref();
346        // Device path: the same `dit_qk`/`dit_softmax`/`dit_pv` chain the
347        // DiT rides, so the n×n score plane stays on the card. This VAE
348        // is the larger half of a render's wall (201 s of 475 on an RTX
349        // 5090), and it was materializing those scores per head on the
350        // host. CMF_VAE3D_ATTN=cpu forces the loop below.
351        if std::env::var("CMF_VAE3D_ATTN").as_deref() != Ok("cpu")
352            && crate::gpu::enabled_here()
353            && n >= 256
354        {
355            // A CMF_VAE3D_CHECK harness lived here and was WRONG: its
356            // reference arm re-entered this very function (so it
357            // recursed through its own check) and its device arm was
358            // never verified to have written anything, so its verdict
359            // — "both layouts identical, 7.2765 from the host" — was
360            // just max|host| against two buffers of zeros. It measured
361            // itself. A replacement must assert the device arm wrote
362            // before it compares, and must call the host repack
363            // DIRECTLY rather than through the dispatcher.
364            // A sound version of the harness the previous one failed to
365            // be: a re-entry guard (its reference arm calls back into
366            // this function), and a sentinel that proves the device arm
367            // WROTE before its numbers are believed.
368            //
369            // VERDICT, and it moves the search a long way: the device
370            // arm writes EVERY element (wrote=3680256/3680256) and it
371            // writes ZEROS — `l1-host` comes back exactly equal to
372            // `host|max|`, which is the distance from an all-zero
373            // buffer. So the whole device attention is empty for this
374            // decoder, and the split's addressing, its bias and its
375            // layout are all downstream of a stage that has already
376            // produced nothing. That also explains why every layout
377            // experiment agreed: zero does not depend on addressing.
378            //
379            // Already eliminated, so nobody re-walks them:
380            //  * the split/qk-norm encoder IS submitted before attention
381            //    reads the planes (`c.queue.submit` at the end of that
382            //    block) — the planes are not merely un-flushed;
383            //  * the scratch slots are grow-only and the DiT ran first
384            //    at LARGER shapes, so nothing here is undersized;
385            //  * the split's dispatch grid is 14376 groups in x, well
386            //    under the 65535 bound, so it is not rounding to zero;
387            //  * `dit_attention_packed_src` returned true, so no stage
388            //    refused — the chain ran and produced zeros.
389            //
390            // What is left: the three planes the split writes are read
391            // back as zeros while the SAME kernels, fed the same three
392            // planes uploaded from the host (`resident=false`, the
393            // shipped VAE path), are correct at this very hd=64. So the
394            // difference is the hand-off of the planes themselves, not
395            // the attention math. Next: read `sc.dq` back right after
396            // the split and compare it against the host repack — one
397            // buffer, one dispatch, no attention involved.
398            if std::env::var("CMF_VAE3D_CHECK").as_deref() == Ok("1") && !CHECKING.with(|c| c.get())
399            {
400                CHECKING.with(|c| c.set(true));
401                const SENT: f32 = -12345.0;
402                let mut d1 = vec![SENT; attn.len()];
403                let mut d0 = vec![SENT; attn.len()];
404                let ok1 = crate::gpu::vae_attention_packed_layout(
405                    qkv,
406                    nh,
407                    n,
408                    hd,
409                    scale,
410                    angles,
411                    self.eps as f32,
412                    &mut d1,
413                    1,
414                );
415                let ok0 = crate::gpu::vae_attention_packed_layout(
416                    qkv,
417                    nh,
418                    n,
419                    hd,
420                    scale,
421                    angles,
422                    self.eps as f32,
423                    &mut d0,
424                    0,
425                );
426                let wrote = |v: &[f32]| v.iter().filter(|&&x| x != SENT).count();
427                let mut host = vec![0f32; attn.len()];
428                let saved = std::env::var("CMF_VAE3D_ATTN").ok();
429                // SAFETY: single-threaded diagnostic, guarded above.
430                unsafe { std::env::set_var("CMF_VAE3D_ATTN", "cpu") };
431                self.attention(qkv, n, &mut host, angles);
432                match &saved {
433                    Some(v) => unsafe { std::env::set_var("CMF_VAE3D_ATTN", v) },
434                    None => unsafe { std::env::remove_var("CMF_VAE3D_ATTN") },
435                }
436                // The split ALONE: no norm, no RoPE, no attention. If
437                // this mismatches, the hand-off is the whole story.
438                let mut sq = vec![SENT; nh * n * hd];
439                let oks = crate::gpu::dit_split_only(qkv, nh, n, hd, 1, None, &mut sq);
440                let mut sqn = vec![SENT; nh * n * hd];
441                let okn = crate::gpu::dit_split_only(
442                    qkv,
443                    nh,
444                    n,
445                    hd,
446                    1,
447                    Some((angles, self.eps as f32)),
448                    &mut sqn,
449                );
450                let mut hq = vec![0f32; nh * n * hd];
451                for p in 0..n {
452                    for h in 0..nh {
453                        let b = p * 3 * dim + h * 3 * hd;
454                        hq[(h * n + p) * hd..(h * n + p) * hd + hd]
455                            .copy_from_slice(&qkv[b..b + hd]);
456                    }
457                }
458                let sq_wrote = sq.iter().filter(|&&x| x != SENT).count();
459                let sq_diff = sq
460                    .iter()
461                    .zip(&hq)
462                    .fold(0f32, |m, (x, y)| m.max((x - y).abs()));
463                // The same q plane after norm+RoPE, against the host's
464                // own loop on a copy of it.
465                let mut hqn = hq.clone();
466                let pairs = angles.len() / n;
467                for p in 0..n {
468                    for h in 0..nh {
469                        let r = &mut hqn[(h * n + p) * hd..(h * n + p) * hd + hd];
470                        rms_norm_plain(r, self.eps);
471                        for j in 0..pairs {
472                            let (sn, cs) = angles[p * pairs + j].sin_cos();
473                            let (a, b) = (r[j], r[j + pairs]);
474                            r[j] = a * cs - b * sn;
475                            r[j + pairs] = a * sn + b * cs;
476                        }
477                    }
478                }
479                let n_wrote = sqn.iter().filter(|&&x| x != SENT).count();
480                let n_diff = sqn
481                    .iter()
482                    .zip(&hqn)
483                    .fold(0f32, |m, (x, y)| m.max((x - y).abs()));
484                eprintln!(
485                    "vae split-only: ok={oks} wrote={sq_wrote}/{} maxdiff={sq_diff:.4e} host|max|={:.4e} || +norm: ok={okn} wrote={n_wrote} maxdiff={n_diff:.4e} host|max|={:.4e}",
486                    sq.len(),
487                    hq.iter().fold(0f32, |m, v| m.max(v.abs())),
488                    hqn.iter().fold(0f32, |m, v| m.max(v.abs())),
489                );
490                let md = |a: &[f32], b: &[f32]| {
491                    a.iter().zip(b).fold(0f32, |m, (x, y)| m.max((x - y).abs()))
492                };
493                static ONCE: std::sync::Once = std::sync::Once::new();
494                ONCE.call_once(|| {
495                    eprintln!(
496                        "vae attn check: ok={ok1}/{ok0} wrote={}/{} of {} | host|max| {:.4e} | l1-host {:.4e} | l0-host {:.4e} | l1-l0 {:.4e}",
497                        wrote(&d1), wrote(&d0), attn.len(),
498                        host.iter().fold(0f32, |m, v| m.max(v.abs())),
499                        md(&d1, &host), md(&d0, &host), md(&d1, &d0),
500                    )
501                });
502                CHECKING.with(|c| c.set(false));
503            }
504            if std::env::var("CMF_VAE3D_SPLIT").as_deref() == Ok("1")
505                && crate::gpu::vae_attention_packed(
506                    qkv,
507                    nh,
508                    n,
509                    hd,
510                    scale,
511                    angles,
512                    self.eps as f32,
513                    attn,
514                )
515            {
516                return;
517            }
518            let t_rp = std::time::Instant::now();
519            let mut qa = vec![0f32; nh * n * hd];
520            let mut ka = vec![0f32; nh * n * hd];
521            let mut va = vec![0f32; nh * n * hd];
522            {
523                let (pq, pk, pv) = (
524                    SendPtr(qa.as_mut_ptr()),
525                    SendPtr(ka.as_mut_ptr()),
526                    SendPtr(va.as_mut_ptr()),
527                );
528                let fill = |lo: usize, hi: usize| {
529                    for p in lo..hi {
530                        for h in 0..nh {
531                            let base = p * 3 * dim + h * 3 * hd;
532                            let dst = (h * n + p) * hd;
533                            // SAFETY: disjoint token ranges per worker,
534                            // and each token owns its own head slots.
535                            let (q, k, v) =
536                                unsafe { (pq.row(dst, hd), pk.row(dst, hd), pv.row(dst, hd)) };
537                            q.copy_from_slice(&qkv[base..base + hd]);
538                            k.copy_from_slice(&qkv[base + hd..base + 2 * hd]);
539                            v.copy_from_slice(&qkv[base + 2 * hd..base + 3 * hd]);
540                            // q and k carry the same norm + rotation the
541                            // host loop applies; v is untouched.
542                            for x in [&mut *q, &mut *k] {
543                                rms_norm_plain(x, self.eps);
544                                for j in 0..pairs {
545                                    let (sn, cs) = angles[p * pairs + j].sin_cos();
546                                    let (a, b) = (x[j], x[j + pairs]);
547                                    x[j] = a * cs - b * sn;
548                                    x[j + pairs] = a * sn + b * cs;
549                                }
550                            }
551                        }
552                    }
553                };
554                match pool {
555                    Some(pl) => pl.run_rows(n, &fill),
556                    None => fill(0, n),
557                }
558            }
559            vprof(2, t_rp);
560            let t_at = std::time::Instant::now();
561            if crate::gpu::dit_attention(&qa, &ka, &va, nh, nh, n, hd, scale, attn) {
562                vprof(3, t_at);
563                return;
564            }
565        }
566        let mut qh = vec![0f32; n * hd];
567        let mut kh = vec![0f32; n * hd];
568        let mut vt = vec![0f32; hd * n];
569        let mut scores = vec![0f32; n * n];
570        let mut oh = vec![0f32; n * hd];
571        for h in 0..nh {
572            for p in 0..n {
573                // to_qkv is viewed [.., heads, 3·hd] then chunked, so a
574                // head's q, k and v are adjacent — not three planes.
575                let base = p * 3 * dim + h * 3 * hd;
576                qh[p * hd..(p + 1) * hd].copy_from_slice(&qkv[base..base + hd]);
577                kh[p * hd..(p + 1) * hd].copy_from_slice(&qkv[base + hd..base + 2 * hd]);
578                for d in 0..hd {
579                    vt[d * n + p] = qkv[base + 2 * hd + d];
580                }
581            }
582            for (buf, _) in [(&mut qh, 0), (&mut kh, 1)] {
583                for p in 0..n {
584                    let x = &mut buf[p * hd..(p + 1) * hd];
585                    rms_norm_plain(x, self.eps);
586                    for j in 0..pairs {
587                        let (s, c) = angles[p * pairs + j].sin_cos();
588                        let (a, b) = (x[j], x[j + pairs]);
589                        x[j] = a * c - b * s;
590                        x[j + pairs] = a * s + b * c;
591                    }
592                }
593            }
594            for v in qh.iter_mut() {
595                *v *= scale;
596            }
597            crate::fcd_ops::gemm_nt(&qh, &kh, &mut scores, n, hd, n, pool);
598            let sp = SendPtr(scores.as_mut_ptr());
599            let soft = |lo: usize, hi: usize| {
600                for r in lo..hi {
601                    // SAFETY: workers own disjoint score rows.
602                    softmax_inplace(unsafe { sp.row(r * n, n) });
603                }
604            };
605            match pool {
606                Some(pl) => pl.run_rows(n, &soft),
607                None => soft(0, n),
608            }
609            crate::fcd_ops::gemm_nt(&scores, &vt, &mut oh, n, n, hd, pool);
610            for p in 0..n {
611                attn[p * dim + h * hd..p * dim + (h + 1) * hd]
612                    .copy_from_slice(&oh[p * hd..(p + 1) * hd]);
613            }
614        }
615    }
616
617    /// One tile-clip: latents `[z_channels, t, h, w]` → pixels
618    /// `[3, t·4, h·16, w·16]`, both channel-major.
619    fn decode_tile(&self, z: &[f32], t: usize, h: usize, w: usize) -> Vec<f32> {
620        let pool = self.pool.as_deref();
621        let dim = self.dim;
622        let np = t * h * w;
623        let suffix = 1 + self.n_reg;
624        let n = np + suffix;
625
626        // [C, T, H, W] → [T·H·W, C], then the 1×1×1 post-quant conv and
627        // the embedder, which are both plain linears at this point.
628        let zc = self.z_channels;
629        let mut rows = vec![0f32; np * zc];
630        for c in 0..zc {
631            for i in 0..np {
632                rows[i * zc + c] = z[c * np + i];
633            }
634        }
635        let mut pq = vec![0f32; np * zc];
636        self.post_quant.matmat(&rows, np, &mut pq, pool);
637        for r in pq.chunks_exact_mut(zc) {
638            for (v, &b) in r.iter_mut().zip(&self.post_quant_b) {
639                *v += b;
640            }
641        }
642        let mut x = vec![0f32; n * dim];
643        self.x_embed.matmat(&pq, np, &mut x[..np * dim], pool);
644        for r in x[..np * dim].chunks_exact_mut(dim) {
645            for (v, &b) in r.iter_mut().zip(&self.x_embed_b) {
646                *v += b;
647            }
648        }
649        // register tokens, then one all-zero token
650        x[np * dim..(np + self.n_reg) * dim].copy_from_slice(&self.registers);
651
652        let angles = self.rope_angles(t, h, w, suffix);
653        let mut xn = vec![0f32; n * dim];
654        let mut qkv = vec![0f32; n * 3 * dim];
655        let mut attn = vec![0f32; n * dim];
656        let mut proj = vec![0f32; n * dim];
657        let inner = 4 * dim;
658        for blk in &self.blocks {
659            let t = std::time::Instant::now();
660            {
661                let px = SendPtr(xn.as_mut_ptr());
662                rows_par(pool, n, &|lo, hi| {
663                    for p in lo..hi {
664                        // SAFETY: workers own disjoint token rows.
665                        rms_norm_into(&x[p * dim..(p + 1) * dim], &blk.norm1, self.eps, unsafe {
666                            px.row(p * dim, dim)
667                        });
668                    }
669                });
670            }
671            vprof(0, t);
672            let t = std::time::Instant::now();
673            // The whole attention half on the card: qkv GEMM, bias, the
674            // weightless q/k norm, RoPE, attention, output projection —
675            // nothing crossing the bus between them. The DiT's chain cut
676            // its step 29%; this decoder spent 18.2 s of 26.1 on the
677            // same six steps. `CMF_VAE3D_FUSE=0` restores the host chain.
678            if std::env::var("CMF_GPU_DEBUG").is_ok() {
679                static ONCE: std::sync::Once = std::sync::Once::new();
680                ONCE.call_once(|| {
681                    eprintln!(
682                        "vae3d shape: n={n} dim={dim} heads={} head_dim={} pairs={}",
683                        self.heads,
684                        self.head_dim,
685                        angles.len() / n.max(1)
686                    )
687                });
688            }
689            let mut fused = false;
690            // Default since the split kernel's encoder started reaching
691            // the queue: this stage 26.1 s → 16.6 s and a 2-step render
692            // 57.1 s → 46.9 s, at 2.33e-3 rel rms from the host chain
693            // (max 8/255 on 3% of pixels) — four times inside the gate
694            // the DiT's own device path is held to. `CMF_VAE3D_FUSE=0`
695            // restores the host chain.
696            //
697            // It read 0.296 rel rms and 97% of pixels for most of a day,
698            // and none of that was this code: the split never ran, so V
699            // was zeros and P·V was zero. Zero does not depend on
700            // addressing, which is why every layout experiment agreed
701            // with every other and sent the search the wrong way.
702            //
703            // Narrowed to four stages, with everything else measured
704            // and cleared (`CMF_VAE3D_CHECK=1` runs the harness):
705            //   * the device attention returns ZEROS — proven with a
706            //     sentinel fill, so "it wrote" is a fact, not a guess;
707            //   * the split reproduces the host repack EXACTLY at this
708            //     head-interleaved layout (maxdiff 0.0000e0);
709            //   * qk-norm + RoPE match the host loop to 3.8e-6 against
710            //     a magnitude of 6.87 — f32 rounding;
711            //   * the plane pickup is sound: with an empty slice
712            //     `Scratch::ensure` asks for 0 bytes, its `cap >= need`
713            //     arm holds for any live slot, and both sides name the
714            //     same slots with the same usage.
715            // So the zeros are made in QK, softmax, PV or the unstack,
716            // at nh=32 hd=64 n=1797 — shapes at which that same code is
717            // correct when the planes arrive by upload instead
718            // (`resident=false`, the shipped path). Give each of those
719            // four private buffers, one at a time; the first that comes
720            // back zero is the answer. Worth 26.1 s → 16.1 s on this stage
721            // and 57.6 s → 47.2 s on a 2-step render once it is right.
722            //
723            // What is known: the error survives with both GEMMs on the
724            // host (`CMF_VAE3D_SPLIT=1` reproduces it byte for byte), so
725            // it lives in the split or the qk-norm kernel, not in the
726            // resident hand-off. Teaching BOTH kernels this panel's
727            // layout (head-interleaved, mode=1) and its bias changed the
728            // output by nothing — which should be impossible at nh=32,
729            // where the two addressings differ for every head above the
730            // first. That contradiction is the thread to pull: the
731            // one-shot probe in `packed_src` fires on the DiT's first
732            // call and never reaches the VAE's, so instrument per layout.
733            if std::env::var("CMF_VAE3D_FUSE").as_deref() != Ok("0")
734                && crate::gpu::enabled_here()
735                && n >= 256
736            {
737                if let (Proj::Q(q), Proj::Q(o)) = (&blk.qkv, &blk.out) {
738                    if let (Some((m, i)), Some((_, oi))) =
739                        (q.mapped_device_gemm(), o.mapped_device_gemm())
740                    {
741                        fused = crate::gpu::vae_qkv_attn_out(
742                            m,
743                            i,
744                            oi,
745                            &xn,
746                            n,
747                            dim,
748                            self.heads,
749                            self.head_dim,
750                            1.0 / (self.head_dim as f32).sqrt(),
751                            &angles,
752                            self.eps as f32,
753                            &blk.qkv_b,
754                            &mut proj,
755                        );
756                    }
757                }
758            }
759            if fused {
760                vprof(1, t);
761            }
762            if !fused {
763                blk.qkv.matmat(&xn, n, &mut qkv, pool);
764                {
765                    let pq = SendPtr(qkv.as_mut_ptr());
766                    rows_par(pool, n, &|lo, hi| {
767                        for p in lo..hi {
768                            // SAFETY: disjoint token rows.
769                            for (v, &b) in unsafe { pq.row(p * 3 * dim, 3 * dim) }
770                                .iter_mut()
771                                .zip(&blk.qkv_b)
772                            {
773                                *v += b;
774                            }
775                        }
776                    });
777                }
778                vprof(1, t);
779                self.attention(&qkv, n, &mut attn, &angles);
780                let t = std::time::Instant::now();
781                blk.out.matmat(&attn, n, &mut proj, pool);
782                vprof(4, t);
783            }
784            {
785                let pxx = SendPtr(x.as_mut_ptr());
786                rows_par(pool, n, &|lo, hi| {
787                    for p in lo..hi {
788                        let r = &proj[p * dim..(p + 1) * dim];
789                        // SAFETY: workers own disjoint token rows.
790                        let dst = unsafe { pxx.row(p * dim, dim) };
791                        for (i, v) in dst.iter_mut().enumerate() {
792                            *v += (r[i] + blk.out_b[i]) * blk.scale1[i];
793                        }
794                    }
795                });
796            }
797            let t = std::time::Instant::now();
798            {
799                let px = SendPtr(xn.as_mut_ptr());
800                rows_par(pool, n, &|lo, hi| {
801                    for p in lo..hi {
802                        // SAFETY: workers own disjoint token rows.
803                        rms_norm_into(&x[p * dim..(p + 1) * dim], &blk.norm2, self.eps, unsafe {
804                            px.row(p * dim, dim)
805                        });
806                    }
807                });
808            }
809            vprof(0, t);
810            let t_ffn = std::time::Instant::now();
811            // Device-resident FFN when both weights are q4tp: fc1 →
812            // SwiGLU (with this VAE's gate/up bias) → fc2 without the
813            // intermediate panel crossing the bus twice per block.
814            // CMF_VAE3D_FFN=cpu forces the host chain.
815            if std::env::var("CMF_VAE3D_FFN").as_deref() != Ok("cpu")
816                && crate::gpu::enabled_here()
817                && n >= 64
818            {
819                if let (Proj::Q(q1), Proj::Q(q2)) = (&blk.w1, &blk.w2) {
820                    if let (Some((m, i1)), Some((_, i2))) =
821                        (q1.mapped_device_gemm(), q2.mapped_device_gemm())
822                    {
823                        let mut fout = vec![0f32; n * dim];
824                        if crate::gpu::q4tp_ffn_packed(
825                            m,
826                            i1,
827                            i2,
828                            &xn,
829                            n,
830                            dim,
831                            inner,
832                            Some(&blk.w1_b),
833                            &mut fout,
834                        ) {
835                            {
836                                let pxx = SendPtr(x.as_mut_ptr());
837                                rows_par(pool, n, &|lo, hi| {
838                                    for p in lo..hi {
839                                        let r = &fout[p * dim..(p + 1) * dim];
840                                        // SAFETY: disjoint token rows.
841                                        let dst = unsafe { pxx.row(p * dim, dim) };
842                                        for (i, v) in dst.iter_mut().enumerate() {
843                                            *v += (r[i] + blk.w2_b[i]) * blk.scale2[i];
844                                        }
845                                    }
846                                });
847                            }
848                            vprof(5, t_ffn);
849                            continue;
850                        }
851                    }
852                }
853            }
854            let mut gu = vec![0f32; n * 2 * inner];
855            blk.w1.matmat(&xn, n, &mut gu, pool);
856            let mut act = vec![0f32; n * inner];
857            for p in 0..n {
858                let r = &gu[p * 2 * inner..(p + 1) * 2 * inner];
859                for i in 0..inner {
860                    act[p * inner + i] =
861                        silu(r[i] + blk.w1_b[i]) * (r[inner + i] + blk.w1_b[inner + i]);
862                }
863            }
864            blk.w2.matmat(&act, n, &mut proj, pool);
865            {
866                let pxx = SendPtr(x.as_mut_ptr());
867                rows_par(pool, n, &|lo, hi| {
868                    for p in lo..hi {
869                        let r = &proj[p * dim..(p + 1) * dim];
870                        // SAFETY: disjoint token rows.
871                        let dst = unsafe { pxx.row(p * dim, dim) };
872                        for (i, v) in dst.iter_mut().enumerate() {
873                            *v += (r[i] + blk.w2_b[i]) * blk.scale2[i];
874                        }
875                    }
876                });
877            }
878            vprof(5, t_ffn);
879        }
880
881        let (pt, ps) = (self.patch_t, self.patch);
882        let od = 3 * pt * ps * ps;
883        let t_head = std::time::Instant::now();
884        let mut head = vec![0f32; np * dim];
885        {
886            let ph = SendPtr(head.as_mut_ptr());
887            rows_par(pool, np, &|lo, hi| {
888                for p in lo..hi {
889                    // SAFETY: disjoint token rows.
890                    layer_norm(
891                        &x[p * dim..(p + 1) * dim],
892                        &self.norm_out_w,
893                        &self.norm_out_b,
894                        self.eps,
895                        unsafe { ph.row(p * dim, dim) },
896                    );
897                }
898            });
899        }
900        let mut out = vec![0f32; np * od];
901        self.proj_out.matmat(&head, np, &mut out, pool);
902        for r in out.chunks_exact_mut(od) {
903            for (v, &b) in r.iter_mut().zip(&self.proj_out_b) {
904                *v += b;
905            }
906        }
907
908        vprof(6, t_head);
909        let t_px = std::time::Instant::now();
910        // [T,H,W, 3,pt,ps,ps] → [3, T·pt, H·ps, W·ps]
911        let (ot, oh, ow) = (t * pt, h * ps, w * ps);
912        let mut px = vec![0f32; 3 * ot * oh * ow];
913        for ti in 0..t {
914            for hi in 0..h {
915                for wi in 0..w {
916                    let src =
917                        &out[((ti * h + hi) * w + wi) * od..((ti * h + hi) * w + wi + 1) * od];
918                    for c in 0..3 {
919                        for a in 0..pt {
920                            for b in 0..ps {
921                                for d in 0..ps {
922                                    px[((c * ot + ti * pt + a) * oh + hi * ps + b) * ow
923                                        + wi * ps
924                                        + d] = src[((c * pt + a) * ps + b) * ps + d];
925                                }
926                            }
927                        }
928                    }
929                }
930            }
931        }
932        vprof(7, t_px);
933        px
934    }
935
936    /// Spatially tiled decode of one temporal clip. `z` is
937    /// `[z_channels, t, zh, zw]`; the result is `[3, t·4, zh·16, zw·16]`.
938    fn decode_clip(&self, z: &[f32], t: usize, zh: usize, zw: usize) -> Vec<f32> {
939        let r = self.patch;
940        let (height, width) = (zh * r, zw * r);
941        let (ys, yl, yo) = split_tiles(height, r);
942        let (xs, xl, xo) = split_tiles(width, r);
943        let frames = t * self.patch_t;
944        let mut canvas = vec![0f32; 3 * frames * height * width];
945
946        // The reference blends each tile into its predecessor's tail and
947        // then trims the overlap off, so a pixel is written once.
948        let mut row_tails: Vec<Vec<f32>> = Vec::new();
949        let mut out_y = 0usize;
950        for (i, (&ip, &il)) in ys.iter().zip(&yl).enumerate() {
951            let (zi, zl) = (ip / r, il / r);
952            let mut new_tails: Vec<Vec<f32>> = Vec::new();
953            let mut left_tail: Option<Vec<f32>> = None;
954            let mut out_x = 0usize;
955            let mut row_h = 0usize;
956            for (j, (&jp, &jl)) in xs.iter().zip(&xl).enumerate() {
957                let (zj, zw_t) = (jp / r, jl / r);
958                let mut sub = vec![0f32; self.z_channels * t * zl * zw_t];
959                for c in 0..self.z_channels {
960                    for ti in 0..t {
961                        for hh in 0..zl {
962                            for ww in 0..zw_t {
963                                sub[((c * t + ti) * zl + hh) * zw_t + ww] =
964                                    z[((c * t + ti) * zh + zi + hh) * zw + zj + ww];
965                            }
966                        }
967                    }
968                }
969                let mut tile = self.decode_tile(&sub, t, zl, zw_t);
970                let (mut th, mut tw) = (zl * r, zw_t * r);
971                if i + 1 < ys.len() {
972                    new_tails.push(crop(&tile, frames, th, tw, th - yo[i], th, 0, tw));
973                }
974                let next_left = if j + 1 < xs.len() {
975                    Some(crop(&tile, frames, th, tw, 0, th, tw - xo[j], tw))
976                } else {
977                    None
978                };
979                if i > 0 {
980                    tile = blend(&row_tails[j], &tile, frames, th, tw, yo[i - 1], 2);
981                }
982                if j > 0 {
983                    let lt = left_tail.as_ref().unwrap();
984                    tile = blend(lt, &tile, frames, th, tw, xo[j - 1], 3);
985                }
986                left_tail = next_left;
987                if i + 1 < ys.len() {
988                    tile = crop(&tile, frames, th, tw, 0, th - yo[i], 0, tw);
989                    th -= yo[i];
990                }
991                if j + 1 < xs.len() {
992                    tile = crop(&tile, frames, th, tw, 0, th, 0, tw - xo[j]);
993                    tw -= xo[j];
994                }
995                for c in 0..3 {
996                    for f in 0..frames {
997                        for hh in 0..th {
998                            let dst = ((c * frames + f) * height + out_y + hh) * width + out_x;
999                            let src = ((c * frames + f) * th + hh) * tw;
1000                            canvas[dst..dst + tw].copy_from_slice(&tile[src..src + tw]);
1001                        }
1002                    }
1003                }
1004                out_x += tw;
1005                row_h = th;
1006            }
1007            row_tails = new_tails;
1008            out_y += row_h;
1009        }
1010        canvas
1011    }
1012
1013    /// Normalized latents `[z_channels, t_lat, zh, zw]` → RGB in [0, 1],
1014    /// `[3, frames, zh·16, zw·16]`.
1015    pub fn decode(&self, z: &[f32], t_lat: usize, zh: usize, zw: usize) -> (Vec<f32>, usize) {
1016        let zc = self.z_channels;
1017        let np = t_lat * zh * zw;
1018        let mut zz = vec![0f32; zc * np];
1019        for c in 0..zc {
1020            let (m, s) = (self.latents_mean[c], self.latents_std[c]);
1021            for i in 0..np {
1022                zz[c * np + i] = z[c * np + i] * s + m;
1023            }
1024        }
1025
1026        let ratio_t = self.patch_t;
1027        let chunk_tokens = CLIP_LENGTH.div_ceil(ratio_t); // 5
1028        let token_overlap = (chunk_tokens - TOKEN_DROP % chunk_tokens) % chunk_tokens; // 2
1029        let frame_pre_pad = (ratio_t - CLIP_LENGTH % ratio_t) % ratio_t; // 3
1030        let frame_overlap = (token_overlap * ratio_t).saturating_sub(frame_pre_pad); // 5
1031        let chunk_dec = chunk_tokens * ratio_t; // 20
1032
1033        // The encoder dropped TOKEN_DROP tokens off the tail, so the
1034        // decoder plans against a longer pseudo-sequence and pads the
1035        // real one out with a repeat of its last token.
1036        let mut pseudo = t_lat + TOKEN_DROP;
1037        let mut pad = 0usize;
1038        if pseudo % chunk_tokens != 0 {
1039            pad = chunk_tokens - pseudo % chunk_tokens;
1040            pseudo += pad;
1041        }
1042        let mut chunks = pseudo / chunk_tokens - usize::from(TOKEN_DROP > 0);
1043        if chunks < 1 {
1044            pad += chunk_tokens;
1045            chunks += 1;
1046        }
1047        let t_pad = t_lat + pad;
1048        if pad > 0 {
1049            let mut grown = vec![0f32; zc * t_pad * zh * zw];
1050            for c in 0..zc {
1051                for ti in 0..t_pad {
1052                    let src = ti.min(t_lat - 1);
1053                    let a = (c * t_lat + src) * zh * zw;
1054                    let b = (c * t_pad + ti) * zh * zw;
1055                    grown[b..b + zh * zw].copy_from_slice(&zz[a..a + zh * zw]);
1056                }
1057            }
1058            zz = grown;
1059        }
1060
1061        let (h, w) = (zh * self.patch, zw * self.patch);
1062        let mut out: Vec<f32> = Vec::new();
1063        let mut carry: Option<(Vec<f32>, usize)> = None;
1064        for i in 0..chunks {
1065            let a = i * chunk_tokens;
1066            let b = (a + chunk_tokens + token_overlap).min(t_pad);
1067            let n = b.saturating_sub(a.min(t_pad));
1068            if n == 0 {
1069                continue;
1070            }
1071            let mut sub = vec![0f32; zc * n * zh * zw];
1072            for c in 0..zc {
1073                let src = (c * t_pad + a) * zh * zw;
1074                let dst = c * n * zh * zw;
1075                sub[dst..dst + n * zh * zw].copy_from_slice(&zz[src..src + n * zh * zw]);
1076            }
1077            let dec = self.decode_clip(&sub, n, zh, zw);
1078            let dec_frames = n * ratio_t;
1079            for j in 0..2 {
1080                let fa = j * chunk_dec;
1081                let fb = (fa + chunk_dec).min(dec_frames);
1082                if fb <= fa + frame_pre_pad {
1083                    continue;
1084                }
1085                let mut part = frames_of(&dec, dec_frames, h, w, fa + frame_pre_pad, fb);
1086                let mut pn = fb - fa - frame_pre_pad;
1087                if j == 0 {
1088                    if let Some((tail, tn)) = carry.take() {
1089                        part = blend_frames(&tail, tn, &part, pn, h, w, frame_overlap);
1090                        pn = part.len() / (3 * h * w);
1091                    }
1092                    append_frames(&mut out, &part, pn, h, w);
1093                } else {
1094                    carry = Some((part, pn));
1095                }
1096            }
1097            if i + 1 == chunks {
1098                if let Some((tail, tn)) = carry.take() {
1099                    append_frames(&mut out, &tail, tn, h, w);
1100                }
1101            }
1102        }
1103
1104        // Undo the ImageNet pixel normalization and clamp; the reference
1105        // then maps to [-1, 1] and every caller maps straight back, so
1106        // stop at [0, 1].
1107        let frames = out.len() / (3 * h * w);
1108        let want = t_lat * ratio_t - pad_frames(t_lat, pad, chunk_tokens, ratio_t);
1109        for c in 0..3 {
1110            let base = c * frames * h * w;
1111            for v in out[base..base + frames * h * w].iter_mut() {
1112                *v = (*v * IMAGENET_STD[c] + IMAGENET_MEAN[c]).clamp(0.0, 1.0);
1113            }
1114        }
1115        let keep = want.min(frames);
1116        if keep < frames {
1117            let mut trimmed = vec![0f32; 3 * keep * h * w];
1118            for c in 0..3 {
1119                let src = c * frames * h * w;
1120                let dst = c * keep * h * w;
1121                trimmed[dst..dst + keep * h * w].copy_from_slice(&out[src..src + keep * h * w]);
1122            }
1123            out = trimmed;
1124        }
1125        (out, keep)
1126    }
1127
1128    pub fn spatial_ratio(&self) -> usize {
1129        self.patch
1130    }
1131    pub fn temporal_ratio(&self) -> usize {
1132        self.patch_t
1133    }
1134}
1135
1136/// Frames the padding tokens manufactured, which the reference trims.
1137fn pad_frames(t_lat: usize, pad: usize, chunk_tokens: usize, ratio_t: usize) -> usize {
1138    if pad == 0 {
1139        return 0;
1140    }
1141    let intra = CLIP_LENGTH % ratio_t;
1142    if intra == 0 {
1143        return pad * ratio_t;
1144    }
1145    (0..pad)
1146        .map(|k| {
1147            if (t_lat + k) % chunk_tokens == 0 {
1148                intra
1149            } else {
1150                ratio_t
1151            }
1152        })
1153        .sum()
1154}
1155
1156/// `[3, f, h, w]` sub-rectangle.
1157#[allow(clippy::too_many_arguments)]
1158fn crop(
1159    x: &[f32],
1160    f: usize,
1161    h: usize,
1162    w: usize,
1163    y0: usize,
1164    y1: usize,
1165    x0: usize,
1166    x1: usize,
1167) -> Vec<f32> {
1168    let (nh, nw) = (y1 - y0, x1 - x0);
1169    let mut out = vec![0f32; 3 * f * nh * nw];
1170    for c in 0..3 {
1171        for fi in 0..f {
1172            for yy in 0..nh {
1173                let s = ((c * f + fi) * h + y0 + yy) * w + x0;
1174                let d = ((c * f + fi) * nh + yy) * nw;
1175                out[d..d + nw].copy_from_slice(&x[s..s + nw]);
1176            }
1177        }
1178    }
1179    out
1180}
1181
1182/// Linear cross-fade of `a`'s tail into `b`'s head along `dim` (2 = y,
1183/// 3 = x); the result is `b` with its first `extent` rows/columns
1184/// replaced by the blend.
1185#[allow(clippy::too_many_arguments)]
1186fn blend(
1187    a: &[f32],
1188    b: &[f32],
1189    f: usize,
1190    h: usize,
1191    w: usize,
1192    extent: usize,
1193    dim: usize,
1194) -> Vec<f32> {
1195    let ah = a.len() / (3 * f * w);
1196    let aw = a.len() / (3 * f * h);
1197    let mut out = b.to_vec();
1198    let e = if dim == 2 {
1199        extent.min(ah).min(h)
1200    } else {
1201        extent.min(aw).min(w)
1202    };
1203    for c in 0..3 {
1204        for fi in 0..f {
1205            for k in 0..e {
1206                let wb = k as f32 / e as f32;
1207                let wa = 1.0 - wb;
1208                if dim == 2 {
1209                    let sa = ((c * f + fi) * ah + ah - e + k) * w;
1210                    let sb = ((c * f + fi) * h + k) * w;
1211                    for x in 0..w {
1212                        out[sb + x] = a[sa + x] * wa + b[sb + x] * wb;
1213                    }
1214                } else {
1215                    for y in 0..h {
1216                        let sa = ((c * f + fi) * h + y) * aw + aw - e + k;
1217                        let sb = ((c * f + fi) * h + y) * w + k;
1218                        out[sb] = a[sa] * wa + b[sb] * wb;
1219                    }
1220                }
1221            }
1222        }
1223    }
1224    out
1225}
1226
1227/// Frames `[a, b)` of a `[3, f, h, w]` clip.
1228fn frames_of(x: &[f32], f: usize, h: usize, w: usize, a: usize, b: usize) -> Vec<f32> {
1229    let n = b - a;
1230    let mut out = vec![0f32; 3 * n * h * w];
1231    for c in 0..3 {
1232        let s = (c * f + a) * h * w;
1233        let d = c * n * h * w;
1234        out[d..d + n * h * w].copy_from_slice(&x[s..s + n * h * w]);
1235    }
1236    out
1237}
1238
1239/// Cross-fade `tail` into the head of `part` along the frame axis.
1240#[allow(clippy::too_many_arguments)]
1241fn blend_frames(
1242    tail: &[f32],
1243    tn: usize,
1244    part: &[f32],
1245    pn: usize,
1246    h: usize,
1247    w: usize,
1248    extent: usize,
1249) -> Vec<f32> {
1250    let e = extent.min(tn).min(pn);
1251    let mut out = part.to_vec();
1252    for c in 0..3 {
1253        for k in 0..e {
1254            let wb = k as f32 / e as f32;
1255            let wa = 1.0 - wb;
1256            let s = ((c * tn) + tn - e + k) * h * w;
1257            let d = ((c * pn) + k) * h * w;
1258            for i in 0..h * w {
1259                out[d + i] = tail[s + i] * wa + part[d + i] * wb;
1260            }
1261        }
1262    }
1263    out
1264}
1265
1266/// Append a `[3, n, h, w]` clip to a growing `[3, ?, h, w]` buffer.
1267fn append_frames(out: &mut Vec<f32>, part: &[f32], n: usize, h: usize, w: usize) {
1268    let old = out.len() / (3 * h * w);
1269    let total = old + n;
1270    let mut grown = vec![0f32; 3 * total * h * w];
1271    for c in 0..3 {
1272        if old > 0 {
1273            let s = c * old * h * w;
1274            let d = c * total * h * w;
1275            grown[d..d + old * h * w].copy_from_slice(&out[s..s + old * h * w]);
1276        }
1277        let s = c * n * h * w;
1278        let d = (c * total + old) * h * w;
1279        grown[d..d + n * h * w].copy_from_slice(&part[s..s + n * h * w]);
1280    }
1281    *out = grown;
1282}
1283
1284// ── the encoder, for keyframes only ─────────────────────────────────
1285//
1286// `fl2va` conditions on a first and/or last frame, and a frame is ONE
1287// frame. That collapses the whole 3-D causal encoder to a 2-D one:
1288// causal padding fills the front with zeros, and the reference's own
1289// `autopad="causal_zero"` therefore trims the kernel to
1290// `weight[:, :, -T:]` — at T = 1, the last temporal tap and nothing
1291// else. So the packer stores that tap alone (a third of the bytes) and
1292// this runs plain 2-D convolutions over it. Encoding real video would
1293// need the other two taps back; keyframes never reach them.
1294
1295/// A 2-D convolution with the reference's padding policy: reflect on
1296/// H and W, applied by hand because the checkpoint's convolutions
1297/// carry no padding of their own.
1298struct EncConv {
1299    w: Vec<f32>, // [out, in, kh, kw]
1300    b: Vec<f32>,
1301    out_ch: usize,
1302    in_ch: usize,
1303    k: usize,
1304    stride: usize,
1305    pad: usize,
1306}
1307
1308impl EncConv {
1309    fn load(model: &Arc<CmfModel>, name: &str, stride: usize, pad: usize) -> Result<Self, String> {
1310        let e = model
1311            .tensor(&format!("{name}.weight"))
1312            .ok_or_else(|| format!("missing {name}.weight"))?;
1313        // [out, in, kh, kw] — the packer already dropped the temporal axis.
1314        let (out_ch, in_ch, k) = (e.shape[0], e.shape[1], e.shape[2]);
1315        Ok(Self {
1316            w: crate::dit::cmf_f32(model, &format!("{name}.weight"))?,
1317            b: crate::dit::cmf_f32(model, &format!("{name}.bias"))?,
1318            out_ch,
1319            in_ch,
1320            k,
1321            stride,
1322            pad,
1323        })
1324    }
1325
1326    /// `x` is `[in_ch, h, w]`; reflect-padded by `self.pad` on each side.
1327    fn apply(
1328        &self,
1329        x: &[f32],
1330        h: usize,
1331        w: usize,
1332        pool: Option<&Pool>,
1333    ) -> (Vec<f32>, usize, usize) {
1334        let (ph, pw) = (h + 2 * self.pad, w + 2 * self.pad);
1335        let oh = (ph - self.k) / self.stride + 1;
1336        let ow = (pw - self.k) / self.stride + 1;
1337        // Reflect padding: index |p| mirrored about the edges, which for
1338        // pad 1 on a plane of at least 2 is just the neighbour row.
1339        let refl = |i: isize, n: usize| -> usize {
1340            let n = n as isize;
1341            let mut i = i;
1342            while i < 0 || i >= n {
1343                if i < 0 {
1344                    i = -i;
1345                }
1346                if i >= n {
1347                    i = 2 * (n - 1) - i;
1348                }
1349            }
1350            i as usize
1351        };
1352        let mut out = vec![0f32; self.out_ch * oh * ow];
1353        let ptr = SendPtr(out.as_mut_ptr());
1354        let work = |lo: usize, hi: usize| {
1355            for o in lo..hi {
1356                // SAFETY: workers own disjoint output channels.
1357                let dst = unsafe { ptr.row(o * oh * ow, oh * ow) };
1358                dst.fill(self.b[o]);
1359                for i in 0..self.in_ch {
1360                    let ker = &self.w[(o * self.in_ch + i) * self.k * self.k
1361                        ..(o * self.in_ch + i + 1) * self.k * self.k];
1362                    let src = &x[i * h * w..(i + 1) * h * w];
1363                    for oy in 0..oh {
1364                        for ox in 0..ow {
1365                            let mut acc = 0f32;
1366                            for ky in 0..self.k {
1367                                let sy = (oy * self.stride + ky) as isize - self.pad as isize;
1368                                let sy = refl(sy, h);
1369                                for kx in 0..self.k {
1370                                    let sx = (ox * self.stride + kx) as isize - self.pad as isize;
1371                                    acc += ker[ky * self.k + kx] * src[sy * w + refl(sx, w)];
1372                                }
1373                            }
1374                            dst[oy * ow + ox] += acc;
1375                        }
1376                    }
1377                }
1378            }
1379        };
1380        match pool {
1381            Some(p) => p.run_rows(self.out_ch, &work),
1382            None => work(0, self.out_ch),
1383        }
1384        (out, oh, ow)
1385    }
1386}
1387
1388/// GroupNorm(32) with affine parameters, over `[ch, h, w]`.
1389struct GroupNorm {
1390    w: Vec<f32>,
1391    b: Vec<f32>,
1392}
1393
1394impl GroupNorm {
1395    fn load(model: &Arc<CmfModel>, name: &str) -> Result<Self, String> {
1396        Ok(Self {
1397            w: crate::dit::cmf_f32(model, &format!("{name}.weight"))?,
1398            b: crate::dit::cmf_f32(model, &format!("{name}.bias"))?,
1399        })
1400    }
1401
1402    fn apply(&self, x: &mut [f32], ch: usize, hw: usize) {
1403        let groups = 32;
1404        let per = ch / groups;
1405        for g in 0..groups {
1406            let seg = &mut x[g * per * hw..(g + 1) * per * hw];
1407            let n = seg.len() as f64;
1408            let mean = seg.iter().map(|&v| v as f64).sum::<f64>() / n;
1409            let var = seg.iter().map(|&v| (v as f64 - mean).powi(2)).sum::<f64>() / n;
1410            let inv = 1.0 / (var + 1e-6).sqrt();
1411            for (i, v) in seg.iter_mut().enumerate() {
1412                let c = g * per + i / hw;
1413                *v = ((*v as f64 - mean) * inv) as f32 * self.w[c] + self.b[c];
1414            }
1415        }
1416    }
1417}
1418
1419struct ResBlock {
1420    norm1: GroupNorm,
1421    norm2: GroupNorm,
1422    conv1: EncConv,
1423    conv2: EncConv,
1424    shortcut: Option<EncConv>,
1425}
1426
1427/// The encoder half: `[3, h, w]` in [-1, 1] → normalized latents
1428/// `[24, h/16, w/16]`.
1429pub struct VideoVaeEncoder {
1430    conv_in: EncConv,
1431    levels: Vec<(Vec<ResBlock>, Option<EncConv>)>,
1432    norm_out: GroupNorm,
1433    conv_out: EncConv,
1434    quant: Vec<f32>, // [48, 48] 1x1x1
1435    quant_b: Vec<f32>,
1436    latents_mean: Vec<f32>,
1437    latents_std: Vec<f32>,
1438    pool: Option<Arc<Pool>>,
1439    z_channels: usize,
1440    ratio: usize,
1441}
1442
1443impl VideoVaeEncoder {
1444    pub fn from_cmf(model: &Arc<CmfModel>) -> Result<Self, String> {
1445        let cfg: serde_json::Value = serde_json::from_slice(
1446            model
1447                .tensor_bytes("vvae.config_json")
1448                .map_err(|e| e.to_string())?,
1449        )
1450        .map_err(|e| format!("vvae.config_json: {e}"))?;
1451        let space_down: Vec<usize> = cfg["space_down"]
1452            .as_array()
1453            .map(|a| a.iter().map(|v| v.as_u64().unwrap_or(1) as usize).collect())
1454            .unwrap_or_else(|| vec![2, 2, 2, 2, 1, 1]);
1455        let n_res = cfg["num_res_blocks"].as_u64().unwrap_or(2) as usize;
1456        let mut levels = Vec::new();
1457        for (i, &sd) in space_down.iter().enumerate() {
1458            let mut blocks = Vec::new();
1459            for j in 0..n_res {
1460                let p = format!("vvae.enc.down.{i}.block.{j}");
1461                let shortcut = model
1462                    .tensor(&format!("{p}.nin_shortcut.weight"))
1463                    .map(|_| EncConv::load(model, &format!("{p}.nin_shortcut"), 1, 0))
1464                    .transpose()?;
1465                blocks.push(ResBlock {
1466                    norm1: GroupNorm::load(model, &format!("{p}.norm1"))?,
1467                    norm2: GroupNorm::load(model, &format!("{p}.norm2"))?,
1468                    conv1: EncConv::load(model, &format!("{p}.conv1"), 1, 1)?,
1469                    conv2: EncConv::load(model, &format!("{p}.conv2"), 1, 1)?,
1470                    shortcut,
1471                });
1472            }
1473            // The downsample's own convolution pads nothing: the caller
1474            // reflect-pads one row and column on the far side instead.
1475            let down = if sd > 1 {
1476                Some(EncConv::load(
1477                    model,
1478                    &format!("vvae.enc.down.{i}.downsample.conv"),
1479                    sd,
1480                    0,
1481                )?)
1482            } else {
1483                None
1484            };
1485            levels.push((blocks, down));
1486        }
1487        Ok(Self {
1488            conv_in: EncConv::load(model, "vvae.enc.conv_in", 1, 1)?,
1489            levels,
1490            norm_out: GroupNorm::load(model, "vvae.enc.norm_out")?,
1491            conv_out: EncConv::load(model, "vvae.enc.conv_out", 1, 1)?,
1492            quant: crate::dit::cmf_f32(model, "vvae.quant_conv.weight")?,
1493            quant_b: crate::dit::cmf_f32(model, "vvae.quant_conv.bias")?,
1494            latents_mean: crate::dit::cmf_f32(model, "vvae.latents_mean")?,
1495            latents_std: crate::dit::cmf_f32(model, "vvae.latents_std")?,
1496            pool: Pool::from_env(),
1497            z_channels: cfg["z_channels"].as_u64().unwrap_or(24) as usize,
1498            ratio: cfg["patch_size"].as_u64().unwrap_or(16) as usize,
1499        })
1500    }
1501
1502    /// One tile, unpadded: `[3, h, w]` → moments `[2·z, h/16, w/16]`.
1503    fn encode_tile(&self, x: &[f32], h: usize, w: usize) -> (Vec<f32>, usize, usize) {
1504        let pool = self.pool.as_deref();
1505        let (mut cur, mut ch, mut cw) = self.conv_in.apply(x, h, w, pool);
1506        let mut c = self.conv_in.out_ch;
1507        for (blocks, down) in &self.levels {
1508            for b in blocks {
1509                let mut hh = cur.clone();
1510                self.norm_out_like(&b.norm1, &mut hh, c, ch * cw);
1511                for v in hh.iter_mut() {
1512                    *v = silu(*v);
1513                }
1514                let (mut t, th, tw) = b.conv1.apply(&hh, ch, cw, pool);
1515                let oc = b.conv1.out_ch;
1516                self.norm_out_like(&b.norm2, &mut t, oc, th * tw);
1517                for v in t.iter_mut() {
1518                    *v = silu(*v);
1519                }
1520                let (t2, th2, tw2) = b.conv2.apply(&t, th, tw, pool);
1521                let skip = match &b.shortcut {
1522                    Some(s) => s.apply(&cur, ch, cw, pool).0,
1523                    None => cur.clone(),
1524                };
1525                cur = t2.iter().zip(&skip).map(|(&a, &b)| a + b).collect();
1526                (ch, cw, c) = (th2, tw2, oc);
1527            }
1528            if let Some(d) = down {
1529                // reflect one row and column on the far side, as the
1530                // reference does before its unpadded stride-2 kernel
1531                let (padded, ph, pw) = reflect_pad_far(&cur, c, ch, cw);
1532                let (t, th, tw) = d.apply(&padded, ph, pw, pool);
1533                cur = t;
1534                (ch, cw, c) = (th, tw, d.out_ch);
1535            }
1536        }
1537        self.norm_out_like(&self.norm_out, &mut cur, c, ch * cw);
1538        for v in cur.iter_mut() {
1539            *v = silu(*v);
1540        }
1541        let (moments, mh, mw) = self.conv_out.apply(&cur, ch, cw, pool);
1542        // quant_conv is 1x1x1: a per-position linear.
1543        let n = 2 * self.z_channels;
1544        let mut out = vec![0f32; n * mh * mw];
1545        for o in 0..n {
1546            for p in 0..mh * mw {
1547                let mut acc = self.quant_b[o];
1548                for i in 0..n {
1549                    acc += self.quant[o * n + i] * moments[i * mh * mw + p];
1550                }
1551                out[o * mh * mw + p] = acc;
1552            }
1553        }
1554        (out, mh, mw)
1555    }
1556
1557    fn norm_out_like(&self, g: &GroupNorm, x: &mut [f32], ch: usize, hw: usize) {
1558        g.apply(x, ch, hw);
1559    }
1560
1561    /// A single frame in [-1, 1], `[3, h, w]` → normalized latents
1562    /// `[z, h/16, w/16]`, spatially tiled exactly as the reference does.
1563    pub fn encode_frame(&self, rgb: &[f32], h: usize, w: usize) -> (Vec<f32>, usize, usize) {
1564        // [-1, 1] → [0, 1] → ImageNet-normalized, the reference's order.
1565        let mut x = vec![0f32; 3 * h * w];
1566        for c in 0..3 {
1567            for p in 0..h * w {
1568                let v = (rgb[c * h * w + p] + 1.0) * 0.5;
1569                x[c * h * w + p] = (v - IMAGENET_MEAN[c]) / IMAGENET_STD[c];
1570            }
1571        }
1572        let r = self.ratio;
1573        let (ys, yl, yo) = split_tiles(h, r);
1574        let (xs, xl, xo) = split_tiles(w, r);
1575        let (zh, zw) = (h / r, w / r);
1576        let zc = 2 * self.z_channels;
1577        let mut rows: Vec<Vec<Vec<f32>>> = Vec::new();
1578        let mut dims: Vec<Vec<(usize, usize)>> = Vec::new();
1579        for (&ip, &il) in ys.iter().zip(&yl) {
1580            let mut row = Vec::new();
1581            let mut rd = Vec::new();
1582            for (&jp, &jl) in xs.iter().zip(&xl) {
1583                let mut sub = vec![0f32; 3 * il * jl];
1584                for c in 0..3 {
1585                    for yy in 0..il {
1586                        let s = (c * h + ip + yy) * w + jp;
1587                        let d = (c * il + yy) * jl;
1588                        sub[d..d + jl].copy_from_slice(&x[s..s + jl]);
1589                    }
1590                }
1591                let (t, th, tw) = self.encode_tile(&sub, il, jl);
1592                row.push(t);
1593                rd.push((th, tw));
1594            }
1595            rows.push(row);
1596            dims.push(rd);
1597        }
1598        // Latent-space blend then trim, mirroring `tiled_encode`.
1599        let mut canvas = vec![0f32; zc * zh * zw];
1600        let mut out_y = 0usize;
1601        for i in 0..rows.len() {
1602            let mut out_x = 0usize;
1603            let mut row_h = 0usize;
1604            for j in 0..rows[i].len() {
1605                let (th, tw) = dims[i][j];
1606                let mut tile = rows[i][j].clone();
1607                let (mut ch_, mut cw_) = (th, tw);
1608                if i > 0 {
1609                    tile = blend_plane(
1610                        &rows[i - 1][j],
1611                        &tile,
1612                        zc,
1613                        dims[i - 1][j],
1614                        (ch_, cw_),
1615                        yo[i - 1] / r,
1616                        0,
1617                    );
1618                }
1619                if j > 0 {
1620                    tile = blend_plane(
1621                        &rows[i][j - 1],
1622                        &tile,
1623                        zc,
1624                        dims[i][j - 1],
1625                        (ch_, cw_),
1626                        xo[j - 1] / r,
1627                        1,
1628                    );
1629                }
1630                if i + 1 < rows.len() {
1631                    tile = crop_plane(&tile, zc, ch_, cw_, 0, ch_ - yo[i] / r, 0, cw_);
1632                    ch_ -= yo[i] / r;
1633                }
1634                if j + 1 < rows[i].len() {
1635                    tile = crop_plane(&tile, zc, ch_, cw_, 0, ch_, 0, cw_ - xo[j] / r);
1636                    cw_ -= xo[j] / r;
1637                }
1638                for c in 0..zc {
1639                    for yy in 0..ch_ {
1640                        let d = (c * zh + out_y + yy) * zw + out_x;
1641                        let s = (c * ch_ + yy) * cw_;
1642                        canvas[d..d + cw_].copy_from_slice(&tile[s..s + cw_]);
1643                    }
1644                }
1645                out_x += cw_;
1646                row_h = ch_;
1647            }
1648            out_y += row_h;
1649        }
1650        // The posterior MEAN is the first z channels; no sampling.
1651        let mut z = vec![0f32; self.z_channels * zh * zw];
1652        for c in 0..self.z_channels {
1653            let (m, s) = (self.latents_mean[c], self.latents_std[c]);
1654            for p in 0..zh * zw {
1655                z[c * zh * zw + p] = (canvas[c * zh * zw + p] - m) / s;
1656            }
1657        }
1658        (z, zh, zw)
1659    }
1660}
1661
1662/// Reflect one row and one column onto the far edge — `F.pad(x, (0,1,0,1))`.
1663fn reflect_pad_far(x: &[f32], c: usize, h: usize, w: usize) -> (Vec<f32>, usize, usize) {
1664    let (ph, pw) = (h + 1, w + 1);
1665    let mut out = vec![0f32; c * ph * pw];
1666    for ci in 0..c {
1667        for y in 0..ph {
1668            let sy = if y < h { y } else { h - 2 };
1669            for x2 in 0..pw {
1670                let sx = if x2 < w { x2 } else { w - 2 };
1671                out[(ci * ph + y) * pw + x2] = x[(ci * h + sy) * w + sx];
1672            }
1673        }
1674    }
1675    (out, ph, pw)
1676}
1677
1678/// Cross-fade `a`'s tail into `b`'s head over `extent`, `dim` 0 = y, 1 = x.
1679fn blend_plane(
1680    a: &[f32],
1681    b: &[f32],
1682    c: usize,
1683    ad: (usize, usize),
1684    bd: (usize, usize),
1685    extent: usize,
1686    dim: usize,
1687) -> Vec<f32> {
1688    let (ah, aw) = ad;
1689    let (bh, bw) = bd;
1690    let mut out = b.to_vec();
1691    let e = if dim == 0 {
1692        extent.min(ah).min(bh)
1693    } else {
1694        extent.min(aw).min(bw)
1695    };
1696    if e == 0 {
1697        return out;
1698    }
1699    for ci in 0..c {
1700        for k in 0..e {
1701            let wb = k as f32 / e as f32;
1702            let wa = 1.0 - wb;
1703            if dim == 0 {
1704                for x in 0..bw.min(aw) {
1705                    let sa = (ci * ah + ah - e + k) * aw + x;
1706                    let sb = (ci * bh + k) * bw + x;
1707                    out[sb] = a[sa] * wa + b[sb] * wb;
1708                }
1709            } else {
1710                for y in 0..bh.min(ah) {
1711                    let sa = (ci * ah + y) * aw + aw - e + k;
1712                    let sb = (ci * bh + y) * bw + k;
1713                    out[sb] = a[sa] * wa + b[sb] * wb;
1714                }
1715            }
1716        }
1717    }
1718    out
1719}
1720
1721/// `[c, h, w]` sub-rectangle.
1722#[allow(clippy::too_many_arguments)]
1723fn crop_plane(
1724    x: &[f32],
1725    c: usize,
1726    h: usize,
1727    w: usize,
1728    y0: usize,
1729    y1: usize,
1730    x0: usize,
1731    x1: usize,
1732) -> Vec<f32> {
1733    let (nh, nw) = (y1 - y0, x1 - x0);
1734    let mut out = vec![0f32; c * nh * nw];
1735    for ci in 0..c {
1736        for y in 0..nh {
1737            let s = (ci * h + y0 + y) * w + x0;
1738            let d = (ci * nh + y) * nw;
1739            out[d..d + nw].copy_from_slice(&x[s..s + nw]);
1740        }
1741    }
1742    out
1743}
1744
1745#[cfg(test)]
1746mod tests {
1747    use super::*;
1748
1749    #[test]
1750    fn tile_schedule_matches_the_reference() {
1751        // 288 tall: two 256 tiles, the overlap grown to swallow the slack.
1752        let (s, l, o) = split_tiles(288, 16);
1753        assert_eq!(s, vec![0, 32]);
1754        assert_eq!(l, vec![256, 256]);
1755        assert_eq!(o, vec![224]);
1756        // 512 wide does not fit in two tiles at the minimum overlap.
1757        let (s, l, o) = split_tiles(512, 16);
1758        assert_eq!(s, vec![0, 128, 256]);
1759        assert_eq!(l, vec![256; 3]);
1760        assert_eq!(o, vec![128, 128]);
1761        // Anything at or under one tile is one tile.
1762        assert_eq!(split_tiles(256, 16).0, vec![0]);
1763        assert_eq!(split_tiles(128, 16).1, vec![128]);
1764    }
1765
1766    #[test]
1767    fn temporal_constants_come_out_as_the_reference_computes_them() {
1768        let ratio_t = 4usize;
1769        let chunk = CLIP_LENGTH.div_ceil(ratio_t);
1770        assert_eq!(chunk, 5);
1771        assert_eq!((chunk - TOKEN_DROP % chunk) % chunk, 2);
1772        assert_eq!((ratio_t - CLIP_LENGTH % ratio_t) % ratio_t, 3);
1773    }
1774}