Coverage for cuda/core/graph/_graph_builder.pyx: 88.65%
458 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-29 01:38 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-29 01:38 +0000
1# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2#
3# SPDX-License-Identifier: Apache-2.0
5from dataclasses import dataclass
6from typing import TYPE_CHECKING
8from libc.stdint cimport intptr_t
10from cuda.bindings cimport cydriver
12from cuda.core.graph._graph_definition cimport GraphCondition, GraphDefinition
13from cuda.core.graph._host_callback cimport _resolve_host_callback
14from cuda.core._resource_handles cimport (
15 GraphHandle,
16 OpaqueHandle,
17 PreparedAttachment,
18 as_cu, as_py,
19 create_child_graph_handle, create_graph_exec_handle, create_graph_handle,
20 graph_clone_attachments,
21 graph_commit_attachment,
22 graph_prepare_attachment,
23 invalidate_child_graph_state,
24 retry_deferred_cleanup,
25)
26from cuda.core._stream cimport Stream
27from cuda.core._utils.cuda_utils cimport HANDLE_RETURN
28from cuda.core._utils.version cimport cy_binding_version, cy_driver_version
30from cuda.core._utils.cuda_utils import (
31 CUDAError,
32 driver,
33 handle_return,
34)
36if TYPE_CHECKING:
37 from cuda.core.graph._graph_definition import GraphDefinition
39__all__ = ['Graph', 'GraphBuilder', 'GraphCompleteOptions', 'GraphDebugPrintOptions']
42@dataclass
43class GraphDebugPrintOptions:
44 """Options for debug_dot_print().
46 Attributes
47 ----------
48 verbose : bool
49 Output all debug data as if every debug flag is enabled (Default to False)
50 runtime_types : bool
51 Use CUDA Runtime structures for output (Default to False)
52 kernel_node_params : bool
53 Adds kernel parameter values to output (Default to False)
54 memcpy_node_params : bool
55 Adds memcpy parameter values to output (Default to False)
56 memset_node_params : bool
57 Adds memset parameter values to output (Default to False)
58 host_node_params : bool
59 Adds host parameter values to output (Default to False)
60 event_node_params : bool
61 Adds event parameter values to output (Default to False)
62 ext_semas_signal_node_params : bool
63 Adds external semaphore signal parameter values to output (Default to False)
64 ext_semas_wait_node_params : bool
65 Adds external semaphore wait parameter values to output (Default to False)
66 kernel_node_attributes : bool
67 Adds kernel node attributes to output (Default to False)
68 handles : bool
69 Adds node handles and every kernel function handle to output (Default to False)
70 mem_alloc_node_params : bool
71 Adds memory alloc parameter values to output (Default to False)
72 mem_free_node_params : bool
73 Adds memory free parameter values to output (Default to False)
74 batch_mem_op_node_params : bool
75 Adds batch mem op parameter values to output (Default to False)
76 extra_topo_info : bool
77 Adds edge numbering information (Default to False)
78 conditional_node_params : bool
79 Adds conditional node parameter values to output (Default to False)
81 """
83 verbose: bool = False
84 runtime_types: bool = False
85 kernel_node_params: bool = False
86 memcpy_node_params: bool = False
87 memset_node_params: bool = False
88 host_node_params: bool = False
89 event_node_params: bool = False
90 ext_semas_signal_node_params: bool = False
91 ext_semas_wait_node_params: bool = False
92 kernel_node_attributes: bool = False
93 handles: bool = False
94 mem_alloc_node_params: bool = False
95 mem_free_node_params: bool = False
96 batch_mem_op_node_params: bool = False
97 extra_topo_info: bool = False
98 conditional_node_params: bool = False
100 def _to_flags(self) -> int:
101 """Convert options to CUDA driver API flags (internal use)."""
102 flags = 0 2Dba
103 if self.verbose: 2Dba
104 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_VERBOSE 2Dba
105 if self.runtime_types: 2Dba
106 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_RUNTIME_TYPES 1a
107 if self.kernel_node_params: 2Dba
108 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_KERNEL_NODE_PARAMS 1a
109 if self.memcpy_node_params: 2Dba
110 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_MEMCPY_NODE_PARAMS 1a
111 if self.memset_node_params: 2Dba
112 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_MEMSET_NODE_PARAMS 1a
113 if self.host_node_params: 2Dba
114 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_HOST_NODE_PARAMS 1a
115 if self.event_node_params: 29 Dba
116 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_EVENT_NODE_PARAMS 1a
117 if self.ext_semas_signal_node_params: 2Dba
118 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_EXT_SEMAS_SIGNAL_NODE_PARAMS 1a
119 if self.ext_semas_wait_node_params: 2Dba
120 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_EXT_SEMAS_WAIT_NODE_PARAMS 1a
121 if self.kernel_node_attributes: 2Dba
122 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_KERNEL_NODE_ATTRIBUTES 1a
123 if self.handles: 2Dba
124 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_HANDLES 2Dba
125 if self.mem_alloc_node_params: 2Dba
126 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_MEM_ALLOC_NODE_PARAMS 1a
127 if self.mem_free_node_params: 2Dba
128 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_MEM_FREE_NODE_PARAMS 1a
129 if self.batch_mem_op_node_params: 2Dba
130 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_BATCH_MEM_OP_NODE_PARAMS 1a
131 if self.extra_topo_info: 2Dba
132 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_EXTRA_TOPO_INFO 1a
133 if self.conditional_node_params: 29 Dba
134 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_CONDITIONAL_NODE_PARAMS 1a
135 return flags 2Dba
138@dataclass
139class GraphCompleteOptions:
140 """Options for graph instantiation.
142 Attributes
143 ----------
144 auto_free_on_launch : bool, optional
145 Automatically free memory allocated in a graph before relaunching. (Default to False)
146 upload_stream : Stream, optional
147 Stream to use to automatically upload the graph after completion. (Default to None)
148 device_launch : bool, optional
149 Configure the graph to be launchable from the device. This flag can only
150 be used on platforms which support unified addressing. This flag cannot be
151 used in conjunction with auto_free_on_launch. (Default to False)
152 use_node_priority : bool, optional
153 Run the graph using the per-node priority attributes rather than the
154 priority of the stream it is launched into. (Default to False)
156 """
158 auto_free_on_launch: bool = False
159 upload_stream: Stream | None = None
160 device_launch: bool = False
161 use_node_priority: bool = False
164def _instantiate_graph(h_graph, options: GraphCompleteOptions | None = None) -> Graph:
165 params = driver.CUDA_GRAPH_INSTANTIATE_PARAMS() 2O J K L M + I Y x y X E F ! Z 0 W b c d e f g h i j k l m n o p q z r s t u v A B C D ob| pb} kbqbrbsbtb~ ubablbbbcbvbdbmbebwbfbnbxb1b2byb3b4b5bEbFbGbzbAb6bHbCbIbJbKbLbMbNbObPbQbRbSbBbTbUbVbWbXbYbZb: , . ; - / # 1 5 $ 2 6 S P Q % 3 7 ' 4 8 gbhbw R ibjbV ( ) T *
166 if options: 2O J K L M + I Y x y X E F ! Z 0 W b c d e f g h i j k l m n o p q z r s t u v A B C D ob| pb} kbqbrbsbtb~ ubablbbbcbvbdbmbebwbfbnbxb1b2byb3b4b5bEbFbGbzbAb6bHbCbIbJbKbLbMbNbObPbQbRbSbBbTbUbVbWbXbYbZb: , . ; - / # 1 5 $ 2 6 S P Q % 3 7 ' 4 8 gbhbw R ibjbV ( ) T *
167 flags = 0 2| } kb~ ablbbbcbdbmbebfbnbxbybS P Q T
168 if options.auto_free_on_launch: 2| } kb~ ablbbbcbdbmbebfbnbxbybS P Q T
169 flags |= driver.CUgraphInstantiate_flags.CUDA_GRAPH_INSTANTIATE_FLAG_AUTO_FREE_ON_LAUNCH 2| ~ cbebxbybS P Q T
170 if options.upload_stream: 2| } kb~ ablbbbcbdbmbebfbnbxbybS P Q T
171 flags |= driver.CUgraphInstantiate_flags.CUDA_GRAPH_INSTANTIATE_FLAG_UPLOAD 2kblbmbnbT
172 params.hUploadStream = options.upload_stream.handle 2kblbmbnbT
173 if options.device_launch: 2| } kb~ ablbbbcbdbmbebfbnbxbybS P Q T
174 flags |= driver.CUgraphInstantiate_flags.CUDA_GRAPH_INSTANTIATE_FLAG_DEVICE_LAUNCH 2bbT
175 if options.use_node_priority: 2| } kb~ ablbbbcbdbmbebfbnbxbybS P Q T
176 flags |= driver.CUgraphInstantiate_flags.CUDA_GRAPH_INSTANTIATE_FLAG_USE_NODE_PRIORITY 2} abdbfbxbybT
177 params.flags = flags 2| } kb~ ablbbbcbdbmbebfbnbxbybS P Q T
179 py_exec = handle_return(driver.cuGraphInstantiateWithParams(h_graph, params)) 2O J K L M + I Y x y X E F ! Z 0 W b c d e f g h i j k l m n o p q z r s t u v A B C D ob| pb} kbqbrbsbtb~ ubablbbbcbvbdbmbebwbfbnbxb1b2byb3b4b5bEbFbGbzbAb6bHbCbIbJbKbLbMbNbObPbQbRbSbBbTbUbVbWbXbYbZb: , . ; - / # 1 5 $ 2 6 S P Q % 3 7 ' 4 8 gbhbw R ibjbV ( ) T *
180 # Check result_out before wrapping the exec: on a non-SUCCESS result the exec
181 # may be invalid, and Graph._init's RAII deleter would call cuGraphExecDestroy
182 # on it during the exception unwind below.
183 if params.result_out == driver.CUgraphInstantiateResult.CUDA_GRAPH_INSTANTIATE_ERROR: 2O J K L M + I Y x y X E F ! Z 0 W b c d e f g h i j k l m n o p q z r s t u v A B C D ob| pb} kbqbrbsbtb~ ubablbbbcbvbdbmbebwbfbnbxb1b2byb3b4b5bEbFbGbzbAb6bHbCbIbJbKbLbMbNbObPbQbRbSbBbTbUbVbWbXbYbZb: , . ; - / # 1 5 $ 2 6 S P Q % 3 7 ' 4 8 gbhbw R ibjbV ( ) T *
184 raise RuntimeError(
185 "Instantiation failed for an unexpected reason which is described in the return value of the function."
186 )
187 elif params.result_out == driver.CUgraphInstantiateResult.CUDA_GRAPH_INSTANTIATE_INVALID_STRUCTURE: 2O J K L M + I Y x y X E F ! Z 0 W b c d e f g h i j k l m n o p q z r s t u v A B C D ob| pb} kbqbrbsbtb~ ubablbbbcbvbdbmbebwbfbnbxb1b2byb3b4b5bEbFbGbzbAb6bHbCbIbJbKbLbMbNbObPbQbRbSbBbTbUbVbWbXbYbZb: , . ; - / # 1 5 $ 2 6 S P Q % 3 7 ' 4 8 gbhbw R ibjbV ( ) T *
188 raise RuntimeError("Instantiation failed due to invalid structure, such as cycles.")
189 elif params.result_out == driver.CUgraphInstantiateResult.CUDA_GRAPH_INSTANTIATE_NODE_OPERATION_NOT_SUPPORTED: 2O J K L M + I Y x y X E F ! Z 0 W b c d e f g h i j k l m n o p q z r s t u v A B C D ob| pb} kbqbrbsbtb~ ubablbbbcbvbdbmbebwbfbnbxb1b2byb3b4b5bEbFbGbzbAb6bHbCbIbJbKbLbMbNbObPbQbRbSbBbTbUbVbWbXbYbZb: , . ; - / # 1 5 $ 2 6 S P Q % 3 7 ' 4 8 gbhbw R ibjbV ( ) T *
190 raise RuntimeError(
191 "Instantiation for device launch failed because the graph contained an unsupported operation."
192 )
193 elif params.result_out == driver.CUgraphInstantiateResult.CUDA_GRAPH_INSTANTIATE_MULTIPLE_CTXS_NOT_SUPPORTED: 2O J K L M + I Y x y X E F ! Z 0 W b c d e f g h i j k l m n o p q z r s t u v A B C D ob| pb} kbqbrbsbtb~ ubablbbbcbvbdbmbebwbfbnbxb1b2byb3b4b5bEbFbGbzbAb6bHbCbIbJbKbLbMbNbObPbQbRbSbBbTbUbVbWbXbYbZb: , . ; - / # 1 5 $ 2 6 S P Q % 3 7 ' 4 8 gbhbw R ibjbV ( ) T *
194 raise RuntimeError("Instantiation for device launch failed due to the nodes belonging to different contexts.")
195 elif ( 2O J K L M + I Y x y X E F ! Z 0 W b c d e f g h i j k l m n o p q z r s t u v A B C D ob| pb} kbqbrbsbtb~ ubablbbbcbvbdbmbebwbfbnbxb1b2byb3b4b5bEbFbGbzbAb6bHbCbIbJbKbLbMbNbObPbQbRbSbBbTbUbVbWbXbYbZb: , . ; - / # 1 5 $ 2 6 S P Q % 3 7 ' 4 8 gbhbw R ibjbV ( ) T *
196 cy_binding_version() >= (12, 8, 0) 2O J K L M + I Y x y X E F ! Z 0 W b c d e f g h i j k l m n o p q z r s t u v A B C D ob| pb} kbqbrbsbtb~ ubablbbbcbvbdbmbebwbfbnbxb1b2byb3b4b5bEbFbGbzbAb6bHbCbIbJbKbLbMbNbObPbQbRbSbBbTbUbVbWbXbYbZb: , . ; - / # 1 5 $ 2 6 S P Q % 3 7 ' 4 8 gbhbw R ibjbV ( ) T *
197 and params.result_out == driver.CUgraphInstantiateResult.CUDA_GRAPH_INSTANTIATE_CONDITIONAL_HANDLE_UNUSED 2O J K L M + I Y x y X E F ! Z 0 W b c d e f g h i j k l m n o p q z r s t u v A B C D ob| pb} kbqbrbsbtb~ ubablbbbcbvbdbmbebwbfbnbxb1b2byb3b4b5bEbFbGbzbAb6bHbCbIbJbKbLbMbNbObPbQbRbSbBbTbUbVbWbXbYbZb: , . ; - / # 1 5 $ 2 6 S P Q % 3 7 ' 4 8 gbhbw R ibjbV ( ) T *
198 ):
199 raise RuntimeError("One or more conditional handles are not associated with conditional builders.")
200 elif params.result_out != driver.CUgraphInstantiateResult.CUDA_GRAPH_INSTANTIATE_SUCCESS: 2O J K L M + I Y x y X E F ! Z 0 W b c d e f g h i j k l m n o p q z r s t u v A B C D ob| pb} kbqbrbsbtb~ ubablbbbcbvbdbmbebwbfbnbxb1b2byb3b4b5bEbFbGbzbAb6bHbCbIbJbKbLbMbNbObPbQbRbSbBbTbUbVbWbXbYbZb: , . ; - / # 1 5 $ 2 6 S P Q % 3 7 ' 4 8 gbhbw R ibjbV ( ) T *
201 raise RuntimeError(f"Graph instantiation failed with unexpected error code: {params.result_out}")
203 cdef cydriver.CUgraphExec c_exec = <cydriver.CUgraphExec><intptr_t>int(py_exec) 2O J K L M + I Y x y X E F ! Z 0 W b c d e f g h i j k l m n o p q z r s t u v A B C D ob| pb} kbqbrbsbtb~ ubablbbbcbvbdbmbebwbfbnbxb1b2byb3b4b5bEbFbGbzbAb6bHbCbIbJbKbLbMbNbObPbQbRbSbBbTbUbVbWbXbYbZb: , . ; - / # 1 5 $ 2 6 S P Q % 3 7 ' 4 8 gbhbw R ibjbV ( ) T *
204 return Graph._init(c_exec) 2O J K L M + I Y x y X E F ! Z 0 W b c d e f g h i j k l m n o p q z r s t u v A B C D ob| pb} kbqbrbsbtb~ ubablbbbcbvbdbmbebwbfbnbxb1b2byb3b4b5bEbFbGbzbAb6bHbCbIbJbKbLbMbNbObPbQbRbSbBbTbUbVbWbXbYbZb: , . ; - / # 1 5 $ 2 6 S P Q % 3 7 ' 4 8 gbhbw R ibjbV ( ) T *
207# Distinguishes the three kinds of GraphBuilder, which differ in how they
208# begin/end stream capture and whether they own the resulting CUgraph.
209# Each kind progresses through _CaptureState as follows:
210#
211# PRIMARY: NOT_STARTED -> CAPTURING -> ENDED
212# FORKED: CAPTURING (never transitions; joined and closed)
213# CONDITIONAL_BODY: NOT_STARTED -> CAPTURING -> ENDED
214#
215cdef enum _BuilderKind:
216 # PRIMARY: The top-level builder created by Device or Stream. Owns the
217 # captured CUgraph via an owning GraphHandle. Progresses through all three
218 # capture states; responsible for ending capture if destroyed early.
219 PRIMARY = 0
220 # FORKED: Created by split(). Captures on a private stream forked from the
221 # primary. Starts in CAPTURING state and never transitions; the user joins
222 # it back to the primary via join(), which closes the builder. Must NOT
223 # call cuStreamEndCapture (the driver requires all forked streams to be
224 # joined first).
225 FORKED = 1
226 # CONDITIONAL_BODY: Created by if_then/if_else/switch/while_loop. Captures
227 # into a non-owned body graph via cuStreamBeginCaptureToGraph. The body
228 # graph's lifetime is tied to a parent graph. Progresses through all three
229 # capture states like PRIMARY.
230 CONDITIONAL_BODY = 2
233# Tracks the capture lifecycle of a GraphBuilder.
234cdef enum _CaptureState:
235 CAPTURE_NOT_STARTED = 0
236 CAPTURING = 1
237 CAPTURE_ENDED = 2 # Finished, valid handle
238 CLOSED = 3 # No valid handle
241cdef class GraphBuilder:
242 """A graph under construction by stream capture.
244 A graph groups a set of CUDA kernels and other CUDA operations together and executes
245 them with a specified dependency tree. It speeds up the workflow by combining the
246 driver activities associated with CUDA kernel launches and CUDA API calls.
248 Directly creating a :obj:`~graph.GraphBuilder` is not supported due
249 to ambiguity. New graph builders should instead be created through a
250 :obj:`~_device.Device`, or a :obj:`~_stream.stream` object.
252 .. note::
254 Operations recorded during capture reference your memory but do not
255 take ownership of it. As with ordinary stream work, you must keep the
256 operands alive for as long as the completed graph may execute -- for
257 example, the :obj:`~_memory.Buffer` objects passed to :func:`~launch`
258 or :meth:`~_memory.Buffer.copy_to`. Host callbacks added with
259 :meth:`callback` are the exception: the callable (and any copied
260 ``user_data``) are retained for the graph's lifetime. This differs from
261 building a graph explicitly with :class:`~graph.GraphDefinition`, which
262 retains the operands it is given.
264 """
266 def __init__(self):
267 raise NotImplementedError(
268 "directly creating a GraphBuilder object can be ambiguous. Please either "
269 "call Device.create_graph_builder() or stream.create_graph_builder()"
270 )
272 def __dealloc__(self):
273 GB_end_capture_if_needed(self, False) 2O ^ J K L M + I Y U x G y ? 0b[ N @ X = ` E { _ F ! 7bZ 0 W b c d e f g h i j k l m n o p q z r s t u v A B C D H : , . ; - / # 1 5 $ 2 6 S P Q % 3 7 ' 4 8 w R V ( ) ] T a *
275 @staticmethod
276 def _init(Stream stream):
277 cdef GraphBuilder self = GraphBuilder.__new__(GraphBuilder) 2O ^ J K L M + I Y U x G y ? 0b[ N @ X = ` E { _ F ! 7bZ 0 W b c d e f g h i j k l m n o p q z r s t u v A B C D H : , . ; - / # 1 5 $ 2 6 S P Q % 3 7 ' 4 8 w R V ( ) ] T a *
278 # _h_graph set by begin_building
279 self._h_stream = stream._h_stream 2O ^ J K L M + I Y U x G y ? 0b[ N @ X = ` E { _ F ! 7bZ 0 W b c d e f g h i j k l m n o p q z r s t u v A B C D H : , . ; - / # 1 5 $ 2 6 S P Q % 3 7 ' 4 8 w R V ( ) ] T a *
280 self._kind = PRIMARY 2O ^ J K L M + I Y U x G y ? 0b[ N @ X = ` E { _ F ! 7bZ 0 W b c d e f g h i j k l m n o p q z r s t u v A B C D H : , . ; - / # 1 5 $ 2 6 S P Q % 3 7 ' 4 8 w R V ( ) ] T a *
281 self._state = CAPTURE_NOT_STARTED 2O ^ J K L M + I Y U x G y ? 0b[ N @ X = ` E { _ F ! 7bZ 0 W b c d e f g h i j k l m n o p q z r s t u v A B C D H : , . ; - / # 1 5 $ 2 6 S P Q % 3 7 ' 4 8 w R V ( ) ] T a *
282 self._stream = stream 2O ^ J K L M + I Y U x G y ? 0b[ N @ X = ` E { _ F ! 7bZ 0 W b c d e f g h i j k l m n o p q z r s t u v A B C D H : , . ; - / # 1 5 $ 2 6 S P Q % 3 7 ' 4 8 w R V ( ) ] T a *
283 return self 2O ^ J K L M + I Y U x G y ? 0b[ N @ X = ` E { _ F ! 7bZ 0 W b c d e f g h i j k l m n o p q z r s t u v A B C D H : , . ; - / # 1 5 $ 2 6 S P Q % 3 7 ' 4 8 w R V ( ) ] T a *
285 def close(self):
286 """Destroy the graph builder."""
287 GB_end_capture_if_needed(self, True) 1YU?N=EF0Wbcdefghijklmnopqrstuva
288 self._h_graph.reset() 1YU?N=EF0Wbcdefghijklmnopqrstuva
289 self._h_stream.reset() 1YU?N=EF0Wbcdefghijklmnopqrstuva
290 retry_deferred_cleanup() 1YU?N=EF0Wbcdefghijklmnopqrstuva
291 self._state = CLOSED 1YU?N=EF0Wbcdefghijklmnopqrstuva
292 self._stream = None 1YU?N=EF0Wbcdefghijklmnopqrstuva
294 @property
295 def stream(self) -> Stream:
296 """Returns the stream associated with the graph builder."""
297 return self._stream 1IYUxy?N@X=E{F!Z0WbcdefghijklmnopqzrstuvABCD:,.;-/#15$26SPQ%37'48wRV()]Ta*
299 @property
300 def is_join_required(self) -> bool:
301 """Returns True if this graph builder must be joined before building is ended."""
302 return self._kind == FORKED 1UNEFbcdefghijklmnopqrstuva
304 @property
305 def graph_definition(self) -> GraphDefinition:
306 """The captured graph as an explicit :class:`~graph.GraphDefinition`.
308 .. versionadded:: 1.1.0
310 The returned :class:`~graph.GraphDefinition` is a view of the same
311 graph this builder is producing: nodes added through it appear in
312 subsequent :meth:`complete` and :meth:`debug_dot_print` calls, and
313 the view stays valid even after the builder is closed.
315 This lets you mix the capture and explicit APIs on a single graph,
316 for example to inspect what was captured, augment it with extra
317 nodes, or build a conditional body entirely with the explicit API.
319 Availability:
321 - **Primary builders** (created by :meth:`Device.create_graph_builder`
322 or :meth:`Stream.create_graph_builder`): only after
323 :meth:`end_building`.
325 - **Conditional-body builders** (returned by :meth:`if_then`,
326 :meth:`if_else`, :meth:`while_loop`, :meth:`switch`): both before
327 :meth:`begin_building` and after :meth:`end_building`. The body
328 graph already exists when the conditional is created, so you may
329 populate it through this view without ever calling
330 :meth:`begin_building` on the body builder.
332 - **Forked builders** (returned by :meth:`split`): never. Forked
333 builders share the primary builder's graph; access it through the
334 primary instead.
336 Returns
337 -------
338 GraphDefinition
339 A view of the graph being built.
341 Raises
342 ------
343 RuntimeError
344 If the builder is closed, forked, currently building, or (for
345 primary builders) has not started building yet. A
346 :class:`~graph.GraphDefinition` obtained before :meth:`close`
347 keeps working; only fresh access through this property is
348 rejected once the builder is closed.
349 """
350 GB_check_open(self) 2x G y ? 0b[ N @ X = H
351 if self._kind == FORKED: 2x G y 0b[ N @ X = H
352 raise RuntimeError( 1N
353 "graph_definition is unavailable on forked graph builders; "
354 "access it through the primary builder instead."
355 )
356 elif self._state == CAPTURING: 2x G y 0b[ @ X = H
357 raise RuntimeError( 1G[
358 "graph_definition is unavailable while capture is in "
359 "progress; call end_building() first."
360 )
361 elif self._kind == PRIMARY: 2x y 0b@ X = H
362 if self._state == CAPTURE_NOT_STARTED: 20b@ X = H
363 raise RuntimeError( 20b
364 "graph_definition is unavailable before begin_building() on "
365 "a primary builder; no graph has been created yet."
366 )
367 return GraphDefinition._from_handle(self._h_graph) 1xy@X=H
369 def begin_building(self, mode: str | None = "relaxed") -> GraphBuilder:
370 """Begins the building process.
372 Build `mode` for controlling interaction with other API calls must be one of the following:
374 - `global` : Prohibit potentially unsafe operations across all streams in the process.
375 - `thread_local` : Prohibit potentially unsafe operations in streams created by the current thread.
376 - `relaxed` : The local thread is not prohibited from potentially unsafe operations.
378 Parameters
379 ----------
380 mode : str, optional
381 Build mode to control the interaction with other API calls that are porentially unsafe.
382 Default set to use relaxed.
384 """
385 GB_check_open(self) 1O^JKLM+IYUxGy?[N@X=`E{_F!Z0WbcdefghijklmnopqzrstuvABCDH:,.;-/#15$26SPQ%37'48wRV()]Ta*
386 if self._state != CAPTURE_NOT_STARTED: 1O^JKLM+IYUxGy?[N@X=`E{_F!Z0WbcdefghijklmnopqzrstuvABCDH:,.;-/#15$26SPQ%37'48wRV()]Ta*
387 if self._state == CAPTURING: 1^!
388 raise RuntimeError("Graph builder is already building.") 1^
389 else:
390 raise RuntimeError("Cannot resume building after building has ended.") 1!
391 cdef cydriver.CUstreamCaptureMode c_mode
392 if mode == "global": 1O^JKLM+IYUxGy?[N@X=`E{_F!Z0WbcdefghijklmnopqzrstuvABCDH:,.;-/#15$26SPQ%37'48wRV()]Ta*
393 c_mode = cydriver.CU_STREAM_CAPTURE_MODE_GLOBAL 1:;#$S%']
394 elif mode == "thread_local": 1O^JKLM+IYUxGy?[N@X=`E{_F!Z0WbcdefghijklmnopqzrstuvABCDH,.-/1526PQ3748wRV()]Ta*
395 c_mode = cydriver.CU_STREAM_CAPTURE_MODE_THREAD_LOCAL 1./56Q78]
396 elif mode == "relaxed": 1O^JKLM+IYUxGy?[N@X=`E{_F!Z0WbcdefghijklmnopqzrstuvABCDH,-12P34wRV()]Ta*
397 c_mode = cydriver.CU_STREAM_CAPTURE_MODE_RELAXED 1O^JKLM+IYUxGy?[N@X=`E{_F!Z0WbcdefghijklmnopqzrstuvABCDH,-12P34wRV()]Ta*
398 else:
399 raise ValueError(f"Unsupported build mode: {mode}") 1]
401 cdef cydriver.CUstream c_stream = as_cu(self._h_stream) 1O^JKLM+IYUxGy?[N@X=`E{_F!Z0WbcdefghijklmnopqzrstuvABCDH:,.;-/#15$26SPQ%37'48wRV()]Ta*
402 cdef cydriver.CUgraph c_graph
403 cdef cydriver.CUstreamCaptureStatus c_status
404 if self._kind == CONDITIONAL_BODY: 1O^JKLM+IYUxGy?[N@X=`E{_F!Z0WbcdefghijklmnopqzrstuvABCDH:,.;-/#15$26SPQ%37'48wRV()]Ta*
405 c_graph = as_cu(self._h_graph) 1xGbcdefghijklmnopqzrstuvABCDwa
406 with nogil: 1xGbcdefghijklmnopqzrstuvABCDwa
407 HANDLE_RETURN(cydriver.cuStreamBeginCaptureToGraph( 1xGbcdefghijklmnopqzrstuvABCDwa
408 c_stream, c_graph, NULL, NULL, 0, c_mode))
409 self._state = CAPTURING 1xGbcdefghijklmnopqzrstuvABCDwa
410 else:
411 with nogil: 1O^JKLM+IYUxGy?[N@X=`E{_F!Z0WbcdefghijklmnopqzrstuvABCDH:,.;-/#15$26SPQ%37'48wRV()]Ta*
412 HANDLE_RETURN(cydriver.cuStreamBeginCapture(c_stream, c_mode)) 1O^JKLM+IYUxGy?[N@X=`E{_F!Z0WbcdefghijklmnopqzrstuvABCDH:,.;-/#15$26SPQ%37'48wRV()]Ta*
413 # Capture is active now; set CAPTURING before the calls below so a
414 # failure in _get_capture_info/create_graph_handle still lets
415 # cleanup end the capture rather than leaving the stream poisoned.
416 self._state = CAPTURING 1O^JKLM+IYUxGy?[N@X=`E{_F!Z0WbcdefghijklmnopqzrstuvABCDH:,.;-/#15$26SPQ%37'48wRV()]Ta*
417 with nogil: 1O^JKLM+IYUxGy?[N@X=`E{_F!Z0WbcdefghijklmnopqzrstuvABCDH:,.;-/#15$26SPQ%37'48wRV()]Ta*
418 # The driver rejects a NULL captureStatus_out, so pass a
419 # stack-local even though we only want the graph handle.
420 _get_capture_info(c_stream, &c_status, &c_graph) 1O^JKLM+IYUxGy?[N@X=`E{_F!Z0WbcdefghijklmnopqzrstuvABCDH:,.;-/#15$26SPQ%37'48wRV()]Ta*
421 self._h_graph = create_graph_handle(c_graph) 1O^JKLM+IYUxGy?[N@X=`E{_F!Z0WbcdefghijklmnopqzrstuvABCDH:,.;-/#15$26SPQ%37'48wRV()]Ta*
422 return self 1O^JKLM+IYUxGy?[N@X=`E{_F!Z0WbcdefghijklmnopqzrstuvABCDH:,.;-/#15$26SPQ%37'48wRV()]Ta*
424 @property
425 def is_building(self) -> bool:
426 """Returns True if the graph builder is currently building."""
427 GB_check_open(self) 1O^JKLM+IYxGy?[N@X=`E_F!Z0WbcdefghijklmnopqzrstuvABCDH:,.;-/#15$26SPQ%37'48wRV()]Ta*
428 cdef cydriver.CUstream c_stream = as_cu(self._h_stream) 1O^JKLM+IYxGy?[N@X=`E_F!Z0WbcdefghijklmnopqzrstuvABCDH:,.;-/#15$26SPQ%37'48wRV()]Ta*
429 cdef cydriver.CUstreamCaptureStatus status
430 with nogil: 1O^JKLM+IYxGy?[N@X=`E_F!Z0WbcdefghijklmnopqzrstuvABCDH:,.;-/#15$26SPQ%37'48wRV()]Ta*
431 _get_capture_info(c_stream, &status, NULL) 1O^JKLM+IYxGy?[N@X=`E_F!Z0WbcdefghijklmnopqzrstuvABCDH:,.;-/#15$26SPQ%37'48wRV()]Ta*
432 if status == cydriver.CU_STREAM_CAPTURE_STATUS_NONE: 1O^JKLM+IYxGy?[N@X=`E_F!Z0WbcdefghijklmnopqzrstuvABCDH:,.;-/#15$26SPQ%37'48wRV()]Ta*
433 return False 1_
434 elif status == cydriver.CU_STREAM_CAPTURE_STATUS_ACTIVE:
435 return True 1O^JKLM+IYxGy?[N@X=`E_F!Z0WbcdefghijklmnopqzrstuvABCDH:,.;-/#15$26SPQ%37'48wRV()]Ta*
436 elif status == cydriver.CU_STREAM_CAPTURE_STATUS_INVALIDATED:
437 raise RuntimeError(
438 "Build process encountered an error and has been invalidated. Build process must now be ended."
439 )
440 else:
441 raise NotImplementedError(f"Unsupported capture status type received: {status}")
443 def end_building(self) -> GraphBuilder:
444 """Ends the building process."""
445 GB_check_open(self) 1O^JKLM+IYxGy?[N@X=`E_F!Z0WbcdefghijklmnopqzrstuvABCDH:,.;-/#15$26SPQ%37'48wRV()]Ta*
446 if not self.is_building: 1O^JKLM+IYxGy?[N@X=`E_F!Z0WbcdefghijklmnopqzrstuvABCDH:,.;-/#15$26SPQ%37'48wRV()]Ta*
447 raise RuntimeError("Graph builder is not building.")
448 cdef cydriver.CUstream c_stream = as_cu(self._h_stream) 1O^JKLM+IYxGy?[N@X=`E_F!Z0WbcdefghijklmnopqzrstuvABCDH:,.;-/#15$26SPQ%37'48wRV()]Ta*
449 cdef cydriver.CUgraph c_graph
450 with nogil: 1O^JKLM+IYxGy?[N@X=`E_F!Z0WbcdefghijklmnopqzrstuvABCDH:,.;-/#15$26SPQ%37'48wRV()]Ta*
451 HANDLE_RETURN(cydriver.cuStreamEndCapture(c_stream, &c_graph)) 1O^JKLM+IYxGy?[N@X=`E_F!Z0WbcdefghijklmnopqzrstuvABCDH:,.;-/#15$26SPQ%37'48wRV()]Ta*
453 # TODO: Resolving https://github.com/NVIDIA/cuda-python/issues/617 would allow us to
454 # resume the build process after the first call to end_building()
455 self._state = CAPTURE_ENDED 1O^JKLM+IYxGy?[N@X=`E_F!Z0WbcdefghijklmnopqzrstuvABCDH:,.;-/#15$26SPQ%37'48wRV()]Ta*
456 return self 1O^JKLM+IYxGy?[N@X=`E_F!Z0WbcdefghijklmnopqzrstuvABCDH:,.;-/#15$26SPQ%37'48wRV()]Ta*
458 def complete(self, options: GraphCompleteOptions | None = None) -> Graph:
459 """Completes the graph builder and returns the built :obj:`~graph.Graph` object.
461 Parameters
462 ----------
463 options : :obj:`~graph.GraphCompleteOptions`, optional
464 Customizable dataclass for the graph builder completion options.
466 Returns
467 -------
468 graph : :obj:`~graph.Graph`
469 The newly built graph.
471 """
472 GB_check_open(self) 1OJKLM+IYUxyXEF!Z0WbcdefghijklmnopqzrstuvABCD:,.;-/#15$26SPQ%37'48wRV()T*
473 if self._state != CAPTURE_ENDED: 1OJKLM+IYxyXEF!Z0WbcdefghijklmnopqzrstuvABCD:,.;-/#15$26SPQ%37'48wRV()T*
474 raise RuntimeError("Graph has not finished building.") 1+
476 return _instantiate_graph(as_py(self._h_graph), options) 1OJKLM+IYxyXEF!Z0WbcdefghijklmnopqzrstuvABCD:,.;-/#15$26SPQ%37'48wRV()T*
478 def debug_dot_print(self, path: str, options: GraphDebugPrintOptions | None = None) -> None:
479 """Generates a DOT debug file for the graph builder.
481 Parameters
482 ----------
483 path : str
484 File path to use for writting debug DOT output
485 options : :obj:`~graph.GraphDebugPrintOptions`, optional
486 Customizable dataclass for the debug print options.
488 """
489 GB_check_open(self) 1a
490 if self._state != CAPTURE_ENDED: 1a
491 raise RuntimeError("Graph has not finished building.")
492 cdef unsigned int c_flags = options._to_flags() if options else 0 1a
493 cdef cydriver.CUgraph c_graph = as_cu(self._h_graph) 1a
494 cdef bytes b_path = path.encode('utf-8') 1a
495 cdef const char* c_path = b_path 1a
496 with nogil: 1a
497 HANDLE_RETURN(cydriver.cuGraphDebugDotPrint(c_graph, c_path, c_flags)) 1a
499 def split(self, count: int) -> tuple[GraphBuilder, ...]:
500 """Splits the original graph builder into multiple graph builders.
502 The new builders inherit work dependencies from the original builder.
503 The original builder is reused for the split and is returned first in the tuple.
505 Parameters
506 ----------
507 count : int
508 The number of graph builders to split the graph builder into.
510 Returns
511 -------
512 graph_builders : tuple[:obj:`~graph.GraphBuilder`, ...]
513 A tuple of split graph builders. The first graph builder in the tuple
514 is always the original graph builder.
516 """
517 if count < 2: 2U N E F 7bb c d e f g h i j k l m n o p q r s t u v a
518 raise ValueError(f"Invalid split count: expecting >= 2, got {count}") 1E
519 GB_check_open(self) 2U N E F 7bb c d e f g h i j k l m n o p q r s t u v a
520 if self._state != CAPTURING: 2U N E F 7bb c d e f g h i j k l m n o p q r s t u v a
521 raise RuntimeError("Graph builder must be building before it can be split.") 27b
523 event = self._stream.record() 1UNEFbcdefghijklmnopqrstuva
524 result = [self] 1UNEFbcdefghijklmnopqrstuva
525 for i in range(count - 1): 1UNEFbcdefghijklmnopqrstuva
526 stream = self._stream.device.create_stream() 1UNEFbcdefghijklmnopqrstuva
527 stream.wait(event) 1UNEFbcdefghijklmnopqrstuva
528 result.append(GB_init_forked(stream, self._h_graph)) 1UNEFbcdefghijklmnopqrstuva
529 event.close() 1UNEFbcdefghijklmnopqrstuva
530 return tuple(result) 1UNEFbcdefghijklmnopqrstuva
532 @staticmethod
533 def join(*graph_builders: GraphBuilder) -> GraphBuilder:
534 """Joins multiple graph builders into a single graph builder.
536 The returned builder inherits work dependencies from the provided builders.
538 Parameters
539 ----------
540 *graph_builders : :obj:`~graph.GraphBuilder`
541 The graph builders to join.
543 Returns
544 -------
545 graph_builder : :obj:`~graph.GraphBuilder`
546 The newly joined graph builder.
548 """
549 if any(not isinstance(builder, GraphBuilder) for builder in graph_builders): 1UNEFbcdefghijklmnopqrstuva
550 raise TypeError("All arguments must be GraphBuilder instances")
551 if len(graph_builders) < 2: 1UNEFbcdefghijklmnopqrstuva
552 raise ValueError("Must join with at least two graph builders") 1E
554 # Discover the root builder others should join
555 root_idx = 0 1UNEFbcdefghijklmnopqrstuva
556 for i, builder in enumerate(graph_builders): 1UNEFbcdefghijklmnopqrstuva
557 if not builder.is_join_required: 1UNEFbcdefghijklmnopqrstuva
558 root_idx = i 1UNEFbcdefghijklmnopqrstuva
559 break 1UNEFbcdefghijklmnopqrstuva
561 # Join all onto the root builder
562 root_bdr = graph_builders[root_idx] 1UNEFbcdefghijklmnopqrstuva
563 for idx, builder in enumerate(graph_builders): 1UNEFbcdefghijklmnopqrstuva
564 if idx == root_idx: 1UNEFbcdefghijklmnopqrstuva
565 continue 1UNEFbcdefghijklmnopqrstuva
566 root_bdr.stream.wait(builder.stream) 1UNEFbcdefghijklmnopqrstuva
567 builder.close() 1UNEFbcdefghijklmnopqrstuva
569 return root_bdr 1UNEFbcdefghijklmnopqrstuva
571 def __cuda_stream__(self) -> tuple[int, int]:
572 """Return an instance of a __cuda_stream__ protocol."""
573 GB_check_open(self)
574 return self.stream.__cuda_stream__()
576 def _get_conditional_context(self) -> driver.CUcontext:
577 return self._stream.context.handle 1xGybcdefghijklmnopqzrstuvABCDwa
579 def create_condition(self, default_value: int | None = None) -> GraphCondition:
580 """Create a condition variable for use with conditional nodes.
582 The returned :class:`GraphCondition` object is passed to conditional-node
583 builder methods (:meth:`if_then`, :meth:`if_else`, :meth:`while_loop`,
584 :meth:`switch`). Its value is controlled at runtime by device code via
585 ``cudaGraphSetConditional``.
587 Parameters
588 ----------
589 default_value : int, optional
590 The default value to assign to the condition. If None, no
591 default is assigned.
593 Returns
594 -------
595 GraphCondition
596 A condition variable for controlling conditional execution.
597 """
598 GB_check_open(self) 1xGybcdefghijklmnopqzrstuvABCDwa
599 if cy_driver_version() < (12, 3, 0): 1xGybcdefghijklmnopqzrstuvABCDwa
600 raise RuntimeError(f"Driver version {'.'.join(map(str, cy_driver_version()))} does not support conditional handles")
601 if cy_binding_version() < (12, 3, 0): 1xGybcdefghijklmnopqzrstuvABCDwa
602 raise RuntimeError(f"Binding version {'.'.join(map(str, cy_binding_version()))} does not support conditional handles")
603 if default_value is not None: 1xGybcdefghijklmnopqzrstuvABCDwa
604 flags = driver.CU_GRAPH_COND_ASSIGN_DEFAULT 1xGyABCDw
605 else:
606 default_value = 0 1bcdefghijklmnopqzrstuva
607 flags = 0 1bcdefghijklmnopqzrstuva
609 status, _, graph, *_, _ = handle_return(driver.cuStreamGetCaptureInfo(self._stream.handle)) 1xGybcdefghijklmnopqzrstuvABCDwa
610 if status != driver.CUstreamCaptureStatus.CU_STREAM_CAPTURE_STATUS_ACTIVE: 1xGybcdefghijklmnopqzrstuvABCDwa
611 raise RuntimeError("Cannot create a condition when graph is not being built")
613 raw_handle = handle_return( 1xGybcdefghijklmnopqzrstuvABCDwa
614 driver.cuGraphConditionalHandleCreate(graph, self._get_conditional_context(), default_value, flags) 1xGybcdefghijklmnopqzrstuvABCDwa
615 )
616 return GraphCondition._from_handle(<cydriver.CUgraphConditionalHandle><intptr_t>int(raw_handle)) 1xGybcdefghijklmnopqzrstuvABCDwa
618 def if_then(self, condition: GraphCondition) -> GraphBuilder:
619 """Adds an if condition branch and returns a new graph builder for it.
621 The resulting if graph will only execute the branch if the
622 condition evaluates to true at runtime.
624 The new builder inherits work dependencies from the original builder.
626 Parameters
627 ----------
628 condition : :class:`~graph.GraphCondition`
629 The condition variable from :meth:`create_condition` controlling
630 whether the branch executes.
632 Returns
633 -------
634 graph_builder : :obj:`~graph.GraphBuilder`
635 The newly created conditional graph builder.
637 """
638 GB_check_open(self) 1xGybcdefghizra
639 if cy_driver_version() < (12, 3, 0): 1xGybcdefghizra
640 raise RuntimeError(f"Driver version {'.'.join(map(str, cy_driver_version()))} does not support conditional if")
641 if cy_binding_version() < (12, 3, 0): 1xGybcdefghizra
642 raise RuntimeError(f"Binding version {'.'.join(map(str, cy_binding_version()))} does not support conditional if")
643 if not isinstance(condition, GraphCondition): 1xGybcdefghizra
644 raise TypeError(
645 f"condition must be a GraphCondition object (from "
646 f"GraphBuilder.create_condition()), got {type(condition).__name__}")
647 node_params = driver.CUgraphNodeParams() 1xGybcdefghizra
648 node_params.type = driver.CUgraphNodeType.CU_GRAPH_NODE_TYPE_CONDITIONAL 1xGybcdefghizra
649 node_params.conditional.handle = condition.handle 1xGybcdefghizra
650 node_params.conditional.type = driver.CUgraphConditionalNodeType.CU_GRAPH_COND_TYPE_IF 1xGybcdefghizra
651 node_params.conditional.size = 1 1xGybcdefghizra
652 node_params.conditional.ctx = self._get_conditional_context() 1xGybcdefghizra
653 return GB_cond_with_params(self, node_params)[0] 1xGybcdefghizra
655 def if_else(self, condition: GraphCondition) -> tuple[GraphBuilder, GraphBuilder]:
656 """Adds an if-else condition branch and returns new graph builders for both branches.
658 The resulting if graph will execute the branch if the condition
659 evaluates to true at runtime, otherwise the else branch will execute.
661 The new builders inherit work dependencies from the original builder.
663 Parameters
664 ----------
665 condition : :class:`~graph.GraphCondition`
666 The condition variable from :meth:`create_condition` controlling
667 which branch executes.
669 Returns
670 -------
671 graph_builders : tuple[:obj:`~graph.GraphBuilder`, :obj:`~graph.GraphBuilder`]
672 A tuple of two new graph builders, one for the if branch and one for the else branch.
674 """
675 GB_check_open(self) 1jklmnopq
676 if cy_driver_version() < (12, 8, 0): 1jklmnopq
677 raise RuntimeError(f"Driver version {'.'.join(map(str, cy_driver_version()))} does not support conditional if-else")
678 if cy_binding_version() < (12, 8, 0): 1jklmnopq
679 raise RuntimeError(f"Binding version {'.'.join(map(str, cy_binding_version()))} does not support conditional if-else")
680 if not isinstance(condition, GraphCondition): 1jklmnopq
681 raise TypeError(
682 f"condition must be a GraphCondition object (from "
683 f"GraphBuilder.create_condition()), got {type(condition).__name__}")
684 node_params = driver.CUgraphNodeParams() 1jklmnopq
685 node_params.type = driver.CUgraphNodeType.CU_GRAPH_NODE_TYPE_CONDITIONAL 1jklmnopq
686 node_params.conditional.handle = condition.handle 1jklmnopq
687 node_params.conditional.type = driver.CUgraphConditionalNodeType.CU_GRAPH_COND_TYPE_IF 1jklmnopq
688 node_params.conditional.size = 2 1jklmnopq
689 node_params.conditional.ctx = self._get_conditional_context() 1jklmnopq
690 return GB_cond_with_params(self, node_params) 1jklmnopq
692 def switch(self, condition: GraphCondition, count: int) -> tuple[GraphBuilder, ...]:
693 """Adds a switch condition branch and returns new graph builders for all cases.
695 The resulting switch graph will execute the branch whose case index
696 matches the value of the condition at runtime. If no match is found, no
697 branch will be executed.
699 The new builders inherit work dependencies from the original builder.
701 Parameters
702 ----------
703 condition : :class:`~graph.GraphCondition`
704 The condition variable from :meth:`create_condition` selecting
705 which case executes.
706 count : int
707 The number of cases to add to the switch conditional.
709 Returns
710 -------
711 graph_builders : tuple[:obj:`~graph.GraphBuilder`, ...]
712 A tuple of new graph builders, one for each branch.
714 """
715 GB_check_open(self) 1stuvw
716 if cy_driver_version() < (12, 8, 0): 1stuvw
717 raise RuntimeError(f"Driver version {'.'.join(map(str, cy_driver_version()))} does not support conditional switch")
718 if cy_binding_version() < (12, 8, 0): 1stuvw
719 raise RuntimeError(f"Binding version {'.'.join(map(str, cy_binding_version()))} does not support conditional switch")
720 if not isinstance(condition, GraphCondition): 1stuvw
721 raise TypeError(
722 f"condition must be a GraphCondition object (from "
723 f"GraphBuilder.create_condition()), got {type(condition).__name__}")
724 node_params = driver.CUgraphNodeParams() 1stuvw
725 node_params.type = driver.CUgraphNodeType.CU_GRAPH_NODE_TYPE_CONDITIONAL 1stuvw
726 node_params.conditional.handle = condition.handle 1stuvw
727 node_params.conditional.type = driver.CUgraphConditionalNodeType.CU_GRAPH_COND_TYPE_SWITCH 1stuvw
728 node_params.conditional.size = count 1stuvw
729 node_params.conditional.ctx = self._get_conditional_context() 1stuvw
730 return GB_cond_with_params(self, node_params) 1stuvw
732 def while_loop(self, condition: GraphCondition) -> GraphBuilder:
733 """Adds a while loop and returns a new graph builder for it.
735 The resulting while loop graph will execute the branch repeatedly at runtime
736 until the condition evaluates to false.
738 The new builder inherits work dependencies from the original builder.
740 Parameters
741 ----------
742 condition : :class:`~graph.GraphCondition`
743 The condition variable from :meth:`create_condition` controlling
744 loop continuation.
746 Returns
747 -------
748 graph_builder : :obj:`~graph.GraphBuilder`
749 The newly created while loop graph builder.
751 """
752 GB_check_open(self) 1ABCD
753 if cy_driver_version() < (12, 3, 0): 1ABCD
754 raise RuntimeError(f"Driver version {'.'.join(map(str, cy_driver_version()))} does not support conditional while loop")
755 if cy_binding_version() < (12, 3, 0): 1ABCD
756 raise RuntimeError(f"Binding version {'.'.join(map(str, cy_binding_version()))} does not support conditional while loop")
757 if not isinstance(condition, GraphCondition): 1ABCD
758 raise TypeError(
759 f"condition must be a GraphCondition object (from "
760 f"GraphBuilder.create_condition()), got {type(condition).__name__}")
761 node_params = driver.CUgraphNodeParams() 1ABCD
762 node_params.type = driver.CUgraphNodeType.CU_GRAPH_NODE_TYPE_CONDITIONAL 1ABCD
763 node_params.conditional.handle = condition.handle 1ABCD
764 node_params.conditional.type = driver.CUgraphConditionalNodeType.CU_GRAPH_COND_TYPE_WHILE 1ABCD
765 node_params.conditional.size = 1 1ABCD
766 node_params.conditional.ctx = self._get_conditional_context() 1ABCD
767 return GB_cond_with_params(self, node_params)[0] 1ABCD
769 def embed(self, GraphBuilder child):
770 """Embed a previously-built :obj:`~graph.GraphBuilder` as a child node.
772 Parameters
773 ----------
774 child : :obj:`~graph.GraphBuilder`
775 The child graph builder. Must have finished building.
776 """
777 GB_check_open(self) 1IH
778 if child._state != CAPTURE_ENDED: 1IH
779 raise ValueError("Child graph has not finished building.")
781 if not self.is_building: 1IH
782 raise ValueError("Parent graph is not being built.")
784 stream_handle = self._stream.handle 1IH
785 _, _, graph_out, *deps_info_out, num_dependencies_out = handle_return( 1IH
786 driver.cuStreamGetCaptureInfo(stream_handle) 1IH
787 )
789 # See https://github.com/NVIDIA/cuda-python/pull/879#issuecomment-3211054159
790 # for rationale
791 dependencies_out = deps_info_out[0] 1IH
792 new_node = handle_return( 1IH
793 driver.cuGraphAddChildGraphNode( 1IH
794 graph_out, dependencies_out,
795 num_dependencies_out, as_py(child._h_graph) 1IH
796 )
797 )
798 cdef cydriver.CUgraphNode c_new_node = (
799 <cydriver.CUgraphNode><intptr_t>int(new_node) 1IH
800 )
801 cdef cydriver.CUgraph embedded_graph = NULL 1IH
802 cdef cydriver.CUresult rollback_status
803 cdef GraphHandle h_embedded
804 try: 1IH
805 with nogil: 1IH
806 HANDLE_RETURN(cydriver.cuGraphChildGraphNodeGetGraph( 1IH
807 c_new_node, &embedded_graph))
808 h_embedded = create_child_graph_handle( 1IH
809 embedded_graph, self._h_graph, c_new_node)
810 HANDLE_RETURN(graph_clone_attachments( 1IH
811 h_embedded, child._h_graph))
812 except:
813 with nogil:
814 rollback_status = cydriver.cuGraphDestroyNode(c_new_node)
815 if rollback_status == cydriver.CUDA_SUCCESS:
816 invalidate_child_graph_state(
817 self._h_graph, c_new_node)
818 raise
820 deps_info_update = [[new_node]] + [None] * (len(deps_info_out) - 1) 1IH
821 handle_return( 1IH
822 driver.cuStreamUpdateCaptureDependencies( 1IH
823 stream_handle, 1IH
824 *deps_info_update, # dependencies, edgeData
825 1,
826 driver.CUstreamUpdateCaptureDependencies_flags.CU_STREAM_SET_CAPTURE_DEPENDENCIES, 1IH
827 )
828 )
830 def callback(self, fn, *, user_data=None) -> None:
831 """Add a host callback to the graph during stream capture.
833 The callback runs on the host CPU when the graph reaches this point
834 in execution. Two modes are supported:
836 - **Python callable**: Pass any callable. The GIL is acquired
837 automatically. The callable must take no arguments; use closures
838 or ``functools.partial`` to bind state.
839 - **ctypes function pointer**: Pass a ``ctypes.CFUNCTYPE`` instance.
840 The function receives a single ``void*`` argument (the
841 ``user_data``). The caller must keep the ctypes wrapper alive
842 for the lifetime of the graph.
844 .. warning::
846 Callbacks must not call CUDA API functions. Doing so may
847 deadlock or corrupt driver state.
849 Use caution when a Python callback retains an object that owns a
850 graph. Any reference cycle involving the callback and a graph that
851 retains it cannot be broken by Python's cyclic garbage collector.
852 Use a weak reference to break such cycles.
854 Parameters
855 ----------
856 fn : callable or ctypes function pointer
857 The callback function.
858 user_data : int or bytes-like, optional
859 Only for ctypes function pointers. If ``int``, passed as a raw
860 pointer (caller manages lifetime). If bytes-like, the data is
861 copied and its lifetime is tied to the graph.
862 """
863 GB_callback(self, fn, user_data, False) 1JKLMH
866cdef inline void GB_callback(
867 GraphBuilder gb, object fn, object user_data,
868 bint fail_tail_discovery_for_testing) except *:
869 GB_check_open(gb) 1OJKLMH
870 cdef Stream stream = gb._stream 1OJKLMH
871 cdef cydriver.CUstream c_stream = as_cu(stream._h_stream) 1OJKLMH
872 cdef cydriver.CUstreamCaptureStatus capture_status
874 with nogil: 1OJKLMH
875 _get_capture_info(c_stream, &capture_status, NULL) 1OJKLMH
877 if capture_status != cydriver.CU_STREAM_CAPTURE_STATUS_ACTIVE: 1OJKLMH
878 raise RuntimeError("Cannot add callback when graph is not being built")
880 cdef cydriver.CUhostFn c_fn
881 cdef void* c_user_data = NULL 1OJKLMH
882 cdef OpaqueHandle fn_owner, data_owner
883 cdef PreparedAttachment prepared
884 _resolve_host_callback( 1OJKLMH
885 fn, user_data, &c_fn, &c_user_data, &fn_owner, &data_owner)
886 HANDLE_RETURN(graph_prepare_attachment( 1OJKLMH
887 gb._h_graph, fn_owner, data_owner, &prepared))
889 with nogil: 1OJKLMH
890 HANDLE_RETURN(cydriver.cuLaunchHostFunc(c_stream, c_fn, c_user_data)) 1OJKLMH
892 # Capturing the host function added a node to the graph; it is now the
893 # stream's sole capture dependency. Attach the callback's owners to it.
894 cdef cydriver.CUgraphNode host_node
895 cdef cydriver.CUresult commit_status
896 try: 1OJKLMH
897 if fail_tail_discovery_for_testing: 1OJKLMH
898 raise RuntimeError("forced capture tail discovery failure") 1O
899 host_node = _capture_tail_node(c_stream) 1JKLMH
900 except: 1O
901 # CUDA added the callback, but its node cannot be identified.
902 # Retain its owners anonymously to prevent dangling pointers.
903 commit_status = graph_commit_attachment(prepared, NULL) 1O
904 HANDLE_RETURN(commit_status) 1O
905 raise 1O
906 HANDLE_RETURN(graph_commit_attachment(prepared, host_node)) 1JKLMH
909def _capture_callback_with_tail_failure_for_testing(
910 GraphBuilder gb, fn, *, user_data=None):
911 """Exercise anonymous attachment retention after node discovery fails."""
912 GB_callback(gb, fn, user_data, True) 1O
915cdef inline int GB_check_open(GraphBuilder gb) except -1:
916 """Reject operations on a builder that has been closed.
918 A CLOSED builder has reset its stream and graph handles, so any method
919 that dereferences them would read a null handle (or, for the cached
920 Stream, a None typed as cdef Stream). Guarding here yields a clear error
921 instead.
922 """
923 if gb._state == CLOSED: 2O ^ J K L M + I Y U x G y ? 0b[ N @ X = ` E { _ F ! 7bZ 0 W b c d e f g h i j k l m n o p q z r s t u v A B C D H : , . ; - / # 1 5 $ 2 6 S P Q % 3 7 ' 4 8 w R V ( ) ] T a *
924 raise RuntimeError("Graph builder has been closed.") 1U?
925 return 0 2O ^ J K L M + I Y U x G y ? 0b[ N @ X = ` E { _ F ! 7bZ 0 W b c d e f g h i j k l m n o p q z r s t u v A B C D H : , . ; - / # 1 5 $ 2 6 S P Q % 3 7 ' 4 8 w R V ( ) ] T a *
928cdef inline int GB_end_capture_if_needed(GraphBuilder gb, bint check_status) except -1 nogil:
929 """End an in-progress capture if this builder owns it.
931 Only a CAPTURING PRIMARY or CONDITIONAL_BODY builder owns the live
932 capture. A FORKED builder must not call cuStreamEndCapture: the driver
933 requires forked streams to be joined first.
935 check_status=True checks the driver return (close()); False ignores it
936 (__dealloc__).
937 """
938 cdef cydriver.CUgraph c_graph
939 cdef cydriver.CUresult err
940 cdef cydriver.CUstream c_stream
941 if gb._h_stream and gb._state == CAPTURING and gb._kind != FORKED: 2O ^ J K L M + I Y U x G y ? 0b[ N @ X = ` E { _ F ! 7bZ 0 W b c d e f g h i j k l m n o p q z r s t u v A B C D H : , . ; - / # 1 5 $ 2 6 S P Q % 3 7 ' 4 8 w R V ( ) ] T a *
942 c_stream = as_cu(gb._h_stream) 1U{
943 with nogil: 1U{
944 err = cydriver.cuStreamEndCapture(c_stream, &c_graph) 1U{
945 if check_status: 1U{
946 HANDLE_RETURN(err)
947 return 0 2O ^ J K L M + I Y U x G y ? 0b[ N @ X = ` E { _ F ! 7bZ 0 W b c d e f g h i j k l m n o p q z r s t u v A B C D H : , . ; - / # 1 5 $ 2 6 S P Q % 3 7 ' 4 8 w R V ( ) ] T a *
950cdef inline GraphBuilder GB_init_forked(Stream stream, GraphHandle h_primary_graph):
951 cdef GraphBuilder gb = GraphBuilder.__new__(GraphBuilder) 1UNEFbcdefghijklmnopqrstuva
952 # A FORKED builder captures into the primary's CUgraph. It holds the
953 # primary's GraphHandle so conditional bodies share its graph hierarchy.
954 gb._h_graph = h_primary_graph 1UNEFbcdefghijklmnopqrstuva
955 gb._h_stream = stream._h_stream 1UNEFbcdefghijklmnopqrstuva
956 gb._kind = FORKED 1UNEFbcdefghijklmnopqrstuva
957 gb._state = CAPTURING 1UNEFbcdefghijklmnopqrstuva
958 gb._stream = stream 1UNEFbcdefghijklmnopqrstuva
959 return gb 1UNEFbcdefghijklmnopqrstuva
962cdef inline GraphBuilder GB_init_conditional(
963 Stream stream, cydriver.CUgraph cond_graph,
964 GraphBuilder parent, cydriver.CUgraphNode owner_node):
965 cdef GraphBuilder gb = GraphBuilder.__new__(GraphBuilder) 1xGybcdefghijklmnopqzrstuvABCDwa
966 gb._h_graph = create_child_graph_handle( 1xGybcdefghijklmnopqzrstuvABCDwa
967 cond_graph, parent._h_graph, owner_node)
968 gb._h_stream = stream._h_stream 1xGybcdefghijklmnopqzrstuvABCDwa
969 gb._kind = CONDITIONAL_BODY 1xGybcdefghijklmnopqzrstuvABCDwa
970 gb._state = CAPTURE_NOT_STARTED 1xGybcdefghijklmnopqzrstuvABCDwa
971 gb._stream = stream 1xGybcdefghijklmnopqzrstuvABCDwa
972 return gb 1xGybcdefghijklmnopqzrstuvABCDwa
975cdef inline int _get_capture_info(
976 cydriver.CUstream stream,
977 cydriver.CUstreamCaptureStatus* status,
978 cydriver.CUgraph* graph) except?-1 nogil:
979 """Thin wrapper around ``cuStreamGetCaptureInfo`` that papers over the
980 CUDA 12 vs 13 signature change.
982 ``status`` must be non-NULL: the driver rejects ``captureStatus_out=NULL``
983 with ``CUDA_ERROR_INVALID_VALUE``. ``graph`` may be NULL when the caller
984 does not need the graph handle.
985 """
986 IF CUDA_CORE_BUILD_MAJOR >= 13:
987 return HANDLE_RETURN(cydriver.cuStreamGetCaptureInfo( 1O^JKLM+IYUxGy?[N@X=`E{_F!Z0WbcdefghijklmnopqzrstuvABCDH:,.;-/#15$26SPQ%37'48wRV()]Ta*
988 stream, status, NULL, graph, NULL, NULL, NULL))
989 ELSE:
990 return HANDLE_RETURN(cydriver.cuStreamGetCaptureInfo(
991 stream, status, NULL, graph, NULL, NULL))
994cdef inline cydriver.CUgraphNode _capture_tail_node(cydriver.CUstream stream) except *:
995 """Return the node a freshly-captured single-node operation left as the
996 stream's sole capture dependency (e.g. the host node added by
997 ``cuLaunchHostFunc``). The driver advances the stream's dependency set to
998 the new node, so the next captured op would depend on it.
999 """
1000 cdef cydriver.CUstreamCaptureStatus status
1001 cdef const cydriver.CUgraphNode* deps = NULL 1JKLMH
1002 cdef size_t num_deps = 0 1JKLMH
1003 with nogil: 1JKLMH
1004 IF CUDA_CORE_BUILD_MAJOR >= 13:
1005 HANDLE_RETURN(cydriver.cuStreamGetCaptureInfo( 1JKLMH
1006 stream, &status, NULL, NULL, &deps, NULL, &num_deps))
1007 ELSE:
1008 HANDLE_RETURN(cydriver.cuStreamGetCaptureInfo(
1009 stream, &status, NULL, NULL, &deps, &num_deps))
1010 if num_deps != 1: 1JKLMH
1011 raise RuntimeError(
1012 f"expected exactly one capture dependency after a host callback, got {num_deps}")
1013 return <cydriver.CUgraphNode>deps[0] 1JKLMH
1016cdef inline tuple GB_cond_with_params(GraphBuilder gb, node_params):
1017 status, _, graph, *deps_info, num_dependencies = handle_return( 1xGybcdefghijklmnopqzrstuvABCDwa
1018 driver.cuStreamGetCaptureInfo(gb._stream.handle) 1xGybcdefghijklmnopqzrstuvABCDwa
1019 )
1020 if status != driver.CUstreamCaptureStatus.CU_STREAM_CAPTURE_STATUS_ACTIVE: 1xGybcdefghijklmnopqzrstuvABCDwa
1021 raise RuntimeError("Cannot add conditional node when not actively capturing")
1023 new_node = handle_return( 1xGybcdefghijklmnopqzrstuvABCDwa
1024 driver.cuGraphAddNode(graph, *deps_info, num_dependencies, node_params)) 1xGybcdefghijklmnopqzrstuvABCDwa
1025 deps_info_update = [[new_node]] + [None] * (len(deps_info) - 1) 1xGybcdefghijklmnopqzrstuvABCDwa
1027 handle_return( 1xGybcdefghijklmnopqzrstuvABCDwa
1028 driver.cuStreamUpdateCaptureDependencies( 1xGybcdefghijklmnopqzrstuvABCDwa
1029 gb._stream.handle, 1xGybcdefghijklmnopqzrstuvABCDwa
1030 *deps_info_update, # dependencies, edgeData 1xGybcdefghijklmnopqzrstuvABCDwa
1031 1, # numDependencies
1032 driver.CUstreamUpdateCaptureDependencies_flags.CU_STREAM_SET_CAPTURE_DEPENDENCIES, 1xGybcdefghijklmnopqzrstuvABCDwa
1033 )
1034 )
1036 return tuple( 1xGybcdefghijklmnopqzrstuvABCDwa
1037 GB_init_conditional( 1xGybcdefghijklmnopqzrstuvABCDwa
1038 gb._stream.device.create_stream(), 1xGybcdefghijklmnopqzrstuvABCDwa
1039 <cydriver.CUgraph><intptr_t>int(node_params.conditional.phGraph_out[i]), 1xGybcdefghijklmnopqzrstuvABCDwa
1040 gb, 1xGybcdefghijklmnopqzrstuvABCDwa
1041 <cydriver.CUgraphNode><intptr_t>int(new_node), 1xGybcdefghijklmnopqzrstuvABCDwa
1042 )
1043 for i in range(node_params.conditional.size) 1xGybcdefghijklmnopqzrstuvABCDwa
1044 )
1047cdef class Graph:
1048 """An executable graph.
1050 A graph groups a set of CUDA kernels and other CUDA operations together and executes
1051 them with a specified dependency tree. It speeds up the workflow by combining the
1052 driver activities associated with CUDA kernel launches and CUDA API calls.
1054 Graphs must be built using a :obj:`~graph.GraphBuilder` object.
1056 """
1058 def __init__(self):
1059 raise RuntimeError("directly constructing a Graph instance is not supported")
1061 @staticmethod
1062 cdef Graph _init(cydriver.CUgraphExec graph_exec):
1063 cdef Graph self = Graph.__new__(Graph) 2O J K L M + I Y x y X E F ! Z 0 W b c d e f g h i j k l m n o p q z r s t u v A B C D ob| pb} kbqbrbsbtb~ ubablbbbcbvbdbmbebwbfbnbxb1b2byb3b4b5bEbFbGbzbAb6bHbCbIbJbKbLbMbNbObPbQbRbSbBbTbUbVbWbXbYbZb: , . ; - / # 1 5 $ 2 6 S P Q % 3 7 ' 4 8 gbhbw R ibjbV ( ) T *
1064 self._h_graph_exec = create_graph_exec_handle(graph_exec) 2O J K L M + I Y x y X E F ! Z 0 W b c d e f g h i j k l m n o p q z r s t u v A B C D ob| pb} kbqbrbsbtb~ ubablbbbcbvbdbmbebwbfbnbxb1b2byb3b4b5bEbFbGbzbAb6bHbCbIbJbKbLbMbNbObPbQbRbSbBbTbUbVbWbXbYbZb: , . ; - / # 1 5 $ 2 6 S P Q % 3 7 ' 4 8 gbhbw R ibjbV ( ) T *
1065 return self 2O J K L M + I Y x y X E F ! Z 0 W b c d e f g h i j k l m n o p q z r s t u v A B C D ob| pb} kbqbrbsbtb~ ubablbbbcbvbdbmbebwbfbnbxb1b2byb3b4b5bEbFbGbzbAb6bHbCbIbJbKbLbMbNbObPbQbRbSbBbTbUbVbWbXbYbZb: , . ; - / # 1 5 $ 2 6 S P Q % 3 7 ' 4 8 gbhbw R ibjbV ( ) T *
1067 def close(self) -> None:
1068 """Destroy the graph."""
1069 self._h_graph_exec.reset() 2Y 0 CbT
1070 retry_deferred_cleanup() 2Y 0 CbT
1072 @property
1073 def handle(self) -> driver.CUgraphExec:
1074 """Return the underlying ``CUgraphExec`` object.
1076 .. caution::
1078 This handle is a Python object. To get the memory address of the underlying C
1079 handle, call ``int()`` on the returned object.
1081 """
1082 return as_py(self._h_graph_exec) 1Y
1084 def update(self, source: "GraphBuilder | GraphDefinition") -> None:
1085 """Update the graph using a new graph definition.
1087 The topology of the provided source must be identical to this graph.
1089 Parameters
1090 ----------
1091 source : :obj:`~graph.GraphBuilder` or :obj:`~graph.GraphDefinition`
1092 The graph definition to update from. A GraphBuilder must have
1093 finished building.
1095 """
1096 from cuda.core.graph import GraphDefinition 2W gbhbw R ibjbV ( )
1098 cdef cydriver.CUgraph cu_graph
1099 cdef cydriver.CUgraphExec cu_exec = as_cu(self._h_graph_exec) 2W gbhbw R ibjbV ( )
1101 if isinstance(source, GraphBuilder): 2W gbhbw R ibjbV ( )
1102 if (<GraphBuilder>source)._state == CLOSED: 1WwRV(
1103 raise ValueError("Source graph builder has been closed.") 1W
1104 if (<GraphBuilder>source)._state != CAPTURE_ENDED: 1wRV(
1105 raise ValueError("Graph has not finished building.") 1(
1106 cu_graph = as_cu((<GraphBuilder>source)._h_graph) 1wRV
1107 elif isinstance(source, GraphDefinition): 2gbhbibjb)
1108 cu_graph = <cydriver.CUgraph><intptr_t>int(source.handle) 2gbhbibjb
1109 else:
1110 raise TypeError( 1)
1111 f"expected GraphBuilder or GraphDefinition, got {type(source).__name__}") 1)
1113 cdef cydriver.CUgraphExecUpdateResultInfo result_info
1114 cdef cydriver.CUresult err
1115 with nogil: 2gbhbw R ibjbV
1116 err = cydriver.cuGraphExecUpdate(cu_exec, cu_graph, &result_info) 2gbhbw R ibjbV
1117 if err == cydriver.CUresult.CUDA_ERROR_GRAPH_EXEC_UPDATE_FAILURE: 2gbhbw R ibjbV
1118 reason = driver.CUgraphExecUpdateResult(result_info.result) 2gbhbV
1119 msg = f"Graph update failed: {reason.__doc__.strip()} ({reason.name})" 2gbhbV
1120 raise CUDAError(msg) 2gbhbV
1121 HANDLE_RETURN(err) 2w R ibjb
1123 def upload(self, stream: Stream) -> None:
1124 """Uploads the graph in a stream.
1126 Parameters
1127 ----------
1128 stream : :obj:`~_stream.Stream`
1129 The stream in which to upload the graph
1131 """
1132 cdef cydriver.CUgraphExec c_exec = as_cu(self._h_graph_exec) 2E Z ob| pb} qbrbsbtb~ ubabbbcbvbdbebwbfbzbAbBb# 1 5 $ 2 6 S P Q % 3 7 ' 4 8
1133 cdef cydriver.CUstream c_stream = <cydriver.CUstream><intptr_t>int(stream.handle) 2E Z ob| pb} qbrbsbtb~ ubabbbcbvbdbebwbfbzbAbBb# 1 5 $ 2 6 S P Q % 3 7 ' 4 8
1134 with nogil: 2E Z ob| pb} qbrbsbtb~ ubabbbcbvbdbebwbfbzbAbBb# 1 5 $ 2 6 S P Q % 3 7 ' 4 8
1135 HANDLE_RETURN(cydriver.cuGraphUpload(c_exec, c_stream)) 2E Z ob| pb} qbrbsbtb~ ubabbbcbvbdbebwbfbzbAbBb# 1 5 $ 2 6 S P Q % 3 7 ' 4 8
1137 def launch(self, stream: Stream) -> None:
1138 """Launches the graph in a stream.
1140 Parameters
1141 ----------
1142 stream : :obj:`~_stream.Stream`
1143 The stream in which to launch the graph.
1145 """
1146 cdef cydriver.CUgraphExec c_exec = as_cu(self._h_graph_exec) 2O J K L M I x y X E ! Z b c d e f g h i j k l m n o p q z r s t u v A B C D ob| pb} kbqbrbsbtb~ ubablbbbcbvbdbmbebwbfbnbEbFbGbzbAbHbCbIbJbKbLbMbNbObPbQbRbSbBbTbUbVbWbXbYbZb# 1 5 $ 2 6 S P Q % 3 7 ' 4 8 gbhbw R ibjb*
1147 cdef cydriver.CUstream c_stream = <cydriver.CUstream><intptr_t>int(stream.handle) 2O J K L M I x y X E ! Z b c d e f g h i j k l m n o p q z r s t u v A B C D ob| pb} kbqbrbsbtb~ ubablbbbcbvbdbmbebwbfbnbEbFbGbzbAbHbCbIbJbKbLbMbNbObPbQbRbSbBbTbUbVbWbXbYbZb# 1 5 $ 2 6 S P Q % 3 7 ' 4 8 gbhbw R ibjb*
1148 with nogil: 2O J K L M I x y X E ! Z b c d e f g h i j k l m n o p q z r s t u v A B C D ob| pb} kbqbrbsbtb~ ubablbbbcbvbdbmbebwbfbnbEbFbGbzbAbHbCbIbJbKbLbMbNbObPbQbRbSbBbTbUbVbWbXbYbZb# 1 5 $ 2 6 S P Q % 3 7 ' 4 8 gbhbw R ibjb*
1149 HANDLE_RETURN(cydriver.cuGraphLaunch(c_exec, c_stream)) 2O J K L M I x y X E ! Z b c d e f g h i j k l m n o p q z r s t u v A B C D ob| pb} kbqbrbsbtb~ ubablbbbcbvbdbmbebwbfbnbEbFbGbzbAbHbCbIbJbKbLbMbNbObPbQbRbSbBbTbUbVbWbXbYbZb# 1 5 $ 2 6 S P Q % 3 7 ' 4 8 gbhbw R ibjb*