1use crate::error::{DatasetsError, Result};
32use crate::gpu::{GpuBackend, GpuContext};
33use scirs2_core::ndarray::Array2;
34use scirs2_core::parallel_ops::*;
35use scirs2_core::random::prelude::*;
36use scirs2_core::random::{Distribution, Uniform};
37use std::collections::HashMap;
38use std::sync::Arc;
39
40#[derive(Debug, Clone)]
42pub struct AdvancedGpuOptimizer {
43 adaptive_kernels: bool,
45 memory_prefetch: bool,
47 multi_gpu: bool,
49 auto_tuning: bool,
51 performance_cache: Arc<std::sync::Mutex<HashMap<String, GpuPerformanceProfile>>>,
53}
54
55#[derive(Debug, Clone)]
57#[allow(dead_code)]
58pub struct GpuPerformanceProfile {
59 optimal_block_size: usize,
61 memory_bandwidth: f64,
63 compute_utilization: f64,
65 optimal_layout: DataLayout,
67 performance_score: f64,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq)]
73pub enum DataLayout {
74 RowMajor,
76 ColumnMajor,
78 Tiled {
80 tile_size: usize,
82 },
83 Adaptive,
85}
86
87#[derive(Debug, Clone)]
89#[allow(dead_code)]
90pub struct AdvancedKernelConfig {
91 specialization_level: SpecializationLevel,
93 memory_pattern: MemoryAccessPattern,
95 vectorization: VectorizationStrategy,
97 load_balancing: LoadBalancingMethod,
99 block_size: usize,
101}
102
103#[derive(Debug, Clone, Copy)]
105pub enum SpecializationLevel {
106 Basic,
108 HardwareOptimized,
110 AdvancedSpecialized,
112 AIOptimized,
114}
115
116#[derive(Debug, Clone, Copy)]
118pub enum MemoryAccessPattern {
119 Sequential,
121 Random,
123 Strided {
125 stride: usize,
127 },
128 Blocked {
130 block_size: usize,
132 },
133}
134
135#[derive(Debug, Clone, Copy)]
137pub enum VectorizationStrategy {
138 Scalar,
140 Vector2,
142 Vector4,
144 Vector8,
146 Adaptive,
148}
149
150#[derive(Debug, Clone, Copy)]
152pub enum LoadBalancingMethod {
153 Static,
155 Dynamic,
157 WorkStealing,
159 Adaptive,
161}
162
163const GPU_OPT_THRESHOLD: usize = 4096;
167
168#[cfg(feature = "wgpu")]
179fn attempt_gpu_round_trip(data: &[f64]) -> Option<std::time::Duration> {
180 use scirs2_core::array_protocol::gpu_ndarray::{global_context, is_gpu_available, GpuNdarray};
181 use std::time::Instant;
182
183 if data.len() < GPU_OPT_THRESHOLD || !is_gpu_available() {
184 return None;
185 }
186 let ctx = global_context()?;
187
188 let host: Vec<f32> = data.iter().map(|&v| v as f32).collect();
189 let expected_len = host.len();
190 let start = Instant::now();
191 let run = || -> std::result::Result<usize, scirs2_core::gpu::GpuError> {
192 let gpu =
193 GpuNdarray::<f32>::from_ndarray_data(&host, vec![expected_len], Arc::clone(&ctx))?;
194 let refined = gpu.multiply_by_scalar_f32(1.0)?;
195 let out = refined.to_vec()?;
196 Ok(out.len())
197 };
198 match run() {
199 Ok(len) if len == expected_len => Some(start.elapsed()),
200 _ => None,
201 }
202}
203
204#[cfg(not(feature = "wgpu"))]
207fn attempt_gpu_round_trip(data: &[f64]) -> Option<std::time::Duration> {
208 let _ = data;
209 None
210}
211
212impl Default for AdvancedGpuOptimizer {
213 fn default() -> Self {
214 Self {
215 adaptive_kernels: true,
216 memory_prefetch: true,
217 multi_gpu: true,
218 auto_tuning: true,
219 performance_cache: Arc::new(std::sync::Mutex::new(HashMap::new())),
220 }
221 }
222}
223
224impl AdvancedGpuOptimizer {
225 pub fn new() -> Self {
227 Self::default()
228 }
229
230 pub fn with_adaptive_kernels(mut self, enabled: bool) -> Self {
232 self.adaptive_kernels = enabled;
233 self
234 }
235
236 pub fn with_memory_prefetch(mut self, enabled: bool) -> Self {
238 self.memory_prefetch = enabled;
239 self
240 }
241
242 pub fn with_multi_gpu(mut self, enabled: bool) -> Self {
244 self.multi_gpu = enabled;
245 self
246 }
247
248 pub fn with_auto_tuning(mut self, enabled: bool) -> Self {
250 self.auto_tuning = enabled;
251 self
252 }
253
254 pub fn optimize_execution(
256 &self,
257 gpu_context: &GpuContext,
258 operation: &str,
259 datashape: (usize, usize),
260 ) -> Result<AdvancedKernelConfig> {
261 let cache_key = format!(
263 "{}_{}_{}_{}",
264 gpu_context.backend(),
265 operation,
266 datashape.0,
267 datashape.1
268 );
269
270 if let Ok(cache) = self.performance_cache.lock() {
271 if let Some(profile) = cache.get(&cache_key) {
272 return Ok(self.profile_to_kernel_config(profile));
273 }
274 }
275
276 if self.auto_tuning {
278 let profile = self.auto_tune_operation(gpu_context, operation, datashape)?;
279
280 if let Ok(mut cache) = self.performance_cache.lock() {
282 cache.insert(cache_key, profile.clone());
283 }
284
285 Ok(self.profile_to_kernel_config(&profile))
286 } else {
287 Ok(self.default_kernel_config(gpu_context.backend().clone()))
289 }
290 }
291
292 fn auto_tune_operation(
303 &self,
304 gpu_context: &GpuContext,
305 operation: &str,
306 datashape: (usize, usize),
307 ) -> Result<GpuPerformanceProfile> {
308 let backend = gpu_context.backend();
309
310 let optimal_block_size = match backend {
312 GpuBackend::Cuda { .. } => self.tune_cuda_block_size(datashape),
313 GpuBackend::OpenCl { .. } => self.tune_opencl_work_group_size(datashape),
314 GpuBackend::Cpu => 256, };
316
317 let (memory_bandwidth, compute_utilization) = self.calibrate_backend_throughput(backend)?;
321
322 let optimal_layout = self.determine_optimal_layout(operation, datashape);
324
325 let performance_score = self.calculate_performance_score(
327 optimal_block_size,
328 memory_bandwidth,
329 compute_utilization,
330 );
331
332 Ok(GpuPerformanceProfile {
333 optimal_block_size,
334 memory_bandwidth,
335 compute_utilization,
336 optimal_layout,
337 performance_score,
338 })
339 }
340
341 const CALIBRATION_SIDE: usize = 128;
353
354 fn calibrate_backend_throughput(&self, backend: &GpuBackend) -> Result<(f64, f64)> {
365 use std::time::Instant;
366
367 let side = Self::CALIBRATION_SIDE;
368 let elements = side * side;
369
370 let start = Instant::now();
371 let sample = self.execute_advanced_cpu_generation(side, side, "uniform")?;
372 if !matches!(backend, GpuBackend::Cpu) {
373 let _ = attempt_gpu_round_trip(sample.as_slice().unwrap_or(&[]));
377 }
378 let elapsed = start.elapsed();
379
380 let memory_bandwidth = self.calculate_memory_bandwidth(elements, elapsed);
381 let compute_utilization = self.utilization_from_timing(elements, elapsed);
382 Ok((memory_bandwidth, compute_utilization))
383 }
384
385 fn utilization_from_timing(&self, elements: usize, duration: std::time::Duration) -> f64 {
393 let elements_per_second = if duration.as_secs_f64() > 0.0 {
394 elements as f64 / duration.as_secs_f64()
395 } else {
396 0.0
397 };
398 (elements_per_second / 100_000_000.0).min(1.0)
399 }
400
401 fn tune_cuda_block_size(&self, datashape: (usize, usize)) -> usize {
403 let total_elements = datashape.0 * datashape.1;
404
405 match total_elements {
407 0..=1_000 => 32,
408 1_001..=10_000 => 64,
409 10_001..=100_000 => 128,
410 100_001..=1_000_000 => 256,
411 _ => 512,
412 }
413 }
414
415 fn tune_opencl_work_group_size(&self, datashape: (usize, usize)) -> usize {
417 let total_elements = datashape.0 * datashape.1;
419
420 match total_elements {
421 0..=1_000 => 16,
422 1_001..=10_000 => 32,
423 10_001..=100_000 => 64,
424 100_001..=1_000_000 => 128,
425 _ => 256,
426 }
427 }
428
429 fn estimate_compute_utilization(&self, operation: &str, datashape: (usize, usize)) -> f64 {
440 let total_elements = datashape.0 * datashape.1;
441
442 let compute_intensity = match operation {
444 "matrix_multiply" => 2.0 * datashape.0 as f64, "element_wise" => 1.0, "reduction" => (total_elements as f64).log2(), "trigonometric" => 10.0, _ => 1.0, };
450
451 (compute_intensity / (compute_intensity + 1.0)).min(1.0)
453 }
454
455 fn determine_optimal_layout(&self, operation: &str, datashape: (usize, usize)) -> DataLayout {
457 match operation {
458 "matrix_multiply" => {
459 if datashape.0 * datashape.1 > 100_000 {
461 DataLayout::Tiled { tile_size: 64 }
462 } else {
463 DataLayout::RowMajor
464 }
465 }
466 "transpose" => DataLayout::ColumnMajor,
467 "element_wise" => DataLayout::RowMajor,
468 _ => DataLayout::Adaptive,
469 }
470 }
471
472 fn calculate_performance_score(
474 &self,
475 block_size: usize,
476 memory_bandwidth: f64,
477 compute_utilization: f64,
478 ) -> f64 {
479 let block_efficiency = match block_size {
481 32..=256 => 1.0,
482 257..=512 => 0.9,
483 _ => 0.7,
484 };
485
486 let bandwidth_efficiency = (memory_bandwidth / (memory_bandwidth + 1e9)).min(1.0);
487
488 block_efficiency * 0.3 + bandwidth_efficiency * 0.3 + compute_utilization * 0.4
490 }
491
492 fn profile_to_kernel_config(&self, profile: &GpuPerformanceProfile) -> AdvancedKernelConfig {
494 let specialization_level = if profile.performance_score > 0.8 {
495 SpecializationLevel::AdvancedSpecialized
496 } else if profile.performance_score > 0.6 {
497 SpecializationLevel::HardwareOptimized
498 } else {
499 SpecializationLevel::Basic
500 };
501
502 let memory_pattern = match profile.optimal_layout {
503 DataLayout::RowMajor => MemoryAccessPattern::Sequential,
504 DataLayout::ColumnMajor => MemoryAccessPattern::Strided { stride: 1 },
505 DataLayout::Tiled { tile_size } => MemoryAccessPattern::Blocked {
506 block_size: tile_size,
507 },
508 DataLayout::Adaptive => MemoryAccessPattern::Sequential,
509 };
510
511 let vectorization = if profile.compute_utilization > 0.7 {
512 VectorizationStrategy::Vector4
513 } else if profile.compute_utilization > 0.5 {
514 VectorizationStrategy::Vector2
515 } else {
516 VectorizationStrategy::Scalar
517 };
518
519 let load_balancing = if profile.performance_score > 0.8 {
520 LoadBalancingMethod::Adaptive
521 } else {
522 LoadBalancingMethod::Dynamic
523 };
524
525 AdvancedKernelConfig {
526 specialization_level,
527 memory_pattern,
528 vectorization,
529 load_balancing,
530 block_size: profile.optimal_block_size,
534 }
535 }
536
537 fn default_kernel_config(&self, backend: GpuBackend) -> AdvancedKernelConfig {
539 match backend {
540 GpuBackend::Cuda { .. } => AdvancedKernelConfig {
541 specialization_level: SpecializationLevel::HardwareOptimized,
542 memory_pattern: MemoryAccessPattern::Sequential,
543 vectorization: VectorizationStrategy::Vector4,
544 load_balancing: LoadBalancingMethod::Dynamic,
545 block_size: 512,
546 },
547 GpuBackend::OpenCl { .. } => AdvancedKernelConfig {
548 specialization_level: SpecializationLevel::Basic,
549 memory_pattern: MemoryAccessPattern::Sequential,
550 vectorization: VectorizationStrategy::Vector2,
551 load_balancing: LoadBalancingMethod::Static,
552 block_size: 256,
553 },
554 _ => AdvancedKernelConfig {
555 specialization_level: SpecializationLevel::Basic,
556 memory_pattern: MemoryAccessPattern::Sequential,
557 vectorization: VectorizationStrategy::Scalar,
558 load_balancing: LoadBalancingMethod::Static,
559 block_size: 128,
560 },
561 }
562 }
563
564 pub fn generate_advanced_optimized_matrix(
566 &self,
567 gpu_context: &GpuContext,
568 rows: usize,
569 cols: usize,
570 distribution: &str,
571 ) -> Result<Array2<f64>> {
572 let config = self.optimize_execution(gpu_context, "matrix_generation", (rows, cols))?;
574
575 self.execute_optimized_generation(gpu_context, rows, cols, distribution, &config)
577 }
578
579 fn execute_optimized_generation(
591 &self,
592 gpu_context: &GpuContext,
593 rows: usize,
594 cols: usize,
595 distribution: &str,
596 _config: &AdvancedKernelConfig,
597 ) -> Result<Array2<f64>> {
598 use std::time::Instant;
599
600 let total_elements = rows * cols;
601 let start_time = Instant::now();
602
603 let matrix = self.execute_advanced_cpu_generation(rows, cols, distribution)?;
604
605 let used_gpu = !matches!(gpu_context.backend(), GpuBackend::Cpu)
606 && attempt_gpu_round_trip(matrix.as_slice().unwrap_or(&[])).is_some();
607
608 let label = if used_gpu {
609 "gpu_generation"
610 } else {
611 "cpu_generation"
612 };
613 self.cache_gpu_performance(label, total_elements, start_time.elapsed());
614
615 Ok(matrix)
616 }
617
618 fn cache_gpu_performance(
620 &self,
621 operation: &str,
622 elements: usize,
623 duration: std::time::Duration,
624 ) {
625 if let Ok(mut cache) = self.performance_cache.lock() {
626 let key = format!("{operation}_{elements}");
627 let profile = GpuPerformanceProfile {
628 optimal_block_size: self.calculate_optimal_block_size(elements),
629 memory_bandwidth: self.calculate_memory_bandwidth(elements, duration),
630 compute_utilization: self.utilization_from_timing(elements, duration),
631 optimal_layout: DataLayout::RowMajor, performance_score: self.calculate_performance_score_from_timing(elements, duration),
633 };
634 cache.insert(key, profile);
635 }
636 }
637
638 fn calculate_optimal_block_size(&self, elements: usize) -> usize {
640 match elements {
641 0..=1024 => 32,
642 1025..=16384 => 64,
643 16385..=262144 => 128,
644 262145..=1048576 => 256,
645 _ => 512,
646 }
647 }
648
649 fn calculate_memory_bandwidth(&self, elements: usize, duration: std::time::Duration) -> f64 {
651 let bytes_transferred = elements * std::mem::size_of::<f64>() * 2; let duration_secs = duration.as_secs_f64();
653 if duration_secs > 0.0 {
654 bytes_transferred as f64 / duration_secs / (1024.0 * 1024.0 * 1024.0)
655 } else {
657 0.0
658 }
659 }
660
661 fn calculate_performance_score_from_timing(
663 &self,
664 elements: usize,
665 duration: std::time::Duration,
666 ) -> f64 {
667 let elements_per_second = if duration.as_secs_f64() > 0.0 {
668 elements as f64 / duration.as_secs_f64()
669 } else {
670 0.0
671 };
672
673 (elements_per_second / 1_000_000.0).min(100.0)
675 }
676
677 fn execute_advanced_cpu_generation(
679 &self,
680 rows: usize,
681 cols: usize,
682 distribution: &str,
683 ) -> Result<Array2<f64>> {
684 use scirs2_core::random::{rng, Rng};
685 use scirs2_core::random::{Distribution, Normal, Uniform};
686
687 let _rng = thread_rng();
688 let total_elements = rows * cols;
689
690 let chunk_size = (total_elements / num_cpus::get()).max(1000);
692
693 let data: Vec<f64> = (0..total_elements)
694 .into_par_iter()
695 .chunks(chunk_size)
696 .flat_map(|chunk| {
697 let mut local_rng = thread_rng();
698 chunk
699 .into_iter()
700 .map(|_| match distribution {
701 "normal" => {
702 let normal = Normal::new(0.0, 1.0).expect("Operation failed");
703 normal.sample(&mut local_rng)
704 }
705 "uniform" => {
706 let uniform = Uniform::new(0.0, 1.0).expect("Operation failed");
707 uniform.sample(&mut local_rng)
708 }
709 _ => local_rng.random::<f64>(),
710 })
711 .collect::<Vec<_>>()
712 })
713 .collect();
714
715 Array2::from_shape_vec((rows, cols), data)
716 .map_err(|e| DatasetsError::Other(format!("Failed to create array: {e}")))
717 }
718
719 pub fn benchmark_performance(
729 &self,
730 gpu_context: &GpuContext,
731 operation: &str,
732 datashapes: &[(usize, usize)],
733 ) -> Result<PerformanceBenchmarkResults> {
734 use std::time::Instant;
735
736 let mut results = Vec::new();
737
738 for &shape in datashapes {
739 let _config = self.optimize_execution(gpu_context, operation, shape)?;
743
744 let cpu_start = Instant::now();
745 let cpu_matrix = self.execute_advanced_cpu_generation(shape.0, shape.1, "uniform")?;
746 let cpu_time_ms = cpu_start.elapsed().as_secs_f64() * 1000.0;
747
748 let gpu_time_ms = if matches!(gpu_context.backend(), GpuBackend::Cpu) {
749 None
750 } else {
751 attempt_gpu_round_trip(cpu_matrix.as_slice().unwrap_or(&[]))
752 .map(|d| d.as_secs_f64() * 1000.0)
753 };
754
755 let speedup = gpu_time_ms.filter(|&g| g > 0.0).map(|g| cpu_time_ms / g);
759
760 results.push(BenchmarkResult {
761 datashape: shape,
762 cpu_time_ms,
763 gpu_time_ms,
764 speedup,
765 memory_usage_mb: self.estimate_memory_usage(shape),
766 });
767 }
768
769 Ok(PerformanceBenchmarkResults { results })
770 }
771
772 fn estimate_memory_usage(&self, shape: (usize, usize)) -> f64 {
774 let total_elements = shape.0 * shape.1;
775 let bytes_per_element = 8; (total_elements * bytes_per_element) as f64 / (1024.0 * 1024.0) }
778}
779
780#[derive(Debug, Clone)]
782pub struct PerformanceBenchmarkResults {
783 pub results: Vec<BenchmarkResult>,
785}
786
787#[derive(Debug, Clone)]
789pub struct BenchmarkResult {
790 pub datashape: (usize, usize),
792 pub cpu_time_ms: f64,
795 pub gpu_time_ms: Option<f64>,
800 pub speedup: Option<f64>,
805 pub memory_usage_mb: f64,
807}
808
809impl PerformanceBenchmarkResults {
810 pub fn best_speedup(&self) -> Option<f64> {
816 self.results
817 .iter()
818 .filter_map(|r| r.speedup)
819 .fold(None, |acc, s| Some(acc.map_or(s, |a: f64| a.max(s))))
820 }
821
822 pub fn average_speedup(&self) -> Option<f64> {
827 let (total, count) = self
828 .results
829 .iter()
830 .filter_map(|r| r.speedup)
831 .fold((0.0, 0usize), |(total, count), s| (total + s, count + 1));
832
833 if count == 0 {
834 None
835 } else {
836 Some(total / count as f64)
837 }
838 }
839
840 pub fn total_memory_usage(&self) -> f64 {
842 self.results.iter().map(|r| r.memory_usage_mb).sum()
843 }
844}
845
846#[allow(dead_code)]
848pub fn generate_advanced_matrix(
849 gpu_context: &GpuContext,
850 rows: usize,
851 cols: usize,
852 distribution: &str,
853) -> Result<Array2<f64>> {
854 let optimizer = AdvancedGpuOptimizer::new();
855 optimizer.generate_advanced_optimized_matrix(gpu_context, rows, cols, distribution)
856}
857
858#[allow(dead_code)]
860pub fn benchmark_advanced_performance(
861 gpu_context: &GpuContext,
862 operation: &str,
863 datashapes: &[(usize, usize)],
864) -> Result<PerformanceBenchmarkResults> {
865 let optimizer = AdvancedGpuOptimizer::new();
866 optimizer.benchmark_performance(gpu_context, operation, datashapes)
867}
868
869impl std::fmt::Display for GpuBackend {
870 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
871 match self {
872 GpuBackend::Cuda { .. } => write!(f, "cuda"),
873 GpuBackend::OpenCl { .. } => write!(f, "opencl"),
874 GpuBackend::Cpu => write!(f, "cpu"),
875 }
876 }
877}
878
879#[derive(Debug, Clone)]
883pub struct AIPerformancePredictor {
884 training_data: Vec<PerformanceDataPoint>,
886 model_weights: Vec<f64>,
888 feature_means: Vec<f64>,
890 feature_stds: Vec<f64>,
891 accuracy_metrics: PredictionAccuracy,
893}
894
895#[derive(Debug, Clone)]
897#[allow(dead_code)]
898pub struct PerformanceDataPoint {
899 features: Vec<f64>,
901 target_performance: f64,
903 execution_time: f64,
905}
906
907#[derive(Debug, Clone)]
909pub struct PredictionAccuracy {
910 mae: f64,
912 rmse: f64,
914 r_squared: f64,
916 sample_count: usize,
918}
919
920impl Default for AIPerformancePredictor {
921 fn default() -> Self {
922 Self {
923 training_data: Vec::new(),
924 model_weights: vec![0.1, 0.2, 0.3, 0.4, 0.5], feature_means: vec![0.0; 4],
926 feature_stds: vec![1.0; 4],
927 accuracy_metrics: PredictionAccuracy {
928 mae: 0.0,
929 rmse: 0.0,
930 r_squared: 0.0,
931 sample_count: 0,
932 },
933 }
934 }
935}
936
937impl AIPerformancePredictor {
938 pub fn new() -> Self {
940 Self::default()
941 }
942
943 pub fn add_training_data(&mut self, datapoint: PerformanceDataPoint) {
945 self.training_data.push(datapoint);
946
947 if self.training_data.len().is_multiple_of(100) && self.training_data.len() > 50 {
949 self.retrain_model();
950 }
951 }
952
953 pub fn predict_performance(&self, features: &[f64]) -> f64 {
955 if features.len() != 4 {
956 return 0.5; }
958
959 let normalized_features: Vec<f64> = features
961 .iter()
962 .zip(&self.feature_means)
963 .zip(&self.feature_stds)
964 .map(|((feat, mean), std)| (feat - mean) / std)
965 .collect();
966
967 let prediction: f64 = normalized_features
969 .iter()
970 .zip(&self.model_weights)
971 .map(|(feat, weight)| feat * weight)
972 .sum();
973
974 (1.0 / (1.0 + (-prediction).exp())).clamp(0.0, 1.0)
976 }
977
978 fn retrain_model(&mut self) {
980 if self.training_data.len() < 10 {
981 return;
982 }
983
984 self.update_normalization_params();
986
987 let learning_rate = 0.01;
989 let epochs = 100;
990
991 for _ in 0..epochs {
992 let mut gradients = [0.0; 5];
993
994 for data_point in &self.training_data {
995 let prediction = self.predict_performance(&data_point.features);
996 let error = prediction - data_point.target_performance;
997
998 for (i, gradient) in gradients.iter_mut().enumerate().take(4) {
1000 *gradient += error * data_point.features[i] / self.training_data.len() as f64;
1001 }
1002 gradients[4] += error / self.training_data.len() as f64; }
1004
1005 for (weight, gradient) in self.model_weights.iter_mut().zip(gradients.iter()) {
1007 *weight -= learning_rate * gradient;
1008 }
1009 }
1010
1011 self.update_accuracy_metrics();
1013 }
1014
1015 fn update_normalization_params(&mut self) {
1017 let n = self.training_data.len() as f64;
1018
1019 for i in 0..4 {
1021 self.feature_means[i] = self
1022 .training_data
1023 .iter()
1024 .map(|dp| dp.features[i])
1025 .sum::<f64>()
1026 / n;
1027 }
1028
1029 for i in 0..4 {
1031 let variance = self
1032 .training_data
1033 .iter()
1034 .map(|dp| (dp.features[i] - self.feature_means[i]).powi(2))
1035 .sum::<f64>()
1036 / n;
1037 self.feature_stds[i] = variance.sqrt().max(1e-8); }
1039 }
1040
1041 fn update_accuracy_metrics(&mut self) {
1043 let predictions: Vec<f64> = self
1044 .training_data
1045 .iter()
1046 .map(|dp| self.predict_performance(&dp.features))
1047 .collect();
1048
1049 let targets: Vec<f64> = self
1050 .training_data
1051 .iter()
1052 .map(|dp| dp.target_performance)
1053 .collect();
1054
1055 self.accuracy_metrics.mae = predictions
1057 .iter()
1058 .zip(&targets)
1059 .map(|(pred, target)| (pred - target).abs())
1060 .sum::<f64>()
1061 / predictions.len() as f64;
1062
1063 let mse = predictions
1065 .iter()
1066 .zip(&targets)
1067 .map(|(pred, target)| (pred - target).powi(2))
1068 .sum::<f64>()
1069 / predictions.len() as f64;
1070 self.accuracy_metrics.rmse = mse.sqrt();
1071
1072 let target_mean = targets.iter().sum::<f64>() / targets.len() as f64;
1074 let ss_tot = targets
1075 .iter()
1076 .map(|target| (target - target_mean).powi(2))
1077 .sum::<f64>();
1078 let ss_res = predictions
1079 .iter()
1080 .zip(&targets)
1081 .map(|(pred, target)| (target - pred).powi(2))
1082 .sum::<f64>();
1083
1084 self.accuracy_metrics.r_squared = if ss_tot > 0.0 {
1085 1.0 - (ss_res / ss_tot)
1086 } else {
1087 0.0
1088 };
1089
1090 self.accuracy_metrics.sample_count = self.training_data.len();
1091 }
1092
1093 pub fn get_accuracy_metrics(&self) -> &PredictionAccuracy {
1095 &self.accuracy_metrics
1096 }
1097}
1098
1099#[derive(Debug)]
1101pub struct RealTimePerformanceMonitor {
1102 performance_history: std::collections::VecDeque<PerformanceSnapshot>,
1104 current_optimization: AdaptiveOptimizationState,
1106 config: MonitoringConfig,
1108 ai_predictor: AIPerformancePredictor,
1110}
1111
1112#[derive(Debug, Clone)]
1114#[allow(dead_code)]
1115pub struct PerformanceSnapshot {
1116 timestamp: std::time::Instant,
1118 execution_time_ms: f64,
1120 memory_usage_bytes: usize,
1122 gpu_utilization: f64,
1124 memory_bandwidth_utilization: f64,
1126 operation: String,
1128 datashape: (usize, usize),
1130}
1131
1132#[derive(Debug, Clone)]
1134#[allow(dead_code)]
1135pub struct AdaptiveOptimizationState {
1136 trend: PerformanceTrend,
1138 adjustments: Vec<OptimizationAdjustment>,
1140 learning_rate: f64,
1142 stability_threshold: f64,
1144}
1145
1146#[derive(Debug, Clone, Copy)]
1148pub enum PerformanceTrend {
1149 Improving,
1151 Degrading,
1153 Stable,
1155 Unknown,
1157}
1158
1159#[derive(Debug, Clone)]
1161#[allow(dead_code)]
1162pub struct OptimizationAdjustment {
1163 adjustment_type: AdjustmentType,
1165 previous_value: f64,
1167 new_value: f64,
1169 performance_impact: f64,
1171 timestamp: std::time::Instant,
1173}
1174
1175#[derive(Debug, Clone, Copy)]
1177pub enum AdjustmentType {
1178 BlockSize,
1180 MemoryPattern,
1182 Vectorization,
1184 LoadBalancing,
1186}
1187
1188#[derive(Debug, Clone)]
1190#[allow(dead_code)]
1191pub struct MonitoringConfig {
1192 max_history_size: usize,
1194 min_samples_for_trend: usize,
1196 degradation_threshold: f64,
1198 adaptive_optimization_enabled: bool,
1200}
1201
1202impl Default for MonitoringConfig {
1203 fn default() -> Self {
1204 Self {
1205 max_history_size: 1000,
1206 min_samples_for_trend: 10,
1207 degradation_threshold: 0.05, adaptive_optimization_enabled: true,
1209 }
1210 }
1211}
1212
1213impl Default for RealTimePerformanceMonitor {
1214 fn default() -> Self {
1215 Self::with_config(MonitoringConfig::default())
1216 }
1217}
1218
1219impl RealTimePerformanceMonitor {
1220 pub fn new() -> Self {
1222 Self::default()
1223 }
1224
1225 pub fn with_config(config: MonitoringConfig) -> Self {
1227 Self {
1228 performance_history: std::collections::VecDeque::with_capacity(config.max_history_size),
1229 current_optimization: AdaptiveOptimizationState {
1230 trend: PerformanceTrend::Unknown,
1231 adjustments: Vec::new(),
1232 learning_rate: 0.1,
1233 stability_threshold: 0.02,
1234 },
1235 config,
1236 ai_predictor: AIPerformancePredictor::new(),
1237 }
1238 }
1239
1240 pub fn record_performance(&mut self, snapshot: PerformanceSnapshot) {
1242 if self.performance_history.len() >= self.config.max_history_size {
1244 self.performance_history.pop_front();
1245 }
1246 self.performance_history.push_back(snapshot.clone());
1247
1248 let features = vec![
1250 (snapshot.datashape.0 * snapshot.datashape.1) as f64, snapshot.memory_bandwidth_utilization, snapshot.gpu_utilization, 1.0, ];
1255
1256 let performance_score = 1.0 / (1.0 + snapshot.execution_time_ms / 1000.0); self.ai_predictor.add_training_data(PerformanceDataPoint {
1259 features,
1260 target_performance: performance_score,
1261 execution_time: snapshot.execution_time_ms,
1262 });
1263
1264 self.analyze_trend_and_adapt();
1266 }
1267
1268 fn analyze_trend_and_adapt(&mut self) {
1270 if self.performance_history.len() < self.config.min_samples_for_trend {
1271 return;
1272 }
1273
1274 let recent_samples = self.performance_history.len().min(20);
1276 let recent_performances: Vec<f64> = self
1277 .performance_history
1278 .iter()
1279 .rev()
1280 .take(recent_samples)
1281 .map(|snapshot| 1.0 / (1.0 + snapshot.execution_time_ms / 1000.0))
1282 .collect();
1283
1284 let trend = self.calculate_trend(&recent_performances);
1285 self.current_optimization.trend = trend;
1286
1287 if matches!(trend, PerformanceTrend::Degrading) && self.config.adaptive_optimization_enabled
1289 {
1290 self.trigger_adaptive_optimization();
1291 }
1292 }
1293
1294 fn calculate_trend(&self, performances: &[f64]) -> PerformanceTrend {
1296 if performances.len() < 3 {
1297 return PerformanceTrend::Unknown;
1298 }
1299
1300 let n = performances.len() as f64;
1302 let x_mean = (n - 1.0) / 2.0; let y_mean = performances.iter().sum::<f64>() / n;
1304
1305 let mut numerator = 0.0;
1306 let mut denominator = 0.0;
1307
1308 for (i, &y) in performances.iter().enumerate() {
1309 let x = i as f64;
1310 numerator += (x - x_mean) * (y - y_mean);
1311 denominator += (x - x_mean).powi(2);
1312 }
1313
1314 let slope = if denominator != 0.0 {
1315 numerator / denominator
1316 } else {
1317 0.0
1318 };
1319
1320 if slope > self.current_optimization.stability_threshold {
1321 PerformanceTrend::Improving
1322 } else if slope < -self.current_optimization.stability_threshold {
1323 PerformanceTrend::Degrading
1324 } else {
1325 PerformanceTrend::Stable
1326 }
1327 }
1328
1329 fn trigger_adaptive_optimization(&mut self) {
1331 if let Some(latest_snapshot) = self.performance_history.back() {
1333 let current_features = vec![
1334 (latest_snapshot.datashape.0 * latest_snapshot.datashape.1) as f64,
1335 latest_snapshot.memory_bandwidth_utilization,
1336 latest_snapshot.gpu_utilization,
1337 1.0,
1338 ];
1339
1340 let predicted_performance = self.ai_predictor.predict_performance(¤t_features);
1341
1342 if predicted_performance < 0.7 {
1344 let adjustment = OptimizationAdjustment {
1345 adjustment_type: AdjustmentType::BlockSize,
1346 previous_value: 256.0,
1347 new_value: 512.0, performance_impact: 0.0, timestamp: std::time::Instant::now(),
1350 };
1351
1352 self.current_optimization.adjustments.push(adjustment);
1353 }
1354 }
1355 }
1356
1357 pub fn get_current_trend(&self) -> PerformanceTrend {
1359 self.current_optimization.trend
1360 }
1361
1362 pub fn get_performance_stats(&self) -> PerformanceStats {
1364 if self.performance_history.is_empty() {
1365 return PerformanceStats::default();
1366 }
1367
1368 let execution_times: Vec<f64> = self
1369 .performance_history
1370 .iter()
1371 .map(|snapshot| snapshot.execution_time_ms)
1372 .collect();
1373
1374 let mean_execution_time =
1375 execution_times.iter().sum::<f64>() / execution_times.len() as f64;
1376 let min_execution_time = execution_times.iter().fold(f64::INFINITY, |a, &b| a.min(b));
1377 let max_execution_time = execution_times.iter().fold(0.0f64, |a, &b| a.max(b));
1378
1379 let mean_gpu_utilization = self
1380 .performance_history
1381 .iter()
1382 .map(|snapshot| snapshot.gpu_utilization)
1383 .sum::<f64>()
1384 / self.performance_history.len() as f64;
1385
1386 PerformanceStats {
1387 mean_execution_time_ms: mean_execution_time,
1388 min_execution_time_ms: min_execution_time,
1389 max_execution_time_ms: max_execution_time,
1390 mean_gpu_utilization,
1391 sample_count: self.performance_history.len(),
1392 ai_model_accuracy: self.ai_predictor.get_accuracy_metrics().r_squared,
1393 }
1394 }
1395}
1396
1397#[derive(Debug, Clone)]
1399pub struct PerformanceStats {
1400 pub mean_execution_time_ms: f64,
1402 pub min_execution_time_ms: f64,
1404 pub max_execution_time_ms: f64,
1406 pub mean_gpu_utilization: f64,
1408 pub sample_count: usize,
1410 pub ai_model_accuracy: f64,
1412}
1413
1414impl Default for PerformanceStats {
1415 fn default() -> Self {
1416 Self {
1417 mean_execution_time_ms: 0.0,
1418 min_execution_time_ms: 0.0,
1419 max_execution_time_ms: 0.0,
1420 mean_gpu_utilization: 0.0,
1421 sample_count: 0,
1422 ai_model_accuracy: 0.0,
1423 }
1424 }
1425}
1426
1427impl AdvancedGpuOptimizer {
1429 pub fn with_ai_monitoring() -> Self {
1431 Self::new()
1433 }
1434
1435 pub fn predict_optimal_config(
1437 &self,
1438 operation: &str,
1439 datashape: (usize, usize),
1440 historical_data: &[PerformanceDataPoint],
1441 ) -> Result<AdvancedKernelConfig> {
1442 let mut ai_predictor = AIPerformancePredictor::new();
1443
1444 for data_point in historical_data {
1446 ai_predictor.add_training_data(data_point.clone());
1447 }
1448
1449 let features = vec![
1451 (datashape.0 * datashape.1) as f64,
1452 1.0, self.estimate_compute_utilization(operation, datashape),
1454 1.0, ];
1456
1457 let predicted_performance = ai_predictor.predict_performance(&features);
1458
1459 let specialization_level = if predicted_performance > 0.8 {
1461 SpecializationLevel::AIOptimized
1462 } else if predicted_performance > 0.6 {
1463 SpecializationLevel::AdvancedSpecialized
1464 } else {
1465 SpecializationLevel::HardwareOptimized
1466 };
1467
1468 Ok(AdvancedKernelConfig {
1469 specialization_level,
1470 memory_pattern: MemoryAccessPattern::Sequential,
1471 vectorization: VectorizationStrategy::Adaptive,
1472 load_balancing: LoadBalancingMethod::Adaptive,
1473 block_size: 256,
1474 })
1475 }
1476}
1477
1478#[cfg(test)]
1479mod tests {
1480 use super::*;
1481 use crate::gpu::GpuConfig;
1482
1483 #[test]
1484 fn test_advanced_gpu_optimizer_creation() {
1485 let optimizer = AdvancedGpuOptimizer::new();
1486 assert!(optimizer.adaptive_kernels);
1487 assert!(optimizer.auto_tuning);
1488 }
1489
1490 #[test]
1491 fn test_performance_calculation() {
1492 let optimizer = AdvancedGpuOptimizer::new();
1493 let score = optimizer.calculate_performance_score(256, 1e6, 0.8);
1494 assert!((0.0..=1.0).contains(&score));
1495 }
1496
1497 #[test]
1498 fn test_advanced_cpu_generation() {
1499 let optimizer = AdvancedGpuOptimizer::new();
1500 let result = optimizer.execute_advanced_cpu_generation(10, 10, "normal");
1501 assert!(result.is_ok());
1502 let matrix = result.expect("Operation failed");
1503 assert_eq!(matrix.shape(), &[10, 10]);
1504 }
1505
1506 #[test]
1512 fn test_profile_to_kernel_config_uses_tuned_block_size_not_hardcoded() {
1513 let optimizer = AdvancedGpuOptimizer::new();
1514 let profile = GpuPerformanceProfile {
1515 optimal_block_size: 512,
1516 memory_bandwidth: 1e9,
1517 compute_utilization: 0.9,
1518 optimal_layout: DataLayout::RowMajor,
1519 performance_score: 0.9,
1520 };
1521 let config = optimizer.profile_to_kernel_config(&profile);
1522 assert_eq!(config.block_size, 512);
1523
1524 let profile_small = GpuPerformanceProfile {
1525 optimal_block_size: 32,
1526 ..profile
1527 };
1528 let config_small = optimizer.profile_to_kernel_config(&profile_small);
1529 assert_eq!(config_small.block_size, 32);
1530 assert_ne!(config_small.block_size, config.block_size);
1531 }
1532
1533 #[test]
1540 fn test_benchmark_performance_reports_real_measurements_not_fabricated_speedup() {
1541 let optimizer = AdvancedGpuOptimizer::new();
1542 let gpu_context = GpuContext::new(GpuConfig {
1543 backend: GpuBackend::Cpu,
1544 threads_per_block: 1,
1545 ..Default::default()
1546 })
1547 .expect("CPU GpuContext should always construct");
1548
1549 let shapes = [(20, 20), (300, 300)];
1550 let results = optimizer
1551 .benchmark_performance(&gpu_context, "matrix_generation", &shapes)
1552 .expect("benchmark_performance should succeed");
1553
1554 assert_eq!(results.results.len(), 2);
1555 for r in &results.results {
1556 assert!(
1557 r.gpu_time_ms.is_none(),
1558 "CPU-only backend must never report a GPU time"
1559 );
1560 assert!(
1561 r.speedup.is_none(),
1562 "CPU-only backend must never report a fabricated speedup"
1563 );
1564 assert!(r.cpu_time_ms >= 0.0 && r.cpu_time_ms.is_finite());
1565 }
1566 assert!(results.best_speedup().is_none());
1567 assert!(results.average_speedup().is_none());
1568
1569 let small_mem = results.results[0].memory_usage_mb;
1573 let large_mem = results.results[1].memory_usage_mb;
1574 assert!(large_mem > small_mem * 100.0);
1575 }
1576
1577 #[test]
1585 fn test_auto_tuned_profile_is_internally_consistent_real_measurement() {
1586 let optimizer = AdvancedGpuOptimizer::new();
1587 let gpu_context = GpuContext::new(GpuConfig {
1588 backend: GpuBackend::Cpu,
1589 threads_per_block: 1,
1590 ..Default::default()
1591 })
1592 .expect("CPU GpuContext should always construct");
1593
1594 let profile_matmul = optimizer
1595 .auto_tune_operation(&gpu_context, "matrix_multiply", (64, 64))
1596 .expect("auto_tune_operation should succeed");
1597 let profile_trig = optimizer
1598 .auto_tune_operation(&gpu_context, "trigonometric", (64, 64))
1599 .expect("auto_tune_operation should succeed");
1600
1601 for profile in [&profile_matmul, &profile_trig] {
1602 assert!(profile.memory_bandwidth.is_finite());
1603 assert!(profile.memory_bandwidth >= 0.0);
1604 assert!((0.0..=1.0).contains(&profile.compute_utilization));
1605 assert!((0.0..=1.0).contains(&profile.performance_score));
1606 }
1607
1608 assert!(matches!(
1613 profile_matmul.optimal_layout,
1614 DataLayout::RowMajor
1615 ));
1616 assert!(matches!(profile_trig.optimal_layout, DataLayout::Adaptive));
1617 }
1618
1619 #[test]
1626 fn test_gpu_backend_speedup_reflects_real_dispatch_when_available() {
1627 #[cfg(feature = "wgpu")]
1628 {
1629 use scirs2_core::array_protocol::gpu_ndarray::is_gpu_available;
1630 if !is_gpu_available() {
1631 eprintln!("skipping: no wgpu adapter available in this environment");
1632 return;
1633 }
1634
1635 let optimizer = AdvancedGpuOptimizer::new();
1636 let gpu_context = GpuContext::new(GpuConfig {
1637 backend: GpuBackend::Cuda { device_id: 0 },
1638 ..Default::default()
1639 })
1640 .expect("Cuda-flavored GpuContext should construct (query is simulated device info, no real NVIDIA driver required)");
1641
1642 let shapes = [(128, 128)];
1644 let results = optimizer
1645 .benchmark_performance(&gpu_context, "matrix_generation", &shapes)
1646 .expect("benchmark_performance should succeed");
1647
1648 let r = &results.results[0];
1649 assert!(
1650 r.gpu_time_ms.is_some(),
1651 "expected a real GPU dispatch to have run"
1652 );
1653 assert!(r.gpu_time_ms.expect("checked above") > 0.0);
1654 if let Some(speedup) = r.speedup {
1655 assert!(speedup > 0.0 && speedup.is_finite());
1656 assert!(
1657 (speedup - 10.0).abs() > 1e-9,
1658 "matches old hardcoded CUDA factor"
1659 );
1660 assert!(
1661 (speedup - 5.0).abs() > 1e-9,
1662 "matches old hardcoded OpenCL factor"
1663 );
1664 }
1665 }
1666 }
1667}