1use std::path::Path;
16
17pub struct StTensor {
21 pub shape: Vec<usize>,
22 pub data: Vec<f32>,
23}
24
25pub fn read_safetensors_each(
29 path: &Path,
30 f: &mut dyn FnMut(&str, Vec<usize>, Vec<f32>) -> Result<(), String>,
31) -> Result<(), String> {
32 let bytes = std::fs::read(path).map_err(|e| format!("{}: {e}", path.display()))?;
33 if bytes.len() < 8 {
34 return Err("safetensors: truncated header".into());
35 }
36 let hlen = u64::from_le_bytes(bytes[..8].try_into().unwrap()) as usize;
37 let header: serde_json::Value = serde_json::from_slice(&bytes[8..8 + hlen])
38 .map_err(|e| format!("safetensors header: {e}"))?;
39 let base = 8 + hlen;
40 let obj = header
41 .as_object()
42 .ok_or("safetensors: header not an object")?;
43 for (name, meta) in obj {
44 if name == "__metadata__" {
45 continue;
46 }
47 let dtype = meta["dtype"].as_str().ok_or("dtype")?;
48 let shape: Vec<usize> = meta["shape"]
49 .as_array()
50 .ok_or("shape")?
51 .iter()
52 .map(|v| v.as_u64().unwrap_or(0) as usize)
53 .collect();
54 let offs = meta["data_offsets"].as_array().ok_or("offsets")?;
55 let (s, e) = (
56 offs[0].as_u64().unwrap_or(0) as usize + base,
57 offs[1].as_u64().unwrap_or(0) as usize + base,
58 );
59 let raw = bytes.get(s..e).ok_or("safetensors: span out of file")?;
60 let n: usize = shape.iter().product::<usize>().max(1);
61 let mut data = Vec::with_capacity(n);
62 match dtype {
63 "F32" => {
64 for c in raw.chunks_exact(4) {
65 data.push(f32::from_le_bytes(c.try_into().unwrap()));
66 }
67 }
68 "F16" => {
69 for c in raw.chunks_exact(2) {
70 data.push(cortiq_core::quant::f16_to_f32(u16::from_le_bytes(
71 c.try_into().unwrap(),
72 )));
73 }
74 }
75 "BF16" => {
76 for c in raw.chunks_exact(2) {
77 let b = u16::from_le_bytes(c.try_into().unwrap());
78 data.push(f32::from_bits((b as u32) << 16));
79 }
80 }
81 other => return Err(format!("safetensors: unsupported dtype {other}")),
82 }
83 f(name, shape, data)?;
84 }
85 Ok(())
86}
87
88pub fn read_safetensors(
90 path: &Path,
91) -> Result<std::collections::HashMap<String, StTensor>, String> {
92 let mut out = std::collections::HashMap::new();
93 read_safetensors_each(path, &mut |name, shape, data| {
94 out.insert(name.to_string(), StTensor { shape, data });
95 Ok(())
96 })?;
97 Ok(out)
98}
99
100pub struct Conv2d {
105 pub w: Vec<f32>, pub b: Vec<f32>, pub oc: usize,
108 pub ic: usize,
109 pub k: usize,
110}
111
112impl Conv2d {
113 fn from(t: &StTensor, bias: &StTensor) -> Self {
114 let (oc, ic, k) = (t.shape[0], t.shape[1], t.shape[2]);
115 Self {
116 w: t.data.clone(),
117 b: bias.data.clone(),
118 oc,
119 ic,
120 k,
121 }
122 }
123
124 pub fn apply(&self, x: &[f32], h: usize, w: usize) -> Vec<f32> {
130 debug_assert_eq!(x.len(), self.ic * h * w);
131 let pad = self.k / 2;
132 let ick2 = self.ic * self.k * self.k;
133 let mut out = vec![0f32; self.oc * h * w];
134 let band = (128 << 20) / (ick2 * w * 4).max(1);
135 let band = band.clamp(1, h);
136 let mut cols = vec![0f32; band * w * ick2];
137 let mut yt = vec![0f32; band * w * self.oc];
138 let mut y0 = 0usize;
139 while y0 < h {
140 let rows = band.min(h - y0);
141 let hw_band = rows * w;
142 for (dy, colrow) in cols[..hw_band * ick2].chunks_mut(w * ick2).enumerate() {
145 let y = y0 + dy;
146 for (xx, patch) in colrow.chunks_mut(ick2).enumerate() {
147 let mut i = 0;
148 for c in 0..self.ic {
149 let img = &x[c * h * w..(c + 1) * h * w];
150 for ky in 0..self.k {
151 let sy = y as isize + ky as isize - pad as isize;
152 for kx in 0..self.k {
153 let sx = xx as isize + kx as isize - pad as isize;
154 patch[i] =
155 if sy >= 0 && sy < h as isize && sx >= 0 && sx < w as isize {
156 img[sy as usize * w + sx as usize]
157 } else {
158 0.0
159 };
160 i += 1;
161 }
162 }
163 }
164 }
165 }
166 crate::fcd_ops::gemm_nt(
167 &cols[..hw_band * ick2],
168 &self.w,
169 &mut yt[..hw_band * self.oc],
170 hw_band,
171 ick2,
172 self.oc,
173 None,
174 );
175 for o in 0..self.oc {
177 let b = self.b[o];
178 let dst = &mut out[o * h * w + y0 * w..][..hw_band];
179 for (p, d) in dst.iter_mut().enumerate() {
180 *d = yt[p * self.oc + o] + b;
181 }
182 }
183 y0 += rows;
184 }
185 out
186 }
187}
188
189pub struct GroupNorm {
191 pub g: usize,
192 pub w: Vec<f32>,
193 pub b: Vec<f32>,
194}
195
196impl GroupNorm {
197 fn from(w: &StTensor, b: &StTensor, groups: usize) -> Self {
198 Self {
199 g: groups,
200 w: w.data.clone(),
201 b: b.data.clone(),
202 }
203 }
204
205 pub fn apply(&self, x: &mut [f32], h: usize, w: usize) {
206 let c = self.w.len();
207 let per = c / self.g;
208 let hw = h * w;
209 std::thread::scope(|s| {
212 for (gi, span) in x.chunks_mut(per * hw).enumerate() {
213 let (wref, bref) = (&self.w, &self.b);
214 s.spawn(move || {
215 let n = span.len() as f64;
216 let mean = span.iter().map(|&v| v as f64).sum::<f64>() / n;
217 let var = span.iter().map(|&v| (v as f64 - mean).powi(2)).sum::<f64>() / n;
218 let inv = 1.0 / (var + 1e-6).sqrt();
219 for (ci, ch) in span.chunks_mut(hw).enumerate() {
220 let cc = gi * per + ci;
221 let (sw, sb) = (wref[cc], bref[cc]);
222 for v in ch.iter_mut() {
223 *v = ((*v as f64 - mean) * inv) as f32 * sw + sb;
224 }
225 }
226 });
227 }
228 });
229 }
230}
231
232fn silu(x: &mut [f32]) {
233 let nt = std::thread::available_parallelism()
234 .map(|n| n.get())
235 .unwrap_or(4);
236 let chunk = x.len().div_ceil(nt).max(1 << 14);
237 std::thread::scope(|s| {
238 for part in x.chunks_mut(chunk) {
239 s.spawn(move || {
240 for v in part.iter_mut() {
241 *v /= 1.0 + (-*v).exp();
242 }
243 });
244 }
245 });
246}
247
248fn upsample2x(x: &[f32], c: usize, h: usize, w: usize) -> Vec<f32> {
250 let mut out = vec![0f32; c * 4 * h * w];
251 for ci in 0..c {
252 let src = &x[ci * h * w..(ci + 1) * h * w];
253 let dst = &mut out[ci * 4 * h * w..(ci + 1) * 4 * h * w];
254 for y in 0..2 * h {
255 for xx in 0..2 * w {
256 dst[y * 2 * w + xx] = src[(y / 2) * w + xx / 2];
257 }
258 }
259 }
260 out
261}
262
263struct ResnetBlock {
264 norm1: GroupNorm,
265 conv1: Conv2d,
266 norm2: GroupNorm,
267 conv2: Conv2d,
268 shortcut: Option<Conv2d>,
269}
270
271impl ResnetBlock {
272 fn apply(&self, x: &[f32], h: usize, w: usize) -> Vec<f32> {
273 let mut t = x.to_vec();
274 self.norm1.apply(&mut t, h, w);
275 silu(&mut t);
276 let mut t = self.conv1.apply(&t, h, w);
277 self.norm2.apply(&mut t, h, w);
278 silu(&mut t);
279 let t = self.conv2.apply(&t, h, w);
280 let skip = match &self.shortcut {
281 Some(sc) => sc.apply(x, h, w),
282 None => x.to_vec(),
283 };
284 skip.iter().zip(&t).map(|(a, b)| a + b).collect()
285 }
286}
287
288struct AttnBlock {
290 norm: GroupNorm,
291 q: (Vec<f32>, Vec<f32>), k: (Vec<f32>, Vec<f32>),
293 v: (Vec<f32>, Vec<f32>),
294 out: (Vec<f32>, Vec<f32>),
295 c: usize,
296}
297
298impl AttnBlock {
299 fn proj(w: &[f32], b: &[f32], x: &[f32], hw: usize, c: usize) -> Vec<f32> {
302 let mut y = vec![0f32; hw * c];
303 crate::fcd_ops::gemm_nt(x, w, &mut y, hw, c, c, None);
304 for row in y.chunks_mut(c) {
305 for (v, bb) in row.iter_mut().zip(b) {
306 *v += bb;
307 }
308 }
309 y
310 }
311
312 fn apply(&self, x: &[f32], h: usize, w: usize) -> Vec<f32> {
313 let (c, hw) = (self.c, h * w);
314 let mut n = x.to_vec();
315 self.norm.apply(&mut n, h, w);
316 let mut nt = vec![0f32; hw * c];
318 for ci in 0..c {
319 for p in 0..hw {
320 nt[p * c + ci] = n[ci * hw + p];
321 }
322 }
323 let mut q = Self::proj(&self.q.0, &self.q.1, &nt, hw, c);
324 let k = Self::proj(&self.k.0, &self.k.1, &nt, hw, c);
325 let v = Self::proj(&self.v.0, &self.v.1, &nt, hw, c);
326 let scale = 1.0 / (c as f32).sqrt();
327 for qv in q.iter_mut() {
328 *qv *= scale;
329 }
330 let mut scores = vec![0f32; hw * hw];
332 crate::fcd_ops::gemm_nt(&q, &k, &mut scores, hw, c, hw, None);
333 for row in scores.chunks_mut(hw) {
334 let mx = row.iter().cloned().fold(f32::MIN, f32::max);
335 let mut den = 0f32;
336 for r in row.iter_mut() {
337 *r = (*r - mx).exp();
338 den += *r;
339 }
340 let inv = 1.0 / den;
341 for r in row.iter_mut() {
342 *r *= inv;
343 }
344 }
345 let mut vt = vec![0f32; c * hw];
347 for p in 0..hw {
348 for ci in 0..c {
349 vt[ci * hw + p] = v[p * c + ci];
350 }
351 }
352 let mut ot = vec![0f32; hw * c];
353 crate::fcd_ops::gemm_nt(&scores, &vt, &mut ot, hw, hw, c, None);
354 let o = Self::proj(&self.out.0, &self.out.1, &ot, hw, c);
355 let mut y = x.to_vec();
357 for p in 0..hw {
358 for ci in 0..c {
359 y[ci * hw + p] += o[p * c + ci];
360 }
361 }
362 y
363 }
364}
365
366struct UpBlock {
367 resnets: Vec<ResnetBlock>,
368 upsample: Option<Conv2d>,
369}
370
371pub struct VaeDecoder {
375 conv_in: Conv2d,
376 mid_res1: ResnetBlock,
377 mid_attn: AttnBlock,
378 mid_res2: ResnetBlock,
379 ups: Vec<UpBlock>,
380 norm_out: GroupNorm,
381 conv_out: Conv2d,
382 pub latent_channels: usize,
383 pub scaling_factor: f32,
384 pub shift_factor: f32,
385}
386
387impl VaeDecoder {
388 pub fn load_dir(dir: &Path) -> Result<Self, String> {
391 let cfg: serde_json::Value = serde_json::from_slice(
392 &std::fs::read(dir.join("config.json")).map_err(|e| format!("config.json: {e}"))?,
393 )
394 .map_err(|e| format!("config.json: {e}"))?;
395 let t = read_safetensors(&dir.join("diffusion_pytorch_model.safetensors"))?;
396 Self::from_tensors(t, &cfg)
397 }
398
399 pub fn from_cmf(model: &cortiq_core::CmfModel) -> Result<Self, String> {
402 let cfg: serde_json::Value = serde_json::from_slice(
403 model
404 .tensor_bytes("vae.config_json")
405 .map_err(|e| e.to_string())?,
406 )
407 .map_err(|e| format!("vae.config_json: {e}"))?;
408 let mut t = std::collections::HashMap::new();
409 for entry in model
410 .tensors
411 .iter()
412 .filter(|e| e.name.starts_with("vae.") && e.name != "vae.config_json")
413 {
414 let data = crate::dit::cmf_f32(model, &entry.name)?;
415 t.insert(
416 entry.name["vae.".len()..].to_string(),
417 StTensor {
418 shape: entry.shape.clone(),
419 data,
420 },
421 );
422 }
423 Self::from_tensors(t, &cfg)
424 }
425
426 fn from_tensors(
427 t: std::collections::HashMap<String, StTensor>,
428 cfg: &serde_json::Value,
429 ) -> Result<Self, String> {
430 let groups = cfg["norm_num_groups"].as_u64().unwrap_or(32) as usize;
431 let get = |n: &str| -> Result<&StTensor, String> {
432 t.get(n).ok_or_else(|| format!("missing tensor {n}"))
433 };
434 let conv = |n: &str| -> Result<Conv2d, String> {
435 Ok(Conv2d::from(
436 get(&format!("{n}.weight"))?,
437 get(&format!("{n}.bias"))?,
438 ))
439 };
440 let gnorm = |n: &str| -> Result<GroupNorm, String> {
441 Ok(GroupNorm::from(
442 get(&format!("{n}.weight"))?,
443 get(&format!("{n}.bias"))?,
444 groups,
445 ))
446 };
447 let resnet = |n: &str| -> Result<ResnetBlock, String> {
448 Ok(ResnetBlock {
449 norm1: gnorm(&format!("{n}.norm1"))?,
450 conv1: conv(&format!("{n}.conv1"))?,
451 norm2: gnorm(&format!("{n}.norm2"))?,
452 conv2: conv(&format!("{n}.conv2"))?,
453 shortcut: if t.contains_key(&format!("{n}.conv_shortcut.weight")) {
454 Some(conv(&format!("{n}.conv_shortcut"))?)
455 } else {
456 None
457 },
458 })
459 };
460 let lin = |n: &str| -> Result<(Vec<f32>, Vec<f32>), String> {
463 Ok((
464 get(&format!("{n}.weight"))?.data.clone(),
465 get(&format!("{n}.bias"))?.data.clone(),
466 ))
467 };
468 let attn_c = get("decoder.mid_block.attentions.0.to_q.weight")?.shape[0];
469 let mid_attn = AttnBlock {
470 norm: gnorm("decoder.mid_block.attentions.0.group_norm")?,
471 q: lin("decoder.mid_block.attentions.0.to_q")?,
472 k: lin("decoder.mid_block.attentions.0.to_k")?,
473 v: lin("decoder.mid_block.attentions.0.to_v")?,
474 out: lin("decoder.mid_block.attentions.0.to_out.0")?,
475 c: attn_c,
476 };
477 let mut ups = Vec::new();
478 for b in 0.. {
479 if !t.contains_key(&format!("decoder.up_blocks.{b}.resnets.0.conv1.weight")) {
480 break;
481 }
482 let mut resnets = Vec::new();
483 for r in 0.. {
484 let n = format!("decoder.up_blocks.{b}.resnets.{r}");
485 if !t.contains_key(&format!("{n}.conv1.weight")) {
486 break;
487 }
488 resnets.push(resnet(&n)?);
489 }
490 let upsample =
491 if t.contains_key(&format!("decoder.up_blocks.{b}.upsamplers.0.conv.weight")) {
492 Some(conv(&format!("decoder.up_blocks.{b}.upsamplers.0.conv"))?)
493 } else {
494 None
495 };
496 ups.push(UpBlock { resnets, upsample });
497 }
498 Ok(Self {
499 conv_in: conv("decoder.conv_in")?,
500 mid_res1: resnet("decoder.mid_block.resnets.0")?,
501 mid_attn,
502 mid_res2: resnet("decoder.mid_block.resnets.1")?,
503 ups,
504 norm_out: gnorm("decoder.conv_norm_out")?,
505 conv_out: conv("decoder.conv_out")?,
506 latent_channels: cfg["latent_channels"].as_u64().unwrap_or(16) as usize,
507 scaling_factor: cfg["scaling_factor"].as_f64().unwrap_or(1.0) as f32,
508 shift_factor: cfg["shift_factor"].as_f64().unwrap_or(0.0) as f32,
509 })
510 }
511
512 pub fn decode(&self, z: &[f32], h: usize, w: usize) -> Vec<f32> {
515 let prof = std::env::var("CMF_VAE_PROF").is_ok();
516 macro_rules! stage {
517 ($name:expr, $e:expr) => {{
518 let t = std::time::Instant::now();
519 let r = $e;
520 if prof {
521 eprintln!("vae {}: {:.2}s", $name, t.elapsed().as_secs_f64());
522 }
523 r
524 }};
525 }
526 let z: Vec<f32> = z
527 .iter()
528 .map(|&v| v / self.scaling_factor + self.shift_factor)
529 .collect();
530 let mut x = stage!("conv_in", self.conv_in.apply(&z, h, w));
531 x = stage!("mid_res1", self.mid_res1.apply(&x, h, w));
532 x = stage!("mid_attn", self.mid_attn.apply(&x, h, w));
533 x = stage!("mid_res2", self.mid_res2.apply(&x, h, w));
534 let (mut h, mut w) = (h, w);
535 for (ui, up) in self.ups.iter().enumerate() {
536 for (ri, r) in up.resnets.iter().enumerate() {
537 x = stage!(format!("up{ui}.res{ri} ({h}x{w})"), r.apply(&x, h, w));
538 }
539 if let Some(upc) = &up.upsample {
540 let c = upc.ic;
541 x = upsample2x(&x, c, h, w);
542 h *= 2;
543 w *= 2;
544 x = stage!(format!("up{ui}.conv ({h}x{w})"), upc.apply(&x, h, w));
545 }
546 }
547 self.norm_out.apply(&mut x, h, w);
548 silu(&mut x);
549 stage!("conv_out", self.conv_out.apply(&x, h, w))
550 }
551}
552
553#[cfg(test)]
554mod tests {
555 use super::*;
556
557 #[test]
559 fn conv2d_matches_hand_reference() {
560 let c = Conv2d {
563 w: vec![1.0; 9],
564 b: vec![0.5],
565 oc: 1,
566 ic: 1,
567 k: 3,
568 };
569 let x = vec![1., 2., 3., 4., 5., 6., 7., 8., 9.];
570 let y = c.apply(&x, 3, 3);
571 assert_eq!(y[4], 45.5);
573 assert_eq!(y[0], 12.5);
574 assert_eq!(y[8], 5. + 6. + 8. + 9. + 0.5);
575 }
576
577 #[test]
580 fn group_norm_standardizes() {
581 let gn = GroupNorm {
582 g: 2,
583 w: vec![2.0, 1.0],
584 b: vec![0.0, 3.0],
585 };
586 let mut x = vec![1., 3., 5., 7., 10., 10., 10., 10.];
587 gn.apply(&mut x, 2, 2);
588 let s = 5f64.sqrt();
590 assert!((x[0] as f64 - (-3.0 / s * 2.0)).abs() < 1e-5);
591 assert!((x[4] - 3.0).abs() < 1e-4);
593 }
594
595 #[test]
597 fn upsample_nearest() {
598 let x = vec![1., 2., 3., 4.];
599 let y = upsample2x(&x, 1, 2, 2);
600 assert_eq!(
601 y,
602 vec![
603 1., 1., 2., 2., 1., 1., 2., 2., 3., 3., 4., 4., 3., 3., 4., 4.,
604 ]
605 );
606 }
607}