Skip to content

Commit 7620712

Browse files
committed
[NVIDIA] Add opt-in NVML-based CUDA availability check
CudaDriver.is_active() calls cuInit(), which initializes CUDA driver state in a parent process and can prevent a subsequently forked child from initializing CUDA. Add an opt-in NVML probe so callers that need to fork can check NVIDIA device availability without initializing CUDA when NVML returns a conclusive result. Keep the existing CUDA probe as the default and as the fallback for inconclusive NVML checks.
1 parent 223ea64 commit 7620712

4 files changed

Lines changed: 129 additions & 0 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,8 @@ See [`python/triton/knobs.py`](python/triton/knobs.py) for the full list of conf
246246
- `LLVM_EXTRACT_DI_LOCAL_VARIABLES` emit full debug info, allowing for eval of values in gpu debuggers (ie cuda-gdb, rocm-gdb etc)
247247
- `TRITON_DEFAULT_BACKEND=<backend>` optionally sets the default backend used by Triton when
248248
constructing the active driver (i.e., `triton.runtime.driver.active`).
249+
- `TRITON_NVML_BASED_CUDA_CHECK=1` makes NVIDIA driver availability checks use NVML when possible
250+
to avoid initializing CUDA before a process forks.
249251

250252
> [!NOTE]
251253
> Some of these environment variables don't have a knob in `knobs.py`-- those are only relevant to the C++ layer(s), hence they don't exist in the python layer.

python/test/unit/runtime/test_driver.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import triton
77
import triton.language as tl
8+
import triton.backends.nvidia.driver as cuda_driver
89
from triton.backends.driver import GPUDriver, expand_signature, wrap_handle_tensordesc_impl
910

1011

@@ -19,6 +20,18 @@ def test_is_lazy():
1920
utils = triton.runtime.driver.active.utils # noqa: F841
2021

2122

23+
def test_cuda_driver_is_active_with_nvml(monkeypatch):
24+
cuda_calls = []
25+
monkeypatch.setattr(cuda_driver, "_device_count_nvml", lambda: 1)
26+
monkeypatch.setattr(cuda_driver, "_cuda_driver_is_active", lambda: cuda_calls.append(None) or False)
27+
28+
with triton.knobs.nvidia.scope():
29+
triton.knobs.nvidia.use_nvml_cuda_check = True
30+
assert cuda_driver.CudaDriver.is_active() is True
31+
32+
assert cuda_calls == []
33+
34+
2235
def test_profile_scratch_stream_zero_uses_default_stream(monkeypatch):
2336

2437
class Scratch:

python/triton/knobs.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,7 @@ class nvidia_knobs(base_knobs):
511511

512512
libdevice_path: env_opt_str = env_opt_str("TRITON_LIBDEVICE_PATH")
513513
libcuda_path: env_opt_str = env_opt_str("TRITON_LIBCUDA_PATH")
514+
use_nvml_cuda_check: env_bool = env_bool("TRITON_NVML_BASED_CUDA_CHECK")
514515

515516

516517
class amd_knobs(base_knobs):

third_party/nvidia/backend/driver.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,115 @@ def library_dirs():
5151
return [libdevice_dir, *libcuda_dirs()]
5252

5353

54+
# Adapted from PyTorch's NVML-based CUDA availability check:
55+
# https://github.com/pytorch/pytorch/blob/70d99e998b4955e0049d13a98d77ae1b14db1f45/torch/cuda/__init__.py
56+
def _parse_visible_devices():
57+
var = os.getenv("CUDA_VISIBLE_DEVICES")
58+
if var is None:
59+
return list(range(64))
60+
61+
def _strtoul(s):
62+
if not s:
63+
return -1
64+
for idx, c in enumerate(s):
65+
if not (c.isdigit() or (idx == 0 and c in "+-")):
66+
break
67+
if idx + 1 == len(s):
68+
idx += 1
69+
return int(s[:idx]) if idx > 0 else -1
70+
71+
def parse_list_with_prefix(prefix):
72+
rc = []
73+
for elem in var.split(","):
74+
if elem in rc:
75+
return []
76+
if not elem.startswith(prefix):
77+
break
78+
rc.append(elem)
79+
return rc
80+
81+
if var.startswith("GPU-"):
82+
return parse_list_with_prefix("GPU-")
83+
if var.startswith("MIG-"):
84+
return parse_list_with_prefix("MIG-")
85+
86+
rc = []
87+
for elem in var.split(","):
88+
x = _strtoul(elem.strip())
89+
if x in rc:
90+
return []
91+
if x < 0:
92+
break
93+
rc.append(x)
94+
return rc
95+
96+
97+
def _raw_device_count_nvml():
98+
nvml_h = ctypes.CDLL("libnvidia-ml.so.1")
99+
if nvml_h.nvmlInit() != 0:
100+
return -1
101+
count = ctypes.c_int(-1)
102+
if nvml_h.nvmlDeviceGetCount_v2(ctypes.byref(count)) != 0:
103+
return -1
104+
return count.value
105+
106+
107+
def _raw_device_uuid_nvml():
108+
nvml_h = ctypes.CDLL("libnvidia-ml.so.1")
109+
if nvml_h.nvmlInit() != 0:
110+
return None
111+
count = ctypes.c_int(-1)
112+
if nvml_h.nvmlDeviceGetCount_v2(ctypes.byref(count)) != 0:
113+
return None
114+
115+
uuids = []
116+
for idx in range(count.value):
117+
device = ctypes.c_void_p()
118+
if nvml_h.nvmlDeviceGetHandleByIndex_v2(idx, ctypes.byref(device)) != 0:
119+
return None
120+
buf = ctypes.create_string_buffer(96)
121+
if nvml_h.nvmlDeviceGetUUID(device, buf, len(buf)) != 0:
122+
return None
123+
uuids.append(buf.raw.decode("ascii").strip("\0"))
124+
return uuids
125+
126+
127+
def _transform_uuid_to_ordinals(candidates, uuids):
128+
ordinals = []
129+
for candidate in candidates:
130+
matches = [idx for idx, uuid in enumerate(uuids) if uuid.startswith(candidate)]
131+
if len(matches) != 1:
132+
break
133+
if matches[0] in ordinals:
134+
return []
135+
ordinals.append(matches[0])
136+
return ordinals
137+
138+
139+
def _device_count_nvml():
140+
visible_devices = _parse_visible_devices()
141+
if not visible_devices:
142+
return 0
143+
try:
144+
if isinstance(visible_devices[0], str):
145+
if visible_devices[0].startswith("MIG-"):
146+
return -1
147+
uuids = _raw_device_uuid_nvml()
148+
if uuids is None:
149+
return -1
150+
visible_devices = _transform_uuid_to_ordinals(visible_devices, uuids)
151+
else:
152+
raw_count = _raw_device_count_nvml()
153+
if raw_count <= 0:
154+
return raw_count
155+
for idx, value in enumerate(visible_devices):
156+
if value >= raw_count:
157+
return idx
158+
except (OSError, AttributeError):
159+
return -1
160+
return len(visible_devices)
161+
162+
54163
def _cuda_driver_is_active():
55164
candidates = ["libcuda.so.1"]
56165
try:
@@ -381,6 +490,10 @@ def get_device_interface(self):
381490

382491
@staticmethod
383492
def is_active():
493+
if knobs.nvidia.use_nvml_cuda_check:
494+
count = _device_count_nvml()
495+
if count >= 0:
496+
return count > 0
384497
return _cuda_driver_is_active()
385498

386499
def map_python_to_cpp_type(self, ty: str) -> str:

0 commit comments

Comments
 (0)