use candle_core::{Device, Result};
use candle_nn::VarMap;
use std::sync::{Arc, Barrier};
use super::sync::{GradientSynchronizer, SyncedGradient};
pub struct CpuStagingSynchronizer {
#[allow(dead_code)]
devices: Vec<Device>,
world_size: usize,
#[allow(dead_code)]
barrier: Arc<Barrier>,
}
impl CpuStagingSynchronizer {
pub fn new(gpu_ids: &[usize]) -> Result<Self> {
let world_size = gpu_ids.len();
if world_size == 0 {
return Err(candle_core::Error::Msg(
"CpuStagingSynchronizer requires at least 1 GPU".to_string()
));
}
let devices: Vec<Device> = gpu_ids
.iter()
.map(|&id| Device::new_cuda(id))
.collect::<Result<Vec<_>>>()?;
let barrier = Arc::new(Barrier::new(world_size));
Ok(Self {
devices,
world_size,
barrier,
})
}
pub fn single_gpu(gpu_id: usize) -> Result<Self> {
Self::new(&[gpu_id])
}
#[allow(dead_code)]
pub fn primary_device(&self) -> &Device {
&self.devices[0]
}
}
impl GradientSynchronizer for CpuStagingSynchronizer {
fn synchronize_gradients(
&self,
grads: &candle_core::backprop::GradStore,
varmap: &VarMap,
) -> Result<Vec<SyncedGradient>> {
if self.world_size == 1 {
return self.passthrough_gradients(grads, varmap);
}
self.average_gradients(grads, varmap)
}
fn local_rank(&self) -> usize {
0 }
fn world_size(&self) -> usize {
self.world_size
}
fn barrier(&self) -> Result<()> {
Ok(())
}
}
impl CpuStagingSynchronizer {
fn passthrough_gradients(
&self,
grads: &candle_core::backprop::GradStore,
varmap: &VarMap,
) -> Result<Vec<SyncedGradient>> {
let data = varmap.data().lock().unwrap();
let mut result = Vec::with_capacity(data.len());
for (name, var) in data.iter() {
if let Some(grad) = grads.get(var) {
result.push((name.clone(), var.clone(), grad.clone()));
}
}
Ok(result)
}
fn average_gradients(
&self,
grads: &candle_core::backprop::GradStore,
varmap: &VarMap,
) -> Result<Vec<SyncedGradient>> {
let data = varmap.data().lock().unwrap();
let mut result = Vec::with_capacity(data.len());
let scale = 1.0 / self.world_size as f64;
for (name, var) in data.iter() {
if let Some(grad) = grads.get(var) {
let grad_cpu = grad.to_device(&Device::Cpu)?;
let scaled_grad = (&grad_cpu * scale)?;
let grad_synced = scaled_grad.to_device(grad.device())?;
result.push((name.clone(), var.clone(), grad_synced));
}
}
Ok(result)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sync_backend_parsing() {
use super::super::sync::SyncBackend;
assert_eq!("cpu".parse::<SyncBackend>().unwrap(), SyncBackend::CpuStaging);
assert_eq!("nccl".parse::<SyncBackend>().unwrap(), SyncBackend::Nccl);
assert_eq!("single".parse::<SyncBackend>().unwrap(), SyncBackend::Single);
}
}