use crate::{
codec::{FibCodeV1, FibQuantizer},
scoring::FibScorer,
FibQuantError, Result,
};
#[derive(Debug, Clone)]
pub struct CompressedAttentionOutput {
pub logits: Vec<f32>,
pub probabilities: Vec<f32>,
pub output: Vec<f32>,
pub top_k_indices: Vec<usize>,
pub decompression_count: usize,
}
pub fn compressed_attention_logits(
query: &[f32],
compressed_keys: &[FibCodeV1],
scorer: &FibScorer,
) -> Result<Vec<f32>> {
if query.is_empty() {
return Err(FibQuantError::ZeroDimension);
}
if compressed_keys.is_empty() {
return Ok(Vec::new());
}
check_finite(query)?;
let head_dim = query.len();
let scale = (head_dim as f64).sqrt() as f32;
let prepared = scorer.prepare_query(query)?;
let mut logits = Vec::with_capacity(compressed_keys.len());
for code in compressed_keys {
let ip = scorer.score_prepared(&prepared, code)?;
logits.push(ip / scale);
}
Ok(logits)
}
pub fn compressed_attention_topk(
query: &[f32],
compressed_keys: &[FibCodeV1],
compressed_values: &[FibCodeV1],
scorer: &FibScorer,
quantizer: &FibQuantizer,
top_k: usize,
) -> Result<CompressedAttentionOutput> {
if query.is_empty() {
return Err(FibQuantError::ZeroDimension);
}
if compressed_keys.is_empty() {
return Err(FibQuantError::CorruptPayload(
"compressed_attention_topk: empty keys".into(),
));
}
if compressed_keys.len() != compressed_values.len() {
return Err(FibQuantError::CorruptPayload(format!(
"compressed_attention_topk: {} keys but {} values",
compressed_keys.len(),
compressed_values.len()
)));
}
check_finite(query)?;
let logits = compressed_attention_logits(query, compressed_keys, scorer)?;
let probabilities = softmax(&logits)?;
let n = compressed_keys.len();
let k = top_k.min(n).max(1);
let top_k_indices = topk_indices_by_probability(&probabilities, k);
let head_dim = query.len();
let mut output = vec![0.0f64; head_dim];
let mut decompression_count = 0usize;
for &idx in &top_k_indices {
let decoded = quantizer.decode(&compressed_values[idx])?;
decompression_count += 1;
let prob = f64::from(probabilities[idx]);
for (channel, acc) in decoded.iter().zip(output.iter_mut()) {
*acc += prob * f64::from(*channel);
}
}
let output: Vec<f32> = output.into_iter().map(|v| v as f32).collect();
Ok(CompressedAttentionOutput {
logits,
probabilities,
output,
top_k_indices,
decompression_count,
})
}
fn softmax(logits: &[f32]) -> Result<Vec<f32>> {
if logits.is_empty() {
return Err(FibQuantError::ZeroDimension);
}
check_finite(logits)?;
let max = logits
.iter()
.copied()
.fold(f32::NEG_INFINITY, |acc, v| acc.max(v));
let mut sum = 0.0f64;
let mut exps = Vec::with_capacity(logits.len());
for &v in logits {
let exp = f64::from(v - max).exp();
sum += exp;
exps.push(exp);
}
if !sum.is_finite() || sum <= 0.0 {
return Err(FibQuantError::NumericalFailure(
"compressed attention softmax underflow".into(),
));
}
Ok(exps.into_iter().map(|e| (e / sum) as f32).collect())
}
fn topk_indices_by_probability(probabilities: &[f32], k: usize) -> Vec<usize> {
let mut indexed: Vec<(usize, f32)> = probabilities.iter().copied().enumerate().collect();
indexed.sort_by(|a, b| b.1.total_cmp(&a.1).then_with(|| a.0.cmp(&b.0)));
indexed.into_iter().take(k).map(|(idx, _)| idx).collect()
}
fn check_finite(values: &[f32]) -> Result<()> {
if values.iter().any(|v| !v.is_finite()) {
return Err(FibQuantError::CorruptPayload(
"compressed attention input contains non-finite value".into(),
));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::profile::FibQuantProfileV1;
fn build_test_quantizer() -> Result<FibQuantizer> {
let profile = FibQuantProfileV1::paper_default(8, 2, 32, 7)?;
FibQuantizer::new(profile)
}
fn mse(a: &[f32], b: &[f32]) -> f64 {
assert_eq!(a.len(), b.len(), "mse length mismatch");
if a.is_empty() {
return 0.0;
}
let sum: f64 = a
.iter()
.zip(b)
.map(|(x, y)| {
let d = f64::from(*x) - f64::from(*y);
d * d
})
.sum();
sum / a.len() as f64
}
#[test]
fn test_compressed_attention_vs_reference() -> Result<()> {
let quantizer = build_test_quantizer()?;
let scorer = FibScorer::new(quantizer.clone())?;
let head_dim = 8usize;
let query: Vec<f32> = vec![0.1, -0.2, 0.3, 0.4, -0.5, 0.6, -0.7, 0.8];
let raw_keys: Vec<Vec<f32>> = vec![
vec![0.8, -0.1, 0.2, 0.3, -0.4, 0.5, -0.6, 0.7],
vec![-0.3, 0.4, -0.5, 0.6, 0.7, -0.8, 0.1, -0.2],
vec![0.5, 0.5, -0.5, 0.1, 0.2, -0.3, 0.4, 0.5],
vec![-0.2, 0.3, 0.4, -0.6, 0.5, -0.1, 0.2, -0.7],
vec![0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1],
vec![0.6, -0.5, 0.4, -0.3, 0.2, -0.1, 0.8, -0.6],
];
let raw_values: Vec<Vec<f32>> = vec![
vec![0.2, 0.3, -0.1, 0.5, 0.4, -0.2, 0.6, 0.1],
vec![-0.5, 0.4, 0.3, -0.2, 0.6, 0.1, -0.3, 0.5],
vec![0.7, -0.3, 0.2, 0.4, -0.1, 0.5, 0.3, -0.4],
vec![0.1, -0.6, 0.3, 0.2, -0.4, 0.7, -0.1, 0.3],
vec![0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3],
vec![-0.2, 0.5, -0.4, 0.6, -0.3, 0.2, 0.7, -0.5],
];
let compressed_keys: Vec<FibCodeV1> = raw_keys
.iter()
.map(|k| quantizer.encode(k))
.collect::<Result<Vec<_>>>()?;
let compressed_values: Vec<FibCodeV1> = raw_values
.iter()
.map(|v| quantizer.encode(v))
.collect::<Result<Vec<_>>>()?;
let top_k = 4usize;
let compressed_out = compressed_attention_topk(
&query,
&compressed_keys,
&compressed_values,
&scorer,
&quantizer,
top_k,
)?;
assert_eq!(
compressed_out.decompression_count, top_k,
"decompression_count should be {}, got {}",
top_k, compressed_out.decompression_count
);
assert_eq!(compressed_out.top_k_indices.len(), top_k);
assert_eq!(compressed_out.output.len(), head_dim);
assert_eq!(compressed_out.logits.len(), raw_keys.len());
assert_eq!(compressed_out.probabilities.len(), raw_keys.len());
let prob_sum: f64 = compressed_out
.probabilities
.iter()
.map(|p| f64::from(*p))
.sum();
assert!(
(prob_sum - 1.0).abs() < 1e-5,
"probabilities should sum to 1.0, got {}",
prob_sum
);
let decoded_keys: Vec<Vec<f32>> = compressed_keys
.iter()
.map(|c| quantizer.decode(c))
.collect::<Result<Vec<_>>>()?;
let decoded_values: Vec<Vec<f32>> = compressed_values
.iter()
.map(|c| quantizer.decode(c))
.collect::<Result<Vec<_>>>()?;
let flat_decoded_keys: Vec<f32> = decoded_keys.iter().flatten().copied().collect();
let flat_decoded_values: Vec<f32> = decoded_values.iter().flatten().copied().collect();
let ref_logits = super::super::attention_ref::reference_attention_logits(
&query,
&flat_decoded_keys,
head_dim,
)?;
let ref_probs = softmax_local(&ref_logits)?;
let ref_output = super::super::attention_ref::reference_value_aggregation(
&ref_probs,
&flat_decoded_values,
head_dim,
)?;
let logit_mse = mse(&compressed_out.logits, &ref_logits);
assert!(logit_mse < 2.0, "logit MSE too large: {}", logit_mse);
let output_mse = mse(&compressed_out.output, &ref_output);
assert!(output_mse < 0.5, "output MSE too large: {}", output_mse);
let ref_topk = topk_indices_by_probability(&ref_probs, top_k);
let overlap = compressed_out
.top_k_indices
.iter()
.filter(|idx| ref_topk.contains(idx))
.count();
let agreement = overlap as f64 / top_k as f64;
assert!(
agreement >= 0.5,
"top-K agreement too low: {}/{} (compressed={:?}, ref={:?})",
overlap,
top_k,
compressed_out.top_k_indices,
ref_topk
);
Ok(())
}
#[test]
fn test_empty_keys_returns_empty_logits() -> Result<()> {
let quantizer = build_test_quantizer()?;
let scorer = FibScorer::new(quantizer.clone())?;
let query: Vec<f32> = vec![0.1, -0.2, 0.3, 0.4, -0.5, 0.6, -0.7, 0.8];
let logits = compressed_attention_logits(&query, &[], &scorer)?;
assert!(logits.is_empty());
Ok(())
}
#[test]
fn test_single_key_logit_finite() -> Result<()> {
let quantizer = build_test_quantizer()?;
let scorer = FibScorer::new(quantizer.clone())?;
let query: Vec<f32> = vec![0.1, -0.2, 0.3, 0.4, -0.5, 0.6, -0.7, 0.8];
let key: Vec<f32> = vec![0.5, 0.5, -0.5, 0.1, 0.2, -0.3, 0.4, 0.5];
let compressed_key = quantizer.encode(&key)?;
let logits = compressed_attention_logits(&query, &[compressed_key], &scorer)?;
assert_eq!(logits.len(), 1);
assert!(logits[0].is_finite());
Ok(())
}
#[test]
fn test_topk_exceeds_n_clamps() -> Result<()> {
let quantizer = build_test_quantizer()?;
let scorer = FibScorer::new(quantizer.clone())?;
let head_dim = 8usize;
let query: Vec<f32> = vec![0.1, -0.2, 0.3, 0.4, -0.5, 0.6, -0.7, 0.8];
let keys: Vec<Vec<f32>> = vec![
vec![0.8, -0.1, 0.2, 0.3, -0.4, 0.5, -0.6, 0.7],
vec![-0.3, 0.4, -0.5, 0.6, 0.7, -0.8, 0.1, -0.2],
vec![0.5, 0.5, -0.5, 0.1, 0.2, -0.3, 0.4, 0.5],
];
let compressed_keys: Vec<FibCodeV1> = keys
.iter()
.map(|k| quantizer.encode(k))
.collect::<Result<Vec<_>>>()?;
let compressed_values: Vec<FibCodeV1> = compressed_keys.clone();
let out = compressed_attention_topk(
&query,
&compressed_keys,
&compressed_values,
&scorer,
&quantizer,
10,
)?;
assert_eq!(out.decompression_count, 3);
assert_eq!(out.top_k_indices.len(), 3);
assert_eq!(out.output.len(), head_dim);
Ok(())
}
fn softmax_local(logits: &[f32]) -> Result<Vec<f32>> {
use crate::FibQuantError;
if logits.is_empty() {
return Err(FibQuantError::ZeroDimension);
}
let max = logits
.iter()
.copied()
.fold(f32::NEG_INFINITY, |acc, v| acc.max(v));
let mut sum = 0.0f64;
let mut exps = Vec::with_capacity(logits.len());
for &v in logits {
let exp = f64::from(v - max).exp();
sum += exp;
exps.push(exp);
}
Ok(exps.into_iter().map(|e| (e / sum) as f32).collect())
}
}