use std::net::TcpStream;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, mpsc};
use std::thread::JoinHandle;
use std::time::Instant;
use crate::distributed::ddp::ElChe;
use crate::distributed::ddp_run::convergence::ConvergenceGuard;
use crate::distributed::ddp_run::{ApplyPolicy, AverageBackend};
use crate::distributed::relay::mux::{MuxRead, MuxRecord, RelayControlMsg};
use crate::distributed::wire::{
ControlFrame, MsgKind, SessionSalt, TimingMsgWire,
};
pub mod config;
mod alerts;
mod averaging;
mod callback_roles;
mod cycle_cpu;
mod cycle_nccl;
mod cycle_state;
mod dead_ranks;
mod epoch_dispatch;
mod event_loop;
mod lifecycle;
mod window_ledger;
mod window_records;
#[cfg(test)]
mod test_helpers;
pub use config::ClusterCoordinatorConfig;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(super) enum RunPhase {
#[default]
Training,
FinalEvalDispatched,
ShutdownInitiated,
}
fn initial_callback_role(
policy: crate::distributed::ddp_run::EpochCallbackPolicy,
world_size: usize,
) -> usize {
match policy {
crate::distributed::ddp_run::EpochCallbackPolicy::Rank(n) => {
if world_size == 0 { 0 } else { n.min(world_size - 1) }
}
crate::distributed::ddp_run::EpochCallbackPolicy::Fastest => 0,
}
}
#[derive(Debug, Clone, Copy)]
pub(super) struct EpochDSummary {
pub(super) count: usize,
pub(super) d_min: f64,
pub(super) d_max: f64,
pub(super) d_sum: f64,
pub(super) d_at_epoch_end: f64,
pub(super) k_at_epoch_end: usize,
}
impl EpochDSummary {
pub(super) fn d_mean(&self) -> f64 {
if self.count == 0 {
0.0
} else {
self.d_sum / self.count as f64
}
}
}
const NCCL_RENDEZVOUS_TIMEOUT_SECS: u64 = 5;
const STALL_DUMP_SECS: u64 = 15;
const STALL_DUMP_PROD_SECS: u64 = 120;
#[derive(Debug)]
struct NcclRendezvousPending {
generator_rank: usize,
survivors_ordered: Vec<usize>,
initiated_at: Instant,
tried_generators: Vec<usize>,
}
#[derive(Debug)]
struct FinalWindowPlan {
epoch: usize,
alloc: Vec<usize>,
pinned_counts: Vec<usize>,
}
pub type ReportedDeaths = std::sync::Arc<std::sync::Mutex<Vec<usize>>>;
pub struct ClusterCoordinator {
policy: ApplyPolicy,
backend: AverageBackend,
world_size: usize,
overshoot_initial: usize,
overshoot_ceiling: usize,
overshoot_auto: bool,
elche_relax_up: bool,
el_che: ElChe,
convergence_guard: Box<dyn ConvergenceGuard>,
version: u64,
avg_count: u64,
global_step: usize,
calibrated: bool,
active_count: usize,
max_overshoot: usize,
window: window_ledger::WindowLedger,
last_batch_ms: Vec<f64>,
last_step_count: Vec<usize>,
cycle: cycle_state::AvgCycleState,
dispatch_hold_logged: Vec<bool>,
epoch_d_min: f64,
epoch_d_max: f64,
epoch_d_sum: f64,
epoch_d_count: usize,
epoch_last_d: f64,
epoch_last_k_max: usize,
lr_event_meta: Option<crate::distributed::lr_event_meta::LrEventMeta>,
last_lr_per_rank: Vec<Option<f64>>,
lost_broadcasts: usize,
prof_enabled: bool,
stall_last_global_step: usize,
stall_since: Option<Instant>,
stall_last_dump: Option<Instant>,
dead_ranks: Option<Arc<crate::distributed::controller::DeadRanks>>,
reported_deaths: Option<ReportedDeaths>,
heartbeat_timeout_secs: u64,
rendezvous_timeout_secs: u64,
last_heartbeat: Vec<Instant>,
last_coord_heartbeat: Option<Instant>,
exited: Vec<bool>,
last_step_count_at_epoch_start: Vec<usize>,
nccl_rendezvous_pending: Option<NcclRendezvousPending>,
local_ranks: Vec<usize>,
rank_hosts: Vec<String>,
max_failure: Option<crate::distributed::max_failure::MaxFailureThreshold>,
epoch_callback_policy: crate::distributed::ddp_run::EpochCallbackPolicy,
checkpoint_role: usize,
eval_role: usize,
pending_eval_intent: bool,
pending_checkpoint_intent: bool,
epoch_callback_role: usize,
epoch_role_dirty: bool,
checkpoint_tried_ranks:
std::collections::HashMap<u64, std::collections::HashSet<usize>>,
last_checkpoint_elapsed_ms_ewma: Option<f64>,
last_eval_elapsed_ms_ewma: Option<f64>,
last_epoch_fn_elapsed_ms_ewma: Option<f64>,
shutdown_with_save_dispatched: bool,
save_path: Option<String>,
checkpoint_every: Option<usize>,
seed: u64,
checkpoint_at_epoch: Option<usize>,
start_coverage: Option<crate::distributed::CoverageBlock>,
checkpoint_forge: Option<std::sync::Arc<crate::distributed::CheckpointForge>>,
pending_checkpoint_coverage: Option<crate::distributed::CoverageBlock>,
rank_epoch: Vec<usize>,
last_aggregated_epoch: Option<usize>,
last_dispatched_epoch: Option<usize>,
run_phase: RunPhase,
epoch_plan_cache: std::collections::HashMap<usize, Vec<crate::distributed::wire::EpochPlanWire>>,
total_samples: usize,
batch_size: usize,
num_epochs: usize,
partition_ratios: Option<Vec<f64>>,
timing_rx: mpsc::Receiver<TimingMsgWire>,
metrics_rx: mpsc::Receiver<crate::distributed::wire::MetricsMsgWire>,
metrics_buffer: std::collections::BTreeMap<
u64,
Vec<crate::distributed::ddp_run::MetricsMsg>,
>,
chunk_pools: std::collections::BTreeMap<usize, crate::distributed::chunk_pool::ChunkPool>,
progressive: bool,
min_chunk_batches: usize,
final_window_plan: Option<FinalWindowPlan>,
metrics_fn: Option<crate::distributed::ddp_run::MetricsFn>,
metrics_sink_tx: Option<mpsc::Sender<crate::distributed::ddp_run::EpochMetrics>>,
eval_result_fn: Option<crate::distributed::ddp_run::EvalResultFn>,
eval_every_epochs: Option<usize>,
report_scheduler: Option<crate::monitor::cadence::ReportScheduler>,
report_in_epoch_steps: f64,
report_epoch_seen: usize,
metrics_device_indices: Vec<u8>,
control_streams: Vec<TcpStream>,
rank_to_conn: Vec<Option<usize>>,
reader_handles: Vec<Option<JoinHandle<()>>>,
shutdown_flag: Arc<AtomicBool>,
bound_port: u16,
salt: SessionSalt,
timeline: Option<Arc<crate::monitor::Timeline>>,
sync_start: Option<std::time::Instant>,
pub(super) dashboard_sink: Option<Arc<dyn crate::distributed::DashboardSink>>,
latest_res: Vec<crate::monitor::record::ResAcc>,
event_lane: crate::monitor::event_lane::EventLane,
}
impl ClusterCoordinator {
pub fn bound_port(&self) -> u16 {
self.bound_port
}
pub fn version(&self) -> u64 {
self.version
}
pub fn is_calibrated(&self) -> bool {
self.calibrated
}
pub fn steps_since_avg(&self) -> &[usize] {
self.window.steps_all()
}
pub fn avg_count(&self) -> u64 {
self.avg_count
}
pub fn global_step(&self) -> usize {
self.global_step
}
pub fn world_size(&self) -> usize {
self.world_size
}
pub fn active_count(&self) -> usize {
self.active_count
}
pub fn max_overshoot(&self) -> usize {
self.max_overshoot
}
pub fn el_che(&self) -> &ElChe {
&self.el_che
}
pub fn last_observed_upload_ms(&self) -> &[Option<f64>] {
&self.cycle.upload_ms
}
pub fn rank_epoch(&self) -> &[usize] {
&self.rank_epoch
}
pub fn checkpoint_role(&self) -> usize {
self.checkpoint_role
}
pub fn last_checkpoint_elapsed_ms_ewma(&self) -> Option<f64> {
self.last_checkpoint_elapsed_ms_ewma
}
pub fn last_eval_elapsed_ms_ewma(&self) -> Option<f64> {
self.last_eval_elapsed_ms_ewma
}
pub fn last_epoch_fn_elapsed_ms_ewma(&self) -> Option<f64> {
self.last_epoch_fn_elapsed_ms_ewma
}
pub fn checkpoint_tried_count(&self, version: u64) -> usize {
self.checkpoint_tried_ranks
.get(&version)
.map(|s| s.len())
.unwrap_or(0)
}
pub fn last_aggregated_epoch(&self) -> Option<usize> {
self.last_aggregated_epoch
}
pub fn batch_size(&self) -> usize {
self.batch_size
}
pub fn num_epochs(&self) -> usize {
self.num_epochs
}
pub fn total_samples(&self) -> usize {
self.total_samples
}
}
impl Drop for ClusterCoordinator {
fn drop(&mut self) {
self.shutdown_flag.store(true, Ordering::SeqCst);
for handle_opt in self.reader_handles.iter_mut() {
if let Some(handle) = handle_opt.take() {
let _ = handle.join();
}
}
}
}
fn relay_reader_loop(
stream: &mut TcpStream,
salt: &SessionSalt,
shutdown: &Arc<AtomicBool>,
tx: &mpsc::Sender<TimingMsgWire>,
metrics_tx: &mpsc::Sender<crate::distributed::wire::MetricsMsgWire>,
) {
loop {
if shutdown.load(Ordering::SeqCst) {
return;
}
match MuxRecord::try_read_from(stream, salt) {
Ok(MuxRead::Record(MuxRecord::Data { rank, payload })) => {
let mut slice = &payload[..];
match ControlFrame::read_from(&mut slice, salt) {
Ok(Some(frame)) => {
if !dispatch_control_frame(rank as usize, frame, tx, metrics_tx) {
return;
}
}
Ok(None) => {
eprintln!(
"cluster_coordinator: relay reader: truncated ControlFrame \
payload for rank {rank}"
);
return;
}
Err(e) => {
eprintln!(
"cluster_coordinator: relay reader: rank {rank} ControlFrame \
parse: {e}"
);
return;
}
}
}
Ok(MuxRead::Record(MuxRecord::Control(RelayControlMsg::RankExit { .. }))) => {
}
Ok(MuxRead::Record(MuxRecord::Control(_))) => {
}
Ok(MuxRead::Record(
MuxRecord::HostFrame { .. } | MuxRecord::Broadcast { .. },
)) => {
eprintln!(
"cluster_coordinator: relay reader: fold record on the \
control channel; dropping"
);
}
Ok(MuxRead::WouldBlock) => continue,
Ok(MuxRead::Eof) => return, Err(e) => {
crate::verbose!("cluster_coordinator: relay reader wire error: {e}");
return;
}
}
}
}
fn dispatch_control_frame(
rank: usize,
frame: ControlFrame,
tx: &mpsc::Sender<TimingMsgWire>,
metrics_tx: &mpsc::Sender<crate::distributed::wire::MetricsMsgWire>,
) -> bool {
match frame.kind {
MsgKind::Timing => match frame.decode::<TimingMsgWire>() {
Ok(msg) => tx.send(msg).is_ok(),
Err(e) => {
eprintln!("cluster_coordinator: reader r{rank} decode TimingMsg: {e}");
false
}
},
MsgKind::Metrics => match frame.decode::<crate::distributed::wire::MetricsMsgWire>() {
Ok(msg) => metrics_tx.send(msg).is_ok(),
Err(e) => {
eprintln!("cluster_coordinator: reader r{rank} decode MetricsMsg: {e}");
false
}
},
MsgKind::Heartbeat => {
true
}
MsgKind::Control | MsgKind::ParamSnapshotMeta | MsgKind::Rendezvous
| MsgKind::Join => {
eprintln!(
"cluster_coordinator: reader r{rank} got unexpected MsgKind {:?} on \
rank→coord path; dropping",
frame.kind
);
true
}
}
}
#[cfg(test)]
mod tests;