use candle_core::DType;
use candle_core::Tensor;
use crate::error::CanddingError;
use crate::error::Result;
pub fn normalize(xs: &Tensor) -> Result<Tensor> {
let norms = xs.sqr()?.sum_keepdim(1)?.sqrt()?.maximum(1e-12)?;
Ok(xs.broadcast_div(&norms)?)
}
pub fn layer_norm(xs: &Tensor, eps: f64) -> Result<Tensor> {
let mean = xs.mean_keepdim(1)?;
let centered = xs.broadcast_sub(&mean)?;
let var = centered.sqr()?.mean_keepdim(1)?;
Ok(centered.broadcast_div(&(var + eps)?.sqrt()?)?)
}
pub fn truncate_dim(xs: &Tensor, dim: usize) -> Result<Tensor> {
let full = xs.dim(1)?;
if dim == 0 || dim > full {
return Err(CanddingError::InvalidDim {
requested: dim,
dim: full,
});
}
Ok(xs.narrow(1, 0, dim)?)
}
pub fn to_rows(xs: &Tensor) -> Result<Vec<Vec<f32>>> {
Ok(xs.to_dtype(DType::F32)?.to_vec2::<f32>()?)
}
#[cfg(test)]
mod tests {
use candle_core::Device;
use super::*;
#[test]
fn normalize_gives_unit_rows() {
let xs = Tensor::new(&[[3f32, 4.0], [0.0, 2.0]], &Device::Cpu).unwrap();
let rows = to_rows(&normalize(&xs).unwrap()).unwrap();
assert_eq!(rows, vec![vec![0.6, 0.8], vec![0.0, 1.0]]);
}
#[test]
fn truncate_keeps_prefix_and_rejects_bad_dims() {
let xs = Tensor::new(&[[1f32, 2.0, 3.0]], &Device::Cpu).unwrap();
assert_eq!(
to_rows(&truncate_dim(&xs, 2).unwrap()).unwrap(),
vec![vec![1.0, 2.0]]
);
assert!(matches!(
truncate_dim(&xs, 4).unwrap_err(),
CanddingError::InvalidDim {
requested: 4,
dim: 3
}
));
assert!(truncate_dim(&xs, 0).is_err());
}
#[test]
fn layer_norm_removes_the_row_mean_and_scales_by_the_biased_std() {
let xs = Tensor::new(&[[1f32, 2.0, 3.0, 6.0]], &Device::Cpu).unwrap();
let out = to_rows(&layer_norm(&xs, 1e-5).unwrap()).unwrap();
let expected = [-1.069_045, -0.534_522, 0.0, 1.603_567];
for (o, e) in out[0].iter().zip(expected) {
assert!((o - e).abs() < 1e-4, "{out:?}");
}
assert!(
out[0].iter().sum::<f32>().abs() < 1e-5,
"the row mean is removed"
);
}
}