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 {
127 pub w: Vec<f32>, pub b: Vec<f32>, pub oc: usize,
130 pub ic: usize,
131 pub k: usize,
132}
133
134impl Conv2d {
135 fn from(t: &StTensor, bias: &StTensor) -> Self {
136 let (oc, ic, k) = (t.shape[0], t.shape[1], t.shape[2]);
137 Self {
138 w: t.data.clone(),
139 b: bias.data.clone(),
140 oc,
141 ic,
142 k,
143 }
144 }
145
146 pub fn apply(&self, x: &[f32], h: usize, w: usize) -> Vec<f32> {
154 debug_assert_eq!(x.len(), self.ic * h * w);
155 if h * w * self.ic * self.oc >= 1 << 26 && crate::gpu::enabled_here() {
160 let mut out = vec![0f32; self.oc * h * w];
161 if std::env::var("CMF_VAE_CONV_COOP").as_deref() == Ok("1")
176 && crate::gpu::vae_conv2d_coop(
177 &self.w,
178 Some(&self.b[..]),
179 x,
180 self.ic,
181 self.oc,
182 h,
183 w,
184 self.k,
185 &mut out,
186 )
187 {
188 return out;
189 }
190 if crate::gpu::vae_conv2d(
191 &self.w, &self.b, x, self.ic, self.oc, h, w, self.k, &mut out,
192 ) {
193 return out;
194 }
195 }
196 let pad = self.k / 2;
197 let ick2 = self.ic * self.k * self.k;
198 let mut out = vec![0f32; self.oc * h * w];
199 let band = (128 << 20) / (ick2 * w * 4).max(1);
200 let band = band.clamp(1, h);
201 let mut cols = vec![0f32; band * w * ick2];
202 let mut yt = vec![0f32; band * w * self.oc];
203 let mut y0 = 0usize;
204 while y0 < h {
205 let rows = band.min(h - y0);
206 let hw_band = rows * w;
207 for (dy, colrow) in cols[..hw_band * ick2].chunks_mut(w * ick2).enumerate() {
210 let y = y0 + dy;
211 for (xx, patch) in colrow.chunks_mut(ick2).enumerate() {
212 let mut i = 0;
213 for c in 0..self.ic {
214 let img = &x[c * h * w..(c + 1) * h * w];
215 for ky in 0..self.k {
216 let sy = y as isize + ky as isize - pad as isize;
217 for kx in 0..self.k {
218 let sx = xx as isize + kx as isize - pad as isize;
219 patch[i] =
220 if sy >= 0 && sy < h as isize && sx >= 0 && sx < w as isize {
221 img[sy as usize * w + sx as usize]
222 } else {
223 0.0
224 };
225 i += 1;
226 }
227 }
228 }
229 }
230 }
231 crate::fcd_ops::gemm_nt(
232 &cols[..hw_band * ick2],
233 &self.w,
234 &mut yt[..hw_band * self.oc],
235 hw_band,
236 ick2,
237 self.oc,
238 None,
239 );
240 for o in 0..self.oc {
242 let b = self.b[o];
243 let dst = &mut out[o * h * w + y0 * w..][..hw_band];
244 for (p, d) in dst.iter_mut().enumerate() {
245 *d = yt[p * self.oc + o] + b;
246 }
247 }
248 y0 += rows;
249 }
250 out
251 }
252}
253
254pub struct GroupNorm {
256 pub g: usize,
257 pub w: Vec<f32>,
258 pub b: Vec<f32>,
259}
260
261impl GroupNorm {
262 fn from(w: &StTensor, b: &StTensor, groups: usize) -> Self {
263 Self {
264 g: groups,
265 w: w.data.clone(),
266 b: b.data.clone(),
267 }
268 }
269
270 pub fn apply(&self, x: &mut [f32], h: usize, w: usize) {
271 let c = self.w.len();
272 let per = c / self.g;
273 let hw = h * w;
274 std::thread::scope(|s| {
277 for (gi, span) in x.chunks_mut(per * hw).enumerate() {
278 let (wref, bref) = (&self.w, &self.b);
279 s.spawn(move || {
280 let n = span.len() as f64;
281 let mean = span.iter().map(|&v| v as f64).sum::<f64>() / n;
282 let var = span.iter().map(|&v| (v as f64 - mean).powi(2)).sum::<f64>() / n;
283 let inv = 1.0 / (var + 1e-6).sqrt();
284 for (ci, ch) in span.chunks_mut(hw).enumerate() {
285 let cc = gi * per + ci;
286 let (sw, sb) = (wref[cc], bref[cc]);
287 for v in ch.iter_mut() {
288 *v = ((*v as f64 - mean) * inv) as f32 * sw + sb;
289 }
290 }
291 });
292 }
293 });
294 }
295}
296
297fn silu(x: &mut [f32]) {
298 let nt = std::thread::available_parallelism()
299 .map(|n| n.get())
300 .unwrap_or(4);
301 let chunk = x.len().div_ceil(nt).max(1 << 14);
302 std::thread::scope(|s| {
303 for part in x.chunks_mut(chunk) {
304 s.spawn(move || {
305 for v in part.iter_mut() {
306 *v /= 1.0 + (-*v).exp();
307 }
308 });
309 }
310 });
311}
312
313fn upsample2x(x: &[f32], c: usize, h: usize, w: usize) -> Vec<f32> {
315 let mut out = vec![0f32; c * 4 * h * w];
316 for ci in 0..c {
317 let src = &x[ci * h * w..(ci + 1) * h * w];
318 let dst = &mut out[ci * 4 * h * w..(ci + 1) * 4 * h * w];
319 for y in 0..2 * h {
320 for xx in 0..2 * w {
321 dst[y * 2 * w + xx] = src[(y / 2) * w + xx / 2];
322 }
323 }
324 }
325 out
326}
327
328struct ResnetBlock {
329 norm1: GroupNorm,
330 conv1: Conv2d,
331 norm2: GroupNorm,
332 conv2: Conv2d,
333 shortcut: Option<Conv2d>,
334}
335
336impl ResnetBlock {
337 fn apply(&self, x: &[f32], h: usize, w: usize) -> Vec<f32> {
338 let (ic, oc) = (self.conv1.ic, self.conv1.oc);
339 if h * w * ic * oc >= 1 << 26 && crate::gpu::enabled_here() {
343 let mut out = vec![0f32; oc * h * w];
344 let args = crate::gpu::VaeResnetArgs {
345 groups: self.norm1.g,
346 ic,
347 oc,
348 h,
349 w,
350 n1w: &self.norm1.w,
351 n1b: &self.norm1.b,
352 c1w: &self.conv1.w,
353 c1b: &self.conv1.b,
354 c1k: self.conv1.k,
355 n2w: &self.norm2.w,
356 n2b: &self.norm2.b,
357 c2w: &self.conv2.w,
358 c2b: &self.conv2.b,
359 c2k: self.conv2.k,
360 shortcut: self
361 .shortcut
362 .as_ref()
363 .map(|s| (s.w.as_slice(), s.b.as_slice(), s.k)),
364 };
365 if std::env::var("CMF_VAE_RESNET").as_deref() != Ok("split")
371 && crate::gpu::vae_resnet(&args, x, &mut out)
372 {
373 return out;
374 }
375 }
376 let mut t = x.to_vec();
377 self.norm1.apply(&mut t, h, w);
378 silu(&mut t);
379 let mut t = self.conv1.apply(&t, h, w);
380 self.norm2.apply(&mut t, h, w);
381 silu(&mut t);
382 let t = self.conv2.apply(&t, h, w);
383 let skip = match &self.shortcut {
384 Some(sc) => sc.apply(x, h, w),
385 None => x.to_vec(),
386 };
387 skip.iter().zip(&t).map(|(a, b)| a + b).collect()
388 }
389}
390
391struct AttnBlock {
393 norm: GroupNorm,
394 q: (Vec<f32>, Vec<f32>), k: (Vec<f32>, Vec<f32>),
396 v: (Vec<f32>, Vec<f32>),
397 out: (Vec<f32>, Vec<f32>),
398 c: usize,
399}
400
401impl AttnBlock {
402 fn proj(w: &[f32], b: &[f32], x: &[f32], hw: usize, c: usize) -> Vec<f32> {
405 let mut y = vec![0f32; hw * c];
406 crate::fcd_ops::gemm_nt(x, w, &mut y, hw, c, c, None);
407 for row in y.chunks_mut(c) {
408 for (v, bb) in row.iter_mut().zip(b) {
409 *v += bb;
410 }
411 }
412 y
413 }
414
415 fn apply(&self, x: &[f32], h: usize, w: usize) -> Vec<f32> {
416 let (c, hw) = (self.c, h * w);
417 let mut n = x.to_vec();
418 self.norm.apply(&mut n, h, w);
419 let mut nt = vec![0f32; hw * c];
421 for ci in 0..c {
422 for p in 0..hw {
423 nt[p * c + ci] = n[ci * hw + p];
424 }
425 }
426 let mut q = Self::proj(&self.q.0, &self.q.1, &nt, hw, c);
427 let k = Self::proj(&self.k.0, &self.k.1, &nt, hw, c);
428 let v = Self::proj(&self.v.0, &self.v.1, &nt, hw, c);
429 let scale = 1.0 / (c as f32).sqrt();
430 if std::env::var("CMF_VAE_ATTN").as_deref() != Ok("cpu")
436 && crate::gpu::enabled_here()
437 && hw >= 256
438 {
439 let mut got = vec![0f32; hw * c];
440 if crate::gpu::dit_attention(&q, &k, &v, 1, 1, hw, c, scale, &mut got) {
441 return Self::finish(self, x, &got, h, w);
442 }
443 }
444 for qv in q.iter_mut() {
445 *qv *= scale;
446 }
447 let mut scores = vec![0f32; hw * hw];
449 crate::fcd_ops::gemm_nt(&q, &k, &mut scores, hw, c, hw, None);
450 for row in scores.chunks_mut(hw) {
451 let mx = row.iter().cloned().fold(f32::MIN, f32::max);
452 let mut den = 0f32;
453 for r in row.iter_mut() {
454 *r = (*r - mx).exp();
455 den += *r;
456 }
457 let inv = 1.0 / den;
458 for r in row.iter_mut() {
459 *r *= inv;
460 }
461 }
462 let mut vt = vec![0f32; c * hw];
464 for p in 0..hw {
465 for ci in 0..c {
466 vt[ci * hw + p] = v[p * c + ci];
467 }
468 }
469 let mut ot = vec![0f32; hw * c];
470 crate::fcd_ops::gemm_nt(&scores, &vt, &mut ot, hw, hw, c, None);
471 Self::finish(self, x, &ot, h, w)
472 }
473
474 fn finish(&self, x: &[f32], ot: &[f32], h: usize, w: usize) -> Vec<f32> {
477 let (c, hw) = (self.c, h * w);
478 let o = Self::proj(&self.out.0, &self.out.1, ot, hw, c);
479 let mut y = x.to_vec();
480 for p in 0..hw {
481 for ci in 0..c {
482 y[ci * hw + p] += o[p * c + ci];
483 }
484 }
485 y
486 }
487}
488
489struct UpBlock {
490 resnets: Vec<ResnetBlock>,
491 upsample: Option<Conv2d>,
492}
493
494pub struct VaeDecoder {
498 conv_in: Conv2d,
499 mid_res1: ResnetBlock,
500 mid_attn: AttnBlock,
501 mid_res2: ResnetBlock,
502 ups: Vec<UpBlock>,
503 norm_out: GroupNorm,
504 conv_out: Conv2d,
505 pub latent_channels: usize,
506 pub scaling_factor: f32,
507 pub shift_factor: f32,
508}
509
510impl VaeDecoder {
511 pub fn load_dir(dir: &Path) -> Result<Self, String> {
514 let cfg: serde_json::Value = serde_json::from_slice(
515 &std::fs::read(dir.join("config.json")).map_err(|e| format!("config.json: {e}"))?,
516 )
517 .map_err(|e| format!("config.json: {e}"))?;
518 let t = read_safetensors(&dir.join("diffusion_pytorch_model.safetensors"))?;
519 Self::from_tensors(t, &cfg)
520 }
521
522 pub fn from_cmf(model: &cortiq_core::CmfModel) -> Result<Self, String> {
525 let cfg: serde_json::Value = serde_json::from_slice(
526 model
527 .tensor_bytes("vae.config_json")
528 .map_err(|e| e.to_string())?,
529 )
530 .map_err(|e| format!("vae.config_json: {e}"))?;
531 let mut t = std::collections::HashMap::new();
532 for entry in model
533 .tensors
534 .iter()
535 .filter(|e| e.name.starts_with("vae.") && e.name != "vae.config_json")
536 {
537 let data = crate::dit::cmf_f32(model, &entry.name)?;
538 t.insert(
539 entry.name["vae.".len()..].to_string(),
540 StTensor {
541 shape: entry.shape.clone(),
542 data,
543 },
544 );
545 }
546 Self::from_tensors(t, &cfg)
547 }
548
549 fn from_tensors(
550 t: std::collections::HashMap<String, StTensor>,
551 cfg: &serde_json::Value,
552 ) -> Result<Self, String> {
553 let groups = cfg["norm_num_groups"].as_u64().unwrap_or(32) as usize;
554 let get = |n: &str| -> Result<&StTensor, String> {
555 t.get(n).ok_or_else(|| format!("missing tensor {n}"))
556 };
557 let conv = |n: &str| -> Result<Conv2d, String> {
558 Ok(Conv2d::from(
559 get(&format!("{n}.weight"))?,
560 get(&format!("{n}.bias"))?,
561 ))
562 };
563 let gnorm = |n: &str| -> Result<GroupNorm, String> {
564 Ok(GroupNorm::from(
565 get(&format!("{n}.weight"))?,
566 get(&format!("{n}.bias"))?,
567 groups,
568 ))
569 };
570 let resnet = |n: &str| -> Result<ResnetBlock, String> {
571 Ok(ResnetBlock {
572 norm1: gnorm(&format!("{n}.norm1"))?,
573 conv1: conv(&format!("{n}.conv1"))?,
574 norm2: gnorm(&format!("{n}.norm2"))?,
575 conv2: conv(&format!("{n}.conv2"))?,
576 shortcut: if t.contains_key(&format!("{n}.conv_shortcut.weight")) {
577 Some(conv(&format!("{n}.conv_shortcut"))?)
578 } else {
579 None
580 },
581 })
582 };
583 let lin = |n: &str| -> Result<(Vec<f32>, Vec<f32>), String> {
586 Ok((
587 get(&format!("{n}.weight"))?.data.clone(),
588 get(&format!("{n}.bias"))?.data.clone(),
589 ))
590 };
591 let attn_c = get("decoder.mid_block.attentions.0.to_q.weight")?.shape[0];
592 let mid_attn = AttnBlock {
593 norm: gnorm("decoder.mid_block.attentions.0.group_norm")?,
594 q: lin("decoder.mid_block.attentions.0.to_q")?,
595 k: lin("decoder.mid_block.attentions.0.to_k")?,
596 v: lin("decoder.mid_block.attentions.0.to_v")?,
597 out: lin("decoder.mid_block.attentions.0.to_out.0")?,
598 c: attn_c,
599 };
600 let mut ups = Vec::new();
601 for b in 0.. {
602 if !t.contains_key(&format!("decoder.up_blocks.{b}.resnets.0.conv1.weight")) {
603 break;
604 }
605 let mut resnets = Vec::new();
606 for r in 0.. {
607 let n = format!("decoder.up_blocks.{b}.resnets.{r}");
608 if !t.contains_key(&format!("{n}.conv1.weight")) {
609 break;
610 }
611 resnets.push(resnet(&n)?);
612 }
613 let upsample =
614 if t.contains_key(&format!("decoder.up_blocks.{b}.upsamplers.0.conv.weight")) {
615 Some(conv(&format!("decoder.up_blocks.{b}.upsamplers.0.conv"))?)
616 } else {
617 None
618 };
619 ups.push(UpBlock { resnets, upsample });
620 }
621 Ok(Self {
622 conv_in: conv("decoder.conv_in")?,
623 mid_res1: resnet("decoder.mid_block.resnets.0")?,
624 mid_attn,
625 mid_res2: resnet("decoder.mid_block.resnets.1")?,
626 ups,
627 norm_out: gnorm("decoder.conv_norm_out")?,
628 conv_out: conv("decoder.conv_out")?,
629 latent_channels: cfg["latent_channels"].as_u64().unwrap_or(16) as usize,
630 scaling_factor: cfg["scaling_factor"].as_f64().unwrap_or(1.0) as f32,
631 shift_factor: cfg["shift_factor"].as_f64().unwrap_or(0.0) as f32,
632 })
633 }
634
635 pub fn decode(&self, z: &[f32], h: usize, w: usize) -> Vec<f32> {
638 let prof = std::env::var("CMF_VAE_PROF").is_ok();
639 macro_rules! stage {
640 ($name:expr, $e:expr) => {{
641 let t = std::time::Instant::now();
642 let r = $e;
643 if prof {
644 eprintln!("vae {}: {:.2}s", $name, t.elapsed().as_secs_f64());
645 }
646 r
647 }};
648 }
649 let z: Vec<f32> = z
650 .iter()
651 .map(|&v| v / self.scaling_factor + self.shift_factor)
652 .collect();
653 let mut x = stage!("conv_in", self.conv_in.apply(&z, h, w));
654 x = stage!("mid_res1", self.mid_res1.apply(&x, h, w));
655 x = stage!("mid_attn", self.mid_attn.apply(&x, h, w));
656 x = stage!("mid_res2", self.mid_res2.apply(&x, h, w));
657 let (mut h, mut w) = (h, w);
658 for (ui, up) in self.ups.iter().enumerate() {
659 for (ri, r) in up.resnets.iter().enumerate() {
660 x = stage!(format!("up{ui}.res{ri} ({h}x{w})"), r.apply(&x, h, w));
661 }
662 if let Some(upc) = &up.upsample {
663 let c = upc.ic;
664 let (h2, w2) = (h * 2, w * 2);
665 x = stage!(format!("up{ui}.conv ({h2}x{w2})"), {
666 let mut fused = None;
669 if h2 * w2 * upc.ic * upc.oc >= 1 << 26 && crate::gpu::enabled_here() {
670 let mut o = vec![0f32; upc.oc * h2 * w2];
671 if crate::gpu::vae_upsample_conv(
672 &upc.w, &upc.b, &x, upc.ic, upc.oc, h, w, upc.k, &mut o,
673 ) {
674 fused = Some(o);
675 }
676 }
677 match fused {
678 Some(o) => o,
679 None => {
680 let xu = upsample2x(&x, c, h, w);
681 upc.apply(&xu, h2, w2)
682 }
683 }
684 });
685 h = h2;
686 w = w2;
687 }
688 }
689 self.norm_out.apply(&mut x, h, w);
690 silu(&mut x);
691 stage!("conv_out", self.conv_out.apply(&x, h, w))
692 }
693}
694
695#[cfg(test)]
696mod tests {
697 use super::*;
698
699 #[test]
701 fn conv2d_matches_hand_reference() {
702 let c = Conv2d {
705 w: vec![1.0; 9],
706 b: vec![0.5],
707 oc: 1,
708 ic: 1,
709 k: 3,
710 };
711 let x = vec![1., 2., 3., 4., 5., 6., 7., 8., 9.];
712 let y = c.apply(&x, 3, 3);
713 assert_eq!(y[4], 45.5);
715 assert_eq!(y[0], 12.5);
716 assert_eq!(y[8], 5. + 6. + 8. + 9. + 0.5);
717 }
718
719 #[test]
722 fn group_norm_standardizes() {
723 let gn = GroupNorm {
724 g: 2,
725 w: vec![2.0, 1.0],
726 b: vec![0.0, 3.0],
727 };
728 let mut x = vec![1., 3., 5., 7., 10., 10., 10., 10.];
729 gn.apply(&mut x, 2, 2);
730 let s = 5f64.sqrt();
732 assert!((x[0] as f64 - (-3.0 / s * 2.0)).abs() < 1e-5);
733 assert!((x[4] - 3.0).abs() < 1e-4);
735 }
736
737 #[test]
739 fn upsample_nearest() {
740 let x = vec![1., 2., 3., 4.];
741 let y = upsample2x(&x, 1, 2, 2);
742 assert_eq!(
743 y,
744 vec![
745 1., 1., 2., 2., 1., 1., 2., 2., 3., 3., 4., 4., 3., 3., 4., 4.,
746 ]
747 );
748 }
749}