Skip to content

Commit a501cc7

Browse files
committed
cythonize device
1 parent dc8d076 commit a501cc7

2 files changed

Lines changed: 91 additions & 79 deletions

File tree

cuda_core/build_hooks.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
get_requires_for_build_sdist = _build_meta.get_requires_for_build_sdist
2525

2626

27+
@functools.cache
2728
def _get_proper_cuda_bindings_major_version() -> str:
2829
# for local development (with/without build isolation)
2930
try:
@@ -92,10 +93,13 @@ def get_cuda_paths():
9293
)
9394

9495
nthreads = int(os.environ.get("CUDA_PYTHON_PARALLEL_LEVEL", os.cpu_count() // 2))
96+
compile_time_env = {"CUDA_CORE_BUILD_MAJOR": _get_proper_cuda_bindings_major_version()}
9597

9698
global _extensions
9799
_extensions = cythonize(
98-
ext_modules, verbose=True, language_level=3, nthreads=nthreads, compiler_directives={"embedsignature": True}
100+
ext_modules, verbose=True, language_level=3, nthreads=nthreads,
101+
compiler_directives={"embedsignature": True, "warn.deprecated.IF": False},
102+
compile_time_env=compile_time_env
99103
)
100104

101105
return _build_meta.build_wheel(wheel_directory, config_settings, metadata_directory)

cuda_core/cuda/core/experimental/_device.py renamed to cuda_core/cuda/core/experimental/_device.pyx

Lines changed: 86 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22
#
33
# SPDX-License-Identifier: Apache-2.0
44

5+
from libc.stdint cimport uintptr_t
6+
7+
# TODO: how about cuda.bindings < 12.6.2?
8+
from cuda.bindings cimport cydriver
9+
10+
from cuda.core.experimental._utils.cuda_utils cimport HANDLE_RETURN
11+
512
import threading
613
from typing import Optional, Union
714

@@ -14,41 +21,44 @@
1421
from cuda.core.experimental._utils.cuda_utils import (
1522
ComputeCapability,
1623
CUDAError,
17-
_check_driver_error,
1824
driver,
1925
handle_return,
2026
runtime,
2127
)
2228

29+
2330
_tls = threading.local()
2431
_lock = threading.Lock()
25-
_is_cuInit = False
32+
cdef bint _is_cuInit = False
2633

2734

28-
class DeviceProperties:
35+
cdef class DeviceProperties:
2936
"""
3037
A class to query various attributes of a CUDA device.
3138
3239
Attributes are read-only and provide information about the device.
3340
"""
41+
cdef:
42+
int _handle
43+
dict _cache
3444

35-
def __new__(self, *args, **kwargs):
45+
def __init__(self, *args, **kwargs):
3646
raise RuntimeError("DeviceProperties cannot be instantiated directly. Please use Device APIs.")
3747

38-
__slots__ = ("_handle", "_cache")
39-
4048
@classmethod
4149
def _init(cls, handle):
42-
self = super().__new__(cls)
50+
cdef DeviceProperties self = DeviceProperties.__new__(cls)
4351
self._handle = handle
4452
self._cache = {}
4553
return self
4654

47-
def _get_attribute(self, attr):
55+
cdef inline _get_attribute(self, cydriver.CUdevice_attribute attr):
4856
"""Retrieve the attribute value directly from the driver."""
49-
return handle_return(driver.cuDeviceGetAttribute(attr, self._handle))
57+
cdef int val
58+
HANDLE_RETURN(cydriver.cuDeviceGetAttribute(&val, attr, self._handle))
59+
return val
5060

51-
def _get_cached_attribute(self, attr):
61+
cdef _get_cached_attribute(self, attr):
5262
"""Retrieve the attribute value, using cache if applicable."""
5363
if attr not in self._cache:
5464
self._cache[attr] = self._get_attribute(attr)
@@ -931,8 +941,17 @@ def multicast_supported(self) -> bool:
931941
return bool(self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MULTICAST_SUPPORTED))
932942

933943

934-
_SUCCESS = driver.CUresult.CUDA_SUCCESS
935-
_INVALID_CTX = driver.CUresult.CUDA_ERROR_INVALID_CONTEXT
944+
cdef cydriver.CUcontext _get_primary_context(int dev_id) except?NULL:
945+
try:
946+
primary_ctxs = _tls.primary_ctxs
947+
except AttributeError:
948+
total = len(_tls.devices)
949+
primary_ctxs = _tls.primary_ctxs = [0] * total
950+
cdef cydriver.CUcontext ctx = <cydriver.CUcontext><uintptr_t>(primary_ctxs[dev_id])
951+
if ctx == NULL:
952+
HANDLE_RETURN(cydriver.cuDevicePrimaryCtxRetain(&ctx, dev_id))
953+
primary_ctxs[dev_id] = <uintptr_t>(ctx)
954+
return ctx
936955

937956

938957
class Device:
@@ -961,55 +980,56 @@ class Device:
961980
Default value of `None` return the currently used device.
962981

963982
"""
964-
965983
__slots__ = ("_id", "_mr", "_has_inited", "_properties")
966984

967985
def __new__(cls, device_id: Optional[int] = None):
968986
global _is_cuInit
969987
if _is_cuInit is False:
970988
with _lock:
971-
handle_return(driver.cuInit(0))
989+
HANDLE_RETURN(cydriver.cuInit(0))
972990
_is_cuInit = True
973991

974992
# important: creating a Device instance does not initialize the GPU!
993+
cdef cydriver.CUdevice dev
994+
cdef cydriver.CUcontext ctx
975995
if device_id is None:
976-
err, dev = driver.cuCtxGetDevice()
977-
if err == _SUCCESS:
996+
err = cydriver.cuCtxGetDevice(&dev)
997+
if err == cydriver.CUresult.CUDA_SUCCESS:
978998
device_id = int(dev)
979-
elif err == _INVALID_CTX:
980-
ctx = handle_return(driver.cuCtxGetCurrent())
981-
assert int(ctx) == 0
999+
elif err == cydriver.CUresult.CUDA_ERROR_INVALID_CONTEXT:
1000+
HANDLE_RETURN(cydriver.cuCtxGetCurrent(&ctx))
1001+
assert <void*>(ctx) == NULL
9821002
device_id = 0 # cudart behavior
9831003
else:
984-
_check_driver_error(err)
1004+
HANDLE_RETURN(err)
9851005
elif device_id < 0:
9861006
raise ValueError(f"device_id must be >= 0, got {device_id}")
9871007

9881008
# ensure Device is singleton
1009+
cdef int total, attr
9891010
try:
9901011
devices = _tls.devices
9911012
except AttributeError:
992-
total = handle_return(driver.cuDeviceGetCount())
1013+
HANDLE_RETURN(cydriver.cuDeviceGetCount(&total))
9931014
devices = _tls.devices = []
9941015
for dev_id in range(total):
995-
dev = super().__new__(cls)
996-
dev._id = dev_id
1016+
device = super().__new__(cls)
1017+
device._id = dev_id
9971018
# If the device is in TCC mode, or does not support memory pools for some other reason,
9981019
# use the SynchronousMemoryResource which does not use memory pools.
999-
if (
1000-
handle_return(
1001-
driver.cuDeviceGetAttribute(
1002-
driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MEMORY_POOLS_SUPPORTED, dev_id
1003-
)
1020+
HANDLE_RETURN(
1021+
cydriver.cuDeviceGetAttribute(
1022+
&attr, cydriver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MEMORY_POOLS_SUPPORTED, dev_id
10041023
)
1005-
) == 1:
1006-
dev._mr = DeviceMemoryResource(dev_id)
1024+
)
1025+
if attr == 1:
1026+
device._mr = DeviceMemoryResource(dev_id)
10071027
else:
1008-
dev._mr = _SynchronousMemoryResource(dev_id)
1028+
device._mr = _SynchronousMemoryResource(dev_id)
10091029

1010-
dev._has_inited = False
1011-
dev._properties = None
1012-
devices.append(dev)
1030+
device._has_inited = False
1031+
device._properties = None
1032+
devices.append(device)
10131033

10141034
try:
10151035
return devices[device_id]
@@ -1022,36 +1042,17 @@ def _check_context_initialized(self):
10221042
f"Device {self._id} is not yet initialized, perhaps you forgot to call .set_current() first?"
10231043
)
10241044

1025-
def _get_primary_context(self) -> driver.CUcontext:
1026-
try:
1027-
primary_ctxs = _tls.primary_ctxs
1028-
except AttributeError:
1029-
total = len(_tls.devices)
1030-
primary_ctxs = _tls.primary_ctxs = [None] * total
1031-
ctx = primary_ctxs[self._id]
1032-
if ctx is None:
1033-
ctx = handle_return(driver.cuDevicePrimaryCtxRetain(self._id))
1034-
primary_ctxs[self._id] = ctx
1035-
return ctx
1036-
10371045
def _get_current_context(self, check_consistency=False) -> driver.CUcontext:
1038-
err, ctx = driver.cuCtxGetCurrent()
1039-
1040-
# TODO: We want to just call this:
1041-
# _check_driver_error(err)
1042-
# but even the simplest success check causes 50-100 ns. Wait until we cythonize this file...
1043-
if ctx is None:
1044-
_check_driver_error(err)
1045-
1046-
if int(ctx) == 0:
1046+
cdef cydriver.CUcontext ctx
1047+
HANDLE_RETURN(cydriver.cuCtxGetCurrent(&ctx))
1048+
if ctx == NULL:
10471049
raise CUDAError("No context is bound to the calling CPU thread.")
1050+
cdef cydriver.CUdevice dev
10481051
if check_consistency:
1049-
err, dev = driver.cuCtxGetDevice()
1050-
if err != _SUCCESS:
1051-
handle_return((err,))
1052-
if int(dev) != self._id:
1052+
HANDLE_RETURN(cydriver.cuCtxGetDevice(&dev))
1053+
if <int>(dev) != self._id:
10531054
raise CUDAError("Internal error (current device is not equal to Device.device_id)")
1054-
return ctx
1055+
return driver.CUcontext(<uintptr_t>ctx)
10551056

10561057
@property
10571058
def device_id(self) -> int:
@@ -1078,20 +1079,23 @@ def uuid(self) -> str:
10781079
driver is older than CUDA 11.4.
10791080

10801081
"""
1081-
driver_ver = handle_return(driver.cuDriverGetVersion())
1082-
if 11040 <= driver_ver < 13000:
1083-
uuid = handle_return(driver.cuDeviceGetUuid_v2(self._id))
1084-
else:
1085-
uuid = handle_return(driver.cuDeviceGetUuid(self._id))
1086-
uuid = uuid.bytes.hex()
1082+
cdef cydriver.CUuuid uuid
1083+
IF CUDA_CORE_BUILD_MAJOR == "12":
1084+
HANDLE_RETURN(cydriver.cuDeviceGetUuid_v2(&uuid, self._id))
1085+
ELSE: # 13.0+
1086+
HANDLE_RETURN(cydriver.cuDeviceGetUuid(&uuid, self._id))
1087+
cdef bytes uuid_b = uuid.bytes
1088+
cdef str uuid_hex = uuid_b.hex()
10871089
# 8-4-4-4-12
1088-
return f"{uuid[:8]}-{uuid[8:12]}-{uuid[12:16]}-{uuid[16:20]}-{uuid[20:]}"
1090+
return f"{uuid_hex[:8]}-{uuid_hex[8:12]}-{uuid_hex[12:16]}-{uuid_hex[16:20]}-{uuid_hex[20:]}"
10891091

10901092
@property
10911093
def name(self) -> str:
10921094
"""Return the device name."""
10931095
# Use 256 characters to be consistent with CUDA Runtime
1094-
name = handle_return(driver.cuDeviceGetName(256, self._id))
1096+
cdef int LENGTH = 256
1097+
cdef bytes name = bytes(LENGTH)
1098+
HANDLE_RETURN(cydriver.cuDeviceGetName(<char*>name, LENGTH, self._id))
10951099
name = name.split(b"\0")[0]
10961100
return name.decode()
10971101

@@ -1106,10 +1110,11 @@ def properties(self) -> DeviceProperties:
11061110
@property
11071111
def compute_capability(self) -> ComputeCapability:
11081112
"""Return a named tuple with 2 fields: major and minor."""
1109-
if "compute_capability" in self.properties._cache:
1110-
return self.properties._cache["compute_capability"]
1111-
cc = ComputeCapability(self.properties.compute_capability_major, self.properties.compute_capability_minor)
1112-
self.properties._cache["compute_capability"] = cc
1113+
cdef DeviceProperties prop = self.properties
1114+
if "compute_capability" in prop._cache:
1115+
return prop._cache["compute_capability"]
1116+
cc = ComputeCapability(prop.compute_capability_major, prop.compute_capability_minor)
1117+
prop._cache["compute_capability"] = cc
11131118
return cc
11141119

11151120
@property
@@ -1190,22 +1195,25 @@ def set_current(self, ctx: Context = None) -> Union[Context, None]:
11901195
>>> # ... do work on device 0 ...
11911196

11921197
"""
1198+
cdef cydriver.CUcontext _ctx
11931199
if ctx is not None:
1200+
# TODO: revisit once Context is cythonized
11941201
assert_type(ctx, Context)
11951202
if ctx._id != self._id:
11961203
raise RuntimeError(
11971204
"the provided context was created on the device with"
11981205
f" id={ctx._id}, which is different from the target id={self._id}"
11991206
)
1200-
prev_ctx = handle_return(driver.cuCtxPopCurrent())
1201-
handle_return(driver.cuCtxPushCurrent(ctx._handle))
1207+
# _ctx is the previous context
1208+
HANDLE_RETURN(cydriver.cuCtxPopCurrent(&_ctx))
1209+
HANDLE_RETURN(cydriver.cuCtxPushCurrent(<cydriver.CUcontext>(ctx._handle)))
12021210
self._has_inited = True
1203-
if int(prev_ctx) != 0:
1204-
return Context._from_ctx(prev_ctx, self._id)
1211+
if _ctx != NULL:
1212+
return Context._from_ctx(<uintptr_t>(_ctx), self._id)
12051213
else:
12061214
# use primary ctx
1207-
ctx = self._get_primary_context()
1208-
handle_return(driver.cuCtxSetCurrent(ctx))
1215+
_ctx = _get_primary_context(self._id)
1216+
HANDLE_RETURN(cydriver.cuCtxSetCurrent(_ctx))
12091217
self._has_inited = True
12101218

12111219
def create_context(self, options: ContextOptions = None) -> Context:

0 commit comments

Comments
 (0)