1use crate::error::{OptimError, Result};
7use crate::utils::{scalar_or, try_scalar};
8use scirs2_core::ndarray::{Array, Dimension, ScalarOperand};
9use scirs2_core::numeric::Float;
10use std::collections::HashMap;
11use std::fmt::Debug;
12
13mod adaptive_tuner;
14mod optimization_state;
15
16pub use adaptive_tuner::{
17 tuned_value_as_f64, AdaptiveTuner, TunableParameter, TuningObservation, TuningOutcome,
18 TuningRecord, TuningStrategy,
19};
20pub use optimization_state::{
21 HardwareOptimizerKind, HardwareStepReport, OptimizationState, DEFAULT_BASE_LEARNING_RATE,
22};
23
24use optimization_state::accumulation_steps_for;
25
26#[derive(Debug, Clone, PartialEq)]
28pub enum HardwarePlatform {
29 CPU {
31 cores: usize,
33 cache_size: usize,
35 simd_support: SIMDSupport,
37 },
38 GPU {
40 memory: usize,
42 compute_units: usize,
44 memory_bandwidth: f64,
46 architecture: GPUArchitecture,
48 },
49 TPU {
51 version: TPUVersion,
53 matrix_units: usize,
55 hbm_size: usize,
57 },
58 Edge {
60 power_budget: f64,
62 memory_limit: usize,
64 quantization_support: QuantizationSupport,
66 },
67 Distributed {
69 num_nodes: usize,
71 network_bandwidth: f64,
73 node_hardware: Box<HardwarePlatform>,
75 },
76}
77
78#[derive(Debug, Clone, Copy, PartialEq)]
80pub enum SIMDSupport {
81 None,
83 SSE,
85 AVX,
87 AVX512,
89 NEON,
91}
92
93#[derive(Debug, Clone, Copy, PartialEq)]
95pub enum GPUArchitecture {
96 Pascal,
98 Volta,
100 Turing,
102 Ampere,
104 Hopper,
106 RDNA,
108 RDNA2,
110 CDNA,
112 XeHPG,
114 XeHPC,
116}
117
118#[derive(Debug, Clone, Copy, PartialEq)]
120pub enum TPUVersion {
121 V1,
123 V2,
125 V3,
127 V4,
129 V5,
131}
132
133#[derive(Debug, Clone, Copy, PartialEq)]
135pub enum QuantizationSupport {
136 None,
138 Int8,
140 FP16,
142 BF16,
144 Int4,
146 Mixed,
148}
149
150#[derive(Debug, Clone)]
152pub struct HardwareOptimizationConfig<A: Float> {
153 pub batch_size: usize,
155 pub memory_strategy: MemoryStrategy,
157 pub parallelization: ParallelizationStrategy,
159 pub precision: PrecisionStrategy,
161 pub optimizer_params: HashMap<String, A>,
163 pub communication: Option<CommunicationStrategy>,
165}
166
167#[derive(Debug, Clone)]
169pub enum MemoryStrategy {
170 Standard,
172 GradientAccumulation {
174 accumulation_steps: usize,
176 },
177 GradientCheckpointing {
179 checkpoint_ratio: f64,
181 },
182 ParameterSharding {
184 shard_size: usize,
186 },
187 CPUOffloading {
189 offload_ratio: f64,
191 },
192 Mixed {
194 strategies: Vec<MemoryStrategy>,
196 strategy_weights: Vec<f64>,
198 },
199}
200
201#[derive(Debug, Clone)]
203pub enum ParallelizationStrategy {
204 SingleThread,
206 DataParallel {
208 num_workers: usize,
210 },
211 ModelParallel {
213 partition_strategy: PartitionStrategy,
215 },
216 Pipeline {
218 pipeline_stages: usize,
220 micro_batches: usize,
222 },
223 TensorParallel {
225 tensor_parallel_size: usize,
227 },
228 Hybrid {
230 data_parallel: usize,
232 model_parallel: usize,
234 pipeline_parallel: usize,
236 },
237}
238
239#[derive(Debug, Clone)]
241pub enum PartitionStrategy {
242 LayerWise,
244 DepthWise,
246 WidthWise,
248 Custom {
250 partition_points: Vec<usize>,
252 },
253}
254
255#[derive(Debug, Clone)]
257pub enum PrecisionStrategy {
258 FP32,
260 FP16,
262 BF16,
264 Mixed {
266 forward_precision: String,
268 backward_precision: String,
270 loss_scaling: bool,
272 },
273 Quantized {
275 weight_bits: u8,
277 activation_bits: u8,
279 quantization_method: String,
281 },
282}
283
284#[derive(Debug, Clone)]
286pub enum CommunicationStrategy {
287 AllReduce {
289 algorithm: AllReduceAlgorithm,
291 compression: bool,
293 },
294 ParameterServer {
296 num_servers: usize,
298 update_frequency: usize,
300 },
301 Gossip {
303 neighbors: usize,
305 gossip_frequency: usize,
307 },
308 Hierarchical {
310 local_groups: usize,
312 inter_group_strategy: Box<CommunicationStrategy>,
314 },
315}
316
317#[derive(Debug, Clone)]
319pub enum AllReduceAlgorithm {
320 Ring,
322 Tree,
324 Butterfly,
326 HalvingDoubling,
328}
329
330#[derive(Debug)]
332pub struct HardwareAwareOptimizer<A: Float + 'static, D: Dimension + 'static> {
333 platform: HardwarePlatform,
335 config: HardwareOptimizationConfig<A>,
337 profiler: PerformanceProfiler<A>,
339 resource_monitor: ResourceMonitor<A>,
341 adaptive_tuner: AdaptiveTuner<A>,
343 current_state: OptimizationState<A, D>,
345}
346
347#[derive(Debug)]
349pub struct PerformanceProfiler<A: Float> {
350 computation_times: Vec<A>,
352 memory_usage: Vec<usize>,
354 energy_consumption: Vec<A>,
356 throughput: Vec<A>,
358}
359
360#[derive(Debug)]
362pub struct ResourceMonitor<A: Float> {
363 current_memory: usize,
365 peak_memory: usize,
367 cpu_utilization: A,
369 power_consumption: A,
371 temperature: A,
373}
374
375impl<
376 A: Float
377 + ScalarOperand
378 + Debug
379 + std::iter::Sum
380 + for<'a> std::iter::Sum<&'a A>
381 + Send
382 + Sync
383 + 'static,
384 D: Dimension + 'static,
385 > HardwareAwareOptimizer<A, D>
386{
387 pub fn new(platform: HardwarePlatform, initialparameters: Array<A, D>) -> Self {
390 Self::new_with_learning_rate(
391 platform,
392 initialparameters,
393 scalar_or(DEFAULT_BASE_LEARNING_RATE, A::zero()),
394 )
395 }
396
397 pub fn new_with_learning_rate(
405 platform: HardwarePlatform,
406 initialparameters: Array<A, D>,
407 base_learning_rate: A,
408 ) -> Self {
409 let config = Self::default_config_for_platform(&platform);
410 let profiler = PerformanceProfiler::new();
411 let resource_monitor = ResourceMonitor::new();
412 let adaptive_tuner = AdaptiveTuner::new();
413
414 let kind = HardwareOptimizerKind::recommend_for(&platform, &config);
415 let current_state = OptimizationState::new(
416 initialparameters,
417 kind,
418 base_learning_rate,
419 accumulation_steps_for(&config.memory_strategy),
420 );
421
422 Self {
423 platform,
424 config,
425 profiler,
426 resource_monitor,
427 adaptive_tuner,
428 current_state,
429 }
430 }
431
432 pub fn optimize_for_hardware(&mut self) -> Result<()> {
434 match self.platform.clone() {
435 HardwarePlatform::CPU {
436 cores,
437 cache_size,
438 simd_support,
439 } => {
440 self.optimize_for_cpu(cores, cache_size, simd_support)?;
441 }
442 HardwarePlatform::GPU {
443 memory,
444 compute_units,
445 memory_bandwidth,
446 architecture,
447 } => {
448 self.optimize_for_gpu(memory, compute_units, memory_bandwidth, architecture)?;
449 }
450 HardwarePlatform::TPU {
451 version,
452 matrix_units,
453 hbm_size,
454 } => {
455 self.optimize_for_tpu(version, matrix_units, hbm_size)?;
456 }
457 HardwarePlatform::Edge {
458 power_budget,
459 memory_limit,
460 quantization_support,
461 } => {
462 self.optimize_for_edge(power_budget, memory_limit, quantization_support)?;
463 }
464 HardwarePlatform::Distributed {
465 num_nodes,
466 network_bandwidth,
467 node_hardware,
468 } => {
469 self.optimize_for_distributed(num_nodes, network_bandwidth, &node_hardware)?;
470 }
471 }
472 self.sync_optimizer_with_config();
473 Ok(())
474 }
475
476 fn sync_optimizer_with_config(&mut self) {
489 self.current_state
490 .set_accumulation_steps(accumulation_steps_for(&self.config.memory_strategy));
491
492 if self.current_state.step_count() == 0 {
493 let recommended = self.recommended_optimizer_kind();
494 if recommended != self.current_state.optimizer_kind() {
495 self.current_state.rebuild_optimizer(recommended);
496 }
497 }
498 }
499
500 pub fn recommended_optimizer_kind(&self) -> HardwareOptimizerKind {
502 HardwareOptimizerKind::recommend_for(&self.platform, &self.config)
503 }
504
505 pub fn adopt_recommended_optimizer(&mut self) -> HardwareOptimizerKind {
510 let recommended = self.recommended_optimizer_kind();
511 if recommended != self.current_state.optimizer_kind() {
512 self.current_state.rebuild_optimizer(recommended);
513 }
514 recommended
515 }
516
517 pub fn step(&mut self, gradients: &Array<A, D>) -> Result<HardwareStepReport<A>> {
523 self.current_state.step(gradients)
524 }
525
526 pub fn parameters(&self) -> &Array<A, D> {
528 self.current_state.parameters()
529 }
530
531 pub fn optimization_state(&self) -> &OptimizationState<A, D> {
534 &self.current_state
535 }
536
537 pub fn optimization_state_mut(&mut self) -> &mut OptimizationState<A, D> {
540 &mut self.current_state
541 }
542
543 pub fn tuner(&self) -> &AdaptiveTuner<A> {
546 &self.adaptive_tuner
547 }
548
549 pub fn tuner_mut(&mut self) -> &mut AdaptiveTuner<A> {
552 &mut self.adaptive_tuner
553 }
554
555 pub fn tune_parameters<F>(&mut self, evaluate: F) -> Result<TuningOutcome<A>>
564 where
565 F: FnMut(&HashMap<String, A>) -> Result<TuningObservation<A>>,
566 {
567 let outcome = self.adaptive_tuner.tune(evaluate)?;
568
569 if let Some(batch_size) = tuned_value_as_f64(&outcome.best_parameters, "batch_size") {
570 if !batch_size.is_finite() {
571 return Err(OptimError::InvalidParameter(
572 "the tuner produced a non-finite batch_size".to_string(),
573 ));
574 }
575 self.config.batch_size = batch_size.round().max(1.0) as usize;
576 }
577
578 Ok(outcome)
579 }
580
581 fn optimize_for_cpu(
583 &mut self,
584 cores: usize,
585 cache_size: usize,
586 simd_support: SIMDSupport,
587 ) -> Result<()> {
588 let cache_friendly_batch_size =
590 (cache_size / 4) / self.current_state.parameters().len().max(1); self.config.batch_size = cache_friendly_batch_size.clamp(16, 512);
592
593 self.config.parallelization = ParallelizationStrategy::DataParallel {
595 num_workers: cores.min(8), };
597
598 match simd_support {
600 SIMDSupport::AVX512 => {
601 self.config
602 .optimizer_params
603 .insert("vectorized_ops".to_string(), try_scalar::<A, _>(512.0)?);
604 }
605 SIMDSupport::AVX => {
606 self.config
607 .optimizer_params
608 .insert("vectorized_ops".to_string(), try_scalar::<A, _>(256.0)?);
609 }
610 SIMDSupport::SSE => {
611 self.config
612 .optimizer_params
613 .insert("vectorized_ops".to_string(), try_scalar::<A, _>(128.0)?);
614 }
615 SIMDSupport::NEON => {
616 self.config
617 .optimizer_params
618 .insert("vectorized_ops".to_string(), try_scalar::<A, _>(128.0)?);
619 }
620 SIMDSupport::None => {
621 self.config
622 .optimizer_params
623 .insert("vectorized_ops".to_string(), try_scalar::<A, _>(32.0)?);
624 }
625 }
626
627 self.config.precision = PrecisionStrategy::FP32;
629
630 Ok(())
631 }
632
633 fn optimize_for_gpu(
635 &mut self,
636 memory: usize,
637 compute_units: usize,
638 memory_bandwidth: f64,
639 architecture: GPUArchitecture,
640 ) -> Result<()> {
641 let gpu_memory_gb = memory as f64 / (1024.0 * 1024.0 * 1024.0);
643 let optimal_batch_size = if gpu_memory_gb >= 32.0 {
644 256
645 } else if gpu_memory_gb >= 16.0 {
646 128
647 } else if gpu_memory_gb >= 8.0 {
648 64
649 } else {
650 32
651 };
652 self.config.batch_size = optimal_batch_size;
653
654 self.config.parallelization = ParallelizationStrategy::DataParallel {
656 num_workers: compute_units.min(16),
657 };
658
659 match architecture {
661 GPUArchitecture::Ampere | GPUArchitecture::Hopper => {
662 self.config.precision = PrecisionStrategy::Mixed {
664 forward_precision: "fp16".to_string(),
665 backward_precision: "fp32".to_string(),
666 loss_scaling: true,
667 };
668 self.config
669 .optimizer_params
670 .insert("tensor_cores".to_string(), try_scalar::<A, _>(1.0)?);
671 }
672 GPUArchitecture::Volta | GPUArchitecture::Turing => {
673 self.config.precision = PrecisionStrategy::FP16;
674 self.config
675 .optimizer_params
676 .insert("tensor_cores".to_string(), try_scalar::<A, _>(1.0)?);
677 }
678 _ => {
679 self.config.precision = PrecisionStrategy::FP32;
680 }
681 }
682
683 if memory_bandwidth < 500.0 {
685 self.config.memory_strategy = MemoryStrategy::GradientAccumulation {
687 accumulation_steps: 4,
688 };
689 } else {
690 self.config.memory_strategy = MemoryStrategy::Standard;
691 }
692
693 Ok(())
694 }
695
696 fn optimize_for_tpu(
698 &mut self,
699 version: TPUVersion,
700 matrix_units: usize,
701 hbm_size: usize,
702 ) -> Result<()> {
703 let tpu_batch_size = match version {
705 TPUVersion::V1 | TPUVersion::V2 => 128,
706 TPUVersion::V3 => 256,
707 TPUVersion::V4 | TPUVersion::V5 => 512,
708 };
709 self.config.batch_size = tpu_batch_size;
710
711 self.config.precision = PrecisionStrategy::BF16;
713
714 self.config.optimizer_params.insert(
716 "matrix_units".to_string(),
717 try_scalar::<A, _>(matrix_units as f64)?,
718 );
719
720 self.config.parallelization = ParallelizationStrategy::TensorParallel {
722 tensor_parallel_size: matrix_units.min(8),
723 };
724
725 if hbm_size > 32 * 1024 * 1024 * 1024 {
727 self.config.memory_strategy = MemoryStrategy::Standard;
729 } else {
730 self.config.memory_strategy = MemoryStrategy::GradientCheckpointing {
731 checkpoint_ratio: 0.5,
732 };
733 }
734
735 Ok(())
736 }
737
738 fn optimize_for_edge(
740 &mut self,
741 power_budget: f64,
742 memory_limit: usize,
743 quantization_support: QuantizationSupport,
744 ) -> Result<()> {
745 let edge_batch_size = (memory_limit / (4 * 1024 * 1024)).clamp(1, 32); self.config.batch_size = edge_batch_size;
748
749 self.config.parallelization = ParallelizationStrategy::SingleThread;
751
752 match quantization_support {
754 QuantizationSupport::Int4 => {
755 self.config.precision = PrecisionStrategy::Quantized {
756 weight_bits: 4,
757 activation_bits: 8,
758 quantization_method: "dynamic".to_string(),
759 };
760 }
761 QuantizationSupport::Int8 => {
762 self.config.precision = PrecisionStrategy::Quantized {
763 weight_bits: 8,
764 activation_bits: 8,
765 quantization_method: "static".to_string(),
766 };
767 }
768 QuantizationSupport::FP16 => {
769 self.config.precision = PrecisionStrategy::FP16;
770 }
771 _ => {
772 self.config.precision = PrecisionStrategy::FP32;
773 }
774 }
775
776 if power_budget < 5.0 {
778 self.config
780 .optimizer_params
781 .insert("update_frequency".to_string(), try_scalar::<A, _>(10.0)?);
782 self.config.memory_strategy = MemoryStrategy::CPUOffloading { offload_ratio: 0.8 };
783 }
784
785 Ok(())
786 }
787
788 fn optimize_for_distributed(
790 &mut self,
791 num_nodes: usize,
792 network_bandwidth: f64,
793 node_hardware: &HardwarePlatform,
794 ) -> Result<()> {
795 let base_batch_size = match node_hardware {
797 HardwarePlatform::GPU { .. } => 128,
798 HardwarePlatform::CPU { .. } => 64,
799 HardwarePlatform::TPU { .. } => 256, HardwarePlatform::Edge { .. } => 32, HardwarePlatform::Distributed { node_hardware, .. } => {
802 match node_hardware.as_ref() {
804 HardwarePlatform::GPU { .. } => 128,
805 HardwarePlatform::CPU { .. } => 64,
806 HardwarePlatform::TPU { .. } => 256,
807 HardwarePlatform::Edge { .. } => 32,
808 HardwarePlatform::Distributed { .. } => 64, }
810 }
811 };
812 self.config.batch_size = base_batch_size * num_nodes;
813
814 let communication = if network_bandwidth >= 100.0 {
816 CommunicationStrategy::AllReduce {
818 algorithm: AllReduceAlgorithm::Ring,
819 compression: false,
820 }
821 } else if network_bandwidth >= 10.0 {
822 CommunicationStrategy::AllReduce {
824 algorithm: AllReduceAlgorithm::Tree,
825 compression: true,
826 }
827 } else {
828 CommunicationStrategy::ParameterServer {
830 num_servers: (num_nodes / 4).max(1),
831 update_frequency: 10,
832 }
833 };
834 self.config.communication = Some(communication);
835
836 if num_nodes >= 64 {
838 self.config.parallelization = ParallelizationStrategy::Hybrid {
839 data_parallel: 8,
840 model_parallel: 4,
841 pipeline_parallel: num_nodes / 32,
842 };
843 } else if num_nodes >= 16 {
844 self.config.parallelization = ParallelizationStrategy::Pipeline {
845 pipeline_stages: 4,
846 micro_batches: 8,
847 };
848 } else {
849 self.config.parallelization = ParallelizationStrategy::DataParallel {
850 num_workers: num_nodes,
851 };
852 }
853
854 Ok(())
855 }
856
857 pub fn profile_performance(&mut self, computation_time: A, memoryused: usize, energy: A) {
859 self.profiler.computation_times.push(computation_time);
860 self.profiler.memory_usage.push(memoryused);
861 self.profiler.energy_consumption.push(energy);
862
863 let throughput = scalar_or(self.config.batch_size as f64, A::zero()) / computation_time;
865 self.profiler.throughput.push(throughput);
866
867 const MAX_HISTORY: usize = 1000;
869 if self.profiler.computation_times.len() > MAX_HISTORY {
870 self.profiler.computation_times.remove(0);
871 self.profiler.memory_usage.remove(0);
872 self.profiler.energy_consumption.remove(0);
873 self.profiler.throughput.remove(0);
874 }
875 }
876
877 pub fn update_resource_monitor(&mut self, memory: usize, cpuutil: A, power: A, temp: A) {
879 self.resource_monitor.current_memory = memory;
880 self.resource_monitor.peak_memory = self.resource_monitor.peak_memory.max(memory);
881 self.resource_monitor.cpu_utilization = cpuutil;
882 self.resource_monitor.power_consumption = power;
883 self.resource_monitor.temperature = temp;
884 }
885
886 pub fn adaptive_tune(&mut self, targetperformance: A) -> Result<()> {
888 self.adaptive_tuner
889 .set_performance_target(targetperformance);
890
891 let current_performance = self.get_average_performance();
893
894 if current_performance < targetperformance {
895 self.tune_for_performance()?;
897 } else {
898 self.tune_for_efficiency()?;
900 }
901
902 Ok(())
903 }
904
905 fn tune_for_performance(&mut self) -> Result<()> {
907 if self.resource_monitor.current_memory < self.resource_monitor.peak_memory * 8 / 10 {
909 self.config.batch_size = (self.config.batch_size * 12 / 10).min(1024);
910 }
911
912 match self.config.precision {
914 PrecisionStrategy::FP32 => {
915 self.config.precision = PrecisionStrategy::FP16;
916 }
917 PrecisionStrategy::FP16 => {
918 self.config.precision = PrecisionStrategy::Mixed {
919 forward_precision: "fp16".to_string(),
920 backward_precision: "fp32".to_string(),
921 loss_scaling: true,
922 };
923 }
924 _ => {}
925 }
926
927 Ok(())
928 }
929
930 fn tune_for_efficiency(&mut self) -> Result<()> {
932 self.config.batch_size = (self.config.batch_size * 9 / 10).max(1);
934
935 self.config.memory_strategy = MemoryStrategy::GradientAccumulation {
937 accumulation_steps: 2,
938 };
939
940 Ok(())
941 }
942
943 fn get_average_performance(&self) -> A {
945 if self.profiler.throughput.is_empty() {
946 A::zero()
947 } else {
948 let recent_throughput =
949 &self.profiler.throughput[self.profiler.throughput.len().saturating_sub(10)..];
950 recent_throughput.iter().copied().sum::<A>()
951 / scalar_or(recent_throughput.len(), A::one())
952 }
953 }
954
955 pub fn get_config(&self) -> &HardwareOptimizationConfig<A> {
957 &self.config
958 }
959
960 pub fn get_performance_stats(&self) -> HardwarePerformanceStats<A> {
962 let avg_computation_time = if self.profiler.computation_times.is_empty() {
963 A::zero()
964 } else {
965 self.profiler.computation_times.iter().sum::<A>()
966 / scalar_or(self.profiler.computation_times.len(), A::one())
967 };
968
969 let avg_throughput = if self.profiler.throughput.is_empty() {
970 A::zero()
971 } else {
972 self.profiler.throughput.iter().sum::<A>()
973 / scalar_or(self.profiler.throughput.len(), A::one())
974 };
975
976 let avg_energy = if self.profiler.energy_consumption.is_empty() {
977 A::zero()
978 } else {
979 self.profiler.energy_consumption.iter().copied().sum::<A>()
980 / scalar_or(self.profiler.energy_consumption.len(), A::one())
981 };
982
983 HardwarePerformanceStats {
984 average_computation_time: avg_computation_time,
985 average_throughput: avg_throughput,
986 peak_memory_usage: self.resource_monitor.peak_memory,
987 average_energy_consumption: avg_energy,
988 hardware_utilization: self.resource_monitor.cpu_utilization,
989 efficiency_score: avg_throughput / (avg_energy + scalar_or(1e-8, A::zero())), }
991 }
992
993 fn default_config_for_platform(platform: &HardwarePlatform) -> HardwareOptimizationConfig<A> {
995 match platform {
996 HardwarePlatform::CPU { .. } => HardwareOptimizationConfig {
997 batch_size: 64,
998 memory_strategy: MemoryStrategy::Standard,
999 parallelization: ParallelizationStrategy::DataParallel { num_workers: 4 },
1000 precision: PrecisionStrategy::FP32,
1001 optimizer_params: HashMap::new(),
1002 communication: None,
1003 },
1004 HardwarePlatform::GPU { .. } => HardwareOptimizationConfig {
1005 batch_size: 128,
1006 memory_strategy: MemoryStrategy::Standard,
1007 parallelization: ParallelizationStrategy::DataParallel { num_workers: 1 },
1008 precision: PrecisionStrategy::FP16,
1009 optimizer_params: HashMap::new(),
1010 communication: None,
1011 },
1012 HardwarePlatform::TPU { .. } => HardwareOptimizationConfig {
1013 batch_size: 256,
1014 memory_strategy: MemoryStrategy::Standard,
1015 parallelization: ParallelizationStrategy::TensorParallel {
1016 tensor_parallel_size: 8,
1017 },
1018 precision: PrecisionStrategy::BF16,
1019 optimizer_params: HashMap::new(),
1020 communication: None,
1021 },
1022 HardwarePlatform::Edge { .. } => HardwareOptimizationConfig {
1023 batch_size: 16,
1024 memory_strategy: MemoryStrategy::GradientCheckpointing {
1025 checkpoint_ratio: 0.5,
1026 },
1027 parallelization: ParallelizationStrategy::SingleThread,
1028 precision: PrecisionStrategy::Quantized {
1029 weight_bits: 8,
1030 activation_bits: 8,
1031 quantization_method: "dynamic".to_string(),
1032 },
1033 optimizer_params: HashMap::new(),
1034 communication: None,
1035 },
1036 HardwarePlatform::Distributed { .. } => HardwareOptimizationConfig {
1037 batch_size: 512,
1038 memory_strategy: MemoryStrategy::Standard,
1039 parallelization: ParallelizationStrategy::DataParallel { num_workers: 8 },
1040 precision: PrecisionStrategy::FP16,
1041 optimizer_params: HashMap::new(),
1042 communication: Some(CommunicationStrategy::AllReduce {
1043 algorithm: AllReduceAlgorithm::Ring,
1044 compression: false,
1045 }),
1046 },
1047 }
1048 }
1049}
1050
1051impl<A: Float + Send + Sync> Default for PerformanceProfiler<A> {
1052 fn default() -> Self {
1053 Self::new()
1054 }
1055}
1056
1057impl<A: Float + Send + Sync> PerformanceProfiler<A> {
1058 pub fn new() -> Self {
1060 Self {
1061 computation_times: Vec::new(),
1062 memory_usage: Vec::new(),
1063 energy_consumption: Vec::new(),
1064 throughput: Vec::new(),
1065 }
1066 }
1067}
1068
1069impl<A: Float + Send + Sync> Default for ResourceMonitor<A> {
1070 fn default() -> Self {
1071 Self::new()
1072 }
1073}
1074
1075impl<A: Float + Send + Sync> ResourceMonitor<A> {
1076 pub fn new() -> Self {
1078 Self {
1079 current_memory: 0,
1080 peak_memory: 0,
1081 cpu_utilization: A::zero(),
1082 power_consumption: A::zero(),
1083 temperature: A::zero(),
1084 }
1085 }
1086}
1087
1088#[derive(Debug, Clone)]
1090pub struct HardwarePerformanceStats<A: Float> {
1091 pub average_computation_time: A,
1093 pub average_throughput: A,
1095 pub peak_memory_usage: usize,
1097 pub average_energy_consumption: A,
1099 pub hardware_utilization: A,
1101 pub efficiency_score: A,
1103}
1104
1105#[cfg(test)]
1106mod tests {
1107 use super::*;
1108 use scirs2_core::ndarray::Array1;
1109
1110 #[test]
1111 fn test_cpu_optimization() {
1112 let platform = HardwarePlatform::CPU {
1113 cores: 8,
1114 cache_size: 32 * 1024 * 1024, simd_support: SIMDSupport::AVX,
1116 };
1117
1118 let initial_params = Array1::from_vec(vec![1.0, 2.0, 3.0]);
1119 let mut optimizer = HardwareAwareOptimizer::new(platform, initial_params);
1120
1121 optimizer.optimize_for_hardware().expect("unwrap failed");
1122
1123 assert!(optimizer.config.batch_size <= 512);
1125 assert!(matches!(
1126 optimizer.config.parallelization,
1127 ParallelizationStrategy::DataParallel { .. }
1128 ));
1129 assert!(matches!(
1130 optimizer.config.precision,
1131 PrecisionStrategy::FP32
1132 ));
1133 assert!(optimizer
1134 .config
1135 .optimizer_params
1136 .contains_key("vectorized_ops"));
1137 }
1138
1139 #[test]
1140 fn test_gpu_optimization() {
1141 let platform = HardwarePlatform::GPU {
1142 memory: 16 * 1024 * 1024 * 1024, compute_units: 80,
1144 memory_bandwidth: 900.0,
1145 architecture: GPUArchitecture::Ampere,
1146 };
1147
1148 let initial_params = Array1::from_vec(vec![1.0, 2.0, 3.0]);
1149 let mut optimizer = HardwareAwareOptimizer::new(platform, initial_params);
1150
1151 optimizer.optimize_for_hardware().expect("unwrap failed");
1152
1153 assert_eq!(optimizer.config.batch_size, 128);
1155 assert!(matches!(
1156 optimizer.config.precision,
1157 PrecisionStrategy::Mixed { .. }
1158 ));
1159 assert!(optimizer
1160 .config
1161 .optimizer_params
1162 .contains_key("tensor_cores"));
1163 }
1164
1165 #[test]
1166 fn test_tpu_optimization() {
1167 let platform = HardwarePlatform::TPU {
1168 version: TPUVersion::V4,
1169 matrix_units: 8,
1170 hbm_size: 32 * 1024 * 1024 * 1024, };
1172
1173 let initial_params = Array1::from_vec(vec![1.0, 2.0, 3.0]);
1174 let mut optimizer = HardwareAwareOptimizer::new(platform, initial_params);
1175
1176 optimizer.optimize_for_hardware().expect("unwrap failed");
1177
1178 assert_eq!(optimizer.config.batch_size, 512);
1180 assert!(matches!(
1181 optimizer.config.precision,
1182 PrecisionStrategy::BF16
1183 ));
1184 assert!(matches!(
1185 optimizer.config.parallelization,
1186 ParallelizationStrategy::TensorParallel { .. }
1187 ));
1188 }
1189
1190 #[test]
1191 fn test_edge_optimization() {
1192 let platform = HardwarePlatform::Edge {
1193 power_budget: 3.0, memory_limit: 512 * 1024 * 1024, quantization_support: QuantizationSupport::Int8,
1196 };
1197
1198 let initial_params = Array1::from_vec(vec![1.0, 2.0, 3.0]);
1199 let mut optimizer = HardwareAwareOptimizer::new(platform, initial_params);
1200
1201 optimizer.optimize_for_hardware().expect("unwrap failed");
1202
1203 assert!(optimizer.config.batch_size <= 32);
1205 assert!(matches!(
1206 optimizer.config.parallelization,
1207 ParallelizationStrategy::SingleThread
1208 ));
1209 assert!(matches!(
1210 optimizer.config.precision,
1211 PrecisionStrategy::Quantized { .. }
1212 ));
1213 }
1214
1215 #[test]
1216 fn test_distributed_optimization() {
1217 let node_hardware = HardwarePlatform::GPU {
1218 memory: 8 * 1024 * 1024 * 1024, compute_units: 40,
1220 memory_bandwidth: 500.0,
1221 architecture: GPUArchitecture::Volta,
1222 };
1223
1224 let platform = HardwarePlatform::Distributed {
1225 num_nodes: 16,
1226 network_bandwidth: 50.0, node_hardware: Box::new(node_hardware),
1228 };
1229
1230 let initial_params = Array1::from_vec(vec![1.0, 2.0, 3.0]);
1231 let mut optimizer = HardwareAwareOptimizer::new(platform, initial_params);
1232
1233 optimizer.optimize_for_hardware().expect("unwrap failed");
1234
1235 assert_eq!(optimizer.config.batch_size, 128 * 16); assert!(optimizer.config.communication.is_some());
1238 assert!(matches!(
1239 optimizer.config.parallelization,
1240 ParallelizationStrategy::Pipeline { .. }
1241 ));
1242 }
1243
1244 #[test]
1245 fn test_performance_profiling() {
1246 let platform = HardwarePlatform::CPU {
1247 cores: 4,
1248 cache_size: 8 * 1024 * 1024,
1249 simd_support: SIMDSupport::SSE,
1250 };
1251
1252 let initial_params = Array1::from_vec(vec![1.0, 2.0, 3.0]);
1253 let mut optimizer = HardwareAwareOptimizer::new(platform, initial_params);
1254
1255 optimizer.profile_performance(0.1, 1000000, 5.0);
1257 optimizer.profile_performance(0.12, 1100000, 5.2);
1258 optimizer.profile_performance(0.09, 950000, 4.8);
1259
1260 let stats = optimizer.get_performance_stats();
1261
1262 assert!(stats.average_computation_time > 0.0);
1263 assert!(stats.average_throughput > 0.0);
1264 assert_eq!(stats.peak_memory_usage, 0); }
1266
1267 #[test]
1268 fn test_adaptive_tuning() {
1269 let platform = HardwarePlatform::GPU {
1270 memory: 8 * 1024 * 1024 * 1024,
1271 compute_units: 20,
1272 memory_bandwidth: 300.0,
1273 architecture: GPUArchitecture::Turing,
1274 };
1275
1276 let initial_params = Array1::from_vec(vec![1.0, 2.0, 3.0]);
1277 let mut optimizer = HardwareAwareOptimizer::new(platform, initial_params);
1278
1279 optimizer.profiler.throughput.push(50.0);
1281 optimizer.resource_monitor.current_memory = 1_000_000_000; optimizer.resource_monitor.peak_memory = 4_000_000_000; let initial_batch_size = optimizer.config.batch_size;
1285 optimizer.adaptive_tune(100.0).expect("unwrap failed"); assert!(optimizer.config.batch_size >= initial_batch_size);
1289 }
1290
1291 #[test]
1292 fn test_hardware_platform_matching() {
1293 let platforms = vec![
1294 HardwarePlatform::CPU {
1295 cores: 8,
1296 cache_size: 16_000_000,
1297 simd_support: SIMDSupport::AVX,
1298 },
1299 HardwarePlatform::GPU {
1300 memory: 12_000_000_000,
1301 compute_units: 60,
1302 memory_bandwidth: 600.0,
1303 architecture: GPUArchitecture::Ampere,
1304 },
1305 HardwarePlatform::TPU {
1306 version: TPUVersion::V3,
1307 matrix_units: 8,
1308 hbm_size: 16_000_000_000,
1309 },
1310 HardwarePlatform::Edge {
1311 power_budget: 2.0,
1312 memory_limit: 256_000_000,
1313 quantization_support: QuantizationSupport::Int4,
1314 },
1315 ];
1316
1317 for platform in platforms {
1318 let initial_params = Array1::from_vec(vec![1.0, 2.0, 3.0]);
1319 let mut optimizer = HardwareAwareOptimizer::new(platform, initial_params);
1320
1321 let result = optimizer.optimize_for_hardware();
1323 assert!(result.is_ok());
1324
1325 let config = optimizer.get_config();
1327 assert!(config.batch_size > 0);
1328 }
1329 }
1330
1331 #[test]
1339 fn the_hardware_aware_optimizer_actually_optimizes() {
1340 let platform = HardwarePlatform::CPU {
1341 cores: 8,
1342 cache_size: 32 * 1024 * 1024,
1343 simd_support: SIMDSupport::AVX,
1344 };
1345
1346 let mut optimizer = HardwareAwareOptimizer::new_with_learning_rate(
1347 platform,
1348 Array1::from_vec(vec![2.0, -3.0, 1.5]),
1349 0.05,
1350 );
1351 optimizer
1352 .optimize_for_hardware()
1353 .expect("hardware configuration must succeed");
1354
1355 let loss = |parameters: &Array1<f64>| -> f64 { parameters.iter().map(|&x| x * x).sum() };
1356 let initial_loss = loss(optimizer.parameters());
1357
1358 for _ in 0..300 {
1359 let gradient = optimizer.parameters().mapv(|x| 2.0 * x);
1360 let report = optimizer.step(&gradient).expect("step must succeed");
1361 assert!(report.applied);
1362 }
1363
1364 let final_loss = loss(optimizer.parameters());
1365 assert_eq!(optimizer.optimization_state().step_count(), 300);
1366 assert!(
1370 final_loss < initial_loss * 1e-2,
1371 "the loss did not fall ({initial_loss} -> {final_loss})"
1372 );
1373 }
1374
1375 #[test]
1378 fn the_configuration_selects_the_optimizer_family() {
1379 let tpu = HardwarePlatform::TPU {
1380 version: TPUVersion::V4,
1381 matrix_units: 8,
1382 hbm_size: 32 * 1024 * 1024 * 1024,
1383 };
1384 let mut optimizer = HardwareAwareOptimizer::new(tpu, Array1::from_vec(vec![1.0, 2.0, 3.0]));
1385 optimizer
1386 .optimize_for_hardware()
1387 .expect("hardware configuration must succeed");
1388 assert_eq!(
1389 optimizer.optimization_state().optimizer_kind(),
1390 HardwareOptimizerKind::Lamb,
1391 "a 512-sample TPU batch calls for a large-batch optimizer"
1392 );
1393
1394 let edge = HardwarePlatform::Edge {
1395 power_budget: 2.0,
1396 memory_limit: 256 * 1024 * 1024,
1397 quantization_support: QuantizationSupport::Int8,
1398 };
1399 let mut optimizer =
1400 HardwareAwareOptimizer::new(edge, Array1::from_vec(vec![1.0, 2.0, 3.0]));
1401 optimizer
1402 .optimize_for_hardware()
1403 .expect("hardware configuration must succeed");
1404 assert_eq!(
1405 optimizer.optimization_state().optimizer_kind(),
1406 HardwareOptimizerKind::Sgd,
1407 "a 2 W budget cannot afford Adam's second moment"
1408 );
1409 }
1410
1411 #[test]
1414 fn the_memory_strategy_drives_gradient_accumulation() {
1415 let platform = HardwarePlatform::GPU {
1417 memory: 8 * 1024 * 1024 * 1024,
1418 compute_units: 40,
1419 memory_bandwidth: 300.0,
1420 architecture: GPUArchitecture::Turing,
1421 };
1422 let mut optimizer = HardwareAwareOptimizer::new_with_learning_rate(
1423 platform,
1424 Array1::from_vec(vec![0.0, 0.0]),
1425 0.1,
1426 );
1427 assert_eq!(optimizer.optimization_state().accumulation_steps(), 1);
1428
1429 optimizer
1430 .optimize_for_hardware()
1431 .expect("hardware configuration must succeed");
1432 assert!(matches!(
1433 optimizer.get_config().memory_strategy,
1434 MemoryStrategy::GradientAccumulation {
1435 accumulation_steps: 4
1436 }
1437 ));
1438 assert_eq!(optimizer.optimization_state().accumulation_steps(), 4);
1439
1440 let gradient = Array1::from_vec(vec![1.0, 1.0]);
1441 for _ in 0..3 {
1442 assert!(!optimizer.step(&gradient).expect("step").applied);
1443 }
1444 assert!(optimizer.step(&gradient).expect("step").applied);
1445 assert_eq!(optimizer.optimization_state().step_count(), 1);
1446 }
1447
1448 #[test]
1450 fn the_optimizer_is_not_swapped_out_from_under_a_running_step_count() {
1451 let platform = HardwarePlatform::CPU {
1452 cores: 4,
1453 cache_size: 8 * 1024 * 1024,
1454 simd_support: SIMDSupport::SSE,
1455 };
1456 let mut optimizer = HardwareAwareOptimizer::new(platform, Array1::from_vec(vec![1.0, 1.0]));
1457 assert_eq!(
1458 optimizer.optimization_state().optimizer_kind(),
1459 HardwareOptimizerKind::Adam
1460 );
1461
1462 optimizer
1463 .step(&Array1::from_vec(vec![1.0, 1.0]))
1464 .expect("step");
1465
1466 optimizer.config.memory_strategy = MemoryStrategy::CPUOffloading { offload_ratio: 0.8 };
1468 optimizer.sync_optimizer_with_config();
1469 assert_eq!(
1470 optimizer.optimization_state().optimizer_kind(),
1471 HardwareOptimizerKind::Adam,
1472 "a mid-run rebuild would throw away Adam's moments"
1473 );
1474 assert_eq!(
1475 optimizer.recommended_optimizer_kind(),
1476 HardwareOptimizerKind::Sgd,
1477 "the new recommendation must still be reported"
1478 );
1479
1480 assert_eq!(
1481 optimizer.adopt_recommended_optimizer(),
1482 HardwareOptimizerKind::Sgd
1483 );
1484 assert_eq!(
1485 optimizer.optimization_state().optimizer_kind(),
1486 HardwareOptimizerKind::Sgd
1487 );
1488 }
1489
1490 #[test]
1493 fn tuning_writes_the_batch_size_back_into_the_configuration() {
1494 let platform = HardwarePlatform::CPU {
1495 cores: 8,
1496 cache_size: 32 * 1024 * 1024,
1497 simd_support: SIMDSupport::AVX,
1498 };
1499 let mut optimizer =
1500 HardwareAwareOptimizer::new(platform, Array1::from_vec(vec![1.0, 2.0, 3.0]));
1501
1502 optimizer
1503 .tuner_mut()
1504 .add_parameter(TunableParameter::new("batch_size", 8.0, 256.0).expect("valid range"))
1505 .expect("register batch_size");
1506 optimizer
1507 .tuner_mut()
1508 .set_strategy(TuningStrategy::GridSearch { resolution: 32 });
1509 optimizer.tuner_mut().set_performance_target(1e9);
1510
1511 let outcome = optimizer
1513 .tune_parameters(|params| {
1514 let batch_size = params.get("batch_size").copied().unwrap_or(0.0);
1515 Ok(TuningObservation {
1516 performance: 1000.0 - (batch_size - 64.0).abs(),
1517 resource_usage: batch_size,
1518 })
1519 })
1520 .expect("tuning must run");
1521
1522 assert_eq!(outcome.evaluations, 32);
1523 let tuned = outcome
1524 .best_parameters
1525 .get("batch_size")
1526 .copied()
1527 .expect("batch_size tuned");
1528 assert!(
1529 (tuned - 64.0).abs() < 16.0,
1530 "the search did not approach the peak: {tuned}"
1531 );
1532 assert_eq!(optimizer.get_config().batch_size, tuned.round() as usize);
1533 assert_eq!(optimizer.tuner().tuning_history().len(), 32);
1534 }
1535}