use super::{DeviceResource, Driver};
use crate::server::{CommunicationId, ReduceOperation, ServerError};
use alloc::format;
use alloc::vec::Vec;
use cubecl_common::device::DeviceId;
use cubecl_environment::backtrace::BackTrace;
use cubecl_environment::collections::HashMap;
use cubecl_ir::ElemType;
pub trait CollectiveDriver: Driver {
type Communicator;
type UniqueId: Copy;
type DataType: Copy;
type CommStream: Copy;
fn group_id(id: &CommunicationId) -> Result<Self::UniqueId, ServerError>;
fn join(
id: Self::UniqueId,
ranks: usize,
rank: usize,
) -> Result<Self::Communicator, ServerError>;
fn data_type(dtype: ElemType, size: u64) -> Result<(Self::DataType, usize), ServerError>;
fn all_reduce(
comm: &Self::Communicator,
src: &DeviceResource<Self>,
dst: &DeviceResource<Self>,
dtype: Self::DataType,
count: usize,
op: ReduceOperation,
stream: Self::CommStream,
) -> Result<(), ServerError>;
fn send(
comm: &Self::Communicator,
src: &DeviceResource<Self>,
dtype: Self::DataType,
count: usize,
peer: usize,
stream: Self::CommStream,
) -> Result<(), ServerError>;
fn recv(
comm: &Self::Communicator,
dst: &DeviceResource<Self>,
dtype: Self::DataType,
count: usize,
peer: usize,
stream: Self::CommStream,
) -> Result<(), ServerError>;
}
pub struct Collectives<D: CollectiveDriver> {
device: DeviceId,
joined: HashMap<CommunicationId, D::Communicator>,
}
impl<D: CollectiveDriver> core::fmt::Debug for Collectives<D> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Collectives")
.field("device", &self.device)
.field("joined", &self.joined.len())
.finish()
}
}
impl<D: CollectiveDriver> Collectives<D> {
pub fn new(device: DeviceId) -> Self {
Self {
device,
joined: HashMap::default(),
}
}
pub fn join(&mut self, devices: Vec<DeviceId>) -> Result<Option<CommunicationId>, ServerError> {
let id = CommunicationId::from(devices.clone());
if self.joined.contains_key(&id) {
return Ok(None);
}
let mut devices = devices;
devices.sort();
let rank = rank_in(self.device, &devices).ok_or_else(|| ServerError::Generic {
reason: format!(
"this device ({:?}) is not among the {} the group was formed over, \
so it has no rank in it",
self.device,
devices.len()
),
backtrace: BackTrace::capture(),
})?;
let comm = D::join(D::group_id(&id)?, devices.len(), rank)?;
self.joined.insert(id.clone(), comm);
Ok(Some(id))
}
pub fn get(&self, devices: &CommunicationId) -> Result<&D::Communicator, ServerError> {
self.joined
.get(devices)
.ok_or_else(|| ServerError::Generic {
reason: "no communicator for this group; it has to be joined first".into(),
backtrace: BackTrace::capture(),
})
}
pub fn peer_rank(&self, devices: &[DeviceId]) -> Result<usize, ServerError> {
peer_of(self.device, devices).ok_or_else(|| ServerError::Generic {
reason: format!(
"every device in the pair is this one ({:?}), so there is no peer",
self.device
),
backtrace: BackTrace::capture(),
})
}
}
fn rank_in(device: DeviceId, devices: &[DeviceId]) -> Option<usize> {
devices.iter().position(|id| id.index_id == device.index_id)
}
fn peer_of(device: DeviceId, devices: &[DeviceId]) -> Option<usize> {
devices.iter().position(|id| id.index_id != device.index_id)
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
fn device(index: u16) -> DeviceId {
DeviceId {
type_id: 0,
index_id: index,
}
}
#[test]
fn a_rank_does_not_depend_on_the_order_the_group_was_given_in() {
let group = [device(7), device(2), device(5)];
for given in [
vec![group[0], group[1], group[2]],
vec![group[2], group[1], group[0]],
vec![group[1], group[2], group[0]],
] {
let mut sorted = given.clone();
sorted.sort();
for (expected, member) in sorted.iter().enumerate() {
assert_eq!(
rank_in(*member, &sorted),
Some(expected),
"{member:?} in {given:?}"
);
}
}
}
#[test]
fn a_device_outside_the_group_has_no_rank() {
assert_eq!(rank_in(device(9), &[device(1), device(2)]), None);
}
#[test]
fn the_peer_of_a_pair_is_the_other_member() {
let pair = [device(3), device(8)];
assert_eq!(peer_of(device(3), &pair), Some(1));
assert_eq!(peer_of(device(8), &pair), Some(0));
}
#[test]
fn a_pair_of_one_device_has_no_peer() {
assert_eq!(peer_of(device(4), &[device(4), device(4)]), None);
}
#[test]
fn a_rank_is_the_device_index_and_nothing_else() {
let other_kind = DeviceId {
type_id: 1,
index_id: 2,
};
assert_eq!(rank_in(other_kind, &[device(1), device(2)]), Some(1));
}
}