1#![allow(dead_code)]
49
50use crate::adam::{Adam, AdamW};
51use crate::averaged_adam::AveragedAdam;
52use crate::lamb::LAMB;
53use crate::lion::Lion;
54use crate::sgd::SGD;
55
56use serde::{Deserialize, Serialize};
57use std::collections::HashMap;
58use std::time::{Duration, Instant};
59use trustformers_core::errors::{Result, TrustformersError};
60use trustformers_core::tensor::Tensor;
61use trustformers_core::traits::Optimizer;
62
63trait BenchmarkOptimizer: Optimizer {
80 fn state_memory_bytes(&self) -> Option<usize> {
81 None
82 }
83}
84
85impl BenchmarkOptimizer for Adam {
86 fn state_memory_bytes(&self) -> Option<usize> {
87 Some(crate::traits::StatefulOptimizer::memory_usage(self).total_bytes)
88 }
89}
90
91impl BenchmarkOptimizer for AdamW {
92 fn state_memory_bytes(&self) -> Option<usize> {
93 Some(crate::traits::StatefulOptimizer::memory_usage(self).total_bytes)
94 }
95}
96
97impl BenchmarkOptimizer for SGD {
98 fn state_memory_bytes(&self) -> Option<usize> {
99 Some(crate::traits::StatefulOptimizer::memory_usage(self).total_bytes)
100 }
101}
102
103impl BenchmarkOptimizer for AveragedAdam {
104 fn state_memory_bytes(&self) -> Option<usize> {
105 Some(crate::traits::StatefulOptimizer::memory_usage(self).total_bytes)
106 }
107}
108
109impl BenchmarkOptimizer for Lion {
110 fn state_memory_bytes(&self) -> Option<usize> {
111 Some(crate::traits::StatefulOptimizer::memory_usage(self).total_bytes)
112 }
113}
114
115impl BenchmarkOptimizer for LAMB {}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct ValidationConfig {
124 pub statistical_significance: bool,
126 pub memory_validation: bool,
128 pub regression_detection: bool,
130 pub convergence_analysis: bool,
132 pub distributed_validation: bool,
134 pub benchmark_iterations: usize,
136 pub confidence_level: f64,
138 pub max_regression_threshold: f64,
140 pub min_memory_efficiency: f64,
142}
143
144impl Default for ValidationConfig {
145 fn default() -> Self {
146 Self {
147 statistical_significance: true,
148 memory_validation: true,
149 regression_detection: true,
150 convergence_analysis: true,
151 distributed_validation: true,
152 benchmark_iterations: 100,
153 confidence_level: 0.95,
154 max_regression_threshold: 5.0, min_memory_efficiency: 75.0, }
157 }
158}
159
160pub struct PerformanceValidator {
162 config: ValidationConfig,
163 baseline_results: Option<HashMap<String, BenchmarkResult>>,
164 validation_history: Vec<ValidationSession>,
165 statistical_analyzer: StatisticalAnalyzer,
166 memory_analyzer: MemoryAnalyzer,
167 convergence_analyzer: ConvergenceAnalyzer,
168 regression_detector: RegressionDetector,
169}
170
171impl Default for PerformanceValidator {
172 fn default() -> Self {
173 Self::new()
174 }
175}
176
177impl PerformanceValidator {
178 pub fn new() -> Self {
180 Self {
181 config: ValidationConfig::default(),
182 baseline_results: None,
183 validation_history: Vec::new(),
184 statistical_analyzer: StatisticalAnalyzer::new(),
185 memory_analyzer: MemoryAnalyzer::new(),
186 convergence_analyzer: ConvergenceAnalyzer::new(),
187 regression_detector: RegressionDetector::new(),
188 }
189 }
190
191 pub fn with_statistical_significance(mut self, enabled: bool) -> Self {
193 self.config.statistical_significance = enabled;
194 self
195 }
196
197 pub fn with_memory_validation(mut self, enabled: bool) -> Self {
198 self.config.memory_validation = enabled;
199 self
200 }
201
202 pub fn with_regression_detection(mut self, enabled: bool) -> Self {
203 self.config.regression_detection = enabled;
204 self
205 }
206
207 pub fn with_convergence_analysis(mut self, enabled: bool) -> Self {
208 self.config.convergence_analysis = enabled;
209 self
210 }
211
212 pub fn with_benchmark_iterations(mut self, iterations: usize) -> Self {
213 self.config.benchmark_iterations = iterations;
214 self
215 }
216
217 pub fn run_comprehensive_validation(&mut self) -> Result<ValidationResults> {
219 let session_start = Instant::now();
220 let mut results = ValidationResults::new();
221
222 let correctness_results = self.validate_mathematical_correctness()?;
224 results.correctness_results = correctness_results;
225
226 let performance_results = self.run_performance_benchmarks()?;
228 results.performance_results = performance_results;
229
230 if self.config.memory_validation {
232 let memory_results = self.validate_memory_efficiency()?;
233 results.memory_results = Some(memory_results);
234 }
235
236 if self.config.convergence_analysis {
238 let convergence_results = self.analyze_convergence_properties()?;
239 results.convergence_results = Some(convergence_results);
240 }
241
242 if self.config.distributed_validation {
244 let distributed_results = self.validate_distributed_training()?;
245 results.distributed_results = Some(distributed_results);
246 }
247
248 if self.config.regression_detection && self.baseline_results.is_some() {
250 let regression_results =
251 self.detect_performance_regressions(&results.performance_results)?;
252 results.regression_results = Some(regression_results);
253 }
254
255 let total_time = session_start.elapsed();
256 results.total_validation_time = total_time;
257
258 let session = ValidationSession {
260 timestamp: std::time::SystemTime::now(),
261 config: self.config.clone(),
262 results: results.clone(),
263 };
264 self.validation_history.push(session);
265
266 Ok(results)
267 }
268
269 fn validate_mathematical_correctness(&mut self) -> Result<CorrectnessResults> {
271 let mut results = CorrectnessResults::new();
272
273 let test_cases = self.create_mathematical_test_cases()?;
275
276 for test_case in &test_cases {
277 let optimizer_results = self.test_optimizers_on_case(test_case)?;
279
280 for (optimizer_name, passed) in optimizer_results {
281 results.optimizer_correctness.insert(optimizer_name, passed);
282 }
283 }
284
285 let total_tests = results.optimizer_correctness.len();
287 let passed_tests = results.optimizer_correctness.values().filter(|&&x| x).count();
288
289 results.overall_correctness_rate = passed_tests as f64 / total_tests as f64;
290 results.passed_tests = passed_tests;
291 results.total_tests = total_tests;
292
293 Ok(results)
294 }
295
296 fn create_mathematical_test_cases(&self) -> Result<Vec<MathematicalTestCase>> {
297 let mut test_cases = Vec::new();
298
299 test_cases.push(MathematicalTestCase {
301 name: "Quadratic Function Convergence".to_string(),
302 description: "f(x) = 0.5 * x^T A x + b^T x".to_string(),
303 parameters: create_test_parameters(vec![10, 10])?,
304 gradients: create_quadratic_gradients(vec![10, 10])?,
305 expected_properties: vec![
306 MathematicalProperty::Convergence,
307 MathematicalProperty::MonotonicImprovement,
308 ],
309 tolerance: 1e-6,
310 });
311
312 test_cases.push(MathematicalTestCase {
314 name: "Convex Optimization".to_string(),
315 description: "Simple convex function with known minimum".to_string(),
316 parameters: create_test_parameters(vec![5, 5])?,
317 gradients: create_convex_gradients(vec![5, 5])?,
318 expected_properties: vec![
319 MathematicalProperty::Convergence,
320 MathematicalProperty::GlobalOptimum,
321 ],
322 tolerance: 1e-5,
323 });
324
325 test_cases.push(MathematicalTestCase {
327 name: "Sparse Gradient Handling".to_string(),
328 description: "Optimization with sparse gradients".to_string(),
329 parameters: create_test_parameters(vec![20, 20])?,
330 gradients: create_sparse_gradients(vec![20, 20], 0.1)?, expected_properties: vec![
332 MathematicalProperty::SparsityHandling,
333 MathematicalProperty::StableConvergence,
334 ],
335 tolerance: 1e-4,
336 });
337
338 Ok(test_cases)
339 }
340
341 fn test_optimizers_on_case(
342 &self,
343 test_case: &MathematicalTestCase,
344 ) -> Result<HashMap<String, bool>> {
345 let mut results = HashMap::new();
346
347 let adam_passed = self.test_optimizer_correctness(
349 "Adam",
350 || Box::new(Adam::new(0.001, (0.9, 0.999), 1e-8, 0.0)),
351 test_case,
352 )?;
353 results.insert("Adam".to_string(), adam_passed);
354
355 let adamw_passed = self.test_optimizer_correctness(
357 "AdamW",
358 || Box::new(AdamW::new(0.001, (0.9, 0.999), 1e-8, 0.01)),
359 test_case,
360 )?;
361 results.insert("AdamW".to_string(), adamw_passed);
362
363 let sgd_passed = self.test_optimizer_correctness(
365 "SGD",
366 || Box::new(SGD::new(0.01, 0.9, 0.0, false)),
367 test_case,
368 )?;
369 results.insert("SGD".to_string(), sgd_passed);
370
371 let avg_adam_passed = self.test_optimizer_correctness(
373 "AveragedAdam",
374 || Box::new(AveragedAdam::new(0.001, (0.9, 0.999), 1e-8, 0.01, 0.999)),
375 test_case,
376 )?;
377 results.insert("AveragedAdam".to_string(), avg_adam_passed);
378
379 Ok(results)
380 }
381
382 fn test_optimizer_correctness<F>(
383 &self,
384 _name: &str,
385 optimizer_factory: F,
386 test_case: &MathematicalTestCase,
387 ) -> Result<bool>
388 where
389 F: Fn() -> Box<dyn Optimizer>,
390 {
391 let mut optimizer = optimizer_factory();
392 let mut parameters = test_case.parameters.clone();
393 let initial_loss = self.compute_test_loss(¶meters, test_case)?;
394 let mut previous_loss = initial_loss;
395
396 let mut convergence_achieved = false;
397 let mut monotonic_improvement = true;
398 let max_iterations = 1000;
399
400 let mut zero_gradient_update_norms: Vec<f32> = Vec::new();
406
407 for iteration in 0..max_iterations {
408 let gradients = self.compute_test_gradients(¶meters, test_case, iteration)?;
410
411 for (param_name, gradient) in &gradients {
413 if let Some(param) = parameters.get_mut(param_name) {
414 let is_zero_gradient = gradient.norm()? == 0.0;
415 let before = if is_zero_gradient { Some(param.clone()) } else { None };
416
417 optimizer.zero_grad();
418 optimizer.update(param, gradient)?;
419 optimizer.step();
420
421 if let Some(before) = before {
422 zero_gradient_update_norms.push(param.sub(&before)?.norm()?);
423 }
424 }
425 }
426
427 let current_loss = self.compute_test_loss(¶meters, test_case)?;
429
430 if test_case
432 .expected_properties
433 .contains(&MathematicalProperty::MonotonicImprovement)
434 && current_loss > previous_loss + test_case.tolerance as f32
435 {
436 monotonic_improvement = false;
437 }
438
439 if (previous_loss - current_loss).abs() < test_case.tolerance as f32 {
441 convergence_achieved = true;
442 break;
443 }
444
445 previous_loss = current_loss;
446 }
447
448 let mut all_properties_satisfied = true;
450
451 for property in &test_case.expected_properties {
452 match property {
453 MathematicalProperty::Convergence => {
454 if !convergence_achieved {
455 all_properties_satisfied = false;
456 }
457 },
458 MathematicalProperty::MonotonicImprovement => {
459 if !monotonic_improvement {
460 all_properties_satisfied = false;
461 }
462 },
463 MathematicalProperty::GlobalOptimum => {
464 let final_loss = self.compute_test_loss(¶meters, test_case)?;
466 if final_loss > (test_case.tolerance * 10.0) as f32 {
467 all_properties_satisfied = false;
468 }
469 },
470 MathematicalProperty::SparsityHandling => {
471 if zero_gradient_update_norms.is_empty() {
478 all_properties_satisfied = false;
482 } else if !zero_gradient_update_norms.iter().all(|n| n.is_finite()) {
483 all_properties_satisfied = false;
484 } else {
485 let non_increasing = zero_gradient_update_norms
486 .windows(2)
487 .all(|pair| pair[1] <= pair[0] + test_case.tolerance as f32);
488 if !non_increasing {
489 all_properties_satisfied = false;
490 }
491 }
492 },
493 MathematicalProperty::StableConvergence => {
494 if !convergence_achieved {
496 all_properties_satisfied = false;
497 }
498 },
499 }
500 }
501
502 Ok(all_properties_satisfied)
503 }
504
505 fn compute_test_loss(
506 &self,
507 parameters: &HashMap<String, Tensor>,
508 test_case: &MathematicalTestCase,
509 ) -> Result<f32> {
510 match test_case.name.as_str() {
512 "Quadratic Function Convergence" => {
513 let mut total_loss = 0.0;
515 for tensor in parameters.values() {
516 let norm_squared = tensor.norm()?.powi(2);
517 total_loss += norm_squared * 0.5;
518 }
519 Ok(total_loss)
520 },
521 "Convex Optimization" => {
522 let mut total_loss = 0.0;
524 for tensor in parameters.values() {
525 let norm_squared = tensor.norm()?.powi(2);
526 total_loss += norm_squared;
527 }
528 Ok(total_loss)
529 },
530 "Sparse Gradient Handling" => {
531 let mut total_loss = 0.0;
533 for tensor in parameters.values() {
534 let norm_squared = tensor.norm()?.powi(2);
535 total_loss += norm_squared * 0.5;
536 }
537 Ok(total_loss)
538 },
539 _ => Ok(0.0),
540 }
541 }
542
543 fn compute_test_gradients(
544 &self,
545 parameters: &HashMap<String, Tensor>,
546 test_case: &MathematicalTestCase,
547 iteration: usize,
548 ) -> Result<HashMap<String, Tensor>> {
549 let mut gradients = HashMap::new();
550
551 match test_case.name.as_str() {
552 "Quadratic Function Convergence" => {
553 for (name, param) in parameters {
555 gradients.insert(name.clone(), param.clone());
556 }
557 },
558 "Convex Optimization" => {
559 for (name, param) in parameters {
561 let grad = param.scalar_mul(2.0)?;
562 gradients.insert(name.clone(), grad);
563 }
564 },
565 "Sparse Gradient Handling" => {
566 for (name, param) in parameters {
568 let grad = param.clone();
569 if iteration % 10 < 3 {
571 let shape = param.shape();
573 let _total_elements = shape.iter().product::<usize>();
574 let sparse_grad = Tensor::zeros(&shape)?;
575 gradients.insert(name.clone(), sparse_grad);
576 } else {
577 gradients.insert(name.clone(), grad);
578 }
579 }
580 },
581 _ => {
582 gradients = test_case.gradients.clone();
584 },
585 }
586
587 Ok(gradients)
588 }
589
590 fn run_performance_benchmarks(&mut self) -> Result<PerformanceBenchmarkResults> {
592 let mut results = PerformanceBenchmarkResults::new();
593
594 let scenarios = vec![
596 BenchmarkScenario {
597 name: "Small Model (1M params)".to_string(),
598 parameter_sizes: vec![1000, 1000], batch_size: 32,
600 iterations: self.config.benchmark_iterations,
601 },
602 BenchmarkScenario {
603 name: "Medium Model (10M params)".to_string(),
604 parameter_sizes: vec![3162, 3162], batch_size: 16,
606 iterations: self.config.benchmark_iterations / 2, },
608 BenchmarkScenario {
609 name: "Large Model (100M params)".to_string(),
610 parameter_sizes: vec![10000, 10000], batch_size: 8,
612 iterations: self.config.benchmark_iterations / 4,
613 },
614 ];
615
616 for scenario in scenarios {
617 let scenario_results = self.benchmark_scenario(&scenario)?;
618 results.scenario_results.push(scenario_results);
619 }
620
621 self.analyze_performance_trends(&mut results)?;
623
624 Ok(results)
625 }
626
627 fn benchmark_scenario(&self, scenario: &BenchmarkScenario) -> Result<ScenarioBenchmarkResult> {
628 let mut result = ScenarioBenchmarkResult {
629 scenario_name: scenario.name.clone(),
630 optimizer_results: HashMap::new(),
631 };
632
633 let optimizers_to_test = vec![
635 ("Adam", OptimizerType::Adam),
636 ("AdamW", OptimizerType::AdamW),
637 ("SGD", OptimizerType::SGD),
638 ("AveragedAdam", OptimizerType::AveragedAdam),
639 ("LAMB", OptimizerType::LAMB),
640 ("Lion", OptimizerType::Lion),
641 ];
642
643 for (name, optimizer_type) in optimizers_to_test {
644 let optimizer_result = self.benchmark_optimizer(name, optimizer_type, scenario)?;
645 result.optimizer_results.insert(name.to_string(), optimizer_result);
646 }
647
648 Ok(result)
649 }
650
651 fn benchmark_optimizer(
652 &self,
653 name: &str,
654 optimizer_type: OptimizerType,
655 scenario: &BenchmarkScenario,
656 ) -> Result<OptimizerBenchmarkResult> {
657 let mut step_times = Vec::new();
658
659 let mut optimizer = self.create_optimizer_instance(optimizer_type)?;
661
662 let mut parameters = create_test_parameters(scenario.parameter_sizes.clone())?;
664
665 for iteration in 0..scenario.iterations {
666 let gradients = create_benchmark_gradients(&scenario.parameter_sizes, iteration)?;
668
669 let step_start = Instant::now();
671
672 for (param_name, gradient) in &gradients {
674 if let Some(param) = parameters.get_mut(param_name) {
675 optimizer.zero_grad();
676 optimizer.update(param, gradient)?;
677 optimizer.step();
678 }
679 }
680
681 let step_time = step_start.elapsed();
682 step_times.push(step_time);
683 }
684
685 let avg_step_time = step_times.iter().sum::<Duration>() / step_times.len() as u32;
687 let min_step_time = step_times.iter().min().copied().unwrap_or(Duration::from_secs(0));
688 let max_step_time = step_times.iter().max().copied().unwrap_or(Duration::from_secs(0));
689
690 let state_memory_bytes = optimizer.state_memory_bytes();
705
706 let total_params: usize = scenario.parameter_sizes.iter().product();
708 let throughput = total_params as f64 / avg_step_time.as_secs_f64();
709
710 let baseline_step_time = self
716 .baseline_results
717 .as_ref()
718 .and_then(|baselines| baselines.get(name))
719 .map(|baseline| baseline.avg_step_time);
720 let statistical_metrics = if self.config.statistical_significance {
721 Some(self.statistical_analyzer.analyze(
722 &step_times,
723 self.config.confidence_level,
724 baseline_step_time,
725 )?)
726 } else {
727 None
728 };
729
730 Ok(OptimizerBenchmarkResult {
731 optimizer_name: name.to_string(),
732 avg_step_time,
733 min_step_time,
734 max_step_time,
735 throughput,
736 avg_memory_usage: state_memory_bytes,
737 statistical_metrics,
738 })
739 }
740
741 fn create_optimizer_instance(
742 &self,
743 optimizer_type: OptimizerType,
744 ) -> Result<Box<dyn BenchmarkOptimizer>> {
745 match optimizer_type {
746 OptimizerType::Adam => Ok(Box::new(Adam::new(0.001, (0.9, 0.999), 1e-8, 0.0))),
747 OptimizerType::AdamW => Ok(Box::new(AdamW::new(0.001, (0.9, 0.999), 1e-8, 0.01))),
748 OptimizerType::SGD => Ok(Box::new(SGD::new(0.01, 0.9, 0.0001, true))),
749 OptimizerType::AveragedAdam => Ok(Box::new(AveragedAdam::new(
750 0.001,
751 (0.9, 0.999),
752 1e-8,
753 0.01,
754 0.999,
755 ))),
756 OptimizerType::LAMB => Ok(Box::new(LAMB::new(0.001, (0.9, 0.999), 1e-6, 0.01))),
757 OptimizerType::Lion => Ok(Box::new(Lion::new(0.0001, (0.9, 0.99), 0.01))),
758 }
759 }
760
761 fn analyze_performance_trends(&self, results: &mut PerformanceBenchmarkResults) -> Result<()> {
762 let mut scaling_analysis = HashMap::new();
764
765 for optimizer_name in ["Adam", "AdamW", "SGD", "AveragedAdam", "LAMB", "Lion"] {
766 let mut throughputs = Vec::new();
767
768 for scenario_result in &results.scenario_results {
769 if let Some(optimizer_result) =
770 scenario_result.optimizer_results.get(optimizer_name)
771 {
772 throughputs.push(optimizer_result.throughput);
773 }
774 }
775
776 if throughputs.len() >= 2 {
777 let scaling_efficiency = self.compute_scaling_efficiency(&throughputs);
778 scaling_analysis.insert(optimizer_name.to_string(), scaling_efficiency);
779 }
780 }
781
782 results.scaling_analysis = scaling_analysis;
783 Ok(())
784 }
785
786 fn compute_scaling_efficiency(&self, throughputs: &[f64]) -> f64 {
787 if throughputs.len() < 2 {
788 return 1.0;
789 }
790
791 let first = throughputs[0];
794 let last = throughputs[throughputs.len() - 1];
795
796 last / first
798 }
799
800 fn validate_memory_efficiency(&mut self) -> Result<MemoryValidationResults> {
802 let mut results = MemoryValidationResults::new();
803
804 let memory_test_results = self.test_memory_efficiency_claims()?;
806 results.eight_bit_efficiency = memory_test_results;
807
808 let compression_results = self.test_gradient_compression_efficiency()?;
810 results.compression_efficiency = compression_results;
811
812 let optimization_results = self.test_memory_optimizations()?;
814 results.optimization_efficiency = optimization_results;
815
816 Ok(results)
817 }
818
819 fn test_memory_efficiency_claims(&self) -> Result<HashMap<String, f64>> {
826 use crate::quantized_advanced::Adam4bit;
827 use crate::traits::StatefulOptimizer;
828
829 let mut results = HashMap::new();
830
831 let shape = vec![64, 64];
832 let parameters = create_test_parameters(shape.clone())?;
833 let gradients = create_benchmark_gradients(&shape, 0)?;
834
835 let mut adam = Adam::new(0.001, (0.9, 0.999), 1e-8, 0.0);
836 let mut adam4bit = Adam4bit::new(0.001, 0.9, 0.999, 1e-8, 0.0);
837
838 for (name, parameter) in ¶meters {
839 let Some(gradient) = gradients.get(name) else {
840 continue;
841 };
842 let mut for_adam = parameter.clone();
843 let mut for_quantized = parameter.clone();
844 adam.update(&mut for_adam, gradient)?;
845 adam4bit.update(&mut for_quantized, gradient)?;
846 }
847
848 let adam_bytes = StatefulOptimizer::memory_usage(&adam).total_bytes;
849 let quantized_bytes = StatefulOptimizer::memory_usage(&adam4bit).total_bytes;
850
851 if adam_bytes == 0 {
852 return Err(TrustformersError::invalid_state(
853 "Adam reported a zero-byte state after a step; memory validation cannot proceed"
854 .to_string(),
855 ));
856 }
857
858 let reduction = (adam_bytes as f64 - quantized_bytes as f64) / adam_bytes as f64 * 100.0;
859 results.insert("Adam4bit".to_string(), reduction);
860 results.insert("Adam.state_bytes".to_string(), adam_bytes as f64);
861 results.insert("Adam4bit.state_bytes".to_string(), quantized_bytes as f64);
862
863 Ok(results)
864 }
865
866 fn test_gradient_compression_efficiency(&self) -> Result<HashMap<String, f64>> {
872 use crate::compression::{CompressionMethod, GradientCompressor};
873
874 let mut results = HashMap::new();
875
876 let shape = vec![1024];
879 let gradients = create_benchmark_gradients(&shape, 7)?;
880 let dense_bytes: usize = gradients
881 .values()
882 .map(|g| g.shape().iter().product::<usize>() * std::mem::size_of::<f32>())
883 .sum();
884 if dense_bytes == 0 {
885 return Err(TrustformersError::invalid_state(
886 "compression validation needs a non-empty gradient".to_string(),
887 ));
888 }
889
890 let methods = [
891 ("TopK", CompressionMethod::TopK { k: 102 }),
892 ("RandomK", CompressionMethod::RandomK { k: 102 }),
893 ("Threshold", CompressionMethod::Threshold { threshold: 0.5 }),
894 ("Quantization", CompressionMethod::Quantization { bits: 8 }),
895 ("SignSGD", CompressionMethod::SignSGD),
896 ];
897
898 for (name, method) in methods {
899 let mut compressor = GradientCompressor::new(method);
900 let compressed = compressor.compress(&gradients)?;
901
902 let payload: usize = compressed.values().map(|c| c.payload_bytes()).sum();
904
905 let restored = compressor.decompress(&compressed)?;
908 for (grad_name, tensor) in &restored {
909 let expected: usize =
910 gradients.get(grad_name).map(|g| g.shape().iter().product()).unwrap_or(0);
911 if tensor.data_f32()?.len() != expected {
912 return Err(TrustformersError::invalid_state(format!(
913 "{name} decompression returned the wrong element count for '{grad_name}'"
914 )));
915 }
916 }
917
918 let reduction = (dense_bytes as f64 - payload as f64) / dense_bytes as f64 * 100.0;
919 results.insert(name.to_string(), reduction);
920 }
921
922 Ok(results)
923 }
924
925 fn test_memory_optimizations(&self) -> Result<HashMap<String, f64>> {
932 let mut results = HashMap::new();
933
934 let dense = Tensor::from_vec(vec![0.5_f32; 4096], &[64, 64])?;
935 let half = match &dense {
936 Tensor::F32(array) => Tensor::F16(array.mapv(half::f16::from_f32)),
937 other => {
938 return Err(TrustformersError::invalid_state(format!(
939 "expected an f32 tensor, got {:?}",
940 other.dtype()
941 )))
942 },
943 };
944
945 let dense_bytes = dense.size_bytes();
946 if dense_bytes == 0 {
947 return Err(TrustformersError::invalid_state(
948 "mixed-precision validation needs a non-empty tensor".to_string(),
949 ));
950 }
951 let reduction =
952 (dense_bytes as f64 - half.size_bytes() as f64) / dense_bytes as f64 * 100.0;
953 results.insert("MixedPrecision".to_string(), reduction);
954
955 Ok(results)
956 }
957
958 fn analyze_convergence_properties(&mut self) -> Result<ConvergenceAnalysisResults> {
960 let mut results = ConvergenceAnalysisResults::new();
961
962 let convergence_tests = self.run_convergence_tests()?;
964 results.convergence_tests = convergence_tests;
965
966 let speed_analysis = self.analyze_convergence_speed(&results.convergence_tests)?;
968 results.speed_analysis = speed_analysis;
969
970 let stability_analysis = self.analyze_convergence_stability(&results.convergence_tests)?;
972 results.stability_analysis = stability_analysis;
973
974 Ok(results)
975 }
976
977 fn run_convergence_tests(&self) -> Result<HashMap<String, ConvergenceTestResult>> {
978 let mut results = HashMap::new();
979
980 let optimizers_to_test = vec![
981 ("Adam", OptimizerType::Adam),
982 ("AdamW", OptimizerType::AdamW),
983 ("AveragedAdam", OptimizerType::AveragedAdam),
984 ("SGD", OptimizerType::SGD),
985 ];
986
987 for (name, optimizer_type) in optimizers_to_test {
988 let convergence_result = self.test_optimizer_convergence(name, optimizer_type)?;
989 results.insert(name.to_string(), convergence_result);
990 }
991
992 Ok(results)
993 }
994
995 fn test_optimizer_convergence(
1003 &self,
1004 _name: &str,
1005 optimizer_type: OptimizerType,
1006 ) -> Result<ConvergenceTestResult> {
1007 let mut optimizer = self.create_optimizer_instance(optimizer_type)?;
1008 let mut parameters = create_test_parameters(vec![32, 32])?;
1009
1010 let evaluate = |params: &HashMap<String, Tensor>| -> Result<f32> {
1012 let mut total = 0.0_f32;
1013 let mut count = 0_usize;
1014 for tensor in params.values() {
1015 for value in tensor.data_f32()? {
1016 total += value * value;
1017 count += 1;
1018 }
1019 }
1020 Ok(if count == 0 { 0.0 } else { total / count as f32 })
1021 };
1022
1023 let initial_loss = evaluate(¶meters)?;
1024 let mut loss_history = Vec::new();
1025 let mut current_loss = initial_loss;
1026
1027 let max_iterations = 500;
1028 let mut converged = false;
1029 let mut convergence_iteration = max_iterations;
1030
1031 let mut names: Vec<String> = parameters.keys().cloned().collect();
1033 names.sort();
1034
1035 for iteration in 0..max_iterations {
1036 for name in &names {
1037 let Some(parameter) = parameters.get_mut(name) else {
1038 continue;
1039 };
1040 let values = parameter.data_f32()?;
1041 let gradient = Tensor::from_vec(
1042 values.iter().map(|v| 2.0 * v).collect::<Vec<f32>>(),
1043 ¶meter.shape(),
1044 )?;
1045 optimizer.update(parameter, &gradient)?;
1046 }
1047 optimizer.step();
1048
1049 current_loss = evaluate(¶meters)?;
1050 loss_history.push(current_loss);
1051
1052 if current_loss < 1e-4 && !converged {
1053 converged = true;
1054 convergence_iteration = iteration;
1055 break;
1056 }
1057 }
1058
1059 let convergence_rate = if converged {
1060 1.0 - (convergence_iteration as f64 / max_iterations as f64)
1061 } else {
1062 0.0
1063 };
1064
1065 let final_loss = current_loss;
1066 let loss_reduction = if initial_loss > 0.0 {
1067 (initial_loss - final_loss) / initial_loss
1068 } else {
1069 0.0
1070 };
1071
1072 Ok(ConvergenceTestResult {
1073 converged,
1074 convergence_iteration,
1075 convergence_rate,
1076 final_loss,
1077 loss_reduction,
1078 loss_history,
1079 })
1080 }
1081
1082 fn analyze_convergence_speed(
1087 &self,
1088 tests: &HashMap<String, ConvergenceTestResult>,
1089 ) -> Result<HashMap<String, f64>> {
1090 let mut results = HashMap::new();
1091 for (name, test) in tests {
1092 let Some(&initial) = test.loss_history.first() else {
1093 continue;
1094 };
1095 let target = initial * 0.01;
1096 let reached = test.loss_history.iter().position(|&loss| loss <= target);
1097 let total = test.loss_history.len().max(1) as f64;
1098 let speed = match reached {
1099 Some(index) => 1.0 - (index as f64 / total),
1100 None => 0.0,
1101 };
1102 results.insert(name.clone(), speed);
1103 }
1104 Ok(results)
1105 }
1106
1107 fn analyze_convergence_stability(
1111 &self,
1112 tests: &HashMap<String, ConvergenceTestResult>,
1113 ) -> Result<HashMap<String, f64>> {
1114 let mut results = HashMap::new();
1115 for (name, test) in tests {
1116 if test.loss_history.len() < 2 {
1117 continue;
1118 }
1119 let monotone = test
1120 .loss_history
1121 .windows(2)
1122 .filter(|pair| pair[1] <= pair[0] + f32::EPSILON)
1123 .count();
1124 let stability = monotone as f64 / (test.loss_history.len() - 1) as f64;
1125 results.insert(name.clone(), stability);
1126 }
1127 Ok(results)
1128 }
1129
1130 fn validate_distributed_training(&mut self) -> Result<DistributedValidationResults> {
1132 let mut results = DistributedValidationResults::new();
1133
1134 let scaling_results = self.test_distributed_scaling()?;
1136 results.scaling_results = scaling_results;
1137
1138 let communication_results = self.test_communication_efficiency()?;
1140 results.communication_results = communication_results;
1141
1142 let fault_tolerance_results = self.test_fault_tolerance()?;
1144 results.fault_tolerance_results = fault_tolerance_results;
1145
1146 Ok(results)
1147 }
1148
1149 fn test_distributed_scaling(&self) -> Result<HashMap<String, f64>> {
1156 use crate::zero::partition_parameters;
1157
1158 let mut results = HashMap::new();
1159 let parameters = create_test_parameters(vec![64, 64])?;
1160 let full_elements: usize =
1161 parameters.values().map(|t| t.shape().iter().product::<usize>()).sum();
1162 if full_elements == 0 {
1163 return Err(TrustformersError::invalid_state(
1164 "ZeRO validation needs a non-empty parameter set".to_string(),
1165 ));
1166 }
1167
1168 for world_size in [1_usize, 2, 4, 8] {
1169 let partitions = partition_parameters(¶meters, world_size, 0)?;
1170 let shard_elements: usize = partitions
1171 .values()
1172 .map(|p| p.local_shard.data_f32().map(|d| d.len()).unwrap_or(0))
1173 .sum();
1174 let reduction = 1.0 - (shard_elements as f64 / full_elements as f64);
1175 results.insert(
1176 format!("{world_size}-rank-parameter-memory-reduction"),
1177 reduction,
1178 );
1179 }
1180
1181 Ok(results)
1182 }
1183
1184 fn test_communication_efficiency(&self) -> Result<HashMap<String, f64>> {
1191 use crate::zero::{gather_shards, partition_parameters};
1192
1193 let mut results = HashMap::new();
1194 let parameters = create_test_parameters(vec![16, 16])?;
1195
1196 for world_size in [2_usize, 3, 5] {
1197 let mut exact = 0_usize;
1198 for (name, tensor) in ¶meters {
1199 let mut shards = Vec::with_capacity(world_size);
1200 for rank in 0..world_size {
1201 let partitions = partition_parameters(¶meters, world_size, rank)?;
1202 let shard =
1203 partitions.get(name).map(|p| p.local_shard.clone()).ok_or_else(|| {
1204 TrustformersError::invalid_state(format!(
1205 "no shard produced for '{name}'"
1206 ))
1207 })?;
1208 shards.push(shard);
1209 }
1210 let gathered = gather_shards(&shards, &tensor.shape())?;
1211 if gathered.data_f32()? == tensor.data_f32()? {
1212 exact += 1;
1213 }
1214 }
1215 let fraction = exact as f64 / parameters.len().max(1) as f64;
1216 results.insert(format!("{world_size}-rank-gather-exactness"), fraction);
1217 }
1218
1219 Ok(results)
1220 }
1221
1222 fn test_fault_tolerance(&self) -> Result<HashMap<String, bool>> {
1229 use crate::traits::StatefulOptimizer;
1230
1231 let mut results = HashMap::new();
1232
1233 let parameters = create_test_parameters(vec![8, 8])?;
1234 let gradients = create_benchmark_gradients(&[8, 8], 3)?;
1235
1236 let mut original = Adam::new(0.001, (0.9, 0.999), 1e-8, 0.0);
1237 let mut names: Vec<String> = parameters.keys().cloned().collect();
1238 names.sort();
1239 for name in &names {
1240 let (Some(parameter), Some(gradient)) = (parameters.get(name), gradients.get(name))
1241 else {
1242 continue;
1243 };
1244 let mut working = parameter.clone();
1245 original.update_named(name, &mut working, gradient)?;
1246 }
1247
1248 let checkpoint = StatefulOptimizer::state_dict(&original)?;
1249 let mut restored = Adam::new(0.1, (0.5, 0.5), 1e-3, 0.5);
1250 StatefulOptimizer::load_state_dict(&mut restored, checkpoint.clone())?;
1251 let round_trip = StatefulOptimizer::state_dict(&restored)?;
1252
1253 let mut identical = round_trip.len() == checkpoint.len();
1254 for (key, tensor) in &checkpoint {
1255 match round_trip.get(key) {
1256 Some(other) if other.data_f32()? == tensor.data_f32()? => {},
1257 _ => identical = false,
1258 }
1259 }
1260 results.insert("CheckpointRecovery".to_string(), identical);
1261
1262 Ok(results)
1263 }
1264
1265 fn detect_performance_regressions(
1267 &mut self,
1268 current_results: &PerformanceBenchmarkResults,
1269 ) -> Result<RegressionAnalysisResults> {
1270 let baseline = self.baseline_results.as_ref().ok_or_else(|| {
1271 TrustformersError::invalid_state(
1272 "baseline_results must be set before detecting regressions".to_string(),
1273 )
1274 })?;
1275 let mut results = RegressionAnalysisResults::new();
1276
1277 for scenario_result in ¤t_results.scenario_results {
1278 for (optimizer_name, current_benchmark) in &scenario_result.optimizer_results {
1279 if let Some(baseline_benchmark) = baseline.get(optimizer_name) {
1280 let regression = self.regression_detector.detect_regression(
1281 baseline_benchmark,
1282 current_benchmark,
1283 self.config.max_regression_threshold,
1284 )?;
1285
1286 if let Some(regression_info) = regression {
1287 results.regressions.push(regression_info);
1288 }
1289 }
1290 }
1291 }
1292
1293 Ok(results)
1294 }
1295
1296 pub fn generate_validation_report(&self, results: &ValidationResults) -> Result<String> {
1298 let mut report = String::new();
1299
1300 report.push_str("# TrustformeRS Optimization Performance Validation Report\\n");
1301 report.push_str("=====================================================\\n\\n");
1302
1303 report.push_str("## Executive Summary\\n");
1305 report.push_str(&format!(
1306 "- **Total Validation Time**: {:.2} seconds\\n",
1307 results.total_validation_time.as_secs_f64()
1308 ));
1309 report.push_str(&format!(
1310 "- **Correctness Tests**: {}/{} passed ({:.1}%)\\n",
1311 results.correctness_results.passed_tests,
1312 results.correctness_results.total_tests,
1313 results.correctness_results.overall_correctness_rate * 100.0
1314 ));
1315
1316 report.push_str("\\n## Performance Benchmark Summary\\n");
1318 for scenario_result in &results.performance_results.scenario_results {
1319 report.push_str(&format!("### {}\\n", scenario_result.scenario_name));
1320
1321 let mut sorted_optimizers: Vec<_> = scenario_result.optimizer_results.iter().collect();
1322 sorted_optimizers.sort_by_key(|a| a.1.avg_step_time);
1323
1324 for (name, result) in sorted_optimizers {
1325 report.push_str(&format!(
1326 "- **{}**: {:.2}ms/step, {:.1}M params/sec\\n",
1327 name,
1328 result.avg_step_time.as_secs_f64() * 1000.0,
1329 result.throughput / 1_000_000.0
1330 ));
1331 }
1332 }
1333
1334 if let Some(memory_results) = &results.memory_results {
1336 report.push_str("\\n## Memory Efficiency Validation\\n");
1337 for (optimizer, efficiency) in &memory_results.eight_bit_efficiency {
1338 report.push_str(&format!(
1339 "- **{}**: {:.1}% memory reduction\\n",
1340 optimizer, efficiency
1341 ));
1342 }
1343 }
1344
1345 if let Some(convergence_results) = &results.convergence_results {
1347 report.push_str("\\n## Convergence Analysis\\n");
1348 for (optimizer, test_result) in &convergence_results.convergence_tests {
1349 report.push_str(&format!(
1350 "- **{}**: {} (rate: {:.3}, reduction: {:.3})\\n",
1351 optimizer,
1352 if test_result.converged { "Converged" } else { "Did not converge" },
1353 test_result.convergence_rate,
1354 test_result.loss_reduction
1355 ));
1356 }
1357 }
1358
1359 if let Some(regression_results) = &results.regression_results {
1361 report.push_str("\\n## Performance Regression Analysis\\n");
1362 if regression_results.regressions.is_empty() {
1363 report.push_str("✅ No performance regressions detected\\n");
1364 } else {
1365 for regression in ®ression_results.regressions {
1366 report.push_str(&format!(
1367 "⚠️ **{}**: {:.1}% performance regression\\n",
1368 regression.optimizer_name, regression.regression_percentage
1369 ));
1370 }
1371 }
1372 }
1373
1374 report.push_str("\\n## Validation Status: ✅ COMPLETE\\n");
1375
1376 Ok(report)
1377 }
1378
1379 pub fn set_baseline(&mut self, results: HashMap<String, BenchmarkResult>) {
1381 self.baseline_results = Some(results);
1382 }
1383}
1384
1385#[derive(Debug, Clone, Serialize, Deserialize)]
1388pub struct ValidationResults {
1389 pub total_validation_time: Duration,
1390 pub correctness_results: CorrectnessResults,
1391 pub performance_results: PerformanceBenchmarkResults,
1392 pub memory_results: Option<MemoryValidationResults>,
1393 pub convergence_results: Option<ConvergenceAnalysisResults>,
1394 pub distributed_results: Option<DistributedValidationResults>,
1395 pub regression_results: Option<RegressionAnalysisResults>,
1396}
1397
1398impl Default for ValidationResults {
1399 fn default() -> Self {
1400 Self::new()
1401 }
1402}
1403
1404impl ValidationResults {
1405 pub fn new() -> Self {
1406 Self {
1407 total_validation_time: Duration::from_secs(0),
1408 correctness_results: CorrectnessResults::new(),
1409 performance_results: PerformanceBenchmarkResults::new(),
1410 memory_results: None,
1411 convergence_results: None,
1412 distributed_results: None,
1413 regression_results: None,
1414 }
1415 }
1416}
1417
1418#[derive(Debug, Clone, Serialize, Deserialize)]
1419pub struct ValidationSession {
1420 pub timestamp: std::time::SystemTime,
1421 pub config: ValidationConfig,
1422 pub results: ValidationResults,
1423}
1424
1425#[derive(Debug, Clone, Serialize, Deserialize)]
1426pub struct CorrectnessResults {
1427 pub optimizer_correctness: HashMap<String, bool>,
1428 pub overall_correctness_rate: f64,
1429 pub passed_tests: usize,
1430 pub total_tests: usize,
1431}
1432
1433impl Default for CorrectnessResults {
1434 fn default() -> Self {
1435 Self::new()
1436 }
1437}
1438
1439impl CorrectnessResults {
1440 pub fn new() -> Self {
1441 Self {
1442 optimizer_correctness: HashMap::new(),
1443 overall_correctness_rate: 0.0,
1444 passed_tests: 0,
1445 total_tests: 0,
1446 }
1447 }
1448}
1449
1450#[derive(Debug, Clone, Serialize, Deserialize)]
1451pub struct PerformanceBenchmarkResults {
1452 pub scenario_results: Vec<ScenarioBenchmarkResult>,
1453 pub scaling_analysis: HashMap<String, f64>,
1454}
1455
1456impl Default for PerformanceBenchmarkResults {
1457 fn default() -> Self {
1458 Self::new()
1459 }
1460}
1461
1462impl PerformanceBenchmarkResults {
1463 pub fn new() -> Self {
1464 Self {
1465 scenario_results: Vec::new(),
1466 scaling_analysis: HashMap::new(),
1467 }
1468 }
1469}
1470
1471#[derive(Debug, Clone, Serialize, Deserialize)]
1472pub struct ScenarioBenchmarkResult {
1473 pub scenario_name: String,
1474 pub optimizer_results: HashMap<String, OptimizerBenchmarkResult>,
1475}
1476
1477#[derive(Debug, Clone, Serialize, Deserialize)]
1478pub struct OptimizerBenchmarkResult {
1479 pub optimizer_name: String,
1480 pub avg_step_time: Duration,
1481 pub min_step_time: Duration,
1482 pub max_step_time: Duration,
1483 pub throughput: f64,
1484 pub avg_memory_usage: Option<usize>,
1496 pub statistical_metrics: Option<StatisticalMetrics>,
1497}
1498
1499#[derive(Debug, Clone, Serialize, Deserialize)]
1500pub struct StatisticalMetrics {
1501 pub mean: Duration,
1502 pub std_dev: Duration,
1503 pub confidence_interval_lower: Duration,
1504 pub confidence_interval_upper: Duration,
1505 pub p_value: Option<f64>,
1518}
1519
1520#[derive(Debug, Clone, Serialize, Deserialize)]
1521pub struct MemoryValidationResults {
1522 pub eight_bit_efficiency: HashMap<String, f64>,
1523 pub compression_efficiency: HashMap<String, f64>,
1524 pub optimization_efficiency: HashMap<String, f64>,
1525}
1526
1527impl Default for MemoryValidationResults {
1528 fn default() -> Self {
1529 Self::new()
1530 }
1531}
1532
1533impl MemoryValidationResults {
1534 pub fn new() -> Self {
1535 Self {
1536 eight_bit_efficiency: HashMap::new(),
1537 compression_efficiency: HashMap::new(),
1538 optimization_efficiency: HashMap::new(),
1539 }
1540 }
1541}
1542
1543#[derive(Debug, Clone, Serialize, Deserialize)]
1544pub struct ConvergenceAnalysisResults {
1545 pub convergence_tests: HashMap<String, ConvergenceTestResult>,
1546 pub speed_analysis: HashMap<String, f64>,
1547 pub stability_analysis: HashMap<String, f64>,
1548}
1549
1550impl Default for ConvergenceAnalysisResults {
1551 fn default() -> Self {
1552 Self::new()
1553 }
1554}
1555
1556impl ConvergenceAnalysisResults {
1557 pub fn new() -> Self {
1558 Self {
1559 convergence_tests: HashMap::new(),
1560 speed_analysis: HashMap::new(),
1561 stability_analysis: HashMap::new(),
1562 }
1563 }
1564}
1565
1566#[derive(Debug, Clone, Serialize, Deserialize)]
1567pub struct ConvergenceTestResult {
1568 pub converged: bool,
1569 pub convergence_iteration: usize,
1570 pub convergence_rate: f64,
1571 pub final_loss: f32,
1572 pub loss_reduction: f32,
1573 pub loss_history: Vec<f32>,
1574}
1575
1576#[derive(Debug, Clone, Serialize, Deserialize)]
1577pub struct DistributedValidationResults {
1578 pub scaling_results: HashMap<String, f64>,
1579 pub communication_results: HashMap<String, f64>,
1580 pub fault_tolerance_results: HashMap<String, bool>,
1581}
1582
1583impl Default for DistributedValidationResults {
1584 fn default() -> Self {
1585 Self::new()
1586 }
1587}
1588
1589impl DistributedValidationResults {
1590 pub fn new() -> Self {
1591 Self {
1592 scaling_results: HashMap::new(),
1593 communication_results: HashMap::new(),
1594 fault_tolerance_results: HashMap::new(),
1595 }
1596 }
1597}
1598
1599#[derive(Debug, Clone, Serialize, Deserialize)]
1600pub struct RegressionAnalysisResults {
1601 pub regressions: Vec<RegressionInfo>,
1602}
1603
1604impl Default for RegressionAnalysisResults {
1605 fn default() -> Self {
1606 Self::new()
1607 }
1608}
1609
1610impl RegressionAnalysisResults {
1611 pub fn new() -> Self {
1612 Self {
1613 regressions: Vec::new(),
1614 }
1615 }
1616}
1617
1618#[derive(Debug, Clone, Serialize, Deserialize)]
1619pub struct RegressionInfo {
1620 pub optimizer_name: String,
1621 pub metric_name: String,
1622 pub baseline_value: f64,
1623 pub current_value: f64,
1624 pub regression_percentage: f64,
1625}
1626
1627#[derive(Debug, Clone)]
1628pub struct MathematicalTestCase {
1629 pub name: String,
1630 pub description: String,
1631 pub parameters: HashMap<String, Tensor>,
1632 pub gradients: HashMap<String, Tensor>,
1633 pub expected_properties: Vec<MathematicalProperty>,
1634 pub tolerance: f64,
1635}
1636
1637#[derive(Debug, Clone, PartialEq)]
1638pub enum MathematicalProperty {
1639 Convergence,
1640 MonotonicImprovement,
1641 GlobalOptimum,
1642 SparsityHandling,
1643 StableConvergence,
1644}
1645
1646#[derive(Debug, Clone)]
1647pub struct BenchmarkScenario {
1648 pub name: String,
1649 pub parameter_sizes: Vec<usize>,
1650 pub batch_size: usize,
1651 pub iterations: usize,
1652}
1653
1654#[derive(Debug, Clone)]
1655pub enum OptimizerType {
1656 Adam,
1657 AdamW,
1658 SGD,
1659 AveragedAdam,
1660 LAMB,
1661 Lion,
1662}
1663
1664#[derive(Debug, Clone, Serialize, Deserialize)]
1665pub struct BenchmarkResult {
1666 pub avg_step_time: Duration,
1667 pub throughput: f64,
1668 pub memory_usage: f64,
1669}
1670
1671pub struct StatisticalAnalyzer;
1673
1674impl Default for StatisticalAnalyzer {
1675 fn default() -> Self {
1676 Self::new()
1677 }
1678}
1679
1680impl StatisticalAnalyzer {
1681 pub fn new() -> Self {
1682 Self
1683 }
1684
1685 pub fn analyze(
1693 &self,
1694 step_times: &[Duration],
1695 confidence_level: f64,
1696 target_step_time: Option<Duration>,
1697 ) -> Result<StatisticalMetrics> {
1698 let times_f64: Vec<f64> = step_times.iter().map(|d| d.as_secs_f64()).collect();
1699
1700 let mean_f64 = trustformers_core::statistics::mean(×_f64).ok_or_else(|| {
1701 TrustformersError::invalid_state(
1702 "StatisticalAnalyzer::analyze requires at least one step time".to_string(),
1703 )
1704 })?;
1705 let sample_std_f64 = trustformers_core::statistics::sample_std_dev(×_f64);
1710 let std_dev_f64 = sample_std_f64.unwrap_or(0.0);
1711
1712 let z_score = if confidence_level >= 0.99 {
1714 2.576
1715 } else if confidence_level >= 0.95 {
1716 1.96
1717 } else {
1718 1.645
1719 };
1720 let margin_of_error = z_score * std_dev_f64 / (times_f64.len() as f64).sqrt();
1721
1722 let p_value = match (target_step_time, sample_std_f64) {
1729 (Some(target), Some(sample_std)) => {
1730 let standard_error = sample_std / (times_f64.len() as f64).sqrt();
1731 let t_statistic = (mean_f64 - target.as_secs_f64()) / standard_error;
1732 let degrees_of_freedom = times_f64.len() as f64 - 1.0;
1733 trustformers_core::statistics::student_t_two_sided_p_value(
1734 t_statistic,
1735 degrees_of_freedom,
1736 )
1737 },
1738 _ => None,
1739 };
1740
1741 Ok(StatisticalMetrics {
1742 mean: Duration::from_secs_f64(mean_f64),
1743 std_dev: Duration::from_secs_f64(std_dev_f64),
1744 confidence_interval_lower: Duration::from_secs_f64(
1745 (mean_f64 - margin_of_error).max(0.0),
1746 ),
1747 confidence_interval_upper: Duration::from_secs_f64(mean_f64 + margin_of_error),
1748 p_value,
1749 })
1750 }
1751}
1752
1753pub struct MemoryAnalyzer;
1755
1756impl Default for MemoryAnalyzer {
1757 fn default() -> Self {
1758 Self::new()
1759 }
1760}
1761
1762impl MemoryAnalyzer {
1763 pub fn new() -> Self {
1764 Self
1765 }
1766}
1767
1768pub struct ConvergenceAnalyzer;
1770
1771impl Default for ConvergenceAnalyzer {
1772 fn default() -> Self {
1773 Self::new()
1774 }
1775}
1776
1777impl ConvergenceAnalyzer {
1778 pub fn new() -> Self {
1779 Self
1780 }
1781}
1782
1783pub struct RegressionDetector;
1785
1786impl Default for RegressionDetector {
1787 fn default() -> Self {
1788 Self::new()
1789 }
1790}
1791
1792impl RegressionDetector {
1793 pub fn new() -> Self {
1794 Self
1795 }
1796
1797 pub fn detect_regression(
1798 &self,
1799 baseline: &BenchmarkResult,
1800 current: &OptimizerBenchmarkResult,
1801 threshold_percentage: f64,
1802 ) -> Result<Option<RegressionInfo>> {
1803 let baseline_time = baseline.avg_step_time.as_secs_f64();
1804 let current_time = current.avg_step_time.as_secs_f64();
1805
1806 let regression_percentage = ((current_time - baseline_time) / baseline_time) * 100.0;
1807
1808 if regression_percentage > threshold_percentage {
1809 Ok(Some(RegressionInfo {
1810 optimizer_name: current.optimizer_name.clone(),
1811 metric_name: "avg_step_time".to_string(),
1812 baseline_value: baseline_time,
1813 current_value: current_time,
1814 regression_percentage,
1815 }))
1816 } else {
1817 Ok(None)
1818 }
1819 }
1820}
1821
1822fn create_test_parameters(sizes: Vec<usize>) -> Result<HashMap<String, Tensor>> {
1825 let mut parameters = HashMap::new();
1826
1827 for (i, &size) in sizes.iter().enumerate() {
1828 let param_name = format!("param_{}", i);
1829 let tensor = Tensor::randn(&[size])?;
1830 parameters.insert(param_name, tensor);
1831 }
1832
1833 Ok(parameters)
1834}
1835
1836fn create_quadratic_gradients(sizes: Vec<usize>) -> Result<HashMap<String, Tensor>> {
1837 let mut gradients = HashMap::new();
1838
1839 for (i, &size) in sizes.iter().enumerate() {
1840 let grad_name = format!("param_{}", i);
1841 let gradient = Tensor::randn(&[size])?;
1843 gradients.insert(grad_name, gradient);
1844 }
1845
1846 Ok(gradients)
1847}
1848
1849fn create_convex_gradients(sizes: Vec<usize>) -> Result<HashMap<String, Tensor>> {
1850 let mut gradients = HashMap::new();
1851
1852 for (i, &size) in sizes.iter().enumerate() {
1853 let grad_name = format!("param_{}", i);
1854 let gradient = Tensor::randn(&[size])?.scalar_mul(2.0)?; gradients.insert(grad_name, gradient);
1856 }
1857
1858 Ok(gradients)
1859}
1860
1861fn create_sparse_gradients(sizes: Vec<usize>, _sparsity: f32) -> Result<HashMap<String, Tensor>> {
1862 let mut gradients = HashMap::new();
1863
1864 for (i, &size) in sizes.iter().enumerate() {
1865 let grad_name = format!("param_{}", i);
1866 let _gradient = Tensor::randn(&[size])?;
1867
1868 let sparse_gradient = Tensor::zeros(&[size])?; gradients.insert(grad_name, sparse_gradient);
1872 }
1873
1874 Ok(gradients)
1875}
1876
1877fn create_benchmark_gradients(
1878 sizes: &[usize],
1879 iteration: usize,
1880) -> Result<HashMap<String, Tensor>> {
1881 let mut gradients = HashMap::new();
1882
1883 let scale = 0.1 / (1.0 + iteration as f32 * 0.01); for (i, &size) in sizes.iter().enumerate() {
1886 let grad_name = format!("param_{}", i);
1887 let gradient = Tensor::randn(&[size])?.scalar_mul(scale)?;
1888 gradients.insert(grad_name, gradient);
1889 }
1890
1891 Ok(gradients)
1892}
1893
1894#[cfg(test)]
1895#[path = "performance_validation_tests.rs"]
1896mod performance_validation_tests;