lemonade-sdk 9.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. lemonade/__init__.py +5 -0
  2. lemonade/api.py +180 -0
  3. lemonade/cache.py +92 -0
  4. lemonade/cli.py +173 -0
  5. lemonade/common/__init__.py +0 -0
  6. lemonade/common/build.py +176 -0
  7. lemonade/common/cli_helpers.py +139 -0
  8. lemonade/common/exceptions.py +98 -0
  9. lemonade/common/filesystem.py +368 -0
  10. lemonade/common/inference_engines.py +408 -0
  11. lemonade/common/network.py +93 -0
  12. lemonade/common/printing.py +110 -0
  13. lemonade/common/status.py +471 -0
  14. lemonade/common/system_info.py +1411 -0
  15. lemonade/common/test_helpers.py +28 -0
  16. lemonade/profilers/__init__.py +1 -0
  17. lemonade/profilers/agt_power.py +437 -0
  18. lemonade/profilers/hwinfo_power.py +429 -0
  19. lemonade/profilers/memory_tracker.py +259 -0
  20. lemonade/profilers/profiler.py +58 -0
  21. lemonade/sequence.py +363 -0
  22. lemonade/state.py +159 -0
  23. lemonade/tools/__init__.py +1 -0
  24. lemonade/tools/accuracy.py +432 -0
  25. lemonade/tools/adapter.py +114 -0
  26. lemonade/tools/bench.py +302 -0
  27. lemonade/tools/flm/__init__.py +1 -0
  28. lemonade/tools/flm/utils.py +305 -0
  29. lemonade/tools/huggingface/bench.py +187 -0
  30. lemonade/tools/huggingface/load.py +235 -0
  31. lemonade/tools/huggingface/utils.py +359 -0
  32. lemonade/tools/humaneval.py +264 -0
  33. lemonade/tools/llamacpp/bench.py +255 -0
  34. lemonade/tools/llamacpp/load.py +222 -0
  35. lemonade/tools/llamacpp/utils.py +1260 -0
  36. lemonade/tools/management_tools.py +319 -0
  37. lemonade/tools/mmlu.py +319 -0
  38. lemonade/tools/oga/__init__.py +0 -0
  39. lemonade/tools/oga/bench.py +120 -0
  40. lemonade/tools/oga/load.py +804 -0
  41. lemonade/tools/oga/migration.py +403 -0
  42. lemonade/tools/oga/utils.py +462 -0
  43. lemonade/tools/perplexity.py +147 -0
  44. lemonade/tools/prompt.py +263 -0
  45. lemonade/tools/report/__init__.py +0 -0
  46. lemonade/tools/report/llm_report.py +203 -0
  47. lemonade/tools/report/table.py +899 -0
  48. lemonade/tools/server/__init__.py +0 -0
  49. lemonade/tools/server/flm.py +133 -0
  50. lemonade/tools/server/llamacpp.py +320 -0
  51. lemonade/tools/server/serve.py +2123 -0
  52. lemonade/tools/server/static/favicon.ico +0 -0
  53. lemonade/tools/server/static/index.html +279 -0
  54. lemonade/tools/server/static/js/chat.js +1059 -0
  55. lemonade/tools/server/static/js/model-settings.js +183 -0
  56. lemonade/tools/server/static/js/models.js +1395 -0
  57. lemonade/tools/server/static/js/shared.js +556 -0
  58. lemonade/tools/server/static/logs.html +191 -0
  59. lemonade/tools/server/static/styles.css +2654 -0
  60. lemonade/tools/server/static/webapp.html +321 -0
  61. lemonade/tools/server/tool_calls.py +153 -0
  62. lemonade/tools/server/tray.py +664 -0
  63. lemonade/tools/server/utils/macos_tray.py +226 -0
  64. lemonade/tools/server/utils/port.py +77 -0
  65. lemonade/tools/server/utils/thread.py +85 -0
  66. lemonade/tools/server/utils/windows_tray.py +408 -0
  67. lemonade/tools/server/webapp.py +34 -0
  68. lemonade/tools/server/wrapped_server.py +559 -0
  69. lemonade/tools/tool.py +374 -0
  70. lemonade/version.py +1 -0
  71. lemonade_install/__init__.py +1 -0
  72. lemonade_install/install.py +239 -0
  73. lemonade_sdk-9.1.1.dist-info/METADATA +276 -0
  74. lemonade_sdk-9.1.1.dist-info/RECORD +84 -0
  75. lemonade_sdk-9.1.1.dist-info/WHEEL +5 -0
  76. lemonade_sdk-9.1.1.dist-info/entry_points.txt +5 -0
  77. lemonade_sdk-9.1.1.dist-info/licenses/LICENSE +201 -0
  78. lemonade_sdk-9.1.1.dist-info/licenses/NOTICE.md +47 -0
  79. lemonade_sdk-9.1.1.dist-info/top_level.txt +3 -0
  80. lemonade_server/cli.py +805 -0
  81. lemonade_server/model_manager.py +758 -0
  82. lemonade_server/pydantic_models.py +159 -0
  83. lemonade_server/server_models.json +643 -0
  84. lemonade_server/settings.py +39 -0
@@ -0,0 +1,1411 @@
1
+ from abc import ABC, abstractmethod
2
+ import importlib.metadata
3
+ import logging
4
+ import platform
5
+ import re
6
+ import subprocess
7
+ import ctypes
8
+ import glob
9
+ from .inference_engines import detect_inference_engines
10
+
11
+ # AMD GPU classification keywords - shared across all OS implementations
12
+ # If a GPU name contains any of these keywords, it's considered discrete
13
+ # Everything else is assumed to be integrated
14
+ AMD_DISCRETE_GPU_KEYWORDS = [
15
+ "rx ",
16
+ "xt",
17
+ "pro w",
18
+ "pro v",
19
+ "radeon pro",
20
+ "firepro",
21
+ "fury",
22
+ ]
23
+
24
+ # NVIDIA GPU classification keywords - shared across all OS implementations
25
+ # NVIDIA GPUs are typically discrete by default, but we include keywords for clarity
26
+ NVIDIA_DISCRETE_GPU_KEYWORDS = [
27
+ "geforce",
28
+ "rtx",
29
+ "gtx",
30
+ "quadro",
31
+ "tesla",
32
+ "titan",
33
+ "a100",
34
+ "a40",
35
+ "a30",
36
+ "a10",
37
+ "a6000",
38
+ "a5000",
39
+ "a4000",
40
+ "a2000",
41
+ "t1000",
42
+ "t600",
43
+ "t400",
44
+ ]
45
+
46
+
47
+ class SystemInfo(ABC):
48
+ """
49
+ Abstract base class for OS-dependent system information classes.
50
+ """
51
+
52
+ def __init__(self):
53
+ pass
54
+
55
+ def get_dict(self):
56
+ """
57
+ Retrieves all the system information into a dictionary.
58
+
59
+ Returns:
60
+ dict: System information.
61
+ """
62
+ info_dict = {
63
+ "OS Version": self.get_os_version(),
64
+ }
65
+ return info_dict
66
+
67
+ def get_device_dict(self):
68
+ """
69
+ Retrieves device information into a dictionary.
70
+
71
+ Returns:
72
+ dict: Device information.
73
+ """
74
+ device_dict = {
75
+ "cpu": self.get_cpu_device(),
76
+ "amd_igpu": self.get_amd_igpu_device(include_inference_engines=True),
77
+ "amd_dgpu": self.get_amd_dgpu_devices(include_inference_engines=True),
78
+ "nvidia_dgpu": self.get_nvidia_dgpu_devices(include_inference_engines=True),
79
+ "npu": self.get_npu_device(),
80
+ }
81
+ return device_dict
82
+
83
+ @abstractmethod
84
+ def get_cpu_device(self) -> dict:
85
+ """
86
+ Retrieves CPU device information.
87
+
88
+ Returns:
89
+ dict: CPU device information.
90
+ """
91
+
92
+ @abstractmethod
93
+ def get_amd_igpu_device(self, include_inference_engines: bool = False) -> dict:
94
+ """
95
+ Retrieves AMD integrated GPU device information.
96
+
97
+ Returns:
98
+ dict: AMD iGPU device information.
99
+ """
100
+
101
+ @abstractmethod
102
+ def get_amd_dgpu_devices(self, include_inference_engines: bool = False) -> list:
103
+ """
104
+ Retrieves AMD discrete GPU device information.
105
+
106
+ Returns:
107
+ list: List of AMD dGPU device information.
108
+ """
109
+
110
+ @abstractmethod
111
+ def get_nvidia_dgpu_devices(self, include_inference_engines: bool = False) -> list:
112
+ """
113
+ Retrieves NVIDIA discrete GPU device information.
114
+
115
+ Returns:
116
+ list: List of NVIDIA dGPU device information.
117
+ """
118
+
119
+ @abstractmethod
120
+ def get_npu_device(self) -> dict:
121
+ """
122
+ Retrieves NPU device information.
123
+
124
+ Returns:
125
+ dict: NPU device information.
126
+ """
127
+
128
+ @staticmethod
129
+ def get_os_version() -> str:
130
+ """
131
+ Retrieves the OS version.
132
+
133
+ Returns:
134
+ str: OS Version.
135
+ """
136
+ try:
137
+ return platform.platform()
138
+ except Exception as e: # pylint: disable=broad-except
139
+ return f"ERROR - {e}"
140
+
141
+ @staticmethod
142
+ def get_python_packages() -> list:
143
+ """
144
+ Retrieves the Python package versions.
145
+
146
+ Returns:
147
+ list: List of Python package versions in the form ["package-name==package-version", ...]
148
+ """
149
+ # Get Python Packages
150
+ distributions = importlib.metadata.distributions()
151
+ return [
152
+ f"{dist.metadata['name']}=={dist.metadata['version']}"
153
+ for dist in distributions
154
+ ]
155
+
156
+
157
+ class WindowsSystemInfo(SystemInfo):
158
+ """
159
+ Class used to access system information in Windows.
160
+ """
161
+
162
+ def __init__(self):
163
+ super().__init__()
164
+ import wmi
165
+
166
+ self.connection = wmi.WMI()
167
+
168
+ def get_cpu_device(self) -> dict:
169
+ """
170
+ Retrieves CPU device information using WMI.
171
+
172
+ Returns:
173
+ dict: CPU device information.
174
+ """
175
+ try:
176
+ processors = self.connection.Win32_Processor()
177
+ if processors:
178
+ processor = processors[0]
179
+ cpu_name = processor.Name.strip()
180
+ cpu_info = {
181
+ "name": cpu_name,
182
+ "cores": processor.NumberOfCores,
183
+ "threads": processor.NumberOfLogicalProcessors,
184
+ "max_clock_speed_mhz": processor.MaxClockSpeed,
185
+ "available": True,
186
+ }
187
+
188
+ # Add inference engine detection
189
+ cpu_info["inference_engines"] = self._detect_inference_engines(
190
+ "cpu", cpu_name
191
+ )
192
+ return cpu_info
193
+
194
+ except Exception as e: # pylint: disable=broad-except
195
+ return {"available": False, "error": f"CPU detection failed: {e}"}
196
+
197
+ return {"available": False, "error": "No CPU information found"}
198
+
199
+ def _detect_amd_gpus(self, gpu_type: str, include_inference_engines: bool = False):
200
+ """
201
+ Shared AMD GPU detection logic for both integrated and discrete GPUs.
202
+ Uses keyword-based classification for simplicity and reliability.
203
+
204
+ Args:
205
+ gpu_type: Either "integrated" or "discrete"
206
+
207
+ Returns:
208
+ list: List of detected GPU info dictionaries
209
+ """
210
+ logging.debug(f"Starting AMD GPU detection for type: {gpu_type}")
211
+ gpu_devices = []
212
+ try:
213
+ video_controllers = self.connection.Win32_VideoController()
214
+ logging.debug(f"Found {len(video_controllers)} video controllers")
215
+
216
+ for i, controller in enumerate(video_controllers):
217
+ logging.debug(
218
+ f"Controller {i}: Name='{controller.Name}', "
219
+ f"PNPDeviceID='{getattr(controller, 'PNPDeviceID', 'N/A')}'"
220
+ )
221
+
222
+ if (
223
+ controller.Name
224
+ and "AMD" in controller.Name
225
+ and "Radeon" in controller.Name
226
+ ):
227
+ logging.debug(f"Found AMD Radeon GPU: {controller.Name}")
228
+
229
+ name_lower = controller.Name.lower()
230
+ logging.debug(f"GPU name (lowercase): {name_lower}")
231
+
232
+ # Keyword-based classification - simple and reliable
233
+ matching_keywords = [
234
+ kw for kw in AMD_DISCRETE_GPU_KEYWORDS if kw in name_lower
235
+ ]
236
+ is_discrete_by_name = any(
237
+ kw in name_lower for kw in AMD_DISCRETE_GPU_KEYWORDS
238
+ )
239
+ is_integrated = not is_discrete_by_name
240
+
241
+ logging.debug(f"Matching discrete keywords: {matching_keywords}")
242
+ logging.debug(
243
+ f"Classified as discrete: {not is_integrated}, integrated: {is_integrated}"
244
+ )
245
+
246
+ # Filter based on requested type
247
+ if (gpu_type == "integrated" and is_integrated) or (
248
+ gpu_type == "discrete" and not is_integrated
249
+ ):
250
+ logging.debug(
251
+ f"GPU matches requested type '{gpu_type}', processing..."
252
+ )
253
+
254
+ device_type = "amd_igpu" if is_integrated else "amd_dgpu"
255
+ gpu_info = {
256
+ "name": controller.Name,
257
+ "available": True,
258
+ }
259
+ logging.debug(f"Created GPU info for {device_type}: {gpu_info}")
260
+
261
+ driver_version = self.get_driver_version(
262
+ "AMD-OpenCL User Mode Driver"
263
+ )
264
+ gpu_info["driver_version"] = (
265
+ driver_version if driver_version else "Unknown"
266
+ )
267
+ logging.debug(f"Driver version: {gpu_info['driver_version']}")
268
+
269
+ # Get VRAM information for discrete GPUs
270
+ if not is_integrated: # Only add VRAM for discrete GPUs
271
+ # Try dxdiag first (most reliable for dedicated memory)
272
+ vram_gb = self._get_gpu_vram_dxdiag_simple(controller.Name)
273
+
274
+ # Fallback to WMI if dxdiag fails
275
+ if vram_gb == 0.0:
276
+ vram_gb = self._get_gpu_vram_wmi(controller)
277
+
278
+ if vram_gb > 0.0:
279
+ gpu_info["vram_gb"] = vram_gb
280
+ else:
281
+ gpu_info["vram_gb"] = "Unknown"
282
+
283
+ if include_inference_engines:
284
+ gpu_info["inference_engines"] = (
285
+ self._detect_inference_engines(
286
+ device_type, controller.Name
287
+ )
288
+ )
289
+ gpu_devices.append(gpu_info)
290
+ logging.debug(f"Added GPU to devices list: {gpu_info}")
291
+ else:
292
+ logging.debug(
293
+ f"GPU does not match requested type '{gpu_type}', skipping"
294
+ )
295
+ continue
296
+ else:
297
+ logging.debug(
298
+ f"Skipping non-AMD/non-Radeon controller: {controller.Name}"
299
+ )
300
+
301
+ except Exception as e: # pylint: disable=broad-except
302
+ error_msg = f"AMD {gpu_type} GPU detection failed: {e}"
303
+ logging.debug(f"Exception in AMD GPU detection: {e}")
304
+ return [{"available": False, "error": error_msg}]
305
+
306
+ logging.debug(
307
+ f"AMD GPU detection completed. Found {len(gpu_devices)} {gpu_type} GPUs: "
308
+ f"{[gpu.get('name', 'Unknown') for gpu in gpu_devices]}"
309
+ )
310
+ return gpu_devices
311
+
312
+ def get_amd_igpu_device(self, include_inference_engines: bool = False) -> dict:
313
+ """
314
+ Retrieves AMD integrated GPU device information using keyword-based classification.
315
+
316
+ Returns:
317
+ dict: AMD iGPU device information.
318
+ """
319
+ igpu_devices = self._detect_amd_gpus(
320
+ "integrated", include_inference_engines=include_inference_engines
321
+ )
322
+ return (
323
+ igpu_devices[0]
324
+ if igpu_devices
325
+ else {"available": False, "error": "No AMD integrated GPU found"}
326
+ )
327
+
328
+ def get_amd_dgpu_devices(self, include_inference_engines: bool = False):
329
+ """
330
+ Retrieves AMD discrete GPU device information using keyword-based classification.
331
+
332
+ Returns:
333
+ list: List of AMD dGPU device information.
334
+ """
335
+ dgpu_devices = self._detect_amd_gpus(
336
+ "discrete", include_inference_engines=include_inference_engines
337
+ )
338
+ return (
339
+ dgpu_devices
340
+ if dgpu_devices
341
+ else [{"available": False, "error": "No AMD discrete GPU found"}]
342
+ )
343
+
344
+ def get_nvidia_dgpu_devices(self, include_inference_engines: bool = False) -> list:
345
+ """
346
+ Retrieves NVIDIA discrete GPU device information using WMI.
347
+
348
+ Returns:
349
+ list: List of NVIDIA dGPU device information.
350
+ """
351
+ gpu_devices = []
352
+ try:
353
+ video_controllers = self.connection.Win32_VideoController()
354
+ for controller in video_controllers:
355
+ if controller.Name and "NVIDIA" in controller.Name.upper():
356
+ name_lower = controller.Name.lower()
357
+
358
+ # Most NVIDIA GPUs are discrete, but we can check keywords for confirmation
359
+ is_discrete = (
360
+ any(kw in name_lower for kw in NVIDIA_DISCRETE_GPU_KEYWORDS)
361
+ or "nvidia" in name_lower
362
+ ) # Default to discrete for NVIDIA
363
+
364
+ if is_discrete:
365
+ gpu_info = {
366
+ "name": controller.Name,
367
+ "available": True,
368
+ }
369
+
370
+ # Try to get NVIDIA driver version using multiple methods
371
+ driver_version = self._get_nvidia_driver_version_windows()
372
+ gpu_info["driver_version"] = (
373
+ driver_version if driver_version else "Unknown"
374
+ )
375
+
376
+ # Get VRAM information
377
+ vram_gb = self._get_gpu_vram_wmi(controller)
378
+ if vram_gb == 0.0:
379
+ # Fallback to nvidia-smi
380
+ vram_gb = self._get_nvidia_vram_smi()
381
+
382
+ if vram_gb > 0.0:
383
+ gpu_info["vram_gb"] = vram_gb
384
+ else:
385
+ gpu_info["vram_gb"] = "Unknown"
386
+
387
+ if include_inference_engines:
388
+ gpu_info["inference_engines"] = (
389
+ self._detect_inference_engines(
390
+ "nvidia_dgpu", controller.Name
391
+ )
392
+ )
393
+ gpu_devices.append(gpu_info)
394
+
395
+ except Exception as e: # pylint: disable=broad-except
396
+ error_msg = f"NVIDIA discrete GPU detection failed: {e}"
397
+ return [{"available": False, "error": error_msg}]
398
+
399
+ return (
400
+ gpu_devices
401
+ if gpu_devices
402
+ else [{"available": False, "error": "No NVIDIA discrete GPU found"}]
403
+ )
404
+
405
+ def get_npu_device(self) -> dict:
406
+ """
407
+ Retrieves NPU device information using existing methods.
408
+
409
+ Returns:
410
+ dict: NPU device information.
411
+ """
412
+ try:
413
+ # Check if NPU driver is present
414
+ driver_version = self.get_driver_version("NPU Compute Accelerator Device")
415
+ if driver_version:
416
+ power_mode = self.get_npu_power_mode()
417
+ npu_info = {
418
+ "name": "AMD NPU",
419
+ "driver_version": driver_version,
420
+ "power_mode": power_mode,
421
+ "available": True,
422
+ }
423
+
424
+ # Add inference engine detection
425
+ npu_info["inference_engines"] = self._detect_inference_engines(
426
+ "npu", "AMD NPU"
427
+ )
428
+ return npu_info
429
+ except Exception as e: # pylint: disable=broad-except
430
+ return {"available": False, "error": f"NPU detection failed: {e}"}
431
+
432
+ return {"available": False, "error": "No NPU device found"}
433
+
434
+ def get_processor_name(self) -> str:
435
+ """
436
+ Retrieves the name of the processor.
437
+
438
+ Returns:
439
+ str: Name of the processor.
440
+ """
441
+ processors = self.connection.Win32_Processor()
442
+ if processors:
443
+ return (
444
+ f"{processors[0].Name.strip()} "
445
+ f"({processors[0].NumberOfCores} cores, "
446
+ f"{processors[0].NumberOfLogicalProcessors} logical processors)"
447
+ )
448
+ return "Processor information not found."
449
+
450
+ def get_basic_processor_name(self) -> str:
451
+ """
452
+ Retrieves the basic name of the processor without core/thread details.
453
+
454
+ Returns:
455
+ str: Basic name of the processor.
456
+ """
457
+ processors = self.connection.Win32_Processor()
458
+ if processors:
459
+ return processors[0].Name.strip()
460
+ return "Processor information not found."
461
+
462
+ def get_system_model(self) -> str:
463
+ """
464
+ Retrieves the model of the computer system.
465
+
466
+ Returns:
467
+ str: Model of the computer system.
468
+ """
469
+ systems = self.connection.Win32_ComputerSystem()
470
+ if systems:
471
+ return systems[0].Model
472
+ return "System model information not found."
473
+
474
+ def get_physical_memory(self) -> str:
475
+ """
476
+ Retrieves the physical memory of the computer system.
477
+
478
+ Returns:
479
+ str: Physical memory.
480
+ """
481
+ memory = self.connection.Win32_PhysicalMemory()
482
+ if memory:
483
+ total_capacity = sum([int(m.Capacity) for m in memory])
484
+ total_capacity_str = f"{total_capacity/(1024**3)} GB"
485
+ return total_capacity_str
486
+ return "Physical memory information not found."
487
+
488
+ def get_bios_version(self) -> str:
489
+ """
490
+ Retrieves the BIOS Version of the computer system.
491
+
492
+ Returns:
493
+ str: BIOS Version.
494
+ """
495
+ bios = self.connection.Win32_BIOS()
496
+ if bios:
497
+ return bios[0].Name
498
+ return "BIOS Version not found."
499
+
500
+ def get_max_clock_speed(self) -> str:
501
+ """
502
+ Retrieves the max clock speed of the CPU of the system.
503
+
504
+ Returns:
505
+ str: Max CPU clock speed.
506
+ """
507
+ processor = self.connection.Win32_Processor()
508
+ if processor:
509
+ return f"{processor[0].MaxClockSpeed} MHz"
510
+ return "Max CPU clock speed not found."
511
+
512
+ def get_driver_version(self, device_name) -> str:
513
+ """
514
+ Retrieves the driver version for the specified device name.
515
+
516
+ Returns:
517
+ str: Driver version, or None if device driver not found.
518
+ """
519
+ drivers = self.connection.Win32_PnPSignedDriver(DeviceName=device_name)
520
+ if drivers:
521
+ return drivers[0].DriverVersion
522
+ return ""
523
+
524
+ def _get_gpu_vram_wmi(self, controller) -> float:
525
+ """
526
+ Get GPU VRAM from WMI VideoController.
527
+
528
+ Args:
529
+ controller: WMI Win32_VideoController object
530
+
531
+ Returns:
532
+ float: VRAM in GB, or 0.0 if detection fails
533
+ """
534
+ try:
535
+ if hasattr(controller, "AdapterRAM"):
536
+ adapter_ram = controller.AdapterRAM
537
+ if adapter_ram and adapter_ram > 0:
538
+ # AdapterRAM is in bytes, convert to GB
539
+ vram_bytes = int(adapter_ram)
540
+ vram_gb = round(vram_bytes / (1024**3), 1)
541
+ return vram_gb
542
+ except (ValueError, AttributeError):
543
+ pass
544
+ return 0.0
545
+
546
+ def _get_gpu_vram_dxdiag_simple(self, gpu_name: str) -> float:
547
+ """
548
+ Get GPU VRAM using dxdiag, looking specifically for dedicated memory.
549
+
550
+ Args:
551
+ gpu_name: Name of the GPU to look for
552
+
553
+ Returns:
554
+ float: VRAM in GB, or 0.0 if detection fails
555
+ """
556
+ try:
557
+ import tempfile
558
+ import os
559
+
560
+ with tempfile.NamedTemporaryFile(
561
+ mode="w+", suffix=".txt", delete=False
562
+ ) as temp_file:
563
+ temp_path = temp_file.name
564
+
565
+ try:
566
+ subprocess.run(
567
+ ["dxdiag", "/t", temp_path],
568
+ check=True,
569
+ timeout=30,
570
+ capture_output=True,
571
+ )
572
+
573
+ with open(temp_path, "r", encoding="utf-8", errors="ignore") as f:
574
+ dxdiag_output = f.read()
575
+
576
+ lines = dxdiag_output.split("\n")
577
+ found_gpu = False
578
+
579
+ for line in lines:
580
+ line = line.strip()
581
+
582
+ # Check if this is our GPU
583
+ if "Card name:" in line and gpu_name.lower() in line.lower():
584
+ found_gpu = True
585
+ continue
586
+
587
+ # Look for dedicated memory line
588
+ if found_gpu and "Dedicated Memory:" in line:
589
+ memory_match = re.search(
590
+ r"(\d+(?:\.\d+)?)\s*MB", line, re.IGNORECASE
591
+ )
592
+ if memory_match:
593
+ vram_mb = float(memory_match.group(1))
594
+ vram_gb = round(vram_mb / 1024, 1)
595
+ return vram_gb
596
+
597
+ # Reset if we hit another display device
598
+ if "Card name:" in line and gpu_name.lower() not in line.lower():
599
+ found_gpu = False
600
+
601
+ finally:
602
+ try:
603
+ os.unlink(temp_path)
604
+ except Exception: # pylint: disable=broad-except
605
+ pass
606
+
607
+ except Exception: # pylint: disable=broad-except
608
+ pass
609
+
610
+ return 0.0
611
+
612
+ def _get_nvidia_driver_version_windows(self) -> str:
613
+ """
614
+ Get NVIDIA driver version on Windows using nvidia-smi and WMI fallback.
615
+
616
+ Returns:
617
+ str: Driver version, or empty string if detection fails
618
+ """
619
+ # Primary: Try nvidia-smi command
620
+ try:
621
+ output = (
622
+ subprocess.check_output(
623
+ [
624
+ "nvidia-smi",
625
+ "--query-gpu=driver_version",
626
+ "--format=csv,noheader,nounits",
627
+ ],
628
+ stderr=subprocess.DEVNULL,
629
+ )
630
+ .decode()
631
+ .strip()
632
+ )
633
+ if output and output != "N/A":
634
+ return output.split("\n")[0]
635
+ except (subprocess.CalledProcessError, FileNotFoundError):
636
+ pass
637
+
638
+ # Fallback: Try WMI Win32_PnPSignedDriver with NVIDIA patterns
639
+ try:
640
+ nvidia_patterns = [
641
+ "NVIDIA GeForce",
642
+ "NVIDIA RTX",
643
+ "NVIDIA GTX",
644
+ "NVIDIA Quadro",
645
+ ]
646
+ all_drivers = self.connection.Win32_PnPSignedDriver()
647
+ for driver in all_drivers:
648
+ if driver.DeviceName and any(
649
+ pattern in driver.DeviceName for pattern in nvidia_patterns
650
+ ):
651
+ if driver.DriverVersion:
652
+ return driver.DriverVersion
653
+ except Exception: # pylint: disable=broad-except
654
+ pass
655
+
656
+ return ""
657
+
658
+ def _get_nvidia_vram_smi(self) -> float:
659
+ """
660
+ Get NVIDIA GPU VRAM using nvidia-smi command.
661
+
662
+ Returns:
663
+ float: VRAM in GB, or 0.0 if detection fails
664
+ """
665
+ try:
666
+ output = (
667
+ subprocess.check_output(
668
+ [
669
+ "nvidia-smi",
670
+ "--query-gpu=memory.total",
671
+ "--format=csv,noheader,nounits",
672
+ ],
673
+ stderr=subprocess.DEVNULL,
674
+ )
675
+ .decode()
676
+ .strip()
677
+ )
678
+
679
+ # nvidia-smi returns memory in MB
680
+ vram_mb = int(output.split("\n")[0])
681
+ vram_gb = round(vram_mb / 1024, 1)
682
+ return vram_gb
683
+ except (subprocess.CalledProcessError, FileNotFoundError, ValueError):
684
+ pass
685
+ return 0.0
686
+
687
+ @staticmethod
688
+ def get_npu_power_mode() -> str:
689
+ """
690
+ Retrieves the NPU power mode.
691
+
692
+ Returns:
693
+ str: NPU power mode.
694
+ """
695
+ try:
696
+ out = subprocess.check_output(
697
+ [
698
+ r"C:\Windows\System32\AMD\xrt-smi.exe",
699
+ "examine",
700
+ "-r",
701
+ "platform",
702
+ ],
703
+ stderr=subprocess.STDOUT,
704
+ ).decode()
705
+ lines = out.splitlines()
706
+ modes = [line.split()[-1] for line in lines if "Mode" in line]
707
+ if len(modes) > 0:
708
+ return modes[0]
709
+ except FileNotFoundError:
710
+ # xrt-smi not present
711
+ pass
712
+ except subprocess.CalledProcessError:
713
+ pass
714
+ return "NPU power mode not found."
715
+
716
+ @staticmethod
717
+ def get_windows_power_setting() -> str:
718
+ """
719
+ Retrieves the Windows power setting.
720
+
721
+ Returns:
722
+ str: Windows power setting.
723
+ """
724
+ try:
725
+ # Capture output as bytes
726
+ out_bytes = subprocess.check_output(["powercfg", "/getactivescheme"])
727
+
728
+ # Get system's OEM code page (e.g., cp437, cp850)
729
+ oem_cp = "cp" + str(ctypes.windll.kernel32.GetOEMCP())
730
+
731
+ # Decode using detected OEM code page
732
+ out = out_bytes.decode(oem_cp)
733
+
734
+ # Extract power scheme name from parentheses
735
+ match = re.search(r"\((.*?)\)", out)
736
+ if match:
737
+ return match.group(1)
738
+ return "Power scheme name not found in output"
739
+
740
+ except subprocess.CalledProcessError:
741
+ return "Windows power setting not found (command failed)"
742
+ except Exception as e: # pylint: disable=broad-except
743
+ return f"Error retrieving power setting: {str(e)}"
744
+
745
+ def get_dict(self) -> dict:
746
+ """
747
+ Retrieves all the system information into a dictionary.
748
+
749
+ Returns:
750
+ dict: System information.
751
+ """
752
+ info_dict = super().get_dict()
753
+ info_dict["Processor"] = self.get_basic_processor_name()
754
+ info_dict["OEM System"] = self.get_system_model()
755
+ info_dict["Physical Memory"] = self.get_physical_memory()
756
+ info_dict["BIOS Version"] = self.get_bios_version()
757
+ info_dict["CPU Max Clock"] = self.get_max_clock_speed()
758
+ info_dict["Windows Power Setting"] = self.get_windows_power_setting()
759
+ return info_dict
760
+
761
+ def _detect_inference_engines(self, device_type: str, device_name: str) -> dict:
762
+ """
763
+ Detect available inference engines for a specific device type.
764
+
765
+ Args:
766
+ device_type: Device type ("cpu", "amd_igpu", "amd_dgpu", "npu")
767
+ device_name: Device name
768
+
769
+ Returns:
770
+ dict: Available inference engines and their information.
771
+ """
772
+ try:
773
+ from .inference_engines import detect_inference_engines
774
+
775
+ return detect_inference_engines(device_type, device_name)
776
+ except Exception as e: # pylint: disable=broad-except
777
+ return {"error": f"Inference engine detection failed: {str(e)}"}
778
+
779
+
780
+ class WSLSystemInfo(SystemInfo):
781
+ """
782
+ Class used to access system information in WSL.
783
+ """
784
+
785
+ def get_cpu_device(self) -> dict:
786
+ """
787
+ Retrieves CPU device information in WSL environment.
788
+ """
789
+ return {"available": False, "error": "Device detection not supported in WSL"}
790
+
791
+ def get_amd_igpu_device(self, include_inference_engines: bool = False) -> dict:
792
+ """
793
+ Retrieves AMD integrated GPU device information in WSL environment.
794
+ """
795
+ return {"available": False, "error": "GPU detection not supported in WSL"}
796
+
797
+ def get_amd_dgpu_devices(self, include_inference_engines: bool = False) -> list:
798
+ """
799
+ Retrieves AMD discrete GPU device information in WSL environment.
800
+ """
801
+ return []
802
+
803
+ def get_nvidia_dgpu_devices(self, include_inference_engines: bool = False) -> list:
804
+ """
805
+ Retrieves NVIDIA discrete GPU device information in WSL environment.
806
+ """
807
+ return [
808
+ {"available": False, "error": "NVIDIA GPU detection not supported in WSL"}
809
+ ]
810
+
811
+ def get_npu_device(self) -> dict:
812
+ """
813
+ Retrieves NPU device information in WSL environment.
814
+ """
815
+ return {"available": False, "error": "NPU detection not supported in WSL"}
816
+
817
+ @staticmethod
818
+ def get_system_model() -> str:
819
+ """
820
+ Retrieves the model of the computer system.
821
+
822
+ Returns:
823
+ str: Model of the computer system.
824
+ """
825
+ try:
826
+ oem_info = (
827
+ subprocess.check_output(
828
+ 'powershell.exe -Command "wmic computersystem get model"',
829
+ shell=True,
830
+ )
831
+ .decode()
832
+ .strip()
833
+ )
834
+ oem_info = (
835
+ oem_info.replace("\r", "").replace("\n", "").split("Model")[-1].strip()
836
+ )
837
+ return oem_info
838
+ except Exception as e: # pylint: disable=broad-except
839
+ return f"ERROR - {e}"
840
+
841
+ def get_dict(self) -> dict:
842
+ """
843
+ Retrieves all the system information into a dictionary.
844
+
845
+ Returns:
846
+ dict: System information.
847
+ """
848
+ info_dict = super().get_dict()
849
+ info_dict["OEM System"] = self.get_system_model()
850
+ return info_dict
851
+
852
+
853
+ class LinuxSystemInfo(SystemInfo):
854
+ """
855
+ Class used to access system information in Linux.
856
+ """
857
+
858
+ def get_cpu_device(self) -> dict:
859
+ """
860
+ Retrieves CPU device information using /proc/cpuinfo and lscpu.
861
+
862
+ Returns:
863
+ dict: CPU device information.
864
+ """
865
+ try:
866
+ cpu_info = subprocess.check_output("lscpu", shell=True).decode()
867
+ cpu_data = {}
868
+
869
+ for line in cpu_info.split("\n"):
870
+ if "Model name:" in line:
871
+ cpu_data["name"] = line.split(":")[1].strip()
872
+ elif "CPU(s):" in line and "NUMA" not in line:
873
+ cpu_data["threads"] = int(line.split(":")[1].strip())
874
+ elif "Core(s) per socket:" in line:
875
+ cores_per_socket = int(line.split(":")[1].strip())
876
+ sockets = (
877
+ 1 # Default to 1 socket as most laptops have a single socket
878
+ )
879
+ for l in cpu_info.split("\n"):
880
+ if "Socket(s):" in l:
881
+ sockets = int(l.split(":")[1].strip())
882
+ break
883
+ cpu_data["cores"] = cores_per_socket * sockets
884
+ elif "Architecture:" in line:
885
+ cpu_data["architecture"] = line.split(":")[1].strip()
886
+
887
+ if "name" in cpu_data:
888
+ cpu_name = cpu_data.get("name", "Unknown")
889
+ cpu_info = {
890
+ "name": cpu_data.get("name", "Unknown"),
891
+ "cores": cpu_data.get("cores", "Unknown"),
892
+ "threads": cpu_data.get("threads", "Unknown"),
893
+ "architecture": cpu_data.get("architecture", "Unknown"),
894
+ "available": True,
895
+ }
896
+
897
+ # Add inference engine detection
898
+ cpu_info["inference_engines"] = self._detect_inference_engines(
899
+ "cpu", cpu_name
900
+ )
901
+ return cpu_info
902
+ except Exception as e: # pylint: disable=broad-except
903
+ return {"available": False, "error": f"CPU detection failed: {e}"}
904
+
905
+ return {"available": False, "error": "No CPU information found"}
906
+
907
+ def _detect_amd_gpus(self, gpu_type: str, include_inference_engines: bool = False):
908
+ """
909
+ Shared AMD GPU detection logic for both integrated and discrete GPUs.
910
+ Uses keyword-based classification for simplicity and reliability.
911
+
912
+ Args:
913
+ gpu_type: Either "integrated" or "discrete".
914
+
915
+ Returns:
916
+ list: List of detected GPU info dictionaries.
917
+ """
918
+ gpu_devices = []
919
+ try:
920
+ lspci_output = subprocess.check_output(
921
+ "lspci | grep -i 'vga\\|3d\\|display'", shell=True
922
+ ).decode()
923
+
924
+ for line in lspci_output.split("\n"):
925
+ if line.strip() and "AMD" in line:
926
+ name_lower = line.lower()
927
+
928
+ # Keyword-based classification - simple and reliable
929
+ is_discrete_by_name = any(
930
+ kw in name_lower for kw in AMD_DISCRETE_GPU_KEYWORDS
931
+ )
932
+ is_integrated = not is_discrete_by_name
933
+
934
+ # Filter based on requested type
935
+ if (gpu_type == "integrated" and is_integrated) or (
936
+ gpu_type == "discrete" and not is_integrated
937
+ ):
938
+
939
+ device_type = "amd_igpu" if is_integrated else "amd_dgpu"
940
+ device_name = line.split(": ")[1] if ": " in line else line
941
+
942
+ gpu_info = {
943
+ "name": device_name,
944
+ "available": True,
945
+ }
946
+
947
+ # Get VRAM information for discrete GPUs
948
+ if not is_integrated: # Only add VRAM for discrete GPUs
949
+ vram_gb = self._get_amd_vram_rocm_smi_linux()
950
+ if vram_gb == 0.0:
951
+ # Fallback to sysfs - extract PCI ID from lspci line
952
+ pci_id = line.split()[0] if line else ""
953
+ vram_gb = self._get_amd_vram_sysfs(pci_id)
954
+
955
+ if vram_gb > 0.0:
956
+ gpu_info["vram_gb"] = vram_gb
957
+ else:
958
+ gpu_info["vram_gb"] = "Unknown"
959
+
960
+ if include_inference_engines:
961
+ gpu_info["inference_engines"] = (
962
+ self._detect_inference_engines(device_type, device_name)
963
+ )
964
+ gpu_devices.append(gpu_info)
965
+
966
+ except Exception as e: # pylint: disable=broad-except
967
+ error_msg = f"AMD {gpu_type} GPU detection failed: {e}"
968
+ return [{"available": False, "error": error_msg}]
969
+
970
+ return gpu_devices
971
+
972
+ def get_amd_igpu_device(self, include_inference_engines: bool = False) -> dict:
973
+ """
974
+ Retrieves AMD integrated GPU device information using keyword-based classification.
975
+
976
+ Returns:
977
+ dict: AMD iGPU device information.
978
+ """
979
+ igpu_devices = self._detect_amd_gpus(
980
+ "integrated", include_inference_engines=include_inference_engines
981
+ )
982
+ return (
983
+ igpu_devices[0]
984
+ if igpu_devices
985
+ else {"available": False, "error": "No AMD integrated GPU found"}
986
+ )
987
+
988
+ def get_amd_dgpu_devices(self, include_inference_engines: bool = False) -> list:
989
+ """
990
+ Retrieves AMD discrete GPU device information using keyword-based classification.
991
+
992
+ Returns:
993
+ list: List of AMD dGPU device information.
994
+ """
995
+ dgpu_devices = self._detect_amd_gpus(
996
+ "discrete", include_inference_engines=include_inference_engines
997
+ )
998
+ return (
999
+ dgpu_devices
1000
+ if dgpu_devices
1001
+ else [{"available": False, "error": "No AMD discrete GPU found"}]
1002
+ )
1003
+
1004
+ def get_nvidia_dgpu_devices(self, include_inference_engines: bool = False) -> list:
1005
+ """
1006
+ Retrieves NVIDIA discrete GPU device information using lspci.
1007
+
1008
+ Returns:
1009
+ list: List of NVIDIA dGPU device information.
1010
+ """
1011
+ gpu_devices = []
1012
+ try:
1013
+ lspci_output = subprocess.check_output(
1014
+ "lspci | grep -i 'vga\\|3d\\|display'", shell=True
1015
+ ).decode()
1016
+
1017
+ for line in lspci_output.split("\n"):
1018
+ if line.strip() and "NVIDIA" in line.upper():
1019
+ name_lower = line.lower()
1020
+
1021
+ # Most NVIDIA GPUs are discrete, check keywords for confirmation
1022
+ is_discrete = (
1023
+ any(kw in name_lower for kw in NVIDIA_DISCRETE_GPU_KEYWORDS)
1024
+ or "nvidia" in name_lower
1025
+ ) # Default to discrete for NVIDIA
1026
+
1027
+ if is_discrete:
1028
+ device_name = line.split(": ")[1] if ": " in line else line
1029
+
1030
+ gpu_info = {
1031
+ "name": device_name,
1032
+ "available": True,
1033
+ }
1034
+
1035
+ # Try to get NVIDIA driver version using multiple methods
1036
+ driver_version = self._get_nvidia_driver_version_linux()
1037
+ gpu_info["driver_version"] = (
1038
+ driver_version if driver_version else "Unknown"
1039
+ )
1040
+
1041
+ # Get VRAM information
1042
+ vram_gb = self._get_nvidia_vram_smi_linux()
1043
+ if vram_gb > 0.0:
1044
+ gpu_info["vram_gb"] = vram_gb
1045
+
1046
+ if include_inference_engines:
1047
+ gpu_info["inference_engines"] = (
1048
+ self._detect_inference_engines(
1049
+ "nvidia_dgpu", device_name
1050
+ )
1051
+ )
1052
+ gpu_devices.append(gpu_info)
1053
+
1054
+ except Exception as e: # pylint: disable=broad-except
1055
+ error_msg = f"NVIDIA discrete GPU detection failed: {e}"
1056
+ return [{"available": False, "error": error_msg}]
1057
+
1058
+ return (
1059
+ gpu_devices
1060
+ if gpu_devices
1061
+ else [{"available": False, "error": "No NVIDIA discrete GPU found"}]
1062
+ )
1063
+
1064
+ def get_npu_device(self) -> dict:
1065
+ """
1066
+ Retrieves NPU device information (limited support on Linux).
1067
+
1068
+ Returns:
1069
+ dict: NPU device information.
1070
+ """
1071
+ return {
1072
+ "available": False,
1073
+ "error": "NPU detection not yet implemented for Linux",
1074
+ }
1075
+
1076
+ def _get_nvidia_driver_version_linux(self) -> str:
1077
+ """
1078
+ Get NVIDIA driver version on Linux using nvidia-smi and proc fallback.
1079
+
1080
+ Returns:
1081
+ str: Driver version, or empty string if detection fails
1082
+ """
1083
+ # Primary: Try nvidia-smi command
1084
+ try:
1085
+ output = (
1086
+ subprocess.check_output(
1087
+ "nvidia-smi --query-gpu=driver_version --format=csv,noheader,nounits",
1088
+ shell=True,
1089
+ stderr=subprocess.DEVNULL,
1090
+ )
1091
+ .decode()
1092
+ .strip()
1093
+ )
1094
+ if output and output != "N/A":
1095
+ return output.split("\n")[0]
1096
+ except (subprocess.CalledProcessError, FileNotFoundError):
1097
+ pass
1098
+
1099
+ # Fallback: Try /proc/driver/nvidia/version
1100
+ try:
1101
+ with open("/proc/driver/nvidia/version", "r", encoding="utf-8") as f:
1102
+ content = f.read()
1103
+ # Look for version pattern like "NVRM version:
1104
+ # NVIDIA UNIX x86_64 Kernel Module 470.82.00"
1105
+ match = re.search(r"Kernel Module\s+(\d+\.\d+(?:\.\d+)?)", content)
1106
+ if match:
1107
+ return match.group(1)
1108
+ except (FileNotFoundError, IOError):
1109
+ pass
1110
+
1111
+ return ""
1112
+
1113
+ @staticmethod
1114
+ def get_processor_name() -> str:
1115
+ """
1116
+ Retrieves the name of the processor.
1117
+
1118
+ Returns:
1119
+ str: Name of the processor.
1120
+ """
1121
+ # Get CPU Information
1122
+ try:
1123
+ cpu_info = subprocess.check_output("lscpu", shell=True).decode()
1124
+ for line in cpu_info.split("\n"):
1125
+ if "Model name:" in line:
1126
+ return line.split(":")[1].strip()
1127
+ except Exception as e: # pylint: disable=broad-except
1128
+ return f"ERROR - {e}"
1129
+
1130
+ @staticmethod
1131
+ def get_system_model() -> str:
1132
+ """
1133
+ Retrieves the model of the computer system.
1134
+
1135
+ Returns:
1136
+ str: Model of the computer system.
1137
+ """
1138
+ # Get OEM System Information
1139
+ try:
1140
+ oem_info = (
1141
+ subprocess.check_output(
1142
+ "sudo -n dmidecode -s system-product-name",
1143
+ shell=True,
1144
+ stderr=subprocess.DEVNULL,
1145
+ )
1146
+ .decode()
1147
+ .strip()
1148
+ .replace("\n", " ")
1149
+ )
1150
+ return oem_info
1151
+ except subprocess.CalledProcessError:
1152
+ # This catches the case where sudo requires a password
1153
+ return "Unable to get oem info - password required"
1154
+ except Exception as e: # pylint: disable=broad-except
1155
+ return f"ERROR - {e}"
1156
+
1157
+ @staticmethod
1158
+ def get_physical_memory() -> str:
1159
+ """
1160
+ Retrieves the physical memory of the computer system.
1161
+
1162
+ Returns:
1163
+ str: Physical memory.
1164
+ """
1165
+ try:
1166
+ mem_info = (
1167
+ subprocess.check_output("free -m", shell=True)
1168
+ .decode()
1169
+ .split("\n")[1]
1170
+ .split()[1]
1171
+ )
1172
+ mem_info_gb = round(int(mem_info) / 1024, 2)
1173
+ return f"{mem_info_gb} GB"
1174
+ except Exception as e: # pylint: disable=broad-except
1175
+ return f"ERROR - {e}"
1176
+
1177
+ def get_dict(self) -> dict:
1178
+ """
1179
+ Retrieves all the system information into a dictionary.
1180
+
1181
+ Returns:
1182
+ dict: System information.
1183
+ """
1184
+ info_dict = super().get_dict()
1185
+ info_dict["Processor"] = self.get_processor_name()
1186
+ info_dict["OEM System"] = self.get_system_model()
1187
+ info_dict["Physical Memory"] = self.get_physical_memory()
1188
+ return info_dict
1189
+
1190
+ def _get_nvidia_vram_smi_linux(self) -> float:
1191
+ """
1192
+ Get NVIDIA GPU VRAM using nvidia-smi command on Linux.
1193
+
1194
+ Returns:
1195
+ float: VRAM in GB, or 0.0 if detection fails
1196
+ """
1197
+ try:
1198
+ output = (
1199
+ subprocess.check_output(
1200
+ [
1201
+ "nvidia-smi",
1202
+ "--query-gpu=memory.total",
1203
+ "--format=csv,noheader,nounits",
1204
+ ],
1205
+ stderr=subprocess.DEVNULL,
1206
+ )
1207
+ .decode()
1208
+ .strip()
1209
+ )
1210
+
1211
+ # nvidia-smi returns memory in MB
1212
+ vram_mb = int(output.split("\n")[0])
1213
+ vram_gb = round(vram_mb / 1024, 1)
1214
+ return vram_gb
1215
+ except (subprocess.CalledProcessError, FileNotFoundError, ValueError):
1216
+ pass
1217
+ return 0.0
1218
+
1219
+ def _get_amd_vram_rocm_smi_linux(self) -> float:
1220
+ """
1221
+ Get AMD GPU VRAM using rocm-smi command on Linux.
1222
+
1223
+ Returns:
1224
+ float: VRAM in GB, or 0.0 if detection fails
1225
+ """
1226
+ try:
1227
+ output = (
1228
+ subprocess.check_output(
1229
+ ["rocm-smi", "--showmeminfo", "vram", "--csv"],
1230
+ stderr=subprocess.DEVNULL,
1231
+ )
1232
+ .decode()
1233
+ .strip()
1234
+ )
1235
+
1236
+ # Parse CSV output to extract VRAM
1237
+ lines = output.split("\n")
1238
+ for line in lines:
1239
+ if "Total VRAM" in line or "vram" in line.lower():
1240
+ # Extract numeric value (assuming it's in MB or GB)
1241
+ numbers = re.findall(r"\d+", line)
1242
+ if numbers:
1243
+ vram_value = int(numbers[0])
1244
+ # Assume MB if value is large, GB if small
1245
+ if vram_value > 100: # Likely MB
1246
+ vram_gb = round(vram_value / 1024, 1)
1247
+ else: # Likely GB
1248
+ vram_gb = float(vram_value)
1249
+ return vram_gb
1250
+ except (subprocess.CalledProcessError, FileNotFoundError, ValueError):
1251
+ pass
1252
+ return 0.0
1253
+
1254
+ def _get_amd_vram_sysfs(self, pci_id: str) -> float:
1255
+ """
1256
+ Get AMD GPU VRAM using sysfs on Linux.
1257
+
1258
+ Args:
1259
+ pci_id: PCI ID of the GPU (e.g., "0000:01:00.0")
1260
+
1261
+ Returns:
1262
+ float: VRAM in GB, or 0.0 if detection fails
1263
+ """
1264
+ try:
1265
+ # Try different sysfs paths for VRAM information
1266
+ sysfs_paths = [
1267
+ f"/sys/bus/pci/devices/{pci_id}/mem_info_vram_total",
1268
+ "/sys/class/drm/card*/device/mem_info_vram_total",
1269
+ ]
1270
+
1271
+ for path in sysfs_paths:
1272
+ try:
1273
+ if "*" in path:
1274
+ # Handle wildcard paths
1275
+ matching_paths = glob.glob(path)
1276
+ for match_path in matching_paths:
1277
+ with open(match_path, "r", encoding="utf-8") as f:
1278
+ vram_bytes = int(f.read().strip())
1279
+ vram_gb = round(vram_bytes / (1024**3), 1)
1280
+ if vram_gb > 0:
1281
+ return vram_gb
1282
+ else:
1283
+ with open(path, "r", encoding="utf-8") as f:
1284
+ vram_bytes = int(f.read().strip())
1285
+ vram_gb = round(vram_bytes / (1024**3), 1)
1286
+ return vram_gb
1287
+ except (FileNotFoundError, ValueError, PermissionError):
1288
+ continue
1289
+ except Exception: # pylint: disable=broad-except
1290
+ pass
1291
+ return 0.0
1292
+
1293
+ def _detect_inference_engines(self, device_type: str, device_name: str) -> dict:
1294
+ """
1295
+ Detect available inference engines for a specific device type.
1296
+
1297
+ Args:
1298
+ device_type: Device type ("cpu", "amd_igpu", "amd_dgpu", "npu")
1299
+
1300
+ Returns:
1301
+ dict: Available inference engines and their information.
1302
+ """
1303
+ try:
1304
+ return detect_inference_engines(device_type, device_name)
1305
+ except Exception as e: # pylint: disable=broad-except
1306
+ return {"error": f"Inference engine detection failed: {str(e)}"}
1307
+
1308
+
1309
+ class UnsupportedOSSystemInfo(SystemInfo):
1310
+ """
1311
+ Class used to access system information in unsupported operating systems.
1312
+ """
1313
+
1314
+ def get_cpu_device(self) -> dict:
1315
+ """
1316
+ Retrieves CPU device information for unsupported OS.
1317
+ """
1318
+ return {
1319
+ "available": False,
1320
+ "error": "Device detection not supported on this operating system",
1321
+ }
1322
+
1323
+ def get_amd_igpu_device(self, include_inference_engines: bool = False) -> dict:
1324
+ """
1325
+ Retrieves AMD integrated GPU device information for unsupported OS.
1326
+ """
1327
+ return {
1328
+ "available": False,
1329
+ "error": "Device detection not supported on this operating system",
1330
+ }
1331
+
1332
+ def get_amd_dgpu_devices(self, include_inference_engines: bool = False) -> list:
1333
+ """
1334
+ Retrieves AMD discrete GPU device information for unsupported OS.
1335
+ """
1336
+ return []
1337
+
1338
+ def get_nvidia_dgpu_devices(self, include_inference_engines: bool = False) -> list:
1339
+ """
1340
+ Retrieves NVIDIA discrete GPU device information for unsupported OS.
1341
+ """
1342
+ return [
1343
+ {
1344
+ "available": False,
1345
+ "error": "Device detection not supported on this operating system",
1346
+ }
1347
+ ]
1348
+
1349
+ def get_npu_device(self) -> dict:
1350
+ """
1351
+ Retrieves NPU device information for unsupported OS.
1352
+ """
1353
+ return {
1354
+ "available": False,
1355
+ "error": "Device detection not supported on this operating system",
1356
+ }
1357
+
1358
+ def get_dict(self):
1359
+ """
1360
+ Retrieves all the system information into a dictionary.
1361
+
1362
+ Returns:
1363
+ dict: System information.
1364
+ """
1365
+ info_dict = super().get_dict()
1366
+ info_dict["Error"] = "UNSUPPORTED OS"
1367
+ return info_dict
1368
+
1369
+
1370
+ def get_system_info() -> SystemInfo:
1371
+ """
1372
+ Creates the appropriate SystemInfo object based on the operating system.
1373
+
1374
+ Returns:
1375
+ A subclass of SystemInfo for the current operating system.
1376
+ """
1377
+ os_type = platform.system()
1378
+ if os_type == "Windows":
1379
+ return WindowsSystemInfo()
1380
+ elif os_type == "Linux":
1381
+ # WSL has to be handled differently compared to native Linux.
1382
+ if "microsoft" in str(platform.release()):
1383
+ return WSLSystemInfo()
1384
+ else:
1385
+ return LinuxSystemInfo()
1386
+ else:
1387
+ return UnsupportedOSSystemInfo()
1388
+
1389
+
1390
+ def get_system_info_dict() -> dict:
1391
+ """
1392
+ Puts the system information into a dictionary.
1393
+
1394
+ Returns:
1395
+ dict: Dictionary containing the system information.
1396
+ """
1397
+ return get_system_info().get_dict()
1398
+
1399
+
1400
+ def get_device_info_dict() -> dict:
1401
+ """
1402
+ Puts the device information into a dictionary.
1403
+
1404
+ Returns:
1405
+ dict: Dictionary containing the device information.
1406
+ """
1407
+ return get_system_info().get_device_dict()
1408
+
1409
+
1410
+ # This file was originally licensed under Apache 2.0. It has been modified.
1411
+ # Modifications Copyright (c) 2025 AMD