use super::activations::{ActivationFn, ActivationType};
use crate::common::diagnostics::NamErrorCode;
use crate::math::common::AlignedVec;
use crate::math::common::SimdMath;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub enum GatingMode {
#[default]
None,
Gated,
Blended,
}
#[derive(Debug, Clone, PartialEq)]
pub struct GatingActivationConfig {
pub input_activation: ActivationType,
pub gating_activation: ActivationType,
}
impl GatingActivationConfig {
pub fn new(input_activation: ActivationType, gating_activation: ActivationType) -> Self {
Self {
input_activation,
gating_activation,
}
}
#[inline]
pub fn apply_gating(&self, buf: &mut [f32]) {
let ch = buf.len() / 2;
debug_assert!(
ch * 2 == buf.len(),
"gating: buf length must be even (2×ch)"
);
self.input_activation.apply(&mut buf[..ch]);
self.gating_activation.apply(&mut buf[ch..]);
for i in 0..ch {
buf[i] *= buf[ch + i];
}
}
#[inline(always)]
pub unsafe fn apply_gating_simd<M: SimdMath>(&self, buf: &mut [f32]) {
let ch = buf.len() / 2;
debug_assert!(
ch * 2 == buf.len(),
"gating: buf length must be even (2×ch)"
);
unsafe {
self.input_activation.apply_simd::<M>(&mut buf[..ch]);
self.gating_activation.apply_simd::<M>(&mut buf[ch..]);
}
for i in 0..ch {
buf[i] *= buf[ch + i];
}
}
}
#[derive(Debug, Clone)]
pub struct BlendingActivationConfig {
pub input_activation: ActivationType,
pub blending_activation: ActivationType,
scratch: AlignedVec<f32>,
}
impl PartialEq for BlendingActivationConfig {
fn eq(&self, other: &Self) -> bool {
self.input_activation == other.input_activation
&& self.blending_activation == other.blending_activation
&& self.scratch.len() == other.scratch.len()
}
}
impl BlendingActivationConfig {
pub fn new(
input_activation: ActivationType,
blending_activation: ActivationType,
ch: usize,
) -> Result<Self, NamErrorCode> {
Ok(Self {
input_activation,
blending_activation,
scratch: AlignedVec::new(ch, 0.0f32)?,
})
}
#[inline]
pub fn apply_blending(&mut self, buf: &mut [f32]) {
let ch = buf.len() / 2;
debug_assert!(
ch * 2 == buf.len(),
"blending: buf length must be even (2×ch)"
);
debug_assert!(
self.scratch.len() >= ch,
"blending: scratch too small ({}) for ch={}",
self.scratch.len(),
ch
);
self.scratch[..ch].copy_from_slice(&buf[..ch]);
self.input_activation.apply(&mut buf[..ch]);
self.blending_activation.apply(&mut buf[ch..]);
for i in 0..ch {
let alpha = buf[ch + i];
buf[i] = (buf[i] - self.scratch[i]).mul_add(alpha, self.scratch[i]);
}
}
#[inline]
pub fn channels(&self) -> usize {
self.scratch.len()
}
#[inline(always)]
pub unsafe fn apply_blending_simd<M: SimdMath>(&mut self, buf: &mut [f32]) {
let ch = buf.len() / 2;
debug_assert!(
ch * 2 == buf.len(),
"blending: buf length must be even (2×ch)"
);
debug_assert!(
self.scratch.len() >= ch,
"blending: scratch too small ({}) for ch={}",
self.scratch.len(),
ch
);
self.scratch[..ch].copy_from_slice(&buf[..ch]);
unsafe {
self.input_activation.apply_simd::<M>(&mut buf[..ch]);
self.blending_activation.apply_simd::<M>(&mut buf[ch..]);
}
for i in 0..ch {
let alpha = buf[ch + i];
buf[i] = (buf[i] - self.scratch[i]).mul_add(alpha, self.scratch[i]);
}
}
}
#[cfg(test)]
#[path = "gating_test.rs"]
mod tests;