1use serde::{Deserialize, Serialize};
35use std::collections::HashMap;
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct CacheShardInfo {
40 rank: usize,
42 world_size: usize,
44 num_layers: usize,
46 layers: Vec<usize>,
48}
49
50impl CacheShardInfo {
51 pub fn new(rank: usize, world_size: usize, num_layers: usize) -> Self {
56 assert!(world_size > 0, "world_size must be > 0");
57 assert!(rank < world_size, "rank must be < world_size");
58 let per_rank = num_layers / world_size;
59 let remainder = num_layers % world_size;
60 let start = per_rank * rank + rank.min(remainder);
61 let count = per_rank + if rank < remainder { 1 } else { 0 };
62 let layers: Vec<usize> = (start..start + count).collect();
63 Self {
64 rank,
65 world_size,
66 num_layers,
67 layers,
68 }
69 }
70
71 pub fn with_layers(
73 rank: usize,
74 world_size: usize,
75 num_layers: usize,
76 layers: Vec<usize>,
77 ) -> Self {
78 Self {
79 rank,
80 world_size,
81 num_layers,
82 layers,
83 }
84 }
85
86 pub fn rank(&self) -> usize {
88 self.rank
89 }
90
91 pub fn world_size(&self) -> usize {
93 self.world_size
94 }
95
96 pub fn num_layers(&self) -> usize {
98 self.num_layers
99 }
100
101 pub fn layers(&self) -> &[usize] {
103 &self.layers
104 }
105
106 pub fn owns_layer(&self, layer: usize) -> bool {
108 self.layers.contains(&layer)
109 }
110}
111
112#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
114pub enum SyncEvent {
115 ShardUpdated {
117 rank: usize,
118 layer: usize,
119 seq_pos: usize,
120 },
121 PrefixConfirmed {
123 prefix_id: u64,
125 num_tokens: usize,
127 },
128 ShardEvicted {
130 rank: usize,
131 layer: usize,
132 seq_pos: usize,
134 },
135 FlushAll,
137}
138
139#[derive(Debug, Clone)]
144pub struct CacheSyncProtocol {
145 world_size: usize,
147 _num_layers: usize,
149 positions: HashMap<(usize, usize), usize>,
151 confirmed_prefixes: HashMap<u64, usize>,
153 event_log: Vec<SyncEvent>,
155 max_log_size: usize,
157}
158
159impl CacheSyncProtocol {
160 pub fn new(world_size: usize, num_layers: usize) -> Self {
162 Self {
163 world_size,
164 _num_layers: num_layers,
165 positions: HashMap::new(),
166 confirmed_prefixes: HashMap::new(),
167 event_log: Vec::new(),
168 max_log_size: 10_000,
169 }
170 }
171
172 pub fn with_max_log_size(mut self, max: usize) -> Self {
174 self.max_log_size = max;
175 self
176 }
177
178 pub fn record_event(&mut self, event: SyncEvent) {
180 match &event {
181 SyncEvent::ShardUpdated {
182 rank,
183 layer,
184 seq_pos,
185 } => {
186 self.positions.insert((*rank, *layer), *seq_pos);
187 }
188 SyncEvent::PrefixConfirmed {
189 prefix_id,
190 num_tokens,
191 } => {
192 self.confirmed_prefixes.insert(*prefix_id, *num_tokens);
193 }
194 SyncEvent::ShardEvicted {
195 rank,
196 layer,
197 seq_pos,
198 } => {
199 self.positions.insert((*rank, *layer), *seq_pos);
200 }
201 SyncEvent::FlushAll => {
202 self.positions.clear();
203 self.confirmed_prefixes.clear();
204 }
205 }
206 self.event_log.push(event);
207 if self.event_log.len() > self.max_log_size {
208 let drain = self.event_log.len() - self.max_log_size;
209 self.event_log.drain(..drain);
210 }
211 }
212
213 pub fn latest_position(&self, rank: usize, layer: usize) -> Option<usize> {
215 self.positions.get(&(rank, layer)).copied()
216 }
217
218 pub fn min_synced_position(&self, layer: usize) -> Option<usize> {
223 let mut min_pos: Option<usize> = None;
224 for rank in 0..self.world_size {
225 match self.positions.get(&(rank, layer)) {
226 Some(&pos) => {
227 min_pos = Some(min_pos.map_or(pos, |m: usize| m.min(pos)));
228 }
229 None => return None,
230 }
231 }
232 min_pos
233 }
234
235 pub fn is_prefix_confirmed(&self, prefix_id: u64) -> bool {
237 self.confirmed_prefixes.contains_key(&prefix_id)
238 }
239
240 pub fn prefix_tokens(&self, prefix_id: u64) -> Option<usize> {
242 self.confirmed_prefixes.get(&prefix_id).copied()
243 }
244
245 pub fn event_count(&self) -> usize {
247 self.event_log.len()
248 }
249
250 pub fn recent_events(&self, n: usize) -> &[SyncEvent] {
252 let start = self.event_log.len().saturating_sub(n);
253 &self.event_log[start..]
254 }
255}
256
257#[derive(Debug, Clone, Serialize, Deserialize)]
262pub struct CacheRoutingHint {
263 pub prefix_id: u64,
265 pub relevant_ranks: Vec<usize>,
267 pub reusable_tokens: usize,
269}
270
271impl CacheRoutingHint {
272 pub fn new(prefix_id: u64, relevant_ranks: Vec<usize>, reusable_tokens: usize) -> Self {
274 Self {
275 prefix_id,
276 relevant_ranks,
277 reusable_tokens,
278 }
279 }
280
281 pub fn has_targets(&self) -> bool {
283 !self.relevant_ranks.is_empty()
284 }
285}
286
287#[cfg(test)]
288mod tests {
289 use super::*;
290
291 #[test]
292 fn shard_info_even_split() {
293 let shard = CacheShardInfo::new(0, 4, 32);
294 assert_eq!(shard.layers().len(), 8);
295 assert_eq!(shard.layers(), &[0, 1, 2, 3, 4, 5, 6, 7]);
296
297 let shard3 = CacheShardInfo::new(3, 4, 32);
298 assert_eq!(shard3.layers(), &[24, 25, 26, 27, 28, 29, 30, 31]);
299 }
300
301 #[test]
302 fn shard_info_uneven_split() {
303 let shard0 = CacheShardInfo::new(0, 3, 10);
304 let shard1 = CacheShardInfo::new(1, 3, 10);
305 let shard2 = CacheShardInfo::new(2, 3, 10);
306 assert_eq!(shard0.layers().len(), 4);
308 assert_eq!(shard1.layers().len(), 3);
309 assert_eq!(shard2.layers().len(), 3);
310 let mut all: Vec<usize> = Vec::new();
312 all.extend(shard0.layers());
313 all.extend(shard1.layers());
314 all.extend(shard2.layers());
315 all.sort();
316 assert_eq!(all, (0..10).collect::<Vec<_>>());
317 }
318
319 #[test]
320 fn owns_layer() {
321 let shard = CacheShardInfo::new(1, 2, 8);
322 assert!(!shard.owns_layer(0)); assert!(shard.owns_layer(4)); }
325
326 #[test]
327 fn protocol_track_updates() {
328 let mut proto = CacheSyncProtocol::new(2, 4);
329 proto.record_event(SyncEvent::ShardUpdated {
330 rank: 0,
331 layer: 0,
332 seq_pos: 10,
333 });
334 proto.record_event(SyncEvent::ShardUpdated {
335 rank: 1,
336 layer: 0,
337 seq_pos: 8,
338 });
339 assert_eq!(proto.latest_position(0, 0), Some(10));
340 assert_eq!(proto.latest_position(1, 0), Some(8));
341 assert_eq!(proto.min_synced_position(0), Some(8));
342 }
343
344 #[test]
345 fn protocol_min_synced_none_if_missing() {
346 let mut proto = CacheSyncProtocol::new(3, 2);
347 proto.record_event(SyncEvent::ShardUpdated {
348 rank: 0,
349 layer: 0,
350 seq_pos: 5,
351 });
352 assert_eq!(proto.min_synced_position(0), None);
354 }
355
356 #[test]
357 fn protocol_prefix_confirmation() {
358 let mut proto = CacheSyncProtocol::new(2, 4);
359 assert!(!proto.is_prefix_confirmed(0xABCD));
360 proto.record_event(SyncEvent::PrefixConfirmed {
361 prefix_id: 0xABCD,
362 num_tokens: 64,
363 });
364 assert!(proto.is_prefix_confirmed(0xABCD));
365 assert_eq!(proto.prefix_tokens(0xABCD), Some(64));
366 }
367
368 #[test]
369 fn protocol_flush_all() {
370 let mut proto = CacheSyncProtocol::new(2, 4);
371 proto.record_event(SyncEvent::ShardUpdated {
372 rank: 0,
373 layer: 0,
374 seq_pos: 10,
375 });
376 proto.record_event(SyncEvent::PrefixConfirmed {
377 prefix_id: 1,
378 num_tokens: 5,
379 });
380 proto.record_event(SyncEvent::FlushAll);
381 assert_eq!(proto.latest_position(0, 0), None);
382 assert!(!proto.is_prefix_confirmed(1));
383 }
384
385 #[test]
386 fn protocol_log_bounded() {
387 let mut proto = CacheSyncProtocol::new(1, 1).with_max_log_size(3);
388 for i in 0..10 {
389 proto.record_event(SyncEvent::ShardUpdated {
390 rank: 0,
391 layer: 0,
392 seq_pos: i,
393 });
394 }
395 assert_eq!(proto.event_count(), 3);
396 }
397
398 #[test]
399 fn protocol_recent_events() {
400 let mut proto = CacheSyncProtocol::new(1, 1);
401 proto.record_event(SyncEvent::ShardUpdated {
402 rank: 0,
403 layer: 0,
404 seq_pos: 1,
405 });
406 proto.record_event(SyncEvent::ShardUpdated {
407 rank: 0,
408 layer: 0,
409 seq_pos: 2,
410 });
411 proto.record_event(SyncEvent::FlushAll);
412 let recent = proto.recent_events(2);
413 assert_eq!(recent.len(), 2);
414 assert_eq!(recent[1], SyncEvent::FlushAll);
415 }
416
417 #[test]
418 fn cache_routing_hint() {
419 let hint = CacheRoutingHint::new(42, vec![0, 2], 128);
420 assert!(hint.has_targets());
421 assert_eq!(hint.reusable_tokens, 128);
422 }
423
424 #[test]
425 fn cache_routing_hint_empty() {
426 let hint = CacheRoutingHint::new(0, vec![], 0);
427 assert!(!hint.has_targets());
428 }
429}