Skip to content

Commit 595e089

Browse files
committed
Return clean errors when conditional is unsupported
1 parent aa353e0 commit 595e089

2 files changed

Lines changed: 97 additions & 33 deletions

File tree

cuda_core/cuda/core/experimental/_graph.py

Lines changed: 76 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,25 @@
1212
from cuda.core.experimental._stream import Stream
1313
from cuda.core.experimental._utils.cuda_utils import (
1414
driver,
15+
get_binding_version,
1516
handle_return,
1617
)
1718

19+
_inited = False
20+
_driver_ver = None
21+
22+
23+
def _lazy_init():
24+
global _inited
25+
if _inited:
26+
return
27+
28+
global _py_major_ver, _driver_ver
29+
# binding availability depends on cuda-python version
30+
_py_major_ver, _ = get_binding_version()
31+
_driver_ver = handle_return(driver.cuDriverGetVersion())
32+
_inited = True
33+
1834

1935
@dataclass
2036
class DebugPrintOptions:
@@ -115,23 +131,34 @@ class GraphBuilder:
115131
"""
116132

117133
class _MembersNeededForFinalize:
118-
__slots__ = ("stream", "is_stream_owner", "graph", "is_conditional", "is_join_required")
134+
__slots__ = ("stream", "is_stream_owner", "graph", "conditional_graph", "is_join_required")
119135

120-
def __init__(self, graph_builder_obj, stream_obj, is_stream_owner, graph, is_conditional, is_join_required):
136+
def __init__(self, graph_builder_obj, stream_obj, is_stream_owner, conditional_graph, is_join_required):
121137
self.stream = stream_obj
122138
self.is_stream_owner = is_stream_owner
123-
self.graph = graph
124-
self.is_conditional = is_conditional
139+
self.graph = None
140+
self.conditional_graph = conditional_graph
125141
self.is_join_required = is_join_required
126142
weakref.finalize(graph_builder_obj, self.close)
127143

128144
def close(self):
129-
if self.graph and not self.is_conditional:
145+
if self.stream:
146+
if not self.is_join_required:
147+
capture_status, _, _, _, _ = handle_return(driver.cuStreamGetCaptureInfo(self.stream.handle))
148+
if capture_status != driver.CUstreamCaptureStatus.CU_STREAM_CAPTURE_STATUS_NONE:
149+
# Note how this condition only occures for the primary graph builder
150+
# This is because calling cuStreamEndCapture streams that were split off of the primary
151+
# would error out with CUDA_ERROR_STREAM_CAPTURE_UNJOINED.
152+
# Therefore, it is currently a requirement that users join all split graph builders
153+
# before a graph builder can be clearly destroyed.
154+
handle_return(driver.cuStreamEndCapture(self.stream.handle))
155+
if self.is_stream_owner:
156+
self.stream.close()
157+
self.stream = None
158+
if self.graph:
130159
handle_return(driver.cuGraphDestroy(self.graph))
131160
self.graph = None
132-
if self.is_stream_owner and self.stream:
133-
self.stream.close()
134-
self.stream = None
161+
self.conditional_graph = None
135162

136163
__slots__ = ("__weakref__", "_mnff", "_building_ended")
137164

@@ -142,14 +169,13 @@ def __init__(self):
142169
)
143170

144171
@classmethod
145-
def _init(cls, stream, is_stream_owner, graph=None, is_conditional=False, is_join_required=False):
172+
def _init(cls, stream, is_stream_owner, conditional_graph=None, is_join_required=False):
146173
self = cls.__new__(cls)
174+
_lazy_init()
147175
self._mnff = GraphBuilder._MembersNeededForFinalize(
148-
self, stream, is_stream_owner, graph, is_conditional, is_join_required
176+
self, stream, is_stream_owner, conditional_graph, is_join_required
149177
)
150178

151-
if not self._mnff.graph:
152-
self._mnff.graph = handle_return(driver.cuGraphCreate(0))
153179
self._building_ended = False
154180
return self
155181

@@ -167,16 +193,23 @@ def begin_building(self) -> GraphBuilder:
167193
"""Begins the building process."""
168194
if self._building_ended:
169195
raise RuntimeError("Cannot resume building after building has ended.")
170-
handle_return(
171-
driver.cuStreamBeginCaptureToGraph(
172-
self._mnff.stream.handle,
173-
self._mnff.graph,
174-
None, # dependencies
175-
None, # dependencyData
176-
0, # numDependencies
177-
driver.CUstreamCaptureMode.CU_STREAM_CAPTURE_MODE_GLOBAL,
196+
if self._mnff.conditional_graph:
197+
handle_return(
198+
driver.cuStreamBeginCaptureToGraph(
199+
self._mnff.stream.handle,
200+
self._mnff.conditional_graph,
201+
None, # dependencies
202+
None, # dependencyData
203+
0, # numDependencies
204+
driver.CUstreamCaptureMode.CU_STREAM_CAPTURE_MODE_GLOBAL,
205+
)
206+
)
207+
else:
208+
handle_return(
209+
driver.cuStreamBeginCapture(
210+
self._mnff.stream.handle, driver.CUstreamCaptureMode.CU_STREAM_CAPTURE_MODE_GLOBAL
211+
)
178212
)
179-
)
180213
return self
181214

182215
@property
@@ -199,7 +232,10 @@ def end_building(self) -> GraphBuilder:
199232
"""Ends the building process."""
200233
if not self.is_building:
201234
raise RuntimeError("Graph builder is not building.")
202-
self._mnff.graph = handle_return(driver.cuStreamEndCapture(self.stream.handle))
235+
if self._mnff.conditional_graph:
236+
self._mnff.conditional_graph = handle_return(driver.cuStreamEndCapture(self.stream.handle))
237+
else:
238+
self._mnff.graph = handle_return(driver.cuStreamEndCapture(self.stream.handle))
203239

204240
# TODO: Resolving https://github.com/NVIDIA/cuda-python/issues/617 would allow us to
205241
# resume the build process after the first call to end_building()
@@ -336,9 +372,7 @@ def split(self, count) -> Tuple[GraphBuilder, ...]:
336372
stream = self._mnff.stream.device.create_stream()
337373
stream.wait(event)
338374
result.append(
339-
GraphBuilder._init(
340-
stream=stream, is_stream_owner=True, graph=None, is_conditional=False, is_join_required=True
341-
)
375+
GraphBuilder._init(stream=stream, is_stream_owner=True, conditional_graph=None, is_join_required=True)
342376
)
343377
event.close()
344378
return result
@@ -407,16 +441,21 @@ def create_conditional_handle(self, default_value=None) -> int:
407441
The newly created conditional handle.
408442
409443
"""
444+
if _driver_ver < 12030:
445+
raise RuntimeError(f"Driver version {_driver_ver} does not support conditional handles")
410446
if default_value is not None:
411447
flags = driver.CU_GRAPH_COND_ASSIGN_DEFAULT
412448
else:
413449
default_value = 0
414450
flags = 0
451+
452+
status, _, graph, _, _ = handle_return(driver.cuStreamGetCaptureInfo(self._mnff.stream.handle))
453+
if status != driver.CUstreamCaptureStatus.CU_STREAM_CAPTURE_STATUS_ACTIVE:
454+
raise RuntimeError("Cannot create a conditional handle when graph is not being built")
455+
415456
return int(
416457
handle_return(
417-
driver.cuGraphConditionalHandleCreate(
418-
self._mnff.graph, self._get_conditional_context(), default_value, flags
419-
)
458+
driver.cuGraphConditionalHandleCreate(graph, self._get_conditional_context(), default_value, flags)
420459
)
421460
)
422461

@@ -447,8 +486,7 @@ def _cond_with_params(self, node_params) -> GraphBuilder:
447486
GraphBuilder._init(
448487
stream=self._mnff.stream.device.create_stream(),
449488
is_stream_owner=True,
450-
graph=node_params.conditional.phGraph_out[i],
451-
is_conditional=True,
489+
conditional_graph=node_params.conditional.phGraph_out[i],
452490
is_join_required=False,
453491
)
454492
for i in range(node_params.conditional.size)
@@ -474,6 +512,8 @@ def if_cond(self, handle: int) -> GraphBuilder:
474512
The newly created conditional graph builder.
475513
476514
"""
515+
if _driver_ver < 12030:
516+
raise RuntimeError(f"Driver version {_driver_ver} does not support conditional if")
477517
node_params = driver.CUgraphNodeParams()
478518
node_params.type = driver.CUgraphNodeType.CU_GRAPH_NODE_TYPE_CONDITIONAL
479519
node_params.conditional.handle = handle
@@ -501,6 +541,8 @@ def if_else(self, handle: int) -> Tuple[GraphBuilder, GraphBuilder]:
501541
A tuple of two new graph builders, one for the if branch and one for the else branch.
502542
503543
"""
544+
if _driver_ver < 12080:
545+
raise RuntimeError(f"Driver version {_driver_ver} does not support conditional if-else")
504546
node_params = driver.CUgraphNodeParams()
505547
node_params.type = driver.CUgraphNodeType.CU_GRAPH_NODE_TYPE_CONDITIONAL
506548
node_params.conditional.handle = handle
@@ -531,6 +573,8 @@ def switch(self, handle: int, count: int) -> Tuple[GraphBuilder, ...]:
531573
A tuple of new graph builders, one for each branch.
532574
533575
"""
576+
if _driver_ver < 12080:
577+
raise RuntimeError(f"Driver version {_driver_ver} does not support conditional switch")
534578
node_params = driver.CUgraphNodeParams()
535579
node_params.type = driver.CUgraphNodeType.CU_GRAPH_NODE_TYPE_CONDITIONAL
536580
node_params.conditional.handle = handle
@@ -558,6 +602,8 @@ def while_loop(self, handle: int) -> GraphBuilder:
558602
The newly created while loop graph builder.
559603
560604
"""
605+
if _driver_ver < 12030:
606+
raise RuntimeError(f"Driver version {_driver_ver} does not support conditional while loop")
561607
node_params = driver.CUgraphNodeParams()
562608
node_params.type = driver.CUgraphNodeType.CU_GRAPH_NODE_TYPE_CONDITIONAL
563609
node_params.conditional.handle = handle

cuda_core/tests/test_graph.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,13 @@ def test_graph_conditional_if_else(init_cuda, condition_value):
255255
launch(gb, LaunchConfig(grid=1, block=1), set_handle, handle, condition_value)
256256

257257
# Add Node B (if condition)
258-
gb_if, gb_else = gb.if_else(handle)
258+
try:
259+
gb_if, gb_else = gb.if_else(handle)
260+
except RuntimeError as e:
261+
with pytest.raises(RuntimeError, match="does not support conditional"):
262+
raise e
263+
gb.end_building()
264+
pytest.skip("Driver does not support conditional if-else")
259265

260266
## IF nodes
261267
gb_if = gb_if.begin_building()
@@ -316,7 +322,13 @@ def test_graph_conditional_switch(init_cuda, condition_value):
316322
launch(gb, LaunchConfig(grid=1, block=1), set_handle, handle, condition_value)
317323

318324
# Add Node B (while condition)
319-
gb_case = list(gb.switch(handle, 3))
325+
try:
326+
gb_case = list(gb.switch(handle, 3))
327+
except RuntimeError as e:
328+
with pytest.raises(RuntimeError, match="does not support conditional"):
329+
raise e
330+
gb.end_building()
331+
pytest.skip("Driver does not support conditional switch")
320332

321333
## Case 0
322334
gb_case[0].begin_building()
@@ -475,7 +487,13 @@ def build_graph(condition_value):
475487
handle = gb.create_conditional_handle(default_value=condition_value)
476488

477489
# Add Node B (while condition)
478-
gb_case = list(gb.switch(handle, 3))
490+
try:
491+
gb_case = list(gb.switch(handle, 3))
492+
except RuntimeError as e:
493+
with pytest.raises(RuntimeError, match="does not support conditional"):
494+
raise e
495+
gb.end_building()
496+
pytest.skip("Driver does not support conditional switch")
479497

480498
## Case 0
481499
gb_case[0].begin_building()

0 commit comments

Comments
 (0)