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