use candle_core::Module;
use candle_core::Tensor;
use candle_nn::Linear;
use candle_nn::VarBuilder;
use candle_nn::linear_no_bias;
use crate::error::Result;
use crate::layers::activation::Activation;
pub struct GatedMlp {
gate: Linear,
up: Linear,
down: Linear,
activation: Activation,
}
impl GatedMlp {
pub fn load(
vb: VarBuilder,
hidden: usize,
intermediate: usize,
activation: Activation,
) -> Result<Self> {
Ok(Self::new(
linear_no_bias(hidden, intermediate, vb.pp("gate_proj"))?,
linear_no_bias(hidden, intermediate, vb.pp("up_proj"))?,
linear_no_bias(intermediate, hidden, vb.pp("down_proj"))?,
activation,
))
}
pub fn new(gate: Linear, up: Linear, down: Linear, activation: Activation) -> Self {
Self {
gate,
up,
down,
activation,
}
}
pub fn forward(&self, xs: &Tensor) -> Result<Tensor> {
let gate = self.activation.forward(&self.gate.forward(xs)?)?;
Ok(self.down.forward(&(gate * self.up.forward(xs)?)?)?)
}
}
#[cfg(test)]
mod tests {
use candle_core::Device;
use candle_core::Tensor;
use candle_nn::Linear;
use super::*;
#[test]
fn swiglu_with_identity_weights() {
let eye = Tensor::new(&[[1f32, 0.0], [0.0, 1.0]], &Device::Cpu).unwrap();
let mlp = GatedMlp::new(
Linear::new(eye.clone(), None),
Linear::new(eye.clone(), None),
Linear::new(eye, None),
Activation::Silu,
);
let out = mlp
.forward(&Tensor::new(&[[1f32, 2.0]], &Device::Cpu).unwrap())
.unwrap()
.to_vec2::<f32>()
.unwrap();
let silu = |x: f32| x / (1.0 + (-x).exp());
assert!(
(out[0][0] - silu(1.0) * 1.0).abs() < 1e-5 && (out[0][1] - silu(2.0) * 2.0).abs() < 1e-5,
"{out:?}"
);
}
}