1pub struct KvCacheMps {
29 basis_k: Vec<f32>,
31 basis_v: Vec<f32>,
33 coeff_k: Vec<Vec<f32>>,
35 coeff_v: Vec<Vec<f32>>,
37 chi_k: usize,
39 chi_v: usize,
41 chi_max: usize,
42 d_k: usize,
43 d_v: usize,
44}
45
46impl KvCacheMps {
47 pub fn new(d_k: usize, d_v: usize, chi_max: usize) -> Self {
52 assert!(chi_max >= 1, "chi_max must be ≥ 1");
53 Self {
54 basis_k: Vec::new(),
55 basis_v: Vec::new(),
56 coeff_k: Vec::new(),
57 coeff_v: Vec::new(),
58 chi_k: 0,
59 chi_v: 0,
60 chi_max,
61 d_k,
62 d_v,
63 }
64 }
65
66 pub fn append(&mut self, k: &[f32], v: &[f32]) {
72 assert_eq!(k.len(), self.d_k);
73 assert_eq!(v.len(), self.d_v);
74
75 let c_k = project_and_extend(
76 &mut self.basis_k,
77 &mut self.chi_k,
78 self.chi_max,
79 self.d_k,
80 k,
81 );
82 self.coeff_k.push(c_k);
83
84 let c_v = project_and_extend(
85 &mut self.basis_v,
86 &mut self.chi_v,
87 self.chi_max,
88 self.d_v,
89 v,
90 );
91 self.coeff_v.push(c_v);
92 }
93
94 pub fn token_count(&self) -> usize {
96 self.coeff_k.len()
97 }
98
99 pub fn max_bond_dim(&self) -> usize {
101 self.chi_k.max(self.chi_v)
102 }
103
104 pub fn attend(&self, query: &[f32], scale: f32) -> Vec<f32> {
111 let n = self.token_count();
112 if n == 0 {
113 return vec![0.0; self.d_v];
114 }
115
116 let q_comp: Vec<f32> = (0..self.chi_k)
118 .map(|k| {
119 let col = &self.basis_k[k * self.d_k..(k + 1) * self.d_k];
120 col.iter().zip(query).map(|(b, q)| b * q).sum()
121 })
122 .collect();
123
124 let mut scores: Vec<f32> = self
129 .coeff_k
130 .iter()
131 .map(|c| {
132 q_comp
133 .iter()
134 .zip(c.iter())
135 .map(|(q, ci)| q * ci)
136 .sum::<f32>()
137 * scale
138 })
139 .collect();
140
141 let max_s = scores.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
143 let mut weights: Vec<f32> = scores.iter_mut().map(|s| (*s - max_s).exp()).collect();
144 let sum_w: f32 = weights.iter().sum();
145 for w in &mut weights {
146 *w /= sum_w;
147 }
148
149 let mut out = vec![0.0f32; self.d_v];
151 for (w, c_v) in weights.iter().zip(self.coeff_v.iter()) {
152 for (k, &ck) in c_v.iter().enumerate() {
153 let wck = w * ck;
154 let col = &self.basis_v[k * self.d_v..(k + 1) * self.d_v];
155 for (o, b) in out.iter_mut().zip(col) {
156 *o += wck * b;
157 }
158 }
159 }
160 out
161 }
162
163 pub fn compression_ratio(&self) -> f64 {
168 let n = self.token_count();
169 if n == 0 {
170 return 1.0;
171 }
172 let flat = n * (self.d_k + self.d_v);
173 let compressed =
175 self.chi_k * self.d_k + self.chi_v * self.d_v + n * self.chi_k + n * self.chi_v;
176 flat as f64 / compressed as f64
177 }
178}
179
180fn project_and_extend(
188 basis: &mut Vec<f32>,
189 chi: &mut usize,
190 chi_max: usize,
191 d: usize,
192 vec: &[f32],
193) -> Vec<f32> {
194 let mut coeff: Vec<f32> = (0..*chi)
196 .map(|k| {
197 let col = &basis[k * d..(k + 1) * d];
198 col.iter().zip(vec).map(|(b, v)| b * v).sum()
199 })
200 .collect();
201
202 if *chi >= chi_max {
203 return coeff;
204 }
205
206 let mut residual = vec.to_vec();
208 for k in 0..*chi {
209 let c = coeff[k];
210 let col = &basis[k * d..(k + 1) * d];
211 for (r, b) in residual.iter_mut().zip(col) {
212 *r -= c * b;
213 }
214 }
215
216 let norm: f32 = residual.iter().map(|x| x * x).sum::<f32>().sqrt();
217 let vec_norm: f32 = vec.iter().map(|x| x * x).sum::<f32>().sqrt();
223 if norm < 1e-4 * vec_norm.max(1e-12) {
224 return coeff;
226 }
227
228 for r in residual.iter_mut() {
230 *r /= norm;
231 }
232 basis.extend_from_slice(&residual);
233 *chi += 1;
234
235 coeff.push(norm);
238 coeff
239}
240
241pub fn svd_truncated(
246 m: &[f32],
247 rows: usize,
248 cols: usize,
249 rank: usize,
250) -> (Vec<f32>, Vec<f32>, Vec<f32>, usize) {
251 let rank = rank.min(rows).min(cols);
252 if rows <= cols {
253 svd_via_gram_left(m, rows, cols, rank)
254 } else {
255 svd_via_gram_right(m, rows, cols, rank)
256 }
257}
258
259fn svd_via_gram_left(
260 m: &[f32],
261 rows: usize,
262 cols: usize,
263 rank: usize,
264) -> (Vec<f32>, Vec<f32>, Vec<f32>, usize) {
265 let mut g = vec![0.0f32; rows * rows];
266 for i in 0..rows {
267 for k in 0..cols {
268 let mik = m[i * cols + k];
269 for j in 0..rows {
270 g[i * rows + j] += mik * m[j * cols + k];
271 }
272 }
273 }
274 let (eigvecs, eigvals) = power_iteration_symmetric(&g, rows, rank);
275
276 const SVD_EPS: f32 = 1e-7;
277 let sigma_max = eigvals[0].max(0.0).sqrt();
278 let mut rank_kept = rank;
279 while rank_kept > 1 && eigvals[rank_kept - 1].max(0.0).sqrt() < SVD_EPS * sigma_max {
280 rank_kept -= 1;
281 }
282 rank_kept = rank_kept.max(1);
283
284 let sigma: Vec<f32> = (0..rank_kept).map(|k| eigvals[k].max(0.0).sqrt()).collect();
285
286 let mut u = vec![0.0f32; rows * rank_kept];
287 for i in 0..rows {
288 for k in 0..rank_kept {
289 u[i * rank_kept + k] = eigvecs[i * rank + k];
290 }
291 }
292
293 let mut vt = vec![0.0f32; rank_kept * cols];
294 for k in 0..rank_kept {
295 if sigma[k] < 1e-15 {
296 continue;
297 }
298 for j in 0..cols {
299 let mut val = 0.0f32;
300 for i in 0..rows {
301 val += m[i * cols + j] * u[i * rank_kept + k];
302 }
303 vt[k * cols + j] = val / sigma[k];
304 }
305 }
306 (u, sigma, vt, rank_kept)
307}
308
309fn svd_via_gram_right(
310 m: &[f32],
311 rows: usize,
312 cols: usize,
313 rank: usize,
314) -> (Vec<f32>, Vec<f32>, Vec<f32>, usize) {
315 let mut g = vec![0.0f32; cols * cols];
316 for k in 0..rows {
317 for i in 0..cols {
318 let mki = m[k * cols + i];
319 for j in 0..cols {
320 g[i * cols + j] += mki * m[k * cols + j];
321 }
322 }
323 }
324 let (eigvecs, eigvals) = power_iteration_symmetric(&g, cols, rank);
325
326 const SVD_EPS: f32 = 1e-7;
327 let sigma_max = eigvals[0].max(0.0).sqrt();
328 let mut rank_kept = rank;
329 while rank_kept > 1 && eigvals[rank_kept - 1].max(0.0).sqrt() < SVD_EPS * sigma_max {
330 rank_kept -= 1;
331 }
332 rank_kept = rank_kept.max(1);
333
334 let sigma: Vec<f32> = (0..rank_kept).map(|k| eigvals[k].max(0.0).sqrt()).collect();
335
336 let mut v = vec![0.0f32; cols * rank_kept];
337 for i in 0..cols {
338 for k in 0..rank_kept {
339 v[i * rank_kept + k] = eigvecs[i * rank + k];
340 }
341 }
342
343 let mut vt = vec![0.0f32; rank_kept * cols];
344 for k in 0..rank_kept {
345 for j in 0..cols {
346 vt[k * cols + j] = v[j * rank_kept + k];
347 }
348 }
349
350 let mut u = vec![0.0f32; rows * rank_kept];
351 for k in 0..rank_kept {
352 if sigma[k] < 1e-15 {
353 continue;
354 }
355 for i in 0..rows {
356 let mut val = 0.0f32;
357 for j in 0..cols {
358 val += m[i * cols + j] * v[j * rank_kept + k];
359 }
360 u[i * rank_kept + k] = val / sigma[k];
361 }
362 }
363 (u, sigma, vt, rank_kept)
364}
365
366fn power_iteration_symmetric(g: &[f32], n: usize, rank: usize) -> (Vec<f32>, Vec<f32>) {
367 const MAX_ITER: usize = 64;
368 const TOL: f32 = 1e-6;
369
370 let mut eigvecs = vec![0.0f32; n * rank];
371 let mut eigvals = vec![0.0f32; rank];
372 let mut deflated = g.to_vec();
373
374 for k in 0..rank {
375 let mut v: Vec<f32> = (0..n).map(|i| if i == k % n { 1.0 } else { 0.0 }).collect();
376 normalise(&mut v);
377 let mut lambda = 0.0f32;
378 for _ in 0..MAX_ITER {
379 let w = matvec_sq(&deflated, &v, n);
380 let lambda_new: f32 = v.iter().zip(w.iter()).map(|(vi, wi)| vi * wi).sum();
381 let mut w2 = w;
382 normalise(&mut w2);
383 let diff: f32 = v.iter().zip(w2.iter()).map(|(a, b)| (a - b).abs()).sum();
384 v = w2;
385 lambda = lambda_new;
386 if diff < TOL {
387 break;
388 }
389 }
390 eigvals[k] = lambda;
391 for i in 0..n {
392 eigvecs[i * rank + k] = v[i];
393 }
394 for i in 0..n {
395 for j in 0..n {
396 deflated[i * n + j] -= lambda * v[i] * v[j];
397 }
398 }
399 }
400 (eigvecs, eigvals)
401}
402
403fn matvec_sq(m: &[f32], x: &[f32], n: usize) -> Vec<f32> {
404 let mut y = vec![0.0f32; n];
405 for i in 0..n {
406 for j in 0..n {
407 y[i] += m[i * n + j] * x[j];
408 }
409 }
410 y
411}
412
413fn normalise(v: &mut [f32]) {
414 let norm: f32 = v.iter().map(|x| x * x).sum::<f32>().sqrt();
415 if norm > 1e-12 {
416 for x in v.iter_mut() {
417 *x /= norm;
418 }
419 }
420}
421
422#[cfg(test)]
425mod tests {
426 use super::*;
427
428 fn vec_norm(v: &[f32]) -> f32 {
429 v.iter().map(|x| x * x).sum::<f32>().sqrt()
430 }
431 fn vec_err(a: &[f32], b: &[f32]) -> f32 {
432 a.iter()
433 .zip(b)
434 .map(|(x, y)| (x - y).powi(2))
435 .sum::<f32>()
436 .sqrt()
437 }
438 fn approx_eq(a: f32, b: f32, tol: f32) -> bool {
439 (a - b).abs() < tol
440 }
441
442 #[test]
445 fn test_svd_rank1_reconstruction() {
446 let u_vec = [1.0f32, 0.0, 0.0];
447 let s = 3.0f32;
448 let vt_vec = [0.0f32, 1.0, 0.0, 0.0];
449 let mut m = vec![0.0f32; 3 * 4];
450 for i in 0..3 {
451 for j in 0..4 {
452 m[i * 4 + j] = u_vec[i] * s * vt_vec[j];
453 }
454 }
455 let (u_out, sigma_out, vt_out, rank) = svd_truncated(&m, 3, 4, 2);
456 assert!(rank >= 1);
457 assert!(approx_eq(sigma_out[0], s, 0.1), "sigma={}", sigma_out[0]);
458 let mut recon = vec![0.0f32; 3 * 4];
459 for k in 0..rank {
460 for i in 0..3 {
461 for j in 0..4 {
462 recon[i * 4 + j] += u_out[i * rank + k] * sigma_out[k] * vt_out[k * 4 + j];
463 }
464 }
465 }
466 let err = vec_err(&recon, &m) / (vec_norm(&m) + 1e-8);
467 assert!(err < 0.05, "reconstruction error = {err:.4}");
468 }
469
470 #[test]
471 fn test_svd_identity_singular_values() {
472 let mut eye = vec![0.0f32; 16];
473 for i in 0..4 {
474 eye[i * 4 + i] = 1.0;
475 }
476 let (_, sigma, _, rank) = svd_truncated(&eye, 4, 4, 4);
477 assert_eq!(rank, 4);
478 for s in &sigma {
479 assert!(approx_eq(*s, 1.0, 0.1), "sigma={s}");
480 }
481 }
482
483 #[test]
486 fn test_empty_cache_attend_zero() {
487 let cache = KvCacheMps::new(4, 4, 8);
488 let out = cache.attend(&[1.0, 0.0, 0.0, 0.0], 1.0);
489 assert_eq!(out, vec![0.0; 4]);
490 }
491
492 #[test]
493 fn test_single_token_attend_returns_value() {
494 let mut cache = KvCacheMps::new(4, 4, 8);
495 let k = vec![1.0f32, 0.0, 0.0, 0.0];
496 let v = vec![0.0f32, 0.0, 1.0, 0.0];
497 cache.append(&k, &v);
498 let out = cache.attend(&[1.0, 0.0, 0.0, 0.0], 1.0);
499 assert!(vec_err(&out, &v) < 1e-4, "expected {v:?}, got {out:?}");
501 }
502
503 #[test]
504 fn test_token_count_increments() {
505 let mut cache = KvCacheMps::new(4, 4, 8);
506 for i in 0..5 {
507 cache.append(&[i as f32, 0.0, 0.0, 0.0], &[0.0, i as f32, 0.0, 0.0]);
508 assert_eq!(cache.token_count(), i + 1);
509 }
510 }
511
512 #[test]
513 fn test_chi_max_bounds_bond_dimension() {
514 let chi_max = 4;
515 let mut cache = KvCacheMps::new(8, 8, chi_max);
516 for i in 0..32 {
517 let k: Vec<f32> = (0..8).map(|j| ((i + j) as f32) * 0.1).collect();
518 let v: Vec<f32> = (0..8).map(|j| ((i * 2 + j) as f32) * 0.1).collect();
519 cache.append(&k, &v);
520 }
521 assert!(
522 cache.max_bond_dim() <= chi_max,
523 "bond dim {} > chi_max {chi_max}",
524 cache.max_bond_dim()
525 );
526 }
527
528 #[test]
529 fn test_compression_ratio_exceeds_one() {
530 let mut cache = KvCacheMps::new(8, 8, 1);
536 for i in 0..20 {
537 let k: Vec<f32> = (0..8).map(|j| (i + j) as f32).collect();
538 let v: Vec<f32> = (0..8).map(|j| (i * 2 + j) as f32).collect();
539 cache.append(&k, &v);
540 }
541 let ratio = cache.compression_ratio();
542 assert!(ratio > 1.0, "expected ratio > 1, got {ratio:.3}");
543 }
544
545 #[test]
546 fn test_higher_chi_lower_attend_error() {
547 let d = 8;
549 let tokens: Vec<(Vec<f32>, Vec<f32>)> = (0..16)
550 .map(|i| {
551 let k: Vec<f32> = (0..d)
552 .map(|j| (((i * 3 + j * 7) % 11) as f32 - 5.0) * 0.3)
553 .collect();
554 let v: Vec<f32> = (0..d)
555 .map(|j| (((i * 5 + j * 3) % 7) as f32 - 3.0) * 0.2)
556 .collect();
557 (k, v)
558 })
559 .collect();
560
561 let query: Vec<f32> = (0..d).map(|i| i as f32 * 0.1).collect();
562 let scale = 1.0 / (d as f32).sqrt();
563
564 let ref_out = direct_attend(&tokens, &query, scale, d);
566
567 let error_for_chi = |chi: usize| {
568 let mut cache = KvCacheMps::new(d, d, chi);
569 for (k, v) in &tokens {
570 cache.append(k, v);
571 }
572 let out = cache.attend(&query, scale);
573 vec_err(&out, &ref_out) / (vec_norm(&ref_out) + 1e-8)
574 };
575
576 let err1 = error_for_chi(1);
577 let err8 = error_for_chi(8);
578
579 assert!(
581 err8 <= err1 + 0.1,
582 "chi=8 error {err8:.4} should be ≤ chi=1 error {err1:.4} + 0.1"
583 );
584 let err_full = error_for_chi(d);
586 assert!(
587 err_full < 0.02,
588 "full-rank attend error {err_full:.4} should be < 2%"
589 );
590 }
591
592 fn direct_attend(
594 tokens: &[(Vec<f32>, Vec<f32>)],
595 query: &[f32],
596 scale: f32,
597 d: usize,
598 ) -> Vec<f32> {
599 let scores_raw: Vec<f32> = tokens
600 .iter()
601 .map(|(k, _)| k.iter().zip(query).map(|(ki, qi)| ki * qi).sum::<f32>() * scale)
602 .collect();
603 let max_s = scores_raw.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
604 let exp: Vec<f32> = scores_raw.iter().map(|s| (s - max_s).exp()).collect();
605 let sum_exp: f32 = exp.iter().sum();
606 let weights: Vec<f32> = exp.iter().map(|e| e / sum_exp).collect();
607 let mut out = vec![0.0f32; d];
608 for (w, (_, v)) in weights.iter().zip(tokens.iter()) {
609 for (o, vi) in out.iter_mut().zip(v.iter()) {
610 *o += w * vi;
611 }
612 }
613 out
614 }
615}