use std::{collections::HashMap, sync::Arc};
use crate::{NodeId, global::shared::GlobalCollectiveError, node::sync::SyncService};
use ruda_tensor::{Backend, TensorMetadata};
use ruda_communication::{Address, Protocol, data_service::TensorDataService};
use futures::{StreamExt, stream::FuturesUnordered};
struct TreeTopology {
parents: HashMap<NodeId, NodeId>,
children: HashMap<NodeId, Vec<NodeId>>,
}
pub(crate) async fn tree_all_reduce_sum<B, P>(
node: NodeId,
nodes: &HashMap<NodeId, Address>,
data_service: Arc<TensorDataService<B, P>>,
sync_service: Arc<SyncService<P>>,
tensor: B::FloatTensorPrimitive,
arity: u32,
) -> Result<B::FloatTensorPrimitive, GlobalCollectiveError>
where
B: Backend,
P: Protocol,
{
let shape = tensor.shape();
let device = &B::float_device(&tensor);
let strategy = get_tree_topology(nodes.keys().cloned().collect::<Vec<_>>(), arity);
let mut result = tensor;
if let Some(children) = strategy.children.get(&node) {
let mut downloads = children
.iter()
.map(|child| {
let child_addr = nodes.get(child).unwrap().clone();
let data_service = data_service.clone();
async move {
let data = data_service
.download_tensor(child_addr.clone(), 0.into())
.await
.ok_or(GlobalCollectiveError::PeerLost(*child))?;
Ok::<B::FloatTensorPrimitive, GlobalCollectiveError>(B::float_from_data(
data, device,
))
}
})
.collect::<FuturesUnordered<_>>();
for _ in children {
let res = downloads.next().await.unwrap().unwrap();
if res.shape() != shape {
return Err(GlobalCollectiveError::PeerSentIncoherentTensor);
}
result = B::float_add(result, res);
}
}
if let Some(parent) = strategy.parents.get(&node) {
data_service.expose(result.clone(), 1, 0.into()).await;
let parent_addr = nodes.get(parent).unwrap().clone();
let data = data_service
.download_tensor(parent_addr.clone(), 1.into())
.await
.ok_or(GlobalCollectiveError::PeerLost(*parent))?;
let parent_tensor = B::float_from_data(data, device);
if parent_tensor.shape() != shape {
return Err(GlobalCollectiveError::PeerSentIncoherentTensor);
}
result = parent_tensor;
}
if let Some(children) = strategy.children.get(&node)
&& !children.is_empty()
{
data_service
.expose(result.clone(), children.len() as u32, 1.into())
.await;
}
sync_service.sync().await;
Ok(result)
}
fn get_tree_topology(mut nodes: Vec<NodeId>, arity: u32) -> TreeTopology {
assert!(arity >= 1, "Arity must be ≥ 1");
nodes.sort();
let n = nodes.len();
let k = arity as usize;
let mut parents: HashMap<_, _> = HashMap::with_capacity(n);
let mut children: HashMap<_, _> = HashMap::with_capacity(n);
for (i, &parent_id) in nodes.iter().enumerate() {
let first = i * k + 1;
if first < n {
let last = usize::min(first + k, n);
let mut ch = Vec::with_capacity(last - first);
for &child_id in &nodes[first..last] {
parents.insert(child_id, parent_id);
ch.push(child_id);
}
children.insert(parent_id, ch);
} else {
children.insert(parent_id, Vec::new());
}
}
TreeTopology { parents, children }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_tree_topology_arity2_size7() {
let mut nodes = vec![];
for i in 0..7 {
nodes.push(i.into());
}
let topology = get_tree_topology(nodes, 2);
assert!(!topology.parents.contains_key(&0.into()));
let expected_parents = [
(1.into(), 0.into()),
(2.into(), 0.into()),
(3.into(), 1.into()),
(4.into(), 1.into()),
(5.into(), 2.into()),
(6.into(), 2.into()),
];
for (child, parent) in &expected_parents {
assert_eq!(
topology.parents.get(child),
Some(parent),
"wrong parent for {child:?}"
);
}
assert_eq!(topology.parents.len(), expected_parents.len());
assert_eq!(
topology.children.get(&0.into()),
Some(&vec![1.into(), 2.into()])
);
assert_eq!(
topology.children.get(&1.into()),
Some(&vec![3.into(), 4.into()])
);
assert_eq!(
topology.children.get(&2.into()),
Some(&vec![5.into(), 6.into()])
);
for leaf in 3..7 {
assert_eq!(
topology.children.get(&leaf.into()),
Some(&Vec::new()),
"leaf {leaf:?} should have no children"
);
}
assert_eq!(topology.children.len(), 7);
}
}