1use crate::Result;
2use serde::{Deserialize, Serialize};
3
4#[cfg(feature = "nvidia")]
5use nvml_wrapper::Nvml;
6
7#[cfg(target_os = "linux")]
9use std::process::Command;
10
11#[cfg(target_os = "windows")]
12use wmi::{COMLibrary, WMIConnection};
13
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
16pub enum GPUVendor {
17 NVIDIA,
18 AMD,
19 Intel,
20 Apple,
21 ARM,
22 Qualcomm,
23 Unknown(String),
24}
25
26impl std::fmt::Display for GPUVendor {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 match self {
29 GPUVendor::NVIDIA => write!(f, "NVIDIA"),
30 GPUVendor::AMD => write!(f, "AMD"),
31 GPUVendor::Intel => write!(f, "Intel"),
32 GPUVendor::Apple => write!(f, "Apple"),
33 GPUVendor::ARM => write!(f, "ARM"),
34 GPUVendor::Qualcomm => write!(f, "Qualcomm"),
35 GPUVendor::Unknown(name) => write!(f, "{name}"),
36 }
37 }
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
42pub enum GPUType {
43 Discrete,
45 Integrated,
47 Workstation,
49 Datacenter,
51 Virtual,
53 Unknown,
55}
56
57impl std::fmt::Display for GPUType {
58 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59 match self {
60 GPUType::Discrete => write!(f, "Discrete"),
61 GPUType::Integrated => write!(f, "Integrated"),
62 GPUType::Workstation => write!(f, "Workstation"),
63 GPUType::Datacenter => write!(f, "Datacenter"),
64 GPUType::Virtual => write!(f, "Virtual"),
65 GPUType::Unknown => write!(f, "Unknown"),
66 }
67 }
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct ComputeCapabilities {
73 pub cuda: Option<String>,
75 pub rocm: bool,
77 pub directml: bool,
79 pub opencl: bool,
81 pub vulkan: bool,
83 pub metal: bool,
85 pub compute_units: Option<u32>,
87 pub max_workgroup_size: Option<u32>,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct GPUInfo {
94 pub vendor: GPUVendor,
96 pub model_name: String,
98 pub gpu_type: GPUType,
100 pub memory_mb: u64,
102 pub memory_type: Option<String>,
104 pub memory_bandwidth: Option<f32>,
106 pub base_clock: Option<u32>,
108 pub boost_clock: Option<u32>,
110 pub memory_clock: Option<u32>,
112 pub shader_units: Option<u32>,
114 pub rt_cores: Option<u32>,
116 pub tensor_cores: Option<u32>,
118 pub compute_capabilities: ComputeCapabilities,
120 pub usage_percent: Option<f32>,
122 pub temperature: Option<f32>,
124 pub power_consumption: Option<f32>,
126 pub power_limit: Option<f32>,
128 pub driver_version: Option<String>,
130 pub vbios_version: Option<String>,
132 pub pci_device_id: Option<String>,
134 pub pci_subsystem_id: Option<String>,
136}
137
138impl GPUInfo {
139 pub fn query_all() -> Result<Vec<Self>> {
141 let mut gpus = Vec::new();
142
143 if let Ok(wmi_gpus) = Self::query_generic_gpus() {
145 gpus.extend(wmi_gpus);
146 }
147
148 if let Ok(nvidia_gpus) = Self::query_nvidia_gpus() {
150 for nvidia_gpu in nvidia_gpus {
152 if let Some(existing) = gpus.iter_mut().find(|g|
154 g.vendor == GPUVendor::NVIDIA &&
155 g.model_name.contains("RTX") == nvidia_gpu.model_name.contains("RTX")
156 ) {
157 existing.compute_capabilities.cuda = nvidia_gpu.compute_capabilities.cuda;
159 existing.usage_percent = nvidia_gpu.usage_percent;
160 existing.temperature = nvidia_gpu.temperature;
161 existing.power_consumption = nvidia_gpu.power_consumption;
162 existing.shader_units = nvidia_gpu.shader_units;
163 existing.rt_cores = nvidia_gpu.rt_cores;
164 existing.tensor_cores = nvidia_gpu.tensor_cores;
165 } else {
166 gpus.push(nvidia_gpu);
168 }
169 }
170 }
171
172 if let Ok(amd_gpus) = Self::query_amd_gpus() {
173 for amd_gpu in amd_gpus {
175 if !gpus.iter().any(|g| g.vendor == GPUVendor::AMD && g.model_name == amd_gpu.model_name) {
176 gpus.push(amd_gpu);
177 }
178 }
179 }
180
181 if let Ok(intel_gpus) = Self::query_intel_gpus() {
182 for intel_gpu in intel_gpus {
184 if !gpus.iter().any(|g| g.vendor == GPUVendor::Intel && g.model_name == intel_gpu.model_name) {
185 gpus.push(intel_gpu);
186 }
187 }
188 }
189
190 if gpus.is_empty() {
192 gpus.push(Self::default_gpu());
193 }
194
195 Ok(gpus)
196 }
197
198 pub fn vendor(&self) -> &GPUVendor {
200 &self.vendor
201 }
202
203 pub fn model_name(&self) -> &str {
205 &self.model_name
206 }
207
208 pub fn gpu_type(&self) -> &GPUType {
210 &self.gpu_type
211 }
212
213 pub fn memory_gb(&self) -> f64 {
215 (self.memory_mb as f64 / 1024.0 * 10.0).round() / 10.0
216 }
217
218 pub fn memory_mb(&self) -> u64 {
220 self.memory_mb
221 }
222
223 pub fn supports_cuda(&self) -> bool {
225 self.compute_capabilities.cuda.is_some()
226 }
227
228 pub fn cuda_capability(&self) -> Option<&str> {
230 self.compute_capabilities.cuda.as_deref()
231 }
232
233 pub fn supports_rocm(&self) -> bool {
235 self.compute_capabilities.rocm
236 }
237
238 pub fn supports_directml(&self) -> bool {
240 self.compute_capabilities.directml
241 }
242
243 pub fn supports_opencl(&self) -> bool {
245 self.compute_capabilities.opencl
246 }
247
248 pub fn supports_vulkan(&self) -> bool {
250 self.compute_capabilities.vulkan
251 }
252
253 pub fn supports_metal(&self) -> bool {
255 self.compute_capabilities.metal
256 }
257
258 pub fn usage_percent(&self) -> Option<f32> {
260 self.usage_percent
261 }
262
263 pub fn temperature(&self) -> Option<f32> {
265 self.temperature
266 }
267
268 fn default_gpu() -> Self {
270 Self {
271 vendor: GPUVendor::Unknown("Generic".to_string()),
272 model_name: "Unknown GPU".to_string(),
273 gpu_type: GPUType::Unknown,
274 memory_mb: 1024, memory_type: None,
276 memory_bandwidth: None,
277 base_clock: None,
278 boost_clock: None,
279 memory_clock: None,
280 shader_units: None,
281 rt_cores: None,
282 tensor_cores: None,
283 compute_capabilities: ComputeCapabilities {
284 cuda: None,
285 rocm: false,
286 directml: cfg!(target_os = "windows"),
287 opencl: false,
288 vulkan: false,
289 metal: cfg!(target_os = "macos"),
290 compute_units: None,
291 max_workgroup_size: None,
292 },
293 usage_percent: None,
294 temperature: None,
295 power_consumption: None,
296 power_limit: None,
297 driver_version: None,
298 vbios_version: None,
299 pci_device_id: None,
300 pci_subsystem_id: None,
301 }
302 }
303
304 fn query_nvidia_gpus() -> Result<Vec<Self>> {
305 #[cfg(feature = "nvidia")]
306 {
307 let nvml = match Nvml::init() {
308 Ok(nvml) => nvml,
309 Err(_) => return Ok(vec![]),
310 };
311
312 let mut gpus = Vec::new();
313 let device_count = nvml.device_count().unwrap_or(0);
314
315 for i in 0..device_count {
316 if let Ok(device) = nvml.device_by_index(i) {
317 let name = device.name().unwrap_or_default();
318 let memory_info = device.memory_info().ok();
319 let cuda_capability = device.cuda_compute_capability().ok();
320 let driver_version = nvml.sys_driver_version().unwrap_or_default();
321
322 let gpu = Self {
323 vendor: GPUVendor::NVIDIA,
324 model_name: name,
325 gpu_type: GPUType::Discrete,
326 memory_mb: memory_info.map(|m| m.total / 1024 / 1024).unwrap_or(0),
327 memory_type: Some("GDDR6".to_string()),
328 memory_bandwidth: None,
329 base_clock: None,
330 boost_clock: None,
331 memory_clock: None,
332 shader_units: None,
333 rt_cores: None,
334 tensor_cores: None,
335 compute_capabilities: ComputeCapabilities {
336 cuda: cuda_capability.map(|c| format!("{}.{}", c.major, c.minor)),
337 rocm: false,
338 directml: cfg!(target_os = "windows"),
339 opencl: true,
340 vulkan: true,
341 metal: cfg!(target_os = "macos"),
342 compute_units: None,
343 max_workgroup_size: None,
344 },
345 usage_percent: device.utilization_rates().ok().map(|u| u.gpu as f32),
346 temperature: device
347 .temperature(
348 nvml_wrapper::enum_wrappers::device::TemperatureSensor::Gpu,
349 )
350 .ok()
351 .map(|t| t as f32),
352 power_consumption: device.power_usage().ok().map(|p| p as f32 / 1000.0),
353 power_limit: device
354 .power_management_limit_default()
355 .ok()
356 .map(|p| p as f32 / 1000.0),
357 driver_version: Some(driver_version),
358 vbios_version: device.vbios_version().ok(),
359 pci_device_id: None,
360 pci_subsystem_id: None,
361 };
362
363 gpus.push(gpu);
364 }
365 }
366
367 Ok(gpus)
368 }
369 #[cfg(not(feature = "nvidia"))]
370 {
371 Ok(vec![])
372 }
373 }
374
375 fn query_amd_gpus() -> Result<Vec<Self>> {
376 #[cfg(feature = "amd")]
377 {
378 Ok(vec![])
381 }
382 #[cfg(not(feature = "amd"))]
383 {
384 Ok(vec![])
385 }
386 }
387
388 fn query_intel_gpus() -> Result<Vec<Self>> {
389 #[cfg(target_os = "windows")]
390 {
391 match WMIConnection::new(COMLibrary::new()?) {
393 Ok(wmi_con) => {
394 let results: Vec<std::collections::HashMap<String, wmi::Variant>> = wmi_con
395 .raw_query("SELECT Name, AdapterRAM FROM Win32_VideoController WHERE Name LIKE '%Intel%'")
396 .unwrap_or_default();
397
398 let mut gpus = Vec::new();
399 for result in results {
400 if let (Some(wmi::Variant::String(name)), Some(wmi::Variant::UI4(ram))) =
401 (result.get("Name"), result.get("AdapterRAM"))
402 {
403 let gpu = Self {
404 vendor: GPUVendor::Intel,
405 model_name: name.clone(),
406 gpu_type: GPUType::Integrated,
407 memory_mb: *ram as u64 / 1024 / 1024,
408 memory_type: Some("System".to_string()),
409 memory_bandwidth: None,
410 base_clock: None,
411 boost_clock: None,
412 memory_clock: None,
413 shader_units: None,
414 rt_cores: None,
415 tensor_cores: None,
416 compute_capabilities: ComputeCapabilities {
417 cuda: None,
418 rocm: false,
419 directml: true,
420 opencl: true,
421 vulkan: true,
422 metal: false,
423 compute_units: None,
424 max_workgroup_size: None,
425 },
426 usage_percent: None,
427 temperature: None,
428 power_consumption: None,
429 power_limit: None,
430 driver_version: None,
431 vbios_version: None,
432 pci_device_id: None,
433 pci_subsystem_id: None,
434 };
435
436 gpus.push(gpu);
437 }
438 }
439
440 Ok(gpus)
441 }
442 Err(_) => Ok(vec![]),
443 }
444 }
445 #[cfg(not(target_os = "windows"))]
446 {
447 Ok(vec![])
448 }
449 }
450
451 fn query_generic_gpus() -> Result<Vec<Self>> {
452 #[cfg(target_os = "windows")]
454 {
455 use std::collections::HashMap;
456 use wmi::{COMLibrary, WMIConnection, Variant};
457
458 let com_con = COMLibrary::new()?;
459 let wmi_con = WMIConnection::new(com_con)?;
460
461 let results: Vec<HashMap<String, Variant>> = wmi_con
462 .raw_query("SELECT * FROM Win32_VideoController WHERE PNPDeviceID IS NOT NULL")?;
463
464 let mut gpus = Vec::new();
465
466 for gpu in results {
467 let name = gpu.get("Name")
468 .and_then(|v| match v {
469 Variant::String(s) => Some(s.clone()),
470 _ => None,
471 })
472 .unwrap_or_else(|| "Unknown GPU".to_string());
473
474 let adapter_ram = gpu.get("AdapterRAM")
475 .and_then(|v| match v {
476 Variant::UI4(val) => Some(*val as u64),
477 Variant::UI8(val) => Some(*val),
478 _ => None,
479 })
480 .unwrap_or(0);
481
482 let device_id = gpu.get("PNPDeviceID")
483 .and_then(|v| match v {
484 Variant::String(s) => Some(s.clone()),
485 _ => None,
486 })
487 .unwrap_or_else(|| "".to_string());
488
489 let driver_version = gpu.get("DriverVersion")
490 .and_then(|v| match v {
491 Variant::String(s) => Some(s.clone()),
492 _ => None,
493 });
494
495 let vendor = if name.to_lowercase().contains("nvidia") || device_id.contains("VEN_10DE") {
497 GPUVendor::NVIDIA
498 } else if name.to_lowercase().contains("amd") || name.to_lowercase().contains("radeon") || device_id.contains("VEN_1002") {
499 GPUVendor::AMD
500 } else if name.to_lowercase().contains("intel") || device_id.contains("VEN_8086") {
501 GPUVendor::Intel
502 } else {
503 GPUVendor::Unknown("Generic".to_string())
504 };
505
506 let gpu_type = Self::classify_gpu_type(&name, &vendor, adapter_ram);
508
509 let memory_mb = if adapter_ram > 0 {
511 adapter_ram / (1024 * 1024)
512 } else {
513 match (&vendor, &gpu_type) {
515 (GPUVendor::NVIDIA, GPUType::Datacenter) => 32768, (GPUVendor::NVIDIA, GPUType::Workstation) => 16384, (GPUVendor::NVIDIA, GPUType::Discrete) => 8192, (GPUVendor::AMD, GPUType::Datacenter) => 32768, (GPUVendor::AMD, GPUType::Workstation) => 16384, (GPUVendor::AMD, GPUType::Discrete) => 8192, (_, GPUType::Integrated) => 512, _ => 4096, }
524 };
525
526 let compute_capabilities = ComputeCapabilities {
528 cuda: if vendor == GPUVendor::NVIDIA { Some("Unknown".to_string()) } else { None },
529 rocm: vendor == GPUVendor::AMD && gpu_type == GPUType::Discrete,
530 directml: true, opencl: true, vulkan: true, metal: false, compute_units: None,
535 max_workgroup_size: None,
536 };
537
538 gpus.push(Self {
539 vendor,
540 model_name: name,
541 gpu_type,
542 memory_mb,
543 memory_type: None,
544 memory_bandwidth: None,
545 base_clock: None,
546 boost_clock: None,
547 memory_clock: None,
548 shader_units: None,
549 rt_cores: None,
550 tensor_cores: None,
551 compute_capabilities,
552 usage_percent: None,
553 temperature: None,
554 power_consumption: None,
555 power_limit: None,
556 driver_version,
557 vbios_version: None,
558 pci_device_id: Some(device_id),
559 pci_subsystem_id: None,
560 });
561 }
562
563 Ok(gpus)
564 }
565 #[cfg(not(target_os = "windows"))]
566 {
567 Ok(vec![])
569 }
570 }
571
572 fn classify_gpu_type(name: &str, vendor: &GPUVendor, adapter_ram: u64) -> GPUType {
574 let name_lower = name.to_lowercase();
575
576 if Self::is_datacenter_gpu(&name_lower, vendor) {
578 return GPUType::Datacenter;
579 }
580
581 if Self::is_workstation_gpu(&name_lower, vendor) {
583 return GPUType::Workstation;
584 }
585
586 if Self::is_integrated_gpu(&name_lower, vendor, adapter_ram) {
588 return GPUType::Integrated;
589 }
590
591 GPUType::Discrete
593 }
594
595 fn is_datacenter_gpu(name: &str, vendor: &GPUVendor) -> bool {
597 match vendor {
598 GPUVendor::NVIDIA => {
599 name.contains("tesla") ||
600 name.contains("a100") ||
601 name.contains("h100") ||
602 name.contains("h200") ||
603 name.contains("v100") ||
604 name.contains("p100") ||
605 name.contains("k80") ||
606 name.contains("k40") ||
607 name.contains("l40") ||
608 name.contains("l4") ||
609 name.contains("data center") ||
610 name.contains("datacenter") ||
611 name.contains("dgx") ||
612 name.contains("hgx")
613 },
614 GPUVendor::AMD => {
615 name.contains("instinct") ||
616 name.contains("mi50") ||
617 name.contains("mi100") ||
618 name.contains("mi200") ||
619 name.contains("mi250") ||
620 name.contains("mi300") ||
621 name.contains("cdna") ||
622 name.contains("datacenter") ||
623 name.contains("server")
624 },
625 GPUVendor::Intel => {
626 name.contains("ponte vecchio") ||
627 name.contains("data center") ||
628 name.contains("max") && (name.contains("1100") || name.contains("1550"))
629 },
630 _ => false,
631 }
632 }
633
634 fn is_workstation_gpu(name: &str, vendor: &GPUVendor) -> bool {
636 match vendor {
637 GPUVendor::NVIDIA => {
638 name.contains("quadro") ||
639 name.contains("rtx a") || name.contains("rtx 4000") || name.contains("rtx 5000") || name.contains("rtx 6000") ||
643 name.contains("rtx 8000") ||
644 name.contains("titan") || name.contains("nvs") || name.contains("t1000") || name.contains("t400") ||
648 name.contains("t600") ||
649 (name.contains("professional") && !name.contains("geforce"))
650 },
651 GPUVendor::AMD => {
652 name.contains("radeon pro") ||
653 name.contains("firepro") ||
654 name.contains("wx ") || name.contains("w6") || name.contains("w7") || name.contains("workstation") ||
658 name.contains("professional")
659 },
660 GPUVendor::Intel => {
661 name.contains("pro") ||
662 name.contains("workstation") ||
663 name.contains("professional")
664 },
665 _ => false,
666 }
667 }
668
669 fn is_integrated_gpu(name: &str, vendor: &GPUVendor, adapter_ram: u64) -> bool {
671 let integrated_keywords = name.contains("integrated") ||
673 name.contains("uhd") ||
674 name.contains("iris") ||
675 name.contains("vega") && name.contains("graphics") ||
676 name.contains("radeon graphics") ||
677 name.contains("apu") ||
678 name.contains("mobile") && !name.contains("rtx") ||
679 name.contains("embedded");
680
681 let low_memory = vendor == &GPUVendor::AMD && adapter_ram < 2_000_000_000;
683
684 integrated_keywords || low_memory
685 }
686}