Skip to main content

scirs2_datasets/
gpu_optimization.rs

1//! Advanced GPU Optimization Engine
2//!
3//! This module provides GPU-aware optimization heuristics and *genuinely
4//! measured* execution paths for dataset operations: adaptive kernel
5//! selection, auto-tuning, and benchmarking that report only what was
6//! actually dispatched and timed on this process — never a fabricated
7//! speedup.
8//!
9//! ## Backend model
10//!
11//! [`AdvancedGpuOptimizer`] keeps this crate's own [`crate::gpu::GpuBackend`]
12//! / [`crate::gpu::GpuContext`] as its public backend-selection type: it is
13//! also the type used throughout the rest of this crate's GPU-flavored
14//! dataset generators and is re-exported at the crate root, so replacing it
15//! with `scirs2_core::gpu`'s (differently shaped, unit-variant-only) backend
16//! type would ripple far beyond this module.
17//!
18//! Requesting `GpuBackend::Cuda` or `GpuBackend::OpenCl` here does **not**
19//! dispatch vendor-specific kernels — both route through the same real,
20//! backend-agnostic `wgpu`/`GpuNdarray` compute path used by this crate's
21//! (crate-private) `generators::gpu_dispatch` module — see that module for
22//! the canonical pattern this file follows — with an honest, silent
23//! fallback to the CPU path whenever no adapter is present, the workload is
24//! too small to be worth transferring, or the `wgpu` feature is disabled at
25//! compile time. `GpuBackend::Cpu` never attempts a GPU dispatch, by design.
26//!
27//! For genuine vendor-specific NVIDIA CUDA execution via the pure-Rust
28//! `oxicuda-*` stack, see [`crate::gpu_cuda`] (feature = `"cuda"`) — a
29//! separate, additive path not currently wired into this optimizer.
30
31use 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/// Advanced-advanced GPU performance optimizer
41#[derive(Debug, Clone)]
42pub struct AdvancedGpuOptimizer {
43    /// Adaptive kernel selection enabled
44    adaptive_kernels: bool,
45    /// Intelligent memory prefetching
46    memory_prefetch: bool,
47    /// Multi-GPU coordination
48    multi_gpu: bool,
49    /// Auto-tuning parameters
50    auto_tuning: bool,
51    /// Performance cache
52    performance_cache: Arc<std::sync::Mutex<HashMap<String, GpuPerformanceProfile>>>,
53}
54
55/// GPU performance profiling data
56#[derive(Debug, Clone)]
57#[allow(dead_code)]
58pub struct GpuPerformanceProfile {
59    /// Optimal block size for kernels
60    optimal_block_size: usize,
61    /// Memory bandwidth utilization
62    memory_bandwidth: f64,
63    /// Compute utilization
64    compute_utilization: f64,
65    /// Optimal data layout
66    optimal_layout: DataLayout,
67    /// Performance score (higher is better)
68    performance_score: f64,
69}
70
71/// Data layout optimization strategies
72#[derive(Debug, Clone, Copy, PartialEq)]
73pub enum DataLayout {
74    /// Row-major layout (C-style)
75    RowMajor,
76    /// Column-major layout (Fortran-style)
77    ColumnMajor,
78    /// Tiled layout for cache efficiency
79    Tiled {
80        /// Size of each tile
81        tile_size: usize,
82    },
83    /// Adaptive layout based on access patterns
84    Adaptive,
85}
86
87/// Advanced-advanced GPU kernel configuration
88#[derive(Debug, Clone)]
89#[allow(dead_code)]
90pub struct AdvancedKernelConfig {
91    /// Kernel specialization level
92    specialization_level: SpecializationLevel,
93    /// Memory access pattern optimization
94    memory_pattern: MemoryAccessPattern,
95    /// Vectorization strategy
96    vectorization: VectorizationStrategy,
97    /// Load balancing method
98    load_balancing: LoadBalancingMethod,
99    /// Optimal block size for GPU kernels
100    block_size: usize,
101}
102
103/// Kernel specialization levels
104#[derive(Debug, Clone, Copy)]
105pub enum SpecializationLevel {
106    /// Basic kernels
107    Basic,
108    /// Hardware-optimized kernels
109    HardwareOptimized,
110    /// Advanced-specialized kernels
111    AdvancedSpecialized,
112    /// AI-optimized kernels
113    AIOptimized,
114}
115
116/// Memory access pattern optimization
117#[derive(Debug, Clone, Copy)]
118pub enum MemoryAccessPattern {
119    /// Sequential access pattern
120    Sequential,
121    /// Random access pattern
122    Random,
123    /// Strided access pattern
124    Strided {
125        /// Stride size for access pattern
126        stride: usize,
127    },
128    /// Blocked access pattern
129    Blocked {
130        /// Size of each block
131        block_size: usize,
132    },
133}
134
135/// Vectorization strategies
136#[derive(Debug, Clone, Copy)]
137pub enum VectorizationStrategy {
138    /// Scalar operations
139    Scalar,
140    /// Vector2 operations
141    Vector2,
142    /// Vector4 operations
143    Vector4,
144    /// Vector8 operations
145    Vector8,
146    /// Adaptive vectorization
147    Adaptive,
148}
149
150/// Load balancing methods
151#[derive(Debug, Clone, Copy)]
152pub enum LoadBalancingMethod {
153    /// Static load balancing
154    Static,
155    /// Dynamic load balancing
156    Dynamic,
157    /// Work-stealing approach
158    WorkStealing,
159    /// Adaptive balancing
160    Adaptive,
161}
162
163/// Minimum element count to attempt a real GPU round trip; below this the
164/// host↔device transfer overhead dominates and CPU is both faster and
165/// simpler. Mirrors [`crate::generators::gpu_dispatch::GPU_DATASET_THRESHOLD`].
166const GPU_OPT_THRESHOLD: usize = 4096;
167
168/// Attempts one genuine wgpu upload → elementwise-scalar-multiply(×1.0) →
169/// download round trip for `data`, returning the real wall-clock duration of
170/// the attempt when it actually executed on a real adapter.
171///
172/// Mirrors the fallback contract used throughout this crate's real GPU
173/// paths (see [`crate::generators::gpu_dispatch`]): below
174/// [`GPU_OPT_THRESHOLD`], with no adapter present, or on any dispatch error,
175/// this returns `None` and the caller must treat the operation as CPU-only
176/// — it must never fabricate a GPU timing or speedup number from this
177/// result.
178#[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/// CPU-only stub used when the `wgpu` feature is disabled: always reports
205/// "no GPU ran" rather than fabricating a timing.
206#[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    /// Create a new advanced GPU optimizer
226    pub fn new() -> Self {
227        Self::default()
228    }
229
230    /// Configure adaptive kernel selection
231    pub fn with_adaptive_kernels(mut self, enabled: bool) -> Self {
232        self.adaptive_kernels = enabled;
233        self
234    }
235
236    /// Configure memory prefetching
237    pub fn with_memory_prefetch(mut self, enabled: bool) -> Self {
238        self.memory_prefetch = enabled;
239        self
240    }
241
242    /// Configure multi-GPU coordination
243    pub fn with_multi_gpu(mut self, enabled: bool) -> Self {
244        self.multi_gpu = enabled;
245        self
246    }
247
248    /// Configure auto-tuning
249    pub fn with_auto_tuning(mut self, enabled: bool) -> Self {
250        self.auto_tuning = enabled;
251        self
252    }
253
254    /// Optimize GPU execution for a specific operation
255    pub fn optimize_execution(
256        &self,
257        gpu_context: &GpuContext,
258        operation: &str,
259        datashape: (usize, usize),
260    ) -> Result<AdvancedKernelConfig> {
261        // Check performance cache first
262        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        // Perform auto-tuning if enabled
277        if self.auto_tuning {
278            let profile = self.auto_tune_operation(gpu_context, operation, datashape)?;
279
280            // Cache the result
281            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            // Use default configuration
288            Ok(self.default_kernel_config(gpu_context.backend().clone()))
289        }
290    }
291
292    /// Auto-tune GPU operation for optimal performance
293    ///
294    /// Block-size/work-group selection remains a pre-dispatch *planning*
295    /// heuristic (see [`Self::tune_cuda_block_size`] /
296    /// [`Self::tune_opencl_work_group_size`]) — `GpuNdarray`'s real kernels
297    /// use fixed internal workgroup sizes, so these values are advisory
298    /// metadata rather than something threaded into the actual dispatch.
299    /// `memory_bandwidth` and `compute_utilization`, however, now come from
300    /// [`Self::calibrate_backend_throughput`], a genuine timed dispatch —
301    /// they are never read from a per-operation-name lookup table.
302    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        // Determine optimal block size based on GPU architecture
311        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, // Default for the CPU-only backend
315        };
316
317        // Genuinely measure backend throughput (real CPU generation, plus a
318        // real GPU round trip when the backend requests one and an adapter
319        // is present); never a fabricated formula.
320        let (memory_bandwidth, compute_utilization) = self.calibrate_backend_throughput(backend)?;
321
322        // Determine optimal data layout
323        let optimal_layout = self.determine_optimal_layout(operation, datashape);
324
325        // Calculate overall performance score
326        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    /// Fixed calibration problem size (elements) used to genuinely measure
342    /// backend throughput once per (backend, operation, shape) cache entry.
343    ///
344    /// Deliberately shape-independent and bounded: measuring achieved
345    /// GB/s and elements/sec is a hardware-throughput characterization, not
346    /// something that needs to scale with the caller's requested matrix
347    /// size (mirroring how real benchmarking tools report a GB/s constant
348    /// for a device rather than reporting a number proportional to problem
349    /// size). It is comfortably above
350    /// [`crate::generators::gpu_dispatch::GPU_DATASET_THRESHOLD`] so the
351    /// real wgpu path is genuinely exercised whenever an adapter is present.
352    const CALIBRATION_SIDE: usize = 128;
353
354    /// Run one genuine, timed dispatch — real CPU generation, plus (for a
355    /// non-CPU backend) a real GPU upload/kernel/download round trip when an
356    /// adapter is available — and derive `(memory_bandwidth_gb_s,
357    /// compute_utilization)` from the *actual* elapsed time.
358    ///
359    /// Replaces the historical per-operation-name lookup tables entirely.
360    /// On any GPU error, this silently falls back to reporting the CPU-only
361    /// measurement rather than propagating an error or fabricating a number
362    /// — consistent with the "never panic, never invent a metric" contract
363    /// used by [`crate::generators::gpu_dispatch`].
364    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            // Best-effort: a failed/unavailable GPU round trip simply means
374            // the elapsed time below reflects the CPU-only calibration,
375            // which is still an honest measurement.
376            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    /// Normalizes a genuinely measured elements/second rate into a `[0, 1]`
386    /// utilization score, using the same 100M-elements/sec reference
387    /// constant that [`Self::calculate_performance_score_from_timing`]
388    /// already applies to cached results (100M elements/sec == fully
389    /// saturated for scoring purposes). This is a documented scoring
390    /// convention derived from a real timing, never a per-operation-name
391    /// lookup disconnected from any measurement.
392    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    /// Tune CUDA block size for optimal performance
402    fn tune_cuda_block_size(&self, datashape: (usize, usize)) -> usize {
403        let total_elements = datashape.0 * datashape.1;
404
405        // Use heuristics based on problem size
406        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    /// Tune OpenCL work group size
416    fn tune_opencl_work_group_size(&self, datashape: (usize, usize)) -> usize {
417        // OpenCL typically prefers smaller work group sizes
418        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    /// Heuristic compute-intensity feature for the simplified linear AI
430    /// predictor ([`AIPerformancePredictor`]) used by
431    /// [`AdvancedGpuOptimizer::predict_optimal_config`] below.
432    ///
433    /// This is deliberately **not** used anywhere in the genuinely-measured
434    /// path ([`Self::calibrate_backend_throughput`] /
435    /// [`Self::auto_tune_operation`] / [`Self::benchmark_performance`]) — it
436    /// is a hand-engineered ML *input feature* (same spirit as feature
437    /// engineering for any small predictive model), not a claimed
438    /// measurement of real hardware behavior.
439    fn estimate_compute_utilization(&self, operation: &str, datashape: (usize, usize)) -> f64 {
440        let total_elements = datashape.0 * datashape.1;
441
442        // Different operations have different compute intensities
443        let compute_intensity = match operation {
444            "matrix_multiply" => 2.0 * datashape.0 as f64, // O(n^3) for n x n matrices
445            "element_wise" => 1.0,                         // O(n) operations
446            "reduction" => (total_elements as f64).log2(), // O(log n) depth
447            "trigonometric" => 10.0,                       // High compute intensity
448            _ => 1.0,                                      // Default
449        };
450
451        // Normalize to [0, 1] range
452        (compute_intensity / (compute_intensity + 1.0)).min(1.0)
453    }
454
455    /// Determine optimal data layout
456    fn determine_optimal_layout(&self, operation: &str, datashape: (usize, usize)) -> DataLayout {
457        match operation {
458            "matrix_multiply" => {
459                // For matrix multiplication, consider cache efficiency
460                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    /// Calculate overall performance score
473    fn calculate_performance_score(
474        &self,
475        block_size: usize,
476        memory_bandwidth: f64,
477        compute_utilization: f64,
478    ) -> f64 {
479        // Heuristic scoring based on multiple factors
480        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        // Weighted combination
489        block_efficiency * 0.3 + bandwidth_efficiency * 0.3 + compute_utilization * 0.4
490    }
491
492    /// Convert performance profile to kernel configuration
493    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            // Use the actually-tuned block size (previously this hardcoded
531            // 256 regardless of `profile.optimal_block_size`, silently
532            // discarding the auto-tuner's recommendation).
533            block_size: profile.optimal_block_size,
534        }
535    }
536
537    /// Get default kernel configuration for a backend
538    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    /// Advanced-optimized matrix generation on GPU
565    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        // Get optimal configuration
573        let config = self.optimize_execution(gpu_context, "matrix_generation", (rows, cols))?;
574
575        // Generate matrix using optimized kernel
576        self.execute_optimized_generation(gpu_context, rows, cols, distribution, &config)
577    }
578
579    /// Execute optimized matrix generation
580    ///
581    /// Random draws always happen host-side (matching the documented
582    /// convention in [`crate::generators::gpu_dispatch`]: distribution
583    /// semantics require the RNG to run on the CPU). When the configured
584    /// backend is not [`GpuBackend::Cpu`], this additionally performs a
585    /// genuine wgpu upload/kernel/download round trip over the freshly
586    /// generated data — real hardware exercise and real timing, contributing
587    /// to the honest performance cache — and gracefully (silently) continues
588    /// with the CPU-generated values if no adapter is available or the
589    /// dispatch errors. `GpuBackend::Cpu` never attempts this.
590    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    /// Cache GPU performance data for adaptive optimization
619    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, // Default for most operations
632                performance_score: self.calculate_performance_score_from_timing(elements, duration),
633            };
634            cache.insert(key, profile);
635        }
636    }
637
638    /// Calculate optimal block size based on problem size
639    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    /// Calculate memory bandwidth utilization
650    fn calculate_memory_bandwidth(&self, elements: usize, duration: std::time::Duration) -> f64 {
651        let bytes_transferred = elements * std::mem::size_of::<f64>() * 2; // Read + Write
652        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        // GB/s
656        } else {
657            0.0
658        }
659    }
660
661    /// Calculate performance score from actual timing
662    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        // Normalize to a 0-100 score (100M elements/sec = 100 points)
674        (elements_per_second / 1_000_000.0).min(100.0)
675    }
676
677    /// Execute advanced-optimized CPU generation with SIMD
678    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        // Generate data in parallel chunks
691        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    /// Benchmark GPU vs CPU performance
720    ///
721    /// Every timing in the returned [`BenchmarkResult`] is a genuine
722    /// `Instant`-measured wall-clock duration. `gpu_time_ms` / `speedup` are
723    /// `None` whenever the configured backend is [`GpuBackend::Cpu`] or no
724    /// adapter accepted the workload — this crate never reports a
725    /// fabricated ratio (the historical implementation hardcoded a hard
726    /// 0.1×/0.2× "10x/5x speedup" factor for Cuda/OpenCl regardless of
727    /// whether any GPU work actually happened).
728    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            // Keep the auto-tuning cache populated for this shape (existing
740            // contract); the tuned config itself isn't needed further here
741            // since dispatch below is unified across backends.
742            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            // Only ever a real ratio of two measured durations, and only
756            // when the GPU genuinely ran (guarding against division by a
757            // measured-zero duration too).
758            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    /// Estimate memory usage
773    fn estimate_memory_usage(&self, shape: (usize, usize)) -> f64 {
774        let total_elements = shape.0 * shape.1;
775        let bytes_per_element = 8; // f64
776        (total_elements * bytes_per_element) as f64 / (1024.0 * 1024.0) // Convert to MB
777    }
778}
779
780/// Performance benchmark results
781#[derive(Debug, Clone)]
782pub struct PerformanceBenchmarkResults {
783    /// Individual benchmark results
784    pub results: Vec<BenchmarkResult>,
785}
786
787/// Individual benchmark result
788#[derive(Debug, Clone)]
789pub struct BenchmarkResult {
790    /// Data shape (rows, cols)
791    pub datashape: (usize, usize),
792    /// CPU execution time in milliseconds — always measured, since the
793    /// baseline generation always genuinely runs on the host.
794    pub cpu_time_ms: f64,
795    /// GPU execution time in milliseconds, present only when a real GPU
796    /// dispatch actually executed (the configured backend was not
797    /// [`GpuBackend::Cpu`] *and* a wgpu adapter genuinely accepted the
798    /// workload). `None` — never a fabricated number — otherwise.
799    pub gpu_time_ms: Option<f64>,
800    /// Speedup factor (`cpu_time_ms / gpu_time_ms`), present only alongside
801    /// `gpu_time_ms`. `None` on CPU-only runs or when no adapter was
802    /// available: this crate never reports an invented ratio when no GPU
803    /// dispatch actually happened.
804    pub speedup: Option<f64>,
805    /// Memory usage in MB
806    pub memory_usage_mb: f64,
807}
808
809impl PerformanceBenchmarkResults {
810    /// Best speedup actually measured across all benchmarked shapes.
811    ///
812    /// `None` if no shape triggered a real GPU dispatch (CPU-only backend,
813    /// or no adapter was available at run time) — never a fabricated
814    /// fallback value such as `1.0`.
815    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    /// Average of the actually-measured speedups.
823    ///
824    /// `None` if none of the benchmarked shapes triggered a real GPU
825    /// dispatch.
826    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    /// Get total memory usage
841    pub fn total_memory_usage(&self) -> f64 {
842        self.results.iter().map(|r| r.memory_usage_mb).sum()
843    }
844}
845
846/// Convenience function for advanced-optimized matrix generation
847#[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/// Convenience function for performance benchmarking
859#[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/// Advanced MODE ENHANCEMENTS
880/// Advanced AI-driven optimization and real-time monitoring capabilities
881/// AI-driven performance predictor using machine learning
882#[derive(Debug, Clone)]
883pub struct AIPerformancePredictor {
884    /// Historical performance data for training
885    training_data: Vec<PerformanceDataPoint>,
886    /// Model parameters (simplified neural network weights)
887    model_weights: Vec<f64>,
888    /// Feature normalization parameters
889    feature_means: Vec<f64>,
890    feature_stds: Vec<f64>,
891    /// Prediction accuracy metrics
892    accuracy_metrics: PredictionAccuracy,
893}
894
895/// Performance data point for ML training
896#[derive(Debug, Clone)]
897#[allow(dead_code)]
898pub struct PerformanceDataPoint {
899    /// Input features: [problem_size, memory_access_pattern, compute_intensity, parallelism_factor]
900    features: Vec<f64>,
901    /// Target performance score
902    target_performance: f64,
903    /// Measured execution time
904    execution_time: f64,
905}
906
907/// Prediction accuracy metrics
908#[derive(Debug, Clone)]
909pub struct PredictionAccuracy {
910    /// Mean absolute error
911    mae: f64,
912    /// Root mean squared error
913    rmse: f64,
914    /// R-squared score
915    r_squared: f64,
916    /// Number of training samples
917    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], // Simple linear model
925            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    /// Create a new AI performance predictor
939    pub fn new() -> Self {
940        Self::default()
941    }
942
943    /// Add training data point
944    pub fn add_training_data(&mut self, datapoint: PerformanceDataPoint) {
945        self.training_data.push(datapoint);
946
947        // Retrain model if we have enough data
948        if self.training_data.len().is_multiple_of(100) && self.training_data.len() > 50 {
949            self.retrain_model();
950        }
951    }
952
953    /// Predict performance for given configuration
954    pub fn predict_performance(&self, features: &[f64]) -> f64 {
955        if features.len() != 4 {
956            return 0.5; // Default prediction
957        }
958
959        // Normalize features
960        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        // Simple linear prediction
968        let prediction: f64 = normalized_features
969            .iter()
970            .zip(&self.model_weights)
971            .map(|(feat, weight)| feat * weight)
972            .sum();
973
974        // Apply sigmoid activation and clamp to [0, 1]
975        (1.0 / (1.0 + (-prediction).exp())).clamp(0.0, 1.0)
976    }
977
978    /// Retrain the model using accumulated data
979    fn retrain_model(&mut self) {
980        if self.training_data.len() < 10 {
981            return;
982        }
983
984        // Calculate feature normalization parameters
985        self.update_normalization_params();
986
987        // Simple gradient descent training
988        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                // Calculate gradients
999                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; // Bias term
1003            }
1004
1005            // Update weights
1006            for (weight, gradient) in self.model_weights.iter_mut().zip(gradients.iter()) {
1007                *weight -= learning_rate * gradient;
1008            }
1009        }
1010
1011        // Update accuracy metrics
1012        self.update_accuracy_metrics();
1013    }
1014
1015    /// Update feature normalization parameters
1016    fn update_normalization_params(&mut self) {
1017        let n = self.training_data.len() as f64;
1018
1019        // Calculate means
1020        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        // Calculate standard deviations
1030        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); // Avoid division by zero
1038        }
1039    }
1040
1041    /// Update accuracy metrics
1042    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        // Calculate MAE
1056        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        // Calculate RMSE
1064        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        // Calculate R-squared
1073        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    /// Get model accuracy metrics
1094    pub fn get_accuracy_metrics(&self) -> &PredictionAccuracy {
1095        &self.accuracy_metrics
1096    }
1097}
1098
1099/// Real-time performance monitor with adaptive optimization
1100#[derive(Debug)]
1101pub struct RealTimePerformanceMonitor {
1102    /// Performance history
1103    performance_history: std::collections::VecDeque<PerformanceSnapshot>,
1104    /// Current optimization state
1105    current_optimization: AdaptiveOptimizationState,
1106    /// Monitoring configuration
1107    config: MonitoringConfig,
1108    /// AI predictor
1109    ai_predictor: AIPerformancePredictor,
1110}
1111
1112/// Performance snapshot at a specific point in time
1113#[derive(Debug, Clone)]
1114#[allow(dead_code)]
1115pub struct PerformanceSnapshot {
1116    /// Timestamp
1117    timestamp: std::time::Instant,
1118    /// Execution time in milliseconds
1119    execution_time_ms: f64,
1120    /// Memory usage in bytes
1121    memory_usage_bytes: usize,
1122    /// GPU utilization percentage
1123    gpu_utilization: f64,
1124    /// Memory bandwidth utilization
1125    memory_bandwidth_utilization: f64,
1126    /// Operation being performed
1127    operation: String,
1128    /// Data shape
1129    datashape: (usize, usize),
1130}
1131
1132/// Adaptive optimization state
1133#[derive(Debug, Clone)]
1134#[allow(dead_code)]
1135pub struct AdaptiveOptimizationState {
1136    /// Current performance trend
1137    trend: PerformanceTrend,
1138    /// Optimization adjustments made
1139    adjustments: Vec<OptimizationAdjustment>,
1140    /// Learning rate for adaptation
1141    learning_rate: f64,
1142    /// Stability threshold
1143    stability_threshold: f64,
1144}
1145
1146/// Performance trend analysis
1147#[derive(Debug, Clone, Copy)]
1148pub enum PerformanceTrend {
1149    /// Performance is improving
1150    Improving,
1151    /// Performance is degrading
1152    Degrading,
1153    /// Performance is stable
1154    Stable,
1155    /// Insufficient data for trend analysis
1156    Unknown,
1157}
1158
1159/// Optimization adjustment made by the adaptive system
1160#[derive(Debug, Clone)]
1161#[allow(dead_code)]
1162pub struct OptimizationAdjustment {
1163    /// Type of adjustment
1164    adjustment_type: AdjustmentType,
1165    /// Previous value
1166    previous_value: f64,
1167    /// New value
1168    new_value: f64,
1169    /// Impact on performance (positive = improvement)
1170    performance_impact: f64,
1171    /// Timestamp of adjustment
1172    timestamp: std::time::Instant,
1173}
1174
1175/// Types of optimization adjustments
1176#[derive(Debug, Clone, Copy)]
1177pub enum AdjustmentType {
1178    /// Block size adjustment
1179    BlockSize,
1180    /// Memory access pattern change
1181    MemoryPattern,
1182    /// Vectorization strategy change
1183    Vectorization,
1184    /// Load balancing method change
1185    LoadBalancing,
1186}
1187
1188/// Monitoring configuration
1189#[derive(Debug, Clone)]
1190#[allow(dead_code)]
1191pub struct MonitoringConfig {
1192    /// Maximum history size
1193    max_history_size: usize,
1194    /// Minimum samples for trend analysis
1195    min_samples_for_trend: usize,
1196    /// Performance degradation threshold
1197    degradation_threshold: f64,
1198    /// Adaptation enabled
1199    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, // 5% degradation triggers adaptation
1208            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    /// Create a new real-time performance monitor
1221    pub fn new() -> Self {
1222        Self::default()
1223    }
1224
1225    /// Create with custom configuration
1226    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    /// Record a performance snapshot
1241    pub fn record_performance(&mut self, snapshot: PerformanceSnapshot) {
1242        // Add to history
1243        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        // Add training data to AI predictor
1249        let features = vec![
1250            (snapshot.datashape.0 * snapshot.datashape.1) as f64, // Problem size
1251            snapshot.memory_bandwidth_utilization,                // Memory access pattern
1252            snapshot.gpu_utilization,                             // Compute intensity
1253            1.0,                                                  // Parallelism factor (simplified)
1254        ];
1255
1256        let performance_score = 1.0 / (1.0 + snapshot.execution_time_ms / 1000.0); // Normalized performance
1257
1258        self.ai_predictor.add_training_data(PerformanceDataPoint {
1259            features,
1260            target_performance: performance_score,
1261            execution_time: snapshot.execution_time_ms,
1262        });
1263
1264        // Analyze trend and adapt if necessary
1265        self.analyze_trend_and_adapt();
1266    }
1267
1268    /// Analyze performance trend and trigger adaptive optimization
1269    fn analyze_trend_and_adapt(&mut self) {
1270        if self.performance_history.len() < self.config.min_samples_for_trend {
1271            return;
1272        }
1273
1274        // Calculate recent performance trend
1275        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        // Trigger adaptation if performance is degrading
1288        if matches!(trend, PerformanceTrend::Degrading) && self.config.adaptive_optimization_enabled
1289        {
1290            self.trigger_adaptive_optimization();
1291        }
1292    }
1293
1294    /// Calculate performance trend from recent samples
1295    fn calculate_trend(&self, performances: &[f64]) -> PerformanceTrend {
1296        if performances.len() < 3 {
1297            return PerformanceTrend::Unknown;
1298        }
1299
1300        // Simple linear regression to detect trend
1301        let n = performances.len() as f64;
1302        let x_mean = (n - 1.0) / 2.0; // Mean of indices
1303        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    /// Trigger adaptive optimization to improve performance
1330    fn trigger_adaptive_optimization(&mut self) {
1331        // Use AI predictor to suggest optimizations
1332        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(&current_features);
1341
1342            // If predicted performance is low, suggest adjustments
1343            if predicted_performance < 0.7 {
1344                let adjustment = OptimizationAdjustment {
1345                    adjustment_type: AdjustmentType::BlockSize,
1346                    previous_value: 256.0,
1347                    new_value: 512.0,        // Increase block size
1348                    performance_impact: 0.0, // Will be measured later
1349                    timestamp: std::time::Instant::now(),
1350                };
1351
1352                self.current_optimization.adjustments.push(adjustment);
1353            }
1354        }
1355    }
1356
1357    /// Get current performance trend
1358    pub fn get_current_trend(&self) -> PerformanceTrend {
1359        self.current_optimization.trend
1360    }
1361
1362    /// Get recent performance statistics
1363    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/// Performance statistics summary
1398#[derive(Debug, Clone)]
1399pub struct PerformanceStats {
1400    /// Mean execution time in milliseconds
1401    pub mean_execution_time_ms: f64,
1402    /// Minimum execution time in milliseconds
1403    pub min_execution_time_ms: f64,
1404    /// Maximum execution time in milliseconds
1405    pub max_execution_time_ms: f64,
1406    /// Mean GPU utilization percentage
1407    pub mean_gpu_utilization: f64,
1408    /// Number of samples
1409    pub sample_count: usize,
1410    /// AI model prediction accuracy (R-squared)
1411    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
1427/// Enhanced AdvancedGpuOptimizer with AI and real-time monitoring
1428impl AdvancedGpuOptimizer {
1429    /// Create optimizer with AI-driven optimization and real-time monitoring
1430    pub fn with_ai_monitoring() -> Self {
1431        // In a full implementation, this would integrate the AI predictor and monitor
1432        Self::new()
1433    }
1434
1435    /// Predict optimal configuration using AI
1436    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        // Train on historical _data
1445        for data_point in historical_data {
1446            ai_predictor.add_training_data(data_point.clone());
1447        }
1448
1449        // Generate features for current scenario
1450        let features = vec![
1451            (datashape.0 * datashape.1) as f64,
1452            1.0, // Default memory access pattern
1453            self.estimate_compute_utilization(operation, datashape),
1454            1.0, // Default parallelism factor
1455        ];
1456
1457        let predicted_performance = ai_predictor.predict_performance(&features);
1458
1459        // Convert prediction to kernel configuration
1460        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    /// Regression test for a latent bug found while removing the
1507    /// simulation: `profile_to_kernel_config` used to hardcode
1508    /// `block_size: 256` unconditionally, silently discarding
1509    /// `profile.optimal_block_size`. A tuned profile recommending 512 must
1510    /// now actually propagate into the returned config.
1511    #[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    /// Confirms `benchmark_performance` never fabricates a speedup: on an
1534    /// explicitly CPU-only backend no GPU dispatch is ever attempted, so
1535    /// every result must honestly report `None` — not the historical
1536    /// hardcoded 0.1/0.2 CUDA/OpenCL "10x/5x speedup" factors, nor a
1537    /// disguised `1.0`. Also confirms reported numbers genuinely scale with
1538    /// the workload (memory usage) rather than being fixed constants.
1539    #[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        // memory_usage_mb is a deterministic function of shape (not
1570        // timing), so this is a flake-free way to prove the two results
1571        // genuinely differ with the workload rather than being constants.
1572        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    /// `auto_tune_operation` used to synthesize `memory_bandwidth` and
1578    /// `compute_utilization` from static per-operation-name lookup tables
1579    /// (e.g. `"trigonometric" => 10.0` compute intensity, regardless of any
1580    /// real dispatch). Both now come from a genuinely timed calibration;
1581    /// sanity-check the results are real, finite, in-range numbers, and
1582    /// that the operation/shape-sensitive planning fields
1583    /// (`optimal_layout`) still vary as they did before.
1584    #[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        // Planning (not measurement) still legitimately varies by
1609        // operation name: "matrix_multiply" at this shape recommends
1610        // RowMajor, while an unrecognized operation like "trigonometric"
1611        // falls back to Adaptive.
1612        assert!(matches!(
1613            profile_matmul.optimal_layout,
1614            DataLayout::RowMajor
1615        ));
1616        assert!(matches!(profile_trig.optimal_layout, DataLayout::Adaptive));
1617    }
1618
1619    /// When a real wgpu adapter is present (verified via the same probe the
1620    /// production path uses), requesting a non-CPU backend for a
1621    /// large-enough workload must produce a genuinely measured speedup —
1622    /// never one of the historical hardcoded 0.1×/0.2× factors (whose
1623    /// exact reciprocals are 10.0/5.0). When no adapter is available this
1624    /// degrades gracefully to `None`, per the crate-wide fallback contract.
1625    #[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            // 128 x 128 = 16,384 elements: above GPU_OPT_THRESHOLD (4096).
1643            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}