optirs_core/distributed/
parameter_server.rs1use super::averaging::{AveragingStrategy, ParameterAverager};
2use crate::error::{OptimError, Result};
3use scirs2_core::ndarray::{Array, Dimension, ScalarOperand};
4use scirs2_core::numeric::Float;
5use std::collections::HashMap;
6use std::fmt::Debug;
7
8#[derive(Debug)]
10pub struct ParameterServer<A: Float, D: Dimension> {
11 averager: ParameterAverager<A, D>,
13 global_parameters: Vec<Array<A, D>>,
15 update_counts: HashMap<usize, usize>,
17 expected_updates_per_round: usize,
19 current_round: usize,
21 pending_updates: HashMap<usize, Vec<Array<A, D>>>,
23}
24
25impl<A: Float + ScalarOperand + Debug + Send + Sync, D: Dimension + Send + Sync>
26 ParameterServer<A, D>
27{
28 pub fn new(
30 strategy: AveragingStrategy,
31 numnodes: usize,
32 expected_updates_per_round: usize,
33 ) -> Self {
34 Self {
35 averager: ParameterAverager::new(strategy, numnodes),
36 global_parameters: Vec::new(),
37 update_counts: HashMap::new(),
38 expected_updates_per_round,
39 current_round: 0,
40 pending_updates: HashMap::new(),
41 }
42 }
43
44 pub fn initialize(&mut self, initialparams: &[Array<A, D>]) -> Result<()> {
46 if self.expected_updates_per_round == 0
47 || self.expected_updates_per_round > self.averager.numnodes()
48 {
49 return Err(OptimError::InvalidConfig(format!(
50 "expected_updates_per_round ({}) must be in [1, numnodes={}]",
51 self.expected_updates_per_round,
52 self.averager.numnodes()
53 )));
54 }
55
56 self.averager.initialize(initialparams)?;
57 self.global_parameters = initialparams.to_vec();
58
59 for nodeid in 0..self.averager.numnodes() {
61 self.update_counts.insert(nodeid, 0);
62 }
63
64 Ok(())
65 }
66
67 pub fn submit_update(&mut self, nodeid: usize, parameters: Vec<Array<A, D>>) -> Result<bool> {
74 if nodeid >= self.averager.numnodes() {
75 return Err(OptimError::InvalidConfig(format!(
76 "Node ID {} exceeds number of nodes {}",
77 nodeid,
78 self.averager.numnodes()
79 )));
80 }
81
82 if self.pending_updates.contains_key(&nodeid) {
83 return Err(OptimError::InvalidState(format!(
84 "Node {} already submitted an update for the current round (round {}); \
85 call force_aggregation() to close the round before resubmitting",
86 nodeid,
87 self.current_round + 1
88 )));
89 }
90
91 self.pending_updates.insert(nodeid, parameters);
93 *self.update_counts.entry(nodeid).or_insert(0) += 1;
94
95 let ready_for_aggregation = self.pending_updates.len() >= self.expected_updates_per_round;
97
98 if ready_for_aggregation {
99 self.aggregate_and_update()?;
100 }
101
102 Ok(ready_for_aggregation)
103 }
104
105 pub fn force_aggregation(&mut self) -> Result<()> {
107 if !self.pending_updates.is_empty() {
108 self.aggregate_and_update()?;
109 }
110 Ok(())
111 }
112
113 fn aggregate_and_update(&mut self) -> Result<()> {
115 let node_params: Vec<(usize, Vec<Array<A, D>>)> = self.pending_updates.drain().collect();
117
118 self.averager.average_parameters(&node_params)?;
120
121 self.global_parameters = self.averager.get_averaged_parameters_cloned();
123
124 self.current_round += 1;
126
127 Ok(())
128 }
129
130 pub fn get_global_parameters(&self) -> &[Array<A, D>] {
132 &self.global_parameters
133 }
134
135 pub fn get_global_parameters_cloned(&self) -> Vec<Array<A, D>> {
137 self.global_parameters.clone()
138 }
139
140 pub fn current_round(&self) -> usize {
142 self.current_round
143 }
144
145 pub fn get_update_count(&self, nodeid: usize) -> usize {
147 self.update_counts.get(&nodeid).copied().unwrap_or(0)
148 }
149
150 pub fn pending_updates_count(&self) -> usize {
152 self.pending_updates.len()
153 }
154
155 pub fn set_node_weight(&mut self, nodeid: usize, weight: A) -> Result<()> {
157 self.averager.set_node_weight(nodeid, weight)
158 }
159
160 pub fn reset(&mut self) {
162 self.averager.reset();
163 self.update_counts.clear();
164 self.pending_updates.clear();
165 self.current_round = 0;
166
167 for nodeid in 0..self.averager.numnodes() {
168 self.update_counts.insert(nodeid, 0);
169 }
170 }
171}