use candle_core::{Device, Result, Tensor};
use candle_nn::VarMap;
use super::sync::{GradientSynchronizer, SyncedGradient};
pub struct NcclSynchronizer {
devices: Vec<Device>,
world_size: usize,
}
impl NcclSynchronizer {
pub fn new(gpu_ids: &[usize]) -> Result<Self> {
let world_size = gpu_ids.len();
if world_size < 2 {
return Err(candle_core::Error::Msg(
"NcclSynchronizer requires at least 2 GPUs. Use CpuStagingSynchronizer for single GPU.".to_string()
));
}
let devices: Vec<Device> = gpu_ids
.iter()
.map(|&id| Device::new_cuda(id))
.collect::<Result<Vec<_>>>()?;
eprintln!("WARNING: NCCL backend is not yet fully implemented.");
eprintln!(" Using GPU device creation for validation only.");
eprintln!(" Gradient synchronization will fall back to no-op.");
Ok(Self {
devices,
world_size,
})
}
#[allow(dead_code)]
pub fn primary_device(&self) -> &Device {
&self.devices[0]
}
}
impl GradientSynchronizer for NcclSynchronizer {
fn synchronize_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) {
let scale = 1.0 / self.world_size as f64;
let scaled_grad = (grad * scale)?;
result.push((name.clone(), var.clone(), scaled_grad));
}
}
Ok(result)
}
fn local_rank(&self) -> usize {
0 }
fn world_size(&self) -> usize {
self.world_size
}
fn barrier(&self) -> Result<()> {
Ok(())
}
}