/ crates / acdc-check / src / gpu.rs
gpu.rs
 1  //! GPU detection and information.
 2  
 3  use acdc_core::{Error, Result};
 4  use serde::{Deserialize, Serialize};
 5  use std::process::Command;
 6  
 7  /// GPU information.
 8  #[derive(Debug, Clone, Serialize, Deserialize)]
 9  pub struct GpuInfo {
10      /// GPU name/model
11      pub name: String,
12      /// Whether a compatible GPU is available
13      pub available: bool,
14      /// VRAM in bytes
15      pub vram_bytes: u64,
16      /// CUDA version (if available)
17      pub cuda_version: Option<String>,
18      /// Driver version
19      pub driver_version: Option<String>,
20  }
21  
22  impl GpuInfo {
23      /// Detect GPU information using nvidia-smi.
24      pub fn detect() -> Result<Self> {
25          // Try nvidia-smi first
26          if let Ok(info) = Self::detect_nvidia() {
27              return Ok(info);
28          }
29  
30          // No compatible GPU found
31          Err(Error::SystemCheck("No compatible GPU detected".to_string()))
32      }
33  
34      fn detect_nvidia() -> Result<Self> {
35          let output = Command::new("nvidia-smi")
36              .args([
37                  "--query-gpu=name,memory.total,driver_version",
38                  "--format=csv,noheader,nounits",
39              ])
40              .output()
41              .map_err(|e| Error::SystemCheck(format!("nvidia-smi not found: {}", e)))?;
42  
43          if !output.status.success() {
44              return Err(Error::SystemCheck("nvidia-smi failed to run".to_string()));
45          }
46  
47          let stdout = String::from_utf8_lossy(&output.stdout);
48          let line = stdout
49              .lines()
50              .next()
51              .ok_or_else(|| Error::SystemCheck("No GPU info returned".to_string()))?;
52  
53          let parts: Vec<&str> = line.split(", ").collect();
54          if parts.len() < 3 {
55              return Err(Error::SystemCheck("Invalid nvidia-smi output".to_string()));
56          }
57  
58          let name = parts[0].trim().to_string();
59          let vram_mb: u64 = parts[1].trim().parse().unwrap_or(0);
60          let driver_version = Some(parts[2].trim().to_string());
61  
62          // Get CUDA version
63          let cuda_version = Command::new("nvcc")
64              .arg("--version")
65              .output()
66              .ok()
67              .and_then(|o| {
68                  let stdout = String::from_utf8_lossy(&o.stdout);
69                  stdout
70                      .lines()
71                      .find(|l| l.contains("release"))
72                      .and_then(|l| l.split("release ").nth(1))
73                      .map(|v| v.split(',').next().unwrap_or(v).trim().to_string())
74              });
75  
76          Ok(Self {
77              name,
78              available: true,
79              vram_bytes: vram_mb * 1024 * 1024,
80              cuda_version,
81              driver_version,
82          })
83      }
84  
85      /// Get VRAM in GB.
86      pub fn vram_gb(&self) -> u64 {
87          self.vram_bytes / (1024 * 1024 * 1024)
88      }
89  }