Skip to main content

scirs2_core/resource/
gpu.rs

1//! # GPU Detection and Capabilities
2//!
3//! This module provides GPU detection and capability assessment for
4//! accelerated computing workloads.
5
6use crate::error::{CoreError, CoreResult};
7
8/// GPU information and capabilities
9#[derive(Debug, Clone)]
10pub struct GpuInfo {
11    /// GPU name/model
12    pub name: String,
13    /// GPU vendor
14    pub vendor: GpuVendor,
15    /// Total GPU memory in bytes
16    pub memory_total: usize,
17    /// Available GPU memory in bytes
18    pub memory_available: usize,
19    /// Memory bandwidth in GB/s
20    pub memorybandwidth_gbps: f64,
21    /// Number of compute units (CUDA cores, stream processors, etc.)
22    pub compute_units: usize,
23    /// Base clock frequency in MHz
24    pub base_clock_mhz: usize,
25    /// Memory clock frequency in MHz
26    pub memory_clock_mhz: usize,
27    /// Compute capability/architecture
28    pub compute_capability: ComputeCapability,
29    /// Supported features
30    pub features: GpuFeatures,
31    /// Performance characteristics
32    pub performance: GpuPerformance,
33}
34
35impl GpuInfo {
36    /// Detect GPU information
37    pub fn detect() -> CoreResult<Self> {
38        #[cfg(feature = "gpu")]
39        {
40            // Try different GPU detection methods
41            if let Ok(gpu) = Self::detect_cuda() {
42                return Ok(gpu);
43            }
44
45            if let Ok(gpu) = Self::detect_opencl() {
46                return Ok(gpu);
47            }
48
49            if let Ok(gpu) = Self::detect_vulkan() {
50                return Ok(gpu);
51            }
52        }
53
54        // Try platform-specific detection
55        #[cfg(target_os = "linux")]
56        if let Ok(gpu) = Self::detect_linux() {
57            return Ok(gpu);
58        }
59
60        #[cfg(target_os = "windows")]
61        if let Ok(gpu) = Self::detect_windows() {
62            return Ok(gpu);
63        }
64
65        #[cfg(target_os = "macos")]
66        if let Ok(gpu) = Self::detect_macos() {
67            return Ok(gpu);
68        }
69
70        Err(CoreError::ComputationError(
71            crate::error::ErrorContext::new("No GPU detected"),
72        ))
73    }
74
75    /// Detect CUDA-capable GPU.
76    ///
77    /// Dedicated CUDA runtime probing (cudarc / the CUDA driver API) is not
78    /// wired into this resource module. It returns an honest "not implemented"
79    /// error so detection falls through to the platform-specific paths below
80    /// (e.g. Linux sysfs) rather than reporting a fabricated GPU.
81    #[cfg(feature = "gpu")]
82    fn detect_cuda() -> CoreResult<Self> {
83        Err(CoreError::ComputationError(
84            crate::error::ErrorContext::new("CUDA detection not implemented"),
85        ))
86    }
87
88    /// Detect OpenCL-capable GPU
89    #[cfg(feature = "gpu")]
90    fn detect_opencl() -> CoreResult<Self> {
91        // In a real implementation, this would use OpenCL API
92        Err(CoreError::ComputationError(
93            crate::error::ErrorContext::new("OpenCL detection not implemented"),
94        ))
95    }
96
97    /// Detect Vulkan-capable GPU
98    #[cfg(feature = "gpu")]
99    fn detect_vulkan() -> CoreResult<Self> {
100        // In a real implementation, this would use Vulkan API
101        Err(CoreError::ComputationError(
102            crate::error::ErrorContext::new("Vulkan detection not implemented"),
103        ))
104    }
105
106    /// Detect GPU on Linux via sysfs
107    #[cfg(target_os = "linux")]
108    fn detect_linux() -> CoreResult<Self> {
109        use std::fs;
110
111        // Try to detect via /sys/class/drm
112        if let Ok(entries) = fs::read_dir("/sys/class/drm") {
113            for entry in entries.flatten() {
114                let path = entry.path();
115                if let Some(name) = path.file_name() {
116                    if name.to_string_lossy().starts_with("card") {
117                        // Try to read device information
118                        let device_path = path.join("device");
119                        if let Ok(vendor) = fs::read_to_string(device_path.join("vendor")) {
120                            if let Ok(device) = fs::read_to_string(device_path.join("device")) {
121                                let vendor_id = vendor.trim();
122                                let device_id = device.trim();
123
124                                return Ok(Self::create_from_pci_ids(vendor_id, device_id));
125                            }
126                        }
127                    }
128                }
129            }
130        }
131
132        Err(CoreError::ComputationError(
133            crate::error::ErrorContext::new("No GPU detected on Linux"),
134        ))
135    }
136
137    /// Detect GPU on Windows
138    #[cfg(target_os = "windows")]
139    fn detect_windows() -> CoreResult<Self> {
140        // In a real implementation, this would use DXGI or WMI
141        Err(CoreError::ComputationError(
142            crate::error::ErrorContext::new("Windows GPU detection not implemented"),
143        ))
144    }
145
146    /// Detect GPU on macOS
147    #[cfg(target_os = "macos")]
148    fn detect_macos() -> CoreResult<Self> {
149        // For Apple Silicon, we know it has integrated GPU
150        #[cfg(target_arch = "aarch64")]
151        {
152            Ok(Self {
153                name: "Apple GPU".to_string(),
154                vendor: GpuVendor::Apple,
155                memory_total: 8 * 1024 * 1024 * 1024, // Unified memory
156                memory_available: 6 * 1024 * 1024 * 1024,
157                memorybandwidth_gbps: 200.0,
158                compute_units: 8,
159                base_clock_mhz: 1000,
160                memory_clock_mhz: 2000,
161                compute_capability: ComputeCapability::Metal,
162                features: GpuFeatures {
163                    unified_memory: true,
164                    double_precision: true,
165                    half_precision: true,
166                    tensor_cores: false,
167                    ray_tracing: false,
168                },
169                performance: GpuPerformance {
170                    fp32_gflops: 2600.0,
171                    fp16_gflops: 5200.0,
172                    memorybandwidth_gbps: 200.0,
173                    efficiency_score: 0.9,
174                },
175            })
176        }
177        #[cfg(not(target_arch = "aarch64"))]
178        {
179            Err(CoreError::ComputationError(
180                crate::error::ErrorContext::new("macOS GPU detection not implemented"),
181            ))
182        }
183    }
184
185    /// Create GPU info from PCI vendor/device IDs
186    #[allow(dead_code)]
187    fn from_pci_ids(vendor_id: u16, _device_id: &str) -> Self {
188        let vendor = match vendor_id {
189            0x10de => GpuVendor::Nvidia,
190            0x1002 => GpuVendor::Amd,
191            0x8086 => GpuVendor::Intel,
192            _ => GpuVendor::Unknown,
193        };
194
195        // This is a simplified mapping - real implementation would have
196        // comprehensive device databases
197        let (name, memory_gb, compute_units) = match vendor_id {
198            0x10de => ("NVIDIA GPU".to_string(), 8, 2048),
199            0x1002 => ("AMD GPU".to_string(), 8, 64),
200            0x8086 => ("Intel GPU".to_string(), 4, 96),
201            _ => ("Unknown GPU".to_string(), 4, 32),
202        };
203
204        Self {
205            name,
206            vendor,
207            memory_total: memory_gb * 1024 * 1024 * 1024,
208            memory_available: (memory_gb * 1024 * 1024 * 1024 * 3) / 4, // 75% available
209            memorybandwidth_gbps: 500.0,
210            compute_units,
211            base_clock_mhz: 1500,
212            memory_clock_mhz: 7000,
213            compute_capability: ComputeCapability::Unknown,
214            features: GpuFeatures::default(),
215            performance: GpuPerformance::default(),
216        }
217    }
218
219    /// Calculate performance score (0.0 to 1.0)
220    pub fn performance_score(&self) -> f64 {
221        let memory_score = (self.memory_total as f64 / (24.0 * 1024.0 * 1024.0 * 1024.0)).min(1.0); // Normalize to 24GB
222        let compute_score = (self.compute_units as f64 / 4096.0).min(1.0); // Normalize to 4096 units
223        let bandwidth_score = (self.memorybandwidth_gbps / 1000.0).min(1.0); // Normalize to 1000 GB/s
224        let efficiency_score = self.performance.efficiency_score;
225
226        (memory_score + compute_score + bandwidth_score + efficiency_score) / 4.0
227    }
228
229    /// Get optimal workgroup/block size
230    pub fn optimal_workgroup_size(&self) -> usize {
231        match self.vendor {
232            GpuVendor::Nvidia => 256, // Typical for NVIDIA
233            GpuVendor::Amd => 64,     // Typical for AMD
234            GpuVendor::Intel => 128,  // Typical for Intel
235            GpuVendor::Apple => 32,   // Typical for Apple
236            GpuVendor::Unknown => 64,
237        }
238    }
239
240    /// Check if suitable for compute workloads
241    pub fn is_compute_capable(&self) -> bool {
242        self.memory_total >= 2 * 1024 * 1024 * 1024 && // At least 2GB
243        self.compute_units >= 32 // At least 32 compute units
244    }
245
246    /// Check if suitable for machine learning
247    pub fn is_ml_capable(&self) -> bool {
248        self.is_compute_capable() && (self.features.tensor_cores || self.features.half_precision)
249    }
250
251    /// Create GpuInfo from PCI IDs
252    pub fn create_from_pci_ids(vendor_id: &str, device_id: &str) -> Self {
253        // Strip 0x prefix if present
254        let vendor_id = vendor_id.strip_prefix("0x").unwrap_or(vendor_id);
255
256        let vendor = match vendor_id {
257            "10de" => GpuVendor::Nvidia,
258            "1002" => GpuVendor::Amd,
259            "8086" => GpuVendor::Intel,
260            _ => GpuVendor::Unknown,
261        };
262
263        // Create appropriate name based on vendor
264        let name = match vendor {
265            GpuVendor::Nvidia => format!("NVIDIA GPU {}", device_id),
266            GpuVendor::Amd => format!("AMD GPU {}", device_id),
267            GpuVendor::Intel => format!("Intel GPU {}", device_id),
268            GpuVendor::Apple => format!("Apple GPU {}", device_id),
269            GpuVendor::Unknown => format!("Unknown GPU {}", device_id),
270        };
271
272        // Default GPU info based on vendor
273        // In a real implementation, this would look up specific device info
274        Self {
275            name,
276            vendor,
277            memory_total: (8u64 * 1024 * 1024 * 1024) as usize, // 8GB default
278            memory_available: (8u64 * 1024 * 1024 * 1024) as usize,
279            memorybandwidth_gbps: 400.0,
280            compute_capability: ComputeCapability::Cuda(7, 0), // Default compute capability
281            compute_units: 128,
282            base_clock_mhz: 1500,
283            memory_clock_mhz: 1750, // Default memory clock
284            features: GpuFeatures::default(),
285            performance: GpuPerformance::default(),
286        }
287    }
288}
289
290/// GPU vendor types
291#[derive(Debug, Clone, Copy, PartialEq, Eq)]
292pub enum GpuVendor {
293    /// NVIDIA
294    Nvidia,
295    /// AMD
296    Amd,
297    /// Intel
298    Intel,
299    /// Apple
300    Apple,
301    /// Unknown vendor
302    Unknown,
303}
304
305/// GPU compute capabilities
306#[derive(Debug, Clone, Copy, PartialEq, Eq)]
307pub enum ComputeCapability {
308    /// CUDA compute capability
309    Cuda(u32, u32), // major, minor
310    /// OpenCL version
311    OpenCL(u32, u32), // major, minor
312    /// Vulkan version
313    Vulkan(u32, u32), // major, minor
314    /// Metal (Apple)
315    Metal,
316    /// DirectCompute (Microsoft)
317    DirectCompute,
318    /// Unknown capability
319    Unknown,
320}
321
322/// GPU feature support
323#[derive(Debug, Clone)]
324pub struct GpuFeatures {
325    /// Unified memory support
326    pub unified_memory: bool,
327    /// Double precision (FP64) support
328    pub double_precision: bool,
329    /// Half precision (FP16) support
330    pub half_precision: bool,
331    /// Tensor cores or equivalent
332    pub tensor_cores: bool,
333    /// Ray tracing support
334    pub ray_tracing: bool,
335}
336
337impl Default for GpuFeatures {
338    fn default() -> Self {
339        Self {
340            unified_memory: false,
341            double_precision: true,
342            half_precision: false,
343            tensor_cores: false,
344            ray_tracing: false,
345        }
346    }
347}
348
349/// GPU performance characteristics
350#[derive(Debug, Clone)]
351pub struct GpuPerformance {
352    /// FP32 performance in GFLOPS
353    pub fp32_gflops: f64,
354    /// FP16 performance in GFLOPS
355    pub fp16_gflops: f64,
356    /// Memory bandwidth in GB/s
357    pub memorybandwidth_gbps: f64,
358    /// Overall efficiency score (0.0 to 1.0)
359    pub efficiency_score: f64,
360}
361
362impl Default for GpuPerformance {
363    fn default() -> Self {
364        Self {
365            fp32_gflops: 1000.0,
366            fp16_gflops: 2000.0,
367            memorybandwidth_gbps: 500.0,
368            efficiency_score: 0.7,
369        }
370    }
371}
372
373/// Multi-GPU information
374#[derive(Debug, Clone)]
375pub struct MultiGpuInfo {
376    /// List of detected GPUs
377    pub gpus: Vec<GpuInfo>,
378    /// Total combined memory
379    pub total_memory: usize,
380    /// Whether GPUs support peer-to-peer communication
381    pub p2p_capable: bool,
382    /// SLI/CrossFire configuration
383    pub multi_gpuconfig: MultiGpuConfig,
384}
385
386impl MultiGpuInfo {
387    /// Detect all available GPUs
388    pub fn detect() -> CoreResult<Self> {
389        let mut gpus = Vec::new();
390
391        // Try to detect multiple GPUs
392        // This is simplified - real implementation would enumerate all devices
393        if let Ok(gpu) = GpuInfo::detect() {
394            gpus.push(gpu);
395        }
396
397        let total_memory = gpus.iter().map(|gpu| gpu.memory_total).sum();
398
399        Ok(Self {
400            gpus,
401            total_memory,
402            p2p_capable: false,
403            multi_gpuconfig: MultiGpuConfig::Single,
404        })
405    }
406
407    /// Get the best GPU for compute workloads
408    pub fn best_compute_gpu(&self) -> Option<&GpuInfo> {
409        self.gpus
410            .iter()
411            .filter(|gpu| gpu.is_compute_capable())
412            .max_by(|a, b| {
413                a.performance_score()
414                    .partial_cmp(&b.performance_score())
415                    .expect("Operation failed")
416            })
417    }
418
419    /// Get total compute capability
420    pub fn total_compute_units(&self) -> usize {
421        self.gpus.iter().map(|gpu| gpu.compute_units).sum()
422    }
423}
424
425/// Multi-GPU configuration types
426#[derive(Debug, Clone, Copy, PartialEq, Eq)]
427pub enum MultiGpuConfig {
428    /// Single GPU
429    Single,
430    /// SLI (NVIDIA)
431    Sli,
432    /// CrossFire (AMD)
433    CrossFire,
434    /// NVLink (NVIDIA)
435    NvLink,
436    /// Independent GPUs
437    Independent,
438}
439
440impl Default for GpuInfo {
441    fn default() -> Self {
442        Self {
443            name: "Default GPU".to_string(),
444            vendor: GpuVendor::Unknown,
445            memory_total: (4u64 * 1024 * 1024 * 1024) as usize, // 4GB
446            memory_available: (3u64 * 1024 * 1024 * 1024) as usize, // 3GB
447            memorybandwidth_gbps: 200.0,
448            compute_units: 512,
449            base_clock_mhz: 1000,
450            memory_clock_mhz: 4000,
451            compute_capability: ComputeCapability::Unknown,
452            features: GpuFeatures::default(),
453            performance: GpuPerformance::default(),
454        }
455    }
456}
457
458#[cfg(test)]
459mod tests {
460    use super::*;
461
462    #[test]
463    fn test_gpu_vendor() {
464        assert_eq!(GpuVendor::Nvidia, GpuVendor::Nvidia);
465        assert_ne!(GpuVendor::Nvidia, GpuVendor::Amd);
466    }
467
468    #[test]
469    fn test_compute_capability() {
470        let cuda_cap = ComputeCapability::Cuda(7, 5);
471        assert_eq!(cuda_cap, ComputeCapability::Cuda(7, 5));
472        assert_ne!(cuda_cap, ComputeCapability::Metal);
473    }
474
475    #[test]
476    fn test_gpu_features() {
477        let features = GpuFeatures {
478            unified_memory: true,
479            tensor_cores: true,
480            ..Default::default()
481        };
482
483        assert!(features.unified_memory);
484        assert!(features.tensor_cores);
485        assert!(!features.ray_tracing);
486    }
487
488    #[test]
489    fn test_gpu_performance() {
490        let perf = GpuPerformance::default();
491        assert!(perf.fp32_gflops > 0.0);
492        assert!(perf.efficiency_score >= 0.0 && perf.efficiency_score <= 1.0);
493    }
494
495    #[test]
496    fn test_pci_id_parsing() {
497        let gpu = GpuInfo::create_from_pci_ids("0x10de", "0x1234");
498        assert_eq!(gpu.vendor, GpuVendor::Nvidia);
499        assert!(gpu.name.contains("NVIDIA"));
500    }
501
502    #[test]
503    fn test_multi_gpu_config() {
504        assert_eq!(MultiGpuConfig::Single, MultiGpuConfig::Single);
505        assert_ne!(MultiGpuConfig::Single, MultiGpuConfig::Sli);
506    }
507
508    #[test]
509    fn test_optimal_workgroup_size() {
510        let nvidia_gpu = GpuInfo {
511            vendor: GpuVendor::Nvidia,
512            ..Default::default()
513        };
514        assert_eq!(nvidia_gpu.optimal_workgroup_size(), 256);
515
516        let amd_gpu = GpuInfo {
517            vendor: GpuVendor::Amd,
518            ..Default::default()
519        };
520        assert_eq!(amd_gpu.optimal_workgroup_size(), 64);
521    }
522}