Skip to content

Commit db8058d

Browse files
authored
Update Device constructor to accept Device or device ordinal. Update public APIs taking a device to accept either. (NVIDIA#1238)
1 parent 27b869e commit db8058d

9 files changed

Lines changed: 73 additions & 53 deletions

File tree

cuda_core/cuda/core/experimental/_device.pyx

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -948,9 +948,16 @@ class Device:
948948
Default value of `None` return the currently used device.
949949

950950
"""
951-
__slots__ = ("_id", "_mr", "_has_inited", "_properties", "_uuid")
951+
__slots__ = ("_id", "_memory_resource", "_has_inited", "_properties", "_uuid")
952952

953-
def __new__(cls, device_id: int | None = None):
953+
def __new__(cls, device_id: Device | int | None = None):
954+
# Handle device_id argument.
955+
if isinstance(device_id, Device):
956+
return device_id
957+
else:
958+
device_id = getattr(device_id, 'device_id', device_id)
959+
960+
# Initialize CUDA.
954961
global _is_cuInit
955962
if _is_cuInit is False:
956963
with _lock, nogil:
@@ -976,7 +983,7 @@ class Device:
976983
raise ValueError(f"device_id must be >= 0, got {device_id}")
977984

978985
# ensure Device is singleton
979-
cdef int total, attr
986+
cdef int total
980987
try:
981988
devices = _tls.devices
982989
except AttributeError:
@@ -986,21 +993,7 @@ class Device:
986993
for dev_id in range(total):
987994
device = super().__new__(cls)
988995
device._id = dev_id
989-
# If the device is in TCC mode, or does not support memory pools for some other reason,
990-
# use the SynchronousMemoryResource which does not use memory pools.
991-
with nogil:
992-
HANDLE_RETURN(
993-
cydriver.cuDeviceGetAttribute(
994-
&attr, cydriver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MEMORY_POOLS_SUPPORTED, dev_id
995-
)
996-
)
997-
if attr == 1:
998-
from cuda.core.experimental._memory import DeviceMemoryResource
999-
device._mr = DeviceMemoryResource(dev_id)
1000-
else:
1001-
from cuda.core.experimental._memory import _SynchronousMemoryResource
1002-
device._mr = _SynchronousMemoryResource(dev_id)
1003-
996+
device._memory_resource = None
1004997
device._has_inited = False
1005998
device._properties = None
1006999
device._uuid = None
@@ -1128,13 +1121,31 @@ class Device:
11281121
@property
11291122
def memory_resource(self) -> MemoryResource:
11301123
"""Return :obj:`~_memory.MemoryResource` associated with this device."""
1131-
return self._mr
1124+
cdef int attr, device_id
1125+
if self._memory_resource is None:
1126+
# If the device is in TCC mode, or does not support memory pools for some other reason,
1127+
# use the SynchronousMemoryResource which does not use memory pools.
1128+
device_id = self._id
1129+
with nogil:
1130+
HANDLE_RETURN(
1131+
cydriver.cuDeviceGetAttribute(
1132+
&attr, cydriver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MEMORY_POOLS_SUPPORTED, device_id
1133+
)
1134+
)
1135+
if attr == 1:
1136+
from cuda.core.experimental._memory import DeviceMemoryResource
1137+
self._memory_resource = DeviceMemoryResource(self._id)
1138+
else:
1139+
from cuda.core.experimental._memory import _SynchronousMemoryResource
1140+
self._memory_resource = _SynchronousMemoryResource(self._id)
1141+
1142+
return self._memory_resource
11321143

11331144
@memory_resource.setter
11341145
def memory_resource(self, mr):
11351146
from cuda.core.experimental._memory import MemoryResource
11361147
assert_type(mr, MemoryResource)
1137-
self._mr = mr
1148+
self._memory_resource = mr
11381149

11391150
@property
11401151
def default_stream(self) -> Stream:
@@ -1324,7 +1335,7 @@ class Device:
13241335
self._check_context_initialized()
13251336
if stream is None:
13261337
stream = default_stream()
1327-
return self._mr.allocate(size, stream)
1338+
return self.memory_resource.allocate(size, stream)
13281339

13291340
def sync(self):
13301341
"""Synchronize the device.

cuda_core/cuda/core/experimental/_event.pyx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ from cuda.core.experimental._utils.cuda_utils import (
2525
)
2626
if TYPE_CHECKING:
2727
import cuda.bindings
28-
from cuda.core.experimental._device import Device
2928

3029

3130
@dataclass

cuda_core/cuda/core/experimental/_memory/_device_memory_resource.pyx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ from cuda.core.experimental._utils.cuda_utils cimport (
1818
HANDLE_RETURN,
1919
)
2020

21-
import cython
2221
from dataclasses import dataclass
2322
from typing import Optional, TYPE_CHECKING
23+
import cython
2424
import platform # no-cython-lint
2525
import uuid
2626
import weakref
@@ -131,7 +131,7 @@ cdef class DeviceMemoryResource(MemoryResource):
131131
132132
Parameters
133133
----------
134-
device_id : int | Device
134+
device_id : Device | int
135135
Device or Device ordinal for which a memory resource is constructed.
136136
137137
options : DeviceMemoryResourceOptions
@@ -211,8 +211,9 @@ cdef class DeviceMemoryResource(MemoryResource):
211211
self._ipc_data = None
212212
self._attributes = None
213213

214-
def __init__(self, device_id: int | Device, options=None):
215-
cdef int dev_id = getattr(device_id, 'device_id', device_id)
214+
def __init__(self, device_id: Device | int, options=None):
215+
from .._device import Device
216+
cdef int dev_id = Device(device_id).device_id
216217
opts = check_or_create_options(
217218
DeviceMemoryResourceOptions, options, "DeviceMemoryResource options",
218219
keep_none=True
@@ -261,7 +262,7 @@ cdef class DeviceMemoryResource(MemoryResource):
261262

262263
@classmethod
263264
def from_allocation_handle(
264-
cls, device_id: int | Device, alloc_handle: int | IPCAllocationHandle
265+
cls, device_id: Device | int, alloc_handle: int | IPCAllocationHandle
265266
) -> DeviceMemoryResource:
266267
"""Create a device memory resource from an allocation handle.
267268

cuda_core/cuda/core/experimental/_memory/_ipc.pyx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,8 @@ cdef DeviceMemoryResource DMR_from_allocation_handle(cls, device_id, alloc_handl
197197

198198
# Construct a new DMR.
199199
cdef DeviceMemoryResource self = DeviceMemoryResource.__new__(cls)
200-
self._dev_id = getattr(device_id, 'device_id', device_id)
200+
from .._device import Device
201+
self._dev_id = Device(device_id).device_id
201202
self._mempool_owned = True
202203
self._ipc_data = IPCData(alloc_handle, mapped=True)
203204

cuda_core/cuda/core/experimental/_memory/_legacy.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,9 @@ class _SynchronousMemoryResource(MemoryResource):
8686
__slots__ = ("_dev_id",)
8787

8888
def __init__(self, device_id):
89-
self._dev_id = getattr(device_id, "device_id", device_id)
89+
from .._device import Device
90+
91+
self._dev_id = Device(device_id).device_id
9092

9193
def allocate(self, size, stream=None) -> Buffer:
9294
if stream is None:

cuda_core/cuda/core/experimental/_memory/_virtual_memory_resource.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from dataclasses import dataclass, field
66
from typing import Iterable, Literal, Union
77

8+
from cuda.core.experimental._device import Device
89
from cuda.core.experimental._memory._buffer import Buffer, MemoryResource
910
from cuda.core.experimental._stream import Stream
1011
from cuda.core.experimental._utils.cuda_utils import (
@@ -140,15 +141,15 @@ class VirtualMemoryResource(MemoryResource):
140141
141142
Parameters
142143
----------
143-
device_id : int
144-
Device ordinal for which a memory resource is constructed.
144+
device_id : Device | int
145+
Device for which a memory resource is constructed.
145146
146147
config : VirtualMemoryResourceOptions
147148
A configuration object for the VirtualMemoryResource
148149
"""
149150

150-
def __init__(self, device, config: VirtualMemoryResourceOptions = None):
151-
self.device = device
151+
def __init__(self, device_id: Device | int, config: VirtualMemoryResourceOptions = None):
152+
self.device = Device(device_id)
152153
self.config = check_or_create_options(
153154
VirtualMemoryResourceOptions, config, "VirtualMemoryResource options", keep_none=False
154155
)

cuda_core/cuda/core/experimental/_module.py

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from typing import Union
88
from warnings import warn
99

10+
from cuda.core.experimental._device import Device
1011
from cuda.core.experimental._launch_config import LaunchConfig, _to_native_launch_config
1112
from cuda.core.experimental._stream import Stream
1213
from cuda.core.experimental._utils.clear_error_support import (
@@ -73,8 +74,9 @@ def _init(cls, kernel):
7374
self._loader = _backend[self._backend_version]
7475
return self
7576

76-
def _get_cached_attribute(self, device_id: int, attribute: driver.CUfunction_attribute) -> int:
77+
def _get_cached_attribute(self, device_id: Device | int, attribute: driver.CUfunction_attribute) -> int:
7778
"""Helper function to get a cached attribute or fetch and cache it if not present."""
79+
device_id = Device(device_id).device_id
7880
cache_key = device_id, attribute
7981
result = self._cache.get(cache_key, cache_key)
8082
if result is not cache_key:
@@ -94,62 +96,62 @@ def _get_cached_attribute(self, device_id: int, attribute: driver.CUfunction_att
9496
self._cache[cache_key] = result
9597
return result
9698

97-
def max_threads_per_block(self, device_id: int = None) -> int:
99+
def max_threads_per_block(self, device_id: Device | int = None) -> int:
98100
"""int : The maximum number of threads per block.
99101
This attribute is read-only."""
100102
return self._get_cached_attribute(
101103
device_id, driver.CUfunction_attribute.CU_FUNC_ATTRIBUTE_MAX_THREADS_PER_BLOCK
102104
)
103105

104-
def shared_size_bytes(self, device_id: int = None) -> int:
106+
def shared_size_bytes(self, device_id: Device | int = None) -> int:
105107
"""int : The size in bytes of statically-allocated shared memory required by this function.
106108
This attribute is read-only."""
107109
return self._get_cached_attribute(device_id, driver.CUfunction_attribute.CU_FUNC_ATTRIBUTE_SHARED_SIZE_BYTES)
108110

109-
def const_size_bytes(self, device_id: int = None) -> int:
111+
def const_size_bytes(self, device_id: Device | int = None) -> int:
110112
"""int : The size in bytes of user-allocated constant memory required by this function.
111113
This attribute is read-only."""
112114
return self._get_cached_attribute(device_id, driver.CUfunction_attribute.CU_FUNC_ATTRIBUTE_CONST_SIZE_BYTES)
113115

114-
def local_size_bytes(self, device_id: int = None) -> int:
116+
def local_size_bytes(self, device_id: Device | int = None) -> int:
115117
"""int : The size in bytes of local memory used by each thread of this function.
116118
This attribute is read-only."""
117119
return self._get_cached_attribute(device_id, driver.CUfunction_attribute.CU_FUNC_ATTRIBUTE_LOCAL_SIZE_BYTES)
118120

119-
def num_regs(self, device_id: int = None) -> int:
121+
def num_regs(self, device_id: Device | int = None) -> int:
120122
"""int : The number of registers used by each thread of this function.
121123
This attribute is read-only."""
122124
return self._get_cached_attribute(device_id, driver.CUfunction_attribute.CU_FUNC_ATTRIBUTE_NUM_REGS)
123125

124-
def ptx_version(self, device_id: int = None) -> int:
126+
def ptx_version(self, device_id: Device | int = None) -> int:
125127
"""int : The PTX virtual architecture version for which the function was compiled.
126128
This attribute is read-only."""
127129
return self._get_cached_attribute(device_id, driver.CUfunction_attribute.CU_FUNC_ATTRIBUTE_PTX_VERSION)
128130

129-
def binary_version(self, device_id: int = None) -> int:
131+
def binary_version(self, device_id: Device | int = None) -> int:
130132
"""int : The binary architecture version for which the function was compiled.
131133
This attribute is read-only."""
132134
return self._get_cached_attribute(device_id, driver.CUfunction_attribute.CU_FUNC_ATTRIBUTE_BINARY_VERSION)
133135

134-
def cache_mode_ca(self, device_id: int = None) -> bool:
136+
def cache_mode_ca(self, device_id: Device | int = None) -> bool:
135137
"""bool : Whether the function has been compiled with user specified option "-Xptxas --dlcm=ca" set.
136138
This attribute is read-only."""
137139
return bool(self._get_cached_attribute(device_id, driver.CUfunction_attribute.CU_FUNC_ATTRIBUTE_CACHE_MODE_CA))
138140

139-
def max_dynamic_shared_size_bytes(self, device_id: int = None) -> int:
141+
def max_dynamic_shared_size_bytes(self, device_id: Device | int = None) -> int:
140142
"""int : The maximum size in bytes of dynamically-allocated shared memory that can be used
141143
by this function."""
142144
return self._get_cached_attribute(
143145
device_id, driver.CUfunction_attribute.CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES
144146
)
145147

146-
def preferred_shared_memory_carveout(self, device_id: int = None) -> int:
148+
def preferred_shared_memory_carveout(self, device_id: Device | int = None) -> int:
147149
"""int : The shared memory carveout preference, in percent of the total shared memory."""
148150
return self._get_cached_attribute(
149151
device_id, driver.CUfunction_attribute.CU_FUNC_ATTRIBUTE_PREFERRED_SHARED_MEMORY_CARVEOUT
150152
)
151153

152-
def cluster_size_must_be_set(self, device_id: int = None) -> bool:
154+
def cluster_size_must_be_set(self, device_id: Device | int = None) -> bool:
153155
"""bool : The kernel must launch with a valid cluster size specified.
154156
This attribute is read-only."""
155157
return bool(
@@ -158,33 +160,33 @@ def cluster_size_must_be_set(self, device_id: int = None) -> bool:
158160
)
159161
)
160162

161-
def required_cluster_width(self, device_id: int = None) -> int:
163+
def required_cluster_width(self, device_id: Device | int = None) -> int:
162164
"""int : The required cluster width in blocks."""
163165
return self._get_cached_attribute(
164166
device_id, driver.CUfunction_attribute.CU_FUNC_ATTRIBUTE_REQUIRED_CLUSTER_WIDTH
165167
)
166168

167-
def required_cluster_height(self, device_id: int = None) -> int:
169+
def required_cluster_height(self, device_id: Device | int = None) -> int:
168170
"""int : The required cluster height in blocks."""
169171
return self._get_cached_attribute(
170172
device_id, driver.CUfunction_attribute.CU_FUNC_ATTRIBUTE_REQUIRED_CLUSTER_HEIGHT
171173
)
172174

173-
def required_cluster_depth(self, device_id: int = None) -> int:
175+
def required_cluster_depth(self, device_id: Device | int = None) -> int:
174176
"""int : The required cluster depth in blocks."""
175177
return self._get_cached_attribute(
176178
device_id, driver.CUfunction_attribute.CU_FUNC_ATTRIBUTE_REQUIRED_CLUSTER_DEPTH
177179
)
178180

179-
def non_portable_cluster_size_allowed(self, device_id: int = None) -> bool:
181+
def non_portable_cluster_size_allowed(self, device_id: Device | int = None) -> bool:
180182
"""bool : Whether the function can be launched with non-portable cluster size."""
181183
return bool(
182184
self._get_cached_attribute(
183185
device_id, driver.CUfunction_attribute.CU_FUNC_ATTRIBUTE_NON_PORTABLE_CLUSTER_SIZE_ALLOWED
184186
)
185187
)
186188

187-
def cluster_scheduling_policy_preference(self, device_id: int = None) -> int:
189+
def cluster_scheduling_policy_preference(self, device_id: Device | int = None) -> int:
188190
"""int : The block scheduling policy of a function."""
189191
return self._get_cached_attribute(
190192
device_id, driver.CUfunction_attribute.CU_FUNC_ATTRIBUTE_CLUSTER_SCHEDULING_POLICY_PREFERENCE

cuda_core/tests/test_memory.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,8 @@ def test_device_memory_resource_initialization(mempool_device, use_device_object
312312
buffer.close()
313313

314314

315-
def test_vmm_allocator_basic_allocation():
315+
@pytest.mark.parametrize("use_device_object", [True, False])
316+
def test_vmm_allocator_basic_allocation(use_device_object):
316317
"""Test basic VMM allocation functionality.
317318
318319
This test verifies that VirtualMemoryResource can allocate memory
@@ -327,7 +328,8 @@ def test_vmm_allocator_basic_allocation():
327328

328329
options = VirtualMemoryResourceOptions()
329330
# Create VMM allocator with default config
330-
vmm_mr = VirtualMemoryResource(device, config=options)
331+
device_arg = device if use_device_object else device.device_id
332+
vmm_mr = VirtualMemoryResource(device_arg, config=options)
331333

332334
# Test basic allocation
333335
buffer = vmm_mr.allocate(4096)

cuda_core/tests/test_module.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,9 @@ def test_read_only_kernel_attributes(get_saxpy_kernel_cubin, attr, expected_type
134134
value = method()
135135
assert value is not None
136136

137-
# get the value for each device on the system
137+
# get the value for each device on the system, using either the device object or ordinal
138138
for device in system.devices:
139+
value = method(device)
139140
value = method(device.device_id)
140141
assert isinstance(value, expected_type), f"Expected {attr} to be of type {expected_type}, but got {type(value)}"
141142

0 commit comments

Comments
 (0)