use super::{DeviceResource, Driver};
use crate::server::{CommunicationId, ReduceOperation, ServerError};
use alloc::{format, vec, 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 elements(dtype: ElemType, size: u64) -> Result<Elements<Self::DataType>, ServerError>;
fn all_reduce(
comm: &Self::Communicator,
src: &DeviceResource<Self>,
dst: &DeviceResource<Self>,
elements: Elements<Self::DataType>,
op: ReduceOperation,
stream: Self::CommStream,
) -> Result<(), ServerError>;
fn all_gather(
comm: &Self::Communicator,
src: &DeviceResource<Self>,
dst: &DeviceResource<Self>,
elements: Elements<Self::DataType>,
stream: Self::CommStream,
) -> Result<(), ServerError>;
fn reduce_scatter(
comm: &Self::Communicator,
src: &DeviceResource<Self>,
dst: &DeviceResource<Self>,
elements: Elements<Self::DataType>,
op: ReduceOperation,
stream: Self::CommStream,
) -> Result<(), ServerError>;
fn broadcast(
comm: &Self::Communicator,
src: Option<&DeviceResource<Self>>,
dst: &DeviceResource<Self>,
elements: Elements<Self::DataType>,
root: usize,
stream: Self::CommStream,
) -> Result<(), ServerError>;
fn all_to_all(
comm: &Self::Communicator,
src: &DeviceResource<Self>,
dst: &DeviceResource<Self>,
elements: Elements<Self::DataType>,
ranks: usize,
stream: Self::CommStream,
) -> Result<(), ServerError>;
fn send(
comm: &Self::Communicator,
src: &DeviceResource<Self>,
elements: Elements<Self::DataType>,
peer: usize,
stream: Self::CommStream,
) -> Result<(), ServerError>;
fn recv(
comm: &Self::Communicator,
dst: &DeviceResource<Self>,
elements: Elements<Self::DataType>,
peer: usize,
stream: Self::CommStream,
) -> Result<(), ServerError>;
}
#[derive(Debug, Clone, Copy)]
pub struct Elements<T> {
pub data_type: T,
pub count: usize,
}
pub struct Collectives<D: CollectiveDriver> {
device: DeviceId,
joined: HashMap<CommunicationId, D::Communicator>,
broken: HashMap<CommunicationId, ServerError>,
}
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())
.field("broken", &self.broken.len())
.finish()
}
}
impl<D: CollectiveDriver> Collectives<D> {
pub fn new(device: DeviceId) -> Self {
Self {
device,
joined: HashMap::default(),
broken: HashMap::default(),
}
}
pub fn mark_broken(&mut self, group: &CommunicationId, cause: ServerError) {
self.broken.entry(group.clone()).or_insert(cause);
}
pub fn ensure_intact(&self, group: &CommunicationId) -> Result<(), ServerError> {
match self.broken.get(group) {
None => Ok(()),
Some(cause) => Err(ServerError::Generic {
reason: format!(
"an earlier collective over this group failed on this device, so its peers \
wait in that one and this device runs no other over the group: {cause}"
),
backtrace: BackTrace::capture(),
}),
}
}
pub fn join(&mut self, devices: Vec<DeviceId>) -> Result<Option<CommunicationId>, ServerError> {
self.join_as(CommunicationId::from(devices.clone()), devices)
}
pub fn transfers_with(&mut self, peer: DeviceId) -> Result<&D::Communicator, ServerError> {
let id = CommunicationId::transfers_between(self.device, peer);
self.join_as(id.clone(), vec![self.device, peer])?;
self.get(&id)
}
fn join_as(
&mut self,
id: CommunicationId,
devices: Vec<DeviceId>,
) -> Result<Option<CommunicationId>, ServerError> {
if self.joined.contains_key(&id) {
return Ok(None);
}
let mut devices = devices;
devices.sort();
if let Some(pair) = devices.windows(2).find(|pair| pair[0] == pair[1]) {
return Err(ServerError::Generic {
reason: format!(
"{:?} appears twice among the devices of a group, which need one rank each",
pair[0]
),
backtrace: BackTrace::capture(),
});
}
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 rank(&self, device: DeviceId, devices: &[DeviceId]) -> Result<usize, ServerError> {
let mut sorted = devices.to_vec();
sorted.sort();
rank_in(device, &sorted).ok_or_else(|| ServerError::Generic {
reason: format!("{device:?} is not among the devices of the group"),
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));
}
}