extern crate alloc;
use crate::kernels::sinkhorn;
use crate::ops::sigmoid;
#[derive(Debug, Clone, Copy)]
pub struct LaneSite {
pub layer: usize,
pub lanes: usize,
pub d_model: usize,
}
pub struct Gates<'a> {
pub post: &'a mut [f32],
pub res: &'a mut [f32],
}
pub struct MhcLayer<'a> {
pub a_pre: f32,
pub a_post: f32,
pub a_res: f32,
pub b_pre: &'a [f32],
pub b_post: &'a [f32],
pub b_res: &'a [f32],
}
pub fn pre_off(layer: usize, lane: usize, lanes: usize) -> f32 {
if lane == layer % lanes {
4.0
} else {
-4.0
}
}
pub fn post_off(layer: usize, lane: usize, lanes: usize) -> f32 {
if lane == layer % lanes {
0.0
} else {
-4.0
}
}
pub fn mix_down(
lanes_buf: &[f32],
phi_pre_rows: &mut [f32],
m: &MhcLayer<'_>,
layer: usize,
lanes: usize,
d_model: usize,
u: &mut [f32],
) {
debug_assert_eq!(lanes_buf.len(), lanes * d_model);
debug_assert_eq!(phi_pre_rows.len(), lanes);
debug_assert_eq!(u.len(), d_model);
for (lane, h) in phi_pre_rows.iter_mut().enumerate() {
*h = sigmoid(m.a_pre * *h + m.b_pre[lane] + pre_off(layer, lane, lanes));
}
for (c, uc) in u.iter_mut().enumerate() {
let mut acc = 0.0f32;
for (lane, &h) in phi_pre_rows.iter().enumerate() {
acc += h * lanes_buf[lane * d_model + c];
}
*uc = acc;
}
}
pub fn scatter_up(
lanes_buf: &mut [f32],
gates: Gates<'_>,
y: &[f32],
m: &MhcLayer<'_>,
at: LaneSite,
scratch: &mut [f32],
) {
let Gates {
post: phi_post_rows,
res: phi_res_rows,
} = gates;
let LaneSite {
layer,
lanes,
d_model,
} = at;
debug_assert_eq!(lanes_buf.len(), lanes * d_model);
debug_assert_eq!(phi_post_rows.len(), lanes);
debug_assert_eq!(phi_res_rows.len(), lanes * lanes);
debug_assert_eq!(y.len(), d_model);
debug_assert_eq!(scratch.len(), lanes * d_model);
for (lane, h) in phi_post_rows.iter_mut().enumerate() {
*h = 2.0 * sigmoid(m.a_post * *h + m.b_post[lane] + post_off(layer, lane, lanes));
}
for (idx, r) in phi_res_rows.iter_mut().enumerate() {
*r = m.a_res * *r + m.b_res[idx];
}
sinkhorn(phi_res_rows, lanes);
scratch.copy_from_slice(lanes_buf);
for i in 0..lanes {
let out = &mut lanes_buf[i * d_model..(i + 1) * d_model];
let hp = phi_post_rows[i];
for (c, o) in out.iter_mut().enumerate() {
let mut acc = 0.0f32;
for j in 0..lanes {
acc += phi_res_rows[i * lanes + j] * scratch[j * d_model + c];
}
*o = acc + hp * y[c];
}
}
}
#[cfg(test)]
mod tests {
use super::*;
extern crate alloc;
use alloc::vec;
#[test]
fn offsets_single_out_the_active_lane() {
assert_eq!(pre_off(5, 1, 4), 4.0);
assert_eq!(pre_off(5, 0, 4), -4.0);
assert_eq!(post_off(5, 1, 4), 0.0);
assert_eq!(post_off(5, 2, 4), -4.0);
}
#[test]
fn mix_down_is_a_gated_lane_sum() {
let lanes_buf = [1.0f32, 2.0, 10.0, 20.0];
let mut rows = [0.0f32, 0.0];
let b_pre = [0.0f32, 0.0];
let b_post = [0.0f32, 0.0];
let b_res = [0.0f32; 4];
let m = MhcLayer {
a_pre: 0.0,
a_post: 0.0,
a_res: 0.0,
b_pre: &b_pre,
b_post: &b_post,
b_res: &b_res,
};
let mut u = [0.0f32; 2];
mix_down(&lanes_buf, &mut rows, &m, 0, 2, 2, &mut u);
let hi = sigmoid(4.0);
let lo = sigmoid(-4.0);
assert!((u[0] - (hi * 1.0 + lo * 10.0)).abs() < 1e-6, "{u:?}");
assert!((u[1] - (hi * 2.0 + lo * 20.0)).abs() < 1e-6, "{u:?}");
}
#[test]
fn scatter_rows_stay_doubly_stochastic() {
let lanes = 3usize;
let d = 2usize;
let mut buf = vec![1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0];
let before: f32 = buf.iter().sum();
let mut post = vec![0.0f32; lanes];
let mut res = vec![0.3f32; lanes * lanes];
let b_pre = vec![0.0f32; lanes];
let b_post = vec![-100.0f32; lanes]; let b_res = vec![0.0f32; lanes * lanes];
let m = MhcLayer {
a_pre: 1.0,
a_post: 1.0,
a_res: 1.0,
b_pre: &b_pre,
b_post: &b_post,
b_res: &b_res,
};
let y = vec![7.0f32, 8.0];
let mut scratch = vec![0.0f32; lanes * d];
scatter_up(
&mut buf,
Gates {
post: &mut post,
res: &mut res,
},
&y,
&m,
LaneSite {
layer: 0,
lanes,
d_model: d,
},
&mut scratch,
);
let after: f32 = buf.iter().sum();
assert!(
(after - before).abs() < 1e-3,
"mass changed: {before} -> {after}"
);
}
}