use fuel::Result;
use fuel::lazy::Tensor;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ReduceOp {
Sum,
Product,
Min,
Max,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CommInfo {
pub rank: usize,
pub world_size: usize,
}
impl CommInfo {
pub fn is_root(&self) -> bool {
self.rank == 0
}
}
pub trait Communicator: Send {
fn info(&self) -> CommInfo;
fn all_reduce(&self, tensor: &Tensor, op: ReduceOp) -> Result<Tensor>;
fn all_gather(&self, tensor: &Tensor, dim: usize) -> Result<Tensor>;
fn reduce_scatter(&self, tensor: &Tensor, op: ReduceOp, dim: usize) -> Result<Tensor>;
fn broadcast(&self, tensor: &Tensor, root: usize) -> Result<Tensor>;
fn barrier(&self) -> Result<()>;
}
pub struct IdentityComm;
impl Communicator for IdentityComm {
fn info(&self) -> CommInfo {
CommInfo {
rank: 0,
world_size: 1,
}
}
fn all_reduce(&self, tensor: &Tensor, _op: ReduceOp) -> Result<Tensor> {
Ok(tensor.clone())
}
fn all_gather(&self, tensor: &Tensor, _dim: usize) -> Result<Tensor> {
Ok(tensor.clone())
}
fn reduce_scatter(&self, tensor: &Tensor, _op: ReduceOp, _dim: usize) -> Result<Tensor> {
Ok(tensor.clone())
}
fn broadcast(&self, tensor: &Tensor, _root: usize) -> Result<Tensor> {
Ok(tensor.clone())
}
fn barrier(&self) -> Result<()> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use fuel::{Device, Shape};
fn t(data: &[f32]) -> Tensor {
Tensor::from_f32(
data.to_vec(),
Shape::from_dims(&[data.len()]),
&Device::cpu(),
)
.unwrap()
}
#[test]
fn identity_comm_info() {
let comm = IdentityComm;
let info = comm.info();
assert_eq!(info.rank, 0);
assert_eq!(info.world_size, 1);
assert!(info.is_root());
}
#[test]
fn identity_all_reduce() {
let comm = IdentityComm;
let result = comm
.all_reduce(&t(&[1.0, 2.0, 3.0]), ReduceOp::Sum)
.unwrap();
assert_eq!(result.realize_f32(), vec![1.0, 2.0, 3.0]);
}
#[test]
fn identity_all_gather() {
let comm = IdentityComm;
let result = comm.all_gather(&t(&[1.0, 2.0]), 0).unwrap();
assert_eq!(result.realize_f32(), vec![1.0, 2.0]);
}
#[test]
fn identity_comm_reduces_nothing_and_that_is_correct() {
let comm = IdentityComm;
let out = comm.all_reduce(&t(&[1.0, 2.0]), ReduceOp::Sum).unwrap();
assert_eq!(comm.info().world_size, 1);
assert_eq!(out.realize_f32(), vec![1.0, 2.0]);
}
#[test]
fn identity_preserves_graph_affinity() {
let comm = IdentityComm;
let input = t(&[1.0, 2.0]);
let out = comm.all_reduce(&input, ReduceOp::Sum).unwrap();
assert_eq!(out.graph_id(), input.graph_id());
assert_eq!(out.add(&input).unwrap().realize_f32(), vec![2.0, 4.0]);
}
#[test]
fn identity_broadcast() {
let comm = IdentityComm;
let result = comm.broadcast(&t(&[5.0]), 0).unwrap();
assert_eq!(result.realize_f32(), vec![5.0]);
}
#[test]
fn identity_reduce_scatter() {
let comm = IdentityComm;
let result = comm
.reduce_scatter(&t(&[1.0, 2.0, 3.0]), ReduceOp::Sum, 0)
.unwrap();
assert_eq!(result.realize_f32(), vec![1.0, 2.0, 3.0]);
}
#[test]
fn identity_barrier() {
let comm = IdentityComm;
assert!(comm.barrier().is_ok());
}
#[test]
fn comm_info_non_root() {
let info = CommInfo {
rank: 3,
world_size: 4,
};
assert!(!info.is_root());
}
#[test]
fn reduce_op_variants() {
let ops = [
ReduceOp::Sum,
ReduceOp::Product,
ReduceOp::Min,
ReduceOp::Max,
];
for (i, a) in ops.iter().enumerate() {
for (j, b) in ops.iter().enumerate() {
assert_eq!(i == j, a == b);
}
}
}
}