use frink_core::matmul::rms_norm;
#[derive(Debug, Clone, PartialEq)]
pub enum NormOp {
Rms(Vec<f32>),
LayerNormNoParams,
RmsNoParams,
LayerNorm(Vec<f32>),
LayerNormBias { weight: Vec<f32>, bias: Vec<f32> },
RmsBias { weight: Vec<f32>, bias: Vec<f32> },
None,
}
impl NormOp {
pub fn apply(&self, x: &[f32], eps: f32) -> Vec<f32> {
match self {
Self::Rms(w) => rms_norm(x, w, eps),
Self::RmsNoParams => rms_norm_no_params(x, eps),
Self::LayerNormNoParams => layer_norm_no_params(x, eps),
Self::LayerNorm(w) => {
let mut out = layer_norm_no_params(x, eps);
debug_assert_eq!(out.len(), w.len());
for (o, w) in out.iter_mut().zip(w.iter()) {
*o *= w;
}
out
}
Self::LayerNormBias { weight, bias } => {
let mut out = layer_norm_no_params(x, eps);
debug_assert_eq!(out.len(), weight.len());
debug_assert_eq!(out.len(), bias.len());
for ((o, w), b) in out.iter_mut().zip(weight.iter()).zip(bias.iter()) {
*o = *o * w + b;
}
out
}
Self::RmsBias { weight, bias } => {
let mut out = rms_norm(x, weight, eps);
debug_assert_eq!(out.len(), bias.len());
for (o, b) in out.iter_mut().zip(bias.iter()) {
*o += b;
}
out
}
Self::None => x.to_vec(),
}
}
pub fn rms_weights(&self) -> Option<&[f32]> {
match self {
Self::Rms(w) => Some(w),
Self::RmsNoParams
| Self::LayerNormNoParams
| Self::LayerNorm(_)
| Self::LayerNormBias { .. }
| Self::RmsBias { .. }
| Self::None => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NormParam {
Weight,
Bias,
}
impl NormParam {
pub fn suffix(self) -> &'static str {
match self {
NormParam::Weight => "weight",
NormParam::Bias => "bias",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NormFunction {
Rms,
LayerNorm,
LayerNormBias,
LayerNormNoParams,
RmsNoParams,
RmsBias,
}
impl NormFunction {
pub fn resolve<E>(
self,
mut load: impl FnMut(NormParam) -> Result<Vec<f32>, E>,
) -> Result<NormOp, E> {
Ok(match self {
Self::Rms => NormOp::Rms(load(NormParam::Weight)?),
Self::LayerNorm => NormOp::LayerNorm(load(NormParam::Weight)?),
Self::LayerNormBias => NormOp::LayerNormBias {
weight: load(NormParam::Weight)?,
bias: load(NormParam::Bias)?,
},
Self::LayerNormNoParams => NormOp::LayerNormNoParams,
Self::RmsNoParams => NormOp::RmsNoParams,
Self::RmsBias => NormOp::RmsBias {
weight: load(NormParam::Weight)?,
bias: load(NormParam::Bias)?,
},
})
}
}
pub fn norm_function(arch: &str) -> NormFunction {
norm_function_for_file(arch, None)
}
pub const POST_NORM_EPS_LITERAL: &[(&str, f32)] = &[("muse-glimmer", 1e-8)];
pub fn post_norm_eps(arch: &str, model_eps: f32) -> f32 {
POST_NORM_EPS_LITERAL
.iter()
.find(|(a, _)| *a == arch)
.map_or(model_eps, |(_, eps)| *eps)
}
pub const NORM_BY_RMS_EPS_KEY: &[(&str, &str)] =
&[("cohere2moe", "src/models/cohere2moe.cpp:4-11,166")];
pub fn norm_function_for_file(arch: &str, declared_rms_eps: Option<f32>) -> NormFunction {
if NORM_BY_RMS_EPS_KEY.iter().any(|(a, _)| *a == arch)
&& declared_rms_eps.is_some_and(|eps| eps != 0.0)
{
return NormFunction::Rms;
}
if crate::capability::uses_non_parametric_layer_norm(arch) {
NormFunction::LayerNormNoParams
} else if crate::capability::uses_non_parametric_rms_norm(arch) {
NormFunction::RmsNoParams
} else if crate::capability::uses_weighted_layer_norm(arch) {
NormFunction::LayerNorm
} else if crate::capability::uses_biased_layer_norm(arch) {
NormFunction::LayerNormBias
} else if crate::capability::uses_biased_rms_norm(arch) {
NormFunction::RmsBias
} else {
NormFunction::Rms
}
}
pub fn rms_norm_no_params(x: &[f32], eps: f32) -> Vec<f32> {
let n = x.len() as f32;
debug_assert!(n > 0.0, "a norm site with no elements");
let mean_sq = x.iter().map(|v| v * v).sum::<f32>() / n;
let scale = 1.0 / (mean_sq + eps).sqrt();
x.iter().map(|v| v * scale).collect()
}
fn layer_norm_no_params(x: &[f32], eps: f32) -> Vec<f32> {
let n = x.len() as f32;
debug_assert!(n > 0.0, "a norm site with no elements");
let mean = x.iter().sum::<f32>() / n;
let mut out: Vec<f32> = x.iter().map(|v| v - mean).collect();
let var = out.iter().map(|d| d * d).sum::<f32>() / n;
let scale = 1.0 / (var + eps).sqrt();
for v in out.iter_mut() {
*v *= scale;
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_norm_is_the_identity_and_an_all_ones_rmsnorm_is_not() {
let x = vec![3.0f32, -4.0, 12.0, 0.5];
let eps = 1e-5;
assert_eq!(NormOp::None.apply(&x, eps), x);
let ones = NormOp::Rms(vec![1.0; x.len()]);
let normed = ones.apply(&x, eps);
let worst = x
.iter()
.zip(normed.iter())
.map(|(a, b)| (a - b).abs())
.fold(0f32, f32::max);
assert!(
worst > 1.0,
"an all-ones RMSNorm moved the vector by only {worst}; if it were the \
identity the post-norm-only topology would not need a variant at all"
);
}
#[test]
fn the_layer_norm_subtracts_the_mean_and_an_all_ones_rmsnorm_does_not() {
let x = vec![3.0f32, -4.0, 12.0, 0.5];
let eps = 1e-5;
let ln = NormOp::LayerNormNoParams.apply(&x, eps);
let rms = NormOp::Rms(vec![1.0; x.len()]).apply(&x, eps);
let mean: f32 = x.iter().sum::<f32>() / x.len() as f32;
assert!(mean.abs() > 1.0, "the input must not be centred: {mean}");
let out_mean: f32 = ln.iter().sum::<f32>() / ln.len() as f32;
assert!(
out_mean.abs() < 1e-5,
"a LayerNorm's output is centred; got mean {out_mean}"
);
let worst = ln
.iter()
.zip(rms.iter())
.map(|(a, b)| (a - b).abs())
.fold(0f32, f32::max);
assert!(
worst > 0.1,
"the two norms differ by only {worst}; an all-ones RMSNorm would then be a \
legitimate stand-in for OLMo-1's norm and this variant would be decoration"
);
}
#[test]
fn the_variance_is_the_biased_one_ggml_uses() {
let x = [1.0f32, 2.0, 3.0, 10.0];
let got = NormOp::LayerNormNoParams.apply(&x, 0.0);
let n = x.len() as f64;
let mean = x.iter().map(|v| *v as f64).sum::<f64>() / n;
let var = x.iter().map(|v| (*v as f64 - mean).powi(2)).sum::<f64>() / n;
let want: Vec<f32> = x
.iter()
.map(|v| ((*v as f64 - mean) / var.sqrt()) as f32)
.collect();
for (g, w) in got.iter().zip(want.iter()) {
assert!((g - w).abs() < 1e-5, "got {got:?}, want {want:?}");
}
let sample = x.iter().map(|v| (*v as f64 - mean).powi(2)).sum::<f64>() / (n - 1.0);
let worst = got
.iter()
.zip(x.iter())
.map(|(g, v)| (g - ((*v as f64 - mean) / sample.sqrt()) as f32).abs())
.fold(0f32, f32::max);
assert!(worst > 0.1, "the two variances differ by only {worst}");
}
#[test]
fn only_the_rms_variant_offers_weights_to_a_fused_kernel() {
assert_eq!(
NormOp::Rms(vec![2.0, 3.0]).rms_weights(),
Some(&[2.0f32, 3.0][..])
);
assert_eq!(NormOp::None.rms_weights(), None);
assert_eq!(NormOp::LayerNormNoParams.rms_weights(), None);
assert_eq!(NormOp::LayerNorm(vec![2.0, 3.0]).rms_weights(), None);
}
#[test]
fn the_weighted_layer_norm_is_the_parameterless_one_times_its_weight() {
let x = vec![3.0f32, -4.0, 12.0, 0.5];
let w = vec![0.5f32, -2.0, 1.5, 4.0];
let eps = 1e-5;
let got = NormOp::LayerNorm(w.clone()).apply(&x, eps);
let base = NormOp::LayerNormNoParams.apply(&x, eps);
for ((g, b), w) in got.iter().zip(base.iter()).zip(w.iter()) {
assert!((g - b * w).abs() < 1e-6, "got {got:?}, base {base:?}");
}
}
#[test]
fn the_weighted_layer_norm_is_not_an_rmsnorm_with_the_same_weight() {
let x = vec![3.0f32, -4.0, 12.0, 0.5];
let w = vec![0.5f32, -2.0, 1.5, 4.0];
let eps = 1e-5;
let ln = NormOp::LayerNorm(w.clone()).apply(&x, eps);
let rms = NormOp::Rms(w).apply(&x, eps);
let worst = ln
.iter()
.zip(rms.iter())
.map(|(a, b)| (a - b).abs())
.fold(0f32, f32::max);
assert!(worst > 0.1, "the two norms differ by only {worst}");
}
#[test]
fn the_norm_function_is_read_off_the_capability_lists() {
assert_eq!(norm_function("olmo"), NormFunction::LayerNormNoParams);
assert_eq!(norm_function("dbrx"), NormFunction::LayerNorm);
for arch in ["llama", "qwen3", "olmo2", "gemma3", "grok"] {
assert_eq!(norm_function(arch), NormFunction::Rms, "{arch}");
}
assert_eq!(norm_function("orion"), NormFunction::LayerNormBias);
assert_eq!(norm_function("nemotron"), NormFunction::LayerNormBias);
let w = |p: NormParam| -> Result<Vec<f32>, ()> {
Ok(match p {
NormParam::Weight => vec![1.0, 2.0],
NormParam::Bias => vec![0.5, -0.5],
})
};
assert_eq!(
NormFunction::LayerNorm.resolve(w),
Ok(NormOp::LayerNorm(vec![1.0, 2.0]))
);
assert_eq!(
NormFunction::Rms.resolve(w),
Ok(NormOp::Rms(vec![1.0, 2.0]))
);
assert_eq!(
NormFunction::LayerNormBias.resolve(w),
Ok(NormOp::LayerNormBias {
weight: vec![1.0, 2.0],
bias: vec![0.5, -0.5],
})
);
}
#[test]
fn the_parameterless_function_never_reads_a_weight() {
let mut asked = false;
let got = NormFunction::LayerNormNoParams.resolve(|_| -> Result<Vec<f32>, ()> {
asked = true;
Err(())
});
assert_eq!(got, Ok(NormOp::LayerNormNoParams));
assert!(!asked, "the loader closure must not run");
}
}