use candle_core::{Result, Tensor, Var};
use candle_nn::VarMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum SyncBackend {
#[default]
Single,
#[serde(alias = "cpu")]
CpuStaging,
Nccl,
}
impl std::str::FromStr for SyncBackend {
type Err = String;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"single" | "none" => Ok(SyncBackend::Single),
"cpu" | "cpu_staging" | "cpustaging" => Ok(SyncBackend::CpuStaging),
"nccl" => Ok(SyncBackend::Nccl),
_ => Err(format!("Unknown sync backend: {}. Valid options: single, cpu, nccl", s)),
}
}
}
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
pub struct DistributedConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default)]
pub backend: SyncBackend,
#[serde(default)]
pub gpu_ids: Vec<usize>,
}
#[allow(dead_code)]
impl DistributedConfig {
pub fn single_gpu(gpu_id: usize) -> Self {
Self {
enabled: false,
backend: SyncBackend::Single,
gpu_ids: vec![gpu_id],
}
}
pub fn multi_gpu(gpu_ids: Vec<usize>, backend: SyncBackend) -> Self {
Self {
enabled: gpu_ids.len() > 1,
backend,
gpu_ids,
}
}
pub fn world_size(&self) -> usize {
self.gpu_ids.len().max(1)
}
pub fn effective_batch_multiplier(&self) -> usize {
if self.enabled {
self.gpu_ids.len().max(1)
} else {
1
}
}
}
pub type SyncedGradient = (String, Var, Tensor);
#[allow(dead_code)]
pub trait GradientSynchronizer: Send + Sync {
fn synchronize_gradients(
&self,
grads: &candle_core::backprop::GradStore,
varmap: &VarMap,
) -> Result<Vec<SyncedGradient>>;
fn local_rank(&self) -> usize;
fn world_size(&self) -> usize;
fn is_primary(&self) -> bool {
self.local_rank() == 0
}
fn barrier(&self) -> Result<()>;
fn is_distributed(&self) -> bool {
self.world_size() > 1
}
}