use crate::OptimizerError;
use std::collections::{HashMap, HashSet, VecDeque};
use std::sync::{Arc, Mutex, RwLock};
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct LazyUpdateConfig {
pub max_delay_ms: u64,
pub gradient_threshold: f32,
pub max_pending_updates: usize,
pub adaptive_threshold: bool,
pub importance_based_updates: bool,
pub batch_size: usize,
}
impl Default for LazyUpdateConfig {
fn default() -> Self {
Self {
max_delay_ms: 1000, gradient_threshold: 1e-6,
max_pending_updates: 1000,
adaptive_threshold: true,
importance_based_updates: true,
batch_size: 100,
}
}
}
#[derive(Debug, Clone)]
pub struct PendingUpdate {
pub parameter_id: String,
pub gradient: Vec<f32>,
pub timestamp: Instant,
pub priority: UpdatePriority,
pub accumulated_magnitude: f32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum UpdatePriority {
Low = 0,
Medium = 1,
High = 2,
Critical = 3,
}
#[derive(Debug, Clone)]
pub struct ParameterImportance {
pub parameter_id: String,
pub update_frequency: f32,
pub average_gradient_magnitude: f32,
pub variance: f32,
pub last_significant_update: Instant,
pub importance_score: f32,
}
impl ParameterImportance {
pub fn new(parameter_id: String) -> Self {
Self {
parameter_id,
update_frequency: 0.0,
average_gradient_magnitude: 0.0,
variance: 0.0,
last_significant_update: Instant::now(),
importance_score: 1.0,
}
}
pub fn update(&mut self, gradient_magnitude: f32, is_significant: bool) {
let alpha = 0.1;
self.average_gradient_magnitude =
(1.0 - alpha) * self.average_gradient_magnitude + alpha * gradient_magnitude;
let variance_delta = (gradient_magnitude - self.average_gradient_magnitude).powi(2);
self.variance = (1.0 - alpha) * self.variance + alpha * variance_delta;
if is_significant {
self.last_significant_update = Instant::now();
self.update_frequency = (1.0 - alpha) * self.update_frequency + alpha;
} else {
self.update_frequency = (1.0 - alpha) * self.update_frequency;
}
let recency_factor =
1.0 / (1.0 + self.last_significant_update.elapsed().as_secs_f32() / 60.0);
let magnitude_factor =
self.average_gradient_magnitude / (1e-6 + self.average_gradient_magnitude);
let frequency_factor = self.update_frequency;
let stability_factor = 1.0 / (1.0 + self.variance);
self.importance_score =
recency_factor * magnitude_factor * frequency_factor * stability_factor;
}
}
pub struct LazyUpdateManager {
config: LazyUpdateConfig,
pending_updates: VecDeque<PendingUpdate>,
parameter_importance: HashMap<String, ParameterImportance>,
adaptive_thresholds: HashMap<String, f32>,
last_batch_update: Instant,
total_updates_processed: usize,
total_updates_skipped: usize,
}
impl LazyUpdateManager {
pub fn new(config: LazyUpdateConfig) -> Self {
Self {
config,
pending_updates: VecDeque::new(),
parameter_importance: HashMap::new(),
adaptive_thresholds: HashMap::new(),
last_batch_update: Instant::now(),
total_updates_processed: 0,
total_updates_skipped: 0,
}
}
pub fn submit_gradient(
&mut self,
parameter_id: String,
gradient: Vec<f32>,
) -> LazyUpdateDecision {
let gradient_magnitude = gradient.iter().map(|&x| x * x).sum::<f32>().sqrt();
let threshold = self.get_threshold(¶meter_id);
let is_significant = gradient_magnitude > threshold;
{
let importance = self
.parameter_importance
.entry(parameter_id.clone())
.or_insert_with(|| ParameterImportance::new(parameter_id.clone()));
importance.update(gradient_magnitude, is_significant);
}
let priority = {
let importance = &self.parameter_importance[¶meter_id];
self.calculate_priority(¶meter_id, gradient_magnitude, importance)
};
if self.should_update_immediately(¶meter_id, gradient_magnitude, priority) {
self.total_updates_processed += 1;
return LazyUpdateDecision::UpdateNow;
}
let pending_update = PendingUpdate {
parameter_id: parameter_id.clone(),
gradient,
timestamp: Instant::now(),
priority,
accumulated_magnitude: gradient_magnitude,
};
self.insert_pending_update(pending_update);
if self.should_process_batch() {
LazyUpdateDecision::ProcessBatch(self.get_batch_updates())
} else {
self.total_updates_skipped += 1;
LazyUpdateDecision::Defer
}
}
pub fn get_batch_updates(&mut self) -> Vec<PendingUpdate> {
let mut batch = Vec::new();
let batch_size = self.config.batch_size.min(self.pending_updates.len());
let mut updates: Vec<_> = self.pending_updates.drain(..).collect();
updates.sort_by(|a, b| {
b.priority
.cmp(&a.priority)
.then_with(|| a.timestamp.cmp(&b.timestamp))
});
for update in updates.into_iter().take(batch_size) {
batch.push(update);
}
self.last_batch_update = Instant::now();
self.total_updates_processed += batch.len();
batch
}
pub fn flush_all_updates(&mut self) -> Vec<PendingUpdate> {
let updates: Vec<_> = self.pending_updates.drain(..).collect();
self.total_updates_processed += updates.len();
updates
}
pub fn get_expired_updates(&mut self) -> Vec<PendingUpdate> {
let max_delay = Duration::from_millis(self.config.max_delay_ms);
let now = Instant::now();
let mut expired = Vec::new();
let mut remaining = VecDeque::new();
while let Some(update) = self.pending_updates.pop_front() {
if update.timestamp.elapsed() > max_delay {
expired.push(update);
} else {
remaining.push_back(update);
}
}
self.pending_updates = remaining;
self.total_updates_processed += expired.len();
expired
}
pub fn statistics(&self) -> LazyUpdateStatistics {
let total_parameters = self.parameter_importance.len();
let pending_count = self.pending_updates.len();
let average_importance = if total_parameters > 0 {
self.parameter_importance
.values()
.map(|imp| imp.importance_score)
.sum::<f32>()
/ total_parameters as f32
} else {
0.0
};
let high_priority_pending = self
.pending_updates
.iter()
.filter(|update| update.priority >= UpdatePriority::High)
.count();
LazyUpdateStatistics {
total_parameters,
pending_updates: pending_count,
high_priority_pending,
total_processed: self.total_updates_processed,
total_skipped: self.total_updates_skipped,
skip_ratio: if self.total_updates_processed + self.total_updates_skipped > 0 {
self.total_updates_skipped as f32
/ (self.total_updates_processed + self.total_updates_skipped) as f32
} else {
0.0
},
average_importance,
}
}
pub fn get_parameter_importance(&self, parameter_id: &str) -> Option<&ParameterImportance> {
self.parameter_importance.get(parameter_id)
}
pub fn set_parameter_threshold(&mut self, parameter_id: String, threshold: f32) {
self.adaptive_thresholds.insert(parameter_id, threshold);
}
pub fn reset_statistics(&mut self) {
self.total_updates_processed = 0;
self.total_updates_skipped = 0;
}
fn get_threshold(&self, parameter_id: &str) -> f32 {
if let Some(&custom_threshold) = self.adaptive_thresholds.get(parameter_id) {
return custom_threshold;
}
if !self.config.adaptive_threshold {
return self.config.gradient_threshold;
}
if let Some(importance) = self.parameter_importance.get(parameter_id) {
let base_threshold = self.config.gradient_threshold;
let variance_factor = (1.0 + importance.variance).sqrt();
let importance_factor = 1.0 / (1.0 + importance.importance_score);
base_threshold * variance_factor * importance_factor
} else {
self.config.gradient_threshold
}
}
fn calculate_priority(
&self,
parameter_id: &str,
gradient_magnitude: f32,
importance: &ParameterImportance,
) -> UpdatePriority {
if !self.config.importance_based_updates {
return UpdatePriority::Medium;
}
let threshold = self.get_threshold(parameter_id);
let magnitude_ratio = gradient_magnitude / threshold;
let importance_factor = importance.importance_score;
let time_factor = importance.last_significant_update.elapsed().as_secs_f32() / 60.0;
let priority_score = magnitude_ratio * importance_factor * (1.0 + time_factor);
if priority_score > 10.0 {
UpdatePriority::Critical
} else if priority_score > 3.0 {
UpdatePriority::High
} else if priority_score > 1.0 {
UpdatePriority::Medium
} else {
UpdatePriority::Low
}
}
fn should_update_immediately(
&self,
_parameter_id: &str,
gradient_magnitude: f32,
priority: UpdatePriority,
) -> bool {
if priority == UpdatePriority::Critical {
return true;
}
if gradient_magnitude > self.config.gradient_threshold * 10.0 {
return true;
}
false
}
fn should_process_batch(&self) -> bool {
if self.pending_updates.len() >= self.config.max_pending_updates {
return true;
}
let high_priority_count = self
.pending_updates
.iter()
.filter(|update| update.priority >= UpdatePriority::High)
.count();
if high_priority_count >= self.config.batch_size / 2 {
return true;
}
let max_delay = Duration::from_millis(self.config.max_delay_ms);
if let Some(oldest) = self.pending_updates.front() {
if oldest.timestamp.elapsed() > max_delay {
return true;
}
}
false
}
fn insert_pending_update(&mut self, update: PendingUpdate) {
let insert_pos = self
.pending_updates
.iter()
.position(|existing| existing.priority < update.priority)
.unwrap_or(self.pending_updates.len());
self.pending_updates.insert(insert_pos, update);
}
}
#[derive(Debug)]
pub enum LazyUpdateDecision {
UpdateNow,
Defer,
ProcessBatch(Vec<PendingUpdate>),
}
#[derive(Debug, Clone)]
pub struct LazyUpdateStatistics {
pub total_parameters: usize,
pub pending_updates: usize,
pub high_priority_pending: usize,
pub total_processed: usize,
pub total_skipped: usize,
pub skip_ratio: f32,
pub average_importance: f32,
}
impl std::fmt::Display for LazyUpdateStatistics {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Lazy Update Statistics:")?;
writeln!(f, " Total Parameters: {}", self.total_parameters)?;
writeln!(f, " Pending Updates: {}", self.pending_updates)?;
writeln!(f, " High Priority Pending: {}", self.high_priority_pending)?;
writeln!(f, " Total Processed: {}", self.total_processed)?;
writeln!(f, " Total Skipped: {}", self.total_skipped)?;
writeln!(f, " Skip Ratio: {:.1}%", self.skip_ratio * 100.0)?;
writeln!(f, " Average Importance: {:.3}", self.average_importance)?;
Ok(())
}
}
pub trait LazyUpdateSupport {
fn apply_update(&mut self, parameter_id: &str, gradient: &[f32]) -> Result<(), OptimizerError>;
fn apply_batch_updates(&mut self, updates: &[PendingUpdate]) -> Result<(), OptimizerError> {
for update in updates {
self.apply_update(&update.parameter_id, &update.gradient)?;
}
Ok(())
}
fn get_parameter_gradient(&self, parameter_id: &str) -> Option<Vec<f32>>;
fn get_parameter_ids(&self) -> Vec<String>;
}
pub struct LazyUpdateOptimizer<T> {
inner: T,
lazy_manager: LazyUpdateManager,
enabled: bool,
}
impl<T> LazyUpdateOptimizer<T>
where
T: LazyUpdateSupport,
{
pub fn new(inner: T, config: LazyUpdateConfig) -> Self {
Self {
inner,
lazy_manager: LazyUpdateManager::new(config),
enabled: true,
}
}
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
if !enabled {
let pending = self.lazy_manager.flush_all_updates();
let _ = self.inner.apply_batch_updates(&pending);
}
}
pub fn inner(&self) -> &T {
&self.inner
}
pub fn inner_mut(&mut self) -> &mut T {
&mut self.inner
}
pub fn lazy_manager(&self) -> &LazyUpdateManager {
&self.lazy_manager
}
pub fn lazy_manager_mut(&mut self) -> &mut LazyUpdateManager {
&mut self.lazy_manager
}
pub fn submit_gradients(
&mut self,
gradients: HashMap<String, Vec<f32>>,
) -> Result<(), OptimizerError> {
if !self.enabled {
for (param_id, gradient) in gradients {
self.inner.apply_update(¶m_id, &gradient)?;
}
return Ok(());
}
for (param_id, gradient) in gradients {
match self.lazy_manager.submit_gradient(param_id, gradient) {
LazyUpdateDecision::UpdateNow => {
}
LazyUpdateDecision::Defer => {
}
LazyUpdateDecision::ProcessBatch(updates) => {
self.inner.apply_batch_updates(&updates)?;
}
}
}
let expired = self.lazy_manager.get_expired_updates();
if !expired.is_empty() {
self.inner.apply_batch_updates(&expired)?;
}
Ok(())
}
pub fn flush_pending_updates(&mut self) -> Result<(), OptimizerError> {
let pending = self.lazy_manager.flush_all_updates();
self.inner.apply_batch_updates(&pending)
}
pub fn statistics(&self) -> LazyUpdateStatistics {
self.lazy_manager.statistics()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug)]
struct MockOptimizer {
parameters: HashMap<String, Vec<f32>>,
gradients: HashMap<String, Vec<f32>>,
update_count: usize,
}
impl MockOptimizer {
fn new() -> Self {
let mut parameters = HashMap::new();
parameters.insert("layer1.weight".to_string(), vec![1.0, 2.0, 3.0]);
parameters.insert("layer1.bias".to_string(), vec![0.1, 0.2]);
Self {
parameters,
gradients: HashMap::new(),
update_count: 0,
}
}
}
impl LazyUpdateSupport for MockOptimizer {
fn apply_update(
&mut self,
parameter_id: &str,
gradient: &[f32],
) -> Result<(), OptimizerError> {
self.gradients
.insert(parameter_id.to_string(), gradient.to_vec());
self.update_count += 1;
Ok(())
}
fn get_parameter_gradient(&self, parameter_id: &str) -> Option<Vec<f32>> {
self.gradients.get(parameter_id).cloned()
}
fn get_parameter_ids(&self) -> Vec<String> {
self.parameters.keys().cloned().collect()
}
}
#[test]
fn test_lazy_update_manager() {
let config = LazyUpdateConfig {
gradient_threshold: 0.1,
max_pending_updates: 5,
..Default::default()
};
let mut manager = LazyUpdateManager::new(config);
let decision = manager.submit_gradient("param1".to_string(), vec![0.01, 0.02]);
assert!(matches!(decision, LazyUpdateDecision::Defer));
let decision = manager.submit_gradient("param2".to_string(), vec![1.0, 2.0]);
assert!(matches!(decision, LazyUpdateDecision::UpdateNow));
for i in 0..6 {
let decision = manager.submit_gradient(format!("param{}", i), vec![0.05, 0.06]);
if i == 3 {
assert!(matches!(decision, LazyUpdateDecision::ProcessBatch(_)));
} else if i < 3 {
assert!(matches!(decision, LazyUpdateDecision::Defer));
}
}
}
#[test]
fn test_lazy_update_optimizer() {
let config = LazyUpdateConfig {
gradient_threshold: 0.1,
max_pending_updates: 3,
..Default::default()
};
let optimizer = MockOptimizer::new();
let mut lazy_optimizer = LazyUpdateOptimizer::new(optimizer, config);
let mut gradients = HashMap::new();
gradients.insert("layer1.weight".to_string(), vec![0.01, 0.02, 0.03]);
gradients.insert("layer1.bias".to_string(), vec![0.05, 0.06]);
lazy_optimizer.submit_gradients(gradients).unwrap();
assert!(lazy_optimizer.inner().update_count < 2);
lazy_optimizer.flush_pending_updates().unwrap();
assert!(lazy_optimizer.inner().update_count >= 2);
}
#[test]
fn test_parameter_importance() {
let mut importance = ParameterImportance::new("test_param".to_string());
importance.update(0.5, true);
assert!(importance.importance_score > 0.0);
importance.update(0.1, false);
importance.update(0.8, true);
assert!(importance.average_gradient_magnitude > 0.0);
assert!(importance.update_frequency > 0.0);
}
}