use candle_core::DType;
use candle_core::Module;
use candle_core::Result;
use candle_core::Tensor;
use candle_nn::VarBuilder;
pub struct LayerNorm {
inner: candle_nn::LayerNorm,
}
impl LayerNorm {
pub fn load(vb: VarBuilder, size: usize, eps: f64) -> Result<Self> {
let config = candle_nn::LayerNormConfig {
eps,
remove_mean: true,
affine: true,
};
let inner = candle_nn::layer_norm(size, config, vb.set_dtype(DType::F32))?;
Ok(Self { inner })
}
pub fn load_without_bias(vb: VarBuilder, size: usize, eps: f64) -> Result<Self> {
let config = candle_nn::LayerNormConfig {
eps,
remove_mean: true,
affine: false,
};
let inner = candle_nn::layer_norm(size, config, vb.set_dtype(DType::F32))?;
Ok(Self { inner })
}
pub fn forward(&self, xs: &Tensor) -> Result<Tensor> {
let dtype = xs.dtype();
if dtype == DType::F32 {
return self.inner.forward(xs);
}
self
.inner
.forward(&xs.to_dtype(DType::F32)?)?
.to_dtype(dtype)
}
}
#[cfg(test)]
mod tests {
use candle_core::Device;
use candle_nn::VarBuilder;
use candle_nn::VarMap;
use super::*;
fn unit_norm(dtype: DType) -> LayerNorm {
let mut varmap = VarMap::new();
let vb = VarBuilder::from_varmap(&varmap, dtype, &Device::Cpu);
let norm = LayerNorm::load(vb, 4, 1e-12).unwrap();
varmap
.set_one("weight", Tensor::ones(4, DType::F32, &Device::Cpu).unwrap())
.unwrap();
varmap
.set_one("bias", Tensor::zeros(4, DType::F32, &Device::Cpu).unwrap())
.unwrap();
norm
}
#[test]
fn output_keeps_the_input_dtype_and_is_centred() {
let norm = unit_norm(DType::F32);
let xs = Tensor::new(&[[1.0f32, 2.0, 3.0, 4.0]], &Device::Cpu).unwrap();
let out = norm.forward(&xs).unwrap();
assert_eq!(out.dtype(), DType::F32);
let row = out.to_vec2::<f32>().unwrap().remove(0);
let mean: f32 = row.iter().sum::<f32>() / 4.0;
assert!(mean.abs() < 1e-5, "{row:?}");
}
#[test]
fn half_precision_tracks_the_f32_result() {
let xs = Tensor::new(&[[10.0f32, 10.5, 11.0, 10.25]], &Device::Cpu).unwrap();
let exact = unit_norm(DType::F32)
.forward(&xs)
.unwrap()
.to_vec2::<f32>()
.unwrap()
.remove(0);
let half = unit_norm(DType::F16)
.forward(&xs.to_dtype(DType::F16).unwrap())
.unwrap()
.to_dtype(DType::F32)
.unwrap()
.to_vec2::<f32>()
.unwrap()
.remove(0);
for (a, b) in exact.iter().zip(&half) {
assert!((a - b).abs() < 2e-2, "{exact:?} vs {half:?}");
}
}
#[test]
fn load_without_bias_reads_the_weight_alone() {
let mut tensors = std::collections::HashMap::new();
tensors.insert(
"weight".to_string(),
Tensor::new(&[1f32, 1.0, 1.0, 1.0], &Device::Cpu).unwrap(),
);
let vb = VarBuilder::from_tensors(tensors, DType::F32, &Device::Cpu);
assert!(LayerNorm::load(vb.clone(), 4, 1e-5).is_err());
let norm = LayerNorm::load_without_bias(vb, 4, 1e-5).unwrap();
let out = norm
.forward(&Tensor::new(&[[1f32, 2.0, 3.0, 6.0]], &Device::Cpu).unwrap())
.unwrap();
let expected = [-1.069045, -0.534522, 0.0, 1.603567];
for (a, e) in out.to_vec2::<f32>().unwrap()[0].iter().zip(expected) {
assert!((a - e).abs() < 1e-4, "{a} vs {e}");
}
}
}