lemonade-sdk 8.1.9__py3-none-any.whl → 8.1.11__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.
Potentially problematic release.
This version of lemonade-sdk might be problematic. Click here for more details.
- lemonade/common/inference_engines.py +13 -4
- lemonade/common/system_info.py +570 -1
- lemonade/tools/flm/__init__.py +1 -0
- lemonade/tools/flm/utils.py +255 -0
- lemonade/tools/llamacpp/utils.py +62 -13
- lemonade/tools/server/flm.py +137 -0
- lemonade/tools/server/llamacpp.py +23 -5
- lemonade/tools/server/serve.py +292 -135
- lemonade/tools/server/static/js/chat.js +165 -82
- lemonade/tools/server/static/js/models.js +87 -54
- lemonade/tools/server/static/js/shared.js +5 -3
- lemonade/tools/server/static/logs.html +47 -0
- lemonade/tools/server/static/styles.css +159 -8
- lemonade/tools/server/static/webapp.html +28 -10
- lemonade/tools/server/tray.py +158 -38
- lemonade/tools/server/utils/macos_tray.py +226 -0
- lemonade/tools/server/utils/{system_tray.py → windows_tray.py} +13 -0
- lemonade/tools/server/webapp.py +4 -1
- lemonade/tools/server/wrapped_server.py +91 -25
- lemonade/version.py +1 -1
- lemonade_install/install.py +25 -2
- {lemonade_sdk-8.1.9.dist-info → lemonade_sdk-8.1.11.dist-info}/METADATA +9 -6
- {lemonade_sdk-8.1.9.dist-info → lemonade_sdk-8.1.11.dist-info}/RECORD +33 -28
- lemonade_server/cli.py +105 -14
- lemonade_server/model_manager.py +186 -45
- lemonade_server/pydantic_models.py +25 -1
- lemonade_server/server_models.json +162 -62
- lemonade_server/settings.py +39 -39
- {lemonade_sdk-8.1.9.dist-info → lemonade_sdk-8.1.11.dist-info}/WHEEL +0 -0
- {lemonade_sdk-8.1.9.dist-info → lemonade_sdk-8.1.11.dist-info}/entry_points.txt +0 -0
- {lemonade_sdk-8.1.9.dist-info → lemonade_sdk-8.1.11.dist-info}/licenses/LICENSE +0 -0
- {lemonade_sdk-8.1.9.dist-info → lemonade_sdk-8.1.11.dist-info}/licenses/NOTICE.md +0 -0
- {lemonade_sdk-8.1.9.dist-info → lemonade_sdk-8.1.11.dist-info}/top_level.txt +0 -0
lemonade/common/system_info.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
import importlib.metadata
|
|
3
|
+
import logging
|
|
3
4
|
import platform
|
|
4
5
|
import re
|
|
5
6
|
import subprocess
|
|
6
7
|
import ctypes
|
|
8
|
+
import glob
|
|
7
9
|
from .inference_engines import detect_inference_engines
|
|
8
10
|
|
|
9
11
|
# AMD GPU classification keywords - shared across all OS implementations
|
|
@@ -19,6 +21,28 @@ AMD_DISCRETE_GPU_KEYWORDS = [
|
|
|
19
21
|
"fury",
|
|
20
22
|
]
|
|
21
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
|
+
|
|
22
46
|
|
|
23
47
|
class SystemInfo(ABC):
|
|
24
48
|
"""
|
|
@@ -51,6 +75,7 @@ class SystemInfo(ABC):
|
|
|
51
75
|
"cpu": self.get_cpu_device(),
|
|
52
76
|
"amd_igpu": self.get_amd_igpu_device(include_inference_engines=True),
|
|
53
77
|
"amd_dgpu": self.get_amd_dgpu_devices(include_inference_engines=True),
|
|
78
|
+
"nvidia_dgpu": self.get_nvidia_dgpu_devices(include_inference_engines=True),
|
|
54
79
|
"npu": self.get_npu_device(),
|
|
55
80
|
}
|
|
56
81
|
return device_dict
|
|
@@ -82,6 +107,15 @@ class SystemInfo(ABC):
|
|
|
82
107
|
list: List of AMD dGPU device information.
|
|
83
108
|
"""
|
|
84
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
|
+
|
|
85
119
|
@abstractmethod
|
|
86
120
|
def get_npu_device(self) -> dict:
|
|
87
121
|
"""
|
|
@@ -173,34 +207,56 @@ class WindowsSystemInfo(SystemInfo):
|
|
|
173
207
|
Returns:
|
|
174
208
|
list: List of detected GPU info dictionaries
|
|
175
209
|
"""
|
|
210
|
+
logging.debug(f"Starting AMD GPU detection for type: {gpu_type}")
|
|
176
211
|
gpu_devices = []
|
|
177
212
|
try:
|
|
178
213
|
video_controllers = self.connection.Win32_VideoController()
|
|
179
|
-
|
|
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
|
+
|
|
180
222
|
if (
|
|
181
223
|
controller.Name
|
|
182
224
|
and "AMD" in controller.Name
|
|
183
225
|
and "Radeon" in controller.Name
|
|
184
226
|
):
|
|
227
|
+
logging.debug(f"Found AMD Radeon GPU: {controller.Name}")
|
|
185
228
|
|
|
186
229
|
name_lower = controller.Name.lower()
|
|
230
|
+
logging.debug(f"GPU name (lowercase): {name_lower}")
|
|
187
231
|
|
|
188
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
|
+
]
|
|
189
236
|
is_discrete_by_name = any(
|
|
190
237
|
kw in name_lower for kw in AMD_DISCRETE_GPU_KEYWORDS
|
|
191
238
|
)
|
|
192
239
|
is_integrated = not is_discrete_by_name
|
|
193
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
|
+
|
|
194
246
|
# Filter based on requested type
|
|
195
247
|
if (gpu_type == "integrated" and is_integrated) or (
|
|
196
248
|
gpu_type == "discrete" and not is_integrated
|
|
197
249
|
):
|
|
250
|
+
logging.debug(
|
|
251
|
+
f"GPU matches requested type '{gpu_type}', processing..."
|
|
252
|
+
)
|
|
198
253
|
|
|
199
254
|
device_type = "amd_igpu" if is_integrated else "amd_dgpu"
|
|
200
255
|
gpu_info = {
|
|
201
256
|
"name": controller.Name,
|
|
202
257
|
"available": True,
|
|
203
258
|
}
|
|
259
|
+
logging.debug(f"Created GPU info for {device_type}: {gpu_info}")
|
|
204
260
|
|
|
205
261
|
driver_version = self.get_driver_version(
|
|
206
262
|
"AMD-OpenCL User Mode Driver"
|
|
@@ -208,6 +264,21 @@ class WindowsSystemInfo(SystemInfo):
|
|
|
208
264
|
gpu_info["driver_version"] = (
|
|
209
265
|
driver_version if driver_version else "Unknown"
|
|
210
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"
|
|
211
282
|
|
|
212
283
|
if include_inference_engines:
|
|
213
284
|
gpu_info["inference_engines"] = (
|
|
@@ -216,11 +287,26 @@ class WindowsSystemInfo(SystemInfo):
|
|
|
216
287
|
)
|
|
217
288
|
)
|
|
218
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
|
+
)
|
|
219
300
|
|
|
220
301
|
except Exception as e: # pylint: disable=broad-except
|
|
221
302
|
error_msg = f"AMD {gpu_type} GPU detection failed: {e}"
|
|
303
|
+
logging.debug(f"Exception in AMD GPU detection: {e}")
|
|
222
304
|
return [{"available": False, "error": error_msg}]
|
|
223
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
|
+
)
|
|
224
310
|
return gpu_devices
|
|
225
311
|
|
|
226
312
|
def get_amd_igpu_device(self, include_inference_engines: bool = False) -> dict:
|
|
@@ -255,6 +341,67 @@ class WindowsSystemInfo(SystemInfo):
|
|
|
255
341
|
else [{"available": False, "error": "No AMD discrete GPU found"}]
|
|
256
342
|
)
|
|
257
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
|
+
|
|
258
405
|
def get_npu_device(self) -> dict:
|
|
259
406
|
"""
|
|
260
407
|
Retrieves NPU device information using existing methods.
|
|
@@ -374,6 +521,169 @@ class WindowsSystemInfo(SystemInfo):
|
|
|
374
521
|
return drivers[0].DriverVersion
|
|
375
522
|
return ""
|
|
376
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
|
+
|
|
377
687
|
@staticmethod
|
|
378
688
|
def get_npu_power_mode() -> str:
|
|
379
689
|
"""
|
|
@@ -490,6 +800,14 @@ class WSLSystemInfo(SystemInfo):
|
|
|
490
800
|
"""
|
|
491
801
|
return []
|
|
492
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
|
+
|
|
493
811
|
def get_npu_device(self) -> dict:
|
|
494
812
|
"""
|
|
495
813
|
Retrieves NPU device information in WSL environment.
|
|
@@ -625,6 +943,20 @@ class LinuxSystemInfo(SystemInfo):
|
|
|
625
943
|
"name": device_name,
|
|
626
944
|
"available": True,
|
|
627
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
|
+
|
|
628
960
|
if include_inference_engines:
|
|
629
961
|
gpu_info["inference_engines"] = (
|
|
630
962
|
self._detect_inference_engines(device_type, device_name)
|
|
@@ -669,6 +1001,66 @@ class LinuxSystemInfo(SystemInfo):
|
|
|
669
1001
|
else [{"available": False, "error": "No AMD discrete GPU found"}]
|
|
670
1002
|
)
|
|
671
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
|
+
|
|
672
1064
|
def get_npu_device(self) -> dict:
|
|
673
1065
|
"""
|
|
674
1066
|
Retrieves NPU device information (limited support on Linux).
|
|
@@ -681,6 +1073,69 @@ class LinuxSystemInfo(SystemInfo):
|
|
|
681
1073
|
"error": "NPU detection not yet implemented for Linux",
|
|
682
1074
|
}
|
|
683
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
|
+
def _get_nvidia_vram_smi_linux(self) -> float:
|
|
1114
|
+
"""
|
|
1115
|
+
Get NVIDIA GPU VRAM on Linux using nvidia-smi command.
|
|
1116
|
+
|
|
1117
|
+
Returns:
|
|
1118
|
+
float: VRAM in GB, or 0.0 if detection fails
|
|
1119
|
+
"""
|
|
1120
|
+
try:
|
|
1121
|
+
output = (
|
|
1122
|
+
subprocess.check_output(
|
|
1123
|
+
"nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits",
|
|
1124
|
+
shell=True,
|
|
1125
|
+
stderr=subprocess.DEVNULL,
|
|
1126
|
+
)
|
|
1127
|
+
.decode()
|
|
1128
|
+
.strip()
|
|
1129
|
+
)
|
|
1130
|
+
|
|
1131
|
+
# nvidia-smi returns memory in MB
|
|
1132
|
+
vram_mb = int(output.split("\n")[0])
|
|
1133
|
+
vram_gb = round(vram_mb / 1024, 1)
|
|
1134
|
+
return vram_gb
|
|
1135
|
+
except (subprocess.CalledProcessError, FileNotFoundError, ValueError):
|
|
1136
|
+
pass
|
|
1137
|
+
return 0.0
|
|
1138
|
+
|
|
684
1139
|
@staticmethod
|
|
685
1140
|
def get_processor_name() -> str:
|
|
686
1141
|
"""
|
|
@@ -758,6 +1213,109 @@ class LinuxSystemInfo(SystemInfo):
|
|
|
758
1213
|
info_dict["Physical Memory"] = self.get_physical_memory()
|
|
759
1214
|
return info_dict
|
|
760
1215
|
|
|
1216
|
+
def _get_nvidia_vram_smi_linux(self) -> float:
|
|
1217
|
+
"""
|
|
1218
|
+
Get NVIDIA GPU VRAM using nvidia-smi command on Linux.
|
|
1219
|
+
|
|
1220
|
+
Returns:
|
|
1221
|
+
float: VRAM in GB, or 0.0 if detection fails
|
|
1222
|
+
"""
|
|
1223
|
+
try:
|
|
1224
|
+
output = (
|
|
1225
|
+
subprocess.check_output(
|
|
1226
|
+
[
|
|
1227
|
+
"nvidia-smi",
|
|
1228
|
+
"--query-gpu=memory.total",
|
|
1229
|
+
"--format=csv,noheader,nounits",
|
|
1230
|
+
],
|
|
1231
|
+
stderr=subprocess.DEVNULL,
|
|
1232
|
+
)
|
|
1233
|
+
.decode()
|
|
1234
|
+
.strip()
|
|
1235
|
+
)
|
|
1236
|
+
|
|
1237
|
+
# nvidia-smi returns memory in MB
|
|
1238
|
+
vram_mb = int(output.split("\n")[0])
|
|
1239
|
+
vram_gb = round(vram_mb / 1024, 1)
|
|
1240
|
+
return vram_gb
|
|
1241
|
+
except (subprocess.CalledProcessError, FileNotFoundError, ValueError):
|
|
1242
|
+
pass
|
|
1243
|
+
return 0.0
|
|
1244
|
+
|
|
1245
|
+
def _get_amd_vram_rocm_smi_linux(self) -> float:
|
|
1246
|
+
"""
|
|
1247
|
+
Get AMD GPU VRAM using rocm-smi command on Linux.
|
|
1248
|
+
|
|
1249
|
+
Returns:
|
|
1250
|
+
float: VRAM in GB, or 0.0 if detection fails
|
|
1251
|
+
"""
|
|
1252
|
+
try:
|
|
1253
|
+
output = (
|
|
1254
|
+
subprocess.check_output(
|
|
1255
|
+
["rocm-smi", "--showmeminfo", "vram", "--csv"],
|
|
1256
|
+
stderr=subprocess.DEVNULL,
|
|
1257
|
+
)
|
|
1258
|
+
.decode()
|
|
1259
|
+
.strip()
|
|
1260
|
+
)
|
|
1261
|
+
|
|
1262
|
+
# Parse CSV output to extract VRAM
|
|
1263
|
+
lines = output.split("\n")
|
|
1264
|
+
for line in lines:
|
|
1265
|
+
if "Total VRAM" in line or "vram" in line.lower():
|
|
1266
|
+
# Extract numeric value (assuming it's in MB or GB)
|
|
1267
|
+
numbers = re.findall(r"\d+", line)
|
|
1268
|
+
if numbers:
|
|
1269
|
+
vram_value = int(numbers[0])
|
|
1270
|
+
# Assume MB if value is large, GB if small
|
|
1271
|
+
if vram_value > 100: # Likely MB
|
|
1272
|
+
vram_gb = round(vram_value / 1024, 1)
|
|
1273
|
+
else: # Likely GB
|
|
1274
|
+
vram_gb = float(vram_value)
|
|
1275
|
+
return vram_gb
|
|
1276
|
+
except (subprocess.CalledProcessError, FileNotFoundError, ValueError):
|
|
1277
|
+
pass
|
|
1278
|
+
return 0.0
|
|
1279
|
+
|
|
1280
|
+
def _get_amd_vram_sysfs(self, pci_id: str) -> float:
|
|
1281
|
+
"""
|
|
1282
|
+
Get AMD GPU VRAM using sysfs on Linux.
|
|
1283
|
+
|
|
1284
|
+
Args:
|
|
1285
|
+
pci_id: PCI ID of the GPU (e.g., "0000:01:00.0")
|
|
1286
|
+
|
|
1287
|
+
Returns:
|
|
1288
|
+
float: VRAM in GB, or 0.0 if detection fails
|
|
1289
|
+
"""
|
|
1290
|
+
try:
|
|
1291
|
+
# Try different sysfs paths for VRAM information
|
|
1292
|
+
sysfs_paths = [
|
|
1293
|
+
f"/sys/bus/pci/devices/{pci_id}/mem_info_vram_total",
|
|
1294
|
+
"/sys/class/drm/card*/device/mem_info_vram_total",
|
|
1295
|
+
]
|
|
1296
|
+
|
|
1297
|
+
for path in sysfs_paths:
|
|
1298
|
+
try:
|
|
1299
|
+
if "*" in path:
|
|
1300
|
+
# Handle wildcard paths
|
|
1301
|
+
matching_paths = glob.glob(path)
|
|
1302
|
+
for match_path in matching_paths:
|
|
1303
|
+
with open(match_path, "r", encoding="utf-8") as f:
|
|
1304
|
+
vram_bytes = int(f.read().strip())
|
|
1305
|
+
vram_gb = round(vram_bytes / (1024**3), 1)
|
|
1306
|
+
if vram_gb > 0:
|
|
1307
|
+
return vram_gb
|
|
1308
|
+
else:
|
|
1309
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
1310
|
+
vram_bytes = int(f.read().strip())
|
|
1311
|
+
vram_gb = round(vram_bytes / (1024**3), 1)
|
|
1312
|
+
return vram_gb
|
|
1313
|
+
except (FileNotFoundError, ValueError, PermissionError):
|
|
1314
|
+
continue
|
|
1315
|
+
except Exception: # pylint: disable=broad-except
|
|
1316
|
+
pass
|
|
1317
|
+
return 0.0
|
|
1318
|
+
|
|
761
1319
|
def _detect_inference_engines(self, device_type: str, device_name: str) -> dict:
|
|
762
1320
|
"""
|
|
763
1321
|
Detect available inference engines for a specific device type.
|
|
@@ -803,6 +1361,17 @@ class UnsupportedOSSystemInfo(SystemInfo):
|
|
|
803
1361
|
"""
|
|
804
1362
|
return []
|
|
805
1363
|
|
|
1364
|
+
def get_nvidia_dgpu_devices(self, include_inference_engines: bool = False) -> list:
|
|
1365
|
+
"""
|
|
1366
|
+
Retrieves NVIDIA discrete GPU device information for unsupported OS.
|
|
1367
|
+
"""
|
|
1368
|
+
return [
|
|
1369
|
+
{
|
|
1370
|
+
"available": False,
|
|
1371
|
+
"error": "Device detection not supported on this operating system",
|
|
1372
|
+
}
|
|
1373
|
+
]
|
|
1374
|
+
|
|
806
1375
|
def get_npu_device(self) -> dict:
|
|
807
1376
|
"""
|
|
808
1377
|
Retrieves NPU device information for unsupported OS.
|