use ferrox_core::matmul::{geglu, swiglu};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum GluAct {
Swiglu,
SwigluClamped {
limit: f32,
},
Geglu,
Reglu,
Xielu(XieluParams),
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct XieluParams {
pub alpha_n: f32,
pub alpha_p: f32,
pub beta: f32,
pub eps: f32,
}
impl XieluParams {
pub fn from_gguf(alpha_n: f32, alpha_p: f32, beta: f32, eps: f32) -> Self {
Self {
alpha_n: beta + softplus(alpha_n),
alpha_p: softplus(alpha_p),
beta,
eps,
}
}
#[inline]
pub fn apply(self, x: f32) -> f32 {
if x > 0.0 {
self.alpha_p * x * x + self.beta * x
} else {
let min_x_eps = x.min(self.eps);
(min_x_eps.exp_m1() - x) * self.alpha_n + self.beta * x
}
}
}
fn softplus(x: f32) -> f32 {
if x > 20.0 {
x
} else {
(1.0 + x.exp()).ln()
}
}
impl GluAct {
pub fn apply(self, gate: &[f32], up: &[f32]) -> Vec<f32> {
match self {
GluAct::Swiglu => swiglu(gate, up),
GluAct::Geglu => geglu(gate, up),
GluAct::Reglu => ferrox_core::matmul::reglu(gate, up),
GluAct::SwigluClamped { .. } | GluAct::Xielu(_) => gate
.iter()
.zip(up)
.map(|(&g, &u)| self.combine(g, u))
.collect(),
}
}
#[inline]
pub fn combine(self, gate: f32, up: f32) -> f32 {
match self {
GluAct::Swiglu => ferrox_core::matmul::silu(gate) * up,
GluAct::SwigluClamped { limit } => {
ferrox_core::matmul::silu(gate).min(limit) * up.clamp(-limit, limit)
}
GluAct::Geglu => ferrox_core::matmul::gelu(gate) * up,
GluAct::Reglu => ferrox_core::matmul::relu(gate) * up,
GluAct::Xielu(p) => p.apply(up),
}
}
pub fn is_swiglu(self) -> bool {
matches!(self, GluAct::Swiglu)
}
pub fn fused_kernel_gelu_flag(self) -> Option<bool> {
match self {
GluAct::Swiglu => Some(false),
GluAct::Geglu => Some(true),
GluAct::Reglu | GluAct::SwigluClamped { .. } | GluAct::Xielu(_) => None,
}
}
pub fn ungated(self) -> Option<Ungated> {
match self {
GluAct::Swiglu | GluAct::SwigluClamped { .. } | GluAct::Geglu => None,
GluAct::Reglu => Some(Ungated::ReluSqr),
GluAct::Xielu(p) => Some(Ungated::Xielu(p)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Ungated {
ReluSqr,
Xielu(XieluParams),
}
impl Ungated {
pub fn apply(self, up: &[f32]) -> Vec<f32> {
match self {
Ungated::ReluSqr => relu_sqr(up),
Ungated::Xielu(p) => up.iter().map(|&x| p.apply(x)).collect(),
}
}
}
pub fn relu_sqr(up: &[f32]) -> Vec<f32> {
up.iter()
.map(|&x| {
let r = ferrox_core::matmul::relu(x);
r * r
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn xielu_matches_ggml_s_op_on_both_branches_and_inside_eps() {
let p = XieluParams::from_gguf(0.8, 0.8, 0.5, -0.3);
let sp = |x: f32| (1.0 + x.exp()).ln();
assert!((p.alpha_n - (0.5 + sp(0.8))).abs() < 1e-6);
assert!((p.alpha_p - sp(0.8)).abs() < 1e-6);
let x = 1.5f32;
assert!((p.apply(x) - (p.alpha_p * x * x + 0.5 * x)).abs() < 1e-6);
let x = -0.1f32;
let want = ((-0.3f32).exp_m1() - x) * p.alpha_n + 0.5 * x;
assert!((p.apply(x) - want).abs() < 1e-6);
let x = -2.0f32;
let want = (x.exp_m1() - x) * p.alpha_n + 0.5 * x;
assert!((p.apply(x) - want).abs() < 1e-6);
assert_eq!(softplus(25.0), 25.0);
}
#[test]
fn xielu_ignores_the_aliased_gate() {
let p = XieluParams::from_gguf(0.2, 1.5, 0.75, -1e-6);
let act = GluAct::Xielu(p);
let up = [-1.0f32, -0.2, 0.0, 0.3, 2.0];
let gate = [5.0f32; 5];
let want: Vec<f32> = up.iter().map(|&x| p.apply(x)).collect();
assert_eq!(act.apply(&gate, &up), want);
for (i, &u) in up.iter().enumerate() {
assert_eq!(act.combine(gate[i], u), want[i]);
}
assert_eq!(act.ungated().expect("ungated").apply(&up), want);
assert_eq!(act.fused_kernel_gelu_flag(), None);
assert!(!act.is_swiglu());
}
#[test]
fn clamped_swiglu_matches_llama_cpp_s_else_branch() {
let act = GluAct::SwigluClamped { limit: 2.0 };
let silu = ferrox_core::matmul::silu;
assert!((act.combine(5.0, -7.0) - silu(5.0).min(2.0) * -2.0).abs() < 1e-6);
assert!(
(silu(5.0) - 2.0).abs() > 0.5,
"the gate clamp must bite here"
);
assert!((act.combine(1.0, 1.5) - GluAct::Swiglu.combine(1.0, 1.5)).abs() < 1e-7);
assert!((act.combine(-9.0, 1.0) - silu(-9.0)).abs() < 1e-7);
assert!(!act.is_swiglu(), "no fused kernel spells the clamp");
assert_eq!(act.fused_kernel_gelu_flag(), None);
assert!(act.ungated().is_none());
}
#[test]
fn combine_is_apply_elementwise_for_every_variant() {
let gate = [-1.5f32, -0.4, 0.0, 0.7, 2.2];
let up = [0.9f32, -1.1, 0.5, -0.3, 1.7];
for act in [
GluAct::Swiglu,
GluAct::SwigluClamped { limit: 0.5 },
GluAct::Geglu,
GluAct::Reglu,
GluAct::Xielu(XieluParams::from_gguf(0.8, 0.8, 0.5, -1e-6)),
] {
let whole = act.apply(&gate, &up);
for i in 0..gate.len() {
let one = act.combine(gate[i], up[i]);
assert!(
(whole[i] - one).abs() < 1e-6,
"{act:?} element {i}: apply {} vs combine {one}",
whole[i]
);
}
}
}
}