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+
512import threading
613from typing import Optional, Union
714
1421from 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
938957class 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