use heapless::Vec as HVec;
pub const MAX_SPARSE_SEQ: usize = 32;
pub const MAX_WINDOW_SIZE: usize = 8;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum AttentionPattern {
Full,
SlidingWindow { window_size: usize },
Strided { stride: usize },
Longformer { window_size: usize, stride: usize },
BlockDiagonal { block_size: usize },
BigBird { window_size: usize, global_tokens: usize },
}
impl Default for AttentionPattern {
fn default() -> Self {
Self::SlidingWindow { window_size: 4 }
}
}
pub struct SparseAttention {
pattern: AttentionPattern,
mask_data: HVec<u32, MAX_SPARSE_SEQ>,
seq_len: usize,
}
impl SparseAttention {
pub fn new(pattern: AttentionPattern, seq_len: usize) -> crate::Result<Self> {
if seq_len > MAX_SPARSE_SEQ {
return Err(crate::Error::BufferOverflow);
}
let mut sa = Self {
pattern,
mask_data: HVec::new(),
seq_len,
};
sa.build_mask()?;
Ok(sa)
}
fn build_mask(&mut self) -> crate::Result<()> {
self.mask_data.clear();
for i in 0..self.seq_len {
let mut row_mask: u32 = 0;
for j in 0..self.seq_len {
if j <= i && self.should_attend(i, j) {
row_mask |= 1 << j;
}
}
self.mask_data.push(row_mask).map_err(|_| crate::Error::BufferOverflow)?;
}
Ok(())
}
fn should_attend(&self, i: usize, j: usize) -> bool {
match self.pattern {
AttentionPattern::Full => true,
AttentionPattern::SlidingWindow { window_size } => {
i.saturating_sub(window_size) <= j
}
AttentionPattern::Strided { stride } => {
j % stride == 0 || i.saturating_sub(1) <= j
}
AttentionPattern::Longformer { window_size, stride } => {
i.saturating_sub(window_size) <= j || j % stride == 0
}
AttentionPattern::BlockDiagonal { block_size } => {
i / block_size == j / block_size
}
AttentionPattern::BigBird { window_size, global_tokens } => {
i.saturating_sub(window_size) <= j || j < global_tokens
}
}
}
#[inline]
pub fn should_attend_at(&self, i: usize, j: usize) -> bool {
if i >= self.seq_len || j >= self.seq_len {
return false;
}
(self.mask_data[i] >> j) & 1 == 1
}
#[inline]
pub fn get_mask_row(&self, i: usize) -> u32 {
self.mask_data.get(i).copied().unwrap_or(0)
}
pub fn sparse_qk(
&self,
query: &[i8], keys: &[&[i8]], scores: &mut [i32], query_pos: usize,
) {
let mask = self.get_mask_row(query_pos);
for (j, key) in keys.iter().enumerate() {
if (mask >> j) & 1 == 1 {
let mut sum: i32 = 0;
for (&q, &k) in query.iter().zip(key.iter()) {
sum += q as i32 * k as i32;
}
scores[j] = sum;
} else {
scores[j] = i32::MIN; }
}
}
pub fn active_positions(&self) -> usize {
self.mask_data.iter().map(|m| m.count_ones() as usize).sum()
}
pub fn sparsity_ratio(&self) -> f32 {
let full = self.seq_len * (self.seq_len + 1) / 2; let sparse = self.active_positions();
sparse as f32 / full as f32
}
pub fn memory_savings(&self) -> &'static str {
match self.pattern {
AttentionPattern::Full => "None (O(n²))",
AttentionPattern::SlidingWindow { .. } => "O(n) - linear",
AttentionPattern::Strided { .. } => "O(n) - linear",
AttentionPattern::Longformer { .. } => "O(n) - linear",
AttentionPattern::BlockDiagonal { .. } => "O(n) - block-linear",
AttentionPattern::BigBird { .. } => "O(n) - linear",
}
}
}
pub struct AttentionPatternCache {
patterns: [Option<SparseAttention>; 4],
}
impl AttentionPatternCache {
pub fn new_sliding(window_size: usize) -> Self {
let pattern = AttentionPattern::SlidingWindow { window_size };
Self {
patterns: [
SparseAttention::new(pattern, 8).ok(),
SparseAttention::new(pattern, 16).ok(),
SparseAttention::new(pattern, 24).ok(),
SparseAttention::new(pattern, 32).ok(),
],
}
}
pub fn get(&self, seq_len: usize) -> Option<&SparseAttention> {
let idx = match seq_len {
1..=8 => 0,
9..=16 => 1,
17..=24 => 2,
25..=32 => 3,
_ => return None,
};
self.patterns[idx].as_ref()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sliding_window() {
let sa = SparseAttention::new(
AttentionPattern::SlidingWindow { window_size: 2 },
8,
).unwrap();
assert!(sa.should_attend_at(0, 0));
assert!(!sa.should_attend_at(0, 1));
assert!(!sa.should_attend_at(4, 1));
assert!(sa.should_attend_at(4, 2));
assert!(sa.should_attend_at(4, 3));
assert!(sa.should_attend_at(4, 4));
assert!(!sa.should_attend_at(4, 5)); }
#[test]
fn test_strided() {
let sa = SparseAttention::new(
AttentionPattern::Strided { stride: 4 },
16,
).unwrap();
assert!(sa.should_attend_at(10, 0)); assert!(sa.should_attend_at(10, 4)); assert!(sa.should_attend_at(10, 8)); assert!(sa.should_attend_at(10, 9)); assert!(sa.should_attend_at(10, 10)); assert!(!sa.should_attend_at(10, 1)); }
#[test]
fn test_sparsity() {
let full = SparseAttention::new(AttentionPattern::Full, 16).unwrap();
let sparse = SparseAttention::new(
AttentionPattern::SlidingWindow { window_size: 4 },
16,
).unwrap();
assert!(full.sparsity_ratio() > 0.99);
assert!(sparse.sparsity_ratio() < full.sparsity_ratio());
}
#[test]
fn test_block_diagonal() {
let sa = SparseAttention::new(
AttentionPattern::BlockDiagonal { block_size: 4 },
16,
).unwrap();
assert!(!sa.should_attend_at(5, 3)); assert!(sa.should_attend_at(5, 4)); assert!(sa.should_attend_at(5, 5)); assert!(!sa.should_attend_at(5, 6)); assert!(!sa.should_attend_at(5, 8)); }
#[test]
fn test_bigbird() {
let sa = SparseAttention::new(
AttentionPattern::BigBird { window_size: 2, global_tokens: 2 },
16,
).unwrap();
assert!(sa.should_attend_at(10, 0)); assert!(sa.should_attend_at(10, 1)); assert!(!sa.should_attend_at(10, 5)); assert!(sa.should_attend_at(10, 8)); assert!(sa.should_attend_at(10, 10)); }
}