use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
use crate::protocols::*;
pub(crate) fn append_dump_events(
events: &mut Vec<RouterEvent>,
event_id: &mut u64,
parent_hash: Option<ExternalSequenceBlockHash>,
edge: &[(LocalBlockHash, ExternalSequenceBlockHash)],
full_workers: &[WorkerWithDpRank],
worker_cutoffs: &[(WorkerWithDpRank, usize)],
) {
let blocks = edge
.iter()
.map(|&(tokens_hash, block_hash)| KvCacheStoredBlockData {
block_hash,
tokens_hash,
mm_extra_info: None,
})
.collect::<Vec<_>>();
for &worker in full_workers {
events.push(dump_event(worker, *event_id, parent_hash, blocks.clone()));
*event_id += 1;
}
for &(worker, cutoff) in worker_cutoffs {
events.push(dump_event(
worker,
*event_id,
parent_hash,
blocks[..cutoff].to_vec(),
));
*event_id += 1;
}
}
fn dump_event(
worker: WorkerWithDpRank,
event_id: u64,
parent_hash: Option<ExternalSequenceBlockHash>,
blocks: Vec<KvCacheStoredBlockData>,
) -> RouterEvent {
RouterEvent::new(
worker.worker_id,
KvCacheEvent {
event_id,
data: KvCacheEventData::Stored(KvCacheStoreData {
parent_hash,
start_position: None,
blocks,
}),
dp_rank: worker.dp_rank,
},
)
}
pub(crate) struct RemoveOutcome {
pub(crate) stale_hashes: Vec<ExternalSequenceBlockHash>,
}
#[derive(Debug)]
pub(crate) struct NodeState {
pub(crate) edge: Vec<(LocalBlockHash, ExternalSequenceBlockHash)>,
pub(crate) edge_index: FxHashMap<ExternalSequenceBlockHash, usize>,
pub(crate) worker_cutoffs: FxHashMap<WorkerWithDpRank, usize>,
pub(crate) full_edge_workers: FxHashSet<WorkerWithDpRank>,
}
impl NodeState {
pub(crate) fn empty() -> Self {
Self {
edge: Vec::new(),
edge_index: FxHashMap::default(),
worker_cutoffs: FxHashMap::default(),
full_edge_workers: FxHashSet::default(),
}
}
pub(crate) fn for_blocks(blocks: &[KvCacheStoredBlockData], worker: WorkerWithDpRank) -> Self {
let edge = blocks
.iter()
.map(|block| (block.tokens_hash, block.block_hash))
.collect::<Vec<_>>();
let mut full_edge_workers = FxHashSet::with_capacity_and_hasher(1, FxBuildHasher);
full_edge_workers.insert(worker);
Self {
edge_index: Self::edge_index_for(&edge),
edge,
worker_cutoffs: FxHashMap::default(),
full_edge_workers,
}
}
pub(crate) fn edge_index_for(
edge: &[(LocalBlockHash, ExternalSequenceBlockHash)],
) -> FxHashMap<ExternalSequenceBlockHash, usize> {
let mut edge_index = FxHashMap::with_capacity_and_hasher(edge.len(), FxBuildHasher);
for (i, &(_, hash)) in edge.iter().enumerate() {
edge_index.insert(hash, i);
}
edge_index
}
#[inline]
pub(crate) fn current_cutoff(&self, worker: WorkerWithDpRank) -> usize {
if self.full_edge_workers.contains(&worker) {
self.edge.len()
} else {
self.worker_cutoffs.get(&worker).copied().unwrap_or(0)
}
}
#[inline]
pub(crate) fn covers_pos(&self, worker: WorkerWithDpRank, pos: usize) -> bool {
self.full_edge_workers.contains(&worker)
|| matches!(self.worker_cutoffs.get(&worker), Some(&cutoff) if pos < cutoff)
}
fn newly_uncovered_hashes(
&self,
new_cutoff: usize,
old_cutoff: usize,
) -> Vec<ExternalSequenceBlockHash> {
debug_assert!(new_cutoff <= old_cutoff);
debug_assert!(old_cutoff <= self.edge.len());
self.edge[new_cutoff..old_cutoff]
.iter()
.map(|&(_, hash)| hash)
.collect()
}
#[inline]
pub(crate) fn drop_worker(&mut self, worker: WorkerWithDpRank) {
self.full_edge_workers.remove(&worker);
self.worker_cutoffs.remove(&worker);
}
#[inline]
pub(crate) fn promote_to_full(&mut self, worker: WorkerWithDpRank) -> bool {
if self.full_edge_workers.contains(&worker) {
return false;
}
self.worker_cutoffs.remove(&worker);
self.full_edge_workers.insert(worker);
true
}
pub(crate) fn cover_prefix_for_worker(
&mut self,
worker: WorkerWithDpRank,
cutoff: usize,
) -> bool {
debug_assert!(cutoff <= self.edge.len());
if cutoff == 0 {
return false;
}
if cutoff >= self.edge.len() {
return self.promote_to_full(worker);
}
if self.full_edge_workers.contains(&worker) {
return false;
}
match self.worker_cutoffs.get_mut(&worker) {
Some(existing) if *existing >= cutoff => false,
Some(existing) => {
*existing = cutoff;
true
}
None => {
self.worker_cutoffs.insert(worker, cutoff);
true
}
}
}
pub(crate) fn tail_hash_is(&self, hash: ExternalSequenceBlockHash) -> bool {
self.edge
.last()
.is_some_and(|&(_, edge_hash)| edge_hash == hash)
}
pub(crate) fn suffix_matches_store(
&self,
parent_pos: usize,
blocks: &[KvCacheStoredBlockData],
) -> bool {
let Some(suffix) = self.edge.get(parent_pos + 1..) else {
return false;
};
if blocks.len() > suffix.len() {
return false;
}
suffix
.iter()
.zip(blocks)
.all(|(&(local_hash, block_hash), block)| {
local_hash == block.tokens_hash && block_hash == block.block_hash
})
}
pub(crate) fn store_starts_with_suffix(
&self,
parent_pos: usize,
blocks: &[KvCacheStoredBlockData],
) -> Option<usize> {
let suffix = self.edge.get(parent_pos + 1..)?;
if blocks.len() <= suffix.len() {
return None;
}
if !suffix
.iter()
.zip(blocks)
.all(|(&(local_hash, block_hash), block)| {
local_hash == block.tokens_hash && block_hash == block.block_hash
})
{
return None;
}
Some(suffix.len())
}
pub(crate) fn append_blocks_to_leaf(
&mut self,
worker: WorkerWithDpRank,
blocks: &[KvCacheStoredBlockData],
) {
debug_assert!(!blocks.is_empty());
let old_len = self.edge.len();
let downgraded_workers = self
.full_edge_workers
.iter()
.copied()
.filter(|&full_worker| full_worker != worker)
.collect::<Vec<_>>();
for downgraded_worker in downgraded_workers {
self.full_edge_workers.remove(&downgraded_worker);
self.worker_cutoffs.insert(downgraded_worker, old_len);
}
self.promote_to_full(worker);
self.edge.reserve(blocks.len());
self.edge_index.reserve(blocks.len());
for (offset, block) in blocks.iter().enumerate() {
self.edge.push((block.tokens_hash, block.block_hash));
self.edge_index.insert(block.block_hash, old_len + offset);
}
}
pub(crate) fn remove_worker_at_pos(
&mut self,
worker: WorkerWithDpRank,
pos: usize,
removed_hash: ExternalSequenceBlockHash,
) -> RemoveOutcome {
let current_cutoff = self.current_cutoff(worker);
if pos >= current_cutoff {
return RemoveOutcome {
stale_hashes: vec![removed_hash],
};
}
let new_cutoff = pos;
let stale_hashes = self.newly_uncovered_hashes(new_cutoff, current_cutoff);
if new_cutoff == 0 {
self.drop_worker(worker);
} else {
self.full_edge_workers.remove(&worker);
self.worker_cutoffs.insert(worker, new_cutoff);
}
RemoveOutcome { stale_hashes }
}
pub(crate) fn has_any_workers(&self) -> bool {
!self.full_edge_workers.is_empty() || !self.worker_cutoffs.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn worker() -> WorkerWithDpRank {
WorkerWithDpRank::new(1, 0)
}
fn block(hash: u64) -> KvCacheStoredBlockData {
KvCacheStoredBlockData {
tokens_hash: LocalBlockHash(hash),
block_hash: ExternalSequenceBlockHash(hash),
mm_extra_info: None,
}
}
#[test]
fn leaf_to_root_removes_return_only_newly_uncovered_hashes() {
let worker = worker();
let blocks = [block(1), block(2), block(3), block(4)];
let mut state = NodeState::for_blocks(&blocks, worker);
let pos_4 = state.edge_index[&ExternalSequenceBlockHash(4)];
let outcome = state.remove_worker_at_pos(worker, pos_4, ExternalSequenceBlockHash(4));
assert_eq!(outcome.stale_hashes, vec![ExternalSequenceBlockHash(4)]);
assert_eq!(state.current_cutoff(worker), 3);
let pos_3 = state.edge_index[&ExternalSequenceBlockHash(3)];
let outcome = state.remove_worker_at_pos(worker, pos_3, ExternalSequenceBlockHash(3));
assert_eq!(outcome.stale_hashes, vec![ExternalSequenceBlockHash(3)]);
assert_eq!(state.current_cutoff(worker), 2);
let pos_2 = state.edge_index[&ExternalSequenceBlockHash(2)];
let outcome = state.remove_worker_at_pos(worker, pos_2, ExternalSequenceBlockHash(2));
assert_eq!(outcome.stale_hashes, vec![ExternalSequenceBlockHash(2)]);
assert_eq!(state.current_cutoff(worker), 1);
let pos_1 = state.edge_index[&ExternalSequenceBlockHash(1)];
let outcome = state.remove_worker_at_pos(worker, pos_1, ExternalSequenceBlockHash(1));
assert_eq!(outcome.stale_hashes, vec![ExternalSequenceBlockHash(1)]);
assert_eq!(state.current_cutoff(worker), 0);
assert!(!state.has_any_workers());
}
}