use crate::math::silu;
use crate::vae::error::{VaeError, VaeResult};
pub fn bn_denorm(
x: &[f32],
mean: &[f32],
var: &[f32],
c: usize,
h: usize,
w: usize,
eps: f32,
) -> VaeResult<Vec<f32>> {
let hw = h * w;
if x.len() != c * hw {
return Err(VaeError::Shape(format!(
"bn_denorm input len {} != C*H*W {}",
x.len(),
c * hw
)));
}
if mean.len() != c || var.len() != c {
return Err(VaeError::Shape(format!(
"bn_denorm mean/var len ({}/{}) != C {c}",
mean.len(),
var.len()
)));
}
let mut out = vec![0.0f32; c * hw];
for ci in 0..c {
let std = (var[ci] + eps).sqrt();
let m = mean[ci];
let src = &x[ci * hw..(ci + 1) * hw];
let dst = &mut out[ci * hw..(ci + 1) * hw];
for (d, &s) in dst.iter_mut().zip(src.iter()) {
*d = s * std + m;
}
}
Ok(out)
}
pub fn unpatchify(x: &[f32], c: usize, h: usize, w: usize) -> VaeResult<UnpatchOut> {
if c % 4 != 0 {
return Err(VaeError::Shape(format!(
"unpatchify channels {c} not a multiple of 4"
)));
}
let hw = h * w;
if x.len() != c * hw {
return Err(VaeError::Shape(format!(
"unpatchify input len {} != C*H*W {}",
x.len(),
c * hw
)));
}
let c_out = c / 4;
let h_out = h * 2;
let w_out = w * 2;
let mut out = vec![0.0f32; c_out * h_out * w_out];
for a in 0..c_out {
for ho in 0..h_out {
let hh = ho / 2;
let b = ho % 2;
for wo in 0..w_out {
let ww = wo / 2;
let d = wo % 2;
let c_in = a * 4 + b * 2 + d;
out[(a * h_out + ho) * w_out + wo] = x[(c_in * h + hh) * w + ww];
}
}
}
Ok(UnpatchOut {
data: out,
c: c_out,
h: h_out,
w: w_out,
})
}
pub struct UnpatchOut {
pub data: Vec<f32>,
pub c: usize,
pub h: usize,
pub w: usize,
}
pub fn upsample_nearest2x(x: &[f32], c: usize, h: usize, w: usize) -> VaeResult<UnpatchOut> {
let hw = h * w;
if x.len() != c * hw {
return Err(VaeError::Shape(format!(
"upsample input len {} != C*H*W {}",
x.len(),
c * hw
)));
}
#[cfg(all(feature = "metal", target_os = "macos"))]
{
if crate::vae::gpu::vae_gpu_enabled() {
if let Ok(up) = crate::vae::gpu::upsample_gpu(x, c, h, w) {
return Ok(UnpatchOut {
data: up.data,
c,
h: up.h,
w: up.w,
});
}
}
}
#[cfg(all(
feature = "native-cuda",
any(target_os = "linux", target_os = "windows")
))]
{
if crate::vae::cuda_gpu::vae_gpu_enabled() {
if let Ok(up) = crate::vae::cuda_gpu::upsample_gpu(x, c, h, w) {
return Ok(UnpatchOut {
data: up.data,
c,
h: up.h,
w: up.w,
});
}
}
}
let h_out = h * 2;
let w_out = w * 2;
let mut out = vec![0.0f32; c * h_out * w_out];
for ci in 0..c {
let src = &x[ci * hw..(ci + 1) * hw];
let dst = &mut out[ci * h_out * w_out..(ci + 1) * h_out * w_out];
for ho in 0..h_out {
let hh = ho / 2;
for wo in 0..w_out {
let ww = wo / 2;
dst[ho * w_out + wo] = src[hh * w + ww];
}
}
}
Ok(UnpatchOut {
data: out,
c,
h: h_out,
w: w_out,
})
}
pub fn silu_inplace(x: &mut [f32]) {
#[cfg(all(feature = "metal", target_os = "macos"))]
{
if crate::vae::gpu::vae_gpu_enabled() && crate::vae::gpu::silu_gpu(x).is_ok() {
return;
}
}
#[cfg(all(
feature = "native-cuda",
any(target_os = "linux", target_os = "windows")
))]
{
if crate::vae::cuda_gpu::vae_gpu_enabled() && crate::vae::cuda_gpu::silu_gpu(x).is_ok() {
return;
}
}
for v in x.iter_mut() {
*v = silu(*v);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bn_denorm_matches_formula() {
let x = vec![1.0, -1.0, 2.0, 0.0]; let mean = vec![10.0, -5.0];
let var = vec![3.0, 0.0];
let out = bn_denorm(&x, &mean, &var, 2, 1, 2, 1.0).expect("bn");
assert_eq!(out, vec![12.0, 8.0, -3.0, -5.0]);
}
#[test]
fn upsample_repeats_2x() {
let x = vec![1.0, 2.0];
let up = upsample_nearest2x(&x, 1, 1, 2).expect("up");
assert_eq!((up.c, up.h, up.w), (1, 2, 4));
assert_eq!(up.data, vec![1.0, 1.0, 2.0, 2.0, 1.0, 1.0, 2.0, 2.0]);
}
#[test]
fn unpatchify_inverts_patch_layout() {
let x = vec![10.0, 11.0, 12.0, 13.0];
let out = unpatchify(&x, 4, 1, 1).expect("unpatch");
assert_eq!((out.c, out.h, out.w), (1, 2, 2));
assert_eq!(out.data, vec![10.0, 11.0, 12.0, 13.0]);
}
#[test]
fn unpatchify_then_dims_double() {
let x = vec![0.0f32; 128 * 32 * 32];
let out = unpatchify(&x, 128, 32, 32).expect("unpatch");
assert_eq!((out.c, out.h, out.w), (32, 64, 64));
}
}