use core::ops::Range;
use std::{collections::HashMap, sync::Arc};
use crate::{
NodeId,
global::shared::GlobalCollectiveError,
local::{get_ring_reduce_slice_ranges, get_slice_dim},
node::sync::SyncService,
};
use ruda_tensor::{Backend, Slice, TensorMetadata};
use ruda_communication::{Address, Protocol, data_service::TensorDataService};
pub(crate) async fn ring_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,
) -> Result<B::FloatTensorPrimitive, GlobalCollectiveError>
where
B: Backend,
P: Protocol,
{
let shape = tensor.shape();
let device = &B::float_device(&tensor);
let slice_dim = get_slice_dim(&shape);
if shape[slice_dim] < nodes.len() {
return Err(GlobalCollectiveError::RingReduceImpossible);
}
let ring = get_ring_topology(nodes.keys().cloned().collect::<Vec<_>>());
let slice_ranges = get_ring_reduce_slice_ranges(shape[slice_dim], ring.len());
let mut slices = slice_tensor::<B>(tensor, slice_dim, slice_ranges);
let mut send_slice_idx = ring
.iter()
.position(|id| *id == node)
.expect("Node is in ring");
let prev_node_idx = (send_slice_idx + ring.len() - 1) % ring.len(); let prev_node = nodes.get(&ring[prev_node_idx]).unwrap();
let mut transfer_counter: u64 = 0;
do_cycles::<B, P>(
&mut slices,
&mut transfer_counter,
&mut send_slice_idx,
true,
prev_node.clone(),
&data_service,
device,
)
.await?;
do_cycles::<B, P>(
&mut slices,
&mut transfer_counter,
&mut send_slice_idx,
false,
prev_node.clone(),
&data_service,
device,
)
.await?;
sync_service.sync().await;
Ok(B::float_cat(slices, slice_dim))
}
async fn do_cycles<B, P>(
slices: &mut [B::FloatTensorPrimitive],
transfer_counter: &mut u64,
send_slice_idx: &mut usize,
is_phase_one: bool,
prev_node: Address,
data_service: &Arc<TensorDataService<B, P>>,
device: &B::Device,
) -> Result<(), GlobalCollectiveError>
where
B: Backend,
P: Protocol,
{
let slice_count = slices.len();
for _ in 0..(slice_count - 1) {
let transfer_id = (*transfer_counter).into();
let recv_slice_idx = (*send_slice_idx + slice_count - 1) % slice_count;
let slice_send = slices[*send_slice_idx].clone();
let upload = {
let data_service = data_service.clone();
tokio::spawn(async move {
data_service
.expose(slice_send.clone(), 1, transfer_id)
.await
})
};
let download = {
let data_client = data_service.clone();
let next_node = prev_node.clone();
tokio::spawn(async move { data_client.download_tensor(next_node, transfer_id).await })
};
upload.await.unwrap();
let download = download.await.unwrap();
if is_phase_one {
let download = download.expect("Peer closed download connection");
let tensor = B::float_from_data(download, device);
slices[recv_slice_idx] = B::float_add(slices[recv_slice_idx].clone(), tensor);
} else {
let tensor = B::float_from_data(download.unwrap(), device);
let old_shape = slices[recv_slice_idx].shape();
if old_shape != tensor.shape() {
return Err(GlobalCollectiveError::PeerSentIncoherentTensor);
}
slices[recv_slice_idx] = tensor;
}
*send_slice_idx = recv_slice_idx;
*transfer_counter += 1;
}
Ok(())
}
fn slice_tensor<B: Backend>(
tensor: B::FloatTensorPrimitive,
slice_dim: usize,
slice_ranges: Vec<Range<usize>>,
) -> Vec<B::FloatTensorPrimitive> {
let shape = tensor.shape();
let full_range = shape
.iter()
.map(|dim| Slice::from(0..*dim))
.collect::<Vec<Slice>>();
let mut slices = vec![];
for range in &slice_ranges {
let mut all_ranges = full_range.clone();
all_ranges[slice_dim] = Slice::from(range.clone());
let slice = B::float_slice(tensor.clone(), &all_ranges);
slices.push(slice);
}
slices
}
fn get_ring_topology(mut nodes: Vec<NodeId>) -> Vec<NodeId> {
nodes.sort();
nodes
}