1use crate::ltxvae::Vol;
12use crate::pool::Pool;
13use cortiq_core::CmfModel;
14use std::sync::Arc;
15
16fn tensor_f32(model: &Arc<CmfModel>, name: &str) -> Result<(Vec<f32>, Vec<usize>), String> {
17 let e = model.tensor(name).ok_or_else(|| format!("missing tensor {name}"))?;
18 let mut out = vec![0.0f32; e.n_elems()];
19 cortiq_core::quant::dequant_tensor(e, model.entry_bytes(e), &mut out)?;
20 Ok((out, e.shape.clone()))
21}
22
23fn silu(v: f32) -> f32 {
24 v / (1.0 + (-v).exp())
25}
26
27struct Conv {
30 w: Vec<f32>,
31 b: Vec<f32>,
32 c_out: usize,
33 c_in: usize,
34 kf: usize,
35 kh: usize,
36 kw: usize,
37}
38
39impl Conv {
40 fn load(model: &Arc<CmfModel>, name: &str) -> Result<Conv, String> {
41 let (w, s) = tensor_f32(model, &format!("{name}.weight"))?;
42 let (b, _) = tensor_f32(model, &format!("{name}.bias"))?;
43 let (kf, kh, kw) = match s.len() {
44 5 => (s[2], s[3], s[4]),
45 4 => (1, s[2], s[3]),
46 _ => return Err(format!("{name}: rank {}", s.len())),
47 };
48 Ok(Conv { w, b, c_out: s[0], c_in: s[1], kf, kh, kw })
49 }
50
51 fn forward(&self, x: &Vol, pool: Option<&Pool>) -> Vol {
52 let (f, h, w) = (x.f, x.h, x.w);
53 let npos = f * h * w;
54 let k = self.c_in * self.kf * self.kh * self.kw;
55 let mut out = Vol::zeros(self.c_out, f, h, w);
56 const CHUNK: usize = 4096;
57 let mut patches = vec![0f32; CHUNK * k];
58 let mut ys = vec![0f32; CHUNK * self.c_out];
59 let (pf, ph, pw) = (self.kf / 2, self.kh / 2, self.kw / 2);
60 let mut p0 = 0usize;
61 while p0 < npos {
62 let n = CHUNK.min(npos - p0);
63 patches[..n * k].fill(0.0);
66 for i in 0..n {
67 let p = p0 + i;
68 let (pw_, rest) = (p % w, p / w);
69 let (ph_, pf_) = (rest % h, rest / h);
70 for ci in 0..self.c_in {
71 for a in 0..self.kf {
72 let sf = pf_ as isize + a as isize - pf as isize;
73 if sf < 0 || sf >= f as isize {
74 continue;
75 }
76 for bb in 0..self.kh {
77 let sh = ph_ as isize + bb as isize - ph as isize;
78 if sh < 0 || sh >= h as isize {
79 continue;
80 }
81 for c in 0..self.kw {
82 let sw = pw_ as isize + c as isize - pw as isize;
83 if sw < 0 || sw >= w as isize {
84 continue;
85 }
86 let v = x.data
87 [((ci * f + sf as usize) * h + sh as usize) * w + sw as usize];
88 patches[i * k + ((ci * self.kf + a) * self.kh + bb) * self.kw + c] = v;
89 }
90 }
91 }
92 }
93 }
94 crate::fcd_ops::gemm_nt(
95 &patches[..n * k],
96 &self.w,
97 &mut ys[..n * self.c_out],
98 n,
99 k,
100 self.c_out,
101 pool,
102 );
103 for i in 0..n {
104 let p = p0 + i;
105 for co in 0..self.c_out {
106 out.data[co * npos + p] = ys[i * self.c_out + co] + self.b[co];
107 }
108 }
109 p0 += n;
110 }
111 out
112 }
113}
114
115fn group_norm(x: &mut Vol, w: &[f32], b: &[f32]) {
117 let groups = 32usize;
118 let per = x.c / groups;
119 let npos = x.positions();
120 for g in 0..groups {
121 let (lo, hi) = (g * per * npos, (g + 1) * per * npos);
122 let s = &x.data[lo..hi];
123 let n = s.len() as f64;
124 let mean = s.iter().map(|&v| v as f64).sum::<f64>() / n;
125 let var = s.iter().map(|&v| (v as f64 - mean) * (v as f64 - mean)).sum::<f64>() / n;
126 let inv = 1.0 / (var + 1e-5).sqrt();
127 for c in 0..per {
128 let ch = g * per + c;
129 let (gw, gb) = (w[ch], b[ch]);
130 for v in x.data[ch * npos..(ch + 1) * npos].iter_mut() {
131 *v = ((*v as f64 - mean) * inv) as f32 * gw + gb;
132 }
133 }
134 }
135}
136
137struct ResBlock {
138 conv1: Conv,
139 n1w: Vec<f32>,
140 n1b: Vec<f32>,
141 conv2: Conv,
142 n2w: Vec<f32>,
143 n2b: Vec<f32>,
144}
145
146impl ResBlock {
147 fn load(model: &Arc<CmfModel>, p: &str) -> Result<ResBlock, String> {
148 Ok(ResBlock {
149 conv1: Conv::load(model, &format!("{p}.conv1"))?,
150 n1w: tensor_f32(model, &format!("{p}.norm1.weight"))?.0,
151 n1b: tensor_f32(model, &format!("{p}.norm1.bias"))?.0,
152 conv2: Conv::load(model, &format!("{p}.conv2"))?,
153 n2w: tensor_f32(model, &format!("{p}.norm2.weight"))?.0,
154 n2b: tensor_f32(model, &format!("{p}.norm2.bias"))?.0,
155 })
156 }
157
158 fn forward(&self, x: &Vol, pool: Option<&Pool>) -> Vol {
159 let mut h = self.conv1.forward(x, pool);
160 group_norm(&mut h, &self.n1w, &self.n1b);
161 for v in h.data.iter_mut() {
162 *v = silu(*v);
163 }
164 let mut h2 = self.conv2.forward(&h, pool);
165 group_norm(&mut h2, &self.n2w, &self.n2b);
166 for (v, &r) in h2.data.iter_mut().zip(&x.data) {
168 *v = silu(*v + r);
169 }
170 h2
171 }
172}
173
174pub struct LatentUpscaler {
175 initial: Conv,
176 in_w: Vec<f32>,
177 in_b: Vec<f32>,
178 pre: Vec<ResBlock>,
179 up: Conv,
180 post: Vec<ResBlock>,
181 final_conv: Conv,
182 mean: Vec<f32>,
183 std: Vec<f32>,
184}
185
186impl LatentUpscaler {
187 pub fn from_cmf(model: &Arc<CmfModel>) -> Result<LatentUpscaler, String> {
188 let load_blocks = |p: &str| -> Result<Vec<ResBlock>, String> {
189 let mut v = Vec::new();
190 let mut i = 0;
191 while model.tensor(&format!("{p}.{i}.conv1.weight")).is_some() {
192 v.push(ResBlock::load(model, &format!("{p}.{i}"))?);
193 i += 1;
194 }
195 Ok(v)
196 };
197 Ok(LatentUpscaler {
198 initial: Conv::load(model, "ups.initial_conv")?,
199 in_w: tensor_f32(model, "ups.initial_norm.weight")?.0,
200 in_b: tensor_f32(model, "ups.initial_norm.bias")?.0,
201 pre: load_blocks("ups.res_blocks")?,
202 up: Conv::load(model, "ups.upsampler.0")?,
203 post: load_blocks("ups.post_upsample_res_blocks")?,
204 final_conv: Conv::load(model, "ups.final_conv")?,
205 mean: tensor_f32(model, "vvae.per_channel_statistics.mean-of-means")?.0,
206 std: tensor_f32(model, "vvae.per_channel_statistics.std-of-means")?.0,
207 })
208 }
209
210 pub fn upscale(&self, latent: &Vol, pool: Option<&Pool>) -> Vol {
213 let npos = latent.positions();
214 let mut x = latent.clone();
215 for c in 0..x.c {
217 for v in x.data[c * npos..(c + 1) * npos].iter_mut() {
218 *v = *v * self.std[c] + self.mean[c];
219 }
220 }
221 let mut h = self.initial.forward(&x, pool);
222 group_norm(&mut h, &self.in_w, &self.in_b);
223 for v in h.data.iter_mut() {
224 *v = silu(*v);
225 }
226 for b in &self.pre {
227 h = b.forward(&h, pool);
228 }
229 h = self.pixel_shuffle(&self.up.forward(&h, pool));
230 for b in &self.post {
231 h = b.forward(&h, pool);
232 }
233 let mut out = self.final_conv.forward(&h, pool);
234 let npos2 = out.positions();
235 for c in 0..out.c {
236 for v in out.data[c * npos2..(c + 1) * npos2].iter_mut() {
237 *v = (*v - self.mean[c]) / self.std[c];
238 }
239 }
240 out
241 }
242
243 fn pixel_shuffle(&self, x: &Vol) -> Vol {
245 let c = x.c / 4;
246 let (h2, w2) = (x.h * 2, x.w * 2);
247 let mut out = Vol::zeros(c, x.f, h2, w2);
248 for ch in 0..c {
249 for p1 in 0..2 {
250 for p2 in 0..2 {
251 let src = (ch * 2 + p1) * 2 + p2;
252 for f in 0..x.f {
253 for y in 0..x.h {
254 for z in 0..x.w {
255 let v = x.data[((src * x.f + f) * x.h + y) * x.w + z];
256 out.data[((ch * x.f + f) * h2 + y * 2 + p1) * w2 + z * 2 + p2] = v;
257 }
258 }
259 }
260 }
261 }
262 }
263 out
264 }
265}