1use crate::EmbeddingModel;
9use anyhow::Result;
10use scirs2_core::random::{Random, RngExt};
11use serde::{Deserialize, Serialize};
12use std::collections::{HashMap, VecDeque};
13use std::sync::{Arc, Mutex};
14use std::time::{Duration, Instant};
15use tokio::time::sleep;
16use tracing::info;
17
18pub struct RealTimeOptimizer {
20 config: OptimizationConfig,
22 performance_monitor: PerformanceMonitor,
24 learning_rate_scheduler: AdaptiveLearningRateScheduler,
26 architecture_optimizer: DynamicArchitectureOptimizer,
28 online_learning_manager: OnlineLearningManager,
30 resource_optimizer: ResourceOptimizer,
32 optimization_history: OptimizationHistory,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct OptimizationConfig {
39 pub enable_adaptive_lr: bool,
41 pub enable_architecture_opt: bool,
43 pub enable_online_learning: bool,
45 pub enable_resource_opt: bool,
47 pub optimization_frequency: u64,
49 pub performance_window_size: usize,
51 pub improvement_threshold: f32,
53 pub max_lr_adjustment: f32,
55 pub architecture_mutation_prob: f32,
57 pub online_batch_size: usize,
59 pub resource_sensitivity: f32,
61}
62
63impl Default for OptimizationConfig {
64 fn default() -> Self {
65 Self {
66 enable_adaptive_lr: true,
67 enable_architecture_opt: true,
68 enable_online_learning: true,
69 enable_resource_opt: true,
70 optimization_frequency: 30, performance_window_size: 100,
72 improvement_threshold: 0.001,
73 max_lr_adjustment: 2.0,
74 architecture_mutation_prob: 0.1,
75 online_batch_size: 32,
76 resource_sensitivity: 0.5,
77 }
78 }
79}
80
81pub struct PerformanceMonitor {
83 metrics_history: Arc<Mutex<VecDeque<PerformanceMetrics>>>,
85 current_baseline: Arc<Mutex<PerformanceMetrics>>,
87 window_size: usize,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct PerformanceMetrics {
94 pub timestamp: chrono::DateTime<chrono::Utc>,
96 pub training_loss: f32,
98 pub validation_accuracy: f32,
100 pub inference_latency: f32,
102 pub memory_usage: f32,
104 pub gpu_utilization: f32,
106 pub throughput: f32,
108 pub learning_rate: f32,
110 pub model_complexity: f32,
112}
113
114impl Default for PerformanceMetrics {
115 fn default() -> Self {
116 Self {
117 timestamp: chrono::Utc::now(),
118 training_loss: 1.0,
119 validation_accuracy: 0.5,
120 inference_latency: 100.0,
121 memory_usage: 1024.0,
122 gpu_utilization: 50.0,
123 throughput: 100.0,
124 learning_rate: 0.001,
125 model_complexity: 0.5,
126 }
127 }
128}
129
130pub struct AdaptiveLearningRateScheduler {
132 current_lr: f32,
134 base_lr: f32,
136 adjustment_history: VecDeque<LearningRateAdjustment>,
138 strategy: LearningRateStrategy,
140}
141
142#[derive(Debug, Clone)]
143pub enum LearningRateStrategy {
144 AdaptiveGradient,
145 CyclicalLearningRate,
146 WarmupCosineAnnealing,
147 PerformanceBased,
148 OneCycle,
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct LearningRateAdjustment {
153 pub timestamp: chrono::DateTime<chrono::Utc>,
154 pub old_lr: f32,
155 pub new_lr: f32,
156 pub reason: String,
157 pub performance_before: f32,
158 pub performance_after: Option<f32>,
159}
160
161impl AdaptiveLearningRateScheduler {
162 pub fn new(base_lr: f32, strategy: LearningRateStrategy) -> Self {
163 Self {
164 current_lr: base_lr,
165 base_lr,
166 adjustment_history: VecDeque::new(),
167 strategy,
168 }
169 }
170
171 pub fn adjust_learning_rate(
173 &mut self,
174 current_metrics: &PerformanceMetrics,
175 recent_metrics: &[PerformanceMetrics],
176 ) -> Result<f32> {
177 let new_lr = match self.strategy {
178 LearningRateStrategy::AdaptiveGradient => {
179 self.adaptive_gradient_adjustment(current_metrics, recent_metrics)?
180 }
181 LearningRateStrategy::CyclicalLearningRate => {
182 self.cyclical_lr_adjustment(current_metrics)?
183 }
184 LearningRateStrategy::WarmupCosineAnnealing => {
185 self.warmup_cosine_adjustment(current_metrics)?
186 }
187 LearningRateStrategy::PerformanceBased => {
188 self.performance_based_adjustment(current_metrics, recent_metrics)?
189 }
190 LearningRateStrategy::OneCycle => self.one_cycle_adjustment(current_metrics)?,
191 };
192
193 self.record_adjustment(new_lr, "Adaptive adjustment".to_string(), current_metrics);
195
196 self.current_lr = new_lr;
197 Ok(new_lr)
198 }
199
200 fn adaptive_gradient_adjustment(
201 &self,
202 _current_metrics: &PerformanceMetrics,
203 recent_metrics: &[PerformanceMetrics],
204 ) -> Result<f32> {
205 if recent_metrics.len() < 2 {
206 return Ok(self.current_lr);
207 }
208
209 let loss_gradient = self.calculate_loss_gradient(recent_metrics);
211
212 let adjustment_factor = if loss_gradient.abs() < 0.001 {
214 1.1 } else if loss_gradient > 0.0 {
216 0.9 } else {
218 1.05 };
220
221 Ok(self.current_lr * adjustment_factor)
222 }
223
224 fn cyclical_lr_adjustment(&self, current_metrics: &PerformanceMetrics) -> Result<f32> {
225 let cycle_length = 1000; let step = current_metrics.timestamp.timestamp() as f32;
228 let cycle_position = (step % cycle_length as f32) / cycle_length as f32;
229
230 let min_lr = self.base_lr * 0.1;
231 let max_lr = self.base_lr * 10.0;
232
233 let lr = min_lr
234 + (max_lr - min_lr) * (1.0 + (cycle_position * 2.0 * std::f32::consts::PI).cos()) / 2.0;
235 Ok(lr)
236 }
237
238 fn warmup_cosine_adjustment(&self, current_metrics: &PerformanceMetrics) -> Result<f32> {
239 let warmup_steps = 1000.0;
241 let total_steps = 10000.0;
242 let step = current_metrics.timestamp.timestamp() as f32;
243
244 if step < warmup_steps {
245 Ok(self.base_lr * step / warmup_steps)
247 } else {
248 let progress = (step - warmup_steps) / (total_steps - warmup_steps);
250 let lr = self.base_lr * 0.5 * (1.0 + (progress * std::f32::consts::PI).cos());
251 Ok(lr)
252 }
253 }
254
255 fn performance_based_adjustment(
256 &self,
257 _current_metrics: &PerformanceMetrics,
258 recent_metrics: &[PerformanceMetrics],
259 ) -> Result<f32> {
260 if recent_metrics.len() < 5 {
261 return Ok(self.current_lr);
262 }
263
264 let recent_losses: Vec<f32> = recent_metrics.iter().map(|m| m.training_loss).collect();
266
267 let improving = self.is_performance_improving(&recent_losses);
268
269 if improving {
270 Ok(self.current_lr * 1.02)
272 } else {
273 Ok(self.current_lr * 0.95)
275 }
276 }
277
278 fn one_cycle_adjustment(&self, current_metrics: &PerformanceMetrics) -> Result<f32> {
279 let cycle_length = 5000.0;
281 let step = current_metrics.timestamp.timestamp() as f32;
282 let cycle_position = step % cycle_length / cycle_length;
283
284 let max_lr = self.base_lr * 10.0;
285
286 if cycle_position < 0.3 {
287 let progress = cycle_position / 0.3;
289 Ok(self.base_lr + (max_lr - self.base_lr) * progress)
290 } else if cycle_position < 0.9 {
291 let progress = (cycle_position - 0.3) / 0.6;
293 Ok(max_lr - (max_lr - self.base_lr) * progress)
294 } else {
295 let progress = (cycle_position - 0.9) / 0.1;
297 Ok(self.base_lr * (1.0 - 0.9 * progress))
298 }
299 }
300
301 fn calculate_loss_gradient(&self, recent_metrics: &[PerformanceMetrics]) -> f32 {
302 if recent_metrics.len() < 2 {
303 return 0.0;
304 }
305
306 let recent_loss = recent_metrics[recent_metrics.len() - 1].training_loss;
307 let previous_loss = recent_metrics[recent_metrics.len() - 2].training_loss;
308
309 recent_loss - previous_loss
310 }
311
312 fn is_performance_improving(&self, recent_losses: &[f32]) -> bool {
313 if recent_losses.len() < 3 {
314 return false;
315 }
316
317 let recent_avg = recent_losses[recent_losses.len() - 3..].iter().sum::<f32>() / 3.0;
318 let earlier_avg = recent_losses[0..3].iter().sum::<f32>() / 3.0;
319
320 recent_avg < earlier_avg
321 }
322
323 fn record_adjustment(
324 &mut self,
325 new_lr: f32,
326 reason: String,
327 current_metrics: &PerformanceMetrics,
328 ) {
329 let adjustment = LearningRateAdjustment {
330 timestamp: chrono::Utc::now(),
331 old_lr: self.current_lr,
332 new_lr,
333 reason,
334 performance_before: current_metrics.training_loss,
335 performance_after: None,
336 };
337
338 self.adjustment_history.push_back(adjustment);
339
340 while self.adjustment_history.len() > 100 {
342 self.adjustment_history.pop_front();
343 }
344 }
345}
346
347pub struct DynamicArchitectureOptimizer {
349 current_architecture: ArchitectureConfig,
351 search_history: Vec<ArchitectureSearchResult>,
353 strategy: ArchitectureOptimizationStrategy,
355}
356
357#[derive(Debug, Clone, Serialize, Deserialize)]
358pub struct ArchitectureConfig {
359 pub embedding_dim: usize,
361 pub num_layers: usize,
363 pub hidden_dims: Vec<usize>,
365 pub activations: Vec<String>,
367 pub dropout_rates: Vec<f32>,
369 pub normalizations: Vec<String>,
371}
372
373#[derive(Debug, Clone)]
374pub enum ArchitectureOptimizationStrategy {
375 NeuralArchitectureSearch,
376 GradientBasedSearch,
377 EvolutionarySearch,
378 HyperparameterOptimization,
379 PruningAndGrowth,
380}
381
382#[derive(Debug, Clone, Serialize, Deserialize)]
383pub struct ArchitectureSearchResult {
384 pub timestamp: chrono::DateTime<chrono::Utc>,
385 pub architecture: ArchitectureConfig,
386 pub performance: f32,
387 pub search_time: f32,
388 pub validation_score: f32,
389}
390
391impl DynamicArchitectureOptimizer {
392 pub fn new(
393 initial_config: ArchitectureConfig,
394 strategy: ArchitectureOptimizationStrategy,
395 ) -> Self {
396 Self {
397 current_architecture: initial_config,
398 search_history: Vec::new(),
399 strategy,
400 }
401 }
402
403 pub async fn optimize_architecture(
405 &mut self,
406 current_metrics: &PerformanceMetrics,
407 model: &dyn EmbeddingModel,
408 ) -> Result<ArchitectureConfig> {
409 info!(
410 "Starting architecture optimization with strategy: {:?}",
411 self.strategy
412 );
413
414 let new_architecture = match self.strategy {
415 ArchitectureOptimizationStrategy::NeuralArchitectureSearch => {
416 self.neural_architecture_search(current_metrics, model)
417 .await?
418 }
419 ArchitectureOptimizationStrategy::GradientBasedSearch => {
420 self.gradient_based_search(current_metrics, model).await?
421 }
422 ArchitectureOptimizationStrategy::EvolutionarySearch => {
423 self.evolutionary_search(current_metrics, model).await?
424 }
425 ArchitectureOptimizationStrategy::HyperparameterOptimization => {
426 self.hyperparameter_optimization(current_metrics, model)
427 .await?
428 }
429 ArchitectureOptimizationStrategy::PruningAndGrowth => {
430 self.pruning_and_growth(current_metrics, model).await?
431 }
432 };
433
434 let performance = self.evaluate_architecture(&new_architecture, model).await?;
436
437 self.record_search_result(new_architecture.clone(), performance);
439
440 if performance > current_metrics.validation_accuracy + 0.01 {
442 info!(
443 "Architecture optimization successful: {:.3} -> {:.3}",
444 current_metrics.validation_accuracy, performance
445 );
446 self.current_architecture = new_architecture.clone();
447 }
448
449 Ok(new_architecture)
450 }
451
452 async fn neural_architecture_search(
453 &self,
454 _current_metrics: &PerformanceMetrics,
455 _model: &dyn EmbeddingModel,
456 ) -> Result<ArchitectureConfig> {
457 let mut new_config = self.current_architecture.clone();
459
460 let mut random = Random::default();
462 if random.random::<f32>() < 0.3 {
463 let adjustment = if random.random::<bool>() { 1.1 } else { 0.9 };
464 new_config.embedding_dim =
465 ((new_config.embedding_dim as f32 * adjustment) as usize).clamp(32, 1024);
466 }
467
468 if random.random::<f32>() < 0.2 {
470 new_config.num_layers = if random.random::<bool>() {
471 (new_config.num_layers + 1).min(10)
472 } else {
473 (new_config.num_layers.saturating_sub(1)).max(1)
474 };
475 }
476
477 for hidden_dim in &mut new_config.hidden_dims {
479 if random.random::<f32>() < 0.2 {
480 let adjustment = 0.8 + random.random::<f32>() * 0.4; *hidden_dim = ((*hidden_dim as f32 * adjustment) as usize).clamp(16, 2048);
482 }
483 }
484
485 Ok(new_config)
486 }
487
488 async fn gradient_based_search(
489 &self,
490 current_metrics: &PerformanceMetrics,
491 _model: &dyn EmbeddingModel,
492 ) -> Result<ArchitectureConfig> {
493 let mut new_config = self.current_architecture.clone();
495
496 if current_metrics.training_loss > 0.5 {
499 new_config.embedding_dim = (new_config.embedding_dim as f32 * 1.1) as usize;
501 new_config.num_layers = (new_config.num_layers + 1).min(8);
502 } else if current_metrics.training_loss < 0.1 {
503 new_config.embedding_dim = (new_config.embedding_dim as f32 * 0.9) as usize;
505 for dropout_rate in &mut new_config.dropout_rates {
506 *dropout_rate = (*dropout_rate + 0.1).min(0.5);
507 }
508 }
509
510 Ok(new_config)
511 }
512
513 async fn evolutionary_search(
514 &self,
515 _current_metrics: &PerformanceMetrics,
516 model: &dyn EmbeddingModel,
517 ) -> Result<ArchitectureConfig> {
518 let population = self.generate_architecture_population(5);
520
521 let mut fitness_scores = Vec::new();
523 for config in &population {
524 let fitness = self.evaluate_architecture(config, model).await?;
525 fitness_scores.push(fitness);
526 }
527
528 let mut indexed_scores: Vec<(usize, f32)> = fitness_scores
530 .iter()
531 .enumerate()
532 .map(|(i, &s)| (i, s))
533 .collect();
534 indexed_scores.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
535
536 let parent1 = &population[indexed_scores[0].0];
538 let parent2 = &population[indexed_scores[1].0];
539 let offspring = self.crossover_architectures(parent1, parent2);
540 let mutated_offspring = self.mutate_architecture(offspring);
541
542 Ok(mutated_offspring)
543 }
544
545 async fn hyperparameter_optimization(
546 &self,
547 _current_metrics: &PerformanceMetrics,
548 _model: &dyn EmbeddingModel,
549 ) -> Result<ArchitectureConfig> {
550 let mut new_config = self.current_architecture.clone();
552
553 let mut random = Random::default();
555 for dropout_rate in &mut new_config.dropout_rates {
556 *dropout_rate = random.random::<f32>() * 0.5; }
558
559 for hidden_dim in &mut new_config.hidden_dims {
561 let log_dim = (*hidden_dim as f32).ln();
562 let noise = (random.random::<f32>() - 0.5) * 0.2;
563 let new_log_dim = log_dim + noise;
564 *hidden_dim = new_log_dim.exp() as usize;
565 }
566
567 Ok(new_config)
568 }
569
570 async fn pruning_and_growth(
571 &self,
572 current_metrics: &PerformanceMetrics,
573 _model: &dyn EmbeddingModel,
574 ) -> Result<ArchitectureConfig> {
575 let mut new_config = self.current_architecture.clone();
577
578 if current_metrics.model_complexity > 0.8 {
580 new_config.embedding_dim = (new_config.embedding_dim as f32 * 0.9) as usize;
581
582 new_config.hidden_dims.sort();
584 if new_config.hidden_dims.len() > 2 {
585 new_config.hidden_dims.remove(0);
586 new_config.num_layers = new_config.num_layers.saturating_sub(1);
587 }
588 }
589
590 if current_metrics.validation_accuracy < 0.6 {
592 new_config.embedding_dim = (new_config.embedding_dim as f32 * 1.1) as usize;
593
594 if new_config.num_layers < 6 {
596 let new_hidden_dim = new_config.embedding_dim / 2;
597 new_config.hidden_dims.push(new_hidden_dim);
598 new_config.num_layers += 1;
599 }
600 }
601
602 Ok(new_config)
603 }
604
605 fn generate_architecture_population(&self, size: usize) -> Vec<ArchitectureConfig> {
606 let mut population = Vec::new();
607
608 for _ in 0..size {
609 let mut config = self.current_architecture.clone();
610
611 let mut random = Random::default();
613 config.embedding_dim =
614 (64..=512).step_by(32).collect::<Vec<_>>()[random.random_range(0..15)];
615 config.num_layers = (1..=6).collect::<Vec<_>>()[random.random_range(0..6)];
616
617 config.hidden_dims = (0..config.num_layers)
619 .map(|_| (32..=1024).step_by(32).collect::<Vec<_>>()[random.random_range(0..31)])
620 .collect();
621
622 population.push(config);
623 }
624
625 population
626 }
627
628 fn crossover_architectures(
629 &self,
630 parent1: &ArchitectureConfig,
631 parent2: &ArchitectureConfig,
632 ) -> ArchitectureConfig {
633 let mut random = Random::default();
634 ArchitectureConfig {
635 embedding_dim: if random.random::<bool>() {
636 parent1.embedding_dim
637 } else {
638 parent2.embedding_dim
639 },
640 num_layers: if random.random::<bool>() {
641 parent1.num_layers
642 } else {
643 parent2.num_layers
644 },
645 hidden_dims: parent1
646 .hidden_dims
647 .iter()
648 .zip(parent2.hidden_dims.iter())
649 .map(|(d1, d2)| if random.random::<bool>() { *d1 } else { *d2 })
650 .collect(),
651 activations: if random.random::<bool>() {
652 parent1.activations.clone()
653 } else {
654 parent2.activations.clone()
655 },
656 dropout_rates: parent1
657 .dropout_rates
658 .iter()
659 .zip(parent2.dropout_rates.iter())
660 .map(|(r1, r2)| if random.random::<bool>() { *r1 } else { *r2 })
661 .collect(),
662 normalizations: if random.random::<bool>() {
663 parent1.normalizations.clone()
664 } else {
665 parent2.normalizations.clone()
666 },
667 }
668 }
669
670 fn mutate_architecture(&self, mut config: ArchitectureConfig) -> ArchitectureConfig {
671 let mut random = Random::default();
672 if random.random::<f32>() < 0.3 {
674 config.embedding_dim =
675 (config.embedding_dim as f32 * (0.8 + random.random::<f32>() * 0.4)) as usize;
676 }
677
678 for hidden_dim in &mut config.hidden_dims {
680 if random.random::<f32>() < 0.2 {
681 *hidden_dim = (*hidden_dim as f32 * (0.8 + random.random::<f32>() * 0.4)) as usize;
682 }
683 }
684
685 for dropout_rate in &mut config.dropout_rates {
687 if random.random::<f32>() < 0.2 {
688 *dropout_rate =
689 (*dropout_rate + (random.random::<f32>() - 0.5) * 0.1).clamp(0.0, 0.5);
690 }
691 }
692
693 config
694 }
695
696 async fn evaluate_architecture(
697 &self,
698 config: &ArchitectureConfig,
699 _model: &dyn EmbeddingModel,
700 ) -> Result<f32> {
701 let complexity_penalty =
706 (config.embedding_dim as f32 / 512.0 + config.num_layers as f32 / 6.0) * 0.1;
707 let mut random = Random::default();
708 let base_score = 0.7 + random.random::<f32>() * 0.2;
709
710 Ok((base_score - complexity_penalty).clamp(0.0, 1.0))
711 }
712
713 fn record_search_result(&mut self, architecture: ArchitectureConfig, performance: f32) {
714 let result = ArchitectureSearchResult {
715 timestamp: chrono::Utc::now(),
716 architecture,
717 performance,
718 search_time: 10.0, validation_score: performance,
720 };
721
722 self.search_history.push(result);
723
724 if self.search_history.len() > 50 {
726 self.search_history.remove(0);
727 }
728 }
729}
730
731pub struct OnlineLearningManager {
733 config: OnlineLearningConfig,
735 data_buffer: VecDeque<OnlineDataPoint>,
737 update_scheduler: UpdateScheduler,
739}
740
741#[derive(Debug, Clone, Serialize, Deserialize)]
742pub struct OnlineLearningConfig {
743 pub buffer_size: usize,
745 pub update_frequency: usize,
747 pub online_lr_decay: f32,
749 pub enable_ewc: bool,
751 pub replay_buffer_size: usize,
753}
754
755#[derive(Debug, Clone, Serialize, Deserialize)]
756pub struct OnlineDataPoint {
757 pub timestamp: chrono::DateTime<chrono::Utc>,
758 pub entity1: String,
759 pub entity2: String,
760 pub relation: String,
761 pub score: f32,
762 pub source: String,
763}
764
765#[derive(Debug, Clone)]
766pub enum UpdateScheduler {
767 Fixed(usize), Adaptive(f32), Timebased(Duration), TriggerBased, }
772
773const DRIFT_COSINE_DISTANCE_THRESHOLD: f32 = 0.15;
776
777impl OnlineLearningManager {
778 pub fn new(config: OnlineLearningConfig) -> Self {
779 Self {
780 config,
781 data_buffer: VecDeque::new(),
782 update_scheduler: UpdateScheduler::Fixed(100),
783 }
784 }
785
786 pub async fn add_data_point(&mut self, data_point: OnlineDataPoint) -> Result<()> {
788 self.data_buffer.push_back(data_point);
789
790 while self.data_buffer.len() > self.config.buffer_size {
792 self.data_buffer.pop_front();
793 }
794
795 if self.should_update() {
797 self.trigger_update().await?;
798 }
799
800 Ok(())
801 }
802
803 pub async fn perform_online_update<M: EmbeddingModel>(
805 &mut self,
806 model: &mut M,
807 ) -> Result<OnlineUpdateResult> {
808 info!(
809 "Performing online model update with {} data points",
810 self.data_buffer.len()
811 );
812
813 let start_time = Instant::now();
814
815 let batch_data: Vec<_> = self
817 .data_buffer
818 .iter()
819 .take(self.config.update_frequency)
820 .cloned()
821 .collect();
822
823 let update_stats = self.update_model_incremental(model, &batch_data).await?;
825
826 let update_time = start_time.elapsed();
827
828 for _ in 0..batch_data.len().min(self.data_buffer.len()) {
830 self.data_buffer.pop_front();
831 }
832
833 Ok(OnlineUpdateResult {
834 timestamp: chrono::Utc::now(),
835 samples_processed: batch_data.len(),
836 update_time: update_time.as_secs_f32(),
837 performance_improvement: update_stats.performance_improvement,
838 memory_usage: update_stats.memory_usage,
839 model_drift_detected: update_stats.drift_detected,
840 })
841 }
842
843 fn should_update(&self) -> bool {
844 match self.update_scheduler {
845 UpdateScheduler::Fixed(n) => self.data_buffer.len() >= n,
846 UpdateScheduler::Adaptive(_threshold) => {
847 self.data_buffer.len() >= self.config.buffer_size / 2
850 }
851 UpdateScheduler::Timebased(_duration) => {
852 true
855 }
856 UpdateScheduler::TriggerBased => {
857 false
859 }
860 }
861 }
862
863 async fn trigger_update(&mut self) -> Result<()> {
864 info!("Triggering online learning update");
865 Ok(())
867 }
868
869 fn evaluate_batch_performance<M: EmbeddingModel>(model: &M, batch: &[OnlineDataPoint]) -> f32 {
875 let mut total_error = 0.0f32;
876 let mut scored = 0usize;
877 for point in batch {
878 if let Ok(predicted) =
879 model.score_triple(&point.entity1, &point.relation, &point.entity2)
880 {
881 total_error += (predicted as f32 - point.score).abs();
882 scored += 1;
883 }
884 }
885 if scored == 0 {
886 return 0.0;
887 }
888 (1.0 - (total_error / scored as f32)).clamp(0.0, 1.0)
889 }
890
891 fn snapshot_embeddings<M: EmbeddingModel>(
895 model: &M,
896 batch: &[OnlineDataPoint],
897 ) -> HashMap<String, Vec<f32>> {
898 let mut snapshot = HashMap::new();
899 for point in batch {
900 for entity in [&point.entity1, &point.entity2] {
901 if snapshot.contains_key(entity) {
902 continue;
903 }
904 if let Ok(embedding) = model.get_entity_embedding(entity) {
905 snapshot.insert(entity.clone(), embedding.values);
906 }
907 }
908 }
909 snapshot
910 }
911
912 fn detect_embedding_drift(
918 before: &HashMap<String, Vec<f32>>,
919 after: &HashMap<String, Vec<f32>>,
920 ) -> bool {
921 let mut total_distance = 0.0f32;
922 let mut compared = 0usize;
923
924 for (entity, before_vec) in before {
925 let Some(after_vec) = after.get(entity) else {
926 continue;
927 };
928 if before_vec.len() != after_vec.len() || before_vec.is_empty() {
929 continue;
930 }
931 let dot: f32 = before_vec
932 .iter()
933 .zip(after_vec.iter())
934 .map(|(a, b)| a * b)
935 .sum();
936 let norm_before = before_vec.iter().map(|v| v * v).sum::<f32>().sqrt();
937 let norm_after = after_vec.iter().map(|v| v * v).sum::<f32>().sqrt();
938 if norm_before <= f32::EPSILON || norm_after <= f32::EPSILON {
939 continue;
940 }
941 let cosine_similarity = (dot / (norm_before * norm_after)).clamp(-1.0, 1.0);
942 total_distance += 1.0 - cosine_similarity;
943 compared += 1;
944 }
945
946 if compared == 0 {
947 return false;
948 }
949 (total_distance / compared as f32) > DRIFT_COSINE_DISTANCE_THRESHOLD
950 }
951
952 async fn update_model_incremental<M: EmbeddingModel>(
953 &self,
954 model: &mut M,
955 batch_data: &[OnlineDataPoint],
956 ) -> Result<IncrementalUpdateStats> {
957 if batch_data.is_empty() {
958 return Ok(IncrementalUpdateStats {
959 performance_improvement: 0.0,
960 memory_usage: 0.0,
961 drift_detected: false,
962 });
963 }
964
965 let performance_before = Self::evaluate_batch_performance(model, batch_data);
968 let embeddings_before = Self::snapshot_embeddings(model, batch_data);
969
970 for point in batch_data {
972 let subject = crate::NamedNode::new(&point.entity1)?;
973 let predicate = crate::NamedNode::new(&point.relation)?;
974 let object = crate::NamedNode::new(&point.entity2)?;
975 model.add_triple(crate::Triple::new(subject, predicate, object))?;
976 }
977
978 model.train(Some(1)).await?;
981
982 let performance_after = Self::evaluate_batch_performance(model, batch_data);
983 let embeddings_after = Self::snapshot_embeddings(model, batch_data);
984 let drift_detected = Self::detect_embedding_drift(&embeddings_before, &embeddings_after);
985
986 let stats = model.get_stats();
989 let memory_usage = ((stats.num_entities + stats.num_relations)
990 * stats.dimensions
991 * std::mem::size_of::<f32>()) as f32
992 / 1024.0;
993
994 Ok(IncrementalUpdateStats {
995 performance_improvement: performance_after - performance_before,
996 memory_usage,
997 drift_detected,
998 })
999 }
1000}
1001
1002#[derive(Debug, Clone, Serialize, Deserialize)]
1003pub struct OnlineUpdateResult {
1004 pub timestamp: chrono::DateTime<chrono::Utc>,
1005 pub samples_processed: usize,
1006 pub update_time: f32,
1007 pub performance_improvement: f32,
1008 pub memory_usage: f32,
1009 pub model_drift_detected: bool,
1010}
1011
1012#[derive(Debug, Clone)]
1013struct IncrementalUpdateStats {
1014 performance_improvement: f32,
1015 memory_usage: f32,
1016 drift_detected: bool,
1017}
1018
1019pub struct ResourceOptimizer {
1021 current_allocation: ResourceAllocation,
1023 usage_history: VecDeque<ResourceUsage>,
1025 strategy: ResourceOptimizationStrategy,
1027}
1028
1029#[derive(Debug, Clone, Serialize, Deserialize)]
1030pub struct ResourceAllocation {
1031 pub cpu_cores: usize,
1033 pub memory_mb: usize,
1035 pub gpu_memory_mb: usize,
1037 pub batch_size: usize,
1039 pub num_workers: usize,
1041}
1042
1043#[derive(Debug, Clone, Serialize, Deserialize)]
1044pub struct ResourceUsage {
1045 pub timestamp: chrono::DateTime<chrono::Utc>,
1046 pub cpu_utilization: f32,
1047 pub memory_usage: f32,
1048 pub gpu_utilization: f32,
1049 pub gpu_memory_usage: f32,
1050 pub throughput: f32,
1051 pub latency: f32,
1052}
1053
1054#[derive(Debug, Clone)]
1055pub enum ResourceOptimizationStrategy {
1056 ThroughputMaximization,
1057 LatencyMinimization,
1058 MemoryEfficiency,
1059 EnergyEfficiency,
1060 CostOptimization,
1061}
1062
1063impl ResourceOptimizer {
1064 pub fn new(
1065 initial_allocation: ResourceAllocation,
1066 strategy: ResourceOptimizationStrategy,
1067 ) -> Self {
1068 Self {
1069 current_allocation: initial_allocation,
1070 usage_history: VecDeque::new(),
1071 strategy,
1072 }
1073 }
1074
1075 pub async fn optimize_resources(
1077 &mut self,
1078 current_usage: &ResourceUsage,
1079 performance_metrics: &PerformanceMetrics,
1080 ) -> Result<ResourceAllocation> {
1081 self.usage_history.push_back(current_usage.clone());
1082
1083 while self.usage_history.len() > 100 {
1085 self.usage_history.pop_front();
1086 }
1087
1088 let new_allocation = match self.strategy {
1089 ResourceOptimizationStrategy::ThroughputMaximization => {
1090 self.optimize_for_throughput(current_usage, performance_metrics)
1091 .await?
1092 }
1093 ResourceOptimizationStrategy::LatencyMinimization => {
1094 self.optimize_for_latency(current_usage, performance_metrics)
1095 .await?
1096 }
1097 ResourceOptimizationStrategy::MemoryEfficiency => {
1098 self.optimize_for_memory(current_usage, performance_metrics)
1099 .await?
1100 }
1101 ResourceOptimizationStrategy::EnergyEfficiency => {
1102 self.optimize_for_energy(current_usage, performance_metrics)
1103 .await?
1104 }
1105 ResourceOptimizationStrategy::CostOptimization => {
1106 self.optimize_for_cost(current_usage, performance_metrics)
1107 .await?
1108 }
1109 };
1110
1111 self.current_allocation = new_allocation.clone();
1112 Ok(new_allocation)
1113 }
1114
1115 async fn optimize_for_throughput(
1116 &self,
1117 current_usage: &ResourceUsage,
1118 _performance_metrics: &PerformanceMetrics,
1119 ) -> Result<ResourceAllocation> {
1120 let mut new_allocation = self.current_allocation.clone();
1121
1122 if current_usage.gpu_utilization < 0.7 {
1124 new_allocation.batch_size = (new_allocation.batch_size as f32 * 1.2) as usize;
1125 }
1126
1127 if current_usage.cpu_utilization < 0.6 {
1129 new_allocation.num_workers = (new_allocation.num_workers + 1).min(16);
1130 }
1131
1132 Ok(new_allocation)
1133 }
1134
1135 async fn optimize_for_latency(
1136 &self,
1137 current_usage: &ResourceUsage,
1138 performance_metrics: &PerformanceMetrics,
1139 ) -> Result<ResourceAllocation> {
1140 let mut new_allocation = self.current_allocation.clone();
1141
1142 if performance_metrics.inference_latency > 100.0 {
1144 new_allocation.batch_size = (new_allocation.batch_size as f32 * 0.8) as usize;
1145 }
1146
1147 if current_usage.memory_usage < 0.8 {
1149 new_allocation.memory_mb = (new_allocation.memory_mb as f32 * 1.1) as usize;
1150 }
1151
1152 Ok(new_allocation)
1153 }
1154
1155 async fn optimize_for_memory(
1156 &self,
1157 current_usage: &ResourceUsage,
1158 _performance_metrics: &PerformanceMetrics,
1159 ) -> Result<ResourceAllocation> {
1160 let mut new_allocation = self.current_allocation.clone();
1161
1162 if current_usage.memory_usage > 0.9 {
1164 new_allocation.batch_size = (new_allocation.batch_size as f32 * 0.8) as usize;
1165 }
1166
1167 if current_usage.gpu_memory_usage < 0.7 {
1169 new_allocation.gpu_memory_mb = (new_allocation.gpu_memory_mb as f32 * 0.9) as usize;
1170 }
1171
1172 Ok(new_allocation)
1173 }
1174
1175 async fn optimize_for_energy(
1176 &self,
1177 current_usage: &ResourceUsage,
1178 _performance_metrics: &PerformanceMetrics,
1179 ) -> Result<ResourceAllocation> {
1180 let mut new_allocation = self.current_allocation.clone();
1181
1182 if current_usage.cpu_utilization < 0.5 {
1184 new_allocation.cpu_cores = (new_allocation.cpu_cores.saturating_sub(1)).max(1);
1185 }
1186
1187 let optimal_batch_size = self.calculate_energy_optimal_batch_size(current_usage);
1189 new_allocation.batch_size = optimal_batch_size;
1190
1191 Ok(new_allocation)
1192 }
1193
1194 async fn optimize_for_cost(
1195 &self,
1196 current_usage: &ResourceUsage,
1197 performance_metrics: &PerformanceMetrics,
1198 ) -> Result<ResourceAllocation> {
1199 let mut new_allocation = self.current_allocation.clone();
1200
1201 let efficiency_ratio = performance_metrics.throughput / current_usage.gpu_utilization;
1203
1204 if efficiency_ratio < 100.0 {
1205 new_allocation.gpu_memory_mb = (new_allocation.gpu_memory_mb as f32 * 0.9) as usize;
1207 new_allocation.batch_size = (new_allocation.batch_size as f32 * 0.9) as usize;
1208 } else {
1209 new_allocation.batch_size = (new_allocation.batch_size as f32 * 1.05) as usize;
1211 }
1212
1213 Ok(new_allocation)
1214 }
1215
1216 fn calculate_energy_optimal_batch_size(&self, current_usage: &ResourceUsage) -> usize {
1217 let base_batch_size = self.current_allocation.batch_size;
1220 let utilization_factor =
1221 (current_usage.gpu_utilization + current_usage.cpu_utilization) / 2.0;
1222
1223 (base_batch_size as f32 * utilization_factor * 1.2) as usize
1224 }
1225}
1226
1227pub struct OptimizationHistory {
1229 performance_history: VecDeque<PerformanceMetrics>,
1231 optimization_actions: VecDeque<OptimizationAction>,
1233 resource_history: VecDeque<ResourceUsage>,
1235}
1236
1237#[derive(Debug, Clone, Serialize, Deserialize)]
1238pub struct OptimizationAction {
1239 pub timestamp: chrono::DateTime<chrono::Utc>,
1240 pub action_type: String,
1241 pub parameters: HashMap<String, f32>,
1242 pub expected_improvement: f32,
1243 pub actual_improvement: Option<f32>,
1244}
1245
1246impl Default for OptimizationHistory {
1247 fn default() -> Self {
1248 Self::new()
1249 }
1250}
1251
1252impl OptimizationHistory {
1253 pub fn new() -> Self {
1254 Self {
1255 performance_history: VecDeque::new(),
1256 optimization_actions: VecDeque::new(),
1257 resource_history: VecDeque::new(),
1258 }
1259 }
1260
1261 pub fn record_optimization_action(&mut self, action: OptimizationAction) {
1263 self.optimization_actions.push_back(action);
1264
1265 while self.optimization_actions.len() > 200 {
1267 self.optimization_actions.pop_front();
1268 }
1269 }
1270
1271 pub fn get_optimization_summary(&self) -> OptimizationSummary {
1273 OptimizationSummary {
1274 total_optimizations: self.optimization_actions.len(),
1275 successful_optimizations: self
1276 .optimization_actions
1277 .iter()
1278 .filter(|a| a.actual_improvement.unwrap_or(0.0) > 0.0)
1279 .count(),
1280 average_improvement: self
1281 .optimization_actions
1282 .iter()
1283 .filter_map(|a| a.actual_improvement)
1284 .sum::<f32>()
1285 / self.optimization_actions.len() as f32,
1286 optimization_efficiency: self.calculate_optimization_efficiency(),
1287 }
1288 }
1289
1290 fn calculate_optimization_efficiency(&self) -> f32 {
1291 let successful = self
1292 .optimization_actions
1293 .iter()
1294 .filter(|a| a.actual_improvement.unwrap_or(0.0) > 0.0)
1295 .count() as f32;
1296
1297 successful / self.optimization_actions.len() as f32
1298 }
1299}
1300
1301#[derive(Debug, Clone, Serialize, Deserialize)]
1302pub struct OptimizationSummary {
1303 pub total_optimizations: usize,
1304 pub successful_optimizations: usize,
1305 pub average_improvement: f32,
1306 pub optimization_efficiency: f32,
1307}
1308
1309impl RealTimeOptimizer {
1310 pub fn new(config: OptimizationConfig) -> Self {
1312 let performance_monitor = PerformanceMonitor {
1313 metrics_history: Arc::new(Mutex::new(VecDeque::new())),
1314 current_baseline: Arc::new(Mutex::new(PerformanceMetrics::default())),
1315 window_size: config.performance_window_size,
1316 };
1317
1318 let learning_rate_scheduler =
1319 AdaptiveLearningRateScheduler::new(0.001, LearningRateStrategy::PerformanceBased);
1320
1321 let architecture_optimizer = DynamicArchitectureOptimizer::new(
1322 ArchitectureConfig {
1323 embedding_dim: 256,
1324 num_layers: 3,
1325 hidden_dims: vec![512, 256, 128],
1326 activations: vec![
1327 "relu".to_string(),
1328 "relu".to_string(),
1329 "sigmoid".to_string(),
1330 ],
1331 dropout_rates: vec![0.1, 0.2, 0.1],
1332 normalizations: vec!["batch".to_string(), "batch".to_string(), "none".to_string()],
1333 },
1334 ArchitectureOptimizationStrategy::EvolutionarySearch,
1335 );
1336
1337 let online_learning_manager = OnlineLearningManager::new(OnlineLearningConfig {
1338 buffer_size: 1000,
1339 update_frequency: 100,
1340 online_lr_decay: 0.999,
1341 enable_ewc: true,
1342 replay_buffer_size: 5000,
1343 });
1344
1345 let resource_optimizer = ResourceOptimizer::new(
1346 ResourceAllocation {
1347 cpu_cores: 4,
1348 memory_mb: 8192,
1349 gpu_memory_mb: 4096,
1350 batch_size: 32,
1351 num_workers: 4,
1352 },
1353 ResourceOptimizationStrategy::ThroughputMaximization,
1354 );
1355
1356 Self {
1357 config,
1358 performance_monitor,
1359 learning_rate_scheduler,
1360 architecture_optimizer,
1361 online_learning_manager,
1362 resource_optimizer,
1363 optimization_history: OptimizationHistory::new(),
1364 }
1365 }
1366
1367 pub async fn start_optimization_loop<M: EmbeddingModel + Send + Clone + 'static>(
1369 &mut self,
1370 model: Arc<Mutex<M>>,
1371 ) -> Result<()> {
1372 info!("Starting real-time optimization loop");
1373
1374 loop {
1375 let current_metrics = self.collect_performance_metrics(&model).await?;
1377
1378 self.record_performance_metrics(current_metrics.clone());
1380
1381 if self.config.enable_adaptive_lr {
1383 self.optimize_learning_rate(¤t_metrics).await?;
1384 }
1385
1386 if self.config.enable_architecture_opt {
1387 self.optimize_architecture::<M>(¤t_metrics, &model)
1388 .await?;
1389 }
1390
1391 if self.config.enable_resource_opt {
1392 self.optimize_resources(¤t_metrics).await?;
1393 }
1394
1395 sleep(Duration::from_secs(self.config.optimization_frequency)).await;
1397 }
1398 }
1399
1400 async fn collect_performance_metrics<M: EmbeddingModel>(
1401 &self,
1402 _model: &Arc<Mutex<M>>,
1403 ) -> Result<PerformanceMetrics> {
1404 let mut random = Random::default();
1407 Ok(PerformanceMetrics {
1408 timestamp: chrono::Utc::now(),
1409 training_loss: 0.5 + random.random::<f32>() * 0.3,
1410 validation_accuracy: 0.7 + random.random::<f32>() * 0.2,
1411 inference_latency: 50.0 + random.random::<f32>() * 100.0,
1412 memory_usage: 2048.0 + random.random::<f32>() * 1024.0,
1413 gpu_utilization: 60.0 + random.random::<f32>() * 30.0,
1414 throughput: 80.0 + random.random::<f32>() * 40.0,
1415 learning_rate: self.learning_rate_scheduler.current_lr,
1416 model_complexity: 0.5 + random.random::<f32>() * 0.3,
1417 })
1418 }
1419
1420 fn record_performance_metrics(&mut self, metrics: PerformanceMetrics) {
1421 let mut history = self
1422 .performance_monitor
1423 .metrics_history
1424 .lock()
1425 .expect("lock should not be poisoned");
1426 history.push_back(metrics.clone());
1427
1428 while history.len() > self.performance_monitor.window_size {
1430 history.pop_front();
1431 }
1432
1433 *self
1435 .performance_monitor
1436 .current_baseline
1437 .lock()
1438 .expect("lock should not be poisoned") = metrics;
1439 }
1440
1441 async fn optimize_learning_rate(&mut self, current_metrics: &PerformanceMetrics) -> Result<()> {
1442 let history = self
1443 .performance_monitor
1444 .metrics_history
1445 .lock()
1446 .expect("lock should not be poisoned");
1447 let recent_metrics: Vec<_> = history.iter().cloned().collect();
1448 drop(history);
1449
1450 let new_lr = self
1451 .learning_rate_scheduler
1452 .adjust_learning_rate(current_metrics, &recent_metrics)?;
1453
1454 info!(
1455 "Learning rate adjusted: {:.6} -> {:.6}",
1456 current_metrics.learning_rate, new_lr
1457 );
1458
1459 Ok(())
1460 }
1461
1462 async fn optimize_architecture<M: EmbeddingModel + Clone + Send + Sync>(
1463 &mut self,
1464 current_metrics: &PerformanceMetrics,
1465 model: &Arc<Mutex<M>>,
1466 ) -> Result<()> {
1467 let cloned_model = {
1470 let model_guard = model.lock().expect("lock should not be poisoned");
1471 (*model_guard).clone()
1472 };
1473 let new_architecture = self
1474 .architecture_optimizer
1475 .optimize_architecture(current_metrics, &cloned_model)
1476 .await?;
1477
1478 info!(
1479 "Architecture optimization completed: {:?}",
1480 new_architecture
1481 );
1482
1483 Ok(())
1484 }
1485
1486 async fn optimize_resources(&mut self, current_metrics: &PerformanceMetrics) -> Result<()> {
1487 let mut random = Random::default();
1488 let current_usage = ResourceUsage {
1489 timestamp: chrono::Utc::now(),
1490 cpu_utilization: 60.0 + random.random::<f32>() * 30.0,
1491 memory_usage: current_metrics.memory_usage / 8192.0,
1492 gpu_utilization: current_metrics.gpu_utilization / 100.0,
1493 gpu_memory_usage: 0.7 + random.random::<f32>() * 0.2,
1494 throughput: current_metrics.throughput,
1495 latency: current_metrics.inference_latency,
1496 };
1497
1498 let new_allocation = self
1499 .resource_optimizer
1500 .optimize_resources(¤t_usage, current_metrics)
1501 .await?;
1502
1503 info!("Resource allocation optimized: {:?}", new_allocation);
1504
1505 Ok(())
1506 }
1507
1508 pub fn get_optimization_summary(&self) -> OptimizationSummary {
1510 self.optimization_history.get_optimization_summary()
1511 }
1512}
1513
1514#[cfg(test)]
1515mod tests {
1516 use super::*;
1517 use crate::models::TransE;
1518 use crate::ModelConfig;
1519
1520 fn sample_config() -> OnlineLearningConfig {
1521 OnlineLearningConfig {
1522 buffer_size: 100,
1523 update_frequency: 10,
1524 online_lr_decay: 0.99,
1525 enable_ewc: false,
1526 replay_buffer_size: 50,
1527 }
1528 }
1529
1530 fn sample_data_point(entity1: &str, entity2: &str) -> OnlineDataPoint {
1531 OnlineDataPoint {
1532 timestamp: chrono::Utc::now(),
1533 entity1: entity1.to_string(),
1534 entity2: entity2.to_string(),
1535 relation: "knows".to_string(),
1536 score: 0.8,
1537 source: "test".to_string(),
1538 }
1539 }
1540
1541 #[tokio::test]
1546 async fn test_perform_online_update_trains_model_and_reports_real_stats() {
1547 let mut model = TransE::new(ModelConfig::default().with_dimensions(8));
1548 model
1549 .add_triple(crate::Triple::new(
1550 crate::NamedNode::new("alice").expect("valid"),
1551 crate::NamedNode::new("knows").expect("valid"),
1552 crate::NamedNode::new("bob").expect("valid"),
1553 ))
1554 .expect("add_triple should succeed");
1555 model
1556 .train(Some(1))
1557 .await
1558 .expect("initial train should succeed");
1559
1560 let mut manager = OnlineLearningManager::new(sample_config());
1561 manager
1562 .add_data_point(sample_data_point("alice", "bob"))
1563 .await
1564 .expect("add_data_point should succeed");
1565
1566 let result = manager
1567 .perform_online_update(&mut model)
1568 .await
1569 .expect("online update should succeed");
1570
1571 assert_eq!(result.samples_processed, 1);
1572 assert!(model.get_entities().contains(&"alice".to_string()));
1574 assert!(model.get_entities().contains(&"bob".to_string()));
1575 assert!(
1578 result.memory_usage > 0.0,
1579 "memory_usage = {}",
1580 result.memory_usage
1581 );
1582 }
1583
1584 #[tokio::test]
1585 async fn test_update_model_incremental_empty_batch_is_a_real_no_op() {
1586 let mut model = TransE::new(ModelConfig::default().with_dimensions(8));
1587 let manager = OnlineLearningManager::new(sample_config());
1588
1589 let stats = manager
1590 .update_model_incremental(&mut model, &[])
1591 .await
1592 .expect("empty batch should succeed as a no-op");
1593
1594 assert_eq!(stats.performance_improvement, 0.0);
1595 assert_eq!(stats.memory_usage, 0.0);
1596 assert!(!stats.drift_detected);
1597 }
1598}