use crate::pool::Pool;
use cortiq_core::CmfModel;
use std::sync::Arc;
const CHUNK: usize = 8192;
pub struct Conv3d {
w: Vec<f32>,
b: Vec<f32>,
c_out: usize,
c_in: usize,
}
#[derive(Clone)]
pub struct Vol {
pub c: usize,
pub f: usize,
pub h: usize,
pub w: usize,
pub data: Vec<f32>,
}
impl Vol {
pub fn zeros(c: usize, f: usize, h: usize, w: usize) -> Vol {
Vol { c, f, h, w, data: vec![0.0; c * f * h * w] }
}
#[inline]
pub fn at(&self, c: usize, f: usize, h: usize, w: usize) -> f32 {
self.data[((c * self.f + f) * self.h + h) * self.w + w]
}
pub fn positions(&self) -> usize {
self.f * self.h * self.w
}
}
fn tensor_f32(model: &Arc<CmfModel>, name: &str) -> Result<(Vec<f32>, Vec<usize>), String> {
let e = model
.tensor(name)
.ok_or_else(|| format!("missing tensor {name}"))?;
let mut out = vec![0.0f32; e.n_elems()];
cortiq_core::quant::dequant_tensor(e, model.entry_bytes(e), &mut out)?;
Ok((out, e.shape.clone()))
}
impl Conv3d {
fn load(model: &Arc<CmfModel>, prefix: &str, _pool: Option<&Pool>) -> Result<Conv3d, String> {
let (w, shape) = tensor_f32(model, &format!("{prefix}.conv.weight"))?;
let (c_out, c_in) = (shape[0], shape[1]);
let b = match model.tensor(&format!("{prefix}.conv.bias")) {
Some(_) => tensor_f32(model, &format!("{prefix}.conv.bias"))?.0,
None => vec![0.0; c_out],
};
Ok(Conv3d { w, b, c_out, c_in })
}
pub fn forward(&self, x: &Vol, pool: Option<&Pool>) -> Vol {
assert_eq!(x.c, self.c_in, "conv3d channels");
let (f, h, w) = (x.f, x.h, x.w);
let mut out = Vol::zeros(self.c_out, f, h, w);
let k = self.c_in * 27;
let npos = f * h * w;
let mut patches = vec![0.0f32; CHUNK.min(npos) * k];
let mut ybuf = vec![0.0f32; CHUNK.min(npos) * self.c_out];
let mut p0 = 0usize;
while p0 < npos {
let n = CHUNK.min(npos - p0);
patches[..n * k].fill(0.0);
for i in 0..n {
let p = p0 + i;
let (pw, rest) = (p % w, p / w);
let (ph, pf) = (rest % h, rest / h);
let row = &mut patches[i * k..(i + 1) * k];
for ci in 0..self.c_in {
for kf in 0..3usize {
let sf = (pf + kf).saturating_sub(1).min(f - 1);
for kh in 0..3usize {
let sh = ph + kh;
if sh == 0 || sh > h {
continue; }
let sh = sh - 1;
for kw in 0..3usize {
let sw = pw + kw;
if sw == 0 || sw > w {
continue;
}
let sw = sw - 1;
row[(ci * 3 + kf) * 9 + kh * 3 + kw] = x.at(ci, sf, sh, sw);
}
}
}
}
}
crate::fcd_ops::gemm_nt(&patches[..n * k], &self.w, &mut ybuf[..n * self.c_out], n, k, self.c_out, pool);
for i in 0..n {
let p = p0 + i;
for co in 0..self.c_out {
out.data[co * npos + p] = ybuf[i * self.c_out + co] + self.b[co];
}
}
p0 += n;
}
out
}
}
#[inline]
fn silu(x: f32) -> f32 {
x / (1.0 + (-x).exp())
}
fn pixel_norm(x: &mut Vol, eps: f32) {
let npos = x.positions();
for p in 0..npos {
let mut s = 0.0f32;
for c in 0..x.c {
let v = x.data[c * npos + p];
s += v * v;
}
let inv = 1.0 / (s / x.c as f32 + eps).sqrt();
for c in 0..x.c {
x.data[c * npos + p] *= inv;
}
}
}
fn silu_inplace(x: &mut Vol) {
for v in x.data.iter_mut() {
*v = silu(*v);
}
}
pub struct ResBlock {
conv1: Conv3d,
conv2: Conv3d,
}
impl ResBlock {
fn forward(&self, x: &Vol, pool: Option<&Pool>) -> Vol {
let mut h = x.clone();
pixel_norm(&mut h, 1e-8);
silu_inplace(&mut h);
let mut h = self.conv1.forward(&h, pool);
pixel_norm(&mut h, 1e-8);
silu_inplace(&mut h);
let h = self.conv2.forward(&h, pool);
let mut out = x.clone();
for (o, v) in out.data.iter_mut().zip(&h.data) {
*o += v;
}
out
}
}
pub struct DepthToSpaceUp {
conv: Conv3d,
stride: (usize, usize, usize),
}
impl DepthToSpaceUp {
fn forward(&self, x: &Vol, pool: Option<&Pool>) -> Vol {
let y = self.conv.forward(x, pool);
let (p1, p2, p3) = self.stride;
let cout = y.c / (p1 * p2 * p3);
let (f2, h2, w2) = (y.f * p1, y.h * p2, y.w * p3);
let mut out = Vol::zeros(cout, f2, h2, w2);
for c in 0..cout {
for a in 0..p1 {
for b in 0..p2 {
for d in 0..p3 {
let src_c = ((c * p1 + a) * p2 + b) * p3 + d;
for f in 0..y.f {
for hh in 0..y.h {
for ww in 0..y.w {
let v = y.at(src_c, f, hh, ww);
let (of, oh, ow) = (f * p1 + a, hh * p2 + b, ww * p3 + d);
out.data[((c * f2 + of) * h2 + oh) * w2 + ow] = v;
}
}
}
}
}
}
}
if p1 == 2 {
let f3 = f2 - 1;
let mut trimmed = Vol::zeros(cout, f3, h2, w2);
for c in 0..cout {
for f in 0..f3 {
let src = ((c * f2 + f + 1) * h2) * w2;
let dst = ((c * f3 + f) * h2) * w2;
trimmed.data[dst..dst + h2 * w2].copy_from_slice(&out.data[src..src + h2 * w2]);
}
}
return trimmed;
}
out
}
}
enum Block {
Res(Vec<ResBlock>),
Up(DepthToSpaceUp),
}
pub struct ConvVaeDecoder {
conv_in: Conv3d,
blocks: Vec<Block>,
conv_out: Conv3d,
mean: Vec<f32>,
std: Vec<f32>,
patch: usize,
}
impl ConvVaeDecoder {
pub fn from_cmf(model: &Arc<CmfModel>, pool: Option<&Pool>) -> Result<ConvVaeDecoder, String> {
let cfg: serde_json::Value = ["vvae.config_json", "ltx.config_json"]
.iter()
.filter_map(|n| model.tensor(n).map(|e| model.entry_bytes(e)))
.filter_map(|b| serde_json::from_slice::<serde_json::Value>(b).ok())
.find(|c| c.get("vae").and_then(|v| v.get("decoder_blocks")).is_some())
.ok_or("no config in this container carries vae.decoder_blocks")?;
let vae = &cfg["vae"];
let patch = vae["patch_size"].as_u64().unwrap_or(4) as usize;
let blocks_cfg = vae["decoder_blocks"]
.as_array()
.ok_or("vae.decoder_blocks missing")?;
let conv_in = Conv3d::load(model, "vvae.decoder.conv_in", pool)?;
let conv_out = Conv3d::load(model, "vvae.decoder.conv_out", pool)?;
let mut blocks = Vec::new();
for (i, entry) in blocks_cfg.iter().rev().enumerate() {
let name = entry[0].as_str().unwrap_or("");
let params = &entry[1];
let prefix = format!("vvae.decoder.up_blocks.{i}");
match name {
"res_x" => {
let n = params["num_layers"].as_u64().unwrap_or(1) as usize;
let mut res = Vec::new();
for j in 0..n {
res.push(ResBlock {
conv1: Conv3d::load(model, &format!("{prefix}.res_blocks.{j}.conv1"), pool)?,
conv2: Conv3d::load(model, &format!("{prefix}.res_blocks.{j}.conv2"), pool)?,
});
}
blocks.push(Block::Res(res));
}
"compress_time" | "compress_space" | "compress_all" => {
let stride = match name {
"compress_time" => (2, 1, 1),
"compress_space" => (1, 2, 2),
_ => (2, 2, 2),
};
blocks.push(Block::Up(DepthToSpaceUp {
conv: Conv3d::load(model, &format!("{prefix}.conv"), pool)?,
stride,
}));
}
other => return Err(format!("unknown decoder block '{other}'")),
}
}
let mean = tensor_f32(model, "vvae.per_channel_statistics.mean-of-means")?.0;
let std = tensor_f32(model, "vvae.per_channel_statistics.std-of-means")?.0;
Ok(ConvVaeDecoder {
conv_in,
blocks,
conv_out,
mean,
std,
patch,
})
}
pub fn decode(&self, latent: &Vol, pool: Option<&Pool>) -> Vol {
self.decode_traced(latent, pool, &mut |_, _| {})
}
pub fn decode_traced(
&self,
latent: &Vol,
pool: Option<&Pool>,
trace: &mut dyn FnMut(&str, &Vol),
) -> Vol {
let mut x = latent.clone();
let npos = x.positions();
for c in 0..x.c {
let (s, m) = (self.std[c], self.mean[c]);
for p in 0..npos {
x.data[c * npos + p] = x.data[c * npos + p] * s + m;
}
}
let mut h = self.conv_in.forward(&x, pool);
trace("after_conv_in", &h);
for (i, b) in self.blocks.iter().enumerate() {
h = match b {
Block::Res(res) => {
let mut cur = h;
for r in res {
cur = r.forward(&cur, pool);
}
cur
}
Block::Up(u) => u.forward(&h, pool),
};
trace(&format!("after_block_{i}"), &h);
}
pixel_norm(&mut h, 1e-8);
silu_inplace(&mut h);
let y = self.conv_out.forward(&h, pool);
trace("after_conv_out", &y);
let out = unpatchify(&y, self.patch);
trace("frames", &out);
out
}
}
pub fn unpatchify(x: &Vol, patch: usize) -> Vol {
if patch == 1 {
return x.clone();
}
let c = x.c / (patch * patch);
let (h2, w2) = (x.h * patch, x.w * patch);
let mut out = Vol::zeros(c, x.f, h2, w2);
for cc in 0..c {
for r in 0..patch {
for q in 0..patch {
let src_c = (cc * patch + r) * patch + q;
for f in 0..x.f {
for hh in 0..x.h {
for ww in 0..x.w {
let v = x.at(src_c, f, hh, ww);
out.data[((cc * x.f + f) * h2 + hh * patch + q) * w2 + ww * patch + r] = v;
}
}
}
}
}
}
out
}