Skip to main content

sklears_utils/gpu_computing/
mod.rs

1// Copyright 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3
4use std::collections::HashMap;
5use std::sync::{Arc, RwLock};
6use std::time::Instant;
7/// GPU device information.
8///
9/// Every field here is either taken verbatim from `oxicuda-driver`'s device
10/// query (`name`, `memory_total`/`memory_available`, `compute_capability`,
11/// `multiprocessor_count`, `clock_rate_mhz`) or is a textbook theoretical-peak
12/// formula computed from real queried values (`memory_bandwidth_bytes_per_sec`,
13/// documented on the field). Nothing here is fabricated: on a host with no
14/// CUDA driver/device, [`GpuUtils::init_devices`] simply produces an empty
15/// device list rather than inventing one.
16#[derive(Debug, Clone)]
17pub struct GpuDevice {
18    pub id: u32,
19    pub name: String,
20    pub memory_total: u64,
21    pub memory_available: u64,
22    pub compute_capability: (u32, u32),
23    /// Number of streaming multiprocessors (SMs), as reported by the driver.
24    /// This is *not* the marketing "CUDA core count" (which depends on an
25    /// architecture-specific cores-per-SM multiplier the driver API does not
26    /// expose) -- it is the real, directly-queried SM count.
27    pub multiprocessor_count: u32,
28    /// Core clock rate in MHz, as reported by the driver.
29    pub clock_rate_mhz: u32,
30    /// Theoretical peak memory bandwidth in bytes/sec, computed from the
31    /// real queried memory clock and bus width as
32    /// `2 * memory_clock_hz * (bus_width_bits / 8)` (the standard
33    /// double-data-rate deviceQuery-style formula). This is a derived
34    /// estimate from real hardware specs, not a measured/benchmarked figure
35    /// and not a fabricated number.
36    pub memory_bandwidth_bytes_per_sec: u64,
37}
38/// Enumerates real CUDA devices via `oxicuda-driver`.
39///
40/// Returns an empty `Vec` -- never a fabricated device -- whenever there is
41/// no CUDA driver installed, no device is present, or this crate was built
42/// without the `gpu` feature. This mirrors `GpuBackend::detect()`'s
43/// `Ok(None)`-on-no-GPU contract in `sklears_core::gpu`.
44#[cfg(feature = "gpu")]
45fn enumerate_real_devices() -> Vec<GpuDevice> {
46    if oxicuda_driver::init().is_err() {
47        return Vec::new();
48    }
49    let count = match oxicuda_driver::Device::count() {
50        Ok(c) if c > 0 => c,
51        _ => return Vec::new(),
52    };
53    let mut devices = Vec::with_capacity(count as usize);
54    for ordinal in 0..count {
55        let Ok(device) = oxicuda_driver::Device::get(ordinal) else {
56            continue;
57        };
58        let Ok(info) = device.info() else {
59            continue;
60        };
61        let free_memory = oxicuda_driver::memory_info::device_memory_info()
62            .map(|(free, _total)| free as u64)
63            .unwrap_or(info.total_memory_bytes as u64);
64        let memory_clock_hz = info.memory_clock_rate_mhz * 1e6;
65        let bus_width_bytes = f64::from(info.memory_bus_width_bits) / 8.0;
66        let memory_bandwidth_bytes_per_sec = (2.0 * memory_clock_hz * bus_width_bytes) as u64;
67        devices.push(GpuDevice {
68            id: ordinal.max(0) as u32,
69            name: info.name,
70            memory_total: info.total_memory_bytes as u64,
71            memory_available: free_memory,
72            compute_capability: (
73                info.compute_capability.0.max(0) as u32,
74                info.compute_capability.1.max(0) as u32,
75            ),
76            multiprocessor_count: info.multiprocessor_count.max(0) as u32,
77            clock_rate_mhz: info.clock_rate_mhz.max(0.0) as u32,
78            memory_bandwidth_bytes_per_sec,
79        });
80    }
81    devices
82}
83/// Non-`gpu`-feature build: always reports no devices, honestly. There is no
84/// CUDA driver code compiled into this build at all.
85#[cfg(not(feature = "gpu"))]
86fn enumerate_real_devices() -> Vec<GpuDevice> {
87    Vec::new()
88}
89/// GPU memory allocation tracking
90#[derive(Debug, Clone)]
91pub struct GpuMemoryAllocation {
92    pub ptr: u64,
93    pub size: u64,
94    pub device_id: u32,
95    pub allocated_at: Instant,
96    pub name: String,
97}
98/// GPU kernel execution info
99#[derive(Debug, Clone)]
100pub struct GpuKernelExecution {
101    pub kernel_name: String,
102    pub device_id: u32,
103    pub grid_size: (u32, u32, u32),
104    pub block_size: (u32, u32, u32),
105    pub shared_memory: u32,
106    pub execution_time: f64,
107    pub parameters: HashMap<String, String>,
108}
109/// GPU computing utilities
110#[derive(Debug)]
111pub struct GpuUtils {
112    devices: Vec<GpuDevice>,
113    allocations: Arc<RwLock<HashMap<u64, GpuMemoryAllocation>>>,
114    kernel_executions: Arc<RwLock<Vec<GpuKernelExecution>>>,
115    performance_counters: Arc<RwLock<HashMap<String, f64>>>,
116}
117impl GpuUtils {
118    /// Create new GPU utilities
119    pub fn new() -> Self {
120        Self {
121            devices: Vec::new(),
122            allocations: Arc::new(RwLock::new(HashMap::new())),
123            kernel_executions: Arc::new(RwLock::new(Vec::new())),
124            performance_counters: Arc::new(RwLock::new(HashMap::new())),
125        }
126    }
127    /// Initialize GPU devices.
128    ///
129    /// Behind the `gpu` feature, this walks the real `oxicuda-driver` device
130    /// list. On a host with no CUDA driver installed, or with the `gpu`
131    /// feature disabled, this always yields an empty device list -- never a
132    /// fabricated device. See `enumerate_real_devices`.
133    pub fn init_devices(&mut self) -> Result<(), GpuError> {
134        self.devices = enumerate_real_devices();
135        Ok(())
136    }
137    /// Get available GPU devices
138    pub fn get_devices(&self) -> &[GpuDevice] {
139        &self.devices
140    }
141    /// Get device by ID
142    pub fn get_device(&self, id: u32) -> Option<&GpuDevice> {
143        self.devices.iter().find(|d| d.id == id)
144    }
145    /// Get best device for ML workloads: the one with the highest
146    /// `multiprocessor_count * clock_rate_mhz` (a real-hardware-derived
147    /// throughput proxy), falling back to the first enumerated device.
148    ///
149    /// Unlike the pre-migration version, this no longer filters out
150    /// "integrated" devices: the driver API this crate queries
151    /// (`oxicuda-driver`'s `DeviceInfo`) has no integrated/discrete flag, so
152    /// pretending to know that distinction would itself be a fabrication.
153    pub fn get_best_device(&self) -> Option<&GpuDevice> {
154        self.devices
155            .iter()
156            .max_by_key(|d| u64::from(d.multiprocessor_count) * u64::from(d.clock_rate_mhz))
157            .or_else(|| self.devices.first())
158    }
159    /// Allocate GPU memory
160    pub fn allocate_memory(&self, size: u64, device_id: u32, name: &str) -> Result<u64, GpuError> {
161        let device = self.get_device(device_id).ok_or(GpuError::DeviceNotFound)?;
162        if size > device.memory_available {
163            return Err(GpuError::OutOfMemory);
164        }
165        let ptr = (std::ptr::null::<u8>() as u64) + size;
166        let allocation = GpuMemoryAllocation {
167            ptr,
168            size,
169            device_id,
170            allocated_at: Instant::now(),
171            name: name.to_string(),
172        };
173        self.allocations
174            .write()
175            .expect("operation should succeed")
176            .insert(ptr, allocation);
177        Ok(ptr)
178    }
179    /// Free GPU memory
180    pub fn free_memory(&self, ptr: u64) -> Result<(), GpuError> {
181        let mut allocations = self.allocations.write().expect("operation should succeed");
182        allocations.remove(&ptr).ok_or(GpuError::InvalidPointer)?;
183        Ok(())
184    }
185    /// Get memory usage statistics
186    pub fn get_memory_stats(&self) -> HashMap<u32, MemoryStats> {
187        let allocations = self.allocations.read().expect("operation should succeed");
188        let mut stats = HashMap::new();
189        for device in &self.devices {
190            let device_allocations: Vec<_> = allocations
191                .values()
192                .filter(|a| a.device_id == device.id)
193                .collect();
194            let total_allocated = device_allocations.iter().map(|a| a.size).sum();
195            let num_allocations = device_allocations.len();
196            stats.insert(
197                device.id,
198                MemoryStats {
199                    total_memory: device.memory_total,
200                    available_memory: device.memory_available,
201                    allocated_memory: total_allocated,
202                    free_memory: device.memory_available - total_allocated,
203                    num_allocations,
204                    largest_allocation: device_allocations
205                        .iter()
206                        .map(|a| a.size)
207                        .max()
208                        .unwrap_or(0),
209                    fragmentation_ratio: if num_allocations > 0 {
210                        (num_allocations as f64) / (total_allocated as f64 / 1024.0)
211                    } else {
212                        0.0
213                    },
214                },
215            );
216        }
217        stats
218    }
219    /// Execute GPU kernel
220    pub fn execute_kernel(&self, kernel: &GpuKernelInfo) -> Result<GpuKernelExecution, GpuError> {
221        let _device = self
222            .get_device(kernel.device_id)
223            .ok_or(GpuError::DeviceNotFound)?;
224        let start_time = Instant::now();
225        std::thread::sleep(std::time::Duration::from_millis(1));
226        let execution_time = start_time.elapsed().as_secs_f64() * 1000.0;
227        let execution = GpuKernelExecution {
228            kernel_name: kernel.name.clone(),
229            device_id: kernel.device_id,
230            grid_size: kernel.grid_size,
231            block_size: kernel.block_size,
232            shared_memory: kernel.shared_memory,
233            execution_time,
234            parameters: kernel.parameters.clone(),
235        };
236        self.kernel_executions
237            .write()
238            .expect("operation should succeed")
239            .push(execution.clone());
240        Ok(execution)
241    }
242    /// Get kernel execution history
243    pub fn get_kernel_history(&self) -> Vec<GpuKernelExecution> {
244        self.kernel_executions
245            .read()
246            .expect("operation should succeed")
247            .clone()
248    }
249    /// Get performance counters
250    pub fn get_performance_counters(&self) -> HashMap<String, f64> {
251        self.performance_counters
252            .read()
253            .expect("operation should succeed")
254            .clone()
255    }
256    /// Update performance counter
257    pub fn update_counter(&self, name: &str, value: f64) {
258        self.performance_counters
259            .write()
260            .expect("operation should succeed")
261            .insert(name.to_string(), value);
262    }
263    /// Get throughput estimate for array operations
264    pub fn estimate_throughput(&self, device_id: u32, array_size: usize, operation: &str) -> f64 {
265        let device = match self.get_device(device_id) {
266            Some(d) => d,
267            None => return 0.0,
268        };
269        let base_throughput = match operation {
270            "add" | "subtract" | "multiply" => device.memory_bandwidth_bytes_per_sec as f64 * 0.8,
271            "divide" | "sqrt" | "exp" | "log" => device.memory_bandwidth_bytes_per_sec as f64 * 0.6,
272            "matrix_multiply" => {
273                (device.multiprocessor_count as f64 * device.clock_rate_mhz as f64 * 1e6) * 0.5
274            }
275            "fft" => {
276                (device.multiprocessor_count as f64 * device.clock_rate_mhz as f64 * 1e6) * 0.3
277            }
278            _ => device.memory_bandwidth_bytes_per_sec as f64 * 0.5,
279        };
280        let array_factor = (array_size as f64).log2() / 20.0;
281        base_throughput * (1.0 - array_factor.min(0.5))
282    }
283    /// Check if operation should use GPU
284    pub fn should_use_gpu(&self, array_size: usize, operation: &str) -> bool {
285        if self.devices.is_empty() {
286            return false;
287        }
288        let min_size = match operation {
289            "add" | "subtract" | "multiply" | "divide" => 1000,
290            "matrix_multiply" => 100,
291            "fft" | "conv" => 512,
292            _ => 1000,
293        };
294        array_size >= min_size
295    }
296    /// Get GPU utilization
297    pub fn get_utilization(&self) -> HashMap<u32, f64> {
298        let mut utilization = HashMap::new();
299        for device in &self.devices {
300            let recent_executions = self
301                .kernel_executions
302                .read()
303                .expect("operation should succeed")
304                .iter()
305                .filter(|e| e.device_id == device.id)
306                .filter(|e| e.execution_time > 0.0)
307                .count();
308            let util = (recent_executions as f64 / 10.0).min(1.0);
309            utilization.insert(device.id, util);
310        }
311        utilization
312    }
313    /// Cleanup all resources
314    pub fn cleanup(&self) -> Result<(), GpuError> {
315        let allocations = self.allocations.read().expect("operation should succeed");
316        if !allocations.is_empty() {
317            return Err(GpuError::ResourcesNotFreed);
318        }
319        self.kernel_executions
320            .write()
321            .expect("operation should succeed")
322            .clear();
323        self.performance_counters
324            .write()
325            .expect("operation should succeed")
326            .clear();
327        Ok(())
328    }
329}
330/// GPU kernel execution information
331#[derive(Debug, Clone)]
332pub struct GpuKernelInfo {
333    pub name: String,
334    pub device_id: u32,
335    pub grid_size: (u32, u32, u32),
336    pub block_size: (u32, u32, u32),
337    pub shared_memory: u32,
338    pub parameters: HashMap<String, String>,
339}
340/// GPU memory usage statistics
341#[derive(Debug, Clone)]
342pub struct MemoryStats {
343    pub total_memory: u64,
344    pub available_memory: u64,
345    pub allocated_memory: u64,
346    pub free_memory: u64,
347    pub num_allocations: usize,
348    pub largest_allocation: u64,
349    pub fragmentation_ratio: f64,
350}
351/// GPU array operations.
352///
353/// `add_arrays`/`multiply_arrays`/`matrix_multiply` dispatch to a real
354/// oxicuda-backed GPU (via `sklears_core::gpu`) when built with the `gpu`
355/// feature and `device_id` names an actual present device; otherwise (no
356/// `gpu` feature, or no such device on this host) they transparently fall
357/// back to the CPU implementation below -- same pattern as
358/// `sklears-svm`'s `DeviceNotAvailable` skip. `apply_activation`/
359/// `reduce_sum`/`reduce_max` remain CPU-only: there is no oxicuda-primitives
360/// elementwise/reduction kernel wired for them yet (deferred 2026-07-06, see
361/// `TODO.md`).
362pub struct GpuArrayOps;
363impl GpuArrayOps {
364    /// Add two arrays, on GPU if `device_id` names a present device and this
365    /// crate was built with the `gpu` feature, on CPU otherwise.
366    pub fn add_arrays(a: &[f32], b: &[f32], device_id: u32) -> Result<Vec<f32>, GpuError> {
367        if a.len() != b.len() {
368            return Err(GpuError::ShapeMismatch);
369        }
370        if let Some(result) = gpu_add_arrays(a, b, device_id)? {
371            return Ok(result);
372        }
373        Ok(a.iter().zip(b.iter()).map(|(x, y)| x + y).collect())
374    }
375    /// Multiply two arrays elementwise, on GPU if `device_id` names a
376    /// present device and this crate was built with the `gpu` feature, on
377    /// CPU otherwise.
378    pub fn multiply_arrays(a: &[f32], b: &[f32], device_id: u32) -> Result<Vec<f32>, GpuError> {
379        if a.len() != b.len() {
380            return Err(GpuError::ShapeMismatch);
381        }
382        if let Some(result) = gpu_multiply_arrays(a, b, device_id)? {
383            return Ok(result);
384        }
385        Ok(a.iter().zip(b.iter()).map(|(x, y)| x * y).collect())
386    }
387    /// Matrix multiplication (`a` is `m x k`, `b` is `k x n`, row-major), on
388    /// GPU if `device_id` names a present device and this crate was built
389    /// with the `gpu` feature, on CPU otherwise.
390    pub fn matrix_multiply(
391        a: &[f32],
392        b: &[f32],
393        m: usize,
394        n: usize,
395        k: usize,
396        device_id: u32,
397    ) -> Result<Vec<f32>, GpuError> {
398        if a.len() != m * k || b.len() != k * n {
399            return Err(GpuError::ShapeMismatch);
400        }
401        if let Some(result) = gpu_matrix_multiply(a, b, m, n, k, device_id)? {
402            return Ok(result);
403        }
404        let mut result = vec![0.0f32; m * n];
405        for i in 0..m {
406            for j in 0..n {
407                for l in 0..k {
408                    result[i * n + j] += a[i * k + l] * b[l * n + j];
409                }
410            }
411        }
412        Ok(result)
413    }
414    /// Apply activation function.
415    ///
416    /// CPU-only: no oxicuda elementwise kernel is wired for activation
417    /// functions yet (deferred 2026-07-06, see `TODO.md`). `device_id` is
418    /// accepted for API symmetry with the other ops but unused.
419    pub fn apply_activation(
420        input: &[f32],
421        activation: ActivationFunction,
422        _device_id: u32,
423    ) -> Result<Vec<f32>, GpuError> {
424        let result: Vec<f32> = input
425            .iter()
426            .map(|&x| match activation {
427                ActivationFunction::ReLU => x.max(0.0),
428                ActivationFunction::Sigmoid => 1.0 / (1.0 + (-x).exp()),
429                ActivationFunction::Tanh => x.tanh(),
430                ActivationFunction::Softmax => x.exp(),
431            })
432            .collect();
433        Ok(result)
434    }
435    /// Compute the sum reduction.
436    ///
437    /// CPU-only: no oxicuda reduction kernel is wired for this op yet
438    /// (deferred 2026-07-06, see `TODO.md`). `device_id` is accepted for API
439    /// symmetry with the other ops but unused.
440    pub fn reduce_sum(input: &[f32], _device_id: u32) -> Result<f32, GpuError> {
441        Ok(input.iter().sum())
442    }
443    /// Compute the max reduction.
444    ///
445    /// CPU-only: no oxicuda reduction kernel is wired for this op yet
446    /// (deferred 2026-07-06, see `TODO.md`). `device_id` is accepted for API
447    /// symmetry with the other ops but unused.
448    pub fn reduce_max(input: &[f32], _device_id: u32) -> Result<f32, GpuError> {
449        input
450            .iter()
451            .fold(f32::NEG_INFINITY, |a, &b| a.max(b))
452            .is_finite()
453            .then_some(input.iter().fold(f32::NEG_INFINITY, |a, &b| a.max(b)))
454            .ok_or(GpuError::ComputationError)
455    }
456}
457/// Attempts a real GPU-backed elementwise add. Returns `Ok(None)` when no
458/// device is present at `device_id` (or the `gpu` feature is off) so the
459/// caller falls back to CPU; returns `Err` only for a genuine GPU failure
460/// (as opposed to "no GPU here").
461#[cfg(feature = "gpu")]
462fn gpu_add_arrays(a: &[f32], b: &[f32], device_id: u32) -> Result<Option<Vec<f32>>, GpuError> {
463    use sklears_core::gpu::{GpuArray, GpuBackend, GpuMatrixOps};
464    let Some(backend) = GpuBackend::with_device_id(device_id as usize)
465        .map_err(|e| GpuError::InitializationFailed(e.to_string()))?
466    else {
467        return Ok(None);
468    };
469    let ga = GpuArray::<f32>::from_slice(&backend, a)
470        .map_err(|e| GpuError::InitializationFailed(e.to_string()))?;
471    let gb = GpuArray::<f32>::from_slice(&backend, b)
472        .map_err(|e| GpuError::InitializationFailed(e.to_string()))?;
473    let sum = ga
474        .add(&gb)
475        .map_err(|e| GpuError::InitializationFailed(e.to_string()))?;
476    Ok(Some(sum.to_cpu().map_err(|e| {
477        GpuError::InitializationFailed(e.to_string())
478    })?))
479}
480#[cfg(not(feature = "gpu"))]
481fn gpu_add_arrays(_a: &[f32], _b: &[f32], _device_id: u32) -> Result<Option<Vec<f32>>, GpuError> {
482    Ok(None)
483}
484/// Attempts a real GPU-backed elementwise multiply. See
485/// [`gpu_add_arrays`] for the `Ok(None)`-means-"fall back to CPU" contract.
486#[cfg(feature = "gpu")]
487fn gpu_multiply_arrays(a: &[f32], b: &[f32], device_id: u32) -> Result<Option<Vec<f32>>, GpuError> {
488    use sklears_core::gpu::{GpuArray, GpuBackend, GpuMatrixOps};
489    let Some(backend) = GpuBackend::with_device_id(device_id as usize)
490        .map_err(|e| GpuError::InitializationFailed(e.to_string()))?
491    else {
492        return Ok(None);
493    };
494    let ga = GpuArray::<f32>::from_slice(&backend, a)
495        .map_err(|e| GpuError::InitializationFailed(e.to_string()))?;
496    let gb = GpuArray::<f32>::from_slice(&backend, b)
497        .map_err(|e| GpuError::InitializationFailed(e.to_string()))?;
498    let prod = ga
499        .mul(&gb)
500        .map_err(|e| GpuError::InitializationFailed(e.to_string()))?;
501    Ok(Some(prod.to_cpu().map_err(|e| {
502        GpuError::InitializationFailed(e.to_string())
503    })?))
504}
505#[cfg(not(feature = "gpu"))]
506fn gpu_multiply_arrays(
507    _a: &[f32],
508    _b: &[f32],
509    _device_id: u32,
510) -> Result<Option<Vec<f32>>, GpuError> {
511    Ok(None)
512}
513/// Attempts a real GPU-backed GEMM (`a` is `m x k`, `b` is `k x n`,
514/// row-major). See [`gpu_add_arrays`] for the `Ok(None)`-means-"fall back to
515/// CPU" contract.
516#[cfg(feature = "gpu")]
517fn gpu_matrix_multiply(
518    a: &[f32],
519    b: &[f32],
520    m: usize,
521    n: usize,
522    k: usize,
523    device_id: u32,
524) -> Result<Option<Vec<f32>>, GpuError> {
525    use scirs2_core::ndarray::Array2;
526    use sklears_core::gpu::{GpuArray, GpuBackend, GpuMatrixOps};
527    let Some(backend) = GpuBackend::with_device_id(device_id as usize)
528        .map_err(|e| GpuError::InitializationFailed(e.to_string()))?
529    else {
530        return Ok(None);
531    };
532    let a2 = Array2::from_shape_vec((m, k), a.to_vec())
533        .map_err(|e| GpuError::InitializationFailed(e.to_string()))?;
534    let b2 = Array2::from_shape_vec((k, n), b.to_vec())
535        .map_err(|e| GpuError::InitializationFailed(e.to_string()))?;
536    let ga = GpuArray::<f32>::from_array2(&backend, &a2)
537        .map_err(|e| GpuError::InitializationFailed(e.to_string()))?;
538    let gb = GpuArray::<f32>::from_array2(&backend, &b2)
539        .map_err(|e| GpuError::InitializationFailed(e.to_string()))?;
540    let gc = ga
541        .matmul(&gb)
542        .map_err(|e| GpuError::InitializationFailed(e.to_string()))?;
543    let c2 = gc
544        .to_array2()
545        .map_err(|e| GpuError::InitializationFailed(e.to_string()))?;
546    Ok(Some(c2.into_raw_vec_and_offset().0))
547}
548#[cfg(not(feature = "gpu"))]
549fn gpu_matrix_multiply(
550    _a: &[f32],
551    _b: &[f32],
552    _m: usize,
553    _n: usize,
554    _k: usize,
555    _device_id: u32,
556) -> Result<Option<Vec<f32>>, GpuError> {
557    Ok(None)
558}
559/// GPU activation functions
560#[derive(Debug, Clone, Copy)]
561pub enum ActivationFunction {
562    ReLU,
563    Sigmoid,
564    Tanh,
565    Softmax,
566}
567/// GPU computing errors
568#[derive(Debug, thiserror::Error)]
569pub enum GpuError {
570    #[error("GPU device not found")]
571    DeviceNotFound,
572    #[error("Out of GPU memory")]
573    OutOfMemory,
574    #[error("Invalid GPU pointer")]
575    InvalidPointer,
576    #[error("GPU computation error")]
577    ComputationError,
578    #[error("Array shape mismatch")]
579    ShapeMismatch,
580    #[error("GPU resources not freed")]
581    ResourcesNotFreed,
582    #[error("GPU initialization failed: {0}")]
583    InitializationFailed(String),
584}
585/// GPU performance profiler
586#[derive(Debug)]
587pub struct GpuProfiler {
588    kernel_times: HashMap<String, Vec<f64>>,
589    memory_transfers: Vec<(Instant, u64, String)>,
590    device_utilization: HashMap<u32, Vec<(Instant, f64)>>,
591}
592impl GpuProfiler {
593    /// Create new GPU profiler
594    pub fn new() -> Self {
595        Self {
596            kernel_times: HashMap::new(),
597            memory_transfers: Vec::new(),
598            device_utilization: HashMap::new(),
599        }
600    }
601    /// Record kernel execution time
602    pub fn record_kernel_time(&mut self, kernel_name: &str, time_ms: f64) {
603        self.kernel_times
604            .entry(kernel_name.to_string())
605            .or_default()
606            .push(time_ms);
607    }
608    /// Record memory transfer
609    pub fn record_memory_transfer(&mut self, size: u64, direction: &str) {
610        self.memory_transfers
611            .push((Instant::now(), size, direction.to_string()));
612    }
613    /// Record device utilization
614    pub fn record_utilization(&mut self, device_id: u32, utilization: f64) {
615        self.device_utilization
616            .entry(device_id)
617            .or_default()
618            .push((Instant::now(), utilization));
619    }
620    /// Get kernel statistics
621    pub fn get_kernel_stats(&self) -> HashMap<String, KernelStats> {
622        let mut stats = HashMap::new();
623        for (kernel_name, times) in &self.kernel_times {
624            let count = times.len();
625            let total_time: f64 = times.iter().sum();
626            let avg_time = total_time / count as f64;
627            let min_time = times.iter().fold(f64::INFINITY, |a, &b| a.min(b));
628            let max_time = times.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
629            stats.insert(
630                kernel_name.clone(),
631                KernelStats {
632                    count,
633                    total_time,
634                    avg_time,
635                    min_time,
636                    max_time,
637                },
638            );
639        }
640        stats
641    }
642    /// Get memory transfer statistics
643    pub fn get_memory_transfer_stats(&self) -> MemoryTransferStats {
644        let total_transfers = self.memory_transfers.len();
645        let total_bytes: u64 = self.memory_transfers.iter().map(|(_, size, _)| size).sum();
646        let host_to_device = self
647            .memory_transfers
648            .iter()
649            .filter(|(_, _, dir)| dir == "host_to_device")
650            .count();
651        let device_to_host = self
652            .memory_transfers
653            .iter()
654            .filter(|(_, _, dir)| dir == "device_to_host")
655            .count();
656        MemoryTransferStats {
657            total_transfers,
658            total_bytes,
659            host_to_device_transfers: host_to_device,
660            device_to_host_transfers: device_to_host,
661        }
662    }
663    /// Clear all profiling data
664    pub fn clear(&mut self) {
665        self.kernel_times.clear();
666        self.memory_transfers.clear();
667        self.device_utilization.clear();
668    }
669}
670/// Kernel execution statistics
671#[derive(Debug, Clone)]
672pub struct KernelStats {
673    pub count: usize,
674    pub total_time: f64,
675    pub avg_time: f64,
676    pub min_time: f64,
677    pub max_time: f64,
678}
679/// Memory transfer statistics
680#[derive(Debug, Clone)]
681pub struct MemoryTransferStats {
682    pub total_transfers: usize,
683    pub total_bytes: u64,
684    pub host_to_device_transfers: usize,
685    pub device_to_host_transfers: usize,
686}
687impl Default for GpuUtils {
688    fn default() -> Self {
689        Self::new()
690    }
691}
692impl Default for GpuProfiler {
693    fn default() -> Self {
694        Self::new()
695    }
696}
697/// Multi-GPU coordinator for distributed computing
698pub struct MultiGpuCoordinator {
699    gpus: HashMap<u32, GpuUtils>,
700    load_balancer: LoadBalancer,
701    #[allow(dead_code)]
702    communication_topology: CommunicationTopology,
703    #[allow(dead_code)]
704    synchronization_barriers: Vec<SynchronizationBarrier>,
705}
706impl Default for MultiGpuCoordinator {
707    fn default() -> Self {
708        Self::new()
709    }
710}
711impl MultiGpuCoordinator {
712    /// Create new multi-GPU coordinator
713    pub fn new() -> Self {
714        Self {
715            gpus: HashMap::new(),
716            load_balancer: LoadBalancer::new(),
717            communication_topology: CommunicationTopology::Ring,
718            synchronization_barriers: Vec::new(),
719        }
720    }
721    /// Initialize all available GPUs
722    pub fn init_all_gpus(&mut self) -> Result<(), GpuError> {
723        for gpu_id in 0..8 {
724            let mut gpu = GpuUtils::new();
725            if gpu.init_devices().is_ok() && !gpu.devices.is_empty() {
726                self.gpus.insert(gpu_id, gpu);
727            }
728        }
729        if self.gpus.is_empty() {
730            return Err(GpuError::InitializationFailed("No GPUs found".to_string()));
731        }
732        Ok(())
733    }
734    /// Get optimal GPU assignment for workload
735    pub fn get_optimal_assignment(&self, workload: &DistributedWorkload) -> Vec<GpuAssignment> {
736        self.load_balancer.assign_workload(workload, &self.gpus)
737    }
738    /// Execute distributed operation across multiple GPUs
739    pub fn execute_distributed(
740        &self,
741        operation: &DistributedOperation,
742    ) -> Result<DistributedResult, GpuError> {
743        let assignments = self.get_optimal_assignment(&operation.workload);
744        let mut results = Vec::new();
745        for assignment in assignments {
746            let gpu = self
747                .gpus
748                .get(&assignment.gpu_id)
749                .ok_or(GpuError::DeviceNotFound)?;
750            let kernel_info = GpuKernelInfo {
751                name: operation.kernel_name.clone(),
752                device_id: assignment.gpu_id,
753                grid_size: assignment.grid_size,
754                block_size: assignment.block_size,
755                shared_memory: assignment.shared_memory,
756                parameters: assignment.parameters.clone(),
757            };
758            let execution = gpu.execute_kernel(&kernel_info)?;
759            results.push(execution);
760        }
761        let total_time: f64 = results.iter().map(|e| e.execution_time).sum();
762        Ok(DistributedResult {
763            executions: results,
764            total_time,
765            communication_overhead: 0.0,
766        })
767    }
768    /// Synchronize all GPUs
769    pub fn synchronize_all(&self) -> Result<(), GpuError> {
770        std::thread::sleep(std::time::Duration::from_millis(1));
771        Ok(())
772    }
773    /// Get cluster-wide memory statistics
774    pub fn get_cluster_memory_stats(&self) -> ClusterMemoryStats {
775        let mut total_memory = 0;
776        let mut total_allocated = 0;
777        let mut device_stats = HashMap::new();
778        for (gpu_id, gpu) in &self.gpus {
779            let stats = gpu.get_memory_stats();
780            if let Some(stat) = stats.get(gpu_id) {
781                total_memory += stat.total_memory;
782                total_allocated += stat.allocated_memory;
783                device_stats.insert(*gpu_id, stat.clone());
784            }
785        }
786        ClusterMemoryStats {
787            total_memory,
788            total_allocated,
789            total_free: total_memory - total_allocated,
790            num_devices: self.gpus.len(),
791            device_stats,
792        }
793    }
794}
795/// GPU memory pool for efficient allocation
796pub struct GpuMemoryPool {
797    pools: HashMap<u32, Vec<MemoryBlock>>,
798    #[allow(dead_code)]
799    allocation_strategy: AllocationStrategy,
800    #[allow(dead_code)]
801    fragmentation_threshold: f64,
802}
803impl GpuMemoryPool {
804    /// Create new memory pool
805    pub fn new(strategy: AllocationStrategy) -> Self {
806        Self {
807            pools: HashMap::new(),
808            allocation_strategy: strategy,
809            fragmentation_threshold: 0.3,
810        }
811    }
812    /// Allocate memory from pool
813    pub fn allocate(&mut self, size: u64, device_id: u32) -> Result<u64, GpuError> {
814        let pool = self.pools.entry(device_id).or_default();
815        for (i, block) in pool.iter().enumerate() {
816            if !block.is_allocated && block.size >= size {
817                if block.size > size * 2 {
818                    let new_block = MemoryBlock {
819                        ptr: block.ptr + size,
820                        size: block.size - size,
821                        is_allocated: false,
822                        allocation_time: None,
823                    };
824                    pool.push(new_block);
825                    pool[i].size = size;
826                }
827                pool[i].is_allocated = true;
828                pool[i].allocation_time = Some(Instant::now());
829                return Ok(pool[i].ptr);
830            }
831        }
832        let ptr = self.allocate_new_block(size, device_id)?;
833        let pool = self.pools.entry(device_id).or_default();
834        pool.push(MemoryBlock {
835            ptr,
836            size,
837            is_allocated: true,
838            allocation_time: Some(Instant::now()),
839        });
840        Ok(ptr)
841    }
842    /// Free memory back to pool
843    pub fn free(&mut self, ptr: u64, device_id: u32) -> Result<(), GpuError> {
844        let pool = self
845            .pools
846            .get_mut(&device_id)
847            .ok_or(GpuError::DeviceNotFound)?;
848        for block in pool.iter_mut() {
849            if block.ptr == ptr {
850                block.is_allocated = false;
851                block.allocation_time = None;
852                self.try_merge_blocks(device_id);
853                return Ok(());
854            }
855        }
856        Err(GpuError::InvalidPointer)
857    }
858    /// Defragment memory pool
859    pub fn defragment(&mut self, device_id: u32) -> Result<DefragmentationResult, GpuError> {
860        let before_fragmentation = self.calculate_fragmentation(device_id);
861        let pool = self
862            .pools
863            .get_mut(&device_id)
864            .ok_or(GpuError::DeviceNotFound)?;
865        let before_blocks = pool.len();
866        pool.sort_by_key(|b| b.ptr);
867        let mut i = 0;
868        while i < pool.len() - 1 {
869            if !pool[i].is_allocated
870                && !pool[i + 1].is_allocated
871                && pool[i].ptr + pool[i].size == pool[i + 1].ptr
872            {
873                pool[i].size += pool[i + 1].size;
874                pool.remove(i + 1);
875            } else {
876                i += 1;
877            }
878        }
879        let after_blocks = pool.len();
880        let after_fragmentation = self.calculate_fragmentation(device_id);
881        Ok(DefragmentationResult {
882            blocks_before: before_blocks,
883            blocks_after: after_blocks,
884            fragmentation_before: before_fragmentation,
885            fragmentation_after: after_fragmentation,
886        })
887    }
888    fn allocate_new_block(&self, size: u64, _device_id: u32) -> Result<u64, GpuError> {
889        let ptr = (std::ptr::null::<u8>() as u64) + size;
890        Ok(ptr)
891    }
892    fn try_merge_blocks(&mut self, device_id: u32) {
893        if let Some(pool) = self.pools.get_mut(&device_id) {
894            pool.sort_by_key(|b| b.ptr);
895            let mut i = 0;
896            while i < pool.len() - 1 {
897                if !pool[i].is_allocated
898                    && !pool[i + 1].is_allocated
899                    && pool[i].ptr + pool[i].size == pool[i + 1].ptr
900                {
901                    pool[i].size += pool[i + 1].size;
902                    pool.remove(i + 1);
903                } else {
904                    i += 1;
905                }
906            }
907        }
908    }
909    fn calculate_fragmentation(&self, device_id: u32) -> f64 {
910        let empty_pool = Vec::new();
911        let pool = self.pools.get(&device_id).unwrap_or(&empty_pool);
912        let free_blocks = pool.iter().filter(|b| !b.is_allocated).count();
913        let total_blocks = pool.len();
914        if total_blocks == 0 {
915            0.0
916        } else {
917            free_blocks as f64 / total_blocks as f64
918        }
919    }
920}
921/// Asynchronous GPU operations
922pub struct AsyncGpuOps {
923    streams: HashMap<u32, Vec<GpuStream>>,
924    pending_operations: Vec<AsyncOperation>,
925}
926impl Default for AsyncGpuOps {
927    fn default() -> Self {
928        Self::new()
929    }
930}
931impl AsyncGpuOps {
932    /// Create new async GPU operations manager
933    pub fn new() -> Self {
934        Self {
935            streams: HashMap::new(),
936            pending_operations: Vec::new(),
937        }
938    }
939    /// Create new GPU stream
940    pub fn create_stream(&mut self, device_id: u32) -> Result<u32, GpuError> {
941        let stream_id = self.streams.get(&device_id).map_or(0, |s| s.len() as u32);
942        let stream = GpuStream {
943            id: stream_id,
944            device_id,
945            is_busy: false,
946            priority: StreamPriority::Normal,
947        };
948        self.streams.entry(device_id).or_default().push(stream);
949        Ok(stream_id)
950    }
951    /// Launch asynchronous kernel
952    pub fn launch_kernel_async(
953        &mut self,
954        kernel: &GpuKernelInfo,
955        stream_id: u32,
956    ) -> Result<AsyncOperationHandle, GpuError> {
957        let operation = AsyncOperation {
958            id: self.pending_operations.len() as u32,
959            kernel_info: kernel.clone(),
960            stream_id,
961            start_time: Instant::now(),
962            status: OperationStatus::Pending,
963        };
964        let handle = AsyncOperationHandle {
965            operation_id: operation.id,
966            device_id: kernel.device_id,
967        };
968        self.pending_operations.push(operation);
969        Ok(handle)
970    }
971    /// Wait for operation completion
972    pub fn wait_for_completion(
973        &mut self,
974        handle: &AsyncOperationHandle,
975    ) -> Result<GpuKernelExecution, GpuError> {
976        std::thread::sleep(std::time::Duration::from_millis(1));
977        if let Some(op) = self
978            .pending_operations
979            .iter_mut()
980            .find(|op| op.id == handle.operation_id)
981        {
982            op.status = OperationStatus::Completed;
983            Ok(GpuKernelExecution {
984                kernel_name: op.kernel_info.name.clone(),
985                device_id: op.kernel_info.device_id,
986                grid_size: op.kernel_info.grid_size,
987                block_size: op.kernel_info.block_size,
988                shared_memory: op.kernel_info.shared_memory,
989                execution_time: op.start_time.elapsed().as_secs_f64() * 1000.0,
990                parameters: op.kernel_info.parameters.clone(),
991            })
992        } else {
993            Err(GpuError::ComputationError)
994        }
995    }
996    /// Check if operation is complete
997    pub fn is_complete(&self, handle: &AsyncOperationHandle) -> bool {
998        self.pending_operations
999            .iter()
1000            .find(|op| op.id == handle.operation_id)
1001            .is_some_and(|op| matches!(op.status, OperationStatus::Completed))
1002    }
1003}
1004/// GPU optimization advisor
1005pub struct GpuOptimizationAdvisor {
1006    performance_history: HashMap<String, Vec<PerformanceMetric>>,
1007    optimization_rules: Vec<OptimizationRule>,
1008}
1009impl Default for GpuOptimizationAdvisor {
1010    fn default() -> Self {
1011        Self::new()
1012    }
1013}
1014impl GpuOptimizationAdvisor {
1015    /// Create new optimization advisor
1016    pub fn new() -> Self {
1017        let mut advisor = Self {
1018            performance_history: HashMap::new(),
1019            optimization_rules: Vec::new(),
1020        };
1021        advisor.init_default_rules();
1022        advisor
1023    }
1024    /// Analyze performance and provide recommendations
1025    pub fn analyze_performance(
1026        &mut self,
1027        kernel_name: &str,
1028        execution: &GpuKernelExecution,
1029        workload_size: usize,
1030    ) -> Vec<OptimizationRecommendation> {
1031        let metric = PerformanceMetric {
1032            execution_time: execution.execution_time,
1033            throughput: workload_size as f64 / execution.execution_time,
1034            memory_bandwidth: 0.0,
1035            occupancy: self.calculate_occupancy(execution),
1036        };
1037        self.performance_history
1038            .entry(kernel_name.to_string())
1039            .or_default()
1040            .push(metric.clone());
1041        let mut recommendations = Vec::new();
1042        for rule in &self.optimization_rules {
1043            if let Some(recommendation) = rule.evaluate(&metric, execution) {
1044                recommendations.push(recommendation);
1045            }
1046        }
1047        recommendations
1048    }
1049    fn init_default_rules(&mut self) {
1050        self.optimization_rules.push(OptimizationRule {
1051            name: "Low Occupancy".to_string(),
1052            condition: Box::new(|metric, _| metric.occupancy < 0.5),
1053            recommendation: "Consider increasing block size or reducing register usage".to_string(),
1054            priority: RecommendationPriority::High,
1055        });
1056        self.optimization_rules.push(OptimizationRule {
1057            name: "Memory Bandwidth".to_string(),
1058            condition: Box::new(|metric, _| metric.memory_bandwidth < 0.7),
1059            recommendation: "Optimize memory access patterns for better coalescing".to_string(),
1060            priority: RecommendationPriority::Medium,
1061        });
1062        self.optimization_rules.push(OptimizationRule {
1063            name: "Small Grid Size".to_string(),
1064            condition: Box::new(|_, execution| {
1065                let total_threads = execution.grid_size.0
1066                    * execution.grid_size.1
1067                    * execution.grid_size.2
1068                    * execution.block_size.0
1069                    * execution.block_size.1
1070                    * execution.block_size.2;
1071                total_threads < 1024
1072            }),
1073            recommendation: "Consider increasing grid size to better utilize GPU cores".to_string(),
1074            priority: RecommendationPriority::Low,
1075        });
1076    }
1077    fn calculate_occupancy(&self, execution: &GpuKernelExecution) -> f64 {
1078        let threads_per_block =
1079            execution.block_size.0 * execution.block_size.1 * execution.block_size.2;
1080        let blocks_per_sm = 2048 / threads_per_block.max(1);
1081        (blocks_per_sm as f64 / 32.0).min(1.0)
1082    }
1083}
1084#[derive(Debug, Clone)]
1085pub struct DistributedWorkload {
1086    pub total_elements: usize,
1087    pub operation_type: String,
1088    pub memory_requirement: u64,
1089    pub computation_complexity: f64,
1090}
1091#[derive(Debug, Clone)]
1092pub struct DistributedOperation {
1093    pub kernel_name: String,
1094    pub workload: DistributedWorkload,
1095}
1096#[derive(Debug, Clone)]
1097pub struct DistributedResult {
1098    pub executions: Vec<GpuKernelExecution>,
1099    pub total_time: f64,
1100    pub communication_overhead: f64,
1101}
1102#[derive(Debug, Clone)]
1103pub struct GpuAssignment {
1104    pub gpu_id: u32,
1105    pub grid_size: (u32, u32, u32),
1106    pub block_size: (u32, u32, u32),
1107    pub shared_memory: u32,
1108    pub parameters: HashMap<String, String>,
1109}
1110#[derive(Debug, Clone)]
1111pub struct LoadBalancer {
1112    #[allow(dead_code)]
1113    strategy: LoadBalancingStrategy,
1114}
1115impl Default for LoadBalancer {
1116    fn default() -> Self {
1117        Self::new()
1118    }
1119}
1120impl LoadBalancer {
1121    pub fn new() -> Self {
1122        Self {
1123            strategy: LoadBalancingStrategy::WorkloadProportional,
1124        }
1125    }
1126    pub fn assign_workload(
1127        &self,
1128        workload: &DistributedWorkload,
1129        gpus: &HashMap<u32, GpuUtils>,
1130    ) -> Vec<GpuAssignment> {
1131        let mut assignments = Vec::new();
1132        let num_gpus = gpus.len() as u32;
1133        if num_gpus == 0 {
1134            return assignments;
1135        }
1136        let elements_per_gpu = workload.total_elements / num_gpus as usize;
1137        for gpu_id in gpus.keys() {
1138            let assignment = GpuAssignment {
1139                gpu_id: *gpu_id,
1140                grid_size: (elements_per_gpu as u32 / 256, 1, 1),
1141                block_size: (256, 1, 1),
1142                shared_memory: 0,
1143                parameters: HashMap::new(),
1144            };
1145            assignments.push(assignment);
1146        }
1147        assignments
1148    }
1149}
1150#[derive(Debug, Clone)]
1151pub enum LoadBalancingStrategy {
1152    RoundRobin,
1153    WorkloadProportional,
1154    MemoryAware,
1155    PerformanceBased,
1156}
1157#[derive(Debug, Clone)]
1158pub enum CommunicationTopology {
1159    Ring,
1160    Tree,
1161    AllToAll,
1162    Custom(Vec<Vec<u32>>),
1163}
1164#[derive(Debug, Clone)]
1165pub struct SynchronizationBarrier {
1166    pub id: u32,
1167    pub participating_gpus: Vec<u32>,
1168    pub barrier_type: BarrierType,
1169}
1170#[derive(Debug, Clone)]
1171pub enum BarrierType {
1172    Global,
1173    Local(Vec<u32>),
1174    Hierarchical,
1175}
1176#[derive(Debug, Clone)]
1177pub struct ClusterMemoryStats {
1178    pub total_memory: u64,
1179    pub total_allocated: u64,
1180    pub total_free: u64,
1181    pub num_devices: usize,
1182    pub device_stats: HashMap<u32, MemoryStats>,
1183}
1184#[derive(Debug, Clone)]
1185pub struct MemoryBlock {
1186    pub ptr: u64,
1187    pub size: u64,
1188    pub is_allocated: bool,
1189    pub allocation_time: Option<Instant>,
1190}
1191#[derive(Debug, Clone)]
1192pub enum AllocationStrategy {
1193    FirstFit,
1194    BestFit,
1195    WorstFit,
1196    BuddySystem,
1197}
1198#[derive(Debug, Clone)]
1199pub struct DefragmentationResult {
1200    pub blocks_before: usize,
1201    pub blocks_after: usize,
1202    pub fragmentation_before: f64,
1203    pub fragmentation_after: f64,
1204}
1205#[derive(Debug, Clone)]
1206pub struct GpuStream {
1207    pub id: u32,
1208    pub device_id: u32,
1209    pub is_busy: bool,
1210    pub priority: StreamPriority,
1211}
1212#[derive(Debug, Clone)]
1213pub enum StreamPriority {
1214    Low,
1215    Normal,
1216    High,
1217}
1218#[derive(Debug, Clone)]
1219pub struct AsyncOperation {
1220    pub id: u32,
1221    pub kernel_info: GpuKernelInfo,
1222    pub stream_id: u32,
1223    pub start_time: Instant,
1224    pub status: OperationStatus,
1225}
1226#[derive(Debug, Clone)]
1227pub enum OperationStatus {
1228    Pending,
1229    Running,
1230    Completed,
1231    Failed,
1232}
1233#[derive(Debug, Clone)]
1234pub struct AsyncOperationHandle {
1235    pub operation_id: u32,
1236    pub device_id: u32,
1237}
1238#[derive(Debug, Clone)]
1239pub struct PerformanceMetric {
1240    pub execution_time: f64,
1241    pub throughput: f64,
1242    pub memory_bandwidth: f64,
1243    pub occupancy: f64,
1244}
1245type OptimizationCondition =
1246    Box<dyn Fn(&PerformanceMetric, &GpuKernelExecution) -> bool + Send + Sync>;
1247pub struct OptimizationRule {
1248    pub name: String,
1249    pub condition: OptimizationCondition,
1250    pub recommendation: String,
1251    pub priority: RecommendationPriority,
1252}
1253impl std::fmt::Debug for OptimizationRule {
1254    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1255        f.debug_struct("OptimizationRule")
1256            .field("name", &self.name)
1257            .field("condition", &"<function>")
1258            .field("recommendation", &self.recommendation)
1259            .field("priority", &self.priority)
1260            .finish()
1261    }
1262}
1263impl Clone for OptimizationRule {
1264    fn clone(&self) -> Self {
1265        OptimizationRule {
1266            name: self.name.clone(),
1267            condition: Box::new(|_metric, _execution| false),
1268            recommendation: self.recommendation.clone(),
1269            priority: self.priority.clone(),
1270        }
1271    }
1272}
1273impl OptimizationRule {
1274    pub fn evaluate(
1275        &self,
1276        metric: &PerformanceMetric,
1277        execution: &GpuKernelExecution,
1278    ) -> Option<OptimizationRecommendation> {
1279        if (self.condition)(metric, execution) {
1280            Some(OptimizationRecommendation {
1281                rule_name: self.name.clone(),
1282                recommendation: self.recommendation.clone(),
1283                priority: self.priority.clone(),
1284                estimated_improvement: 0.0,
1285            })
1286        } else {
1287            None
1288        }
1289    }
1290}
1291#[derive(Debug, Clone)]
1292pub struct OptimizationRecommendation {
1293    pub rule_name: String,
1294    pub recommendation: String,
1295    pub priority: RecommendationPriority,
1296    pub estimated_improvement: f64,
1297}
1298#[derive(Debug, Clone)]
1299pub enum RecommendationPriority {
1300    Low,
1301    Medium,
1302    High,
1303    Critical,
1304}
1305
1306#[cfg(test)]
1307mod tests;