pub mod combine;
pub mod forward;
#[cfg(feature = "gpu")]
pub mod gpu;
pub mod index;
pub mod llama3_bridge;
pub mod proxy;
#[cfg(feature = "quant")]
pub mod quant;
pub mod scheduler;
mod simd;
pub mod topk;
pub mod types;
pub use combine::lse_combine;
pub use forward::{kvouter_forward, ForwardPartials};
pub use index::build_kvouter_index;
pub use proxy::compute_proxy_block_max_scores;
pub use scheduler::{build_fixed_schedule, enumerate_work_units, WorkSplit};
pub use topk::{sparse_topk_select, sparse_topk_select_batch};
pub use types::{BlockTables, CuSeqlensQ, KvOuterIndex, SparseAttentionError, SparseSelection};
#[allow(clippy::too_many_arguments)]
pub fn kvouter_attention(
q: &[f32],
k_pages: &[f32],
v_pages: &[f32],
selected: &SparseSelection,
block_tables: &BlockTables,
cu_seqlens_q: &CuSeqlensQ,
used_kv_lens: Option<&[i32]>,
hq: usize,
hkv: usize,
head_dim: usize,
block_size: usize,
page_size: usize,
softmax_scale: f32,
causal: bool,
) -> Result<Vec<f32>, SparseAttentionError> {
let idx = build_kvouter_index(
selected,
block_tables,
cu_seqlens_q,
used_kv_lens,
block_size,
page_size,
)?;
let partials = kvouter_forward(
q,
k_pages,
v_pages,
&idx,
block_tables,
cu_seqlens_q,
used_kv_lens,
hq,
hkv,
head_dim,
block_size,
page_size,
softmax_scale,
causal,
)?;
lse_combine(&partials, &idx, hq)
}
#[cfg(test)]
mod api_tests {
use super::*;
#[test]
fn kvouter_attention_one_shot_matches_manual_chain() {
let tq = 1;
let hq = 1;
let hkv = 1;
let head_dim = 2;
let page_size = 2;
let block_size = 2;
let num_pages = 2;
let q = vec![1.0f32, 0.0];
let k = vec![1.0, 0.0, 0.0, 1.0, 0.5, 0.5, 0.0, 0.0];
let v = vec![0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8];
let sel = SparseSelection::new(vec![0, 1], tq, hkv, 2).unwrap();
let tbl = BlockTables::new(vec![0, 1], 1, 2).unwrap();
let cu = CuSeqlensQ::new(vec![0, tq as i64]).unwrap();
let used = vec![num_pages * page_size as i32];
let softmax_scale = 1.0f32 / (head_dim as f32).sqrt();
let one_shot = kvouter_attention(
&q,
&k,
&v,
&sel,
&tbl,
&cu,
Some(&used),
hq,
hkv,
head_dim,
block_size,
page_size,
softmax_scale,
false,
)
.unwrap();
let idx = build_kvouter_index(&sel, &tbl, &cu, Some(&used), block_size, page_size).unwrap();
let partials = kvouter_forward(
&q,
&k,
&v,
&idx,
&tbl,
&cu,
Some(&used),
hq,
hkv,
head_dim,
block_size,
page_size,
softmax_scale,
false,
)
.unwrap();
let manual = lse_combine(&partials, &idx, hq).unwrap();
assert_eq!(one_shot.len(), manual.len());
for (a, b) in one_shot.iter().zip(manual.iter()) {
assert!((a - b).abs() < 1e-6);
}
}
}