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 uid: u64,
510}
511
512#[derive(Clone, Copy)]
517pub struct VaeConvRef<'a> {
518 pub w: &'a [f32],
519 pub b: &'a [f32],
520 pub oc: usize,
521 pub ic: usize,
522 pub k: usize,
523}
524
525#[derive(Clone, Copy)]
527pub struct VaeNormRef<'a> {
528 pub w: &'a [f32],
529 pub b: &'a [f32],
530 pub groups: usize,
531}
532
533#[derive(Clone, Copy)]
536pub struct VaeResnetRef<'a> {
537 pub norm1: VaeNormRef<'a>,
538 pub conv1: VaeConvRef<'a>,
539 pub norm2: VaeNormRef<'a>,
540 pub conv2: VaeConvRef<'a>,
541 pub shortcut: Option<VaeConvRef<'a>>,
542}
543
544#[derive(Clone, Copy)]
548pub struct VaeAttnRef<'a> {
549 pub norm: VaeNormRef<'a>,
550 pub q: (&'a [f32], &'a [f32]),
551 pub k: (&'a [f32], &'a [f32]),
552 pub v: (&'a [f32], &'a [f32]),
553 pub out: (&'a [f32], &'a [f32]),
554 pub c: usize,
555}
556
557pub struct VaeUpRef<'a> {
560 pub resnets: Vec<VaeResnetRef<'a>>,
561 pub upsample: Option<VaeConvRef<'a>>,
562}
563
564pub struct VaeChainArgs<'a> {
570 pub key: u64,
571 pub conv_in: VaeConvRef<'a>,
572 pub mid_res1: VaeResnetRef<'a>,
573 pub mid_attn: VaeAttnRef<'a>,
574 pub mid_res2: VaeResnetRef<'a>,
575 pub ups: Vec<VaeUpRef<'a>>,
576 pub norm_out: VaeNormRef<'a>,
577 pub conv_out: VaeConvRef<'a>,
578 pub latent_channels: usize,
579 pub scaling_factor: f32,
580 pub shift_factor: f32,
581}
582
583impl Conv2d {
584 fn chain_ref(&self) -> VaeConvRef<'_> {
585 VaeConvRef {
586 w: &self.w,
587 b: &self.b,
588 oc: self.oc,
589 ic: self.ic,
590 k: self.k,
591 }
592 }
593}
594
595impl GroupNorm {
596 fn chain_ref(&self) -> VaeNormRef<'_> {
597 VaeNormRef {
598 w: &self.w,
599 b: &self.b,
600 groups: self.g,
601 }
602 }
603}
604
605impl ResnetBlock {
606 fn chain_ref(&self) -> VaeResnetRef<'_> {
607 VaeResnetRef {
608 norm1: self.norm1.chain_ref(),
609 conv1: self.conv1.chain_ref(),
610 norm2: self.norm2.chain_ref(),
611 conv2: self.conv2.chain_ref(),
612 shortcut: self.shortcut.as_ref().map(Conv2d::chain_ref),
613 }
614 }
615}
616
617static VAE_UID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
618
619impl VaeDecoder {
620 pub fn chain_args(&self) -> VaeChainArgs<'_> {
622 let a = &self.mid_attn;
623 VaeChainArgs {
624 key: self.uid,
625 conv_in: self.conv_in.chain_ref(),
626 mid_res1: self.mid_res1.chain_ref(),
627 mid_attn: VaeAttnRef {
628 norm: a.norm.chain_ref(),
629 q: (&a.q.0, &a.q.1),
630 k: (&a.k.0, &a.k.1),
631 v: (&a.v.0, &a.v.1),
632 out: (&a.out.0, &a.out.1),
633 c: a.c,
634 },
635 mid_res2: self.mid_res2.chain_ref(),
636 ups: self
637 .ups
638 .iter()
639 .map(|u| VaeUpRef {
640 resnets: u.resnets.iter().map(ResnetBlock::chain_ref).collect(),
641 upsample: u.upsample.as_ref().map(Conv2d::chain_ref),
642 })
643 .collect(),
644 norm_out: self.norm_out.chain_ref(),
645 conv_out: self.conv_out.chain_ref(),
646 latent_channels: self.latent_channels,
647 scaling_factor: self.scaling_factor,
648 shift_factor: self.shift_factor,
649 }
650 }
651
652 pub fn decode_fast(&self, z: &[f32], h: usize, w: usize) -> Vec<f32> {
657 if crate::gpu::enabled_here() {
658 let zin: Vec<f32> = z
659 .iter()
660 .map(|&v| v / self.scaling_factor + self.shift_factor)
661 .collect();
662 let mut out = vec![0f32; 3 * 64 * h * w];
663 if crate::gpu::vae_decode_chain(&self.chain_args(), &zin, h, w, &mut out) {
664 return out;
665 }
666 }
667 self.decode(z, h, w)
668 }
669}
670
671impl VaeDecoder {
672 pub fn load_dir(dir: &Path) -> Result<Self, String> {
675 let cfg: serde_json::Value = serde_json::from_slice(
676 &std::fs::read(dir.join("config.json")).map_err(|e| format!("config.json: {e}"))?,
677 )
678 .map_err(|e| format!("config.json: {e}"))?;
679 let t = read_safetensors(&dir.join("diffusion_pytorch_model.safetensors"))?;
680 Self::from_tensors(t, &cfg)
681 }
682
683 pub fn from_cmf(model: &cortiq_core::CmfModel) -> Result<Self, String> {
686 let cfg: serde_json::Value = serde_json::from_slice(
687 model
688 .tensor_bytes("vae.config_json")
689 .map_err(|e| e.to_string())?,
690 )
691 .map_err(|e| format!("vae.config_json: {e}"))?;
692 let mut t = std::collections::HashMap::new();
693 for entry in model
694 .tensors
695 .iter()
696 .filter(|e| e.name.starts_with("vae.") && e.name != "vae.config_json")
697 {
698 let data = crate::dit::cmf_f32(model, &entry.name)?;
699 t.insert(
700 entry.name["vae.".len()..].to_string(),
701 StTensor {
702 shape: entry.shape.clone(),
703 data,
704 },
705 );
706 }
707 Self::from_tensors(t, &cfg)
708 }
709
710 fn from_tensors(
711 t: std::collections::HashMap<String, StTensor>,
712 cfg: &serde_json::Value,
713 ) -> Result<Self, String> {
714 let groups = cfg["norm_num_groups"].as_u64().unwrap_or(32) as usize;
715 let get = |n: &str| -> Result<&StTensor, String> {
716 t.get(n).ok_or_else(|| format!("missing tensor {n}"))
717 };
718 let conv = |n: &str| -> Result<Conv2d, String> {
719 Ok(Conv2d::from(
720 get(&format!("{n}.weight"))?,
721 get(&format!("{n}.bias"))?,
722 ))
723 };
724 let gnorm = |n: &str| -> Result<GroupNorm, String> {
725 Ok(GroupNorm::from(
726 get(&format!("{n}.weight"))?,
727 get(&format!("{n}.bias"))?,
728 groups,
729 ))
730 };
731 let resnet = |n: &str| -> Result<ResnetBlock, String> {
732 Ok(ResnetBlock {
733 norm1: gnorm(&format!("{n}.norm1"))?,
734 conv1: conv(&format!("{n}.conv1"))?,
735 norm2: gnorm(&format!("{n}.norm2"))?,
736 conv2: conv(&format!("{n}.conv2"))?,
737 shortcut: if t.contains_key(&format!("{n}.conv_shortcut.weight")) {
738 Some(conv(&format!("{n}.conv_shortcut"))?)
739 } else {
740 None
741 },
742 })
743 };
744 let lin = |n: &str| -> Result<(Vec<f32>, Vec<f32>), String> {
747 Ok((
748 get(&format!("{n}.weight"))?.data.clone(),
749 get(&format!("{n}.bias"))?.data.clone(),
750 ))
751 };
752 let attn_c = get("decoder.mid_block.attentions.0.to_q.weight")?.shape[0];
753 let mid_attn = AttnBlock {
754 norm: gnorm("decoder.mid_block.attentions.0.group_norm")?,
755 q: lin("decoder.mid_block.attentions.0.to_q")?,
756 k: lin("decoder.mid_block.attentions.0.to_k")?,
757 v: lin("decoder.mid_block.attentions.0.to_v")?,
758 out: lin("decoder.mid_block.attentions.0.to_out.0")?,
759 c: attn_c,
760 };
761 let mut ups = Vec::new();
762 for b in 0.. {
763 if !t.contains_key(&format!("decoder.up_blocks.{b}.resnets.0.conv1.weight")) {
764 break;
765 }
766 let mut resnets = Vec::new();
767 for r in 0.. {
768 let n = format!("decoder.up_blocks.{b}.resnets.{r}");
769 if !t.contains_key(&format!("{n}.conv1.weight")) {
770 break;
771 }
772 resnets.push(resnet(&n)?);
773 }
774 let upsample =
775 if t.contains_key(&format!("decoder.up_blocks.{b}.upsamplers.0.conv.weight")) {
776 Some(conv(&format!("decoder.up_blocks.{b}.upsamplers.0.conv"))?)
777 } else {
778 None
779 };
780 ups.push(UpBlock { resnets, upsample });
781 }
782 Ok(Self {
783 conv_in: conv("decoder.conv_in")?,
784 mid_res1: resnet("decoder.mid_block.resnets.0")?,
785 mid_attn,
786 mid_res2: resnet("decoder.mid_block.resnets.1")?,
787 ups,
788 norm_out: gnorm("decoder.conv_norm_out")?,
789 conv_out: conv("decoder.conv_out")?,
790 latent_channels: cfg["latent_channels"].as_u64().unwrap_or(16) as usize,
791 scaling_factor: cfg["scaling_factor"].as_f64().unwrap_or(1.0) as f32,
792 shift_factor: cfg["shift_factor"].as_f64().unwrap_or(0.0) as f32,
793 uid: VAE_UID.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
794 })
795 }
796
797 pub fn decode(&self, z: &[f32], h: usize, w: usize) -> Vec<f32> {
800 let prof = std::env::var("CMF_VAE_PROF").is_ok();
801 macro_rules! stage {
802 ($name:expr, $e:expr) => {{
803 let t = std::time::Instant::now();
804 let r = $e;
805 if prof {
806 eprintln!("vae {}: {:.2}s", $name, t.elapsed().as_secs_f64());
807 }
808 r
809 }};
810 }
811 let z: Vec<f32> = z
812 .iter()
813 .map(|&v| v / self.scaling_factor + self.shift_factor)
814 .collect();
815 let mut x = stage!("conv_in", self.conv_in.apply(&z, h, w));
816 x = stage!("mid_res1", self.mid_res1.apply(&x, h, w));
817 x = stage!("mid_attn", self.mid_attn.apply(&x, h, w));
818 x = stage!("mid_res2", self.mid_res2.apply(&x, h, w));
819 let (mut h, mut w) = (h, w);
820 for (ui, up) in self.ups.iter().enumerate() {
821 for (ri, r) in up.resnets.iter().enumerate() {
822 x = stage!(format!("up{ui}.res{ri} ({h}x{w})"), r.apply(&x, h, w));
823 }
824 if let Some(upc) = &up.upsample {
825 let c = upc.ic;
826 let (h2, w2) = (h * 2, w * 2);
827 x = stage!(format!("up{ui}.conv ({h2}x{w2})"), {
828 let mut fused = None;
831 if h2 * w2 * upc.ic * upc.oc >= 1 << 26 && crate::gpu::enabled_here() {
832 let mut o = vec![0f32; upc.oc * h2 * w2];
833 if crate::gpu::vae_upsample_conv(
834 &upc.w, &upc.b, &x, upc.ic, upc.oc, h, w, upc.k, &mut o,
835 ) {
836 fused = Some(o);
837 }
838 }
839 match fused {
840 Some(o) => o,
841 None => {
842 let xu = upsample2x(&x, c, h, w);
843 upc.apply(&xu, h2, w2)
844 }
845 }
846 });
847 h = h2;
848 w = w2;
849 }
850 }
851 self.norm_out.apply(&mut x, h, w);
852 silu(&mut x);
853 stage!("conv_out", self.conv_out.apply(&x, h, w))
854 }
855}
856
857#[cfg(test)]
858mod tests {
859 use super::*;
860
861 #[test]
863 fn conv2d_matches_hand_reference() {
864 let c = Conv2d {
867 w: vec![1.0; 9],
868 b: vec![0.5],
869 oc: 1,
870 ic: 1,
871 k: 3,
872 };
873 let x = vec![1., 2., 3., 4., 5., 6., 7., 8., 9.];
874 let y = c.apply(&x, 3, 3);
875 assert_eq!(y[4], 45.5);
877 assert_eq!(y[0], 12.5);
878 assert_eq!(y[8], 5. + 6. + 8. + 9. + 0.5);
879 }
880
881 #[test]
884 fn group_norm_standardizes() {
885 let gn = GroupNorm {
886 g: 2,
887 w: vec![2.0, 1.0],
888 b: vec![0.0, 3.0],
889 };
890 let mut x = vec![1., 3., 5., 7., 10., 10., 10., 10.];
891 gn.apply(&mut x, 2, 2);
892 let s = 5f64.sqrt();
894 assert!((x[0] as f64 - (-3.0 / s * 2.0)).abs() < 1e-5);
895 assert!((x[4] - 3.0).abs() < 1e-4);
897 }
898
899 #[test]
901 fn upsample_nearest() {
902 let x = vec![1., 2., 3., 4.];
903 let y = upsample2x(&x, 1, 2, 2);
904 assert_eq!(
905 y,
906 vec![
907 1., 1., 2., 2., 1., 1., 2., 2., 3., 3., 4., 4., 3., 3., 4., 4.,
908 ]
909 );
910 }
911}