use super::index::build_kvouter_index;
use super::proxy::compute_proxy_block_max_scores;
use super::topk::sparse_topk_select_batch;
use super::types::{BlockTables, CuSeqlensQ, SparseAttentionError, SparseSelection};
use super::{kvouter_attention, kvouter_forward, lse_combine};
#[derive(Debug, Clone, Copy)]
pub struct DenseKvCacheView<'a> {
pub k: &'a [f32],
pub v: &'a [f32],
pub seq_len: usize,
pub hkv: usize,
pub head_dim: usize,
}
#[derive(Debug, Clone, Copy)]
pub struct BridgeConfig {
pub hq: usize,
pub hkv: usize,
pub head_dim: usize,
pub block_size: usize,
pub page_size: usize,
pub softmax_scale: f32,
pub causal: bool,
pub topk: usize,
}
pub fn llama3_sparse_attention(
q: &[f32],
kv: &DenseKvCacheView<'_>,
tq: usize,
cfg: &BridgeConfig,
) -> Result<Vec<f32>, SparseAttentionError> {
if cfg.hq == 0 || cfg.hkv == 0 || !cfg.hq.is_multiple_of(cfg.hkv) {
return Err(SparseAttentionError::HeadCountMismatch {
hq: cfg.hq,
hkv: cfg.hkv,
});
}
if cfg.head_dim == 0 || cfg.block_size == 0 || cfg.page_size == 0 {
return Err(SparseAttentionError::BlockPageMismatch {
block_size: cfg.block_size,
page_size: cfg.page_size,
});
}
if !cfg.block_size.is_multiple_of(cfg.page_size) {
return Err(SparseAttentionError::BlockPageMismatch {
block_size: cfg.block_size,
page_size: cfg.page_size,
});
}
if kv.hkv != cfg.hkv || kv.head_dim != cfg.head_dim {
return Err(SparseAttentionError::HeadDimMismatch);
}
let expected_kv = kv.seq_len * kv.hkv * kv.head_dim;
if kv.k.len() != expected_kv || kv.v.len() != expected_kv {
return Err(SparseAttentionError::ShapeMismatch {
what: "DenseKvCacheView k/v",
expected: expected_kv,
got: kv.k.len().max(kv.v.len()),
});
}
if q.len() != tq * cfg.hq * cfg.head_dim {
return Err(SparseAttentionError::ShapeMismatch {
what: "q",
expected: tq * cfg.hq * cfg.head_dim,
got: q.len(),
});
}
let num_pages = kv.seq_len.div_ceil(cfg.page_size).max(1);
let msb = num_pages / (cfg.block_size / cfg.page_size);
if msb == 0 {
return Err(SparseAttentionError::BlockPageMismatch {
block_size: cfg.block_size,
page_size: cfg.page_size,
});
}
let page_stride = cfg.hkv * cfg.page_size * cfg.head_dim;
let mut k_pages = vec![0.0f32; num_pages * page_stride];
let mut v_pages = vec![0.0f32; num_pages * page_stride];
for seq_pos in 0..kv.seq_len {
let page_id = seq_pos / cfg.page_size;
let pos_in_page = seq_pos % cfg.page_size;
for h in 0..cfg.hkv {
let dense_off = (seq_pos * cfg.hkv + h) * cfg.head_dim;
let paged_off = page_id * page_stride
+ h * cfg.page_size * cfg.head_dim
+ pos_in_page * cfg.head_dim;
k_pages[paged_off..paged_off + cfg.head_dim]
.copy_from_slice(&kv.k[dense_off..dense_off + cfg.head_dim]);
v_pages[paged_off..paged_off + cfg.head_dim]
.copy_from_slice(&kv.v[dense_off..dense_off + cfg.head_dim]);
}
}
let block_tables_data: Vec<i32> = (0..num_pages as i32).collect();
let block_tables = BlockTables::new(block_tables_data, 1, num_pages)?;
let cu_seqlens_q = CuSeqlensQ::new(vec![0, tq as i64])?;
let used_kv_lens = vec![kv.seq_len as i32];
let effective_topk = if cfg.topk == 0 || cfg.topk >= msb {
msb
} else {
cfg.topk
};
let selection = if effective_topk == msb {
let mut sel_flat = Vec::with_capacity(tq * cfg.hkv * msb);
for _i in 0..tq {
for _h in 0..cfg.hkv {
for r in 0..msb {
sel_flat.push(r as i32);
}
}
}
SparseSelection::new(sel_flat, tq, cfg.hkv, msb)?
} else {
let qhead = cfg.hq / cfg.hkv;
let mut proxy_q = Vec::with_capacity(tq * cfg.hkv * cfg.head_dim);
for i in 0..tq {
for h in 0..cfg.hkv {
let hq_abs = h * qhead;
let base = (i * cfg.hq + hq_abs) * cfg.head_dim;
proxy_q.extend_from_slice(&q[base..base + cfg.head_dim]);
}
}
let scores = compute_proxy_block_max_scores(
&proxy_q,
&k_pages,
&block_tables,
&cu_seqlens_q,
Some(&used_kv_lens),
cfg.hkv,
cfg.head_dim,
cfg.block_size,
cfg.page_size,
)?;
let rows = tq * cfg.hkv;
let caps = vec![msb; rows];
let sel_flat = sparse_topk_select_batch(&scores, rows, msb, effective_topk, &caps);
SparseSelection::new(sel_flat, tq, cfg.hkv, effective_topk)?
};
if effective_topk == msb {
kvouter_attention(
q,
&k_pages,
&v_pages,
&selection,
&block_tables,
&cu_seqlens_q,
Some(&used_kv_lens),
cfg.hq,
cfg.hkv,
cfg.head_dim,
cfg.block_size,
cfg.page_size,
cfg.softmax_scale,
cfg.causal,
)
} else {
let idx = build_kvouter_index(
&selection,
&block_tables,
&cu_seqlens_q,
Some(&used_kv_lens),
cfg.block_size,
cfg.page_size,
)?;
let partials = kvouter_forward(
q,
&k_pages,
&v_pages,
&idx,
&block_tables,
&cu_seqlens_q,
Some(&used_kv_lens),
cfg.hq,
cfg.hkv,
cfg.head_dim,
cfg.block_size,
cfg.page_size,
cfg.softmax_scale,
cfg.causal,
)?;
lse_combine(&partials, &idx, cfg.hq)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::attention::scaled_dot_product_attention;
fn naive_dense_reference(
q: &[f32],
kv: &DenseKvCacheView<'_>,
tq: usize,
hq: usize,
) -> Vec<f32> {
let head_dim = kv.head_dim;
let hkv = kv.hkv;
let qhead = hq / hkv;
let mut out = vec![0.0f32; tq * hq * head_dim];
for h in 0..hkv {
let k_rows: Vec<Vec<f32>> = (0..kv.seq_len)
.map(|s| {
let off = (s * hkv + h) * head_dim;
kv.k[off..off + head_dim].to_vec()
})
.collect();
let v_rows: Vec<Vec<f32>> = (0..kv.seq_len)
.map(|s| {
let off = (s * hkv + h) * head_dim;
kv.v[off..off + head_dim].to_vec()
})
.collect();
for qh in 0..qhead {
let hq_abs = h * qhead + qh;
let q_rows: Vec<Vec<f32>> = (0..tq)
.map(|i| {
let off = (i * hq + hq_abs) * head_dim;
q[off..off + head_dim].to_vec()
})
.collect();
let out_rows = scaled_dot_product_attention(&q_rows, &k_rows, &v_rows, None);
for (i, row) in out_rows.into_iter().enumerate() {
let dst_off = (i * hq + hq_abs) * head_dim;
out[dst_off..dst_off + head_dim].copy_from_slice(&row);
}
}
}
out
}
type GqaFixture = (
Vec<f32>,
Vec<f32>,
Vec<f32>,
usize,
usize,
usize,
usize,
usize,
);
fn fixture_gqa() -> GqaFixture {
let tq = 3;
let hq = 4;
let hkv = 2;
let head_dim = 8;
let seq_len = 12;
let q: Vec<f32> = (0..tq * hq * head_dim)
.map(|i| ((i as f32) * 0.05).sin())
.collect();
let k: Vec<f32> = (0..seq_len * hkv * head_dim)
.map(|i| ((i as f32) * 0.07).cos())
.collect();
let v: Vec<f32> = (0..seq_len * hkv * head_dim)
.map(|i| ((i as f32) * 0.03).sin())
.collect();
(q, k, v, tq, hq, hkv, head_dim, seq_len)
}
#[test]
fn bridge_dense_fallback_matches_naive_dense() {
let (q, k, v, tq, hq, hkv, head_dim, seq_len) = fixture_gqa();
let kv = DenseKvCacheView {
k: &k,
v: &v,
seq_len,
hkv,
head_dim,
};
let cfg = BridgeConfig {
hq,
hkv,
head_dim,
block_size: 4,
page_size: 4,
softmax_scale: 1.0f32 / (head_dim as f32).sqrt(),
causal: false,
topk: 0, };
let bridge_out = llama3_sparse_attention(&q, &kv, tq, &cfg).unwrap();
let dense_out = naive_dense_reference(&q, &kv, tq, hq);
assert_eq!(bridge_out.len(), dense_out.len());
for (i, (a, b)) in bridge_out.iter().zip(&dense_out).enumerate() {
let rel = (a - b).abs() / b.abs().max(1e-6);
assert!(rel < 1e-4, "elem {i}: bridge={a}, dense={b}, rel={rel}");
}
}
#[test]
fn bridge_topk_equal_to_num_blocks_matches_naive_dense() {
let (q, k, v, tq, hq, hkv, head_dim, seq_len) = fixture_gqa();
let kv = DenseKvCacheView {
k: &k,
v: &v,
seq_len,
hkv,
head_dim,
};
let cfg = BridgeConfig {
hq,
hkv,
head_dim,
block_size: 4,
page_size: 4,
softmax_scale: 1.0f32 / (head_dim as f32).sqrt(),
causal: false,
topk: 3,
};
let bridge_out = llama3_sparse_attention(&q, &kv, tq, &cfg).unwrap();
let dense_out = naive_dense_reference(&q, &kv, tq, hq);
for (i, (a, b)) in bridge_out.iter().zip(&dense_out).enumerate() {
let rel = (a - b).abs() / b.abs().max(1e-6);
assert!(rel < 1e-4, "elem {i}: bridge={a}, dense={b}, rel={rel}");
}
}
#[test]
fn bridge_topk_partial_produces_bounded_output() {
let (q, k, v, tq, hq, hkv, head_dim, seq_len) = fixture_gqa();
let kv = DenseKvCacheView {
k: &k,
v: &v,
seq_len,
hkv,
head_dim,
};
let cfg = BridgeConfig {
hq,
hkv,
head_dim,
block_size: 4,
page_size: 4,
softmax_scale: 1.0f32 / (head_dim as f32).sqrt(),
causal: false,
topk: 2, };
let out = llama3_sparse_attention(&q, &kv, tq, &cfg).unwrap();
assert_eq!(out.len(), tq * hq * head_dim);
for (i, v) in out.iter().enumerate() {
assert!(v.is_finite(), "non-finite output at {i}: {v}");
}
}
#[test]
fn bridge_rejects_shape_mismatch() {
let kv_k = vec![0.0f32; 4 * 2 * 8]; let kv_v = kv_k.clone();
let kv = DenseKvCacheView {
k: &kv_k,
v: &kv_v,
seq_len: 4,
hkv: 2,
head_dim: 8,
};
let cfg = BridgeConfig {
hq: 4,
hkv: 2,
head_dim: 8,
block_size: 4,
page_size: 4,
softmax_scale: 1.0,
causal: false,
topk: 0,
};
let err = llama3_sparse_attention(&[0.0f32; 30], &kv, 1, &cfg).unwrap_err();
matches!(err, SparseAttentionError::ShapeMismatch { .. });
}
#[test]
fn bridge_rejects_head_dim_mismatch() {
let kv_k = vec![0.0f32; 4 * 2 * 8];
let kv_v = kv_k.clone();
let kv = DenseKvCacheView {
k: &kv_k,
v: &kv_v,
seq_len: 4,
hkv: 2,
head_dim: 8,
};
let cfg = BridgeConfig {
hq: 4,
hkv: 2,
head_dim: 16, block_size: 4,
page_size: 4,
softmax_scale: 1.0,
causal: false,
topk: 0,
};
let err = llama3_sparse_attention(&vec![0.0f32; 4 * 16], &kv, 1, &cfg).unwrap_err();
matches!(err, SparseAttentionError::HeadDimMismatch);
}
}