use crate::sampling::softmax_inplace;
#[must_use]
pub fn scaled_dot_product_attention(
query: &[Vec<f32>],
key: &[Vec<f32>],
value: &[Vec<f32>],
mask: Option<&[Vec<f32>]>,
) -> Vec<Vec<f32>> {
if query.is_empty() || key.is_empty() || value.is_empty() {
return Vec::new();
}
let head_dim = query[0].len();
let scale = 1.0 / (head_dim as f32).sqrt();
let q_len = query.len();
let k_len = key.len();
let mut scores = vec![vec![0.0f32; k_len]; q_len];
for i in 0..q_len {
for j in 0..k_len {
let mut dot = 0.0f32;
for d in 0..head_dim {
dot += query[i][d] * key[j][d];
}
scores[i][j] = dot * scale;
}
}
if let Some(m) = mask {
for i in 0..q_len {
for j in 0..k_len {
if j < m[i].len() {
scores[i][j] += m[i][j];
}
}
}
}
for row in &mut scores {
softmax_inplace(row);
}
let v_dim = value[0].len();
let mut output = vec![vec![0.0f32; v_dim]; q_len];
for i in 0..q_len {
for j in 0..k_len {
let w = scores[i][j];
for d in 0..v_dim {
output[i][d] += w * value[j][d];
}
}
}
output
}
#[must_use]
pub fn causal_mask(seq_len: usize) -> Vec<Vec<f32>> {
let mut mask = vec![vec![0.0f32; seq_len]; seq_len];
for (i, row) in mask.iter_mut().enumerate() {
for val in row.iter_mut().skip(i + 1) {
*val = f32::NEG_INFINITY;
}
}
mask
}