1pub struct FrameCodec {
34 head_dim: usize,
36 k0: Vec<f32>,
38 v0: Vec<f32>,
40 frames_k: Vec<Vec<f32>>,
43 frames_v: Vec<Vec<f32>>,
45}
46
47impl FrameCodec {
48 pub fn new(head_dim: usize) -> Self {
50 Self {
51 head_dim,
52 k0: Vec::new(),
53 v0: Vec::new(),
54 frames_k: Vec::new(),
55 frames_v: Vec::new(),
56 }
57 }
58
59 pub fn encode_step(&mut self, k: &[f32], v: &[f32]) {
64 assert_eq!(k.len(), self.head_dim, "k length must equal head_dim");
65 assert_eq!(v.len(), self.head_dim, "v length must equal head_dim");
66
67 if self.k0.is_empty() {
68 self.k0 = k.to_vec();
70 self.v0 = v.to_vec();
71 return;
72 }
73
74 let k_prev = self.decode_key(self.token_count() - 1);
76 let v_prev = self.decode_val(self.token_count() - 1);
77
78 let fk = rank1_frame_transition(k, &k_prev, self.head_dim);
84 let fv = rank1_frame_transition(v, &v_prev, self.head_dim);
85
86 self.frames_k.push(fk);
87 self.frames_v.push(fv);
88 }
89
90 pub fn token_count(&self) -> usize {
92 if self.k0.is_empty() {
93 0
94 } else {
95 1 + self.frames_k.len()
96 }
97 }
98
99 pub fn decode_key(&self, t: usize) -> Vec<f32> {
102 self.decode_vector(&self.k0, &self.frames_k, t)
103 }
104
105 pub fn decode_val(&self, t: usize) -> Vec<f32> {
107 self.decode_vector(&self.v0, &self.frames_v, t)
108 }
109
110 pub fn attend(&self, query: &[f32]) -> Vec<f32> {
114 let n = self.token_count();
115 if n == 0 {
116 return vec![0.0; self.head_dim];
117 }
118
119 let scale = 1.0 / (self.head_dim as f32).sqrt();
120 let mut scores = Vec::with_capacity(n);
121
122 let mut keys = Vec::with_capacity(n);
124 let mut vals = Vec::with_capacity(n);
125 keys.push(self.k0.clone());
126 vals.push(self.v0.clone());
127 for t in 1..n {
128 keys.push(self.decode_key(t));
129 vals.push(self.decode_val(t));
130 }
131
132 for k in &keys {
133 let dot: f32 = query.iter().zip(k.iter()).map(|(q, ki)| q * ki).sum();
134 scores.push(dot * scale);
135 }
136
137 let max_s = scores.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
139 let mut exp_scores: Vec<f32> = scores.iter().map(|s| (s - max_s).exp()).collect();
140 let sum_exp: f32 = exp_scores.iter().sum();
141 for e in &mut exp_scores {
142 *e /= sum_exp;
143 }
144
145 let mut out = vec![0.0f32; self.head_dim];
147 for (weight, v) in exp_scores.iter().zip(vals.iter()) {
148 for (o, vi) in out.iter_mut().zip(v.iter()) {
149 *o += weight * vi;
150 }
151 }
152 out
153 }
154
155 pub fn bytes_used(&self) -> usize {
157 let anchor = 2 * self.head_dim * 4;
158 let frames =
159 (self.frames_k.len() + self.frames_v.len()) * self.head_dim * self.head_dim * 4;
160 anchor + frames
161 }
162
163 pub fn bytes_absolute(&self) -> usize {
165 self.token_count() * 2 * self.head_dim * 4
166 }
167
168 fn decode_vector(&self, anchor: &[f32], frames: &[Vec<f32>], t: usize) -> Vec<f32> {
171 assert!(
172 t < self.token_count(),
173 "t={t} out of range (n={})",
174 self.token_count()
175 );
176 if t == 0 {
177 return anchor.to_vec();
178 }
179 let d = self.head_dim;
180 let mut current = anchor.to_vec();
182 for frame in &frames[..t] {
183 current = matvec(frame, ¤t, d);
184 }
185 current
186 }
187}
188
189fn rank1_frame_transition(curr: &[f32], prev: &[f32], d: usize) -> Vec<f32> {
198 let norm_sq: f32 = prev.iter().map(|x| x * x).sum();
199 let mut frame = identity_matrix(d);
200
201 if norm_sq < 1e-12 {
202 for i in 0..d {
204 frame[i * d + i] += curr[i]; }
206 return frame;
207 }
208
209 let delta: Vec<f32> = curr.iter().zip(prev.iter()).map(|(c, p)| c - p).collect();
211
212 for i in 0..d {
214 for j in 0..d {
215 frame[i * d + j] += delta[i] * prev[j] / norm_sq;
216 }
217 }
218 frame
219}
220
221fn identity_matrix(d: usize) -> Vec<f32> {
223 let mut m = vec![0.0f32; d * d];
224 for i in 0..d {
225 m[i * d + i] = 1.0;
226 }
227 m
228}
229
230fn matvec(m: &[f32], x: &[f32], d: usize) -> Vec<f32> {
232 let mut y = vec![0.0f32; d];
233 for i in 0..d {
234 for j in 0..d {
235 y[i] += m[i * d + j] * x[j];
236 }
237 }
238 y
239}
240
241#[cfg(test)]
244mod tests {
245 use super::*;
246
247 fn approx_eq_vec(a: &[f32], b: &[f32], tol: f32) -> bool {
248 a.len() == b.len() && a.iter().zip(b).all(|(x, y)| (x - y).abs() < tol)
249 }
250
251 #[test]
254 fn test_encode_decode_roundtrip_anchor() {
255 let mut codec = FrameCodec::new(4);
256 let k0 = vec![1.0, 0.0, 0.0, 0.0];
257 let v0 = vec![0.0, 1.0, 0.0, 0.0];
258 codec.encode_step(&k0, &v0);
259 assert!(approx_eq_vec(&codec.decode_key(0), &k0, 1e-6));
260 assert!(approx_eq_vec(&codec.decode_val(0), &v0, 1e-6));
261 }
262
263 #[test]
264 fn test_encode_decode_roundtrip_second_token() {
265 let mut codec = FrameCodec::new(4);
266 let k0 = vec![1.0, 0.0, 0.0, 0.0];
267 let v0 = vec![0.0, 1.0, 0.0, 0.0];
268 let k1 = vec![0.5, 0.5, 0.0, 0.0];
269 let v1 = vec![0.0, 0.5, 0.5, 0.0];
270 codec.encode_step(&k0, &v0);
271 codec.encode_step(&k1, &v1);
272 assert!(
273 approx_eq_vec(&codec.decode_key(1), &k1, 1e-5),
274 "decode_key(1) = {:?}, expected {:?}",
275 codec.decode_key(1),
276 k1
277 );
278 assert!(approx_eq_vec(&codec.decode_val(1), &v1, 1e-5));
279 }
280
281 #[test]
282 fn test_encode_decode_multiple_tokens() {
283 let d = 4;
284 let mut codec = FrameCodec::new(d);
285 let kvs: Vec<(Vec<f32>, Vec<f32>)> = (0..5)
286 .map(|i| {
287 let k = (0..d).map(|j| ((i + j) as f32) * 0.1).collect();
288 let v = (0..d).map(|j| ((i * 2 + j) as f32) * 0.1).collect();
289 (k, v)
290 })
291 .collect();
292 for (k, v) in &kvs {
293 codec.encode_step(k, v);
294 }
295 for (t, (k, v)) in kvs.iter().enumerate() {
296 assert!(
297 approx_eq_vec(&codec.decode_key(t), k, 1e-4),
298 "key mismatch at t={t}"
299 );
300 assert!(
301 approx_eq_vec(&codec.decode_val(t), v, 1e-4),
302 "val mismatch at t={t}"
303 );
304 }
305 }
306
307 #[test]
308 fn test_identity_frame_for_constant_sequence() {
309 let d = 3;
310 let mut codec = FrameCodec::new(d);
311 let k = vec![1.0, 0.0, 0.0];
312 let v = vec![0.0, 1.0, 0.0];
313 for _ in 0..4 {
315 codec.encode_step(&k, &v);
316 }
317 for t in 0..4 {
319 assert!(approx_eq_vec(&codec.decode_key(t), &k, 1e-5), "t={t}");
320 }
321 }
322
323 #[test]
326 fn test_attend_matches_direct_single_token() {
327 let d = 4;
328 let k0 = vec![1.0, 0.0, 0.0, 0.0];
329 let v0 = vec![0.0, 0.0, 1.0, 0.0];
330 let q = vec![1.0, 0.0, 0.0, 0.0];
331
332 let mut codec = FrameCodec::new(d);
333 codec.encode_step(&k0, &v0);
334
335 let out = codec.attend(&q);
337 assert!(
338 approx_eq_vec(&out, &v0, 1e-5),
339 "single-token attend = {out:?}"
340 );
341 }
342
343 #[test]
344 fn test_attend_matches_direct_multi_token() {
345 let d = 4;
346 let kvs = vec![
347 (vec![1.0f32, 0.0, 0.0, 0.0], vec![1.0f32, 0.0, 0.0, 0.0]),
348 (vec![0.0f32, 1.0, 0.0, 0.0], vec![0.0f32, 1.0, 0.0, 0.0]),
349 (vec![0.0f32, 0.0, 1.0, 0.0], vec![0.0f32, 0.0, 1.0, 0.0]),
350 ];
351 let q = vec![1.0f32, 0.0, 0.0, 0.0]; let mut codec = FrameCodec::new(d);
354 for (k, v) in &kvs {
355 codec.encode_step(k, v);
356 }
357
358 let scale = 1.0 / (d as f32).sqrt();
360 let scores_raw: Vec<f32> = kvs
361 .iter()
362 .map(|(k, _)| k.iter().zip(&q).map(|(ki, qi)| ki * qi).sum::<f32>() * scale)
363 .collect();
364 let max_s = scores_raw.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
365 let exp: Vec<f32> = scores_raw.iter().map(|s| (s - max_s).exp()).collect();
366 let sum_exp: f32 = exp.iter().sum();
367 let weights: Vec<f32> = exp.iter().map(|e| e / sum_exp).collect();
368 let mut expected = vec![0.0f32; d];
369 for (w, (_, v)) in weights.iter().zip(kvs.iter()) {
370 for (o, vi) in expected.iter_mut().zip(v.iter()) {
371 *o += w * vi;
372 }
373 }
374
375 let out = codec.attend(&q);
376 assert!(
377 approx_eq_vec(&out, &expected, 1e-4),
378 "attend mismatch: got {out:?}, expected {expected:?}"
379 );
380 }
381
382 #[test]
383 fn test_attend_empty_returns_zero() {
384 let codec = FrameCodec::new(4);
385 let out = codec.attend(&[1.0, 0.0, 0.0, 0.0]);
386 assert_eq!(out, vec![0.0; 4]);
387 }
388
389 #[test]
392 fn test_token_count() {
393 let mut codec = FrameCodec::new(4);
394 assert_eq!(codec.token_count(), 0);
395 codec.encode_step(&[1.0, 0.0, 0.0, 0.0], &[0.0, 1.0, 0.0, 0.0]);
396 assert_eq!(codec.token_count(), 1);
397 codec.encode_step(&[0.5, 0.5, 0.0, 0.0], &[0.0, 0.5, 0.5, 0.0]);
398 assert_eq!(codec.token_count(), 2);
399 }
400}