Skip to main content

trustformers_optim/
hyperparameter_tuning.rs

1//! # Automated Hyperparameter Tuning Framework
2//!
3//! This module provides state-of-the-art automated hyperparameter optimization
4//! for all TrustformeRS optimizers using modern optimization techniques including
5//! Bayesian optimization, TPE (Tree-structured Parzen Estimator), and multi-objective
6//! optimization for the 2025 era.
7//!
8//! ## Key Features
9//!
10//! - **Bayesian Optimization**: Uses Gaussian processes for efficient hyperparameter search
11//! - **Multi-Objective Optimization**: Simultaneously optimizes convergence speed and stability
12//! - **Adaptive Sampling**: Intelligent exploration vs exploitation balance
13//! - **Transfer Learning**: Leverages previous optimization results across tasks
14//! - **Ensemble Methods**: Combines multiple tuning strategies for robustness
15//! - **Real-time Adaptation**: Adjusts hyperparameters during training based on performance
16//!
17//! ## Supported Optimizers
18//!
19//! Works with all TrustformeRS optimizers including aMacP, NovoGrad, Adam, AdamW,
20//! LAMB, Lion, Sophia, and 40+ other variants.
21
22// reason: research-stage module — reserved API/scaffolding fields and methods
23// retained intentionally for in-progress features; not yet on active call paths.
24#![allow(dead_code)]
25
26use crate::{amacp::AMacPConfig, novograd::NovoGradConfig};
27// Explicit import for .choose() method
28use scirs2_core::random::*; // Replaces rand - SciRS2 Integration Policy
29use serde::{Deserialize, Serialize};
30use std::collections::HashMap;
31use std::time::{Duration, Instant};
32use trustformers_core::errors::{Result, TrustformersError};
33
34/// Hyperparameter search space definition
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct HyperparameterSpace {
37    /// Learning rate bounds (min, max)
38    pub learning_rate: (f32, f32),
39    /// Beta1 momentum bounds
40    pub beta1: (f32, f32),
41    /// Beta2 momentum bounds
42    pub beta2: (f32, f32),
43    /// Weight decay bounds
44    pub weight_decay: (f32, f32),
45    /// Epsilon bounds
46    pub epsilon: (f32, f32),
47    /// Batch size options (discrete)
48    pub batch_sizes: Vec<usize>,
49    /// Whether to use logarithmic scaling for learning rate
50    pub log_scale_lr: bool,
51    /// Custom parameter ranges for specific optimizers
52    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    /// Create search space optimized for transformer models
72    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    /// Create search space for vision models
91    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    /// Create search space for scientific computing
105    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/// Individual hyperparameter configuration sample
122#[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    /// Performance score (higher is better)
132    pub performance_score: Option<f32>,
133    /// Training time in seconds
134    pub training_time: Option<f32>,
135    /// Memory usage in bytes
136    pub memory_usage: Option<usize>,
137}
138
139/// Training task definition for hyperparameter optimization
140#[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/// Performance metrics for hyperparameter evaluation
162#[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, // samples/second
170    pub gradient_norm_variance: f32,
171    pub composite_score: f32,
172}
173
174/// Bayesian optimization state using Tree-structured Parzen Estimator (TPE)
175#[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, // Fraction of samples to consider as "good"
185}
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    /// Suggest next hyperparameter configuration using TPE
202    pub fn suggest(&mut self) -> HyperparameterSample {
203        if self.samples.len() < self.n_startup_trials {
204            // Random sampling for initial trials
205            self.random_sample()
206        } else {
207            // TPE-based sampling
208            self.tpe_sample()
209        }
210    }
211
212    /// Update optimizer with performance result
213    pub fn update(&mut self, mut sample: HyperparameterSample, performance: f32) {
214        sample.performance_score = Some(performance);
215
216        // Update performance threshold as median of all samples
217        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        // Classify sample as good or poor
227        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        // Keep only top gamma fraction as good samples
236        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        // Import trait for .choose() method
250        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        // Simplified TPE implementation
284        // In practice, this would use kernel density estimation
285        // Import trait for .choose() method
286        let mut rng = thread_rng();
287
288        if self.good_samples.is_empty() {
289            return self.random_sample();
290        }
291
292        // Sample from good samples with some noise
293        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    /// Get best hyperparameters found so far
330    pub fn get_best(&self) -> Option<&HyperparameterSample> {
331        self.samples.iter().filter(|s| s.performance_score.is_some()).max_by(|a, b| {
332            // Safe: filter ensures performance_score is Some
333            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/// Multi-objective hyperparameter optimizer
342#[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    /// Update with multi-objective performance metrics
367    pub fn update_multi_objective(
368        &mut self,
369        sample: HyperparameterSample,
370        metrics: &PerformanceMetrics,
371    ) {
372        // Combine multiple objectives into single score
373        let mut weighted_score = 0.0;
374        weighted_score += self.weights[0] * (1.0 / (1.0 + metrics.final_loss)); // Minimize loss
375        weighted_score += self.weights[1] * (1.0 / (1.0 + metrics.convergence_epoch as f32)); // Faster convergence
376        if self.weights.len() > 2 {
377            weighted_score += self.weights[2] * metrics.stability_score; // Maximize stability
378        }
379        if self.weights.len() > 3 {
380            weighted_score += self.weights[3] * (1.0 / (1.0 + metrics.training_time.as_secs_f32()));
381            // Minimize time
382        }
383
384        self.bayesian_opt.update(sample, weighted_score);
385        self.update_pareto_front();
386    }
387
388    fn update_pareto_front(&mut self) {
389        // Simple Pareto front update (could be optimized)
390        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/// Complete hyperparameter tuning framework
414#[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    /// Create new hyperparameter tuner
440    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    /// Enable multi-objective optimization
462    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    /// Get next hyperparameter configuration to try
471    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    /// Evaluate hyperparameter configuration
481    pub fn evaluate_config(&mut self, config: HyperparameterSample) -> Result<PerformanceMetrics> {
482        let _start_time = Instant::now();
483
484        // Simulate training with these hyperparameters
485        let metrics = self.simulate_training(&config)?;
486
487        // Update optimizer with results
488        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        // Update best configuration
495        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        // Simulate realistic training behavior based on hyperparameters
509        let mut rng = thread_rng();
510
511        // Learning rate affects convergence speed and final performance
512        let lr_factor = if config.learning_rate > 1e-2 {
513            0.7_f64 // Too high LR - poor convergence
514        } else if config.learning_rate < 1e-5 {
515            0.8_f64 // Too low LR - slow convergence
516        } else {
517            1.0_f64 // Good LR range
518        };
519
520        // Beta parameters affect stability
521        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        // Weight decay affects generalization
525        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        // Composite score combining multiple factors
548        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    /// Run complete hyperparameter optimization
566    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            // Early stopping if we find excellent results
597            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    /// Get optimization history for analysis
650    pub fn get_history(&self) -> &[(HyperparameterSample, PerformanceMetrics)] {
651        &self.optimization_history
652    }
653
654    /// Get Pareto front for multi-objective optimization
655    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
660/// Convenience functions for common optimization tasks
661impl HyperparameterTuner {
662    /// Optimize aMacP hyperparameters for transformer training
663    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, // 125M parameters
668            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    /// Optimize NovoGrad hyperparameters for large language models
690    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, // 1B parameters
695            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        // Test that the convenience functions can be called without errors
848        // Note: In real tests, these would use mocked training functions
849        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}