use crate::distributed::ddp_run::{
convergence, ApplyPolicy, AverageBackend, DdpRunConfig, EvalResultFn, MetricsFn,
};
use crate::tensor::Result;
#[allow(clippy::too_many_arguments)]
pub(crate) fn build_coord_config_from_builder(
policy: ApplyPolicy,
backend: AverageBackend,
config: &DdpRunConfig,
convergence_guard: Option<Box<dyn convergence::ConvergenceGuard>>,
metrics_fn: Option<MetricsFn>,
eval_result_fn: Option<EvalResultFn>,
world_size: usize,
total_samples: usize,
batch_size: usize,
num_epochs: usize,
) -> Result<crate::distributed::cluster_coordinator::ClusterCoordinatorConfig> {
use crate::distributed::cluster_coordinator::ClusterCoordinatorConfig;
use crate::distributed::ddp::ElChe;
let resume_meta: Option<crate::distributed::CheckpointMeta> = match config.resume_from {
Some(ref stem) => {
let path = crate::distributed::CheckpointBundle::meta_path(stem);
Some(crate::distributed::CheckpointMeta::read_from_file(&path)?)
}
None => None,
};
let anchor = config.elche.anchor;
let mut el_che = ElChe::new(world_size, anchor);
if let Some(target) = config.elche.overhead_target {
el_che = el_che.with_overhead_target(target);
}
if let Some(max) = config.elche.max_anchor {
el_che = el_che.with_max_anchor(max);
}
if let Some(min) = config.elche.min_anchor {
el_che = el_che.with_min_anchor(min);
}
if let Some(diff) = config.elche.max_batch_diff {
el_che = el_che.with_max_batch_diff(diff);
}
el_che = el_che.with_window_growth_applicable(policy != ApplyPolicy::Sync);
let mut coord_config = ClusterCoordinatorConfig::new(
policy,
backend,
world_size,
el_che,
)
.total_samples(total_samples)
.batch_size(batch_size)
.num_epochs(num_epochs)
.elche_relax_up(config.elche.relax_up)
.meta_controller(config.elche.meta_controller)
.partition_ratios(config.elche.partition_ratios.clone());
let resume_trend_history: Option<Vec<f64>> = resume_meta
.as_ref()
.and_then(|m| m.elche_state.as_ref())
.and_then(|s| s.trend_history.clone());
let guard: Box<dyn convergence::ConvergenceGuard> = match convergence_guard {
Some(g) => g,
None => {
if config.elche.no_divergence_guard {
Box::new(convergence::NoGuard)
} else {
let mut tg = convergence::TrendGuard::new(
config.elche.divergence_threshold.unwrap_or_else(|| {
default_trend_threshold(config.elche.easgd_alpha)
}),
);
if let Some(history) = resume_trend_history {
tg = tg.with_history(history);
}
Box::new(tg)
}
}
};
coord_config = coord_config.with_convergence_guard(guard);
if let Some(n) = config.elche.max_overshoot {
if policy != ApplyPolicy::Async {
eprintln!(
"fdl: max_overshoot={n} is ignored outside CpuAsync \
(mode resolves to policy {policy:?}); the async streaming \
lookahead bound has no effect here"
);
}
coord_config = coord_config.overshoot(n, n, false);
}
if let Some(threshold) = config.max_failure {
coord_config = coord_config.max_failure(threshold);
}
if let Some(ref stem) = config.save_path {
coord_config = coord_config.save_path(stem.clone());
}
if let Some(secs) = config.heartbeat_timeout_secs {
coord_config = coord_config.heartbeat_timeout_secs(secs);
}
if let Some(every) = config.checkpoint_every {
coord_config = coord_config.checkpoint_every(every);
}
if let Some(epoch) = config.checkpoint_at_epoch {
coord_config = coord_config.checkpoint_at_epoch(epoch);
}
if let Some(f) = metrics_fn {
coord_config = coord_config.metrics_fn(f);
}
if let Some(every) = config.eval_every_epochs {
coord_config = coord_config.eval_every_epochs(every);
}
if let Some(n) = config.reports_per_epoch {
coord_config = coord_config.reports_per_epoch(n);
}
if let Some(dir) = config.record_log_dir.clone() {
coord_config = coord_config.record_log(dir, config.max_log_size.unwrap_or(0));
}
if let Some(path) = config.dashboard_html.clone() {
coord_config = coord_config.dashboard_html(path);
}
if let Some(theme) = config.dashboard_theme.clone() {
coord_config = coord_config.dashboard_theme(theme);
}
if !config.scalar_reductions.is_empty() {
coord_config = coord_config.scalar_reductions(config.scalar_reductions.clone());
}
if let Some(f) = eval_result_fn {
coord_config = coord_config.eval_result_fn(f);
}
if let Some(enabled) = config.progressive_dispatch {
coord_config = coord_config.progressive(enabled);
}
coord_config = coord_config.epoch_callback_policy(config.epoch_callback_policy);
if let Some(ref tl) = config.timeline {
coord_config = coord_config.timeline(std::sync::Arc::clone(tl));
}
if let Some(seed) = resume_meta
.as_ref()
.and_then(|m| m.coverage.as_ref())
.map(|c| c.seed)
{
coord_config = coord_config.seed(seed);
}
if let Some(meta) = resume_meta {
coord_config = coord_config.resume_from_meta(&meta);
}
Ok(coord_config)
}
pub(crate) fn default_trend_threshold(easgd_alpha: Option<f64>) -> f64 {
if easgd_alpha.is_some() { 0.3 } else { 0.05 }
}
#[cfg(test)]
mod tests {
use super::default_trend_threshold;
#[test]
fn trend_threshold_default_is_easgd_aware() {
assert_eq!(default_trend_threshold(None), 0.05);
assert_eq!(default_trend_threshold(Some(0.5)), 0.3);
}
}