1use std::sync::atomic::{AtomicU64, Ordering};
21
22pub 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
48pub 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
92const TILE: usize = 256;
94const TILE_OVERLAP_MIN: usize = 64;
95const 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, qkv_b: Vec<f32>,
107 out: Proj,
108 out_b: Vec<f32>,
109 w1: Proj, w1_b: Vec<f32>,
111 w2: Proj, 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>, 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 rope_axes: usize,
141 eps: f64,
142}
143
144fn layer_norm(x: &[f32], w: &[f32], b: &[f32], eps: f64, dst: &mut [f32]) {
145 let n = x.len() as f64;
146 let mean = x.iter().map(|&v| v as f64).sum::<f64>() / n;
147 let var = x.iter().map(|&v| (v as f64 - mean).powi(2)).sum::<f64>() / n;
148 let inv = 1.0 / (var + eps).sqrt();
149 for (((d, &v), &g), &bb) in dst.iter_mut().zip(x).zip(w).zip(b) {
150 *d = ((v as f64 - mean) * inv) as f32 * g + bb;
151 }
152}
153
154fn rms_norm_into(x: &[f32], w: &[f32], eps: f64, dst: &mut [f32]) {
156 let ss = x.iter().map(|&v| (v as f64) * (v as f64)).sum::<f64>() / x.len() as f64;
157 let inv = 1.0 / (ss + eps).sqrt();
158 for ((d, &v), &g) in dst.iter_mut().zip(x).zip(w) {
159 *d = (v as f64 * inv) as f32 * g;
160 }
161}
162
163thread_local! {
165 static CHECKING: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
168}
169
170fn rms_norm_plain(x: &mut [f32], eps: f64) {
171 let ss = x.iter().map(|&v| (v as f64) * (v as f64)).sum::<f64>() / x.len() as f64;
172 let inv = 1.0 / (ss + eps).sqrt();
173 for v in x.iter_mut() {
174 *v = (*v as f64 * inv) as f32;
175 }
176}
177
178fn silu(v: f32) -> f32 {
179 v / (1.0 + (-v).exp())
180}
181
182fn rows_par(pool: Option<&crate::pool::Pool>, n: usize, f: &(dyn Fn(usize, usize) + Sync)) {
186 match pool {
187 Some(pl) => pl.run_rows(n, f),
188 None => f(0, n),
189 }
190}
191
192struct SendPtr(*mut f32);
193unsafe impl Send for SendPtr {}
194unsafe impl Sync for SendPtr {}
195impl SendPtr {
196 #[allow(clippy::mut_from_ref)]
198 unsafe fn row(&self, off: usize, len: usize) -> &mut [f32] {
199 unsafe { std::slice::from_raw_parts_mut(self.0.add(off), len) }
200 }
201}
202
203fn softmax_inplace(row: &mut [f32]) {
204 let mx = row.iter().cloned().fold(f32::MIN, f32::max);
205 let mut den = 0f32;
206 for r in row.iter_mut() {
207 *r = (*r - mx).exp();
208 den += *r;
209 }
210 if den > 0.0 {
211 let inv = 1.0 / den;
212 for r in row.iter_mut() {
213 *r *= inv;
214 }
215 }
216}
217
218fn split_tiles(input_len: usize, ratio: usize) -> (Vec<usize>, Vec<usize>, Vec<usize>) {
222 if TILE >= input_len {
223 return (vec![0], vec![input_len], Vec::new());
224 }
225 let mut n = input_len.div_ceil(TILE);
226 let (mut overlaps, remaining);
227 loop {
228 overlaps = vec![TILE_OVERLAP_MIN; n - 1];
229 let total: usize = overlaps.iter().sum();
230 if TILE * n < total + input_len {
231 n += 1;
232 continue;
233 }
234 remaining = TILE * n - total - input_len;
235 break;
236 }
237 for i in 0..remaining / ratio {
238 overlaps[i % (n - 1)] += ratio;
239 }
240 let mut starts = vec![0usize];
241 for i in 0..n - 1 {
242 starts.push(starts[i] + TILE - overlaps[i]);
243 }
244 (starts, vec![TILE; n], overlaps)
245}
246
247impl VideoVae {
248 pub fn from_cmf(model: &Arc<CmfModel>) -> Result<Self, String> {
249 let cfg: serde_json::Value = serde_json::from_slice(
250 model
251 .tensor_bytes("vvae.config_json")
252 .map_err(|e| e.to_string())?,
253 )
254 .map_err(|e| format!("vvae.config_json: {e}"))?;
255 let u = |k: &str, d: usize| cfg[k].as_u64().map(|v| v as usize).unwrap_or(d);
256 let f32v = |n: &str| crate::dit::cmf_f32(model, n);
257 let n = u("num_layers", 36);
258 let mut blocks = Vec::with_capacity(n);
259 for l in 0..n {
260 let p = format!("vvae.blocks.{l}");
261 blocks.push(Block {
262 norm1: f32v(&format!("{p}.norm1"))?,
263 norm2: f32v(&format!("{p}.norm2"))?,
264 scale1: f32v(&format!("{p}.scale1"))?,
265 scale2: f32v(&format!("{p}.scale2"))?,
266 qkv: Proj::from_model(model, &format!("{p}.attn.to_qkv.weight"))?,
267 qkv_b: f32v(&format!("{p}.attn.to_qkv.bias"))?,
268 out: Proj::from_model(model, &format!("{p}.attn.to_out.weight"))?,
269 out_b: f32v(&format!("{p}.attn.to_out.bias"))?,
270 w1: Proj::from_model(model, &format!("{p}.ff.w1.weight"))?,
271 w1_b: f32v(&format!("{p}.ff.w1.bias"))?,
272 w2: Proj::from_model(model, &format!("{p}.ff.w2.weight"))?,
273 w2_b: f32v(&format!("{p}.ff.w2.bias"))?,
274 });
275 }
276 let dim = u("dim", 2048);
277 let heads = u("heads", 32);
278 Ok(Self {
279 post_quant: Proj::from_model(model, "vvae.post_quant_conv.weight")?,
280 post_quant_b: f32v("vvae.post_quant_conv.bias")?,
281 x_embed: Proj::from_model(model, "vvae.x_embedder.weight")?,
282 x_embed_b: f32v("vvae.x_embedder.bias")?,
283 registers: f32v("vvae.register_tokens")?,
284 blocks,
285 norm_out_w: f32v("vvae.norm_out.weight")?,
286 norm_out_b: f32v("vvae.norm_out.bias")?,
287 proj_out: Proj::from_model(model, "vvae.proj_out.weight")?,
288 proj_out_b: f32v("vvae.proj_out.bias")?,
289 latents_mean: f32v("vvae.latents_mean")?,
290 latents_std: f32v("vvae.latents_std")?,
291 pool: Pool::from_env(),
292 dim,
293 heads,
294 head_dim: dim / heads,
295 z_channels: u("z_channels", 24),
296 patch: u("patch_size", 16),
297 patch_t: u("patch_size_t", 4),
298 n_reg: u("num_register_tokens", 4),
299 rope_theta: cfg["rope_theta"].as_f64().unwrap_or(100.0) as f32,
300 rope_dim: ((dim / heads) as f64 * cfg["rope_dim_ratio"].as_f64().unwrap_or(0.75))
301 as usize,
302 rope_axes: u("rope_dims", 3).clamp(2, 3),
303 eps: cfg["eps"].as_f64().unwrap_or(1e-5),
304 })
305 }
306
307 fn axis(n: usize) -> Vec<f32> {
311 (0..n)
312 .map(|i| 2.0 * ((i as f32 + 0.5) / n as f32) - 1.0)
313 .collect()
314 }
315
316 fn rope_angles(&self, t: usize, h: usize, w: usize, suffix: usize) -> Vec<f32> {
321 let ax = self.rope_axes;
322 let k = self.rope_dim / (2 * ax); let inv: Vec<f32> = (0..k)
324 .map(|i| {
325 1.0 / self
326 .rope_theta
327 .powf(i as f32 * 2.0 * ax as f32 / self.rope_dim as f32)
328 })
329 .collect();
330 let (ta, ha, wa) = (Self::axis(t), Self::axis(h), Self::axis(w));
331 let tau = 2.0 * std::f32::consts::PI;
332 let mut out = Vec::with_capacity((t * h * w + suffix) * ax * k);
333 for ti in 0..t {
334 for hi in 0..h {
335 for wi in 0..w {
336 let coords = [ta[ti], ha[hi], wa[wi]];
337 for &c in &coords[3 - ax..] {
338 for &f in &inv {
339 out.push(tau * c * f);
340 }
341 }
342 }
343 }
344 }
345 out.extend(std::iter::repeat_n(0.0, suffix * ax * k));
346 out
347 }
348
349 fn attention(&self, qkv: &[f32], n: usize, attn: &mut [f32], angles: &[f32]) {
350 let (nh, hd, dim) = (self.heads, self.head_dim, self.dim);
351 let pairs = angles.len() / n; let scale = 1.0 / (hd as f32).sqrt();
353 let pool = self.pool.as_deref();
354 if std::env::var("CMF_VAE3D_ATTN").as_deref() != Ok("cpu")
360 && crate::gpu::enabled_here()
361 && n >= 256
362 {
363 if std::env::var("CMF_VAE3D_CHECK").as_deref() == Ok("1") && !CHECKING.with(|c| c.get())
407 {
408 CHECKING.with(|c| c.set(true));
409 const SENT: f32 = -12345.0;
410 let mut d1 = vec![SENT; attn.len()];
411 let mut d0 = vec![SENT; attn.len()];
412 let ok1 = crate::gpu::vae_attention_packed_layout(
413 qkv,
414 nh,
415 n,
416 hd,
417 scale,
418 angles,
419 self.eps as f32,
420 &mut d1,
421 1,
422 );
423 let ok0 = crate::gpu::vae_attention_packed_layout(
424 qkv,
425 nh,
426 n,
427 hd,
428 scale,
429 angles,
430 self.eps as f32,
431 &mut d0,
432 0,
433 );
434 let wrote = |v: &[f32]| v.iter().filter(|&&x| x != SENT).count();
435 let mut host = vec![0f32; attn.len()];
436 let saved = std::env::var("CMF_VAE3D_ATTN").ok();
437 unsafe { std::env::set_var("CMF_VAE3D_ATTN", "cpu") };
439 self.attention(qkv, n, &mut host, angles);
440 match &saved {
441 Some(v) => unsafe { std::env::set_var("CMF_VAE3D_ATTN", v) },
442 None => unsafe { std::env::remove_var("CMF_VAE3D_ATTN") },
443 }
444 let mut sq = vec![SENT; nh * n * hd];
447 let oks = crate::gpu::dit_split_only(qkv, nh, n, hd, 1, None, &mut sq);
448 let mut sqn = vec![SENT; nh * n * hd];
449 let okn = crate::gpu::dit_split_only(
450 qkv,
451 nh,
452 n,
453 hd,
454 1,
455 Some((angles, self.eps as f32)),
456 &mut sqn,
457 );
458 let mut hq = vec![0f32; nh * n * hd];
459 for p in 0..n {
460 for h in 0..nh {
461 let b = p * 3 * dim + h * 3 * hd;
462 hq[(h * n + p) * hd..(h * n + p) * hd + hd]
463 .copy_from_slice(&qkv[b..b + hd]);
464 }
465 }
466 let sq_wrote = sq.iter().filter(|&&x| x != SENT).count();
467 let sq_diff = sq
468 .iter()
469 .zip(&hq)
470 .fold(0f32, |m, (x, y)| m.max((x - y).abs()));
471 let mut hqn = hq.clone();
474 let pairs = angles.len() / n;
475 for p in 0..n {
476 for h in 0..nh {
477 let r = &mut hqn[(h * n + p) * hd..(h * n + p) * hd + hd];
478 rms_norm_plain(r, self.eps);
479 for j in 0..pairs {
480 let (sn, cs) = angles[p * pairs + j].sin_cos();
481 let (a, b) = (r[j], r[j + pairs]);
482 r[j] = a * cs - b * sn;
483 r[j + pairs] = a * sn + b * cs;
484 }
485 }
486 }
487 let n_wrote = sqn.iter().filter(|&&x| x != SENT).count();
488 let n_diff = sqn
489 .iter()
490 .zip(&hqn)
491 .fold(0f32, |m, (x, y)| m.max((x - y).abs()));
492 eprintln!(
493 "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}",
494 sq.len(),
495 hq.iter().fold(0f32, |m, v| m.max(v.abs())),
496 hqn.iter().fold(0f32, |m, v| m.max(v.abs())),
497 );
498 let md = |a: &[f32], b: &[f32]| {
499 a.iter().zip(b).fold(0f32, |m, (x, y)| m.max((x - y).abs()))
500 };
501 static ONCE: std::sync::Once = std::sync::Once::new();
502 ONCE.call_once(|| {
503 eprintln!(
504 "vae attn check: ok={ok1}/{ok0} wrote={}/{} of {} | host|max| {:.4e} | l1-host {:.4e} | l0-host {:.4e} | l1-l0 {:.4e}",
505 wrote(&d1), wrote(&d0), attn.len(),
506 host.iter().fold(0f32, |m, v| m.max(v.abs())),
507 md(&d1, &host), md(&d0, &host), md(&d1, &d0),
508 )
509 });
510 CHECKING.with(|c| c.set(false));
511 }
512 if std::env::var("CMF_VAE3D_SPLIT").as_deref() == Ok("1")
513 && crate::gpu::vae_attention_packed(
514 qkv,
515 nh,
516 n,
517 hd,
518 scale,
519 angles,
520 self.eps as f32,
521 attn,
522 )
523 {
524 return;
525 }
526 let t_rp = std::time::Instant::now();
527 let mut qa = vec![0f32; nh * n * hd];
528 let mut ka = vec![0f32; nh * n * hd];
529 let mut va = vec![0f32; nh * n * hd];
530 {
531 let (pq, pk, pv) = (
532 SendPtr(qa.as_mut_ptr()),
533 SendPtr(ka.as_mut_ptr()),
534 SendPtr(va.as_mut_ptr()),
535 );
536 let fill = |lo: usize, hi: usize| {
537 for p in lo..hi {
538 for h in 0..nh {
539 let base = p * 3 * dim + h * 3 * hd;
540 let dst = (h * n + p) * hd;
541 let (q, k, v) =
544 unsafe { (pq.row(dst, hd), pk.row(dst, hd), pv.row(dst, hd)) };
545 q.copy_from_slice(&qkv[base..base + hd]);
546 k.copy_from_slice(&qkv[base + hd..base + 2 * hd]);
547 v.copy_from_slice(&qkv[base + 2 * hd..base + 3 * hd]);
548 for x in [&mut *q, &mut *k] {
551 rms_norm_plain(x, self.eps);
552 for j in 0..pairs {
553 let (sn, cs) = angles[p * pairs + j].sin_cos();
554 let (a, b) = (x[j], x[j + pairs]);
555 x[j] = a * cs - b * sn;
556 x[j + pairs] = a * sn + b * cs;
557 }
558 }
559 }
560 }
561 };
562 match pool {
563 Some(pl) => pl.run_rows(n, &fill),
564 None => fill(0, n),
565 }
566 }
567 vprof(2, t_rp);
568 let t_at = std::time::Instant::now();
569 if crate::gpu::dit_attention(&qa, &ka, &va, nh, nh, n, hd, scale, attn) {
570 vprof(3, t_at);
571 return;
572 }
573 }
574 let mut qh = vec![0f32; n * hd];
575 let mut kh = vec![0f32; n * hd];
576 let mut vt = vec![0f32; hd * n];
577 let mut scores = vec![0f32; n * n];
578 let mut oh = vec![0f32; n * hd];
579 for h in 0..nh {
580 for p in 0..n {
581 let base = p * 3 * dim + h * 3 * hd;
584 qh[p * hd..(p + 1) * hd].copy_from_slice(&qkv[base..base + hd]);
585 kh[p * hd..(p + 1) * hd].copy_from_slice(&qkv[base + hd..base + 2 * hd]);
586 for d in 0..hd {
587 vt[d * n + p] = qkv[base + 2 * hd + d];
588 }
589 }
590 for (buf, _) in [(&mut qh, 0), (&mut kh, 1)] {
591 for p in 0..n {
592 let x = &mut buf[p * hd..(p + 1) * hd];
593 rms_norm_plain(x, self.eps);
594 for j in 0..pairs {
595 let (s, c) = angles[p * pairs + j].sin_cos();
596 let (a, b) = (x[j], x[j + pairs]);
597 x[j] = a * c - b * s;
598 x[j + pairs] = a * s + b * c;
599 }
600 }
601 }
602 for v in qh.iter_mut() {
603 *v *= scale;
604 }
605 crate::fcd_ops::gemm_nt(&qh, &kh, &mut scores, n, hd, n, pool);
606 let sp = SendPtr(scores.as_mut_ptr());
607 let soft = |lo: usize, hi: usize| {
608 for r in lo..hi {
609 softmax_inplace(unsafe { sp.row(r * n, n) });
611 }
612 };
613 match pool {
614 Some(pl) => pl.run_rows(n, &soft),
615 None => soft(0, n),
616 }
617 crate::fcd_ops::gemm_nt(&scores, &vt, &mut oh, n, n, hd, pool);
618 for p in 0..n {
619 attn[p * dim + h * hd..p * dim + (h + 1) * hd]
620 .copy_from_slice(&oh[p * hd..(p + 1) * hd]);
621 }
622 }
623 }
624
625 fn embed_tile(&self, z: &[f32], t: usize, h: usize, w: usize) -> Vec<f32> {
629 let pool = self.pool.as_deref();
630 let dim = self.dim;
631 let np = t * h * w;
632 let suffix = 1 + self.n_reg;
633 let n = np + suffix;
634
635 let zc = self.z_channels;
638 let mut rows = vec![0f32; np * zc];
639 for c in 0..zc {
640 for i in 0..np {
641 rows[i * zc + c] = z[c * np + i];
642 }
643 }
644 let mut pq = vec![0f32; np * zc];
645 self.post_quant.matmat(&rows, np, &mut pq, pool);
646 for r in pq.chunks_exact_mut(zc) {
647 for (v, &b) in r.iter_mut().zip(&self.post_quant_b) {
648 *v += b;
649 }
650 }
651 let mut x = vec![0f32; n * dim];
652 self.x_embed.matmat(&pq, np, &mut x[..np * dim], pool);
653 for r in x[..np * dim].chunks_exact_mut(dim) {
654 for (v, &b) in r.iter_mut().zip(&self.x_embed_b) {
655 *v += b;
656 }
657 }
658 x[np * dim..(np + self.n_reg) * dim].copy_from_slice(&self.registers);
660 x
661 }
662
663 fn decode_tile(&self, z: &[f32], t: usize, h: usize, w: usize) -> Vec<f32> {
666 let pool = self.pool.as_deref();
667 let dim = self.dim;
668 let np = t * h * w;
669 let suffix = 1 + self.n_reg;
670 let n = np + suffix;
671 let mut x = self.embed_tile(z, t, h, w);
672
673 let angles = self.rope_angles(t, h, w, suffix);
674 let mut xn = vec![0f32; n * dim];
675 let mut qkv = vec![0f32; n * 3 * dim];
676 let mut attn = vec![0f32; n * dim];
677 let mut proj = vec![0f32; n * dim];
678 let inner = 4 * dim;
679 for blk in &self.blocks {
680 let t = std::time::Instant::now();
681 {
682 let px = SendPtr(xn.as_mut_ptr());
683 rows_par(pool, n, &|lo, hi| {
684 for p in lo..hi {
685 rms_norm_into(&x[p * dim..(p + 1) * dim], &blk.norm1, self.eps, unsafe {
687 px.row(p * dim, dim)
688 });
689 }
690 });
691 }
692 vprof(0, t);
693 let t = std::time::Instant::now();
694 if std::env::var("CMF_GPU_DEBUG").is_ok() {
700 static ONCE: std::sync::Once = std::sync::Once::new();
701 ONCE.call_once(|| {
702 eprintln!(
703 "vae3d shape: n={n} dim={dim} heads={} head_dim={} pairs={}",
704 self.heads,
705 self.head_dim,
706 angles.len() / n.max(1)
707 )
708 });
709 }
710 let mut fused = false;
711 if std::env::var("CMF_VAE3D_FUSE").as_deref() != Ok("0")
755 && crate::gpu::enabled_here()
756 && n >= 256
757 {
758 if let (Proj::Q(q), Proj::Q(o)) = (&blk.qkv, &blk.out) {
759 if let (Some((m, i)), Some((_, oi))) =
760 (q.mapped_device_gemm(), o.mapped_device_gemm())
761 {
762 fused = crate::gpu::vae_qkv_attn_out(
763 m,
764 i,
765 oi,
766 &xn,
767 n,
768 dim,
769 self.heads,
770 self.head_dim,
771 1.0 / (self.head_dim as f32).sqrt(),
772 &angles,
773 self.eps as f32,
774 &blk.qkv_b,
775 &mut proj,
776 );
777 }
778 }
779 }
780 if fused {
781 vprof(1, t);
782 }
783 if !fused {
784 blk.qkv.matmat(&xn, n, &mut qkv, pool);
785 {
786 let pq = SendPtr(qkv.as_mut_ptr());
787 rows_par(pool, n, &|lo, hi| {
788 for p in lo..hi {
789 for (v, &b) in unsafe { pq.row(p * 3 * dim, 3 * dim) }
791 .iter_mut()
792 .zip(&blk.qkv_b)
793 {
794 *v += b;
795 }
796 }
797 });
798 }
799 vprof(1, t);
800 self.attention(&qkv, n, &mut attn, &angles);
801 let t = std::time::Instant::now();
802 blk.out.matmat(&attn, n, &mut proj, pool);
803 vprof(4, t);
804 }
805 {
806 let pxx = SendPtr(x.as_mut_ptr());
807 rows_par(pool, n, &|lo, hi| {
808 for p in lo..hi {
809 let r = &proj[p * dim..(p + 1) * dim];
810 let dst = unsafe { pxx.row(p * dim, dim) };
812 for (i, v) in dst.iter_mut().enumerate() {
813 *v += (r[i] + blk.out_b[i]) * blk.scale1[i];
814 }
815 }
816 });
817 }
818 let t = std::time::Instant::now();
819 {
820 let px = SendPtr(xn.as_mut_ptr());
821 rows_par(pool, n, &|lo, hi| {
822 for p in lo..hi {
823 rms_norm_into(&x[p * dim..(p + 1) * dim], &blk.norm2, self.eps, unsafe {
825 px.row(p * dim, dim)
826 });
827 }
828 });
829 }
830 vprof(0, t);
831 let t_ffn = std::time::Instant::now();
832 if std::env::var("CMF_VAE3D_FFN").as_deref() != Ok("cpu")
837 && crate::gpu::enabled_here()
838 && n >= 64
839 {
840 if let (Proj::Q(q1), Proj::Q(q2)) = (&blk.w1, &blk.w2) {
841 if let (Some((m, i1)), Some((_, i2))) =
842 (q1.mapped_device_gemm(), q2.mapped_device_gemm())
843 {
844 let mut fout = vec![0f32; n * dim];
845 if crate::gpu::q4tp_ffn_packed(
846 m,
847 i1,
848 i2,
849 &xn,
850 n,
851 dim,
852 inner,
853 Some(&blk.w1_b),
854 &mut fout,
855 ) {
856 {
857 let pxx = SendPtr(x.as_mut_ptr());
858 rows_par(pool, n, &|lo, hi| {
859 for p in lo..hi {
860 let r = &fout[p * dim..(p + 1) * dim];
861 let dst = unsafe { pxx.row(p * dim, dim) };
863 for (i, v) in dst.iter_mut().enumerate() {
864 *v += (r[i] + blk.w2_b[i]) * blk.scale2[i];
865 }
866 }
867 });
868 }
869 vprof(5, t_ffn);
870 continue;
871 }
872 }
873 }
874 }
875 let mut gu = vec![0f32; n * 2 * inner];
876 blk.w1.matmat(&xn, n, &mut gu, pool);
877 let mut act = vec![0f32; n * inner];
878 for p in 0..n {
879 let r = &gu[p * 2 * inner..(p + 1) * 2 * inner];
880 for i in 0..inner {
881 act[p * inner + i] =
882 silu(r[i] + blk.w1_b[i]) * (r[inner + i] + blk.w1_b[inner + i]);
883 }
884 }
885 blk.w2.matmat(&act, n, &mut proj, pool);
886 {
887 let pxx = SendPtr(x.as_mut_ptr());
888 rows_par(pool, n, &|lo, hi| {
889 for p in lo..hi {
890 let r = &proj[p * dim..(p + 1) * dim];
891 let dst = unsafe { pxx.row(p * dim, dim) };
893 for (i, v) in dst.iter_mut().enumerate() {
894 *v += (r[i] + blk.w2_b[i]) * blk.scale2[i];
895 }
896 }
897 });
898 }
899 vprof(5, t_ffn);
900 }
901
902 self.head_tile(&x, t, h, w)
903 }
904
905 fn head_tile(&self, x: &[f32], t: usize, h: usize, w: usize) -> Vec<f32> {
909 let pool = self.pool.as_deref();
910 let dim = self.dim;
911 let np = t * h * w;
912 let (pt, ps) = (self.patch_t, self.patch);
913 let od = 3 * pt * ps * ps;
914 let t_head = std::time::Instant::now();
915 let mut head = vec![0f32; np * dim];
916 {
917 let ph = SendPtr(head.as_mut_ptr());
918 rows_par(pool, np, &|lo, hi| {
919 for p in lo..hi {
920 layer_norm(
922 &x[p * dim..(p + 1) * dim],
923 &self.norm_out_w,
924 &self.norm_out_b,
925 self.eps,
926 unsafe { ph.row(p * dim, dim) },
927 );
928 }
929 });
930 }
931 let mut out = vec![0f32; np * od];
932 self.proj_out.matmat(&head, np, &mut out, pool);
933 for r in out.chunks_exact_mut(od) {
934 for (v, &b) in r.iter_mut().zip(&self.proj_out_b) {
935 *v += b;
936 }
937 }
938
939 vprof(6, t_head);
940 let t_px = std::time::Instant::now();
941 let (ot, oh, ow) = (t * pt, h * ps, w * ps);
943 let mut px = vec![0f32; 3 * ot * oh * ow];
944 for ti in 0..t {
945 for hi in 0..h {
946 for wi in 0..w {
947 let src =
948 &out[((ti * h + hi) * w + wi) * od..((ti * h + hi) * w + wi + 1) * od];
949 for c in 0..3 {
950 for a in 0..pt {
951 for b in 0..ps {
952 for d in 0..ps {
953 px[((c * ot + ti * pt + a) * oh + hi * ps + b) * ow
954 + wi * ps
955 + d] = src[((c * pt + a) * ps + b) * ps + d];
956 }
957 }
958 }
959 }
960 }
961 }
962 }
963 vprof(7, t_px);
964 px
965 }
966
967 fn decode_tiles(&self, subs: &[Vec<f32>], t: usize, h: usize, w: usize) -> Vec<Vec<f32>> {
973 if let Some(out) = self.decode_tiles_chain(subs, t, h, w) {
974 return out;
975 }
976 subs.iter().map(|z| self.decode_tile(z, t, h, w)).collect()
977 }
978
979 fn decode_tiles_chain(
980 &self,
981 subs: &[Vec<f32>],
982 t: usize,
983 h: usize,
984 w: usize,
985 ) -> Option<Vec<Vec<f32>>> {
986 if std::env::var("CMF_VAE3D_CHAIN").as_deref() == Ok("0")
987 || !crate::gpu::enabled_here()
988 || !crate::gpu::h3_chain_available()
989 {
990 return None;
991 }
992 let dim = self.dim;
993 let n = t * h * w + 1 + self.n_reg;
994 let mapped = |p: &Proj| match p {
995 Proj::Q(q) => q.mapped_device_gemm().map(|(m, i)| (m.clone(), i)),
996 Proj::F32 { .. } => None,
997 };
998 let model = mapped(&self.blocks.first()?.qkv)?.0;
999 let chain: Vec<crate::gpu::H3ChainBlock> = self
1000 .blocks
1001 .iter()
1002 .map(|b| {
1003 Some(crate::gpu::H3ChainBlock {
1004 qkv: mapped(&b.qkv)?.1,
1005 out: mapped(&b.out)?.1,
1006 fc1: mapped(&b.w1)?.1,
1007 fc2: mapped(&b.w2)?.1,
1008 norm1: &b.norm1,
1009 norm2: &b.norm2,
1010 q_norm: &[],
1011 k_norm: &[],
1012 mods: &[],
1013 qkv_bias: &b.qkv_b,
1014 out_bias: &b.out_b,
1015 fc1_bias: &b.w1_b,
1016 fc2_bias: &b.w2_b,
1017 scale1: &b.scale1,
1018 scale2: &b.scale2,
1019 })
1020 })
1021 .collect::<Option<Vec<_>>>()?;
1022 let angles = self.rope_angles(t, h, w, 1 + self.n_reg);
1023 let g_max = ((256usize << 20) / (self.heads * n * n * 6)).clamp(1, 64);
1026 let mut out = Vec::with_capacity(subs.len());
1027 for batch in subs.chunks(g_max) {
1028 let t_blk = std::time::Instant::now();
1029 let mut x = Vec::with_capacity(batch.len() * n * dim);
1030 for z in batch {
1031 x.extend_from_slice(&self.embed_tile(z, t, h, w));
1032 }
1033 let job = crate::gpu::H3Chain {
1034 model: &model,
1035 blocks: &chain,
1036 n,
1037 hidden: dim,
1038 heads: self.heads,
1039 head_dim: self.head_dim,
1040 ffn: 4 * dim,
1041 eps: self.eps as f32,
1042 qk_eps: self.eps as f32,
1043 angles: &angles,
1044 rows: &[],
1045 groups: batch.len(),
1046 qkv_interleaved: true,
1047 };
1048 if !crate::gpu::h3_chain(&job, &mut x) {
1049 return None;
1050 }
1051 vprof(3, t_blk);
1052 for k in 0..batch.len() {
1053 out.push(self.head_tile(&x[k * n * dim..(k + 1) * n * dim], t, h, w));
1054 }
1055 }
1056 Some(out)
1057 }
1058
1059 fn decode_clip(&self, z: &[f32], t: usize, zh: usize, zw: usize) -> Vec<f32> {
1062 let r = self.patch;
1063 let (height, width) = (zh * r, zw * r);
1064 let (ys, yl, yo) = split_tiles(height, r);
1065 let (xs, xl, xo) = split_tiles(width, r);
1066 let frames = t * self.patch_t;
1067 let mut canvas = vec![0f32; 3 * frames * height * width];
1068
1069 let mut subs: Vec<Vec<f32>> = Vec::with_capacity(ys.len() * xs.len());
1072 for (&ip, &il) in ys.iter().zip(&yl) {
1073 let (zi, zl) = (ip / r, il / r);
1074 for (&jp, &jl) in xs.iter().zip(&xl) {
1075 let (zj, zw_t) = (jp / r, jl / r);
1076 let mut sub = vec![0f32; self.z_channels * t * zl * zw_t];
1077 for c in 0..self.z_channels {
1078 for ti in 0..t {
1079 for hh in 0..zl {
1080 let s = ((c * t + ti) * zh + zi + hh) * zw + zj;
1081 let d = ((c * t + ti) * zl + hh) * zw_t;
1082 sub[d..d + zw_t].copy_from_slice(&z[s..s + zw_t]);
1083 }
1084 }
1085 }
1086 subs.push(sub);
1087 }
1088 }
1089 let mut tiles = self.decode_tiles(&subs, t, yl[0] / r, xl[0] / r);
1090 drop(subs);
1091
1092 let mut row_tails: Vec<Vec<f32>> = Vec::new();
1095 let mut out_y = 0usize;
1096 for (i, &il) in yl.iter().enumerate() {
1097 let zl = il / r;
1098 let mut new_tails: Vec<Vec<f32>> = Vec::new();
1099 let mut left_tail: Option<Vec<f32>> = None;
1100 let mut out_x = 0usize;
1101 let mut row_h = 0usize;
1102 for (j, &jl) in xl.iter().enumerate() {
1103 let zw_t = jl / r;
1104 let mut tile = std::mem::take(&mut tiles[i * xs.len() + j]);
1105 let (mut th, mut tw) = (zl * r, zw_t * r);
1106 if i + 1 < ys.len() {
1107 new_tails.push(crop(&tile, frames, th, tw, th - yo[i], th, 0, tw));
1108 }
1109 let next_left = if j + 1 < xs.len() {
1110 Some(crop(&tile, frames, th, tw, 0, th, tw - xo[j], tw))
1111 } else {
1112 None
1113 };
1114 if i > 0 {
1115 tile = blend(&row_tails[j], &tile, frames, th, tw, yo[i - 1], 2);
1116 }
1117 if j > 0 {
1118 let lt = left_tail.as_ref().unwrap();
1119 tile = blend(lt, &tile, frames, th, tw, xo[j - 1], 3);
1120 }
1121 left_tail = next_left;
1122 if i + 1 < ys.len() {
1123 tile = crop(&tile, frames, th, tw, 0, th - yo[i], 0, tw);
1124 th -= yo[i];
1125 }
1126 if j + 1 < xs.len() {
1127 tile = crop(&tile, frames, th, tw, 0, th, 0, tw - xo[j]);
1128 tw -= xo[j];
1129 }
1130 for c in 0..3 {
1131 for f in 0..frames {
1132 for hh in 0..th {
1133 let dst = ((c * frames + f) * height + out_y + hh) * width + out_x;
1134 let src = ((c * frames + f) * th + hh) * tw;
1135 canvas[dst..dst + tw].copy_from_slice(&tile[src..src + tw]);
1136 }
1137 }
1138 }
1139 out_x += tw;
1140 row_h = th;
1141 }
1142 row_tails = new_tails;
1143 out_y += row_h;
1144 }
1145 canvas
1146 }
1147
1148 pub fn decode(&self, z: &[f32], t_lat: usize, zh: usize, zw: usize) -> (Vec<f32>, usize) {
1151 let zc = self.z_channels;
1152 let np = t_lat * zh * zw;
1153 let mut zz = vec![0f32; zc * np];
1154 for c in 0..zc {
1155 let (m, s) = (self.latents_mean[c], self.latents_std[c]);
1156 for i in 0..np {
1157 zz[c * np + i] = z[c * np + i] * s + m;
1158 }
1159 }
1160
1161 let ratio_t = self.patch_t;
1162 let chunk_tokens = CLIP_LENGTH.div_ceil(ratio_t); let token_overlap = (chunk_tokens - TOKEN_DROP % chunk_tokens) % chunk_tokens; let frame_pre_pad = (ratio_t - CLIP_LENGTH % ratio_t) % ratio_t; let frame_overlap = (token_overlap * ratio_t).saturating_sub(frame_pre_pad); let chunk_dec = chunk_tokens * ratio_t; let mut pseudo = t_lat + TOKEN_DROP;
1172 let mut pad = 0usize;
1173 if pseudo % chunk_tokens != 0 {
1174 pad = chunk_tokens - pseudo % chunk_tokens;
1175 pseudo += pad;
1176 }
1177 let mut chunks = pseudo / chunk_tokens - usize::from(TOKEN_DROP > 0);
1178 if chunks < 1 {
1179 pad += chunk_tokens;
1180 chunks += 1;
1181 }
1182 let t_pad = t_lat + pad;
1183 if pad > 0 {
1184 let mut grown = vec![0f32; zc * t_pad * zh * zw];
1185 for c in 0..zc {
1186 for ti in 0..t_pad {
1187 let src = ti.min(t_lat - 1);
1188 let a = (c * t_lat + src) * zh * zw;
1189 let b = (c * t_pad + ti) * zh * zw;
1190 grown[b..b + zh * zw].copy_from_slice(&zz[a..a + zh * zw]);
1191 }
1192 }
1193 zz = grown;
1194 }
1195
1196 let (h, w) = (zh * self.patch, zw * self.patch);
1197 let mut out: Vec<f32> = Vec::new();
1198 let mut carry: Option<(Vec<f32>, usize)> = None;
1199 for i in 0..chunks {
1200 let a = i * chunk_tokens;
1201 let b = (a + chunk_tokens + token_overlap).min(t_pad);
1202 let n = b.saturating_sub(a.min(t_pad));
1203 if n == 0 {
1204 continue;
1205 }
1206 let mut sub = vec![0f32; zc * n * zh * zw];
1207 for c in 0..zc {
1208 let src = (c * t_pad + a) * zh * zw;
1209 let dst = c * n * zh * zw;
1210 sub[dst..dst + n * zh * zw].copy_from_slice(&zz[src..src + n * zh * zw]);
1211 }
1212 let dec = self.decode_clip(&sub, n, zh, zw);
1213 let dec_frames = n * ratio_t;
1214 for j in 0..2 {
1215 let fa = j * chunk_dec;
1216 let fb = (fa + chunk_dec).min(dec_frames);
1217 if fb <= fa + frame_pre_pad {
1218 continue;
1219 }
1220 let mut part = frames_of(&dec, dec_frames, h, w, fa + frame_pre_pad, fb);
1221 let mut pn = fb - fa - frame_pre_pad;
1222 if j == 0 {
1223 if let Some((tail, tn)) = carry.take() {
1224 part = blend_frames(&tail, tn, &part, pn, h, w, frame_overlap);
1225 pn = part.len() / (3 * h * w);
1226 }
1227 append_frames(&mut out, &part, pn, h, w);
1228 } else {
1229 carry = Some((part, pn));
1230 }
1231 }
1232 if i + 1 == chunks {
1233 if let Some((tail, tn)) = carry.take() {
1234 append_frames(&mut out, &tail, tn, h, w);
1235 }
1236 }
1237 }
1238
1239 let frames = out.len() / (3 * h * w);
1243 let want = t_lat * ratio_t - pad_frames(t_lat, pad, chunk_tokens, ratio_t);
1244 for c in 0..3 {
1245 let base = c * frames * h * w;
1246 for v in out[base..base + frames * h * w].iter_mut() {
1247 *v = (*v * IMAGENET_STD[c] + IMAGENET_MEAN[c]).clamp(0.0, 1.0);
1248 }
1249 }
1250 let keep = want.min(frames);
1251 if keep < frames {
1252 let mut trimmed = vec![0f32; 3 * keep * h * w];
1253 for c in 0..3 {
1254 let src = c * frames * h * w;
1255 let dst = c * keep * h * w;
1256 trimmed[dst..dst + keep * h * w].copy_from_slice(&out[src..src + keep * h * w]);
1257 }
1258 out = trimmed;
1259 }
1260 (out, keep)
1261 }
1262
1263 pub fn decode_image(&self, z: &[f32], zh: usize, zw: usize) -> Vec<f32> {
1268 debug_assert_eq!(self.patch_t, 1, "decode_image on a video decoder");
1269 let zc = self.z_channels;
1270 let np = zh * zw;
1271 let mut zz = vec![0f32; zc * np];
1272 for c in 0..zc {
1273 let (m, s) = (self.latents_mean[c], self.latents_std[c]);
1274 for i in 0..np {
1275 zz[c * np + i] = z[c * np + i] * s + m;
1276 }
1277 }
1278 let mut out = self.decode_clip(&zz, 1, zh, zw);
1279 let hw = zh * self.patch * zw * self.patch;
1280 for c in 0..3 {
1281 for v in out[c * hw..(c + 1) * hw].iter_mut() {
1282 *v = (*v * IMAGENET_STD[c] + IMAGENET_MEAN[c]).clamp(0.0, 1.0);
1283 }
1284 }
1285 out
1286 }
1287
1288 pub fn spatial_ratio(&self) -> usize {
1289 self.patch
1290 }
1291 pub fn temporal_ratio(&self) -> usize {
1292 self.patch_t
1293 }
1294}
1295
1296fn pad_frames(t_lat: usize, pad: usize, chunk_tokens: usize, ratio_t: usize) -> usize {
1298 if pad == 0 {
1299 return 0;
1300 }
1301 let intra = CLIP_LENGTH % ratio_t;
1302 if intra == 0 {
1303 return pad * ratio_t;
1304 }
1305 (0..pad)
1306 .map(|k| {
1307 if (t_lat + k) % chunk_tokens == 0 {
1308 intra
1309 } else {
1310 ratio_t
1311 }
1312 })
1313 .sum()
1314}
1315
1316#[allow(clippy::too_many_arguments)]
1318fn crop(
1319 x: &[f32],
1320 f: usize,
1321 h: usize,
1322 w: usize,
1323 y0: usize,
1324 y1: usize,
1325 x0: usize,
1326 x1: usize,
1327) -> Vec<f32> {
1328 let (nh, nw) = (y1 - y0, x1 - x0);
1329 let mut out = vec![0f32; 3 * f * nh * nw];
1330 for c in 0..3 {
1331 for fi in 0..f {
1332 for yy in 0..nh {
1333 let s = ((c * f + fi) * h + y0 + yy) * w + x0;
1334 let d = ((c * f + fi) * nh + yy) * nw;
1335 out[d..d + nw].copy_from_slice(&x[s..s + nw]);
1336 }
1337 }
1338 }
1339 out
1340}
1341
1342#[allow(clippy::too_many_arguments)]
1346fn blend(
1347 a: &[f32],
1348 b: &[f32],
1349 f: usize,
1350 h: usize,
1351 w: usize,
1352 extent: usize,
1353 dim: usize,
1354) -> Vec<f32> {
1355 let ah = a.len() / (3 * f * w);
1356 let aw = a.len() / (3 * f * h);
1357 let mut out = b.to_vec();
1358 let e = if dim == 2 {
1359 extent.min(ah).min(h)
1360 } else {
1361 extent.min(aw).min(w)
1362 };
1363 for c in 0..3 {
1364 for fi in 0..f {
1365 for k in 0..e {
1366 let wb = k as f32 / e as f32;
1367 let wa = 1.0 - wb;
1368 if dim == 2 {
1369 let sa = ((c * f + fi) * ah + ah - e + k) * w;
1370 let sb = ((c * f + fi) * h + k) * w;
1371 for x in 0..w {
1372 out[sb + x] = a[sa + x] * wa + b[sb + x] * wb;
1373 }
1374 } else {
1375 for y in 0..h {
1376 let sa = ((c * f + fi) * h + y) * aw + aw - e + k;
1377 let sb = ((c * f + fi) * h + y) * w + k;
1378 out[sb] = a[sa] * wa + b[sb] * wb;
1379 }
1380 }
1381 }
1382 }
1383 }
1384 out
1385}
1386
1387fn frames_of(x: &[f32], f: usize, h: usize, w: usize, a: usize, b: usize) -> Vec<f32> {
1389 let n = b - a;
1390 let mut out = vec![0f32; 3 * n * h * w];
1391 for c in 0..3 {
1392 let s = (c * f + a) * h * w;
1393 let d = c * n * h * w;
1394 out[d..d + n * h * w].copy_from_slice(&x[s..s + n * h * w]);
1395 }
1396 out
1397}
1398
1399#[allow(clippy::too_many_arguments)]
1401fn blend_frames(
1402 tail: &[f32],
1403 tn: usize,
1404 part: &[f32],
1405 pn: usize,
1406 h: usize,
1407 w: usize,
1408 extent: usize,
1409) -> Vec<f32> {
1410 let e = extent.min(tn).min(pn);
1411 let mut out = part.to_vec();
1412 for c in 0..3 {
1413 for k in 0..e {
1414 let wb = k as f32 / e as f32;
1415 let wa = 1.0 - wb;
1416 let s = ((c * tn) + tn - e + k) * h * w;
1417 let d = ((c * pn) + k) * h * w;
1418 for i in 0..h * w {
1419 out[d + i] = tail[s + i] * wa + part[d + i] * wb;
1420 }
1421 }
1422 }
1423 out
1424}
1425
1426fn append_frames(out: &mut Vec<f32>, part: &[f32], n: usize, h: usize, w: usize) {
1428 let old = out.len() / (3 * h * w);
1429 let total = old + n;
1430 let mut grown = vec![0f32; 3 * total * h * w];
1431 for c in 0..3 {
1432 if old > 0 {
1433 let s = c * old * h * w;
1434 let d = c * total * h * w;
1435 grown[d..d + old * h * w].copy_from_slice(&out[s..s + old * h * w]);
1436 }
1437 let s = c * n * h * w;
1438 let d = (c * total + old) * h * w;
1439 grown[d..d + n * h * w].copy_from_slice(&part[s..s + n * h * w]);
1440 }
1441 *out = grown;
1442}
1443
1444struct EncConv {
1459 w: Vec<f32>, b: Vec<f32>,
1461 out_ch: usize,
1462 in_ch: usize,
1463 k: usize,
1464 stride: usize,
1465 pad: usize,
1466}
1467
1468impl EncConv {
1469 fn load(model: &Arc<CmfModel>, name: &str, stride: usize, pad: usize) -> Result<Self, String> {
1470 let e = model
1471 .tensor(&format!("{name}.weight"))
1472 .ok_or_else(|| format!("missing {name}.weight"))?;
1473 let (out_ch, in_ch, k) = (e.shape[0], e.shape[1], e.shape[2]);
1475 Ok(Self {
1476 w: crate::dit::cmf_f32(model, &format!("{name}.weight"))?,
1477 b: crate::dit::cmf_f32(model, &format!("{name}.bias"))?,
1478 out_ch,
1479 in_ch,
1480 k,
1481 stride,
1482 pad,
1483 })
1484 }
1485
1486 fn apply(
1488 &self,
1489 x: &[f32],
1490 h: usize,
1491 w: usize,
1492 pool: Option<&Pool>,
1493 ) -> (Vec<f32>, usize, usize) {
1494 let (ph, pw) = (h + 2 * self.pad, w + 2 * self.pad);
1495 let oh = (ph - self.k) / self.stride + 1;
1496 let ow = (pw - self.k) / self.stride + 1;
1497 let refl = |i: isize, n: usize| -> usize {
1500 let n = n as isize;
1501 let mut i = i;
1502 while i < 0 || i >= n {
1503 if i < 0 {
1504 i = -i;
1505 }
1506 if i >= n {
1507 i = 2 * (n - 1) - i;
1508 }
1509 }
1510 i as usize
1511 };
1512 let mut out = vec![0f32; self.out_ch * oh * ow];
1513 let kk = self.in_ch * self.k * self.k;
1520 if kk * self.out_ch >= (1 << 12) && oh * ow >= 64 {
1521 let n = oh * ow;
1522 let chunk = ((64usize << 20) / (kk * 4)).clamp(1, n);
1523 let mut cols = vec![0f32; chunk * kk];
1524 let mut y = vec![0f32; chunk * self.out_ch];
1525 let (k, stride, pad) = (self.k, self.stride, self.pad as isize);
1526 for p0 in (0..n).step_by(chunk) {
1527 let np = chunk.min(n - p0);
1528 {
1529 let cp = SendPtr(cols.as_mut_ptr());
1530 rows_par(pool, np, &|lo, hi| {
1531 for p in lo..hi {
1532 let (oy, ox) = ((p0 + p) / ow, (p0 + p) % ow);
1533 let row = unsafe { cp.row(p * kk, kk) };
1535 for ky in 0..k {
1536 let sy = refl((oy * stride + ky) as isize - pad, h);
1537 for kx in 0..k {
1538 let sx = refl((ox * stride + kx) as isize - pad, w);
1539 let at = sy * w + sx;
1540 for i in 0..self.in_ch {
1541 row[(i * k + ky) * k + kx] = x[i * h * w + at];
1542 }
1543 }
1544 }
1545 }
1546 });
1547 }
1548 crate::fcd_ops::gemm_nt(
1549 &cols[..np * kk],
1550 &self.w,
1551 &mut y[..np * self.out_ch],
1552 np,
1553 kk,
1554 self.out_ch,
1555 pool,
1556 );
1557 for o in 0..self.out_ch {
1558 let dst = &mut out[o * n + p0..o * n + p0 + np];
1559 for (p, d) in dst.iter_mut().enumerate() {
1560 *d = y[p * self.out_ch + o] + self.b[o];
1561 }
1562 }
1563 }
1564 return (out, oh, ow);
1565 }
1566 let ptr = SendPtr(out.as_mut_ptr());
1567 let work = |lo: usize, hi: usize| {
1568 for o in lo..hi {
1569 let dst = unsafe { ptr.row(o * oh * ow, oh * ow) };
1571 dst.fill(self.b[o]);
1572 for i in 0..self.in_ch {
1573 let ker = &self.w[(o * self.in_ch + i) * self.k * self.k
1574 ..(o * self.in_ch + i + 1) * self.k * self.k];
1575 let src = &x[i * h * w..(i + 1) * h * w];
1576 for oy in 0..oh {
1577 for ox in 0..ow {
1578 let mut acc = 0f32;
1579 for ky in 0..self.k {
1580 let sy = (oy * self.stride + ky) as isize - self.pad as isize;
1581 let sy = refl(sy, h);
1582 for kx in 0..self.k {
1583 let sx = (ox * self.stride + kx) as isize - self.pad as isize;
1584 acc += ker[ky * self.k + kx] * src[sy * w + refl(sx, w)];
1585 }
1586 }
1587 dst[oy * ow + ox] += acc;
1588 }
1589 }
1590 }
1591 }
1592 };
1593 match pool {
1594 Some(p) => p.run_rows(self.out_ch, &work),
1595 None => work(0, self.out_ch),
1596 }
1597 (out, oh, ow)
1598 }
1599}
1600
1601struct GroupNorm {
1603 w: Vec<f32>,
1604 b: Vec<f32>,
1605}
1606
1607impl GroupNorm {
1608 fn load(model: &Arc<CmfModel>, name: &str) -> Result<Self, String> {
1609 Ok(Self {
1610 w: crate::dit::cmf_f32(model, &format!("{name}.weight"))?,
1611 b: crate::dit::cmf_f32(model, &format!("{name}.bias"))?,
1612 })
1613 }
1614
1615 fn apply(&self, x: &mut [f32], ch: usize, hw: usize) {
1616 let groups = 32;
1617 let per = ch / groups;
1618 for g in 0..groups {
1619 let seg = &mut x[g * per * hw..(g + 1) * per * hw];
1620 let n = seg.len() as f64;
1621 let mean = seg.iter().map(|&v| v as f64).sum::<f64>() / n;
1622 let var = seg.iter().map(|&v| (v as f64 - mean).powi(2)).sum::<f64>() / n;
1623 let inv = 1.0 / (var + 1e-6).sqrt();
1624 for (i, v) in seg.iter_mut().enumerate() {
1625 let c = g * per + i / hw;
1626 *v = ((*v as f64 - mean) * inv) as f32 * self.w[c] + self.b[c];
1627 }
1628 }
1629 }
1630}
1631
1632struct ResBlock {
1633 norm1: GroupNorm,
1634 norm2: GroupNorm,
1635 conv1: EncConv,
1636 conv2: EncConv,
1637 shortcut: Option<EncConv>,
1638}
1639
1640pub struct VideoVaeEncoder {
1643 conv_in: EncConv,
1644 levels: Vec<(Vec<ResBlock>, Option<EncConv>)>,
1645 norm_out: GroupNorm,
1646 conv_out: EncConv,
1647 quant: Vec<f32>, quant_b: Vec<f32>,
1649 latents_mean: Vec<f32>,
1650 latents_std: Vec<f32>,
1651 pool: Option<Arc<Pool>>,
1652 z_channels: usize,
1653 ratio: usize,
1654}
1655
1656impl VideoVaeEncoder {
1657 pub fn from_cmf(model: &Arc<CmfModel>) -> Result<Self, String> {
1658 let cfg: serde_json::Value = serde_json::from_slice(
1659 model
1660 .tensor_bytes("vvae.config_json")
1661 .map_err(|e| e.to_string())?,
1662 )
1663 .map_err(|e| format!("vvae.config_json: {e}"))?;
1664 let space_down: Vec<usize> = cfg["space_down"]
1665 .as_array()
1666 .map(|a| a.iter().map(|v| v.as_u64().unwrap_or(1) as usize).collect())
1667 .unwrap_or_else(|| vec![2, 2, 2, 2, 1, 1]);
1668 let n_res = cfg["num_res_blocks"].as_u64().unwrap_or(2) as usize;
1669 let mut levels = Vec::new();
1670 for (i, &sd) in space_down.iter().enumerate() {
1671 let mut blocks = Vec::new();
1672 for j in 0..n_res {
1673 let p = format!("vvae.enc.down.{i}.block.{j}");
1674 let shortcut = model
1675 .tensor(&format!("{p}.nin_shortcut.weight"))
1676 .map(|_| EncConv::load(model, &format!("{p}.nin_shortcut"), 1, 0))
1677 .transpose()?;
1678 blocks.push(ResBlock {
1679 norm1: GroupNorm::load(model, &format!("{p}.norm1"))?,
1680 norm2: GroupNorm::load(model, &format!("{p}.norm2"))?,
1681 conv1: EncConv::load(model, &format!("{p}.conv1"), 1, 1)?,
1682 conv2: EncConv::load(model, &format!("{p}.conv2"), 1, 1)?,
1683 shortcut,
1684 });
1685 }
1686 let down = if sd > 1 {
1689 Some(EncConv::load(
1690 model,
1691 &format!("vvae.enc.down.{i}.downsample.conv"),
1692 sd,
1693 0,
1694 )?)
1695 } else {
1696 None
1697 };
1698 levels.push((blocks, down));
1699 }
1700 Ok(Self {
1701 conv_in: EncConv::load(model, "vvae.enc.conv_in", 1, 1)?,
1702 levels,
1703 norm_out: GroupNorm::load(model, "vvae.enc.norm_out")?,
1704 conv_out: EncConv::load(model, "vvae.enc.conv_out", 1, 1)?,
1705 quant: crate::dit::cmf_f32(model, "vvae.quant_conv.weight")?,
1706 quant_b: crate::dit::cmf_f32(model, "vvae.quant_conv.bias")?,
1707 latents_mean: crate::dit::cmf_f32(model, "vvae.latents_mean")?,
1708 latents_std: crate::dit::cmf_f32(model, "vvae.latents_std")?,
1709 pool: Pool::from_env(),
1710 z_channels: cfg["z_channels"].as_u64().unwrap_or(24) as usize,
1711 ratio: cfg["patch_size"].as_u64().unwrap_or(16) as usize,
1712 })
1713 }
1714
1715 fn encode_tile_chain(&self, x: &[f32], h: usize, w: usize) -> Option<(Vec<f32>, usize, usize)> {
1718 use crate::gpu::ConvStep;
1719 if std::env::var("CMF_VAE3D_CHAIN").as_deref() == Ok("0") || !crate::gpu::enabled_here() {
1720 return None;
1721 }
1722 fn conv(c: &EncConv, far: bool) -> ConvStep<'_> {
1723 ConvStep::Conv {
1724 w: &c.w,
1725 b: &c.b,
1726 out_ch: c.out_ch,
1727 in_ch: c.in_ch,
1728 k: c.k,
1729 stride: c.stride,
1730 pad: c.pad,
1731 far,
1732 }
1733 }
1734 let mut steps = vec![conv(&self.conv_in, false)];
1735 for (blocks, down) in &self.levels {
1736 for b in blocks {
1737 steps.push(ConvStep::Save);
1738 steps.push(ConvStep::GnSilu { w: &b.norm1.w, b: &b.norm1.b });
1739 steps.push(conv(&b.conv1, false));
1740 steps.push(ConvStep::GnSilu { w: &b.norm2.w, b: &b.norm2.b });
1741 steps.push(conv(&b.conv2, false));
1742 steps.push(ConvStep::AddSaved {
1743 via: b.shortcut.as_ref().map(|s| (&s.w[..], &s.b[..])),
1744 });
1745 }
1746 if let Some(d) = down {
1747 steps.push(conv(d, true));
1750 }
1751 }
1752 steps.push(ConvStep::GnSilu { w: &self.norm_out.w, b: &self.norm_out.b });
1753 steps.push(conv(&self.conv_out, false));
1754 let (moments, _, mh, mw) = crate::gpu::conv_chain(&steps, x, 3, h, w)?;
1755 Some((moments, mh, mw))
1756 }
1757
1758 fn quant_moments(&self, moments: &[f32], hw: usize) -> Vec<f32> {
1761 let n = 2 * self.z_channels;
1762 let mut out = vec![0f32; n * hw];
1763 for o in 0..n {
1764 for p in 0..hw {
1765 let mut acc = self.quant_b[o];
1766 for i in 0..n {
1767 acc += self.quant[o * n + i] * moments[i * hw + p];
1768 }
1769 out[o * hw + p] = acc;
1770 }
1771 }
1772 out
1773 }
1774
1775 fn encode_tile(&self, x: &[f32], h: usize, w: usize) -> (Vec<f32>, usize, usize) {
1777 if let Some((moments, mh, mw)) = self.encode_tile_chain(x, h, w) {
1778 return (self.quant_moments(&moments, mh * mw), mh, mw);
1779 }
1780 let pool = self.pool.as_deref();
1781 let (mut cur, mut ch, mut cw) = self.conv_in.apply(x, h, w, pool);
1782 let mut c = self.conv_in.out_ch;
1783 for (blocks, down) in &self.levels {
1784 for b in blocks {
1785 let mut hh = cur.clone();
1786 self.norm_out_like(&b.norm1, &mut hh, c, ch * cw);
1787 for v in hh.iter_mut() {
1788 *v = silu(*v);
1789 }
1790 let (mut t, th, tw) = b.conv1.apply(&hh, ch, cw, pool);
1791 let oc = b.conv1.out_ch;
1792 self.norm_out_like(&b.norm2, &mut t, oc, th * tw);
1793 for v in t.iter_mut() {
1794 *v = silu(*v);
1795 }
1796 let (t2, th2, tw2) = b.conv2.apply(&t, th, tw, pool);
1797 let skip = match &b.shortcut {
1798 Some(s) => s.apply(&cur, ch, cw, pool).0,
1799 None => cur.clone(),
1800 };
1801 cur = t2.iter().zip(&skip).map(|(&a, &b)| a + b).collect();
1802 (ch, cw, c) = (th2, tw2, oc);
1803 }
1804 if let Some(d) = down {
1805 let (padded, ph, pw) = reflect_pad_far(&cur, c, ch, cw);
1808 let (t, th, tw) = d.apply(&padded, ph, pw, pool);
1809 cur = t;
1810 (ch, cw, c) = (th, tw, d.out_ch);
1811 }
1812 }
1813 self.norm_out_like(&self.norm_out, &mut cur, c, ch * cw);
1814 for v in cur.iter_mut() {
1815 *v = silu(*v);
1816 }
1817 let (moments, mh, mw) = self.conv_out.apply(&cur, ch, cw, pool);
1818 (self.quant_moments(&moments, mh * mw), mh, mw)
1819 }
1820
1821 fn norm_out_like(&self, g: &GroupNorm, x: &mut [f32], ch: usize, hw: usize) {
1822 g.apply(x, ch, hw);
1823 }
1824
1825 pub fn encode_frame(&self, rgb: &[f32], h: usize, w: usize) -> (Vec<f32>, usize, usize) {
1830 let (canvas, zh, zw) = self.moments(rgb, h, w);
1831 let mut z = vec![0f32; self.z_channels * zh * zw];
1832 for c in 0..self.z_channels {
1833 let (m, s) = (self.latents_mean[c], self.latents_std[c]);
1834 for p in 0..zh * zw {
1835 z[c * zh * zw + p] = (canvas[c * zh * zw + p] - m) / s;
1836 }
1837 }
1838 (z, zh, zw)
1839 }
1840
1841 pub fn encode_frame_sampled(
1847 &self,
1848 rgb: &[f32],
1849 h: usize,
1850 w: usize,
1851 seed: u64,
1852 ) -> (Vec<f32>, usize, usize) {
1853 let (canvas, zh, zw) = self.moments(rgb, h, w);
1854 let zc = self.z_channels;
1855 let noise = crate::videogen::gauss_pub(zc * zh * zw, seed);
1856 let mut z = vec![0f32; zc * zh * zw];
1857 for c in 0..zc {
1858 let (m, s) = (self.latents_mean[c], self.latents_std[c]);
1859 for p in 0..zh * zw {
1860 let mean = canvas[c * zh * zw + p];
1861 let logvar = canvas[(zc + c) * zh * zw + p].clamp(-30.0, 20.0);
1862 let v = mean + (0.5 * logvar).exp() * noise[c * zh * zw + p];
1863 z[c * zh * zw + p] = (v - m) / s;
1864 }
1865 }
1866 (z, zh, zw)
1867 }
1868
1869 fn moments(&self, rgb: &[f32], h: usize, w: usize) -> (Vec<f32>, usize, usize) {
1873 let mut x = vec![0f32; 3 * h * w];
1875 for c in 0..3 {
1876 for p in 0..h * w {
1877 let v = (rgb[c * h * w + p] + 1.0) * 0.5;
1878 x[c * h * w + p] = (v - IMAGENET_MEAN[c]) / IMAGENET_STD[c];
1879 }
1880 }
1881 let r = self.ratio;
1882 let (ys, yl, yo) = split_tiles(h, r);
1883 let (xs, xl, xo) = split_tiles(w, r);
1884 let (zh, zw) = (h / r, w / r);
1885 let zc = 2 * self.z_channels;
1886 let mut rows: Vec<Vec<Vec<f32>>> = Vec::new();
1887 let mut dims: Vec<Vec<(usize, usize)>> = Vec::new();
1888 for (&ip, &il) in ys.iter().zip(&yl) {
1889 let mut row = Vec::new();
1890 let mut rd = Vec::new();
1891 for (&jp, &jl) in xs.iter().zip(&xl) {
1892 let mut sub = vec![0f32; 3 * il * jl];
1893 for c in 0..3 {
1894 for yy in 0..il {
1895 let s = (c * h + ip + yy) * w + jp;
1896 let d = (c * il + yy) * jl;
1897 sub[d..d + jl].copy_from_slice(&x[s..s + jl]);
1898 }
1899 }
1900 let (t, th, tw) = self.encode_tile(&sub, il, jl);
1901 row.push(t);
1902 rd.push((th, tw));
1903 }
1904 rows.push(row);
1905 dims.push(rd);
1906 }
1907 let mut canvas = vec![0f32; zc * zh * zw];
1909 let mut out_y = 0usize;
1910 for i in 0..rows.len() {
1911 let mut out_x = 0usize;
1912 let mut row_h = 0usize;
1913 for j in 0..rows[i].len() {
1914 let (th, tw) = dims[i][j];
1915 let mut tile = rows[i][j].clone();
1916 let (mut ch_, mut cw_) = (th, tw);
1917 if i > 0 {
1918 tile = blend_plane(
1919 &rows[i - 1][j],
1920 &tile,
1921 zc,
1922 dims[i - 1][j],
1923 (ch_, cw_),
1924 yo[i - 1] / r,
1925 0,
1926 );
1927 }
1928 if j > 0 {
1929 tile = blend_plane(
1930 &rows[i][j - 1],
1931 &tile,
1932 zc,
1933 dims[i][j - 1],
1934 (ch_, cw_),
1935 xo[j - 1] / r,
1936 1,
1937 );
1938 }
1939 if i + 1 < rows.len() {
1940 tile = crop_plane(&tile, zc, ch_, cw_, 0, ch_ - yo[i] / r, 0, cw_);
1941 ch_ -= yo[i] / r;
1942 }
1943 if j + 1 < rows[i].len() {
1944 tile = crop_plane(&tile, zc, ch_, cw_, 0, ch_, 0, cw_ - xo[j] / r);
1945 cw_ -= xo[j] / r;
1946 }
1947 for c in 0..zc {
1948 for yy in 0..ch_ {
1949 let d = (c * zh + out_y + yy) * zw + out_x;
1950 let s = (c * ch_ + yy) * cw_;
1951 canvas[d..d + cw_].copy_from_slice(&tile[s..s + cw_]);
1952 }
1953 }
1954 out_x += cw_;
1955 row_h = ch_;
1956 }
1957 out_y += row_h;
1958 }
1959 (canvas, zh, zw)
1960 }
1961}
1962
1963fn reflect_pad_far(x: &[f32], c: usize, h: usize, w: usize) -> (Vec<f32>, usize, usize) {
1965 let (ph, pw) = (h + 1, w + 1);
1966 let mut out = vec![0f32; c * ph * pw];
1967 for ci in 0..c {
1968 for y in 0..ph {
1969 let sy = if y < h { y } else { h - 2 };
1970 for x2 in 0..pw {
1971 let sx = if x2 < w { x2 } else { w - 2 };
1972 out[(ci * ph + y) * pw + x2] = x[(ci * h + sy) * w + sx];
1973 }
1974 }
1975 }
1976 (out, ph, pw)
1977}
1978
1979fn blend_plane(
1981 a: &[f32],
1982 b: &[f32],
1983 c: usize,
1984 ad: (usize, usize),
1985 bd: (usize, usize),
1986 extent: usize,
1987 dim: usize,
1988) -> Vec<f32> {
1989 let (ah, aw) = ad;
1990 let (bh, bw) = bd;
1991 let mut out = b.to_vec();
1992 let e = if dim == 0 {
1993 extent.min(ah).min(bh)
1994 } else {
1995 extent.min(aw).min(bw)
1996 };
1997 if e == 0 {
1998 return out;
1999 }
2000 for ci in 0..c {
2001 for k in 0..e {
2002 let wb = k as f32 / e as f32;
2003 let wa = 1.0 - wb;
2004 if dim == 0 {
2005 for x in 0..bw.min(aw) {
2006 let sa = (ci * ah + ah - e + k) * aw + x;
2007 let sb = (ci * bh + k) * bw + x;
2008 out[sb] = a[sa] * wa + b[sb] * wb;
2009 }
2010 } else {
2011 for y in 0..bh.min(ah) {
2012 let sa = (ci * ah + y) * aw + aw - e + k;
2013 let sb = (ci * bh + y) * bw + k;
2014 out[sb] = a[sa] * wa + b[sb] * wb;
2015 }
2016 }
2017 }
2018 }
2019 out
2020}
2021
2022#[allow(clippy::too_many_arguments)]
2024fn crop_plane(
2025 x: &[f32],
2026 c: usize,
2027 h: usize,
2028 w: usize,
2029 y0: usize,
2030 y1: usize,
2031 x0: usize,
2032 x1: usize,
2033) -> Vec<f32> {
2034 let (nh, nw) = (y1 - y0, x1 - x0);
2035 let mut out = vec![0f32; c * nh * nw];
2036 for ci in 0..c {
2037 for y in 0..nh {
2038 let s = (ci * h + y0 + y) * w + x0;
2039 let d = (ci * nh + y) * nw;
2040 out[d..d + nw].copy_from_slice(&x[s..s + nw]);
2041 }
2042 }
2043 out
2044}
2045
2046#[cfg(test)]
2047mod tests {
2048 use super::*;
2049
2050 #[test]
2051 fn tile_schedule_matches_the_reference() {
2052 let (s, l, o) = split_tiles(288, 16);
2054 assert_eq!(s, vec![0, 32]);
2055 assert_eq!(l, vec![256, 256]);
2056 assert_eq!(o, vec![224]);
2057 let (s, l, o) = split_tiles(512, 16);
2059 assert_eq!(s, vec![0, 128, 256]);
2060 assert_eq!(l, vec![256; 3]);
2061 assert_eq!(o, vec![128, 128]);
2062 assert_eq!(split_tiles(256, 16).0, vec![0]);
2064 assert_eq!(split_tiles(128, 16).1, vec![128]);
2065 }
2066
2067 #[test]
2068 fn temporal_constants_come_out_as_the_reference_computes_them() {
2069 let ratio_t = 4usize;
2070 let chunk = CLIP_LENGTH.div_ceil(ratio_t);
2071 assert_eq!(chunk, 5);
2072 assert_eq!((chunk - TOKEN_DROP % chunk) % chunk, 2);
2073 assert_eq!((ratio_t - CLIP_LENGTH % ratio_t) % ratio_t, 3);
2074 }
2075}