Skip to main content

cortiq_engine/
s2v.rs

1//! Kandinsky S2V `KandinskyS2VTransformer3DModel`: the speech-to-video DiT,
2//! read from a `kandinsky_s2v` container. This module is the exact host path
3//! and the geometry every device path shares.
4//!
5//! The visual stream is one packed sequence `[memory | ref | noisy]`:
6//! FramePack memory of the previous chunk at 4×, 2× and 1× temporal
7//! downsampling (negative temporal RoPE positions), the reference frame
8//! (pinned to temporal RoPE index 40), then the latent being denoised. Clean
9//! rows are modulated at t = 0 and noisy rows at t, so each visual block's
10//! modulation holds two rows and every row op selects one per token.
11//!
12//! Semantics reproduced (diffusers `transformer_kandinsky_s2v.py`):
13//! - adaLN: `LayerNorm(x)·(1 + scale) + shift`, residual `x + gate·out`;
14//! - attention: RMS-normalize q and k per head (ε 1e-5), rotate them when the
15//!   block has RoPE (never in cross-attention), 1/√128 softmax;
16//! - speech cross-attention: the blocks in `audio_inject_layers`, noisy rows only, frame i attends only to its own audio tokens, residual
17//!   ungated, LayerNorm with learned weight and bias;
18//! - GELU is the erf form, not the tanh approximation;
19//! - the time embedding and every modulation stay fp32 in the reference, so
20//!   they are computed once for the whole render here.
21
22use crate::dit::{Proj, cmf_f32};
23use crate::pool::Pool;
24use crate::zimage::host_gemm;
25use cortiq_core::CmfModel;
26use std::sync::Arc;
27
28pub const ARCH_NAME: &str = "kandinsky_s2v";
29pub const HEAD_DIM: usize = 128;
30pub const ROPE_PAIRS: usize = HEAD_DIM / 2;
31const NORM_EPS: f64 = 1e-5;
32/// Rows of the temporal RoPE table on each side of zero.
33const ROPE_T_MAX: i32 = 128;
34
35#[derive(Clone, Debug)]
36pub struct S2vConfig {
37    pub in_visual: usize,
38    pub out_visual: usize,
39    pub in_text: usize,
40    pub in_text2: usize,
41    pub in_audio: usize,
42    pub time_dim: usize,
43    pub patch: [usize; 3],
44    pub frame_buckets: [usize; 3],
45    pub ref_rope_index: i32,
46    pub dim: usize,
47    pub ff: usize,
48    pub n_text: usize,
49    pub n_audio: usize,
50    pub n_visual: usize,
51    pub axes: [usize; 3],
52    pub audio_layers: Vec<usize>,
53}
54
55impl S2vConfig {
56    pub fn from_json(v: &serde_json::Value) -> Result<Self, String> {
57        let u = |k: &str| -> Result<usize, String> {
58            v[k].as_u64().map(|x| x as usize).ok_or_else(|| format!("dit.config_json: no {k}"))
59        };
60        let tri = |k: &str| -> Result<[usize; 3], String> {
61            let a = v[k].as_array().ok_or_else(|| format!("dit.config_json: no {k}"))?;
62            if a.len() != 3 {
63                return Err(format!("dit.config_json: {k} is not a triple"));
64            }
65            Ok([
66                a[0].as_u64().unwrap_or(0) as usize,
67                a[1].as_u64().unwrap_or(0) as usize,
68                a[2].as_u64().unwrap_or(0) as usize,
69            ])
70        };
71        let cfg = S2vConfig {
72            in_visual: u("in_visual_dim")?,
73            out_visual: u("out_visual_dim")?,
74            in_text: u("in_text_dim")?,
75            in_text2: u("in_text_dim2")?,
76            in_audio: u("in_audio_dim")?,
77            time_dim: u("time_dim")?,
78            patch: tri("patch_size")?,
79            frame_buckets: tri("frame_buckets")?,
80            ref_rope_index: u("index_rope_ref_image")? as i32,
81            dim: u("model_dim")?,
82            ff: u("ff_dim")?,
83            n_text: u("num_text_blocks")?,
84            n_audio: u("num_audio_blocks")?,
85            n_visual: u("num_visual_blocks")?,
86            axes: tri("axes_dims")?,
87            audio_layers: v["audio_inject_layers"]
88                .as_array()
89                .ok_or("dit.config_json: no audio_inject_layers")?
90                .iter()
91                .map(|x| x.as_u64().unwrap_or(0) as usize)
92                .collect(),
93        };
94        if cfg.patch != [1, 2, 2] || cfg.axes.iter().sum::<usize>() != HEAD_DIM || cfg.dim % HEAD_DIM != 0 {
95            return Err(format!("unsupported S2V geometry: {cfg:?}"));
96        }
97        Ok(cfg)
98    }
99    pub fn heads(&self) -> usize {
100        self.dim / HEAD_DIM
101    }
102}
103
104/// Token layout of one forward. Every count derives from the latent shape.
105#[derive(Clone, Copy, Debug)]
106pub struct S2vGeom {
107    pub lt: usize,
108    pub lh: usize,
109    pub lw: usize,
110    /// Rows of memory 4×, 2×, 1×, the reference and the noisy tail.
111    pub n4: usize,
112    pub n2: usize,
113    pub n1: usize,
114    pub nref: usize,
115    pub nvis: usize,
116}
117
118impl S2vGeom {
119    pub fn new(cfg: &S2vConfig, lt: usize, lh: usize, lw: usize) -> Self {
120        let per = |c: usize| (lh / (2 * c)) * (lw / (2 * c));
121        let b = cfg.frame_buckets;
122        S2vGeom {
123            lt,
124            lh,
125            lw,
126            n4: (b[2] / 4) * per(4),
127            n2: (b[1] / 2) * per(2),
128            n1: b[0] * per(1),
129            nref: per(1),
130            nvis: lt * per(1),
131        }
132    }
133    pub fn clean(&self) -> usize {
134        self.n4 + self.n2 + self.n1 + self.nref
135    }
136    pub fn total(&self) -> usize {
137        self.clean() + self.nvis
138    }
139    pub fn per_frame(&self) -> usize {
140        self.nvis / self.lt
141    }
142}
143
144/// `torch.exp(-log(max_period)·arange(d)/d)` in f32, in the same order.
145fn freqs(d: usize) -> Vec<f32> {
146    let ln = (10000f64).ln() as f32;
147    (0..d).map(|j| (-ln * j as f32 / d as f32).exp()).collect()
148}
149
150/// RoPE of an explicit position list on one flat frequency ladder (the 1-D
151/// text and audio tables).
152pub fn rope_1d(pos: &[i32]) -> (Vec<f32>, Vec<f32>) {
153    let f = freqs(ROPE_PAIRS);
154    let mut c = Vec::with_capacity(pos.len() * ROPE_PAIRS);
155    let mut s = Vec::with_capacity(pos.len() * ROPE_PAIRS);
156    for &p in pos {
157        for &fr in &f {
158            let a = p as f32 * fr;
159            c.push(a.cos());
160            s.push(a.sin());
161        }
162    }
163    (c, s)
164}
165
166/// The 3-D RoPE of the packed visual sequence `[4× | 2× | 1× | ref | noisy]`,
167/// `[n][64]` cos and sin rows. The temporal table is offset by its half size
168/// so memory can sit at negative positions.
169pub fn rope_visual(cfg: &S2vConfig, g: &S2vGeom, sf: [f32; 3]) -> (Vec<f32>, Vec<f32>) {
170    let ladders = [freqs(cfg.axes[0] / 2), freqs(cfg.axes[1] / 2), freqs(cfg.axes[2] / 2)];
171    let (ph, pw) = (g.lh / 2, g.lw / 2);
172    let b = cfg.frame_buckets;
173    let bounds = [0i32, b[0] as i32, (b[0] + b[1]) as i32, (b[0] + b[1] + b[2]) as i32];
174    // (temporal positions, row step, column step) in sequence order.
175    let mut parts: Vec<(Vec<i32>, usize)> = Vec::new();
176    let span = |lo: i32, hi: i32, st: i32| -> Vec<i32> { (lo..hi).step_by(st as usize).collect() };
177    parts.push((span(-bounds[3], -bounds[2], 4), 4));
178    parts.push((span(-bounds[2], -bounds[1], 2), 2));
179    parts.push((span(-bounds[1], -bounds[0], 1), 1));
180    parts.push((vec![cfg.ref_rope_index], 1));
181    parts.push(((0..g.lt as i32).collect(), 1));
182    let mut cos = Vec::with_capacity(g.total() * ROPE_PAIRS);
183    let mut sin = Vec::with_capacity(g.total() * ROPE_PAIRS);
184    for (ts, st) in parts {
185        // The reference's memory buffers are cropped to the coarser patch, so
186        // both counts floor exactly as `memory_rope_pos` does.
187        let rows = (0..(ph - ph % st)).step_by(st);
188        for &t in &ts {
189            debug_assert!(t >= -ROPE_T_MAX && t < ROPE_T_MAX);
190            for r in rows.clone() {
191                for col in (0..(pw - pw % st)).step_by(st) {
192                    for (ax, lad) in ladders.iter().enumerate() {
193                        let p = [t, r as i32, col as i32][ax] as f32;
194                        for &fr in lad {
195                            let a = p * fr / sf[ax];
196                            cos.push(a.cos());
197                            sin.push(a.sin());
198                        }
199                    }
200                }
201            }
202        }
203    }
204    debug_assert_eq!(cos.len(), g.total() * ROPE_PAIRS);
205    (cos, sin)
206}
207
208/// Group boundaries of the frame-aligned speech attention: the first latent
209/// frame takes one token, the rest split the remainder evenly.
210pub fn audio_groups(tokens: usize, frames: usize) -> Vec<(usize, usize)> {
211    let rest = tokens - 1;
212    let bounds: Vec<usize> = (0..frames).map(|i| rest * i / (frames - 1)).collect();
213    let mut v = vec![(0usize, 1usize)];
214    for w in bounds.windows(2) {
215        v.push((1 + w[0], w[1] - w[0]));
216    }
217    v
218}
219
220/// The latent frame each speech token belongs to (audio RoPE positions).
221pub fn audio_rope_pos(tokens: usize, frames: usize) -> Vec<i32> {
222    let mut p = vec![0i32; tokens];
223    for (fr, (st, len)) in audio_groups(tokens, frames).into_iter().enumerate() {
224        for v in &mut p[st..st + len] {
225            *v = fr as i32;
226        }
227    }
228    p
229}
230
231pub(crate) struct Rows(pub(crate) *mut f32);
232unsafe impl Send for Rows {}
233unsafe impl Sync for Rows {}
234impl Rows {
235    /// SAFETY: callers take disjoint ranges.
236    #[allow(clippy::mut_from_ref)]
237    pub(crate) unsafe fn at(&self, off: usize, len: usize) -> &mut [f32] {
238        unsafe { std::slice::from_raw_parts_mut(self.0.add(off), len) }
239    }
240}
241
242pub(crate) fn pool_rows(pool: Option<&Pool>, n: usize, f: &(dyn Fn(usize, usize) + Sync)) {
243    match pool {
244        Some(p) => p.run_rows(n, f),
245        None => f(0, n),
246    }
247}
248
249/// `nn.GELU()` — the erf form (A&S 7.1.26, |ε| < 1.5e-7).
250#[inline]
251pub(crate) fn gelu(x: f32) -> f32 {
252    let z = x * std::f32::consts::FRAC_1_SQRT_2;
253    let a = z.abs();
254    let t = 1.0 / (1.0 + 0.327_591_1 * a);
255    let e = 1.0
256        - (((((1.061_405_4 * t - 1.453_152_1) * t) + 1.421_413_8) * t - 0.284_496_72) * t + 0.254_829_6)
257            * t
258            * (-a * a).exp();
259    0.5 * x * (1.0 + if z < 0.0 { -e } else { e })
260}
261
262#[inline]
263pub(crate) fn silu(v: f32) -> f32 {
264    v / (1.0 + (-v).exp())
265}
266
267/// LayerNorm over one row (biased variance, torch's form), no affine.
268#[inline]
269pub(crate) fn layer_norm_row(x: &[f32], eps: f64, dst: &mut [f32]) {
270    let n = x.len() as f64;
271    let mean = x.iter().map(|&v| v as f64).sum::<f64>() / n;
272    let var = x.iter().map(|&v| (v as f64 - mean) * (v as f64 - mean)).sum::<f64>() / n;
273    let inv = 1.0 / (var + eps).sqrt();
274    for (d, &v) in dst.iter_mut().zip(x) {
275        *d = ((v as f64 - mean) * inv) as f32;
276    }
277}
278
279fn rms_row(v: &mut [f32], w: &[f32], eps: f64) {
280    let ss = v.iter().map(|&x| (x as f64) * (x as f64)).sum::<f64>() / v.len() as f64;
281    let inv = 1.0 / (ss + eps).sqrt();
282    for (x, &g) in v.iter_mut().zip(w) {
283        *x = (*x as f64 * inv) as f32 * g;
284    }
285}
286
287fn softmax(row: &mut [f32]) {
288    let mx = row.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
289    let mut den = 0f64;
290    for r in row.iter_mut() {
291        *r = (*r - mx).exp();
292        den += *r as f64;
293    }
294    let inv = (1.0 / den) as f32;
295    for r in row.iter_mut() {
296        *r *= inv;
297    }
298}
299
300/// A linear layer read from the container: quantized weights stay mapped.
301pub(crate) struct Lin {
302    pub(crate) w: Proj,
303    pub(crate) b: Option<Vec<f32>>,
304}
305
306impl Lin {
307    pub(crate) fn load(m: &Arc<CmfModel>, p: &str) -> Result<Lin, String> {
308        let name = format!("{p}.weight");
309        let e = m.tensor(&name).ok_or_else(|| format!("missing tensor {name}"))?;
310        let w = match e.dtype {
311            cortiq_core::TensorDtype::F16 | cortiq_core::TensorDtype::Bf16 => {
312                let cols = *e.shape.last().unwrap_or(&1);
313                Proj::f32(cmf_f32(m, &name)?, cols)
314            }
315            _ => Proj::from_model(m, &name)?,
316        };
317        let bn = format!("{p}.bias");
318        let b = if m.tensor(&bn).is_some() { Some(cmf_f32(m, &bn)?) } else { None };
319        Ok(Lin { w, b })
320    }
321    pub(crate) fn rows(&self) -> usize {
322        self.w.rows()
323    }
324    /// y[n, rows] = x[n, cols]·Wᵀ + b.
325    pub(crate) fn apply(&self, x: &[f32], n: usize, pool: Option<&Pool>) -> Vec<f32> {
326        let rows = self.w.rows();
327        let mut y = vec![0f32; n * rows];
328        host_lin(&self.w, x, n, &mut y, pool);
329        if let Some(b) = &self.b {
330            let sr = Rows(y.as_mut_ptr());
331            pool_rows(pool, n, &|lo, hi| {
332                for r in lo..hi {
333                    // SAFETY: disjoint rows.
334                    for (d, &v) in unsafe { sr.at(r * rows, rows) }.iter_mut().zip(b) {
335                        *d += v;
336                    }
337                }
338            });
339        }
340        y
341    }
342}
343
344/// The host GEMM of the exact path; device offload is the chain's business.
345pub(crate) fn host_lin(p: &Proj, x: &[f32], n: usize, y: &mut [f32], pool: Option<&Pool>) {
346    let (rows, cols) = (p.rows(), p.cols());
347    match p {
348        Proj::F32 { w, .. } => host_gemm::gemm_nt(x, w, y, n, cols, rows, pool),
349        Proj::Q(q) => {
350            const CH: usize = 768;
351            let mut wbuf = vec![0f32; CH.min(rows) * cols];
352            let mut r0 = 0;
353            while r0 < rows {
354                let rc = CH.min(rows - r0);
355                {
356                    let wp = Rows(wbuf.as_mut_ptr());
357                    pool_rows(pool, rc, &|lo, hi| {
358                        for r in lo..hi {
359                            // SAFETY: disjoint rows.
360                            q.row_f32(r0 + r, unsafe { wp.at(r * cols, cols) });
361                        }
362                    });
363                }
364                host_gemm::gemm_nt_ld(x, &wbuf[..rc * cols], &mut y[r0..], rows, n, cols, rc, pool);
365                r0 += rc;
366            }
367        }
368    }
369}
370
371/// A LayerNorm's learned weight and bias.
372pub(crate) struct Affine {
373    pub(crate) w: Vec<f32>,
374    pub(crate) b: Vec<f32>,
375}
376
377impl Affine {
378    pub(crate) fn load(m: &Arc<CmfModel>, p: &str) -> Result<Affine, String> {
379        Ok(Affine { w: cmf_f32(m, &format!("{p}.weight"))?, b: cmf_f32(m, &format!("{p}.bias"))? })
380    }
381    pub(crate) fn apply(&self, x: &[f32], n: usize, pool: Option<&Pool>) -> Vec<f32> {
382        let d = self.w.len();
383        let mut y = vec![0f32; n * d];
384        let sr = Rows(y.as_mut_ptr());
385        pool_rows(pool, n, &|lo, hi| {
386            for r in lo..hi {
387                // SAFETY: disjoint rows.
388                let row = unsafe { sr.at(r * d, d) };
389                layer_norm_row(&x[r * d..(r + 1) * d], NORM_EPS, row);
390                for ((v, &w), &b) in row.iter_mut().zip(&self.w).zip(&self.b) {
391                    *v = *v * w + b;
392                }
393            }
394        });
395        y
396    }
397}
398
399pub(crate) struct Attn {
400    pub(crate) q: Lin,
401    pub(crate) k: Lin,
402    pub(crate) v: Lin,
403    pub(crate) o: Lin,
404    pub(crate) qn: Vec<f32>,
405    pub(crate) kn: Vec<f32>,
406}
407
408impl Attn {
409    fn load(m: &Arc<CmfModel>, p: &str) -> Result<Attn, String> {
410        Ok(Attn {
411            q: Lin::load(m, &format!("{p}.to_query"))?,
412            k: Lin::load(m, &format!("{p}.to_key"))?,
413            v: Lin::load(m, &format!("{p}.to_value"))?,
414            o: Lin::load(m, &format!("{p}.out_layer"))?,
415            qn: cmf_f32(m, &format!("{p}.query_norm.weight"))?,
416            kn: cmf_f32(m, &format!("{p}.key_norm.weight"))?,
417        })
418    }
419
420    /// `x` [n, h] queries, `ctx` [m, h] keys and values. `rope` rotates both
421    /// (same table: self-attention only). `groups` makes the attention
422    /// block-diagonal: per group `(q0, qn, k0, kn)`.
423    fn forward(
424        &self,
425        x: &[f32],
426        n: usize,
427        ctx: &[f32],
428        m: usize,
429        rope: Option<(&[f32], &[f32])>,
430        groups: Option<&[(usize, usize, usize, usize)]>,
431        pool: Option<&Pool>,
432    ) -> Vec<f32> {
433        let h = self.q.rows();
434        let nh = h / HEAD_DIM;
435        let mut q = self.q.apply(x, n, pool);
436        let mut k = self.k.apply(ctx, m, pool);
437        let v = self.v.apply(ctx, m, pool);
438        let norm_rot = |buf: &mut [f32], rows: usize, w: &[f32]| {
439            let sr = Rows(buf.as_mut_ptr());
440            pool_rows(pool, rows, &|lo, hi| {
441                for r in lo..hi {
442                    for hh in 0..nh {
443                        // SAFETY: disjoint rows.
444                        let row = unsafe { sr.at((r * nh + hh) * HEAD_DIM, HEAD_DIM) };
445                        rms_row(row, w, NORM_EPS);
446                        if let Some((cos, sin)) = rope {
447                            for j in 0..ROPE_PAIRS {
448                                let (c, s) = (cos[r * ROPE_PAIRS + j], sin[r * ROPE_PAIRS + j]);
449                                let (a, b) = (row[2 * j], row[2 * j + 1]);
450                                row[2 * j] = a * c - b * s;
451                                row[2 * j + 1] = a * s + b * c;
452                            }
453                        }
454                    }
455                }
456            });
457        };
458        norm_rot(&mut q, n, &self.qn);
459        norm_rot(&mut k, m, &self.kn);
460        let whole = [(0usize, n, 0usize, m)];
461        let gs: &[(usize, usize, usize, usize)] = groups.unwrap_or(&whole);
462        let scale = 1.0 / (HEAD_DIM as f32).sqrt();
463        let mut att = vec![0f32; n * h];
464        for &(q0, qn, k0, kn) in gs {
465            let mut qh = vec![0f32; qn * HEAD_DIM];
466            let mut kh = vec![0f32; kn * HEAD_DIM];
467            let mut vt = vec![0f32; HEAD_DIM * kn];
468            let mut sc = vec![0f32; qn * kn];
469            let mut oh = vec![0f32; qn * HEAD_DIM];
470            for hh in 0..nh {
471                for r in 0..qn {
472                    for d in 0..HEAD_DIM {
473                        qh[r * HEAD_DIM + d] = q[((q0 + r) * nh + hh) * HEAD_DIM + d] * scale;
474                    }
475                }
476                for r in 0..kn {
477                    let base = ((k0 + r) * nh + hh) * HEAD_DIM;
478                    kh[r * HEAD_DIM..(r + 1) * HEAD_DIM].copy_from_slice(&k[base..base + HEAD_DIM]);
479                    for d in 0..HEAD_DIM {
480                        vt[d * kn + r] = v[base + d];
481                    }
482                }
483                host_gemm::gemm_nt(&qh, &kh, &mut sc, qn, HEAD_DIM, kn, pool);
484                let sp = Rows(sc.as_mut_ptr());
485                pool_rows(pool, qn, &|lo, hi| {
486                    for r in lo..hi {
487                        // SAFETY: disjoint rows.
488                        softmax(unsafe { sp.at(r * kn, kn) });
489                    }
490                });
491                host_gemm::gemm_nt(&sc, &vt, &mut oh, qn, kn, HEAD_DIM, pool);
492                for r in 0..qn {
493                    let o = ((q0 + r) * nh + hh) * HEAD_DIM;
494                    att[o..o + HEAD_DIM].copy_from_slice(&oh[r * HEAD_DIM..(r + 1) * HEAD_DIM]);
495                }
496            }
497        }
498        self.o.apply(&att, n, pool)
499    }
500}
501
502pub(crate) struct Ffn {
503    pub(crate) w1: Lin,
504    pub(crate) w2: Lin,
505}
506
507impl Ffn {
508    fn load(m: &Arc<CmfModel>, p: &str) -> Result<Ffn, String> {
509        Ok(Ffn {
510            w1: Lin::load(m, &format!("{p}.in_layer"))?,
511            w2: Lin::load(m, &format!("{p}.out_layer"))?,
512        })
513    }
514    fn forward(&self, x: &[f32], n: usize, pool: Option<&Pool>) -> Vec<f32> {
515        let mut hid = self.w1.apply(x, n, pool);
516        let inner = self.w1.rows();
517        let sr = Rows(hid.as_mut_ptr());
518        pool_rows(pool, n, &|lo, hi| {
519            for r in lo..hi {
520                // SAFETY: disjoint rows.
521                for v in unsafe { sr.at(r * inner, inner) } {
522                    *v = gelu(*v);
523                }
524            }
525        });
526        self.w2.apply(&hid, n, pool)
527    }
528}
529
530pub(crate) struct VisualBlock {
531    pub(crate) sa: Attn,
532    pub(crate) ca: Attn,
533    pub(crate) v2a: Option<(Affine, Attn)>,
534    pub(crate) ffn: Ffn,
535}
536
537pub(crate) struct TextBlock {
538    pub(crate) attn: Attn,
539    pub(crate) ffn: Ffn,
540}
541
542pub(crate) struct AudioBlock {
543    pub(crate) sa_norm: Affine,
544    pub(crate) attn: Attn,
545    pub(crate) ff_norm: Affine,
546    pub(crate) ffn: Ffn,
547}
548
549pub struct S2vDit {
550    pub(crate) model: Arc<CmfModel>,
551    pub cfg: S2vConfig,
552    pub(crate) pool: Option<Arc<Pool>>,
553    pub(crate) time_in: Lin,
554    pub(crate) time_out: Lin,
555    pub(crate) time_freqs: Vec<f32>,
556    pub(crate) text_in: Lin,
557    pub(crate) text_norm: Affine,
558    pub(crate) pooled_in: Lin,
559    pub(crate) pooled_norm: Affine,
560    pub(crate) audio_in: Lin,
561    pub(crate) audio_norm: Affine,
562    pub(crate) emb1: Lin,
563    pub(crate) emb_p1: Lin,
564    pub(crate) emb_p2: Lin,
565    pub(crate) emb_p4: Lin,
566    pub(crate) mask: Vec<f32>,
567    pub(crate) text: Vec<TextBlock>,
568    pub(crate) audio: Vec<AudioBlock>,
569    pub(crate) visual: Vec<VisualBlock>,
570    pub(crate) out_w: Lin,
571}
572
573/// Inputs of one forward, in the reference's layouts: latents `[T, H, W, C]`.
574pub struct S2vInput<'a> {
575    pub x: &'a [f32],
576    pub reference: &'a [f32],
577    pub mem1: &'a [f32],
578    pub mem2: &'a [f32],
579    pub mem4: &'a [f32],
580    pub t: f32,
581    pub text_feats: &'a [f32],
582    pub audio_feats: &'a [f32],
583    pub pooled: &'a [f32],
584    pub geom: S2vGeom,
585    pub scale_factor: [f32; 3],
586}
587
588/// `[T', H', W', C]` patches → `[n, pt·ph·pw·C]` in the `(pt, ph, pw, c)` order
589/// the embedders were trained on.
590pub fn patchify(x: &[f32], c: usize, pt: usize, ps: usize, h: usize, w: usize) -> Vec<f32> {
591    let frames = x.len() / (h * w * c);
592    let (th, tw, tt) = (h / ps, w / ps, frames / pt);
593    let dim = pt * ps * ps * c;
594    let mut out = vec![0f32; tt * th * tw * dim];
595    for it in 0..tt {
596        for ih in 0..th {
597            for iw in 0..tw {
598                let row = (it * th + ih) * tw + iw;
599                let mut o = row * dim;
600                for dt in 0..pt {
601                    for dh in 0..ps {
602                        for dw in 0..ps {
603                            let src = (((it * pt + dt) * h + ih * ps + dh) * w + iw * ps + dw) * c;
604                            out[o..o + c].copy_from_slice(&x[src..src + c]);
605                            o += c;
606                        }
607                    }
608                }
609            }
610        }
611    }
612    out
613}
614
615/// `[n, ph·pw·C]` head output `[C][ph][pw]` → `[T, H, W, C]`.
616pub fn unpatchify_out(tok: &[f32], g: &S2vGeom, c: usize) -> Vec<f32> {
617    let (ph, pw) = (g.lh / 2, g.lw / 2);
618    let mut out = vec![0f32; g.lt * g.lh * g.lw * c];
619    for t in 0..g.lt {
620        for r in 0..ph {
621            for col in 0..pw {
622                let row = (t * ph + r) * pw + col;
623                for dh in 0..2 {
624                    for dw in 0..2 {
625                        let dst = ((t * g.lh + r * 2 + dh) * g.lw + col * 2 + dw) * c;
626                        for ch in 0..c {
627                            out[dst + ch] = tok[row * 4 * c + ch * 4 + dh * 2 + dw];
628                        }
629                    }
630                }
631            }
632        }
633    }
634    out
635}
636
637impl S2vDit {
638    pub fn from_cmf(model: &Arc<CmfModel>) -> Result<S2vDit, String> {
639        let raw = model.tensor_bytes("dit.config_json").map_err(|e| e.to_string())?;
640        let v: serde_json::Value = serde_json::from_slice(raw).map_err(|e| format!("dit.config_json: {e}"))?;
641        let cfg = S2vConfig::from_json(&v)?;
642        let m = model;
643        let mut text = Vec::with_capacity(cfg.n_text);
644        for i in 0..cfg.n_text {
645            let p = format!("dit.text_transformer_blocks.{i}");
646            text.push(TextBlock {
647                attn: Attn::load(m, &format!("{p}.self_attention"))?,
648                ffn: Ffn::load(m, &format!("{p}.feed_forward"))?,
649            });
650        }
651        let mut audio = Vec::with_capacity(cfg.n_audio);
652        for i in 0..cfg.n_audio {
653            let p = format!("dit.audio_transformer_blocks.{i}");
654            audio.push(AudioBlock {
655                sa_norm: Affine::load(m, &format!("{p}.self_attention_norm"))?,
656                attn: Attn::load(m, &format!("{p}.self_attention"))?,
657                ff_norm: Affine::load(m, &format!("{p}.feed_forward_norm"))?,
658                ffn: Ffn::load(m, &format!("{p}.feed_forward"))?,
659            });
660        }
661        let mut visual = Vec::with_capacity(cfg.n_visual);
662        for i in 0..cfg.n_visual {
663            let p = format!("dit.visual_transformer_blocks.{i}");
664            let v2a = if cfg.audio_layers.contains(&i) {
665                Some((
666                    Affine::load(m, &format!("{p}.cross_attention_norm_v2a"))?,
667                    Attn::load(m, &format!("{p}.cross_attention_v2a"))?,
668                ))
669            } else {
670                None
671            };
672            visual.push(VisualBlock {
673                sa: Attn::load(m, &format!("{p}.self_attention"))?,
674                ca: Attn::load(m, &format!("{p}.cross_attention"))?,
675                v2a,
676                ffn: Ffn::load(m, &format!("{p}.feed_forward"))?,
677            });
678        }
679        Ok(S2vDit {
680            model: m.clone(),
681            pool: Pool::from_env(),
682            time_in: Lin::load(m, "dit.time_embeddings.in_layer")?,
683            time_out: Lin::load(m, "dit.time_embeddings.out_layer")?,
684            time_freqs: freqs(cfg.dim / 2),
685            text_in: Lin::load(m, "dit.text_embeddings.in_layer")?,
686            text_norm: Affine::load(m, "dit.text_embeddings.norm")?,
687            pooled_in: Lin::load(m, "dit.pooled_text_embeddings.in_layer")?,
688            pooled_norm: Affine::load(m, "dit.pooled_text_embeddings.norm")?,
689            audio_in: Lin::load(m, "dit.audio_embeddings.in_layer")?,
690            audio_norm: Affine::load(m, "dit.audio_embeddings.norm")?,
691            emb1: Lin::load(m, "dit.visual_embeddings.in_layer")?,
692            emb_p1: Lin::load(m, "dit.visual_embeddings_proj1x.in_layer")?,
693            emb_p2: Lin::load(m, "dit.visual_embeddings_proj2x.in_layer")?,
694            emb_p4: Lin::load(m, "dit.visual_embeddings_proj4x.in_layer")?,
695            mask: cmf_f32(m, "dit.mask_embeddings.weight")?,
696            text,
697            audio,
698            visual,
699            out_w: Lin::load(m, "dit.out_layer.out_layer")?,
700            cfg,
701        })
702    }
703
704    pub(crate) fn pool(&self) -> Option<&Pool> {
705        self.pool.as_deref()
706    }
707
708    /// The linear part of a modulation: `Y[n, 9h] = silu(te)[n, time]·Wᵀ + b`.
709    /// Read from the file per block and dropped, so 2.3 B parameters of them
710    /// never sit in RAM.
711    pub(crate) fn modulate(&self, name: &str, te_silu: &[f32], n: usize) -> Result<Vec<f32>, String> {
712        let l = Lin::load(&self.model, name)?;
713        Ok(l.apply(te_silu, n, self.pool()))
714    }
715
716    /// `modulate` over many layers at once, a thread per layer: each read is a
717    /// bf16 widening and a small GEMM, so they scale and the pool would not help.
718    pub(crate) fn modulate_all(&self, names: &[String], te_silu: &[f32], n: usize) -> Result<Vec<Vec<f32>>, String> {
719        let threads = Pool::effective_threads().clamp(1, 16);
720        let next = std::sync::atomic::AtomicUsize::new(0);
721        let out: Vec<std::sync::Mutex<Option<Result<Vec<f32>, String>>>> = names.iter().map(|_| std::sync::Mutex::new(None)).collect();
722        std::thread::scope(|sc| {
723            for _ in 0..threads {
724                sc.spawn(|| {
725                    loop {
726                        let i = next.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
727                        if i >= names.len() {
728                            break;
729                        }
730                        let r = Lin::load(&self.model, &names[i]).map(|l| l.apply(te_silu, n, None));
731                        *out[i].lock().unwrap() = Some(r);
732                    }
733                });
734            }
735        });
736        out.into_iter().map(|m| m.into_inner().unwrap().unwrap()).collect()
737    }
738
739    /// `time_embeddings(t) + pooled_text_embeddings(pooled)`.
740    pub fn time_embed(&self, t: f32, pooled_emb: &[f32]) -> Vec<f32> {
741        let d = self.cfg.dim / 2;
742        let mut e = vec![0f32; self.cfg.dim];
743        for j in 0..d {
744            let a = t * self.time_freqs[j];
745            e[j] = a.cos();
746            e[d + j] = a.sin();
747        }
748        let mut h = self.time_in.apply(&e, 1, None);
749        for v in &mut h {
750            *v = silu(*v);
751        }
752        let mut out = self.time_out.apply(&h, 1, None);
753        for (o, &p) in out.iter_mut().zip(pooled_emb) {
754            *o += p;
755        }
756        out
757    }
758
759    pub fn pooled_embed(&self, pooled: &[f32]) -> Vec<f32> {
760        let y = self.pooled_in.apply(pooled, 1, None);
761        self.pooled_norm.apply(&y, 1, None)
762    }
763
764    pub fn embed_text(&self, feats: &[f32]) -> Vec<f32> {
765        let n = feats.len() / self.cfg.in_text;
766        let y = self.text_in.apply(feats, n, self.pool());
767        self.text_norm.apply(&y, n, self.pool())
768    }
769
770    pub fn embed_audio(&self, feats: &[f32]) -> Vec<f32> {
771        let n = feats.len() / self.cfg.in_audio;
772        let y = self.audio_in.apply(feats, n, self.pool());
773        self.audio_norm.apply(&y, n, self.pool())
774    }
775
776    /// One text block at the modulation row `m` (`[6h]`).
777    fn text_block(&self, b: &TextBlock, x: &mut [f32], m: &[f32], rope: (&[f32], &[f32])) {
778        let h = self.cfg.dim;
779        let n = x.len() / h;
780        let pool = self.pool();
781        let xn = modulated_norm(x, n, h, &[&m[..3 * h]], &|_| 0, pool);
782        let o = b.attn.forward(&xn, n, &xn, n, Some(rope), None, pool);
783        gated_add(x, &o, n, h, &[&m[2 * h..3 * h]], &|_| 0, pool);
784        let xn = modulated_norm(x, n, h, &[&m[3 * h..6 * h]], &|_| 0, pool);
785        let o = b.ffn.forward(&xn, n, pool);
786        gated_add(x, &o, n, h, &[&m[5 * h..6 * h]], &|_| 0, pool);
787    }
788
789    /// `visual_embeddings_*` of every part, the mask embeddings added.
790    pub fn embed_visual(&self, i: &S2vInput<'_>) -> Vec<f32> {
791        let (c, g) = (self.cfg.in_visual, &i.geom);
792        let h = self.cfg.dim;
793        let pool = self.pool();
794        let part = |lin: &Lin, data: &[f32], pt: usize, ps: usize, mask: usize| -> Vec<f32> {
795            let (hh, ww) = (g.lh - g.lh % ps, g.lw - g.lw % ps);
796            let tok = patchify(data, c, pt, ps, hh, ww);
797            let n = tok.len() / lin.w.cols();
798            let mut y = lin.apply(&tok, n, pool);
799            for row in y.chunks_exact_mut(h) {
800                for (v, &m) in row.iter_mut().zip(&self.mask[mask * h..(mask + 1) * h]) {
801                    *v += m;
802                }
803            }
804            y
805        };
806        let mut out = Vec::with_capacity(g.total() * h);
807        out.extend(part(&self.emb_p4, i.mem4, 4, 8, 2));
808        out.extend(part(&self.emb_p2, i.mem2, 2, 4, 2));
809        out.extend(part(&self.emb_p1, i.mem1, 1, 2, 2));
810        out.extend(part(&self.emb1, i.reference, 1, 2, 1));
811        out.extend(part(&self.emb1, i.x, 1, 2, 0));
812        out
813    }
814
815    /// The clean rows of the visual stream, `[memory | ref]` embedded.
816    pub fn embed_clean(&self, reference: &[f32], memory: &[Vec<f32>; 3], g: &S2vGeom) -> Vec<f32> {
817        let (c, h) = (self.cfg.in_visual, self.cfg.dim);
818        let pool = self.pool();
819        let part = |lin: &Lin, data: &[f32], pt: usize, ps: usize, mask: usize| -> Vec<f32> {
820            let (hh, ww) = (g.lh - g.lh % ps, g.lw - g.lw % ps);
821            let tok = patchify(data, c, pt, ps, hh, ww);
822            let n = tok.len() / lin.w.cols();
823            let mut y = lin.apply(&tok, n, pool);
824            for row in y.chunks_exact_mut(h) {
825                for (v, &m) in row.iter_mut().zip(&self.mask[mask * h..(mask + 1) * h]) {
826                    *v += m;
827                }
828            }
829            y
830        };
831        let mut out = Vec::with_capacity(g.clean() * h);
832        out.extend(part(&self.emb_p4, &memory[2], 4, 8, 2));
833        out.extend(part(&self.emb_p2, &memory[1], 2, 4, 2));
834        out.extend(part(&self.emb_p1, &memory[0], 1, 2, 2));
835        out.extend(part(&self.emb1, reference, 1, 2, 1));
836        out
837    }
838
839    /// One whole forward on the host. Returns `[T, H, W, out_visual]`.
840    /// `tap` receives each block's output under the oracle's name.
841    pub fn forward(
842        &self,
843        i: &S2vInput<'_>,
844        mut tap: Option<&mut dyn FnMut(&str, &[f32])>,
845    ) -> Result<Vec<f32>, String> {
846        let (h, g) = (self.cfg.dim, &i.geom);
847        let pool = self.pool();
848        let pe = self.pooled_embed(i.pooled);
849        let te = self.time_embed(i.t, &pe);
850        let te0 = self.time_embed(0.0, &pe);
851        let silu_all = |v: &[f32]| -> Vec<f32> { v.iter().map(|&x| silu(x)).collect() };
852        let (s, s0) = (silu_all(&te), silu_all(&te0));
853
854        let mut text = self.embed_text(i.text_feats);
855        let (tc, ts) = rope_1d(&(0..(text.len() / h) as i32).collect::<Vec<_>>());
856        for (bi, b) in self.text.iter().enumerate() {
857            let name = format!("dit.text_transformer_blocks.{bi}.text_modulation.out_layer");
858            let m = self.modulate(&name, &s, 1)?;
859            self.text_block(b, &mut text, &m, (&tc, &ts));
860            if let Some(t) = tap.as_mut() {
861                t(&format!("text_transformer_blocks.{bi}"), &text);
862            }
863        }
864        let mut audio = self.embed_audio(i.audio_feats);
865        {
866            let n = audio.len() / h;
867            let (ac, as_) = rope_1d(&audio_rope_pos(n, g.lt));
868            for (bi, b) in self.audio.iter().enumerate() {
869                let xn = b.sa_norm.apply(&audio, n, pool);
870                let o = b.attn.forward(&xn, n, &xn, n, Some((&ac, &as_)), None, pool);
871                add_assign(&mut audio, &o);
872                let xn = b.ff_norm.apply(&audio, n, pool);
873                let o = b.ffn.forward(&xn, n, pool);
874                add_assign(&mut audio, &o);
875                if let Some(t) = tap.as_mut() {
876                    t(&format!("audio_transformer_blocks.{bi}"), &audio);
877                }
878            }
879        }
880        let mut x = self.embed_visual(i);
881        let (vc, vs) = rope_visual(&self.cfg, g, i.scale_factor);
882        let n = g.total();
883        let clean = g.clean();
884        let sel = move |r: usize| usize::from(r >= clean);
885        let na = audio.len() / h;
886        let nt = text.len() / h;
887        let groups: Vec<(usize, usize, usize, usize)> = audio_groups(na, g.lt)
888            .into_iter()
889            .enumerate()
890            .map(|(f, (k0, kn))| (f * g.per_frame(), g.per_frame(), k0, kn))
891            .collect();
892        let mut st = [s0.clone(), s.clone()].concat();
893        for (bi, b) in self.visual.iter().enumerate() {
894            let name = format!("dit.visual_transformer_blocks.{bi}.visual_modulation.out_layer");
895            let m = self.modulate(&name, &st, 2)?;
896            let row = |r: usize, a: usize, e: usize| &m[r * 9 * h + a * h..r * 9 * h + e * h];
897            // self-attention
898            let xn = modulated_norm(&x, n, h, &[row(0, 0, 3), row(1, 0, 3)], &sel, pool);
899            let o = b.sa.forward(&xn, n, &xn, n, Some((&vc, &vs)), None, pool);
900            gated_add(&mut x, &o, n, h, &[row(0, 2, 3), row(1, 2, 3)], &sel, pool);
901            // prompt cross-attention
902            let xn = modulated_norm(&x, n, h, &[row(0, 3, 6), row(1, 3, 6)], &sel, pool);
903            let o = b.ca.forward(&xn, n, &text, nt, None, None, pool);
904            gated_add(&mut x, &o, n, h, &[row(0, 5, 6), row(1, 5, 6)], &sel, pool);
905            // speech cross-attention
906            if let Some((norm, att)) = &b.v2a {
907                let tail = &x[clean * h..];
908                let xn = norm.apply(tail, g.nvis, pool);
909                let o = att.forward(&xn, g.nvis, &audio, na, None, Some(&groups), pool);
910                add_assign(&mut x[clean * h..], &o);
911            }
912            // feed-forward
913            let xn = modulated_norm(&x, n, h, &[row(0, 6, 9), row(1, 6, 9)], &sel, pool);
914            let o = b.ffn.forward(&xn, n, pool);
915            gated_add(&mut x, &o, n, h, &[row(0, 8, 9), row(1, 8, 9)], &sel, pool);
916            if let Some(t) = tap.as_mut() {
917                t(&format!("visual_transformer_blocks.{bi}"), &x);
918            }
919        }
920        st.clear();
921        // Final layer: noisy rows, the t row only.
922        let m = self.modulate("dit.out_layer.modulation.out_layer", &s, 1)?;
923        let tail = &x[clean * h..];
924        let sc: &[&[f32]] = &[&m[..2 * h]];
925        let xn = modulated_norm(tail, g.nvis, h, sc, &|_| 0, pool);
926        let y = self.out_w.apply(&xn, g.nvis, pool);
927        Ok(unpatchify_out(&y, g, self.cfg.out_visual))
928    }
929}
930
931fn add_assign(x: &mut [f32], y: &[f32]) {
932    for (a, &b) in x.iter_mut().zip(y) {
933        *a += b;
934    }
935}
936
937/// `LayerNorm(x)·(1 + scale) + shift` where each `chunks[k]` is `[shift, scale, …]`.
938fn modulated_norm(
939    x: &[f32],
940    n: usize,
941    h: usize,
942    chunks: &[&[f32]],
943    sel: &(dyn Fn(usize) -> usize + Sync),
944    pool: Option<&Pool>,
945) -> Vec<f32> {
946    let mut y = vec![0f32; n * h];
947    let sr = Rows(y.as_mut_ptr());
948    pool_rows(pool, n, &|lo, hi| {
949        for r in lo..hi {
950            // SAFETY: disjoint rows.
951            let row = unsafe { sr.at(r * h, h) };
952            layer_norm_row(&x[r * h..(r + 1) * h], NORM_EPS, row);
953            let c = chunks[sel(r)];
954            let (shift, scale) = (&c[..h], &c[h..2 * h]);
955            for ((v, &sc), &sh) in row.iter_mut().zip(scale).zip(shift) {
956                *v = *v * (sc + 1.0) + sh;
957            }
958        }
959    });
960    y
961}
962
963fn gated_add(
964    x: &mut [f32],
965    o: &[f32],
966    n: usize,
967    h: usize,
968    gates: &[&[f32]],
969    sel: &(dyn Fn(usize) -> usize + Sync),
970    pool: Option<&Pool>,
971) {
972    let sr = Rows(x.as_mut_ptr());
973    pool_rows(pool, n, &|lo, hi| {
974        for r in lo..hi {
975            // SAFETY: disjoint rows.
976            let row = unsafe { sr.at(r * h, h) };
977            let g = gates[sel(r)];
978            for ((v, &gg), &b) in row.iter_mut().zip(g).zip(&o[r * h..(r + 1) * h]) {
979                *v += gg * b;
980            }
981        }
982    });
983}