use crate::vae::error::{VaeError, VaeResult};
pub struct GroupNorm {
pub num_groups: usize,
pub weight: Vec<f32>,
pub bias: Vec<f32>,
pub channels: usize,
pub eps: f32,
}
impl GroupNorm {
pub fn new(weight: &[f32], bias: &[f32], num_groups: usize, eps: f32) -> VaeResult<Self> {
if weight.len() != bias.len() {
return Err(VaeError::Shape(format!(
"groupnorm weight len {} != bias len {}",
weight.len(),
bias.len()
)));
}
let channels = weight.len();
if num_groups == 0 || channels % num_groups != 0 {
return Err(VaeError::Shape(format!(
"groupnorm channels {channels} not divisible by num_groups {num_groups}"
)));
}
Ok(Self {
num_groups,
weight: weight.to_vec(),
bias: bias.to_vec(),
channels,
eps,
})
}
pub fn forward_inplace(&self, x: &mut [f32], h: usize, w: usize) -> VaeResult<()> {
let hw = h * w;
if x.len() != self.channels * hw {
return Err(VaeError::Shape(format!(
"groupnorm input len {} != C*H*W {}",
x.len(),
self.channels * hw
)));
}
#[cfg(all(feature = "metal", target_os = "macos"))]
{
if crate::vae::gpu::vae_gpu_enabled()
&& crate::vae::gpu::groupnorm_gpu(
x,
&self.weight,
&self.bias,
self.channels,
hw,
self.num_groups,
self.eps,
)
.is_ok()
{
return Ok(());
}
}
#[cfg(all(
feature = "native-cuda",
any(target_os = "linux", target_os = "windows")
))]
{
if crate::vae::cuda_gpu::vae_gpu_enabled()
&& crate::vae::cuda_gpu::groupnorm_gpu(
x,
&self.weight,
&self.bias,
self.channels,
hw,
self.num_groups,
self.eps,
)
.is_ok()
{
return Ok(());
}
}
let gs = self.channels / self.num_groups; let group_elems = gs * hw;
let inv_n = 1.0f64 / group_elems as f64;
for g in 0..self.num_groups {
let c0 = g * gs;
let base = c0 * hw;
let group = &mut x[base..base + group_elems];
let mut mean = 0.0f64;
for &v in group.iter() {
mean += v as f64;
}
mean *= inv_n;
let mut var = 0.0f64;
for &v in group.iter() {
let d = v as f64 - mean;
var += d * d;
}
var *= inv_n;
let inv_std = (1.0 / (var + self.eps as f64).sqrt()) as f32;
let mean_f = mean as f32;
for ci in 0..gs {
let c = c0 + ci;
let wgt = self.weight[c];
let bia = self.bias[c];
let chan = &mut group[ci * hw..(ci + 1) * hw];
for v in chan.iter_mut() {
*v = (*v - mean_f) * inv_std * wgt + bia;
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn two_groups_normalize_independently() {
let weight = vec![1.0, 1.0];
let bias = vec![0.0, 0.0];
let gn = GroupNorm::new(&weight, &bias, 2, 0.0).expect("gn");
let mut x = vec![1.0, 3.0, 10.0, 14.0]; gn.forward_inplace(&mut x, 1, 2).expect("fwd");
for v in &x {
assert!((v.abs() - 1.0).abs() < 1e-5, "{v}");
}
assert!(x[0] < 0.0 && x[1] > 0.0 && x[2] < 0.0 && x[3] > 0.0);
}
#[test]
fn affine_is_applied_per_channel() {
let weight = vec![2.0, 0.5];
let bias = vec![1.0, -1.0];
let gn = GroupNorm::new(&weight, &bias, 1, 0.0).expect("gn");
let mut x = vec![0.0, 2.0, 0.0, 2.0]; gn.forward_inplace(&mut x, 1, 2).expect("fwd");
assert!((x[0] - (-1.0)).abs() < 1e-5);
assert!((x[1] - 3.0).abs() < 1e-5);
assert!((x[2] - (-1.5)).abs() < 1e-5);
assert!((x[3] - (-0.5)).abs() < 1e-5);
}
}