use super::simd;
use super::types::{BlockTables, CuSeqlensQ, SparseAttentionError};
#[cfg(feature = "parallel")]
use rayon::prelude::*;
pub fn compute_proxy_block_max_scores(
proxy_q: &[f32],
k_pages: &[f32],
block_tables: &BlockTables,
cu_seqlens_q: &CuSeqlensQ,
used_kv_lens: Option<&[i32]>,
hkv: usize,
head_dim: usize,
block_size: usize,
page_size: usize,
) -> Result<Vec<f32>, SparseAttentionError> {
let tq = cu_seqlens_q.total_tq();
if tq == 0 || hkv == 0 || head_dim == 0 {
return Err(SparseAttentionError::EmptyInput);
}
if page_size == 0 || block_size == 0 || !block_size.is_multiple_of(page_size) {
return Err(SparseAttentionError::BlockPageMismatch {
block_size,
page_size,
});
}
let pages_per_block = block_size / page_size;
let msb = block_tables.max_pages / pages_per_block;
if msb == 0 {
return Err(SparseAttentionError::BlockPageMismatch {
block_size,
page_size,
});
}
let expected_q = tq * hkv * head_dim;
if proxy_q.len() != expected_q {
return Err(SparseAttentionError::ShapeMismatch {
what: "proxy_q",
expected: expected_q,
got: proxy_q.len(),
});
}
let page_stride = hkv * page_size * head_dim;
if !k_pages.len().is_multiple_of(page_stride) {
return Err(SparseAttentionError::ShapeMismatch {
what: "k_pages (not a whole number of pages)",
expected: page_stride,
got: k_pages.len(),
});
}
let num_pages = k_pages.len() / page_stride;
if cu_seqlens_q.batch_size() != block_tables.batch_size {
return Err(SparseAttentionError::ShapeMismatch {
what: "cu_seqlens_q.batch_size vs block_tables.batch_size",
expected: block_tables.batch_size,
got: cu_seqlens_q.batch_size(),
});
}
if let Some(used) = used_kv_lens {
if used.len() != cu_seqlens_q.batch_size() {
return Err(SparseAttentionError::ShapeMismatch {
what: "used_kv_lens",
expected: cu_seqlens_q.batch_size(),
got: used.len(),
});
}
}
let scale = 1.0f32 / (head_dim as f32).sqrt();
let mut out = vec![f32::NEG_INFINITY; tq * hkv * msb];
let batch_size = cu_seqlens_q.batch_size();
let mut batch_of_tq: Vec<usize> = vec![0; tq];
{
let mut b = 0usize;
for i in 0..tq {
while b + 1 < batch_size && (i as i64) >= cu_seqlens_q.prefix[b + 1] {
b += 1;
}
batch_of_tq[i] = b;
}
}
let row_stride = hkv * msb;
let fill_row = |i: usize, row: &mut [f32]| -> Result<(), SparseAttentionError> {
let b = batch_of_tq[i];
let lk = used_kv_lens.map_or(msb * block_size, |u| u[b].max(0) as usize);
let valid_slots = lk.div_ceil(block_size).min(msb);
for h in 0..hkv {
let q_base = (i * hkv + h) * head_dim;
let q = &proxy_q[q_base..q_base + head_dim];
for blk in 0..valid_slots {
let mut block_max = f32::NEG_INFINITY;
let block_start = blk * block_size;
let block_end = ((blk + 1) * block_size).min(lk);
if block_end <= block_start {
continue;
}
for page_local in 0..pages_per_block {
let page_slot = blk * pages_per_block + page_local;
let page_seq_start = blk * block_size + page_local * page_size;
let page_seq_end = page_seq_start + page_size;
let page_kv_start = block_start.max(page_seq_start);
let page_kv_end = block_end.min(page_seq_end);
if page_kv_end <= page_kv_start {
continue;
}
let page_id = block_tables.get(b, page_slot);
if page_id < 0 {
continue;
}
let page_id_u = page_id as usize;
if page_id_u >= num_pages {
return Err(SparseAttentionError::ShapeMismatch {
what: "block_tables page id",
expected: num_pages,
got: page_id_u,
});
}
let base_page = page_id_u * page_stride + h * page_size * head_dim;
let pos_start = page_kv_start - page_seq_start;
let pos_end = page_kv_end - page_seq_start;
for pos in pos_start..pos_end {
let k_base = base_page + pos * head_dim;
let k = &k_pages[k_base..k_base + head_dim];
let s = simd::dot(q, k) * scale;
if s > block_max {
block_max = s;
}
}
}
row[h * msb + blk] = block_max;
}
}
Ok(())
};
#[cfg(feature = "parallel")]
out.par_chunks_mut(row_stride)
.enumerate()
.try_for_each(|(i, row)| fill_row(i, row))?;
#[cfg(not(feature = "parallel"))]
for (i, row) in out.chunks_mut(row_stride).enumerate() {
fill_row(i, row)?;
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::super::topk::sparse_topk_select;
use super::*;
fn tiny_setup() -> (Vec<f32>, Vec<f32>, BlockTables, CuSeqlensQ) {
let q = vec![1.0, 0.0];
let k = vec![
1.0, 0.0, 0.0, 1.0, 0.5, 0.5, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ];
let tbl = BlockTables::new(vec![0, 1, 2, 3], 1, 4).unwrap();
let cu = CuSeqlensQ::new(vec![0, 1]).unwrap();
(q, k, tbl, cu)
}
#[test]
fn proxy_computes_per_block_max_score() {
let (q, k, tbl, cu) = tiny_setup();
let scores = compute_proxy_block_max_scores(&q, &k, &tbl, &cu, None, 1, 2, 2, 2).unwrap();
let s = 1.0f32 / 2.0f32.sqrt();
assert!((scores[0] - 1.0 * s).abs() < 1e-6);
assert!((scores[1] - 0.5 * s).abs() < 1e-6);
assert!((scores[2] - 2.0 * s).abs() < 1e-6);
assert!((scores[3] - 0.0 * s).abs() < 1e-6);
}
#[test]
fn proxy_masks_beyond_used_kv_lens() {
let (q, k, tbl, cu) = tiny_setup();
let scores =
compute_proxy_block_max_scores(&q, &k, &tbl, &cu, Some(&[3]), 1, 2, 2, 2).unwrap();
let s = 1.0f32 / 2.0f32.sqrt();
assert!((scores[0] - 1.0 * s).abs() < 1e-6);
assert!((scores[1] - 0.5 * s).abs() < 1e-6);
assert!(scores[2].is_infinite() && scores[2] < 0.0);
assert!(scores[3].is_infinite() && scores[3] < 0.0);
}
#[test]
fn proxy_negative_page_id_is_skipped() {
let q = vec![1.0, 0.0];
let k = vec![
1.0, 0.0, 0.0, 1.0, 2.0, 0.0, 0.0, 0.0, ];
let tbl = BlockTables::new(vec![0, -1], 1, 2).unwrap();
let cu = CuSeqlensQ::new(vec![0, 1]).unwrap();
let scores = compute_proxy_block_max_scores(&q, &k, &tbl, &cu, None, 1, 2, 2, 2).unwrap();
let s = 1.0f32 / 2.0f32.sqrt();
assert!((scores[0] - 1.0 * s).abs() < 1e-6);
assert!(scores[1].is_infinite() && scores[1] < 0.0);
}
#[test]
fn proxy_and_topk_pipeline_end_to_end() {
let (q, k, tbl, cu) = tiny_setup();
let scores = compute_proxy_block_max_scores(&q, &k, &tbl, &cu, None, 1, 2, 2, 2).unwrap();
let row = &scores[0..4];
let top = sparse_topk_select(row, 2, 4);
assert_eq!(top, vec![2, 0]);
}
}