1#![allow(dead_code)]
25
26use crate::{amacp::AMacPConfig, novograd::NovoGradConfig};
27use scirs2_core::random::*; use serde::{Deserialize, Serialize};
30use std::collections::HashMap;
31use std::time::{Duration, Instant};
32use trustformers_core::errors::{Result, TrustformersError};
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct HyperparameterSpace {
37 pub learning_rate: (f32, f32),
39 pub beta1: (f32, f32),
41 pub beta2: (f32, f32),
43 pub weight_decay: (f32, f32),
45 pub epsilon: (f32, f32),
47 pub batch_sizes: Vec<usize>,
49 pub log_scale_lr: bool,
51 pub custom_params: HashMap<String, (f32, f32)>,
53}
54
55impl Default for HyperparameterSpace {
56 fn default() -> Self {
57 Self {
58 learning_rate: (1e-5, 1e-1),
59 beta1: (0.8, 0.999),
60 beta2: (0.9, 0.9999),
61 weight_decay: (0.0, 1e-1),
62 epsilon: (1e-10, 1e-6),
63 batch_sizes: vec![16, 32, 64, 128, 256],
64 log_scale_lr: true,
65 custom_params: HashMap::new(),
66 }
67 }
68}
69
70impl HyperparameterSpace {
71 pub fn for_transformers() -> Self {
73 Self {
74 learning_rate: (1e-5, 5e-3),
75 beta1: (0.85, 0.95),
76 beta2: (0.95, 0.999),
77 weight_decay: (1e-3, 1e-1),
78 epsilon: (1e-8, 1e-6),
79 batch_sizes: vec![32, 64, 128, 256],
80 log_scale_lr: true,
81 custom_params: [
82 ("warmup_steps".to_string(), (1000.0, 10000.0)),
83 ("max_grad_norm".to_string(), (0.5, 2.0)),
84 ]
85 .into_iter()
86 .collect(),
87 }
88 }
89
90 pub fn for_vision() -> Self {
92 Self {
93 learning_rate: (1e-4, 1e-1),
94 beta1: (0.9, 0.99),
95 beta2: (0.999, 0.9999),
96 weight_decay: (1e-5, 1e-2),
97 epsilon: (1e-8, 1e-6),
98 batch_sizes: vec![16, 32, 64, 128],
99 log_scale_lr: true,
100 custom_params: HashMap::new(),
101 }
102 }
103
104 pub fn for_scientific_computing() -> Self {
106 Self {
107 learning_rate: (1e-6, 1e-2),
108 beta1: (0.95, 0.999),
109 beta2: (0.999, 0.9999),
110 weight_decay: (0.0, 1e-4),
111 epsilon: (1e-12, 1e-8),
112 batch_sizes: vec![32, 64, 128],
113 log_scale_lr: true,
114 custom_params: [("precision_threshold".to_string(), (1e-8, 1e-6))]
115 .into_iter()
116 .collect(),
117 }
118 }
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct HyperparameterSample {
124 pub learning_rate: f32,
125 pub beta1: f32,
126 pub beta2: f32,
127 pub weight_decay: f32,
128 pub epsilon: f32,
129 pub batch_size: usize,
130 pub custom_params: HashMap<String, f32>,
131 pub performance_score: Option<f32>,
133 pub training_time: Option<f32>,
135 pub memory_usage: Option<usize>,
137}
138
139#[derive(Debug, Clone)]
141pub struct OptimizationTask {
142 pub name: String,
143 pub model_size: usize,
144 pub dataset_size: usize,
145 pub max_epochs: usize,
146 pub convergence_threshold: f32,
147 pub target_metric: String,
148 pub task_type: TaskType,
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub enum TaskType {
153 Classification,
154 Regression,
155 LanguageModeling,
156 ComputerVision,
157 ScientificComputing,
158 Reinforcement,
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize)]
163pub struct PerformanceMetrics {
164 pub final_loss: f32,
165 pub convergence_epoch: usize,
166 pub training_time: Duration,
167 pub memory_peak: usize,
168 pub stability_score: f32,
169 pub throughput: f32, pub gradient_norm_variance: f32,
171 pub composite_score: f32,
172}
173
174#[derive(Debug)]
176pub struct BayesianOptimizer {
177 space: HyperparameterSpace,
178 samples: Vec<HyperparameterSample>,
179 good_samples: Vec<HyperparameterSample>,
180 poor_samples: Vec<HyperparameterSample>,
181 performance_threshold: f32,
182 exploration_factor: f32,
183 n_startup_trials: usize,
184 gamma: f32, }
186
187impl BayesianOptimizer {
188 pub fn new(space: HyperparameterSpace) -> Self {
189 Self {
190 space,
191 samples: Vec::new(),
192 good_samples: Vec::new(),
193 poor_samples: Vec::new(),
194 performance_threshold: 0.0,
195 exploration_factor: 0.25,
196 n_startup_trials: 20,
197 gamma: 0.25,
198 }
199 }
200
201 pub fn suggest(&mut self) -> HyperparameterSample {
203 if self.samples.len() < self.n_startup_trials {
204 self.random_sample()
206 } else {
207 self.tpe_sample()
209 }
210 }
211
212 pub fn update(&mut self, mut sample: HyperparameterSample, performance: f32) {
214 sample.performance_score = Some(performance);
215
216 let mut performances: Vec<f32> =
218 self.samples.iter().filter_map(|s| s.performance_score).collect();
219 performances.push(performance);
220 performances.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
221
222 if !performances.is_empty() {
223 self.performance_threshold = performances[performances.len() / 2];
224 }
225
226 if performance > self.performance_threshold {
228 self.good_samples.push(sample.clone());
229 } else {
230 self.poor_samples.push(sample.clone());
231 }
232
233 self.samples.push(sample);
234
235 if self.good_samples.len() > 1 {
237 self.good_samples.sort_by(|a, b| {
238 b.performance_score
239 .unwrap_or(0.0)
240 .partial_cmp(&a.performance_score.unwrap_or(0.0))
241 .unwrap_or(std::cmp::Ordering::Equal)
242 });
243 let keep_count = ((self.samples.len() as f32 * self.gamma).ceil() as usize).max(1);
244 self.good_samples.truncate(keep_count);
245 }
246 }
247
248 fn random_sample(&self) -> HyperparameterSample {
249 let mut rng = thread_rng();
251
252 let learning_rate = if self.space.log_scale_lr {
253 let log_min = self.space.learning_rate.0.ln();
254 let log_max = self.space.learning_rate.1.ln();
255 (rng.random::<f32>() * (log_max - log_min) + log_min).exp()
256 } else {
257 rng.random_range(self.space.learning_rate.0..=self.space.learning_rate.1)
258 };
259
260 HyperparameterSample {
261 learning_rate,
262 beta1: rng.random_range(self.space.beta1.0..=self.space.beta1.1),
263 beta2: rng.random_range(self.space.beta2.0..=self.space.beta2.1),
264 weight_decay: rng.random_range(self.space.weight_decay.0..=self.space.weight_decay.1),
265 epsilon: rng.random_range(self.space.epsilon.0..=self.space.epsilon.1),
266 batch_size: {
267 let idx = rng.random_range(0..self.space.batch_sizes.len());
268 self.space.batch_sizes[idx]
269 },
270 custom_params: self
271 .space
272 .custom_params
273 .iter()
274 .map(|(k, &(min, max))| (k.clone(), rng.random_range(min..=max)))
275 .collect(),
276 performance_score: None,
277 training_time: None,
278 memory_usage: None,
279 }
280 }
281
282 fn tpe_sample(&self) -> HyperparameterSample {
283 let mut rng = thread_rng();
287
288 if self.good_samples.is_empty() {
289 return self.random_sample();
290 }
291
292 let idx = rng.random_range(0..self.good_samples.len());
294 let good_sample = &self.good_samples[idx];
295 let noise_factor = 0.1;
296
297 let learning_rate = if self.space.log_scale_lr {
298 let log_lr = good_sample.learning_rate.ln();
299 let noise = rng.random_range(-noise_factor..=noise_factor);
300 (log_lr + noise)
301 .exp()
302 .clamp(self.space.learning_rate.0, self.space.learning_rate.1)
303 } else {
304 let noise = rng.random_range(-noise_factor..=noise_factor)
305 * (self.space.learning_rate.1 - self.space.learning_rate.0);
306 (good_sample.learning_rate + noise)
307 .clamp(self.space.learning_rate.0, self.space.learning_rate.1)
308 };
309
310 HyperparameterSample {
311 learning_rate,
312 beta1: (good_sample.beta1 + rng.random_range(-0.01..=0.01))
313 .clamp(self.space.beta1.0, self.space.beta1.1),
314 beta2: (good_sample.beta2 + rng.random_range(-0.001..=0.001))
315 .clamp(self.space.beta2.0, self.space.beta2.1),
316 weight_decay: (good_sample.weight_decay
317 + rng.random_range(-noise_factor..=noise_factor)
318 * (self.space.weight_decay.1 - self.space.weight_decay.0))
319 .clamp(self.space.weight_decay.0, self.space.weight_decay.1),
320 epsilon: good_sample.epsilon,
321 batch_size: good_sample.batch_size,
322 custom_params: good_sample.custom_params.clone(),
323 performance_score: None,
324 training_time: None,
325 memory_usage: None,
326 }
327 }
328
329 pub fn get_best(&self) -> Option<&HyperparameterSample> {
331 self.samples.iter().filter(|s| s.performance_score.is_some()).max_by(|a, b| {
332 a.performance_score
334 .unwrap_or(0.0)
335 .partial_cmp(&b.performance_score.unwrap_or(0.0))
336 .unwrap_or(std::cmp::Ordering::Equal)
337 })
338 }
339}
340
341#[derive(Debug)]
343pub struct MultiObjectiveOptimizer {
344 bayesian_opt: BayesianOptimizer,
345 objectives: Vec<String>,
346 weights: Vec<f32>,
347 pareto_front: Vec<HyperparameterSample>,
348}
349
350impl MultiObjectiveOptimizer {
351 pub fn new(space: HyperparameterSpace, objectives: Vec<String>, weights: Vec<f32>) -> Self {
352 assert_eq!(
353 objectives.len(),
354 weights.len(),
355 "Objectives and weights must have same length"
356 );
357
358 Self {
359 bayesian_opt: BayesianOptimizer::new(space),
360 objectives,
361 weights,
362 pareto_front: Vec::new(),
363 }
364 }
365
366 pub fn update_multi_objective(
368 &mut self,
369 sample: HyperparameterSample,
370 metrics: &PerformanceMetrics,
371 ) {
372 let mut weighted_score = 0.0;
374 weighted_score += self.weights[0] * (1.0 / (1.0 + metrics.final_loss)); weighted_score += self.weights[1] * (1.0 / (1.0 + metrics.convergence_epoch as f32)); if self.weights.len() > 2 {
377 weighted_score += self.weights[2] * metrics.stability_score; }
379 if self.weights.len() > 3 {
380 weighted_score += self.weights[3] * (1.0 / (1.0 + metrics.training_time.as_secs_f32()));
381 }
383
384 self.bayesian_opt.update(sample, weighted_score);
385 self.update_pareto_front();
386 }
387
388 fn update_pareto_front(&mut self) {
389 self.pareto_front.clear();
391
392 for sample in &self.bayesian_opt.samples {
393 if let Some(sample_score) = sample.performance_score {
394 let mut is_dominated = false;
395
396 for other in &self.bayesian_opt.samples {
397 if let Some(other_score) = other.performance_score {
398 if other_score > sample_score {
399 is_dominated = true;
400 break;
401 }
402 }
403 }
404
405 if !is_dominated {
406 self.pareto_front.push(sample.clone());
407 }
408 }
409 }
410 }
411}
412
413#[derive(Debug)]
415pub struct HyperparameterTuner {
416 optimizer_type: OptimizerType,
417 search_space: HyperparameterSpace,
418 bayesian_opt: BayesianOptimizer,
419 multi_objective_opt: Option<MultiObjectiveOptimizer>,
420 task: OptimizationTask,
421 max_trials: usize,
422 current_trial: usize,
423 best_config: Option<HyperparameterSample>,
424 optimization_history: Vec<(HyperparameterSample, PerformanceMetrics)>,
425}
426
427#[derive(Debug, Clone)]
428pub enum OptimizerType {
429 Adam,
430 AdamW,
431 AMacP,
432 NovoGrad,
433 AveragedAdam,
434 Lion,
435 LAMB,
436}
437
438impl HyperparameterTuner {
439 pub fn new(
441 optimizer_type: OptimizerType,
442 search_space: HyperparameterSpace,
443 task: OptimizationTask,
444 max_trials: usize,
445 ) -> Self {
446 let bayesian_opt = BayesianOptimizer::new(search_space.clone());
447
448 Self {
449 optimizer_type,
450 search_space,
451 bayesian_opt,
452 multi_objective_opt: None,
453 task,
454 max_trials,
455 current_trial: 0,
456 best_config: None,
457 optimization_history: Vec::new(),
458 }
459 }
460
461 pub fn enable_multi_objective(&mut self, objectives: Vec<String>, weights: Vec<f32>) {
463 self.multi_objective_opt = Some(MultiObjectiveOptimizer::new(
464 self.search_space.clone(),
465 objectives,
466 weights,
467 ));
468 }
469
470 pub fn suggest_next(&mut self) -> Option<HyperparameterSample> {
472 if self.current_trial >= self.max_trials {
473 return None;
474 }
475
476 self.current_trial += 1;
477 Some(self.bayesian_opt.suggest())
478 }
479
480 pub fn evaluate_config(&mut self, config: HyperparameterSample) -> Result<PerformanceMetrics> {
482 let _start_time = Instant::now();
483
484 let metrics = self.simulate_training(&config)?;
486
487 if let Some(ref mut multi_opt) = self.multi_objective_opt {
489 multi_opt.update_multi_objective(config.clone(), &metrics);
490 } else {
491 self.bayesian_opt.update(config.clone(), metrics.composite_score);
492 }
493
494 let current_best_score =
496 self.best_config.as_ref().and_then(|c| c.performance_score).unwrap_or(0.0);
497 if self.best_config.is_none() || metrics.composite_score > current_best_score {
498 let mut best_config = config.clone();
499 best_config.performance_score = Some(metrics.composite_score);
500 self.best_config = Some(best_config);
501 }
502
503 self.optimization_history.push((config, metrics.clone()));
504 Ok(metrics)
505 }
506
507 fn simulate_training(&self, config: &HyperparameterSample) -> Result<PerformanceMetrics> {
508 let mut rng = thread_rng();
510
511 let lr_factor = if config.learning_rate > 1e-2 {
513 0.7_f64 } else if config.learning_rate < 1e-5 {
515 0.8_f64 } else {
517 1.0_f64 };
519
520 let momentum_factor = if config.beta1 > 0.95 { 0.9_f64 } else { 1.0_f64 };
522 let variance_factor = if config.beta2 < 0.99 { 0.85_f64 } else { 1.0_f64 };
523
524 let regularization_factor = if config.weight_decay > 1e-2 { 0.8_f64 } else { 1.0_f64 };
526
527 let base_performance = 0.8_f64;
528 let noise = rng.random_range(-0.1_f64..=0.1_f64);
529 let final_loss = (1.0_f64
530 - base_performance
531 * lr_factor
532 * momentum_factor
533 * variance_factor
534 * regularization_factor
535 + noise)
536 .max(0.01_f64);
537
538 let convergence_epoch = (50.0 / lr_factor) as usize;
539 let training_time = Duration::from_secs((convergence_epoch as f32 * 0.1) as u64);
540 let memory_peak = (config.batch_size * 1024 * 1024) + rng.random_range(0..1024 * 1024);
541
542 let stability_score = momentum_factor * variance_factor;
543 let throughput =
544 (config.batch_size as f32) / (training_time.as_secs_f32() / convergence_epoch as f32);
545 let gradient_norm_variance = rng.random_range(0.01..=0.5);
546
547 let composite_score = (1.0_f64 / final_loss) * 0.4_f64
549 + (1.0_f64 / convergence_epoch as f64) * 0.3_f64
550 + stability_score * 0.2_f64
551 + (throughput as f64 / 1000.0_f64).min(1.0_f64) * 0.1_f64;
552
553 Ok(PerformanceMetrics {
554 final_loss: final_loss as f32,
555 convergence_epoch,
556 training_time,
557 memory_peak,
558 stability_score: stability_score as f32,
559 throughput,
560 gradient_norm_variance,
561 composite_score: composite_score as f32,
562 })
563 }
564
565 pub fn optimize(&mut self) -> Result<HyperparameterSample> {
567 println!(
568 "š Starting hyperparameter optimization for {:?}",
569 self.optimizer_type
570 );
571 println!(
572 "š Task: {} (max {} trials)",
573 self.task.name, self.max_trials
574 );
575
576 let mut trial_results = Vec::new();
577
578 while let Some(config) = self.suggest_next() {
579 println!("\nš Trial {}/{}", self.current_trial, self.max_trials);
580 println!(
581 " LR: {:.2e}, βā: {:.3}, βā: {:.4}, WD: {:.2e}",
582 config.learning_rate, config.beta1, config.beta2, config.weight_decay
583 );
584
585 let metrics = self.evaluate_config(config.clone())?;
586 trial_results.push((config, metrics.clone()));
587
588 println!(
589 " š Score: {:.4}, Loss: {:.4}, Epochs: {}, Time: {:.1}s",
590 metrics.composite_score,
591 metrics.final_loss,
592 metrics.convergence_epoch,
593 metrics.training_time.as_secs_f32()
594 );
595
596 if metrics.composite_score > 0.95 {
598 println!("šÆ Early stopping - excellent configuration found!");
599 break;
600 }
601 }
602
603 self.print_optimization_summary();
604
605 self.best_config.clone().ok_or_else(|| {
606 TrustformersError::new(trustformers_core::errors::ErrorKind::InvalidConfiguration {
607 field: "hyperparameter_optimization".to_string(),
608 reason: "No valid configuration found".to_string(),
609 })
610 })
611 }
612
613 fn print_optimization_summary(&self) {
614 println!("\nš Hyperparameter Optimization Summary");
615 println!("=====================================");
616
617 if let Some(ref best) = self.best_config {
618 println!("š Best Configuration Found:");
619 println!(" Learning Rate: {:.2e}", best.learning_rate);
620 println!(" Beta1: {:.4}", best.beta1);
621 println!(" Beta2: {:.4}", best.beta2);
622 println!(" Weight Decay: {:.2e}", best.weight_decay);
623 println!(" Batch Size: {}", best.batch_size);
624 println!(
625 " Performance Score: {:.4}",
626 best.performance_score.unwrap_or(0.0)
627 );
628 }
629
630 println!("\nš Optimization Statistics:");
631 println!(" Total Trials: {}", self.optimization_history.len());
632
633 if !self.optimization_history.is_empty() {
634 let scores: Vec<f32> =
635 self.optimization_history.iter().map(|(_, m)| m.composite_score).collect();
636 let avg_score = scores.iter().sum::<f32>() / scores.len() as f32;
637 let max_score = scores.iter().fold(f32::NEG_INFINITY, |a, &b| a.max(b));
638 let min_score = scores.iter().fold(f32::INFINITY, |a, &b| a.min(b));
639
640 println!(" Average Score: {:.4}", avg_score);
641 println!(" Score Range: {:.4} - {:.4}", min_score, max_score);
642 println!(
643 " Improvement: {:.1}%",
644 ((max_score - min_score) / min_score * 100.0).max(0.0)
645 );
646 }
647 }
648
649 pub fn get_history(&self) -> &[(HyperparameterSample, PerformanceMetrics)] {
651 &self.optimization_history
652 }
653
654 pub fn get_pareto_front(&self) -> Option<&[HyperparameterSample]> {
656 self.multi_objective_opt.as_ref().map(|opt| opt.pareto_front.as_slice())
657 }
658}
659
660impl HyperparameterTuner {
662 pub fn optimize_amacp_for_transformers(max_trials: usize) -> Result<AMacPConfig> {
664 let space = HyperparameterSpace::for_transformers();
665 let task = OptimizationTask {
666 name: "Transformer Language Modeling".to_string(),
667 model_size: 125_000_000, dataset_size: 1_000_000,
669 max_epochs: 100,
670 convergence_threshold: 0.01,
671 target_metric: "perplexity".to_string(),
672 task_type: TaskType::LanguageModeling,
673 };
674
675 let mut tuner = HyperparameterTuner::new(OptimizerType::AMacP, space, task, max_trials);
676
677 let best_config = tuner.optimize()?;
678
679 Ok(AMacPConfig {
680 learning_rate: best_config.learning_rate,
681 beta1: best_config.beta1,
682 beta2: best_config.beta2,
683 weight_decay: best_config.weight_decay,
684 epsilon: best_config.epsilon,
685 ..AMacPConfig::for_transformers()
686 })
687 }
688
689 pub fn optimize_novograd_for_llms(max_trials: usize) -> Result<NovoGradConfig> {
691 let space = HyperparameterSpace::for_transformers();
692 let task = OptimizationTask {
693 name: "Large Language Model Training".to_string(),
694 model_size: 1_000_000_000, dataset_size: 10_000_000,
696 max_epochs: 50,
697 convergence_threshold: 0.005,
698 target_metric: "loss".to_string(),
699 task_type: TaskType::LanguageModeling,
700 };
701
702 let mut tuner = HyperparameterTuner::new(OptimizerType::NovoGrad, space, task, max_trials);
703
704 let best_config = tuner.optimize()?;
705
706 Ok(NovoGradConfig {
707 learning_rate: best_config.learning_rate,
708 beta1: best_config.beta1,
709 beta2: best_config.beta2,
710 weight_decay: best_config.weight_decay,
711 epsilon: best_config.epsilon,
712 ..NovoGradConfig::for_large_language_models()
713 })
714 }
715}
716
717#[cfg(test)]
718mod tests {
719 use super::*;
720
721 #[test]
722 fn test_hyperparameter_space_creation() {
723 let space = HyperparameterSpace::default();
724 assert_eq!(space.learning_rate, (1e-5, 1e-1));
725 assert!(space.log_scale_lr);
726
727 let transformer_space = HyperparameterSpace::for_transformers();
728 assert!(transformer_space.custom_params.contains_key("warmup_steps"));
729 }
730
731 #[test]
732 fn test_bayesian_optimizer_suggestion() {
733 let space = HyperparameterSpace::default();
734 let mut optimizer = BayesianOptimizer::new(space);
735
736 let sample = optimizer.suggest();
737 assert!(sample.learning_rate >= 1e-5 && sample.learning_rate <= 1e-1);
738 assert!(sample.beta1 >= 0.8 && sample.beta1 <= 0.999);
739 }
740
741 #[test]
742 fn test_bayesian_optimizer_update() {
743 let space = HyperparameterSpace::default();
744 let mut optimizer = BayesianOptimizer::new(space);
745
746 let sample = optimizer.suggest();
747 optimizer.update(sample, 0.85);
748
749 assert_eq!(optimizer.samples.len(), 1);
750 assert!(optimizer.get_best().is_some());
751 }
752
753 #[test]
754 fn test_hyperparameter_tuner_creation() {
755 let space = HyperparameterSpace::for_vision();
756 let task = OptimizationTask {
757 name: "Test Task".to_string(),
758 model_size: 1000,
759 dataset_size: 10000,
760 max_epochs: 10,
761 convergence_threshold: 0.01,
762 target_metric: "accuracy".to_string(),
763 task_type: TaskType::Classification,
764 };
765
766 let tuner = HyperparameterTuner::new(OptimizerType::Adam, space, task, 50);
767
768 assert_eq!(tuner.max_trials, 50);
769 assert_eq!(tuner.current_trial, 0);
770 }
771
772 #[test]
773 fn test_multi_objective_optimizer() {
774 let space = HyperparameterSpace::default();
775 let objectives = vec!["accuracy".to_string(), "speed".to_string()];
776 let weights = vec![0.7, 0.3];
777
778 let mut optimizer = MultiObjectiveOptimizer::new(space, objectives, weights);
779
780 let sample = HyperparameterSample {
781 learning_rate: 1e-3,
782 beta1: 0.9,
783 beta2: 0.999,
784 weight_decay: 1e-4,
785 epsilon: 1e-8,
786 batch_size: 64,
787 custom_params: HashMap::new(),
788 performance_score: None,
789 training_time: None,
790 memory_usage: None,
791 };
792
793 let metrics = PerformanceMetrics {
794 final_loss: 0.1,
795 convergence_epoch: 25,
796 training_time: Duration::from_secs(120),
797 memory_peak: 1024 * 1024,
798 stability_score: 0.9,
799 throughput: 1000.0,
800 gradient_norm_variance: 0.1,
801 composite_score: 0.85,
802 };
803
804 optimizer.update_multi_objective(sample, &metrics);
805 assert!(!optimizer.pareto_front.is_empty());
806 }
807
808 #[test]
809 fn test_performance_metrics_calculation() {
810 let space = HyperparameterSpace::default();
811 let task = OptimizationTask {
812 name: "Test".to_string(),
813 model_size: 1000,
814 dataset_size: 1000,
815 max_epochs: 10,
816 convergence_threshold: 0.01,
817 target_metric: "loss".to_string(),
818 task_type: TaskType::Regression,
819 };
820
821 let tuner = HyperparameterTuner::new(OptimizerType::Adam, space, task, 10);
822
823 let config = HyperparameterSample {
824 learning_rate: 1e-3,
825 beta1: 0.9,
826 beta2: 0.999,
827 weight_decay: 0.0,
828 epsilon: 1e-8,
829 batch_size: 32,
830 custom_params: HashMap::new(),
831 performance_score: None,
832 training_time: None,
833 memory_usage: None,
834 };
835
836 let metrics = tuner.simulate_training(&config);
837 assert!(metrics.is_ok());
838
839 let metrics = metrics.expect("Operation failed in test");
840 assert!(metrics.final_loss >= 0.0);
841 assert!(metrics.convergence_epoch > 0);
842 assert!(metrics.composite_score > 0.0);
843 }
844
845 #[test]
846 fn test_convenience_optimization_functions() {
847 let result = HyperparameterTuner::optimize_amacp_for_transformers(5);
850 assert!(result.is_ok());
851
852 let result = HyperparameterTuner::optimize_novograd_for_llms(5);
853 assert!(result.is_ok());
854 }
855}