1use skippy_protocol::{StageConfig, StageKvCacheConfig};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub struct ResidentCacheConfig {
5 pub max_entries: usize,
6 pub max_bytes: u64,
7 pub min_tokens: u64,
8 pub reserved_seq_count: i32,
9 pub max_resident_tokens: u64,
26}
27
28impl ResidentCacheConfig {
29 pub fn from_stage(config: &StageConfig, cache: &StageKvCacheConfig) -> Self {
30 let reserved_seq_count = i32::try_from(config.lane_count.saturating_mul(2))
31 .unwrap_or(i32::MAX)
32 .max(2);
33 let max_resident_tokens = derive_max_resident_tokens(u64::from(config.ctx_size));
34 Self {
35 max_entries: cache.max_entries.clamp(1, 512),
36 max_bytes: cache.max_bytes,
37 min_tokens: cache.min_tokens,
38 reserved_seq_count,
39 max_resident_tokens,
40 }
41 }
42}
43
44const MIN_CTX_FOR_CELL_CAP: u64 = 8192;
65
66fn derive_max_resident_tokens(ctx_size: u64) -> u64 {
67 if ctx_size < MIN_CTX_FOR_CELL_CAP {
68 return 0;
69 }
70 ctx_size.saturating_div(2)
71}
72
73#[cfg(test)]
74mod resident_cache_config_tests {
75 use super::*;
76
77 #[test]
78 fn cap_disabled_for_smoke_test_ctx_size() {
79 assert_eq!(derive_max_resident_tokens(768), 0);
83 }
84
85 #[test]
86 fn cap_enabled_for_production_ctx_size() {
87 assert_eq!(derive_max_resident_tokens(131072), 65536);
89 assert_eq!(derive_max_resident_tokens(8192), 4096);
91 assert_eq!(derive_max_resident_tokens(16384), 8192);
93 }
94
95 #[test]
96 fn cap_disabled_just_below_floor() {
97 assert_eq!(derive_max_resident_tokens(8191), 0);
99 assert_eq!(derive_max_resident_tokens(4096), 0);
100 }
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub struct PrefixCandidatePolicy {
105 pub min_tokens: u64,
106 pub stride_tokens: u64,
107 pub record_limit: u64,
108 pub page_size_tokens: u64,
109}
110
111impl PrefixCandidatePolicy {
112 pub fn from_cache(cache: &StageKvCacheConfig) -> Self {
113 Self {
114 min_tokens: cache.min_tokens,
115 stride_tokens: cache.shared_prefix_stride_tokens,
116 record_limit: cache.shared_prefix_record_limit,
117 page_size_tokens: cache.min_tokens.max(1),
118 }
119 }
120
121 pub fn candidate_token_counts(self, token_count: u64) -> Vec<u64> {
122 if token_count == 0 {
123 return Vec::new();
124 }
125 let mut counts = vec![token_count];
126 if self.min_tokens == 0 || token_count <= self.min_tokens {
127 return counts;
128 }
129 let stride = self.stride_tokens.max(1).min(self.page_size_tokens.max(1));
130 let mut candidate = stable_grid_floor(token_count, stride);
131 if candidate == token_count {
132 candidate = candidate.saturating_sub(stride);
133 }
134 if candidate < self.min_tokens {
135 candidate = self.min_tokens;
136 }
137 while candidate >= self.min_tokens {
138 counts.push(candidate);
139 if candidate == self.min_tokens {
140 break;
141 }
142 let next = candidate.saturating_sub(stride);
143 candidate = next.max(self.min_tokens);
144 }
145 counts.sort_unstable_by(|a, b| b.cmp(a));
146 counts.dedup();
147 counts
148 }
149
150 pub fn record_candidate_token_counts(self, token_count: u64) -> Vec<u64> {
151 let candidates = self.candidate_token_counts(token_count);
152 let limit = self.record_limit as usize;
153 if limit == 0 || candidates.len() <= limit {
154 return candidates;
155 }
156
157 let mut selected = Vec::with_capacity(limit);
158 selected.push(token_count);
159
160 let shared_slots = limit.saturating_sub(1);
161 if shared_slots == 0 {
162 return selected;
163 }
164 for candidate in candidates.iter().copied() {
165 if selected.len() >= limit {
166 break;
167 }
168 if candidate != token_count {
169 selected.push(candidate);
170 }
171 }
172 if selected.len() < limit {
173 for candidate in candidates.into_iter().rev() {
174 if selected.len() >= limit {
175 break;
176 }
177 if !selected.contains(&candidate) {
178 selected.push(candidate);
179 }
180 }
181 }
182 selected.sort_unstable_by(|a, b| b.cmp(a));
183 selected.dedup();
184 selected
185 }
186}
187
188fn stable_grid_floor(token_count: u64, stride: u64) -> u64 {
189 token_count.saturating_sub(token_count % stride.max(1))
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[test]
197 fn lookup_candidates_prefer_longest_prefix_first() {
198 let policy = PrefixCandidatePolicy {
199 min_tokens: 64,
200 stride_tokens: 32,
201 record_limit: 2,
202 page_size_tokens: 64,
203 };
204
205 assert_eq!(policy.candidate_token_counts(160), vec![160, 128, 96, 64]);
206 }
207
208 #[test]
209 fn record_candidates_are_limited_but_keep_current_and_shared_prefix() {
210 let policy = PrefixCandidatePolicy {
211 min_tokens: 64,
212 stride_tokens: 32,
213 record_limit: 2,
214 page_size_tokens: 64,
215 };
216
217 assert_eq!(policy.record_candidate_token_counts(160), vec![160, 128]);
218 }
219
220 #[test]
221 fn candidates_below_min_only_use_exact_request() {
222 let policy = PrefixCandidatePolicy {
223 min_tokens: 64,
224 stride_tokens: 32,
225 record_limit: 2,
226 page_size_tokens: 64,
227 };
228
229 assert_eq!(policy.candidate_token_counts(63), vec![63]);
230 assert_eq!(policy.record_candidate_token_counts(63), vec![63]);
231 }
232
233 #[test]
234 fn unlimited_record_candidates_keep_shared_prefix_grid() {
235 let policy = PrefixCandidatePolicy {
236 min_tokens: 64,
237 stride_tokens: 32,
238 record_limit: 0,
239 page_size_tokens: 64,
240 };
241
242 assert_eq!(
243 policy.record_candidate_token_counts(160),
244 vec![160, 128, 96, 64]
245 );
246 }
247
248 #[test]
249 fn same_prefix_different_tail_prompts_share_near_tail_candidate() {
250 let policy = PrefixCandidatePolicy {
251 min_tokens: 256,
252 stride_tokens: 128,
253 record_limit: 2,
254 page_size_tokens: 256,
255 };
256
257 let recorded = policy.record_candidate_token_counts(2214);
258 let lookup = policy.candidate_token_counts(2231);
259 let shared = recorded
260 .iter()
261 .copied()
262 .find(|candidate| lookup.contains(candidate));
263
264 assert_eq!(recorded, vec![2214, 2176]);
265 assert_eq!(shared, Some(2176));
266 }
267
268 #[test]
269 fn non_aligned_min_tokens_still_provides_shared_floor_candidate() {
270 let policy = PrefixCandidatePolicy {
271 min_tokens: 300,
272 stride_tokens: 128,
273 record_limit: 2,
274 page_size_tokens: 300,
275 };
276
277 assert_eq!(policy.candidate_token_counts(350), vec![350, 300]);
278 assert_eq!(policy.record_candidate_token_counts(350), vec![350, 300]);
279 }
280}