pub struct KvCacheMps {
basis_k: Vec<f32>,
basis_v: Vec<f32>,
coeff_k: Vec<Vec<f32>>,
coeff_v: Vec<Vec<f32>>,
chi_k: usize,
chi_v: usize,
chi_max: usize,
d_k: usize,
d_v: usize,
}
impl KvCacheMps {
pub fn new(d_k: usize, d_v: usize, chi_max: usize) -> Self {
assert!(chi_max >= 1, "chi_max must be ≥ 1");
Self {
basis_k: Vec::new(),
basis_v: Vec::new(),
coeff_k: Vec::new(),
coeff_v: Vec::new(),
chi_k: 0,
chi_v: 0,
chi_max,
d_k,
d_v,
}
}
pub fn append(&mut self, k: &[f32], v: &[f32]) {
assert_eq!(k.len(), self.d_k);
assert_eq!(v.len(), self.d_v);
let c_k = project_and_extend(
&mut self.basis_k,
&mut self.chi_k,
self.chi_max,
self.d_k,
k,
);
self.coeff_k.push(c_k);
let c_v = project_and_extend(
&mut self.basis_v,
&mut self.chi_v,
self.chi_max,
self.d_v,
v,
);
self.coeff_v.push(c_v);
}
pub fn token_count(&self) -> usize {
self.coeff_k.len()
}
pub fn max_bond_dim(&self) -> usize {
self.chi_k.max(self.chi_v)
}
pub fn attend(&self, query: &[f32], scale: f32) -> Vec<f32> {
let n = self.token_count();
if n == 0 {
return vec![0.0; self.d_v];
}
let q_comp: Vec<f32> = (0..self.chi_k)
.map(|k| {
let col = &self.basis_k[k * self.d_k..(k + 1) * self.d_k];
col.iter().zip(query).map(|(b, q)| b * q).sum()
})
.collect();
let mut scores: Vec<f32> = self
.coeff_k
.iter()
.map(|c| {
q_comp
.iter()
.zip(c.iter())
.map(|(q, ci)| q * ci)
.sum::<f32>()
* scale
})
.collect();
let max_s = scores.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
let mut weights: Vec<f32> = scores.iter_mut().map(|s| (*s - max_s).exp()).collect();
let sum_w: f32 = weights.iter().sum();
for w in &mut weights {
*w /= sum_w;
}
let mut out = vec![0.0f32; self.d_v];
for (w, c_v) in weights.iter().zip(self.coeff_v.iter()) {
for (k, &ck) in c_v.iter().enumerate() {
let wck = w * ck;
let col = &self.basis_v[k * self.d_v..(k + 1) * self.d_v];
for (o, b) in out.iter_mut().zip(col) {
*o += wck * b;
}
}
}
out
}
pub fn compression_ratio(&self) -> f64 {
let n = self.token_count();
if n == 0 {
return 1.0;
}
let flat = n * (self.d_k + self.d_v);
let compressed =
self.chi_k * self.d_k + self.chi_v * self.d_v + n * self.chi_k + n * self.chi_v;
flat as f64 / compressed as f64
}
}
fn project_and_extend(
basis: &mut Vec<f32>,
chi: &mut usize,
chi_max: usize,
d: usize,
vec: &[f32],
) -> Vec<f32> {
let mut coeff: Vec<f32> = (0..*chi)
.map(|k| {
let col = &basis[k * d..(k + 1) * d];
col.iter().zip(vec).map(|(b, v)| b * v).sum()
})
.collect();
if *chi >= chi_max {
return coeff;
}
let mut residual = vec.to_vec();
for k in 0..*chi {
let c = coeff[k];
let col = &basis[k * d..(k + 1) * d];
for (r, b) in residual.iter_mut().zip(col) {
*r -= c * b;
}
}
let norm: f32 = residual.iter().map(|x| x * x).sum::<f32>().sqrt();
let vec_norm: f32 = vec.iter().map(|x| x * x).sum::<f32>().sqrt();
if norm < 1e-4 * vec_norm.max(1e-12) {
return coeff;
}
for r in residual.iter_mut() {
*r /= norm;
}
basis.extend_from_slice(&residual);
*chi += 1;
coeff.push(norm);
coeff
}
pub fn svd_truncated(
m: &[f32],
rows: usize,
cols: usize,
rank: usize,
) -> (Vec<f32>, Vec<f32>, Vec<f32>, usize) {
let rank = rank.min(rows).min(cols);
if rows <= cols {
svd_via_gram_left(m, rows, cols, rank)
} else {
svd_via_gram_right(m, rows, cols, rank)
}
}
fn svd_via_gram_left(
m: &[f32],
rows: usize,
cols: usize,
rank: usize,
) -> (Vec<f32>, Vec<f32>, Vec<f32>, usize) {
let mut g = vec![0.0f32; rows * rows];
for i in 0..rows {
for k in 0..cols {
let mik = m[i * cols + k];
for j in 0..rows {
g[i * rows + j] += mik * m[j * cols + k];
}
}
}
let (eigvecs, eigvals) = power_iteration_symmetric(&g, rows, rank);
const SVD_EPS: f32 = 1e-7;
let sigma_max = eigvals[0].max(0.0).sqrt();
let mut rank_kept = rank;
while rank_kept > 1 && eigvals[rank_kept - 1].max(0.0).sqrt() < SVD_EPS * sigma_max {
rank_kept -= 1;
}
rank_kept = rank_kept.max(1);
let sigma: Vec<f32> = (0..rank_kept).map(|k| eigvals[k].max(0.0).sqrt()).collect();
let mut u = vec![0.0f32; rows * rank_kept];
for i in 0..rows {
for k in 0..rank_kept {
u[i * rank_kept + k] = eigvecs[i * rank + k];
}
}
let mut vt = vec![0.0f32; rank_kept * cols];
for k in 0..rank_kept {
if sigma[k] < 1e-15 {
continue;
}
for j in 0..cols {
let mut val = 0.0f32;
for i in 0..rows {
val += m[i * cols + j] * u[i * rank_kept + k];
}
vt[k * cols + j] = val / sigma[k];
}
}
(u, sigma, vt, rank_kept)
}
fn svd_via_gram_right(
m: &[f32],
rows: usize,
cols: usize,
rank: usize,
) -> (Vec<f32>, Vec<f32>, Vec<f32>, usize) {
let mut g = vec![0.0f32; cols * cols];
for k in 0..rows {
for i in 0..cols {
let mki = m[k * cols + i];
for j in 0..cols {
g[i * cols + j] += mki * m[k * cols + j];
}
}
}
let (eigvecs, eigvals) = power_iteration_symmetric(&g, cols, rank);
const SVD_EPS: f32 = 1e-7;
let sigma_max = eigvals[0].max(0.0).sqrt();
let mut rank_kept = rank;
while rank_kept > 1 && eigvals[rank_kept - 1].max(0.0).sqrt() < SVD_EPS * sigma_max {
rank_kept -= 1;
}
rank_kept = rank_kept.max(1);
let sigma: Vec<f32> = (0..rank_kept).map(|k| eigvals[k].max(0.0).sqrt()).collect();
let mut v = vec![0.0f32; cols * rank_kept];
for i in 0..cols {
for k in 0..rank_kept {
v[i * rank_kept + k] = eigvecs[i * rank + k];
}
}
let mut vt = vec![0.0f32; rank_kept * cols];
for k in 0..rank_kept {
for j in 0..cols {
vt[k * cols + j] = v[j * rank_kept + k];
}
}
let mut u = vec![0.0f32; rows * rank_kept];
for k in 0..rank_kept {
if sigma[k] < 1e-15 {
continue;
}
for i in 0..rows {
let mut val = 0.0f32;
for j in 0..cols {
val += m[i * cols + j] * v[j * rank_kept + k];
}
u[i * rank_kept + k] = val / sigma[k];
}
}
(u, sigma, vt, rank_kept)
}
fn power_iteration_symmetric(g: &[f32], n: usize, rank: usize) -> (Vec<f32>, Vec<f32>) {
const MAX_ITER: usize = 64;
const TOL: f32 = 1e-6;
let mut eigvecs = vec![0.0f32; n * rank];
let mut eigvals = vec![0.0f32; rank];
let mut deflated = g.to_vec();
for k in 0..rank {
let mut v: Vec<f32> = (0..n).map(|i| if i == k % n { 1.0 } else { 0.0 }).collect();
normalise(&mut v);
let mut lambda = 0.0f32;
for _ in 0..MAX_ITER {
let w = matvec_sq(&deflated, &v, n);
let lambda_new: f32 = v.iter().zip(w.iter()).map(|(vi, wi)| vi * wi).sum();
let mut w2 = w;
normalise(&mut w2);
let diff: f32 = v.iter().zip(w2.iter()).map(|(a, b)| (a - b).abs()).sum();
v = w2;
lambda = lambda_new;
if diff < TOL {
break;
}
}
eigvals[k] = lambda;
for i in 0..n {
eigvecs[i * rank + k] = v[i];
}
for i in 0..n {
for j in 0..n {
deflated[i * n + j] -= lambda * v[i] * v[j];
}
}
}
(eigvecs, eigvals)
}
fn matvec_sq(m: &[f32], x: &[f32], n: usize) -> Vec<f32> {
let mut y = vec![0.0f32; n];
for i in 0..n {
for j in 0..n {
y[i] += m[i * n + j] * x[j];
}
}
y
}
fn normalise(v: &mut [f32]) {
let norm: f32 = v.iter().map(|x| x * x).sum::<f32>().sqrt();
if norm > 1e-12 {
for x in v.iter_mut() {
*x /= norm;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn vec_norm(v: &[f32]) -> f32 {
v.iter().map(|x| x * x).sum::<f32>().sqrt()
}
fn vec_err(a: &[f32], b: &[f32]) -> f32 {
a.iter()
.zip(b)
.map(|(x, y)| (x - y).powi(2))
.sum::<f32>()
.sqrt()
}
fn approx_eq(a: f32, b: f32, tol: f32) -> bool {
(a - b).abs() < tol
}
#[test]
fn test_svd_rank1_reconstruction() {
let u_vec = [1.0f32, 0.0, 0.0];
let s = 3.0f32;
let vt_vec = [0.0f32, 1.0, 0.0, 0.0];
let mut m = vec![0.0f32; 3 * 4];
for i in 0..3 {
for j in 0..4 {
m[i * 4 + j] = u_vec[i] * s * vt_vec[j];
}
}
let (u_out, sigma_out, vt_out, rank) = svd_truncated(&m, 3, 4, 2);
assert!(rank >= 1);
assert!(approx_eq(sigma_out[0], s, 0.1), "sigma={}", sigma_out[0]);
let mut recon = vec![0.0f32; 3 * 4];
for k in 0..rank {
for i in 0..3 {
for j in 0..4 {
recon[i * 4 + j] += u_out[i * rank + k] * sigma_out[k] * vt_out[k * 4 + j];
}
}
}
let err = vec_err(&recon, &m) / (vec_norm(&m) + 1e-8);
assert!(err < 0.05, "reconstruction error = {err:.4}");
}
#[test]
fn test_svd_identity_singular_values() {
let mut eye = vec![0.0f32; 16];
for i in 0..4 {
eye[i * 4 + i] = 1.0;
}
let (_, sigma, _, rank) = svd_truncated(&eye, 4, 4, 4);
assert_eq!(rank, 4);
for s in &sigma {
assert!(approx_eq(*s, 1.0, 0.1), "sigma={s}");
}
}
#[test]
fn test_empty_cache_attend_zero() {
let cache = KvCacheMps::new(4, 4, 8);
let out = cache.attend(&[1.0, 0.0, 0.0, 0.0], 1.0);
assert_eq!(out, vec![0.0; 4]);
}
#[test]
fn test_single_token_attend_returns_value() {
let mut cache = KvCacheMps::new(4, 4, 8);
let k = vec![1.0f32, 0.0, 0.0, 0.0];
let v = vec![0.0f32, 0.0, 1.0, 0.0];
cache.append(&k, &v);
let out = cache.attend(&[1.0, 0.0, 0.0, 0.0], 1.0);
assert!(vec_err(&out, &v) < 1e-4, "expected {v:?}, got {out:?}");
}
#[test]
fn test_token_count_increments() {
let mut cache = KvCacheMps::new(4, 4, 8);
for i in 0..5 {
cache.append(&[i as f32, 0.0, 0.0, 0.0], &[0.0, i as f32, 0.0, 0.0]);
assert_eq!(cache.token_count(), i + 1);
}
}
#[test]
fn test_chi_max_bounds_bond_dimension() {
let chi_max = 4;
let mut cache = KvCacheMps::new(8, 8, chi_max);
for i in 0..32 {
let k: Vec<f32> = (0..8).map(|j| ((i + j) as f32) * 0.1).collect();
let v: Vec<f32> = (0..8).map(|j| ((i * 2 + j) as f32) * 0.1).collect();
cache.append(&k, &v);
}
assert!(
cache.max_bond_dim() <= chi_max,
"bond dim {} > chi_max {chi_max}",
cache.max_bond_dim()
);
}
#[test]
fn test_compression_ratio_exceeds_one() {
let mut cache = KvCacheMps::new(8, 8, 1);
for i in 0..20 {
let k: Vec<f32> = (0..8).map(|j| (i + j) as f32).collect();
let v: Vec<f32> = (0..8).map(|j| (i * 2 + j) as f32).collect();
cache.append(&k, &v);
}
let ratio = cache.compression_ratio();
assert!(ratio > 1.0, "expected ratio > 1, got {ratio:.3}");
}
#[test]
fn test_higher_chi_lower_attend_error() {
let d = 8;
let tokens: Vec<(Vec<f32>, Vec<f32>)> = (0..16)
.map(|i| {
let k: Vec<f32> = (0..d)
.map(|j| (((i * 3 + j * 7) % 11) as f32 - 5.0) * 0.3)
.collect();
let v: Vec<f32> = (0..d)
.map(|j| (((i * 5 + j * 3) % 7) as f32 - 3.0) * 0.2)
.collect();
(k, v)
})
.collect();
let query: Vec<f32> = (0..d).map(|i| i as f32 * 0.1).collect();
let scale = 1.0 / (d as f32).sqrt();
let ref_out = direct_attend(&tokens, &query, scale, d);
let error_for_chi = |chi: usize| {
let mut cache = KvCacheMps::new(d, d, chi);
for (k, v) in &tokens {
cache.append(k, v);
}
let out = cache.attend(&query, scale);
vec_err(&out, &ref_out) / (vec_norm(&ref_out) + 1e-8)
};
let err1 = error_for_chi(1);
let err8 = error_for_chi(8);
assert!(
err8 <= err1 + 0.1,
"chi=8 error {err8:.4} should be ≤ chi=1 error {err1:.4} + 0.1"
);
let err_full = error_for_chi(d);
assert!(
err_full < 0.02,
"full-rank attend error {err_full:.4} should be < 2%"
);
}
fn direct_attend(
tokens: &[(Vec<f32>, Vec<f32>)],
query: &[f32],
scale: f32,
d: usize,
) -> Vec<f32> {
let scores_raw: Vec<f32> = tokens
.iter()
.map(|(k, _)| k.iter().zip(query).map(|(ki, qi)| ki * qi).sum::<f32>() * scale)
.collect();
let max_s = scores_raw.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
let exp: Vec<f32> = scores_raw.iter().map(|s| (s - max_s).exp()).collect();
let sum_exp: f32 = exp.iter().sum();
let weights: Vec<f32> = exp.iter().map(|e| e / sum_exp).collect();
let mut out = vec![0.0f32; d];
for (w, (_, v)) in weights.iter().zip(tokens.iter()) {
for (o, vi) in out.iter_mut().zip(v.iter()) {
*o += w * vi;
}
}
out
}
}