Skip to content

Commit d52e5f3

Browse files
committed
Fix nvml library and nvidia-smi parsing
1 parent 63c77e2 commit d52e5f3

2 files changed

Lines changed: 81 additions & 20 deletions

File tree

backend/gpu_detector.py

Lines changed: 80 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@
88
"""
99

1010
try:
11-
import nvidia_ml_py3 as pynvml # Preferred package
12-
except ImportError:
13-
import pynvml # Fallback for environments that still ship pynvml
11+
import pynvml # Provided by the nvidia-ml-py package
12+
except ImportError as exc:
13+
raise ImportError(
14+
"pynvml module not available. Install the 'nvidia-ml-py' package to enable GPU detection."
15+
) from exc
16+
1417
import subprocess
1518
import json
1619
import os
@@ -71,23 +74,49 @@ def _check_metal() -> bool:
7174
# NVIDIA GPU Detection
7275
# ============================================================================
7376

77+
def _query_nvml_cuda_version(initialized: bool = False) -> Optional[str]:
78+
"""
79+
Retrieve CUDA driver version via NVML. If NVML is not initialized, this
80+
function will initialize and shut it down automatically.
81+
"""
82+
try:
83+
if not initialized:
84+
pynvml.nvmlInit()
85+
version = pynvml.nvmlSystemGetCudaDriverVersion()
86+
major = version // 1000
87+
minor = (version % 1000) // 10
88+
return f"{major}.{minor}"
89+
except Exception:
90+
return None
91+
finally:
92+
if not initialized:
93+
try:
94+
pynvml.nvmlShutdown()
95+
except Exception:
96+
pass
97+
98+
7499
async def detect_nvidia_gpu() -> Optional[Dict]:
75100
"""Detect NVIDIA GPUs using pynvml and nvidia-smi"""
101+
cuda_version_hint: Optional[str] = None
102+
76103
try:
77104
pynvml.nvmlInit()
78-
105+
cuda_version_hint = _query_nvml_cuda_version(initialized=True)
106+
79107
device_count = pynvml.nvmlDeviceGetCount()
80108
if device_count == 0:
81109
logger.debug("NVML reported zero NVIDIA devices; falling back to nvidia-smi detection")
82-
raise RuntimeError("NVML reported zero devices")
110+
return await _detect_nvidia_via_smi(cuda_version_hint)
83111

84112
gpus = []
85113

86114
for i in range(device_count):
87115
handle = pynvml.nvmlDeviceGetHandleByIndex(i)
88116

89117
# Get basic info
90-
name = pynvml.nvmlDeviceGetName(handle).decode('utf-8')
118+
raw_name = pynvml.nvmlDeviceGetName(handle)
119+
name = raw_name.decode('utf-8') if isinstance(raw_name, bytes) else str(raw_name)
91120
memory_info = pynvml.nvmlDeviceGetMemoryInfo(handle)
92121

93122
# Get compute capability
@@ -146,30 +175,45 @@ async def detect_nvidia_gpu() -> Optional[Dict]:
146175
except Exception as exc:
147176
logger.debug(f"Failed to detect NVIDIA GPUs via NVML: {exc}")
148177
# Fallback to nvidia-smi
149-
return await _detect_nvidia_via_smi()
178+
return await _detect_nvidia_via_smi(cuda_version_hint)
179+
finally:
180+
try:
181+
pynvml.nvmlShutdown()
182+
except Exception:
183+
pass
150184

151185

152-
async def _detect_nvidia_via_smi() -> Optional[Dict]:
186+
async def _detect_nvidia_via_smi(cuda_version_hint: Optional[str] = None) -> Optional[Dict]:
153187
"""Fallback NVIDIA detection using nvidia-smi"""
154188
try:
189+
query_fields = [
190+
"index",
191+
"name",
192+
"memory.total",
193+
"memory.free",
194+
"memory.used",
195+
"compute_cap",
196+
"driver_version",
197+
]
198+
155199
result = subprocess.run(
156200
[
157201
"nvidia-smi",
158-
"--query-gpu=index,name,memory.total,memory.free,memory.used,compute_cap,driver_version,cuda_version",
202+
f"--query-gpu={','.join(query_fields)}",
159203
"--format=csv,noheader,nounits",
160204
],
161205
capture_output=True,
162206
text=True,
163-
check=True
207+
check=True,
164208
)
165-
209+
166210
gpus = []
167211
lines = result.stdout.strip().split('\n')
168-
169-
reported_cuda_version = None
212+
213+
reported_cuda_version = cuda_version_hint
170214
for line in lines:
171215
parts = [p.strip() for p in line.split(',')]
172-
if len(parts) >= 8:
216+
if len(parts) >= len(query_fields):
173217
# nvidia-smi reports memory in MiB when using nounits
174218
try:
175219
total_bytes = int(parts[2]) * 1024 * 1024
@@ -180,8 +224,11 @@ async def _detect_nvidia_via_smi() -> Optional[Dict]:
180224

181225
compute_capability = parts[5]
182226
driver_version = parts[6]
183-
cuda_version = parts[7]
184-
reported_cuda_version = cuda_version or reported_cuda_version
227+
cuda_version = None
228+
if len(parts) > 7:
229+
cuda_version = parts[7].strip() or cuda_version
230+
if cuda_version and not reported_cuda_version:
231+
reported_cuda_version = cuda_version
185232

186233
gpu_info = {
187234
"index": int(parts[0]),
@@ -196,7 +243,22 @@ async def _detect_nvidia_via_smi() -> Optional[Dict]:
196243
"cuda_version": cuda_version,
197244
}
198245
gpus.append(gpu_info)
199-
246+
247+
if reported_cuda_version in (None, "", "Unknown"):
248+
try:
249+
detail_output = subprocess.run(
250+
["nvidia-smi", "-q"],
251+
capture_output=True,
252+
text=True,
253+
check=True,
254+
).stdout
255+
for line in detail_output.splitlines():
256+
if "CUDA Version" in line:
257+
reported_cuda_version = line.split(":", 1)[1].strip() or reported_cuda_version
258+
break
259+
except subprocess.CalledProcessError:
260+
pass
261+
200262
return {
201263
"vendor": "nvidia",
202264
"cuda_version": reported_cuda_version or "Unknown",
@@ -205,7 +267,7 @@ async def _detect_nvidia_via_smi() -> Optional[Dict]:
205267
"total_vram": sum(gpu["memory"]["total"] for gpu in gpus),
206268
"available_vram": sum(gpu["memory"]["free"] for gpu in gpus)
207269
}
208-
270+
209271
except (subprocess.CalledProcessError, FileNotFoundError):
210272
return None
211273

requirements.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ python-multipart==0.0.20
1010
websockets==15.0.1
1111
psutil==7.1.2
1212
pyyaml==6.0.3
13-
nvidia-ml-py3
14-
pynvml==13.0.1
13+
nvidia-ml-py
1514
aiohttp==3.13.2
1615
httpx==0.28.1
1716
httpx-sse==0.4.3

0 commit comments

Comments
 (0)