From 23aefa3e140edc13c569e1bcfe0a6735461bbfd8 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Fri, 16 Jan 2026 12:42:36 -0500 Subject: [PATCH 1/5] cuda.core.system: Add conveniences to convert device types --- cuda_core/cuda/core/_device.pyx | 11 +++++++ cuda_core/cuda/core/system/_device.pyx | 30 ++++++++++++++++++++ cuda_core/tests/system/test_system_device.py | 17 +++++++++++ cuda_core/tests/test_device.py | 16 +++++++++++ 4 files changed, 74 insertions(+) diff --git a/cuda_core/cuda/core/_device.pyx b/cuda_core/cuda/core/_device.pyx index dc7b0b69c9..4b6a99b32d 100644 --- a/cuda_core/cuda/core/_device.pyx +++ b/cuda_core/cuda/core/_device.pyx @@ -1034,6 +1034,17 @@ class Device: total = system.get_num_devices() return tuple(cls(device_id) for device_id in range(total)) + def to_system_device(self) -> 'cuda.core.system.Device': + """ + Get the corresponding :class:`cuda.core.system.Device` (which is used + for NVIDIA Machine Library (NVML) access) for this + :class:`cuda.core.Device` (which is used for CUDA access). + + The devices are mapped to one another by their UUID. + """ + from cuda.core.system import Device as SystemDevice + return SystemDevice(uuid=self.uuid) + @property def device_id(self) -> int: """Return device ordinal.""" diff --git a/cuda_core/cuda/core/system/_device.pyx b/cuda_core/cuda/core/system/_device.pyx index 71cb35b907..73196aca8b 100644 --- a/cuda_core/cuda/core/system/_device.pyx +++ b/cuda_core/cuda/core/system/_device.pyx @@ -722,6 +722,36 @@ cdef class Device: pci_bus_id = pci_bus_id.decode("ascii") self._handle = nvml.device_get_handle_by_pci_bus_id_v2(pci_bus_id) + def to_cuda_device(self) -> "cuda.core.Device": + """ + Get the corresponding :class:`cuda.core.Device` (which is used for CUDA + access) for this :class:`cuda.core.system.Device` (which is used for + NVIDIA machine library (NVML) access). + + The devices are mapped to one another by their UUID. + + Returns + ------- + cuda.core.Device + The corresponding CUDA device. + """ + from cuda.core import Device as CudaDevice + + # CUDA does not have an API to get a device by its UUID, so we just + # search all the devices for one with a matching UUID. + + # NVML UUIDs have a `GPU-` or `MIG-` prefix. Possibly we should only do + # this matching when it has a `GPU-` prefix, but for now we just strip + # it. If a matching CUDA device can't be found, we will get a helpful + # exception, anyway, below. + uuid = self.uuid[4:] + + for cuda_device in CudaDevice.get_all_devices(): + if cuda_device.uuid == uuid: + return cuda_device + + raise RuntimeError("No corresponding CUDA device found for this NVML device.") + @classmethod def get_device_count(cls) -> int: """ diff --git a/cuda_core/tests/system/test_system_device.py b/cuda_core/tests/system/test_system_device.py index 2e762ce860..060a4c7061 100644 --- a/cuda_core/tests/system/test_system_device.py +++ b/cuda_core/tests/system/test_system_device.py @@ -33,6 +33,23 @@ def test_device_count(): assert system.Device.get_device_count() == system.get_num_devices() +def test_to_cuda_device(): + from cuda.core import Device as CudaDevice + + for device in system.Device.get_all_devices(): + cuda_device = device.to_cuda_device() + + assert isinstance(cuda_device, CudaDevice) + assert cuda_device.uuid == device.uuid[4:] + + # Technically, this test will only work with PCI devices, but are there + # non-PCI devices we need to support? + + # CUDA only returns a 2-byte PCI bus ID domain, whereas NVML returns a + # 4-byte domain + assert cuda_device.pci_bus_id == device.pci_info.bus_id[4:] + + def test_device_architecture(): for device in system.Device.get_all_devices(): device_arch = device.architecture diff --git a/cuda_core/tests/test_device.py b/cuda_core/tests/test_device.py index e4365ac0c9..dc640150af 100644 --- a/cuda_core/tests/test_device.py +++ b/cuda_core/tests/test_device.py @@ -25,6 +25,22 @@ def cuda_version(): return _py_major_ver, _driver_ver +def test_to_system_device(deinit_cuda): + from cuda.core.system import Device as SystemDevice + + device = Device() + system_device = device.to_system_device() + assert isinstance(system_device, SystemDevice) + assert system_device.uuid[4:] == device.uuid + + # Technically, this test will only work with PCI devices, but are there + # non-PCI devices we need to support? + + # CUDA only returns a 2-byte PCI bus ID domain, whereas NVML returns a + # 4-byte domain + assert device.pci_bus_id == system_device.pci_info.bus_id[4:] + + def test_device_set_current(deinit_cuda): device = Device() device.set_current() From c995aec28daef5fb0c02cf1573acc7ba9201650a Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Fri, 16 Jan 2026 12:53:17 -0500 Subject: [PATCH 2/5] Update cuda_core/cuda/core/_device.pyx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- cuda_core/cuda/core/_device.pyx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cuda_core/cuda/core/_device.pyx b/cuda_core/cuda/core/_device.pyx index 4b6a99b32d..7b5ce1e4a5 100644 --- a/cuda_core/cuda/core/_device.pyx +++ b/cuda_core/cuda/core/_device.pyx @@ -1041,6 +1041,11 @@ class Device: :class:`cuda.core.Device` (which is used for CUDA access). The devices are mapped to one another by their UUID. + + Returns + ------- + cuda.core.system.Device + The corresponding system-level device instance used for NVML access. """ from cuda.core.system import Device as SystemDevice return SystemDevice(uuid=self.uuid) From 784ba3ce5b9ee126cbce11a47f8d03ab6dde2a6a Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Fri, 16 Jan 2026 13:24:25 -0500 Subject: [PATCH 3/5] Support systems with old cuda.bindings --- cuda_core/cuda/core/_device.pyx | 7 +++++++ cuda_core/tests/test_device.py | 10 +++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/cuda_core/cuda/core/_device.pyx b/cuda_core/cuda/core/_device.pyx index 7b5ce1e4a5..018c75b7e9 100644 --- a/cuda_core/cuda/core/_device.pyx +++ b/cuda_core/cuda/core/_device.pyx @@ -1047,6 +1047,13 @@ class Device: cuda.core.system.Device The corresponding system-level device instance used for NVML access. """ + from cuda.core.system._system import CUDA_BINDINGS_NVML_IS_COMPATIBLE + + if not CUDA_BINDINGS_NVML_IS_COMPATIBLE: + raise RuntimeError( + "cuda.core.system.Device requires cuda_bindings 13.1.2+ or 12.9.6+" + ) + from cuda.core.system import Device as SystemDevice return SystemDevice(uuid=self.uuid) diff --git a/cuda_core/tests/test_device.py b/cuda_core/tests/test_device.py index dc640150af..23cec00f82 100644 --- a/cuda_core/tests/test_device.py +++ b/cuda_core/tests/test_device.py @@ -26,9 +26,17 @@ def cuda_version(): def test_to_system_device(deinit_cuda): - from cuda.core.system import Device as SystemDevice + from cuda.core.system import _system device = Device() + + if not _system.CUDA_BINDINGS_NVML_IS_COMPATIBLE: + with pytest.raises(RuntimeError): + device.to_system_device() + pytest.skip("NVML support requires cuda.bindings version 12.9.6+ or 13.1.2+") + + from cuda.core.system import Device as SystemDevice + system_device = device.to_system_device() assert isinstance(system_device, SystemDevice) assert system_device.uuid[4:] == device.uuid From 20875b336e8fcd3d0320b84959447aea90bea3b9 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Tue, 20 Jan 2026 13:09:39 -0500 Subject: [PATCH 4/5] Make uuid match between NVML and CUDA --- cuda_core/cuda/core/system/_device.pyx | 15 +++++++-------- cuda_core/tests/system/test_system_device.py | 2 +- cuda_core/tests/test_device.py | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/cuda_core/cuda/core/system/_device.pyx b/cuda_core/cuda/core/system/_device.pyx index 73196aca8b..82546d94b1 100644 --- a/cuda_core/cuda/core/system/_device.pyx +++ b/cuda_core/cuda/core/system/_device.pyx @@ -740,14 +740,8 @@ cdef class Device: # CUDA does not have an API to get a device by its UUID, so we just # search all the devices for one with a matching UUID. - # NVML UUIDs have a `GPU-` or `MIG-` prefix. Possibly we should only do - # this matching when it has a `GPU-` prefix, but for now we just strip - # it. If a matching CUDA device can't be found, we will get a helpful - # exception, anyway, below. - uuid = self.uuid[4:] - for cuda_device in CudaDevice.get_all_devices(): - if cuda_device.uuid == uuid: + if cuda_device.uuid == self.uuid: return cuda_device raise RuntimeError("No corresponding CUDA device found for this NVML device.") @@ -1067,7 +1061,12 @@ cdef class Device: device, as a 5 part hexadecimal string, that augments the immutable, board serial identifier. """ - return nvml.device_get_uuid(self._handle) + # NVML UUIDs have a `GPU-` or `MIG-` prefix. We remove that here. + + # TODO: If the user cares about the prefix, we will expose that in the + # future using the MIG-related APIs in NVML. + + return nvml.device_get_uuid(self._handle)[4:] def register_events(self, events: EventType | int | list[EventType | int]) -> DeviceEvents: """ diff --git a/cuda_core/tests/system/test_system_device.py b/cuda_core/tests/system/test_system_device.py index 083bdf7697..a2bc0178a3 100644 --- a/cuda_core/tests/system/test_system_device.py +++ b/cuda_core/tests/system/test_system_device.py @@ -40,7 +40,7 @@ def test_to_cuda_device(): cuda_device = device.to_cuda_device() assert isinstance(cuda_device, CudaDevice) - assert cuda_device.uuid == device.uuid[4:] + assert cuda_device.uuid == device.uuid # Technically, this test will only work with PCI devices, but are there # non-PCI devices we need to support? diff --git a/cuda_core/tests/test_device.py b/cuda_core/tests/test_device.py index 23cec00f82..0af40fece6 100644 --- a/cuda_core/tests/test_device.py +++ b/cuda_core/tests/test_device.py @@ -39,7 +39,7 @@ def test_to_system_device(deinit_cuda): system_device = device.to_system_device() assert isinstance(system_device, SystemDevice) - assert system_device.uuid[4:] == device.uuid + assert system_device.uuid == device.uuid # Technically, this test will only work with PCI devices, but are there # non-PCI devices we need to support? From a10f3bc9eacd3e3c3094393a7b716b2a08c30c86 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Tue, 20 Jan 2026 13:10:55 -0500 Subject: [PATCH 5/5] Add documentation --- cuda_core/cuda/core/system/_device.pyx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cuda_core/cuda/core/system/_device.pyx b/cuda_core/cuda/core/system/_device.pyx index 82546d94b1..00a9f2a216 100644 --- a/cuda_core/cuda/core/system/_device.pyx +++ b/cuda_core/cuda/core/system/_device.pyx @@ -1060,6 +1060,9 @@ cdef class Device: Retrieves the globally unique immutable UUID associated with this device, as a 5 part hexadecimal string, that augments the immutable, board serial identifier. + + In the upstream NVML C++ API, the UUID includes a ``gpu-`` or ``mig-`` + prefix. That is not included in ``cuda.core.system``. """ # NVML UUIDs have a `GPU-` or `MIG-` prefix. We remove that here.