use std::marker::PhantomData;
use static_assertions::assert_impl_all;
use crate::{Element, Symbol, Tape, Tensor, Value};
use super::{Module, Visitor};
assert_impl_all!(LayerNorm<f64>: Send, Sync);
#[derive(Debug, Clone)]
pub struct LayerNorm<E> {
scale: Symbol,
shift: Symbol,
epsilon: Symbol,
_marker: PhantomData<E>,
}
impl<E: Element> LayerNorm<E> {
pub fn new(tape: &Tape<E>, scale: Tensor<E>, shift: Tensor<E>, epsilon: Tensor<E>) -> Self {
let scale_shape = scale.shape();
let shift_shape = shift.shape();
let epsilon_shape = epsilon.shape();
assert_eq!(
scale_shape.rank(),
1,
"layer-norm scale must be rank 1, got {scale_shape}"
);
assert_eq!(
shift_shape, scale_shape,
"layer-norm shift {shift_shape} must be shaped like the scale {scale_shape}"
);
assert_eq!(
epsilon_shape.volume(),
1,
"layer-norm epsilon must hold a single value, got {epsilon_shape}"
);
Self {
scale: tape.parameter(scale).symbol(),
shift: tape.parameter(shift).symbol(),
epsilon: tape.leaf(epsilon).symbol(),
_marker: PhantomData,
}
}
pub fn parameters(&self) -> impl Iterator<Item = Symbol> + '_ {
super::parameters(self).into_iter()
}
}
impl<E: Element> LayerNorm<E> {}
#[cfg(test)]
#[path = "tests/layer_norm_tests.rs"]
mod tests;
impl<E: Element> Module<E> for LayerNorm<E> {
fn express<'tape>(&self, input: Value<'tape, E>) -> Value<'tape, E> {
let tape = input.tape();
let scale = tape.resolve(self.scale);
let shift = tape.resolve(self.shift);
let epsilon = tape.resolve(self.epsilon);
let input_shape = input.shape();
let scale_shape = scale.shape();
assert_eq!(
input_shape.rank(),
2,
"layer-norm input must be rank 2 [batch, features], got {input_shape}"
);
assert_eq!(
input_shape.axes()[1],
scale_shape.axes()[0],
"layer-norm input {input_shape} and scale {scale_shape} disagree on features"
);
let mean = input.mean_along(1);
let centered = input - mean.broadcast_along_like(1, input);
let variance = (centered * centered).mean_along(1);
let deviation = (variance + epsilon.broadcast_like(variance)).sqrt();
let normalized = centered / deviation.broadcast_along_like(1, input);
normalized * scale.broadcast_along_like(0, input) + shift.broadcast_along_like(0, input)
}
fn visit(&self, visitor: &mut dyn Visitor) {
visitor.parameter("scale", self.scale);
visitor.parameter("shift", self.shift);
}
}