use super::averaging::{AveragingStrategy, ParameterAverager};
use crate::error::{OptimError, Result};
use scirs2_core::ndarray::{Array, Dimension, ScalarOperand};
use scirs2_core::numeric::Float;
use std::collections::HashMap;
use std::fmt::Debug;
#[derive(Debug)]
pub struct ParameterServer<A: Float, D: Dimension> {
averager: ParameterAverager<A, D>,
global_parameters: Vec<Array<A, D>>,
update_counts: HashMap<usize, usize>,
expected_updates_per_round: usize,
current_round: usize,
pending_updates: HashMap<usize, Vec<Array<A, D>>>,
}
impl<A: Float + ScalarOperand + Debug + Send + Sync, D: Dimension + Send + Sync>
ParameterServer<A, D>
{
pub fn new(
strategy: AveragingStrategy,
numnodes: usize,
expected_updates_per_round: usize,
) -> Self {
Self {
averager: ParameterAverager::new(strategy, numnodes),
global_parameters: Vec::new(),
update_counts: HashMap::new(),
expected_updates_per_round,
current_round: 0,
pending_updates: HashMap::new(),
}
}
pub fn initialize(&mut self, initialparams: &[Array<A, D>]) -> Result<()> {
if self.expected_updates_per_round == 0
|| self.expected_updates_per_round > self.averager.numnodes()
{
return Err(OptimError::InvalidConfig(format!(
"expected_updates_per_round ({}) must be in [1, numnodes={}]",
self.expected_updates_per_round,
self.averager.numnodes()
)));
}
self.averager.initialize(initialparams)?;
self.global_parameters = initialparams.to_vec();
for nodeid in 0..self.averager.numnodes() {
self.update_counts.insert(nodeid, 0);
}
Ok(())
}
pub fn submit_update(&mut self, nodeid: usize, parameters: Vec<Array<A, D>>) -> Result<bool> {
if nodeid >= self.averager.numnodes() {
return Err(OptimError::InvalidConfig(format!(
"Node ID {} exceeds number of nodes {}",
nodeid,
self.averager.numnodes()
)));
}
if self.pending_updates.contains_key(&nodeid) {
return Err(OptimError::InvalidState(format!(
"Node {} already submitted an update for the current round (round {}); \
call force_aggregation() to close the round before resubmitting",
nodeid,
self.current_round + 1
)));
}
self.pending_updates.insert(nodeid, parameters);
*self.update_counts.entry(nodeid).or_insert(0) += 1;
let ready_for_aggregation = self.pending_updates.len() >= self.expected_updates_per_round;
if ready_for_aggregation {
self.aggregate_and_update()?;
}
Ok(ready_for_aggregation)
}
pub fn force_aggregation(&mut self) -> Result<()> {
if !self.pending_updates.is_empty() {
self.aggregate_and_update()?;
}
Ok(())
}
fn aggregate_and_update(&mut self) -> Result<()> {
let node_params: Vec<(usize, Vec<Array<A, D>>)> = self.pending_updates.drain().collect();
self.averager.average_parameters(&node_params)?;
self.global_parameters = self.averager.get_averaged_parameters_cloned();
self.current_round += 1;
Ok(())
}
pub fn get_global_parameters(&self) -> &[Array<A, D>] {
&self.global_parameters
}
pub fn get_global_parameters_cloned(&self) -> Vec<Array<A, D>> {
self.global_parameters.clone()
}
pub fn current_round(&self) -> usize {
self.current_round
}
pub fn get_update_count(&self, nodeid: usize) -> usize {
self.update_counts.get(&nodeid).copied().unwrap_or(0)
}
pub fn pending_updates_count(&self) -> usize {
self.pending_updates.len()
}
pub fn set_node_weight(&mut self, nodeid: usize, weight: A) -> Result<()> {
self.averager.set_node_weight(nodeid, weight)
}
pub fn reset(&mut self) {
self.averager.reset();
self.update_counts.clear();
self.pending_updates.clear();
self.current_round = 0;
for nodeid in 0..self.averager.numnodes() {
self.update_counts.insert(nodeid, 0);
}
}
}