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