use pagesift_infer::{Linear, Tensor, Weights};
pub(super) struct Attention {
q: Linear,
pub(super) k: Linear,
pub(super) v: Linear,
out: Linear,
heads: usize,
}
impl Attention {
pub(super) fn load(w: &Weights, p: &str, heads: usize) -> Result<Attention, String> {
Ok(Attention {
q: Linear::load(w, &format!("{p}.q_proj"))?,
k: Linear::load(w, &format!("{p}.k_proj"))?,
v: Linear::load(w, &format!("{p}.v_proj"))?,
out: Linear::load(w, &format!("{p}.out_proj"))?,
heads,
})
}
pub(super) fn attend(
&self,
x: &Tensor,
keys: &[f32],
values: &[f32],
visible: impl Fn(usize) -> usize,
) -> Tensor {
let q = self.q.forward(x);
let (t, d) = q.tc();
let hd = d / self.heads;
let mut out = vec![0f32; t * d];
attend_rows(&q.data, d, self.heads, keys, values, visible, &mut out);
debug_assert_eq!(hd * self.heads, d);
self.out.forward(&Tensor::new(vec![t, d], out))
}
}
fn attend_rows(
q: &[f32],
d: usize,
heads: usize,
keys: &[f32],
values: &[f32],
visible: impl Fn(usize) -> usize,
out: &mut [f32],
) {
let hd = d / heads;
let scale = (hd as f32).powf(-0.5);
let mut scores = Vec::new();
for (i, (qr, or)) in q.chunks_exact(d).zip(out.chunks_exact_mut(d)).enumerate() {
let n = visible(i);
for h in 0..heads {
let at = h * hd;
let qh = &qr[at..at + hd];
scores.clear();
scores.extend(keys[..n * d].chunks_exact(d).map(|k| {
let dot: f32 = qh.iter().zip(&k[at..at + hd]).map(|(a, b)| a * b).sum();
dot * scale
}));
let m = scores.iter().copied().fold(f32::NEG_INFINITY, f32::max);
let mut sum = 0f32;
for s in &mut scores {
*s = (*s - m).exp();
sum += *s;
}
let oh = &mut or[at..at + hd];
for (p, v) in scores.iter().zip(values[..n * d].chunks_exact(d)) {
let p = p / sum;
for (o, v) in oh.iter_mut().zip(&v[at..at + hd]) {
*o += p * v;
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn one_head_by_hand() {
let mut out = vec![0f32; 2];
attend_rows(
&[1.0, 0.0],
2,
1,
&[1.0, 0.0, 0.0, 1.0],
&[1.0, 2.0, 3.0, 4.0],
|_| 2,
&mut out,
);
let e = (std::f32::consts::FRAC_1_SQRT_2).exp();
let (p0, p1) = (e / (e + 1.0), 1.0 / (e + 1.0));
assert!((out[0] - (p0 + 3.0 * p1)).abs() < 1e-6);
assert!((out[1] - (2.0 * p0 + 4.0 * p1)).abs() < 1e-6);
}
#[test]
fn heads_split_columns() {
let big = 100.0;
let mut out = vec![0f32; 2];
attend_rows(
&[big, big],
2,
2,
&[1.0, 0.0, 0.0, 1.0],
&[5.0, 6.0, 7.0, 8.0],
|_| 2,
&mut out,
);
assert!((out[0] - 5.0).abs() < 1e-4);
assert!((out[1] - 8.0).abs() < 1e-4);
}
#[test]
fn mask_limits_keys() {
let mut out = vec![0f32; 4];
attend_rows(
&[0.0, 1.0, 0.0, 1.0],
2,
1,
&[1.0, 0.0, 0.0, 1.0],
&[1.0, 2.0, 3.0, 4.0],
|i| i + 1,
&mut out,
);
assert_eq!(&out[..2], &[1.0, 2.0]);
assert!(out[2] > 1.0 && out[2] < 3.0);
}
}