1use crate::ltxvae::Vol;
20use crate::pool::Pool;
21use cortiq_core::CmfModel;
22use std::sync::Arc;
23
24fn tensor_f32(model: &Arc<CmfModel>, name: &str) -> Result<(Vec<f32>, Vec<usize>), String> {
25 let e = model.tensor(name).ok_or_else(|| format!("missing tensor {name}"))?;
26 let mut out = vec![0.0f32; e.n_elems()];
27 cortiq_core::quant::dequant_tensor(e, model.entry_bytes(e), &mut out)?;
28 Ok((out, e.shape.clone()))
29}
30
31fn silu(x: f32) -> f32 {
32 x / (1.0 + (-x).exp())
33}
34
35fn pixel_norm(x: &mut Vol) {
36 let npos = x.positions();
37 for p in 0..npos {
38 let mut s = 0f32;
39 for c in 0..x.c {
40 let v = x.data[c * npos + p];
41 s += v * v;
42 }
43 let inv = 1.0 / (s / x.c as f32 + 1e-6).sqrt();
44 for c in 0..x.c {
45 x.data[c * npos + p] *= inv;
46 }
47 }
48}
49
50struct Conv {
54 w: Vec<f32>,
55 b: Vec<f32>,
56 c_out: usize,
57 c_in: usize,
58 stride: (usize, usize, usize),
59}
60
61impl Conv {
62 fn load(model: &Arc<CmfModel>, p: &str, stride: (usize, usize, usize)) -> Result<Conv, String> {
63 let (w, s) = tensor_f32(model, &format!("{p}.conv.weight"))?;
64 let b = match model.tensor(&format!("{p}.conv.bias")) {
65 Some(_) => tensor_f32(model, &format!("{p}.conv.bias"))?.0,
66 None => vec![0.0; s[0]],
67 };
68 Ok(Conv { w, b, c_out: s[0], c_in: s[1], stride })
69 }
70
71 fn forward(&self, x: &Vol, pool: Option<&Pool>) -> Vol {
72 let (sf, sh, sw) = self.stride;
73 let (of, oh, ow) = (x.f.div_ceil(sf), x.h.div_ceil(sh), x.w.div_ceil(sw));
74 let mut out = Vol::zeros(self.c_out, of, oh, ow);
75 let k = self.c_in * 27;
76 let npos = of * oh * ow;
77 const CHUNK: usize = 8192;
78 let mut patches = vec![0f32; CHUNK.min(npos) * k];
79 let mut ys = vec![0f32; CHUNK.min(npos) * self.c_out];
80 let mut p0 = 0usize;
81 while p0 < npos {
82 let n = CHUNK.min(npos - p0);
83 patches[..n * k].fill(0.0);
84 for i in 0..n {
85 let p = p0 + i;
86 let (pw, rest) = (p % ow, p / ow);
87 let (ph, pf) = (rest % oh, rest / oh);
88 let (bf, bh, bw) = (pf * sf, ph * sh, pw * sw);
89 let row = &mut patches[i * k..(i + 1) * k];
90 for ci in 0..self.c_in {
91 for kf in 0..3usize {
92 let src_f = (bf + kf).saturating_sub(2).min(x.f - 1);
95 for kh in 0..3usize {
96 let s = bh + kh;
97 if s == 0 || s > x.h {
98 continue;
99 }
100 let src_h = s - 1;
101 for kw in 0..3usize {
102 let s = bw + kw;
103 if s == 0 || s > x.w {
104 continue;
105 }
106 row[(ci * 3 + kf) * 9 + kh * 3 + kw] = x.at(ci, src_f, src_h, s - 1);
107 }
108 }
109 }
110 }
111 }
112 crate::fcd_ops::gemm_nt(
113 &patches[..n * k],
114 &self.w,
115 &mut ys[..n * self.c_out],
116 n,
117 k,
118 self.c_out,
119 pool,
120 );
121 for i in 0..n {
122 for co in 0..self.c_out {
123 out.data[co * npos + p0 + i] = ys[i * self.c_out + co] + self.b[co];
124 }
125 }
126 p0 += n;
127 }
128 out
129 }
130}
131
132struct Res {
133 c1: Conv,
134 c2: Conv,
135}
136
137impl Res {
138 fn forward(&self, x: &Vol, pool: Option<&Pool>) -> Vol {
139 let mut h = x.clone();
140 pixel_norm(&mut h);
141 h.data.iter_mut().for_each(|v| *v = silu(*v));
142 let mut h = self.c1.forward(&h, pool);
143 pixel_norm(&mut h);
144 h.data.iter_mut().for_each(|v| *v = silu(*v));
145 let mut h = self.c2.forward(&h, pool);
146 for (v, &r) in h.data.iter_mut().zip(&x.data) {
147 *v += r;
148 }
149 h
150 }
151}
152
153struct Down {
159 conv: Conv,
160 stride: (usize, usize, usize),
161 out_channels: usize,
162}
163
164impl Down {
165 fn load(
166 model: &Arc<CmfModel>,
167 p: &str,
168 stride: (usize, usize, usize),
169 multiplier: usize,
170 ) -> Result<Down, String> {
171 let conv = Conv::load(model, &format!("{p}.conv"), (1, 1, 1))?;
172 let out_channels = conv.c_in * multiplier;
173 Ok(Down { conv, stride, out_channels })
174 }
175
176 fn forward(&self, x: &Vol, pool: Option<&Pool>) -> Vol {
177 let (sf, sh, sw) = self.stride;
178 let padded;
179 let src = if sf == 2 {
180 let mut v = Vol::zeros(x.c, x.f + 1, x.h, x.w);
181 for c in 0..x.c {
182 for f in 0..=x.f {
183 let from = f.saturating_sub(1);
184 for y in 0..x.h {
185 for z in 0..x.w {
186 v.data[((c * (x.f + 1) + f) * x.h + y) * x.w + z] = x.at(c, from, y, z);
187 }
188 }
189 }
190 }
191 padded = v;
192 &padded
193 } else {
194 x
195 };
196 let folded = space_to_depth(src, self.stride);
197 let group = folded.c / self.out_channels;
199 let np = folded.positions();
200 let mut skip = Vol::zeros(self.out_channels, folded.f, folded.h, folded.w);
201 for c in 0..self.out_channels {
202 for g in 0..group {
203 let sc = c * group + g;
204 for i in 0..np {
205 skip.data[c * np + i] += folded.data[sc * np + i];
206 }
207 }
208 let inv = 1.0 / group as f32;
209 for i in 0..np {
210 skip.data[c * np + i] *= inv;
211 }
212 }
213 let mut out = space_to_depth(&self.conv.forward(src, pool), self.stride);
214 for (v, &r) in out.data.iter_mut().zip(&skip.data) {
215 *v += r;
216 }
217 out
218 }
219}
220
221fn space_to_depth(x: &Vol, stride: (usize, usize, usize)) -> Vol {
223 let (p1, p2, p3) = stride;
224 let (df, dh, dw) = (x.f / p1, x.h / p2, x.w / p3);
225 let mut out = Vol::zeros(x.c * p1 * p2 * p3, df, dh, dw);
226 let np = df * dh * dw;
227 for c in 0..x.c {
228 for i1 in 0..p1 {
229 for i2 in 0..p2 {
230 for i3 in 0..p3 {
231 let dc = ((c * p1 + i1) * p2 + i2) * p3 + i3;
232 for f in 0..df {
233 for y in 0..dh {
234 for z in 0..dw {
235 out.data[dc * np + (f * dh + y) * dw + z] =
236 x.at(c, f * p1 + i1, y * p2 + i2, z * p3 + i3);
237 }
238 }
239 }
240 }
241 }
242 }
243 }
244 out
245}
246
247enum Block {
248 Res(Vec<Res>),
249 Down(Down),
250}
251
252pub struct VideoEncoder {
253 patch: usize,
254 conv_in: Conv,
255 blocks: Vec<Block>,
256 conv_out: Conv,
257 mean: Vec<f32>,
258 std: Vec<f32>,
259 latent_channels: usize,
260}
261
262impl VideoEncoder {
263 pub fn from_cmf(model: &Arc<CmfModel>) -> Result<VideoEncoder, String> {
264 let cfg: serde_json::Value = ["vvae.config_json", "ltx.config_json"]
265 .iter()
266 .filter_map(|n| model.tensor(n).map(|e| model.entry_bytes(e)))
267 .filter_map(|b| serde_json::from_slice::<serde_json::Value>(b).ok())
268 .find(|c| c.get("vae").and_then(|v| v.get("encoder_blocks")).is_some())
269 .ok_or("no config in this container carries vae.encoder_blocks")?;
270 let vae = &cfg["vae"];
271 let patch = vae["patch_size"].as_u64().unwrap_or(4) as usize;
272 let list = vae["encoder_blocks"].as_array().ok_or("vae.encoder_blocks")?;
273 let mut blocks = Vec::new();
274 for (i, entry) in list.iter().enumerate() {
275 let name = entry[0].as_str().unwrap_or("");
276 let p = format!("vvae.encoder.down_blocks.{i}");
277 match name {
278 "res_x" => {
279 let mut r = Vec::new();
280 let mut j = 0usize;
281 while model.tensor(&format!("{p}.res_blocks.{j}.conv1.conv.weight")).is_some() {
282 r.push(Res {
283 c1: Conv::load(model, &format!("{p}.res_blocks.{j}.conv1"), (1, 1, 1))?,
284 c2: Conv::load(model, &format!("{p}.res_blocks.{j}.conv2"), (1, 1, 1))?,
285 });
286 j += 1;
287 }
288 blocks.push(Block::Res(r));
289 }
290 "compress_all_res" | "compress_time_res" | "compress_space_res" => {
291 let stride = match name {
292 "compress_time_res" => (2, 1, 1),
293 "compress_space_res" => (1, 2, 2),
294 _ => (2, 2, 2),
295 };
296 let mult = entry[1]
297 .get("multiplier")
298 .and_then(|v| v.as_u64())
299 .unwrap_or(2) as usize;
300 blocks.push(Block::Down(Down::load(model, &p, stride, mult)?));
301 }
302 other => return Err(format!("encoder block '{other}' is not ported")),
303 }
304 }
305 Ok(VideoEncoder {
306 patch,
307 conv_in: Conv::load(model, "vvae.encoder.conv_in", (1, 1, 1))?,
308 blocks,
309 conv_out: Conv::load(model, "vvae.encoder.conv_out", (1, 1, 1))?,
310 mean: tensor_f32(model, "vvae.per_channel_statistics.mean-of-means")?.0,
311 std: tensor_f32(model, "vvae.per_channel_statistics.std-of-means")?.0,
312 latent_channels: vae["latent_channels"].as_u64().unwrap_or(128) as usize,
313 })
314 }
315
316 pub fn encode(&self, frames: &Vol, pool: Option<&Pool>) -> Vol {
319 let p = self.patch;
320 let (h2, w2) = (frames.h / p, frames.w / p);
321 let mut x = Vol::zeros(frames.c * p * p, frames.f, h2, w2);
326 let np = x.positions();
327 for c in 0..frames.c {
328 for a in 0..p {
329 for b in 0..p {
330 let dc = (c * p + b) * p + a;
331 for f in 0..frames.f {
332 for y in 0..h2 {
333 for z in 0..w2 {
334 x.data[dc * np + (f * h2 + y) * w2 + z] =
335 frames.at(c, f, y * p + a, z * p + b);
336 }
337 }
338 }
339 }
340 }
341 }
342 let mut h = self.conv_in.forward(&x, pool);
343 for b in &self.blocks {
344 h = match b {
345 Block::Res(rs) => {
346 let mut cur = h;
347 for r in rs {
348 cur = r.forward(&cur, pool);
349 }
350 cur
351 }
352 Block::Down(c) => c.forward(&h, pool),
353 };
354 }
355 pixel_norm(&mut h);
356 h.data.iter_mut().for_each(|v| *v = silu(*v));
357 let out = self.conv_out.forward(&h, pool);
358 let npos = out.positions();
361 let mut lat = Vol::zeros(self.latent_channels, out.f, out.h, out.w);
362 for c in 0..self.latent_channels {
363 for i in 0..npos {
364 lat.data[c * npos + i] = (out.data[c * npos + i] - self.mean[c]) / self.std[c];
365 }
366 }
367 lat
368 }
369}