1use std::collections::HashMap;
14use std::sync::atomic::{AtomicU64, Ordering};
15use std::sync::{Mutex, PoisonError};
16
17use kime_tok::layout::CompatSequence;
18
19pub(crate) type Key = [u8; 32];
21
22#[derive(Debug, Clone, PartialEq)]
24pub(crate) struct Entry {
25 pub(crate) logits: Box<[f32]>,
26 pub(crate) act: [f32; 2],
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
31pub enum CacheMode {
32 #[default]
34 Use,
35 Bypass,
37 Refresh,
39}
40
41impl CacheMode {
42 #[must_use]
44 pub fn parse(s: &str) -> Option<CacheMode> {
45 match s {
46 "use" => Some(CacheMode::Use),
47 "bypass" => Some(CacheMode::Bypass),
48 "refresh" => Some(CacheMode::Refresh),
49 _ => None,
50 }
51 }
52}
53
54#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
56pub struct CacheStats {
57 pub capacity: usize,
59 pub entries: usize,
61 pub hits: u64,
63 pub misses: u64,
65}
66
67#[derive(Default)]
68struct Generations {
69 young: HashMap<Key, Entry>,
70 old: HashMap<Key, Entry>,
71}
72
73pub(crate) struct AnswerCache {
74 half: usize,
76 maps: Mutex<Generations>,
77 hits: AtomicU64,
78 misses: AtomicU64,
79}
80
81impl AnswerCache {
82 pub(crate) fn new(capacity: usize) -> Self {
84 AnswerCache {
85 half: capacity.div_ceil(2).max(1),
86 maps: Mutex::default(),
87 hits: AtomicU64::new(0),
88 misses: AtomicU64::new(0),
89 }
90 }
91
92 pub(crate) fn key(seq: &CompatSequence, qtype: u8) -> Key {
93 let mut h = blake3::Hasher::new();
94 h.update(&[qtype]);
95 h.update(&(seq.ids.len() as u64).to_le_bytes());
96 for id in &seq.ids {
97 h.update(&id.to_le_bytes());
98 }
99 for m in &seq.markers {
100 h.update(&m.to_le_bytes());
101 }
102 *h.finalize().as_bytes()
103 }
104
105 pub(crate) fn get(&self, keys: &[Key]) -> Vec<Option<Entry>> {
107 let mut g = self.maps.lock().unwrap_or_else(PoisonError::into_inner);
108 let out: Vec<Option<Entry>> = keys
109 .iter()
110 .map(|k| {
111 if let Some(e) = g.young.get(k) {
112 return Some(e.clone());
113 }
114 let e = g.old.remove(k)?;
115 self.put(&mut g, *k, e.clone());
116 Some(e)
117 })
118 .collect();
119 drop(g);
120 let hits = out.iter().filter(|e| e.is_some()).count() as u64;
121 self.hits.fetch_add(hits, Ordering::Relaxed);
122 self.misses.fetch_add(keys.len() as u64 - hits, Ordering::Relaxed);
123 out
124 }
125
126 pub(crate) fn all(&self, keys: &[Key]) -> Option<Vec<Entry>> {
129 let g = self.maps.lock().unwrap_or_else(PoisonError::into_inner);
130 let out = keys
131 .iter()
132 .map(|k| g.young.get(k).or_else(|| g.old.get(k)).cloned())
133 .collect::<Option<Vec<_>>>()?;
134 drop(g);
135 self.hits.fetch_add(keys.len() as u64, Ordering::Relaxed);
136 Some(out)
137 }
138
139 pub(crate) fn insert(&self, entries: impl IntoIterator<Item = (Key, Entry)>) {
141 let mut g = self.maps.lock().unwrap_or_else(PoisonError::into_inner);
142 for (k, e) in entries {
143 g.old.remove(&k);
144 self.put(&mut g, k, e);
145 }
146 }
147
148 fn put(&self, g: &mut Generations, k: Key, e: Entry) {
149 if g.young.len() >= self.half && !g.young.contains_key(&k) {
150 g.old = std::mem::take(&mut g.young);
151 }
152 g.young.insert(k, e);
153 }
154
155 pub(crate) fn stats(&self) -> CacheStats {
156 let g = self.maps.lock().unwrap_or_else(PoisonError::into_inner);
157 CacheStats {
158 capacity: self.half * 2,
159 entries: g.young.len() + g.old.len(),
160 hits: self.hits.load(Ordering::Relaxed),
161 misses: self.misses.load(Ordering::Relaxed),
162 }
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 fn entry(x: f32) -> Entry {
171 Entry { logits: vec![x].into(), act: [x, 0.0] }
172 }
173
174 #[test]
175 fn keeps_what_is_used_and_counts() {
176 let c = AnswerCache::new(4);
177 let k = |i: u8| [i; 32];
178 c.insert((0..2).map(|i| (k(i), entry(f32::from(i)))));
179 c.insert((2..4).map(|i| (k(i), entry(f32::from(i)))));
181 assert_eq!(c.stats().entries, 4);
182 assert_eq!(c.get(&[k(0)]), vec![Some(entry(0.0))]);
185 c.insert([(k(4), entry(4.0))]);
186 let got = c.get(&[k(0), k(1), k(4), k(9)]);
187 assert_eq!(got, vec![Some(entry(0.0)), None, Some(entry(4.0)), None]);
188 let s = c.stats();
189 assert_eq!((s.hits, s.misses, s.capacity), (3, 2, 4));
190 assert!(s.entries <= 4, "{s:?}");
191 }
192
193 #[test]
194 fn insert_overwrites() {
195 let c = AnswerCache::new(10);
196 c.insert([([1; 32], entry(1.0))]);
197 c.insert([([1; 32], entry(2.0))]);
198 assert_eq!(c.get(&[[1; 32]]), vec![Some(entry(2.0))]);
199 assert_eq!(c.stats().entries, 1);
200 }
201
202 #[test]
203 fn modes() {
204 assert_eq!(CacheMode::parse("bypass"), Some(CacheMode::Bypass));
205 assert_eq!(CacheMode::parse("refresh"), Some(CacheMode::Refresh));
206 assert_eq!(CacheMode::parse("use"), Some(CacheMode::Use));
207 assert_eq!(CacheMode::parse("off"), None);
208 }
209}