Skip to content

Commit 1bcebd5

Browse files
Merge branch 'main' into IPC
2 parents 140ba3f + e76b1c5 commit 1bcebd5

26 files changed

Lines changed: 413 additions & 155 deletions

cuda_core/cuda/core/experimental/_context.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
from dataclasses import dataclass
66

7-
from cuda.core.experimental._utils import driver
7+
from cuda.core.experimental._utils.clear_error_support import assert_type
8+
from cuda.core.experimental._utils.cuda_utils import driver
89

910

1011
@dataclass
@@ -20,7 +21,7 @@ def __new__(self, *args, **kwargs):
2021

2122
@classmethod
2223
def _from_ctx(cls, obj, dev_id):
23-
assert isinstance(obj, driver.CUcontext)
24+
assert_type(obj, driver.CUcontext)
2425
ctx = super().__new__(cls)
2526
ctx._handle = obj
2627
ctx._id = dev_id

cuda_core/cuda/core/experimental/_device.py

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,22 @@
33
# SPDX-License-Identifier: LicenseRef-NVIDIA-SOFTWARE-LICENSE
44

55
import threading
6-
from typing import Union
6+
from typing import Optional, Union
77

88
from cuda.core.experimental._context import Context, ContextOptions
99
from cuda.core.experimental._memory import AsyncMempool, Buffer, MemoryResource, _SynchronousMemoryResource
10+
from cuda.core.experimental._event import Event, EventOptions
11+
1012
from cuda.core.experimental._stream import Stream, StreamOptions, default_stream
11-
from cuda.core.experimental._utils import ComputeCapability, CUDAError, driver, handle_return, precondition, runtime
13+
from cuda.core.experimental._utils.clear_error_support import assert_type
14+
from cuda.core.experimental._utils.cuda_utils import (
15+
ComputeCapability,
16+
CUDAError,
17+
driver,
18+
handle_return,
19+
precondition,
20+
runtime,
21+
)
1222

1323
_tls = threading.local()
1424
_lock = threading.Lock()
@@ -949,10 +959,11 @@ def __new__(cls, device_id=None):
949959
# important: creating a Device instance does not initialize the GPU!
950960
if device_id is None:
951961
device_id = handle_return(runtime.cudaGetDevice())
952-
assert isinstance(device_id, int), f"{device_id=}"
962+
assert_type(device_id, int)
953963
else:
954964
total = handle_return(runtime.cudaGetDeviceCount())
955-
if not isinstance(device_id, int) or not (0 <= device_id < total):
965+
assert_type(device_id, int)
966+
if not (0 <= device_id < total):
956967
raise ValueError(f"device_id must be within [0, {total}), got {device_id}")
957968

958969
# ensure Device is singleton
@@ -981,7 +992,9 @@ def __new__(cls, device_id=None):
981992

982993
def _check_context_initialized(self, *args, **kwargs):
983994
if not self._has_inited:
984-
raise CUDAError("the device is not yet initialized, perhaps you forgot to call .set_current() first?")
995+
raise CUDAError(
996+
f"Device {self._id} is not yet initialized, perhaps you forgot to call .set_current() first?"
997+
)
985998

986999
@property
9871000
def device_id(self) -> int:
@@ -1053,7 +1066,8 @@ def context(self) -> Context:
10531066
10541067
"""
10551068
ctx = handle_return(driver.cuCtxGetCurrent())
1056-
assert int(ctx) != 0
1069+
if int(ctx) == 0:
1070+
raise CUDAError("No context is bound to the calling CPU thread.")
10571071
return Context._from_ctx(ctx, self._id)
10581072

10591073
@property
@@ -1063,8 +1077,7 @@ def memory_resource(self) -> MemoryResource:
10631077

10641078
@memory_resource.setter
10651079
def memory_resource(self, mr):
1066-
if not isinstance(mr, MemoryResource):
1067-
raise TypeError
1080+
assert_type(mr, MemoryResource)
10681081
self._mr = mr
10691082

10701083
@property
@@ -1118,12 +1131,11 @@ def set_current(self, ctx: Context = None) -> Union[Context, None]:
11181131
11191132
"""
11201133
if ctx is not None:
1121-
if not isinstance(ctx, Context):
1122-
raise TypeError("a Context object is required")
1134+
assert_type(ctx, Context)
11231135
if ctx._id != self._id:
11241136
raise RuntimeError(
1125-
"the provided context was created on a different "
1126-
f"device {ctx._id} other than the target {self._id}"
1137+
"the provided context was created on the device with"
1138+
f" id={ctx._id}, which is different from the target id={self._id}"
11271139
)
11281140
prev_ctx = handle_return(driver.cuCtxPopCurrent())
11291141
handle_return(driver.cuCtxPushCurrent(ctx._handle))
@@ -1165,7 +1177,7 @@ def create_context(self, options: ContextOptions = None) -> Context:
11651177
Newly created context object.
11661178
11671179
"""
1168-
raise NotImplementedError("TODO")
1180+
raise NotImplementedError("WIP: https://github.com/NVIDIA/cuda-python/issues/189")
11691181

11701182
@precondition(_check_context_initialized)
11711183
def create_stream(self, obj=None, options: StreamOptions = None) -> Stream:
@@ -1198,6 +1210,27 @@ def create_stream(self, obj=None, options: StreamOptions = None) -> Stream:
11981210
"""
11991211
return Stream._init(obj=obj, options=options)
12001212

1213+
@precondition(_check_context_initialized)
1214+
def create_event(self, options: Optional[EventOptions] = None) -> Event:
1215+
"""Create an Event object without recording it to a Stream.
1216+
1217+
Note
1218+
----
1219+
Device must be initialized.
1220+
1221+
Parameters
1222+
----------
1223+
options : :obj:`EventOptions`, optional
1224+
Customizable dataclass for event creation options.
1225+
1226+
Returns
1227+
-------
1228+
:obj:`~_event.Event`
1229+
Newly created event object.
1230+
1231+
"""
1232+
return Event._init(options)
1233+
12011234
@precondition(_check_context_initialized)
12021235
def allocate(self, size, stream=None) -> Buffer:
12031236
"""Allocate device memory from a specified stream.

cuda_core/cuda/core/experimental/_event.py

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from dataclasses import dataclass
99
from typing import TYPE_CHECKING, Optional
1010

11-
from cuda.core.experimental._utils import CUDAError, check_or_create_options, driver, handle_return
11+
from cuda.core.experimental._utils.cuda_utils import CUDAError, check_or_create_options, driver, handle_return
1212

1313
if TYPE_CHECKING:
1414
import cuda.bindings
@@ -47,8 +47,21 @@ class Event:
4747
the last recorded stream.
4848
4949
Events can be used to monitor device's progress, query completion
50-
of work up to event's record, and help establish dependencies
51-
between GPU work submissions.
50+
of work up to event's record, help establish dependencies
51+
between GPU work submissions, and record the elapsed time (in milliseconds)
52+
on GPU:
53+
54+
.. code-block:: python
55+
56+
# To create events and record the timing:
57+
s = Device().create_stream()
58+
e1 = Device().create_event({"enable_timing": True})
59+
e2 = Device().create_event({"enable_timing": True})
60+
s.record(e1)
61+
# ... run some GPU works ...
62+
s.record(e2)
63+
e2.sync()
64+
print(f"time = {e2 - e1} milliseconds")
5265
5366
Directly creating an :obj:`~_event.Event` is not supported due to ambiguity,
5467
and they should instead be created through a :obj:`~_stream.Stream` object.
@@ -88,14 +101,30 @@ def _init(cls, options: Optional[EventOptions] = None):
88101
flags |= driver.CUevent_flags.CU_EVENT_BLOCKING_SYNC
89102
self._busy_waited = True
90103
if options.support_ipc:
91-
raise NotImplementedError("TODO")
104+
raise NotImplementedError("WIP: https://github.com/NVIDIA/cuda-python/issues/103")
92105
self._mnff.handle = handle_return(driver.cuEventCreate(flags))
93106
return self
94107

95108
def close(self):
96109
"""Destroy the event."""
97110
self._mnff.close()
98111

112+
def __isub__(self, other):
113+
return NotImplemented
114+
115+
def __rsub__(self, other):
116+
return NotImplemented
117+
118+
def __sub__(self, other):
119+
# return self - other (in milliseconds)
120+
try:
121+
timing = handle_return(driver.cuEventElapsedTime(other.handle, self.handle))
122+
except CUDAError as e:
123+
raise RuntimeError(
124+
"Timing capability must be enabled in order to subtract two Events; timing is disabled by default."
125+
) from e
126+
return timing
127+
99128
@property
100129
def is_timing_disabled(self) -> bool:
101130
"""Return True if the event does not record timing data, otherwise False."""
@@ -109,7 +138,7 @@ def is_sync_busy_waited(self) -> bool:
109138
@property
110139
def is_ipc_supported(self) -> bool:
111140
"""Return True if this event can be used as an interprocess event, otherwise False."""
112-
raise NotImplementedError("TODO")
141+
raise NotImplementedError("WIP: https://github.com/NVIDIA/cuda-python/issues/103")
113142

114143
def sync(self):
115144
"""Synchronize until the event completes.
@@ -129,10 +158,9 @@ def is_done(self) -> bool:
129158
(result,) = driver.cuEventQuery(self._mnff.handle)
130159
if result == driver.CUresult.CUDA_SUCCESS:
131160
return True
132-
elif result == driver.CUresult.CUDA_ERROR_NOT_READY:
161+
if result == driver.CUresult.CUDA_ERROR_NOT_READY:
133162
return False
134-
else:
135-
raise CUDAError(f"unexpected error: {result}")
163+
handle_return(result)
136164

137165
@property
138166
def handle(self) -> cuda.bindings.driver.CUevent:

cuda_core/cuda/core/experimental/_launcher.py

Lines changed: 24 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,15 @@
99
from cuda.core.experimental._kernel_arg_handler import ParamHolder
1010
from cuda.core.experimental._module import Kernel
1111
from cuda.core.experimental._stream import Stream
12-
from cuda.core.experimental._utils import CUDAError, check_or_create_options, driver, get_binding_version, handle_return
12+
from cuda.core.experimental._utils.clear_error_support import assert_type
13+
from cuda.core.experimental._utils.cuda_utils import (
14+
CUDAError,
15+
cast_to_3_tuple,
16+
check_or_create_options,
17+
driver,
18+
get_binding_version,
19+
handle_return,
20+
)
1321

1422
# TODO: revisit this treatment for py313t builds
1523
_inited = False
@@ -59,41 +67,23 @@ class LaunchConfig:
5967

6068
def __post_init__(self):
6169
_lazy_init()
62-
self.grid = self._cast_to_3_tuple(self.grid)
63-
self.block = self._cast_to_3_tuple(self.block)
70+
self.grid = cast_to_3_tuple("LaunchConfig.grid", self.grid)
71+
self.block = cast_to_3_tuple("LaunchConfig.block", self.block)
6472
# thread block clusters are supported starting H100
6573
if self.cluster is not None:
6674
if not _use_ex:
67-
raise CUDAError("thread block clusters require cuda.bindings & driver 11.8+")
68-
if Device().compute_capability < (9, 0):
69-
raise CUDAError("thread block clusters are not supported on devices with compute capability < 9.0")
70-
self.cluster = self._cast_to_3_tuple(self.cluster)
75+
err, drvers = driver.cuDriverGetVersion()
76+
drvers_fmt = f" (got driver version {drvers})" if err == driver.CUresult.CUDA_SUCCESS else ""
77+
raise CUDAError(f"thread block clusters require cuda.bindings & driver 11.8+{drvers_fmt}")
78+
cc = Device().compute_capability
79+
if cc < (9, 0):
80+
raise CUDAError(
81+
f"thread block clusters are not supported on devices with compute capability < 9.0 (got {cc})"
82+
)
83+
self.cluster = cast_to_3_tuple("LaunchConfig.cluster", self.cluster)
7184
if self.shmem_size is None:
7285
self.shmem_size = 0
7386

74-
def _cast_to_3_tuple(self, cfg):
75-
if isinstance(cfg, int):
76-
if cfg < 1:
77-
raise ValueError
78-
return (cfg, 1, 1)
79-
elif isinstance(cfg, tuple):
80-
size = len(cfg)
81-
if size == 1:
82-
cfg = cfg[0]
83-
if cfg < 1:
84-
raise ValueError
85-
return (cfg, 1, 1)
86-
elif size == 2:
87-
if cfg[0] < 1 or cfg[1] < 1:
88-
raise ValueError
89-
return (*cfg, 1)
90-
elif size == 3:
91-
if cfg[0] < 1 or cfg[1] < 1 or cfg[2] < 1:
92-
raise ValueError
93-
return cfg
94-
else:
95-
raise ValueError
96-
9787

9888
def launch(stream, config, kernel, *kernel_args):
9989
"""Launches a :obj:`~_module.Kernel`
@@ -120,9 +110,10 @@ def launch(stream, config, kernel, *kernel_args):
120110
try:
121111
stream = Stream._init(stream)
122112
except Exception as e:
123-
raise ValueError("stream must either be a Stream object or support __cuda_stream__") from e
124-
if not isinstance(kernel, Kernel):
125-
raise ValueError
113+
raise ValueError(
114+
f"stream must either be a Stream object or support __cuda_stream__ (got {type(stream)})"
115+
) from e
116+
assert_type(kernel, Kernel)
126117
config = check_or_create_options(LaunchConfig, config, "launch config")
127118

128119
# TODO: can we ensure kernel_args is valid/safe to use here?

cuda_core/cuda/core/experimental/_linker.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616

1717
from cuda.core.experimental._device import Device
1818
from cuda.core.experimental._module import ObjectCode
19-
from cuda.core.experimental._utils import check_or_create_options, driver, handle_return, is_sequence
19+
from cuda.core.experimental._utils.clear_error_support import assert_type
20+
from cuda.core.experimental._utils.cuda_utils import check_or_create_options, driver, handle_return, is_sequence
2021

2122
# TODO: revisit this treatment for py313t builds
2223
_driver = None # populated if nvJitLink cannot be used
@@ -382,12 +383,12 @@ def __init__(self, *object_codes: ObjectCode, options: LinkerOptions = None):
382383
self._mnff = Linker._MembersNeededForFinalize(self, handle, use_nvjitlink)
383384

384385
for code in object_codes:
385-
assert isinstance(code, ObjectCode)
386+
assert_type(code, ObjectCode)
386387
self._add_code_object(code)
387388

388389
def _add_code_object(self, object_code: ObjectCode):
389390
data = object_code._module
390-
assert isinstance(data, bytes)
391+
assert_type(data, bytes)
391392
with _exception_manager(self):
392393
if _nvjitlink:
393394
_nvjitlink.add_data(

0 commit comments

Comments
 (0)