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