use crate::error::{OptimError, Result};
use crate::utils::{scalar_or, try_scalar};
use scirs2_core::ndarray::{Array, Dimension, ScalarOperand};
use scirs2_core::numeric::Float;
use std::collections::HashMap;
use std::fmt::Debug;
mod adaptive_tuner;
mod optimization_state;
pub use adaptive_tuner::{
tuned_value_as_f64, AdaptiveTuner, TunableParameter, TuningObservation, TuningOutcome,
TuningRecord, TuningStrategy,
};
pub use optimization_state::{
HardwareOptimizerKind, HardwareStepReport, OptimizationState, DEFAULT_BASE_LEARNING_RATE,
};
use optimization_state::accumulation_steps_for;
#[derive(Debug, Clone, PartialEq)]
pub enum HardwarePlatform {
CPU {
cores: usize,
cache_size: usize,
simd_support: SIMDSupport,
},
GPU {
memory: usize,
compute_units: usize,
memory_bandwidth: f64,
architecture: GPUArchitecture,
},
TPU {
version: TPUVersion,
matrix_units: usize,
hbm_size: usize,
},
Edge {
power_budget: f64,
memory_limit: usize,
quantization_support: QuantizationSupport,
},
Distributed {
num_nodes: usize,
network_bandwidth: f64,
node_hardware: Box<HardwarePlatform>,
},
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum SIMDSupport {
None,
SSE,
AVX,
AVX512,
NEON,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum GPUArchitecture {
Pascal,
Volta,
Turing,
Ampere,
Hopper,
RDNA,
RDNA2,
CDNA,
XeHPG,
XeHPC,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TPUVersion {
V1,
V2,
V3,
V4,
V5,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum QuantizationSupport {
None,
Int8,
FP16,
BF16,
Int4,
Mixed,
}
#[derive(Debug, Clone)]
pub struct HardwareOptimizationConfig<A: Float> {
pub batch_size: usize,
pub memory_strategy: MemoryStrategy,
pub parallelization: ParallelizationStrategy,
pub precision: PrecisionStrategy,
pub optimizer_params: HashMap<String, A>,
pub communication: Option<CommunicationStrategy>,
}
#[derive(Debug, Clone)]
pub enum MemoryStrategy {
Standard,
GradientAccumulation {
accumulation_steps: usize,
},
GradientCheckpointing {
checkpoint_ratio: f64,
},
ParameterSharding {
shard_size: usize,
},
CPUOffloading {
offload_ratio: f64,
},
Mixed {
strategies: Vec<MemoryStrategy>,
strategy_weights: Vec<f64>,
},
}
#[derive(Debug, Clone)]
pub enum ParallelizationStrategy {
SingleThread,
DataParallel {
num_workers: usize,
},
ModelParallel {
partition_strategy: PartitionStrategy,
},
Pipeline {
pipeline_stages: usize,
micro_batches: usize,
},
TensorParallel {
tensor_parallel_size: usize,
},
Hybrid {
data_parallel: usize,
model_parallel: usize,
pipeline_parallel: usize,
},
}
#[derive(Debug, Clone)]
pub enum PartitionStrategy {
LayerWise,
DepthWise,
WidthWise,
Custom {
partition_points: Vec<usize>,
},
}
#[derive(Debug, Clone)]
pub enum PrecisionStrategy {
FP32,
FP16,
BF16,
Mixed {
forward_precision: String,
backward_precision: String,
loss_scaling: bool,
},
Quantized {
weight_bits: u8,
activation_bits: u8,
quantization_method: String,
},
}
#[derive(Debug, Clone)]
pub enum CommunicationStrategy {
AllReduce {
algorithm: AllReduceAlgorithm,
compression: bool,
},
ParameterServer {
num_servers: usize,
update_frequency: usize,
},
Gossip {
neighbors: usize,
gossip_frequency: usize,
},
Hierarchical {
local_groups: usize,
inter_group_strategy: Box<CommunicationStrategy>,
},
}
#[derive(Debug, Clone)]
pub enum AllReduceAlgorithm {
Ring,
Tree,
Butterfly,
HalvingDoubling,
}
#[derive(Debug)]
pub struct HardwareAwareOptimizer<A: Float + 'static, D: Dimension + 'static> {
platform: HardwarePlatform,
config: HardwareOptimizationConfig<A>,
profiler: PerformanceProfiler<A>,
resource_monitor: ResourceMonitor<A>,
adaptive_tuner: AdaptiveTuner<A>,
current_state: OptimizationState<A, D>,
}
#[derive(Debug)]
pub struct PerformanceProfiler<A: Float> {
computation_times: Vec<A>,
memory_usage: Vec<usize>,
energy_consumption: Vec<A>,
throughput: Vec<A>,
}
#[derive(Debug)]
pub struct ResourceMonitor<A: Float> {
current_memory: usize,
peak_memory: usize,
cpu_utilization: A,
power_consumption: A,
temperature: A,
}
impl<
A: Float
+ ScalarOperand
+ Debug
+ std::iter::Sum
+ for<'a> std::iter::Sum<&'a A>
+ Send
+ Sync
+ 'static,
D: Dimension + 'static,
> HardwareAwareOptimizer<A, D>
{
pub fn new(platform: HardwarePlatform, initialparameters: Array<A, D>) -> Self {
Self::new_with_learning_rate(
platform,
initialparameters,
scalar_or(DEFAULT_BASE_LEARNING_RATE, A::zero()),
)
}
pub fn new_with_learning_rate(
platform: HardwarePlatform,
initialparameters: Array<A, D>,
base_learning_rate: A,
) -> Self {
let config = Self::default_config_for_platform(&platform);
let profiler = PerformanceProfiler::new();
let resource_monitor = ResourceMonitor::new();
let adaptive_tuner = AdaptiveTuner::new();
let kind = HardwareOptimizerKind::recommend_for(&platform, &config);
let current_state = OptimizationState::new(
initialparameters,
kind,
base_learning_rate,
accumulation_steps_for(&config.memory_strategy),
);
Self {
platform,
config,
profiler,
resource_monitor,
adaptive_tuner,
current_state,
}
}
pub fn optimize_for_hardware(&mut self) -> Result<()> {
match self.platform.clone() {
HardwarePlatform::CPU {
cores,
cache_size,
simd_support,
} => {
self.optimize_for_cpu(cores, cache_size, simd_support)?;
}
HardwarePlatform::GPU {
memory,
compute_units,
memory_bandwidth,
architecture,
} => {
self.optimize_for_gpu(memory, compute_units, memory_bandwidth, architecture)?;
}
HardwarePlatform::TPU {
version,
matrix_units,
hbm_size,
} => {
self.optimize_for_tpu(version, matrix_units, hbm_size)?;
}
HardwarePlatform::Edge {
power_budget,
memory_limit,
quantization_support,
} => {
self.optimize_for_edge(power_budget, memory_limit, quantization_support)?;
}
HardwarePlatform::Distributed {
num_nodes,
network_bandwidth,
node_hardware,
} => {
self.optimize_for_distributed(num_nodes, network_bandwidth, &node_hardware)?;
}
}
self.sync_optimizer_with_config();
Ok(())
}
fn sync_optimizer_with_config(&mut self) {
self.current_state
.set_accumulation_steps(accumulation_steps_for(&self.config.memory_strategy));
if self.current_state.step_count() == 0 {
let recommended = self.recommended_optimizer_kind();
if recommended != self.current_state.optimizer_kind() {
self.current_state.rebuild_optimizer(recommended);
}
}
}
pub fn recommended_optimizer_kind(&self) -> HardwareOptimizerKind {
HardwareOptimizerKind::recommend_for(&self.platform, &self.config)
}
pub fn adopt_recommended_optimizer(&mut self) -> HardwareOptimizerKind {
let recommended = self.recommended_optimizer_kind();
if recommended != self.current_state.optimizer_kind() {
self.current_state.rebuild_optimizer(recommended);
}
recommended
}
pub fn step(&mut self, gradients: &Array<A, D>) -> Result<HardwareStepReport<A>> {
self.current_state.step(gradients)
}
pub fn parameters(&self) -> &Array<A, D> {
self.current_state.parameters()
}
pub fn optimization_state(&self) -> &OptimizationState<A, D> {
&self.current_state
}
pub fn optimization_state_mut(&mut self) -> &mut OptimizationState<A, D> {
&mut self.current_state
}
pub fn tuner(&self) -> &AdaptiveTuner<A> {
&self.adaptive_tuner
}
pub fn tuner_mut(&mut self) -> &mut AdaptiveTuner<A> {
&mut self.adaptive_tuner
}
pub fn tune_parameters<F>(&mut self, evaluate: F) -> Result<TuningOutcome<A>>
where
F: FnMut(&HashMap<String, A>) -> Result<TuningObservation<A>>,
{
let outcome = self.adaptive_tuner.tune(evaluate)?;
if let Some(batch_size) = tuned_value_as_f64(&outcome.best_parameters, "batch_size") {
if !batch_size.is_finite() {
return Err(OptimError::InvalidParameter(
"the tuner produced a non-finite batch_size".to_string(),
));
}
self.config.batch_size = batch_size.round().max(1.0) as usize;
}
Ok(outcome)
}
fn optimize_for_cpu(
&mut self,
cores: usize,
cache_size: usize,
simd_support: SIMDSupport,
) -> Result<()> {
let cache_friendly_batch_size =
(cache_size / 4) / self.current_state.parameters().len().max(1); self.config.batch_size = cache_friendly_batch_size.clamp(16, 512);
self.config.parallelization = ParallelizationStrategy::DataParallel {
num_workers: cores.min(8), };
match simd_support {
SIMDSupport::AVX512 => {
self.config
.optimizer_params
.insert("vectorized_ops".to_string(), try_scalar::<A, _>(512.0)?);
}
SIMDSupport::AVX => {
self.config
.optimizer_params
.insert("vectorized_ops".to_string(), try_scalar::<A, _>(256.0)?);
}
SIMDSupport::SSE => {
self.config
.optimizer_params
.insert("vectorized_ops".to_string(), try_scalar::<A, _>(128.0)?);
}
SIMDSupport::NEON => {
self.config
.optimizer_params
.insert("vectorized_ops".to_string(), try_scalar::<A, _>(128.0)?);
}
SIMDSupport::None => {
self.config
.optimizer_params
.insert("vectorized_ops".to_string(), try_scalar::<A, _>(32.0)?);
}
}
self.config.precision = PrecisionStrategy::FP32;
Ok(())
}
fn optimize_for_gpu(
&mut self,
memory: usize,
compute_units: usize,
memory_bandwidth: f64,
architecture: GPUArchitecture,
) -> Result<()> {
let gpu_memory_gb = memory as f64 / (1024.0 * 1024.0 * 1024.0);
let optimal_batch_size = if gpu_memory_gb >= 32.0 {
256
} else if gpu_memory_gb >= 16.0 {
128
} else if gpu_memory_gb >= 8.0 {
64
} else {
32
};
self.config.batch_size = optimal_batch_size;
self.config.parallelization = ParallelizationStrategy::DataParallel {
num_workers: compute_units.min(16),
};
match architecture {
GPUArchitecture::Ampere | GPUArchitecture::Hopper => {
self.config.precision = PrecisionStrategy::Mixed {
forward_precision: "fp16".to_string(),
backward_precision: "fp32".to_string(),
loss_scaling: true,
};
self.config
.optimizer_params
.insert("tensor_cores".to_string(), try_scalar::<A, _>(1.0)?);
}
GPUArchitecture::Volta | GPUArchitecture::Turing => {
self.config.precision = PrecisionStrategy::FP16;
self.config
.optimizer_params
.insert("tensor_cores".to_string(), try_scalar::<A, _>(1.0)?);
}
_ => {
self.config.precision = PrecisionStrategy::FP32;
}
}
if memory_bandwidth < 500.0 {
self.config.memory_strategy = MemoryStrategy::GradientAccumulation {
accumulation_steps: 4,
};
} else {
self.config.memory_strategy = MemoryStrategy::Standard;
}
Ok(())
}
fn optimize_for_tpu(
&mut self,
version: TPUVersion,
matrix_units: usize,
hbm_size: usize,
) -> Result<()> {
let tpu_batch_size = match version {
TPUVersion::V1 | TPUVersion::V2 => 128,
TPUVersion::V3 => 256,
TPUVersion::V4 | TPUVersion::V5 => 512,
};
self.config.batch_size = tpu_batch_size;
self.config.precision = PrecisionStrategy::BF16;
self.config.optimizer_params.insert(
"matrix_units".to_string(),
try_scalar::<A, _>(matrix_units as f64)?,
);
self.config.parallelization = ParallelizationStrategy::TensorParallel {
tensor_parallel_size: matrix_units.min(8),
};
if hbm_size > 32 * 1024 * 1024 * 1024 {
self.config.memory_strategy = MemoryStrategy::Standard;
} else {
self.config.memory_strategy = MemoryStrategy::GradientCheckpointing {
checkpoint_ratio: 0.5,
};
}
Ok(())
}
fn optimize_for_edge(
&mut self,
power_budget: f64,
memory_limit: usize,
quantization_support: QuantizationSupport,
) -> Result<()> {
let edge_batch_size = (memory_limit / (4 * 1024 * 1024)).clamp(1, 32); self.config.batch_size = edge_batch_size;
self.config.parallelization = ParallelizationStrategy::SingleThread;
match quantization_support {
QuantizationSupport::Int4 => {
self.config.precision = PrecisionStrategy::Quantized {
weight_bits: 4,
activation_bits: 8,
quantization_method: "dynamic".to_string(),
};
}
QuantizationSupport::Int8 => {
self.config.precision = PrecisionStrategy::Quantized {
weight_bits: 8,
activation_bits: 8,
quantization_method: "static".to_string(),
};
}
QuantizationSupport::FP16 => {
self.config.precision = PrecisionStrategy::FP16;
}
_ => {
self.config.precision = PrecisionStrategy::FP32;
}
}
if power_budget < 5.0 {
self.config
.optimizer_params
.insert("update_frequency".to_string(), try_scalar::<A, _>(10.0)?);
self.config.memory_strategy = MemoryStrategy::CPUOffloading { offload_ratio: 0.8 };
}
Ok(())
}
fn optimize_for_distributed(
&mut self,
num_nodes: usize,
network_bandwidth: f64,
node_hardware: &HardwarePlatform,
) -> Result<()> {
let base_batch_size = match node_hardware {
HardwarePlatform::GPU { .. } => 128,
HardwarePlatform::CPU { .. } => 64,
HardwarePlatform::TPU { .. } => 256, HardwarePlatform::Edge { .. } => 32, HardwarePlatform::Distributed { node_hardware, .. } => {
match node_hardware.as_ref() {
HardwarePlatform::GPU { .. } => 128,
HardwarePlatform::CPU { .. } => 64,
HardwarePlatform::TPU { .. } => 256,
HardwarePlatform::Edge { .. } => 32,
HardwarePlatform::Distributed { .. } => 64, }
}
};
self.config.batch_size = base_batch_size * num_nodes;
let communication = if network_bandwidth >= 100.0 {
CommunicationStrategy::AllReduce {
algorithm: AllReduceAlgorithm::Ring,
compression: false,
}
} else if network_bandwidth >= 10.0 {
CommunicationStrategy::AllReduce {
algorithm: AllReduceAlgorithm::Tree,
compression: true,
}
} else {
CommunicationStrategy::ParameterServer {
num_servers: (num_nodes / 4).max(1),
update_frequency: 10,
}
};
self.config.communication = Some(communication);
if num_nodes >= 64 {
self.config.parallelization = ParallelizationStrategy::Hybrid {
data_parallel: 8,
model_parallel: 4,
pipeline_parallel: num_nodes / 32,
};
} else if num_nodes >= 16 {
self.config.parallelization = ParallelizationStrategy::Pipeline {
pipeline_stages: 4,
micro_batches: 8,
};
} else {
self.config.parallelization = ParallelizationStrategy::DataParallel {
num_workers: num_nodes,
};
}
Ok(())
}
pub fn profile_performance(&mut self, computation_time: A, memoryused: usize, energy: A) {
self.profiler.computation_times.push(computation_time);
self.profiler.memory_usage.push(memoryused);
self.profiler.energy_consumption.push(energy);
let throughput = scalar_or(self.config.batch_size as f64, A::zero()) / computation_time;
self.profiler.throughput.push(throughput);
const MAX_HISTORY: usize = 1000;
if self.profiler.computation_times.len() > MAX_HISTORY {
self.profiler.computation_times.remove(0);
self.profiler.memory_usage.remove(0);
self.profiler.energy_consumption.remove(0);
self.profiler.throughput.remove(0);
}
}
pub fn update_resource_monitor(&mut self, memory: usize, cpuutil: A, power: A, temp: A) {
self.resource_monitor.current_memory = memory;
self.resource_monitor.peak_memory = self.resource_monitor.peak_memory.max(memory);
self.resource_monitor.cpu_utilization = cpuutil;
self.resource_monitor.power_consumption = power;
self.resource_monitor.temperature = temp;
}
pub fn adaptive_tune(&mut self, targetperformance: A) -> Result<()> {
self.adaptive_tuner
.set_performance_target(targetperformance);
let current_performance = self.get_average_performance();
if current_performance < targetperformance {
self.tune_for_performance()?;
} else {
self.tune_for_efficiency()?;
}
Ok(())
}
fn tune_for_performance(&mut self) -> Result<()> {
if self.resource_monitor.current_memory < self.resource_monitor.peak_memory * 8 / 10 {
self.config.batch_size = (self.config.batch_size * 12 / 10).min(1024);
}
match self.config.precision {
PrecisionStrategy::FP32 => {
self.config.precision = PrecisionStrategy::FP16;
}
PrecisionStrategy::FP16 => {
self.config.precision = PrecisionStrategy::Mixed {
forward_precision: "fp16".to_string(),
backward_precision: "fp32".to_string(),
loss_scaling: true,
};
}
_ => {}
}
Ok(())
}
fn tune_for_efficiency(&mut self) -> Result<()> {
self.config.batch_size = (self.config.batch_size * 9 / 10).max(1);
self.config.memory_strategy = MemoryStrategy::GradientAccumulation {
accumulation_steps: 2,
};
Ok(())
}
fn get_average_performance(&self) -> A {
if self.profiler.throughput.is_empty() {
A::zero()
} else {
let recent_throughput =
&self.profiler.throughput[self.profiler.throughput.len().saturating_sub(10)..];
recent_throughput.iter().copied().sum::<A>()
/ scalar_or(recent_throughput.len(), A::one())
}
}
pub fn get_config(&self) -> &HardwareOptimizationConfig<A> {
&self.config
}
pub fn get_performance_stats(&self) -> HardwarePerformanceStats<A> {
let avg_computation_time = if self.profiler.computation_times.is_empty() {
A::zero()
} else {
self.profiler.computation_times.iter().sum::<A>()
/ scalar_or(self.profiler.computation_times.len(), A::one())
};
let avg_throughput = if self.profiler.throughput.is_empty() {
A::zero()
} else {
self.profiler.throughput.iter().sum::<A>()
/ scalar_or(self.profiler.throughput.len(), A::one())
};
let avg_energy = if self.profiler.energy_consumption.is_empty() {
A::zero()
} else {
self.profiler.energy_consumption.iter().copied().sum::<A>()
/ scalar_or(self.profiler.energy_consumption.len(), A::one())
};
HardwarePerformanceStats {
average_computation_time: avg_computation_time,
average_throughput: avg_throughput,
peak_memory_usage: self.resource_monitor.peak_memory,
average_energy_consumption: avg_energy,
hardware_utilization: self.resource_monitor.cpu_utilization,
efficiency_score: avg_throughput / (avg_energy + scalar_or(1e-8, A::zero())), }
}
fn default_config_for_platform(platform: &HardwarePlatform) -> HardwareOptimizationConfig<A> {
match platform {
HardwarePlatform::CPU { .. } => HardwareOptimizationConfig {
batch_size: 64,
memory_strategy: MemoryStrategy::Standard,
parallelization: ParallelizationStrategy::DataParallel { num_workers: 4 },
precision: PrecisionStrategy::FP32,
optimizer_params: HashMap::new(),
communication: None,
},
HardwarePlatform::GPU { .. } => HardwareOptimizationConfig {
batch_size: 128,
memory_strategy: MemoryStrategy::Standard,
parallelization: ParallelizationStrategy::DataParallel { num_workers: 1 },
precision: PrecisionStrategy::FP16,
optimizer_params: HashMap::new(),
communication: None,
},
HardwarePlatform::TPU { .. } => HardwareOptimizationConfig {
batch_size: 256,
memory_strategy: MemoryStrategy::Standard,
parallelization: ParallelizationStrategy::TensorParallel {
tensor_parallel_size: 8,
},
precision: PrecisionStrategy::BF16,
optimizer_params: HashMap::new(),
communication: None,
},
HardwarePlatform::Edge { .. } => HardwareOptimizationConfig {
batch_size: 16,
memory_strategy: MemoryStrategy::GradientCheckpointing {
checkpoint_ratio: 0.5,
},
parallelization: ParallelizationStrategy::SingleThread,
precision: PrecisionStrategy::Quantized {
weight_bits: 8,
activation_bits: 8,
quantization_method: "dynamic".to_string(),
},
optimizer_params: HashMap::new(),
communication: None,
},
HardwarePlatform::Distributed { .. } => HardwareOptimizationConfig {
batch_size: 512,
memory_strategy: MemoryStrategy::Standard,
parallelization: ParallelizationStrategy::DataParallel { num_workers: 8 },
precision: PrecisionStrategy::FP16,
optimizer_params: HashMap::new(),
communication: Some(CommunicationStrategy::AllReduce {
algorithm: AllReduceAlgorithm::Ring,
compression: false,
}),
},
}
}
}
impl<A: Float + Send + Sync> Default for PerformanceProfiler<A> {
fn default() -> Self {
Self::new()
}
}
impl<A: Float + Send + Sync> PerformanceProfiler<A> {
pub fn new() -> Self {
Self {
computation_times: Vec::new(),
memory_usage: Vec::new(),
energy_consumption: Vec::new(),
throughput: Vec::new(),
}
}
}
impl<A: Float + Send + Sync> Default for ResourceMonitor<A> {
fn default() -> Self {
Self::new()
}
}
impl<A: Float + Send + Sync> ResourceMonitor<A> {
pub fn new() -> Self {
Self {
current_memory: 0,
peak_memory: 0,
cpu_utilization: A::zero(),
power_consumption: A::zero(),
temperature: A::zero(),
}
}
}
#[derive(Debug, Clone)]
pub struct HardwarePerformanceStats<A: Float> {
pub average_computation_time: A,
pub average_throughput: A,
pub peak_memory_usage: usize,
pub average_energy_consumption: A,
pub hardware_utilization: A,
pub efficiency_score: A,
}
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::Array1;
#[test]
fn test_cpu_optimization() {
let platform = HardwarePlatform::CPU {
cores: 8,
cache_size: 32 * 1024 * 1024, simd_support: SIMDSupport::AVX,
};
let initial_params = Array1::from_vec(vec![1.0, 2.0, 3.0]);
let mut optimizer = HardwareAwareOptimizer::new(platform, initial_params);
optimizer.optimize_for_hardware().expect("unwrap failed");
assert!(optimizer.config.batch_size <= 512);
assert!(matches!(
optimizer.config.parallelization,
ParallelizationStrategy::DataParallel { .. }
));
assert!(matches!(
optimizer.config.precision,
PrecisionStrategy::FP32
));
assert!(optimizer
.config
.optimizer_params
.contains_key("vectorized_ops"));
}
#[test]
fn test_gpu_optimization() {
let platform = HardwarePlatform::GPU {
memory: 16 * 1024 * 1024 * 1024, compute_units: 80,
memory_bandwidth: 900.0,
architecture: GPUArchitecture::Ampere,
};
let initial_params = Array1::from_vec(vec![1.0, 2.0, 3.0]);
let mut optimizer = HardwareAwareOptimizer::new(platform, initial_params);
optimizer.optimize_for_hardware().expect("unwrap failed");
assert_eq!(optimizer.config.batch_size, 128);
assert!(matches!(
optimizer.config.precision,
PrecisionStrategy::Mixed { .. }
));
assert!(optimizer
.config
.optimizer_params
.contains_key("tensor_cores"));
}
#[test]
fn test_tpu_optimization() {
let platform = HardwarePlatform::TPU {
version: TPUVersion::V4,
matrix_units: 8,
hbm_size: 32 * 1024 * 1024 * 1024, };
let initial_params = Array1::from_vec(vec![1.0, 2.0, 3.0]);
let mut optimizer = HardwareAwareOptimizer::new(platform, initial_params);
optimizer.optimize_for_hardware().expect("unwrap failed");
assert_eq!(optimizer.config.batch_size, 512);
assert!(matches!(
optimizer.config.precision,
PrecisionStrategy::BF16
));
assert!(matches!(
optimizer.config.parallelization,
ParallelizationStrategy::TensorParallel { .. }
));
}
#[test]
fn test_edge_optimization() {
let platform = HardwarePlatform::Edge {
power_budget: 3.0, memory_limit: 512 * 1024 * 1024, quantization_support: QuantizationSupport::Int8,
};
let initial_params = Array1::from_vec(vec![1.0, 2.0, 3.0]);
let mut optimizer = HardwareAwareOptimizer::new(platform, initial_params);
optimizer.optimize_for_hardware().expect("unwrap failed");
assert!(optimizer.config.batch_size <= 32);
assert!(matches!(
optimizer.config.parallelization,
ParallelizationStrategy::SingleThread
));
assert!(matches!(
optimizer.config.precision,
PrecisionStrategy::Quantized { .. }
));
}
#[test]
fn test_distributed_optimization() {
let node_hardware = HardwarePlatform::GPU {
memory: 8 * 1024 * 1024 * 1024, compute_units: 40,
memory_bandwidth: 500.0,
architecture: GPUArchitecture::Volta,
};
let platform = HardwarePlatform::Distributed {
num_nodes: 16,
network_bandwidth: 50.0, node_hardware: Box::new(node_hardware),
};
let initial_params = Array1::from_vec(vec![1.0, 2.0, 3.0]);
let mut optimizer = HardwareAwareOptimizer::new(platform, initial_params);
optimizer.optimize_for_hardware().expect("unwrap failed");
assert_eq!(optimizer.config.batch_size, 128 * 16); assert!(optimizer.config.communication.is_some());
assert!(matches!(
optimizer.config.parallelization,
ParallelizationStrategy::Pipeline { .. }
));
}
#[test]
fn test_performance_profiling() {
let platform = HardwarePlatform::CPU {
cores: 4,
cache_size: 8 * 1024 * 1024,
simd_support: SIMDSupport::SSE,
};
let initial_params = Array1::from_vec(vec![1.0, 2.0, 3.0]);
let mut optimizer = HardwareAwareOptimizer::new(platform, initial_params);
optimizer.profile_performance(0.1, 1000000, 5.0);
optimizer.profile_performance(0.12, 1100000, 5.2);
optimizer.profile_performance(0.09, 950000, 4.8);
let stats = optimizer.get_performance_stats();
assert!(stats.average_computation_time > 0.0);
assert!(stats.average_throughput > 0.0);
assert_eq!(stats.peak_memory_usage, 0); }
#[test]
fn test_adaptive_tuning() {
let platform = HardwarePlatform::GPU {
memory: 8 * 1024 * 1024 * 1024,
compute_units: 20,
memory_bandwidth: 300.0,
architecture: GPUArchitecture::Turing,
};
let initial_params = Array1::from_vec(vec![1.0, 2.0, 3.0]);
let mut optimizer = HardwareAwareOptimizer::new(platform, initial_params);
optimizer.profiler.throughput.push(50.0);
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;
optimizer.adaptive_tune(100.0).expect("unwrap failed");
assert!(optimizer.config.batch_size >= initial_batch_size);
}
#[test]
fn test_hardware_platform_matching() {
let platforms = vec![
HardwarePlatform::CPU {
cores: 8,
cache_size: 16_000_000,
simd_support: SIMDSupport::AVX,
},
HardwarePlatform::GPU {
memory: 12_000_000_000,
compute_units: 60,
memory_bandwidth: 600.0,
architecture: GPUArchitecture::Ampere,
},
HardwarePlatform::TPU {
version: TPUVersion::V3,
matrix_units: 8,
hbm_size: 16_000_000_000,
},
HardwarePlatform::Edge {
power_budget: 2.0,
memory_limit: 256_000_000,
quantization_support: QuantizationSupport::Int4,
},
];
for platform in platforms {
let initial_params = Array1::from_vec(vec![1.0, 2.0, 3.0]);
let mut optimizer = HardwareAwareOptimizer::new(platform, initial_params);
let result = optimizer.optimize_for_hardware();
assert!(result.is_ok());
let config = optimizer.get_config();
assert!(config.batch_size > 0);
}
}
#[test]
fn the_hardware_aware_optimizer_actually_optimizes() {
let platform = HardwarePlatform::CPU {
cores: 8,
cache_size: 32 * 1024 * 1024,
simd_support: SIMDSupport::AVX,
};
let mut optimizer = HardwareAwareOptimizer::new_with_learning_rate(
platform,
Array1::from_vec(vec![2.0, -3.0, 1.5]),
0.05,
);
optimizer
.optimize_for_hardware()
.expect("hardware configuration must succeed");
let loss = |parameters: &Array1<f64>| -> f64 { parameters.iter().map(|&x| x * x).sum() };
let initial_loss = loss(optimizer.parameters());
for _ in 0..300 {
let gradient = optimizer.parameters().mapv(|x| 2.0 * x);
let report = optimizer.step(&gradient).expect("step must succeed");
assert!(report.applied);
}
let final_loss = loss(optimizer.parameters());
assert_eq!(optimizer.optimization_state().step_count(), 300);
assert!(
final_loss < initial_loss * 1e-2,
"the loss did not fall ({initial_loss} -> {final_loss})"
);
}
#[test]
fn the_configuration_selects_the_optimizer_family() {
let tpu = HardwarePlatform::TPU {
version: TPUVersion::V4,
matrix_units: 8,
hbm_size: 32 * 1024 * 1024 * 1024,
};
let mut optimizer = HardwareAwareOptimizer::new(tpu, Array1::from_vec(vec![1.0, 2.0, 3.0]));
optimizer
.optimize_for_hardware()
.expect("hardware configuration must succeed");
assert_eq!(
optimizer.optimization_state().optimizer_kind(),
HardwareOptimizerKind::Lamb,
"a 512-sample TPU batch calls for a large-batch optimizer"
);
let edge = HardwarePlatform::Edge {
power_budget: 2.0,
memory_limit: 256 * 1024 * 1024,
quantization_support: QuantizationSupport::Int8,
};
let mut optimizer =
HardwareAwareOptimizer::new(edge, Array1::from_vec(vec![1.0, 2.0, 3.0]));
optimizer
.optimize_for_hardware()
.expect("hardware configuration must succeed");
assert_eq!(
optimizer.optimization_state().optimizer_kind(),
HardwareOptimizerKind::Sgd,
"a 2 W budget cannot afford Adam's second moment"
);
}
#[test]
fn the_memory_strategy_drives_gradient_accumulation() {
let platform = HardwarePlatform::GPU {
memory: 8 * 1024 * 1024 * 1024,
compute_units: 40,
memory_bandwidth: 300.0,
architecture: GPUArchitecture::Turing,
};
let mut optimizer = HardwareAwareOptimizer::new_with_learning_rate(
platform,
Array1::from_vec(vec![0.0, 0.0]),
0.1,
);
assert_eq!(optimizer.optimization_state().accumulation_steps(), 1);
optimizer
.optimize_for_hardware()
.expect("hardware configuration must succeed");
assert!(matches!(
optimizer.get_config().memory_strategy,
MemoryStrategy::GradientAccumulation {
accumulation_steps: 4
}
));
assert_eq!(optimizer.optimization_state().accumulation_steps(), 4);
let gradient = Array1::from_vec(vec![1.0, 1.0]);
for _ in 0..3 {
assert!(!optimizer.step(&gradient).expect("step").applied);
}
assert!(optimizer.step(&gradient).expect("step").applied);
assert_eq!(optimizer.optimization_state().step_count(), 1);
}
#[test]
fn the_optimizer_is_not_swapped_out_from_under_a_running_step_count() {
let platform = HardwarePlatform::CPU {
cores: 4,
cache_size: 8 * 1024 * 1024,
simd_support: SIMDSupport::SSE,
};
let mut optimizer = HardwareAwareOptimizer::new(platform, Array1::from_vec(vec![1.0, 1.0]));
assert_eq!(
optimizer.optimization_state().optimizer_kind(),
HardwareOptimizerKind::Adam
);
optimizer
.step(&Array1::from_vec(vec![1.0, 1.0]))
.expect("step");
optimizer.config.memory_strategy = MemoryStrategy::CPUOffloading { offload_ratio: 0.8 };
optimizer.sync_optimizer_with_config();
assert_eq!(
optimizer.optimization_state().optimizer_kind(),
HardwareOptimizerKind::Adam,
"a mid-run rebuild would throw away Adam's moments"
);
assert_eq!(
optimizer.recommended_optimizer_kind(),
HardwareOptimizerKind::Sgd,
"the new recommendation must still be reported"
);
assert_eq!(
optimizer.adopt_recommended_optimizer(),
HardwareOptimizerKind::Sgd
);
assert_eq!(
optimizer.optimization_state().optimizer_kind(),
HardwareOptimizerKind::Sgd
);
}
#[test]
fn tuning_writes_the_batch_size_back_into_the_configuration() {
let platform = HardwarePlatform::CPU {
cores: 8,
cache_size: 32 * 1024 * 1024,
simd_support: SIMDSupport::AVX,
};
let mut optimizer =
HardwareAwareOptimizer::new(platform, Array1::from_vec(vec![1.0, 2.0, 3.0]));
optimizer
.tuner_mut()
.add_parameter(TunableParameter::new("batch_size", 8.0, 256.0).expect("valid range"))
.expect("register batch_size");
optimizer
.tuner_mut()
.set_strategy(TuningStrategy::GridSearch { resolution: 32 });
optimizer.tuner_mut().set_performance_target(1e9);
let outcome = optimizer
.tune_parameters(|params| {
let batch_size = params.get("batch_size").copied().unwrap_or(0.0);
Ok(TuningObservation {
performance: 1000.0 - (batch_size - 64.0).abs(),
resource_usage: batch_size,
})
})
.expect("tuning must run");
assert_eq!(outcome.evaluations, 32);
let tuned = outcome
.best_parameters
.get("batch_size")
.copied()
.expect("batch_size tuned");
assert!(
(tuned - 64.0).abs() < 16.0,
"the search did not approach the peak: {tuned}"
);
assert_eq!(optimizer.get_config().batch_size, tuned.round() as usize);
assert_eq!(optimizer.tuner().tuning_history().len(), 32);
}
}