use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CacheShardInfo {
rank: usize,
world_size: usize,
num_layers: usize,
layers: Vec<usize>,
}
impl CacheShardInfo {
pub fn new(rank: usize, world_size: usize, num_layers: usize) -> Self {
assert!(world_size > 0, "world_size must be > 0");
assert!(rank < world_size, "rank must be < world_size");
let per_rank = num_layers / world_size;
let remainder = num_layers % world_size;
let start = per_rank * rank + rank.min(remainder);
let count = per_rank + if rank < remainder { 1 } else { 0 };
let layers: Vec<usize> = (start..start + count).collect();
Self {
rank,
world_size,
num_layers,
layers,
}
}
pub fn with_layers(
rank: usize,
world_size: usize,
num_layers: usize,
layers: Vec<usize>,
) -> Self {
Self {
rank,
world_size,
num_layers,
layers,
}
}
pub fn rank(&self) -> usize {
self.rank
}
pub fn world_size(&self) -> usize {
self.world_size
}
pub fn num_layers(&self) -> usize {
self.num_layers
}
pub fn layers(&self) -> &[usize] {
&self.layers
}
pub fn owns_layer(&self, layer: usize) -> bool {
self.layers.contains(&layer)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SyncEvent {
ShardUpdated {
rank: usize,
layer: usize,
seq_pos: usize,
},
PrefixConfirmed {
prefix_id: u64,
num_tokens: usize,
},
ShardEvicted {
rank: usize,
layer: usize,
seq_pos: usize,
},
FlushAll,
}
#[derive(Debug, Clone)]
pub struct CacheSyncProtocol {
world_size: usize,
_num_layers: usize,
positions: HashMap<(usize, usize), usize>,
confirmed_prefixes: HashMap<u64, usize>,
event_log: Vec<SyncEvent>,
max_log_size: usize,
}
impl CacheSyncProtocol {
pub fn new(world_size: usize, num_layers: usize) -> Self {
Self {
world_size,
_num_layers: num_layers,
positions: HashMap::new(),
confirmed_prefixes: HashMap::new(),
event_log: Vec::new(),
max_log_size: 10_000,
}
}
pub fn with_max_log_size(mut self, max: usize) -> Self {
self.max_log_size = max;
self
}
pub fn record_event(&mut self, event: SyncEvent) {
match &event {
SyncEvent::ShardUpdated {
rank,
layer,
seq_pos,
} => {
self.positions.insert((*rank, *layer), *seq_pos);
}
SyncEvent::PrefixConfirmed {
prefix_id,
num_tokens,
} => {
self.confirmed_prefixes.insert(*prefix_id, *num_tokens);
}
SyncEvent::ShardEvicted {
rank,
layer,
seq_pos,
} => {
self.positions.insert((*rank, *layer), *seq_pos);
}
SyncEvent::FlushAll => {
self.positions.clear();
self.confirmed_prefixes.clear();
}
}
self.event_log.push(event);
if self.event_log.len() > self.max_log_size {
let drain = self.event_log.len() - self.max_log_size;
self.event_log.drain(..drain);
}
}
pub fn latest_position(&self, rank: usize, layer: usize) -> Option<usize> {
self.positions.get(&(rank, layer)).copied()
}
pub fn min_synced_position(&self, layer: usize) -> Option<usize> {
let mut min_pos: Option<usize> = None;
for rank in 0..self.world_size {
match self.positions.get(&(rank, layer)) {
Some(&pos) => {
min_pos = Some(min_pos.map_or(pos, |m: usize| m.min(pos)));
}
None => return None,
}
}
min_pos
}
pub fn is_prefix_confirmed(&self, prefix_id: u64) -> bool {
self.confirmed_prefixes.contains_key(&prefix_id)
}
pub fn prefix_tokens(&self, prefix_id: u64) -> Option<usize> {
self.confirmed_prefixes.get(&prefix_id).copied()
}
pub fn event_count(&self) -> usize {
self.event_log.len()
}
pub fn recent_events(&self, n: usize) -> &[SyncEvent] {
let start = self.event_log.len().saturating_sub(n);
&self.event_log[start..]
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CacheRoutingHint {
pub prefix_id: u64,
pub relevant_ranks: Vec<usize>,
pub reusable_tokens: usize,
}
impl CacheRoutingHint {
pub fn new(prefix_id: u64, relevant_ranks: Vec<usize>, reusable_tokens: usize) -> Self {
Self {
prefix_id,
relevant_ranks,
reusable_tokens,
}
}
pub fn has_targets(&self) -> bool {
!self.relevant_ranks.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn shard_info_even_split() {
let shard = CacheShardInfo::new(0, 4, 32);
assert_eq!(shard.layers().len(), 8);
assert_eq!(shard.layers(), &[0, 1, 2, 3, 4, 5, 6, 7]);
let shard3 = CacheShardInfo::new(3, 4, 32);
assert_eq!(shard3.layers(), &[24, 25, 26, 27, 28, 29, 30, 31]);
}
#[test]
fn shard_info_uneven_split() {
let shard0 = CacheShardInfo::new(0, 3, 10);
let shard1 = CacheShardInfo::new(1, 3, 10);
let shard2 = CacheShardInfo::new(2, 3, 10);
assert_eq!(shard0.layers().len(), 4);
assert_eq!(shard1.layers().len(), 3);
assert_eq!(shard2.layers().len(), 3);
let mut all: Vec<usize> = Vec::new();
all.extend(shard0.layers());
all.extend(shard1.layers());
all.extend(shard2.layers());
all.sort();
assert_eq!(all, (0..10).collect::<Vec<_>>());
}
#[test]
fn owns_layer() {
let shard = CacheShardInfo::new(1, 2, 8);
assert!(!shard.owns_layer(0)); assert!(shard.owns_layer(4)); }
#[test]
fn protocol_track_updates() {
let mut proto = CacheSyncProtocol::new(2, 4);
proto.record_event(SyncEvent::ShardUpdated {
rank: 0,
layer: 0,
seq_pos: 10,
});
proto.record_event(SyncEvent::ShardUpdated {
rank: 1,
layer: 0,
seq_pos: 8,
});
assert_eq!(proto.latest_position(0, 0), Some(10));
assert_eq!(proto.latest_position(1, 0), Some(8));
assert_eq!(proto.min_synced_position(0), Some(8));
}
#[test]
fn protocol_min_synced_none_if_missing() {
let mut proto = CacheSyncProtocol::new(3, 2);
proto.record_event(SyncEvent::ShardUpdated {
rank: 0,
layer: 0,
seq_pos: 5,
});
assert_eq!(proto.min_synced_position(0), None);
}
#[test]
fn protocol_prefix_confirmation() {
let mut proto = CacheSyncProtocol::new(2, 4);
assert!(!proto.is_prefix_confirmed(0xABCD));
proto.record_event(SyncEvent::PrefixConfirmed {
prefix_id: 0xABCD,
num_tokens: 64,
});
assert!(proto.is_prefix_confirmed(0xABCD));
assert_eq!(proto.prefix_tokens(0xABCD), Some(64));
}
#[test]
fn protocol_flush_all() {
let mut proto = CacheSyncProtocol::new(2, 4);
proto.record_event(SyncEvent::ShardUpdated {
rank: 0,
layer: 0,
seq_pos: 10,
});
proto.record_event(SyncEvent::PrefixConfirmed {
prefix_id: 1,
num_tokens: 5,
});
proto.record_event(SyncEvent::FlushAll);
assert_eq!(proto.latest_position(0, 0), None);
assert!(!proto.is_prefix_confirmed(1));
}
#[test]
fn protocol_log_bounded() {
let mut proto = CacheSyncProtocol::new(1, 1).with_max_log_size(3);
for i in 0..10 {
proto.record_event(SyncEvent::ShardUpdated {
rank: 0,
layer: 0,
seq_pos: i,
});
}
assert_eq!(proto.event_count(), 3);
}
#[test]
fn protocol_recent_events() {
let mut proto = CacheSyncProtocol::new(1, 1);
proto.record_event(SyncEvent::ShardUpdated {
rank: 0,
layer: 0,
seq_pos: 1,
});
proto.record_event(SyncEvent::ShardUpdated {
rank: 0,
layer: 0,
seq_pos: 2,
});
proto.record_event(SyncEvent::FlushAll);
let recent = proto.recent_events(2);
assert_eq!(recent.len(), 2);
assert_eq!(recent[1], SyncEvent::FlushAll);
}
#[test]
fn cache_routing_hint() {
let hint = CacheRoutingHint::new(42, vec![0, 2], 128);
assert!(hint.has_targets());
assert_eq!(hint.reusable_tokens, 128);
}
#[test]
fn cache_routing_hint_empty() {
let hint = CacheRoutingHint::new(0, vec![], 0);
assert!(!hint.has_targets());
}
}