use std::collections::{BTreeMap, HashMap};
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct SlotMoves {
pub recv: Vec<(usize, usize)>,
pub send: Vec<(usize, usize)>,
pub local_copy: Vec<(usize, usize)>,
pub free_rider: Vec<(usize, usize)>,
}
impl SlotMoves {
pub fn num_changed_slots(&self) -> usize {
self.recv.len() + self.local_copy.len() + self.free_rider.len()
}
}
fn spread(elements: &[usize], over: &[usize]) -> HashMap<usize, usize> {
let mut assignment = HashMap::new();
if elements.is_empty() || over.is_empty() {
return assignment;
}
let (short, long_runs) = (elements.len() / over.len(), elements.len() % over.len());
let mut start = 0;
for (index, &target) in over.iter().enumerate() {
let end = start + short + usize::from(index < long_runs);
for &element in &elements[start..end] {
assignment.insert(element, target);
}
start = end;
}
assignment
}
pub fn plan_slot_moves(
old: &[i32],
new: &[i32],
ep_rank: usize,
ep_rank_nodes: &[i64],
num_local: usize,
) -> Result<SlotMoves, String> {
let ep_size = ep_rank_nodes.len();
let num_physical = old.len();
if new.len() != num_physical {
return Err("old_row and new_row must have the same length".to_owned());
}
if num_local == 0 || num_physical != ep_size * num_local {
return Err(format!(
"{num_physical} slots are not {ep_size} ranks x {num_local} local slots"
));
}
if ep_rank >= ep_size {
return Err(format!(
"ep_rank={ep_rank} is outside the {ep_size} EP ranks"
));
}
let first = ep_rank * num_local;
let mut src_ranks_of: HashMap<i32, Vec<usize>> = HashMap::new();
for (p, &logical) in old.iter().enumerate() {
let ranks = src_ranks_of.entry(logical).or_default();
if ranks.last() != Some(&(p / num_local)) {
ranks.push(p / num_local);
}
}
let mut dst_ranks_of: HashMap<i32, Vec<usize>> = HashMap::new();
for (p, &logical) in new.iter().enumerate() {
let rank = p / num_local;
let holds = src_ranks_of
.get(&logical)
.is_some_and(|ranks| ranks.contains(&rank));
let ranks = dst_ranks_of.entry(logical).or_default();
if !holds && ranks.last() != Some(&rank) {
ranks.push(rank);
}
}
let mut held_slot_of: HashMap<i32, usize> = HashMap::new();
for slot in (0..num_local).rev() {
held_slot_of.insert(old[first + slot], slot);
}
let source_for = |logical: i32, dst_rank: usize| -> usize {
let src_ranks = &src_ranks_of[&logical];
let dst_ranks = &dst_ranks_of[&logical];
let dst_node = ep_rank_nodes[dst_rank];
let same_node_sources: Vec<usize> = src_ranks
.iter()
.copied()
.filter(|&r| ep_rank_nodes[r] == dst_node)
.collect();
if !same_node_sources.is_empty() {
let same_node_dsts: Vec<usize> = dst_ranks
.iter()
.copied()
.filter(|&r| ep_rank_nodes[r] == dst_node)
.collect();
return spread(&same_node_dsts, &same_node_sources)[&dst_rank];
}
let src_nodes: Vec<i64> = src_ranks.iter().map(|&r| ep_rank_nodes[r]).collect();
let cross_node_dsts: Vec<usize> = dst_ranks
.iter()
.copied()
.filter(|&r| !src_nodes.contains(&ep_rank_nodes[r]))
.collect();
spread(&cross_node_dsts, src_ranks)[&dst_rank]
};
let mut recv: Vec<(i32, usize, usize)> = Vec::new();
let mut moves = SlotMoves::default();
let mut first_receiving_slot: HashMap<i32, usize> = HashMap::new();
for dst_slot in 0..num_local {
let logical = new[first + dst_slot];
if old[first + dst_slot] == logical {
continue;
}
if let Some(&held) = held_slot_of.get(&logical) {
moves.local_copy.push((dst_slot, held));
continue;
}
let earlier = *first_receiving_slot.entry(logical).or_insert(dst_slot);
if earlier != dst_slot {
moves.free_rider.push((dst_slot, earlier));
continue;
}
recv.push((logical, dst_slot, source_for(logical, ep_rank)));
}
let mut send: Vec<(i32, usize, usize)> = Vec::new();
let by_slot: BTreeMap<usize, i32> = held_slot_of
.iter()
.map(|(&logical, &slot)| (slot, logical))
.collect();
for (&src_slot, &logical) in &by_slot {
for &dst_rank in dst_ranks_of.get(&logical).map_or(&[][..], Vec::as_slice) {
if source_for(logical, dst_rank) == ep_rank {
send.push((logical, src_slot, dst_rank));
}
}
}
recv.sort_unstable();
send.sort_unstable();
moves.recv = recv
.into_iter()
.map(|(_, slot, rank)| (slot, rank))
.collect();
moves.send = send
.into_iter()
.map(|(_, slot, rank)| (slot, rank))
.collect();
Ok(moves)
}