Coverage for cuda/core/graph/_graph_builder.pyx: 89.33%
431 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-19 01:12 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-19 01:12 +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 _attach_host_callback_owners, _resolve_host_callback
14from cuda.core._resource_handles cimport (
15 GraphHandle,
16 OpaqueHandle,
17 as_cu, as_py,
18 create_graph_exec_handle, create_graph_handle, create_graph_handle_ref,
19)
20from cuda.core._stream cimport Stream
21from cuda.core._utils.cuda_utils cimport HANDLE_RETURN
22from cuda.core._utils.version cimport cy_binding_version, cy_driver_version
24from cuda.core._utils.cuda_utils import (
25 CUDAError,
26 driver,
27 handle_return,
28)
30if TYPE_CHECKING:
31 from cuda.core.graph._graph_definition import GraphDefinition
33__all__ = ['Graph', 'GraphBuilder', 'GraphCompleteOptions', 'GraphDebugPrintOptions']
36@dataclass
37class GraphDebugPrintOptions:
38 """Options for debug_dot_print().
40 Attributes
41 ----------
42 verbose : bool
43 Output all debug data as if every debug flag is enabled (Default to False)
44 runtime_types : bool
45 Use CUDA Runtime structures for output (Default to False)
46 kernel_node_params : bool
47 Adds kernel parameter values to output (Default to False)
48 memcpy_node_params : bool
49 Adds memcpy parameter values to output (Default to False)
50 memset_node_params : bool
51 Adds memset parameter values to output (Default to False)
52 host_node_params : bool
53 Adds host parameter values to output (Default to False)
54 event_node_params : bool
55 Adds event parameter values to output (Default to False)
56 ext_semas_signal_node_params : bool
57 Adds external semaphore signal parameter values to output (Default to False)
58 ext_semas_wait_node_params : bool
59 Adds external semaphore wait parameter values to output (Default to False)
60 kernel_node_attributes : bool
61 Adds kernel node attributes to output (Default to False)
62 handles : bool
63 Adds node handles and every kernel function handle to output (Default to False)
64 mem_alloc_node_params : bool
65 Adds memory alloc parameter values to output (Default to False)
66 mem_free_node_params : bool
67 Adds memory free parameter values to output (Default to False)
68 batch_mem_op_node_params : bool
69 Adds batch mem op parameter values to output (Default to False)
70 extra_topo_info : bool
71 Adds edge numbering information (Default to False)
72 conditional_node_params : bool
73 Adds conditional node parameter values to output (Default to False)
75 """
77 verbose: bool = False
78 runtime_types: bool = False
79 kernel_node_params: bool = False
80 memcpy_node_params: bool = False
81 memset_node_params: bool = False
82 host_node_params: bool = False
83 event_node_params: bool = False
84 ext_semas_signal_node_params: bool = False
85 ext_semas_wait_node_params: bool = False
86 kernel_node_attributes: bool = False
87 handles: bool = False
88 mem_alloc_node_params: bool = False
89 mem_free_node_params: bool = False
90 batch_mem_op_node_params: bool = False
91 extra_topo_info: bool = False
92 conditional_node_params: bool = False
94 def _to_flags(self) -> int:
95 """Convert options to CUDA driver API flags (internal use)."""
96 flags = 0 2xba
97 if self.verbose: 2xba
98 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_VERBOSE 2xba
99 if self.runtime_types: 2xba
100 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_RUNTIME_TYPES 1a
101 if self.kernel_node_params: 2xba
102 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_KERNEL_NODE_PARAMS 1a
103 if self.memcpy_node_params: 2xba
104 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_MEMCPY_NODE_PARAMS 1a
105 if self.memset_node_params: 2xba
106 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_MEMSET_NODE_PARAMS 1a
107 if self.host_node_params: 2xba
108 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_HOST_NODE_PARAMS 1a
109 if self.event_node_params: 2xba
110 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_EVENT_NODE_PARAMS 1a
111 if self.ext_semas_signal_node_params: 2xba
112 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_EXT_SEMAS_SIGNAL_NODE_PARAMS 1a
113 if self.ext_semas_wait_node_params: 2xba
114 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_EXT_SEMAS_WAIT_NODE_PARAMS 1a
115 if self.kernel_node_attributes: 2( xba
116 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_KERNEL_NODE_ATTRIBUTES 1a
117 if self.handles: 2xba
118 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_HANDLES 2xba
119 if self.mem_alloc_node_params: 2xba
120 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_MEM_ALLOC_NODE_PARAMS 1a
121 if self.mem_free_node_params: 2xba
122 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_MEM_FREE_NODE_PARAMS 1a
123 if self.batch_mem_op_node_params: 2xba
124 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_BATCH_MEM_OP_NODE_PARAMS 1a
125 if self.extra_topo_info: 2xba
126 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_EXTRA_TOPO_INFO 1a
127 if self.conditional_node_params: 2xba
128 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_CONDITIONAL_NODE_PARAMS 1a
129 return flags 2xba
132@dataclass
133class GraphCompleteOptions:
134 """Options for graph instantiation.
136 Attributes
137 ----------
138 auto_free_on_launch : bool, optional
139 Automatically free memory allocated in a graph before relaunching. (Default to False)
140 upload_stream : Stream, optional
141 Stream to use to automatically upload the graph after completion. (Default to None)
142 device_launch : bool, optional
143 Configure the graph to be launchable from the device. This flag can only
144 be used on platforms which support unified addressing. This flag cannot be
145 used in conjunction with auto_free_on_launch. (Default to False)
146 use_node_priority : bool, optional
147 Run the graph using the per-node priority attributes rather than the
148 priority of the stream it is launched into. (Default to False)
150 """
152 auto_free_on_launch: bool = False
153 upload_stream: Stream | None = None
154 device_launch: bool = False
155 use_node_priority: bool = False
158def _instantiate_graph(h_graph, options: GraphCompleteOptions | None = None) -> Graph:
159 cdef cydriver.CUgraphExec c_exec
160 params = driver.CUDA_GRAPH_INSTANTIATE_PARAMS() 2I J K L ) M 1 x y U E F 6 W 7 V 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 jb` kb{ fblbmbnbob| pb} gb~ abqbbbhbcbrbdbibsbRbSbtbTbUbVbybubvbzbAbBbCbDbEbFbGbHbIbJbwbKbLbMbNbObPb. * , / + - 8 X 2 9 Y 3 Q N O ! Z 4 # 0 5 w P ebR $ % S '
161 if options: 2I J K L ) M 1 x y U E F 6 W 7 V 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 jb` kb{ fblbmbnbob| pb} gb~ abqbbbhbcbrbdbibsbRbSbtbTbUbVbybubvbzbAbBbCbDbEbFbGbHbIbJbwbKbLbMbNbObPb. * , / + - 8 X 2 9 Y 3 Q N O ! Z 4 # 0 5 w P ebR $ % S '
162 flags = 0 2` { fb| } gb~ abbbhbcbdbibsbtbQ N O S
163 if options.auto_free_on_launch: 2` { fb| } gb~ abbbhbcbdbibsbtbQ N O S
164 flags |= driver.CUgraphInstantiate_flags.CUDA_GRAPH_INSTANTIATE_FLAG_AUTO_FREE_ON_LAUNCH 2` | abcbsbtbQ N O S
165 if options.upload_stream: 2` { fb| } gb~ abbbhbcbdbibsbtbQ N O S
166 flags |= driver.CUgraphInstantiate_flags.CUDA_GRAPH_INSTANTIATE_FLAG_UPLOAD 2fbgbhbibS
167 params.hUploadStream = options.upload_stream.handle 2fbgbhbibS
168 if options.device_launch: 2` { fb| } gb~ abbbhbcbdbibsbtbQ N O S
169 flags |= driver.CUgraphInstantiate_flags.CUDA_GRAPH_INSTANTIATE_FLAG_DEVICE_LAUNCH 1~S
170 if options.use_node_priority: 2` { fb| } gb~ abbbhbcbdbibsbtbQ N O S
171 flags |= driver.CUgraphInstantiate_flags.CUDA_GRAPH_INSTANTIATE_FLAG_USE_NODE_PRIORITY 2{ } bbdbsbtbS
172 params.flags = flags 2` { fb| } gb~ abbbhbcbdbibsbtbQ N O S
174 py_exec = handle_return(driver.cuGraphInstantiateWithParams(h_graph, params)) 2I J K L ) M 1 x y U E F 6 W 7 V 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 jb` kb{ fblbmbnbob| pb} gb~ abqbbbhbcbrbdbibsbRbSbtbTbUbVbybubvbzbAbBbCbDbEbFbGbHbIbJbwbKbLbMbNbObPb. * , / + - 8 X 2 9 Y 3 Q N O ! Z 4 # 0 5 w P ebR $ % S '
175 # Check result_out before wrapping the exec: on a non-SUCCESS result the exec
176 # may be invalid, and Graph._init's RAII deleter would call cuGraphExecDestroy
177 # on it during the exception unwind below.
178 if params.result_out == driver.CUgraphInstantiateResult.CUDA_GRAPH_INSTANTIATE_ERROR: 2I J K L ) M 1 x y U E F 6 W 7 V 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 jb` kb{ fblbmbnbob| pb} gb~ abqbbbhbcbrbdbibsbRbSbtbTbUbVbybubvbzbAbBbCbDbEbFbGbHbIbJbwbKbLbMbNbObPb. * , / + - 8 X 2 9 Y 3 Q N O ! Z 4 # 0 5 w P ebR $ % S '
179 raise RuntimeError(
180 "Instantiation failed for an unexpected reason which is described in the return value of the function."
181 )
182 elif params.result_out == driver.CUgraphInstantiateResult.CUDA_GRAPH_INSTANTIATE_INVALID_STRUCTURE: 2I J K L ) M 1 x y U E F 6 W 7 V 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 jb` kb{ fblbmbnbob| pb} gb~ abqbbbhbcbrbdbibsbRbSbtbTbUbVbybubvbzbAbBbCbDbEbFbGbHbIbJbwbKbLbMbNbObPb. * , / + - 8 X 2 9 Y 3 Q N O ! Z 4 # 0 5 w P ebR $ % S '
183 raise RuntimeError("Instantiation failed due to invalid structure, such as cycles.")
184 elif params.result_out == driver.CUgraphInstantiateResult.CUDA_GRAPH_INSTANTIATE_NODE_OPERATION_NOT_SUPPORTED: 2I J K L ) M 1 x y U E F 6 W 7 V 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 jb` kb{ fblbmbnbob| pb} gb~ abqbbbhbcbrbdbibsbRbSbtbTbUbVbybubvbzbAbBbCbDbEbFbGbHbIbJbwbKbLbMbNbObPb. * , / + - 8 X 2 9 Y 3 Q N O ! Z 4 # 0 5 w P ebR $ % S '
185 raise RuntimeError(
186 "Instantiation for device launch failed because the graph contained an unsupported operation."
187 )
188 elif params.result_out == driver.CUgraphInstantiateResult.CUDA_GRAPH_INSTANTIATE_MULTIPLE_CTXS_NOT_SUPPORTED: 2I J K L ) M 1 x y U E F 6 W 7 V 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 jb` kb{ fblbmbnbob| pb} gb~ abqbbbhbcbrbdbibsbRbSbtbTbUbVbybubvbzbAbBbCbDbEbFbGbHbIbJbwbKbLbMbNbObPb. * , / + - 8 X 2 9 Y 3 Q N O ! Z 4 # 0 5 w P ebR $ % S '
189 raise RuntimeError("Instantiation for device launch failed due to the nodes belonging to different contexts.")
190 elif ( 2I J K L ) M 1 x y U E F 6 W 7 V 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 jb` kb{ fblbmbnbob| pb} gb~ abqbbbhbcbrbdbibsbRbSbtbTbUbVbybubvbzbAbBbCbDbEbFbGbHbIbJbwbKbLbMbNbObPb. * , / + - 8 X 2 9 Y 3 Q N O ! Z 4 # 0 5 w P ebR $ % S '
191 cy_binding_version() >= (12, 8, 0) 2I J K L ) M 1 x y U E F 6 W 7 V 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 jb` kb{ fblbmbnbob| pb} gb~ abqbbbhbcbrbdbibsbRbSbtbTbUbVbybubvbzbAbBbCbDbEbFbGbHbIbJbwbKbLbMbNbObPb. * , / + - 8 X 2 9 Y 3 Q N O ! Z 4 # 0 5 w P ebR $ % S '
192 and params.result_out == driver.CUgraphInstantiateResult.CUDA_GRAPH_INSTANTIATE_CONDITIONAL_HANDLE_UNUSED 2I J K L ) M 1 x y U E F 6 W 7 V 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 jb` kb{ fblbmbnbob| pb} gb~ abqbbbhbcbrbdbibsbRbSbtbTbUbVbybubvbzbAbBbCbDbEbFbGbHbIbJbwbKbLbMbNbObPb. * , / + - 8 X 2 9 Y 3 Q N O ! Z 4 # 0 5 w P ebR $ % S '
193 ):
194 raise RuntimeError("One or more conditional handles are not associated with conditional builders.")
195 elif params.result_out != driver.CUgraphInstantiateResult.CUDA_GRAPH_INSTANTIATE_SUCCESS: 2I J K L ) M 1 x y U E F 6 W 7 V 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 jb` kb{ fblbmbnbob| pb} gb~ abqbbbhbcbrbdbibsbRbSbtbTbUbVbybubvbzbAbBbCbDbEbFbGbHbIbJbwbKbLbMbNbObPb. * , / + - 8 X 2 9 Y 3 Q N O ! Z 4 # 0 5 w P ebR $ % S '
196 raise RuntimeError(f"Graph instantiation failed with unexpected error code: {params.result_out}")
198 c_exec = <cydriver.CUgraphExec><intptr_t>int(py_exec) 2I J K L ) M 1 x y U E F 6 W 7 V 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 jb` kb{ fblbmbnbob| pb} gb~ abqbbbhbcbrbdbibsbRbSbtbTbUbVbybubvbzbAbBbCbDbEbFbGbHbIbJbwbKbLbMbNbObPb. * , / + - 8 X 2 9 Y 3 Q N O ! Z 4 # 0 5 w P ebR $ % S '
199 return Graph._init(c_exec) 2I J K L ) M 1 x y U E F 6 W 7 V 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 jb` kb{ fblbmbnbob| pb} gb~ abqbbbhbcbrbdbibsbRbSbtbTbUbVbybubvbzbAbBbCbDbEbFbGbHbIbJbwbKbLbMbNbObPb. * , / + - 8 X 2 9 Y 3 Q N O ! Z 4 # 0 5 w P ebR $ % S '
202# Distinguishes the three kinds of GraphBuilder, which differ in how they
203# begin/end stream capture and whether they own the resulting CUgraph.
204# Each kind progresses through _CaptureState as follows:
205#
206# PRIMARY: NOT_STARTED -> CAPTURING -> ENDED
207# FORKED: CAPTURING (never transitions; joined and closed)
208# CONDITIONAL_BODY: NOT_STARTED -> CAPTURING -> ENDED
209#
210cdef enum _BuilderKind:
211 # PRIMARY: The top-level builder created by Device or Stream. Owns the
212 # captured CUgraph via an owning GraphHandle. Progresses through all three
213 # capture states; responsible for ending capture if destroyed early.
214 PRIMARY = 0
215 # FORKED: Created by split(). Captures on a private stream forked from the
216 # primary. Starts in CAPTURING state and never transitions; the user joins
217 # it back to the primary via join(), which closes the builder. Must NOT
218 # call cuStreamEndCapture (the driver requires all forked streams to be
219 # joined first).
220 FORKED = 1
221 # CONDITIONAL_BODY: Created by if_then/if_else/switch/while_loop. Captures
222 # into a non-owned body graph via cuStreamBeginCaptureToGraph. The body
223 # graph's lifetime is tied to a parent graph. Progresses through all three
224 # capture states like PRIMARY.
225 CONDITIONAL_BODY = 2
228# Tracks the capture lifecycle of a GraphBuilder.
229cdef enum _CaptureState:
230 CAPTURE_NOT_STARTED = 0
231 CAPTURING = 1
232 CAPTURE_ENDED = 2 # Finished, valid handle
233 CLOSED = 3 # No valid handle
236cdef class GraphBuilder:
237 """A graph under construction by stream capture.
239 A graph groups a set of CUDA kernels and other CUDA operations together and executes
240 them with a specified dependency tree. It speeds up the workflow by combining the
241 driver activities associated with CUDA kernel launches and CUDA API calls.
243 Directly creating a :obj:`~graph.GraphBuilder` is not supported due
244 to ambiguity. New graph builders should instead be created through a
245 :obj:`~_device.Device`, or a :obj:`~_stream.stream` object.
247 .. note::
249 Operations recorded during capture reference your memory but do not
250 take ownership of it. As with ordinary stream work, you must keep the
251 operands alive for as long as the completed graph may execute -- for
252 example, the :obj:`~_memory.Buffer` objects passed to :func:`~launch`
253 or :meth:`~_memory.Buffer.copy_to`. Host callbacks added with
254 :meth:`callback` are the exception: the callable (and any copied
255 ``user_data``) are retained for the graph's lifetime. This differs from
256 building a graph explicitly with :class:`~graph.GraphDefinition`, which
257 retains the operands it is given.
259 """
261 def __init__(self):
262 raise NotImplementedError(
263 "directly creating a GraphBuilder object can be ambiguous. Please either "
264 "call Device.create_graph_builder() or stream.create_graph_builder()"
265 )
267 def __dealloc__(self):
268 GB_end_capture_if_needed(self, False) 2[ I J K L ) M 1 T x G y ; Qb? H = U : ^ E _ ] F 6 WbW 7 V 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 . * , / + - 8 X 2 9 Y 3 Q N O ! Z 4 # 0 5 w P R $ % @ S a '
270 @staticmethod
271 def _init(Stream stream):
272 cdef GraphBuilder self = GraphBuilder.__new__(GraphBuilder) 2[ I J K L ) M 1 T x G y ; Qb? H = U : ^ E _ ] F 6 WbW 7 V 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 . * , / + - 8 X 2 9 Y 3 Q N O ! Z 4 # 0 5 w P R $ % @ S a '
273 # _h_graph set by begin_building
274 self._h_stream = stream._h_stream 2[ I J K L ) M 1 T x G y ; Qb? H = U : ^ E _ ] F 6 WbW 7 V 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 . * , / + - 8 X 2 9 Y 3 Q N O ! Z 4 # 0 5 w P R $ % @ S a '
275 self._kind = PRIMARY 2[ I J K L ) M 1 T x G y ; Qb? H = U : ^ E _ ] F 6 WbW 7 V 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 . * , / + - 8 X 2 9 Y 3 Q N O ! Z 4 # 0 5 w P R $ % @ S a '
276 self._state = CAPTURE_NOT_STARTED 2[ I J K L ) M 1 T x G y ; Qb? H = U : ^ E _ ] F 6 WbW 7 V 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 . * , / + - 8 X 2 9 Y 3 Q N O ! Z 4 # 0 5 w P R $ % @ S a '
277 self._stream = stream 2[ I J K L ) M 1 T x G y ; Qb? H = U : ^ E _ ] F 6 WbW 7 V 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 . * , / + - 8 X 2 9 Y 3 Q N O ! Z 4 # 0 5 w P R $ % @ S a '
278 return self 2[ I J K L ) M 1 T x G y ; Qb? H = U : ^ E _ ] F 6 WbW 7 V 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 . * , / + - 8 X 2 9 Y 3 Q N O ! Z 4 # 0 5 w P R $ % @ S a '
280 def close(self):
281 """Destroy the graph builder."""
282 GB_end_capture_if_needed(self, True) 11T;H:EF7Vbcdefghijklmnopqrstuva
283 self._h_graph.reset() 11T;H:EF7Vbcdefghijklmnopqrstuva
284 self._h_stream.reset() 11T;H:EF7Vbcdefghijklmnopqrstuva
285 self._state = CLOSED 11T;H:EF7Vbcdefghijklmnopqrstuva
286 self._stream = None 11T;H:EF7Vbcdefghijklmnopqrstuva
288 @property
289 def stream(self) -> Stream:
290 """Returns the stream associated with the graph builder."""
291 return self._stream 1M1Txy;H=U:E_F6W7VbcdefghijklmnopqzrstuvABCD.*,/+-8X29Y3QNO!Z4#05wPR$%@Sa'
293 @property
294 def is_join_required(self) -> bool:
295 """Returns True if this graph builder must be joined before building is ended."""
296 return self._kind == FORKED 1THEFbcdefghijklmnopqrstuva
298 @property
299 def graph_definition(self) -> GraphDefinition:
300 """The captured graph as an explicit :class:`~graph.GraphDefinition`.
302 .. versionadded:: 1.1.0
304 The returned :class:`~graph.GraphDefinition` is a view of the same
305 graph this builder is producing: nodes added through it appear in
306 subsequent :meth:`complete` and :meth:`debug_dot_print` calls, and
307 the view stays valid even after the builder is closed.
309 This lets you mix the capture and explicit APIs on a single graph,
310 for example to inspect what was captured, augment it with extra
311 nodes, or build a conditional body entirely with the explicit API.
313 Availability:
315 - **Primary builders** (created by :meth:`Device.create_graph_builder`
316 or :meth:`Stream.create_graph_builder`): only after
317 :meth:`end_building`.
319 - **Conditional-body builders** (returned by :meth:`if_then`,
320 :meth:`if_else`, :meth:`while_loop`, :meth:`switch`): both before
321 :meth:`begin_building` and after :meth:`end_building`. The body
322 graph already exists when the conditional is created, so you may
323 populate it through this view without ever calling
324 :meth:`begin_building` on the body builder.
326 - **Forked builders** (returned by :meth:`split`): never. Forked
327 builders share the primary builder's graph; access it through the
328 primary instead.
330 Returns
331 -------
332 GraphDefinition
333 A view of the graph being built.
335 Raises
336 ------
337 RuntimeError
338 If the builder is closed, forked, currently building, or (for
339 primary builders) has not started building yet. A
340 :class:`~graph.GraphDefinition` obtained before :meth:`close`
341 keeps working; only fresh access through this property is
342 rejected once the builder is closed.
343 """
344 GB_check_open(self) 2x G y ; Qb? H = U :
345 if self._kind == FORKED: 2x G y Qb? H = U :
346 raise RuntimeError( 1H
347 "graph_definition is unavailable on forked graph builders; "
348 "access it through the primary builder instead."
349 )
350 elif self._state == CAPTURING: 2x G y Qb? = U :
351 raise RuntimeError( 1G?
352 "graph_definition is unavailable while capture is in "
353 "progress; call end_building() first."
354 )
355 elif self._kind == PRIMARY: 2x y Qb= U :
356 if self._state == CAPTURE_NOT_STARTED: 2Qb= U :
357 raise RuntimeError( 2Qb
358 "graph_definition is unavailable before begin_building() on "
359 "a primary builder; no graph has been created yet."
360 )
361 return GraphDefinition._from_handle(self._h_graph) 1xy=U:
363 def begin_building(self, mode: str | None = "relaxed") -> GraphBuilder:
364 """Begins the building process.
366 Build `mode` for controlling interaction with other API calls must be one of the following:
368 - `global` : Prohibit potentially unsafe operations across all streams in the process.
369 - `thread_local` : Prohibit potentially unsafe operations in streams created by the current thread.
370 - `relaxed` : The local thread is not prohibited from potentially unsafe operations.
372 Parameters
373 ----------
374 mode : str, optional
375 Build mode to control the interaction with other API calls that are porentially unsafe.
376 Default set to use relaxed.
378 """
379 GB_check_open(self) 1[IJKL)M1TxGy;?H=U:^E_]F6W7VbcdefghijklmnopqzrstuvABCD.*,/+-8X29Y3QNO!Z4#05wPR$%@Sa'
380 if self._state != CAPTURE_NOT_STARTED: 1[IJKL)M1TxGy;?H=U:^E_]F6W7VbcdefghijklmnopqzrstuvABCD.*,/+-8X29Y3QNO!Z4#05wPR$%@Sa'
381 if self._state == CAPTURING: 1[6
382 raise RuntimeError("Graph builder is already building.") 1[
383 else:
384 raise RuntimeError("Cannot resume building after building has ended.") 16
385 cdef cydriver.CUstreamCaptureMode c_mode
386 if mode == "global": 1[IJKL)M1TxGy;?H=U:^E_]F6W7VbcdefghijklmnopqzrstuvABCD.*,/+-8X29Y3QNO!Z4#05wPR$%@Sa'
387 c_mode = cydriver.CU_STREAM_CAPTURE_MODE_GLOBAL 1./89Q!#@
388 elif mode == "thread_local": 1[IJKL)M1TxGy;?H=U:^E_]F6W7VbcdefghijklmnopqzrstuvABCD*,+-X2Y3NOZ405wPR$%@Sa'
389 c_mode = cydriver.CU_STREAM_CAPTURE_MODE_THREAD_LOCAL 1,-23O45@
390 elif mode == "relaxed": 1[IJKL)M1TxGy;?H=U:^E_]F6W7VbcdefghijklmnopqzrstuvABCD*+XYNZ0wPR$%@Sa'
391 c_mode = cydriver.CU_STREAM_CAPTURE_MODE_RELAXED 1[IJKL)M1TxGy;?H=U:^E_]F6W7VbcdefghijklmnopqzrstuvABCD*+XYNZ0wPR$%@Sa'
392 else:
393 raise ValueError(f"Unsupported build mode: {mode}") 1@
395 cdef cydriver.CUstream c_stream = as_cu(self._h_stream) 1[IJKL)M1TxGy;?H=U:^E_]F6W7VbcdefghijklmnopqzrstuvABCD.*,/+-8X29Y3QNO!Z4#05wPR$%@Sa'
396 cdef cydriver.CUgraph c_graph
397 cdef cydriver.CUstreamCaptureStatus c_status
398 if self._kind == CONDITIONAL_BODY: 1[IJKL)M1TxGy;?H=U:^E_]F6W7VbcdefghijklmnopqzrstuvABCD.*,/+-8X29Y3QNO!Z4#05wPR$%@Sa'
399 c_graph = as_cu(self._h_graph) 1xGbcdefghijklmnopqzrstuvABCDwa
400 with nogil: 1xGbcdefghijklmnopqzrstuvABCDwa
401 HANDLE_RETURN(cydriver.cuStreamBeginCaptureToGraph( 1xGbcdefghijklmnopqzrstuvABCDwa
402 c_stream, c_graph, NULL, NULL, 0, c_mode))
403 self._state = CAPTURING 1xGbcdefghijklmnopqzrstuvABCDwa
404 else:
405 with nogil: 1[IJKL)M1TxGy;?H=U:^E_]F6W7VbcdefghijklmnopqzrstuvABCD.*,/+-8X29Y3QNO!Z4#05wPR$%@Sa'
406 HANDLE_RETURN(cydriver.cuStreamBeginCapture(c_stream, c_mode)) 1[IJKL)M1TxGy;?H=U:^E_]F6W7VbcdefghijklmnopqzrstuvABCD.*,/+-8X29Y3QNO!Z4#05wPR$%@Sa'
407 # Capture is active now; set CAPTURING before the calls below so a
408 # failure in _get_capture_info/create_graph_handle still lets
409 # cleanup end the capture rather than leaving the stream poisoned.
410 self._state = CAPTURING 1[IJKL)M1TxGy;?H=U:^E_]F6W7VbcdefghijklmnopqzrstuvABCD.*,/+-8X29Y3QNO!Z4#05wPR$%@Sa'
411 with nogil: 1[IJKL)M1TxGy;?H=U:^E_]F6W7VbcdefghijklmnopqzrstuvABCD.*,/+-8X29Y3QNO!Z4#05wPR$%@Sa'
412 # The driver rejects a NULL captureStatus_out, so pass a
413 # stack-local even though we only want the graph handle.
414 _get_capture_info(c_stream, &c_status, &c_graph) 1[IJKL)M1TxGy;?H=U:^E_]F6W7VbcdefghijklmnopqzrstuvABCD.*,/+-8X29Y3QNO!Z4#05wPR$%@Sa'
415 self._h_graph = create_graph_handle(c_graph) 1[IJKL)M1TxGy;?H=U:^E_]F6W7VbcdefghijklmnopqzrstuvABCD.*,/+-8X29Y3QNO!Z4#05wPR$%@Sa'
416 return self 1[IJKL)M1TxGy;?H=U:^E_]F6W7VbcdefghijklmnopqzrstuvABCD.*,/+-8X29Y3QNO!Z4#05wPR$%@Sa'
418 @property
419 def is_building(self) -> bool:
420 """Returns True if the graph builder is currently building."""
421 GB_check_open(self) 1[IJKL)M1xGy;?H=U:^E]F6W7VbcdefghijklmnopqzrstuvABCD.*,/+-8X29Y3QNO!Z4#05wPR$%@Sa'
422 cdef cydriver.CUstream c_stream = as_cu(self._h_stream) 1[IJKL)M1xGy;?H=U:^E]F6W7VbcdefghijklmnopqzrstuvABCD.*,/+-8X29Y3QNO!Z4#05wPR$%@Sa'
423 cdef cydriver.CUstreamCaptureStatus status
424 with nogil: 1[IJKL)M1xGy;?H=U:^E]F6W7VbcdefghijklmnopqzrstuvABCD.*,/+-8X29Y3QNO!Z4#05wPR$%@Sa'
425 _get_capture_info(c_stream, &status, NULL) 1[IJKL)M1xGy;?H=U:^E]F6W7VbcdefghijklmnopqzrstuvABCD.*,/+-8X29Y3QNO!Z4#05wPR$%@Sa'
426 if status == cydriver.CU_STREAM_CAPTURE_STATUS_NONE: 1[IJKL)M1xGy;?H=U:^E]F6W7VbcdefghijklmnopqzrstuvABCD.*,/+-8X29Y3QNO!Z4#05wPR$%@Sa'
427 return False 1]
428 elif status == cydriver.CU_STREAM_CAPTURE_STATUS_ACTIVE:
429 return True 1[IJKL)M1xGy;?H=U:^E]F6W7VbcdefghijklmnopqzrstuvABCD.*,/+-8X29Y3QNO!Z4#05wPR$%@Sa'
430 elif status == cydriver.CU_STREAM_CAPTURE_STATUS_INVALIDATED:
431 raise RuntimeError(
432 "Build process encountered an error and has been invalidated. Build process must now be ended."
433 )
434 else:
435 raise NotImplementedError(f"Unsupported capture status type received: {status}")
437 def end_building(self) -> GraphBuilder:
438 """Ends the building process."""
439 GB_check_open(self) 1[IJKL)M1xGy;?H=U:^E]F6W7VbcdefghijklmnopqzrstuvABCD.*,/+-8X29Y3QNO!Z4#05wPR$%@Sa'
440 if not self.is_building: 1[IJKL)M1xGy;?H=U:^E]F6W7VbcdefghijklmnopqzrstuvABCD.*,/+-8X29Y3QNO!Z4#05wPR$%@Sa'
441 raise RuntimeError("Graph builder is not building.")
442 cdef cydriver.CUstream c_stream = as_cu(self._h_stream) 1[IJKL)M1xGy;?H=U:^E]F6W7VbcdefghijklmnopqzrstuvABCD.*,/+-8X29Y3QNO!Z4#05wPR$%@Sa'
443 cdef cydriver.CUgraph c_graph
444 with nogil: 1[IJKL)M1xGy;?H=U:^E]F6W7VbcdefghijklmnopqzrstuvABCD.*,/+-8X29Y3QNO!Z4#05wPR$%@Sa'
445 HANDLE_RETURN(cydriver.cuStreamEndCapture(c_stream, &c_graph)) 1[IJKL)M1xGy;?H=U:^E]F6W7VbcdefghijklmnopqzrstuvABCD.*,/+-8X29Y3QNO!Z4#05wPR$%@Sa'
447 # TODO: Resolving https://github.com/NVIDIA/cuda-python/issues/617 would allow us to
448 # resume the build process after the first call to end_building()
449 self._state = CAPTURE_ENDED 1[IJKL)M1xGy;?H=U:^E]F6W7VbcdefghijklmnopqzrstuvABCD.*,/+-8X29Y3QNO!Z4#05wPR$%@Sa'
450 return self 1[IJKL)M1xGy;?H=U:^E]F6W7VbcdefghijklmnopqzrstuvABCD.*,/+-8X29Y3QNO!Z4#05wPR$%@Sa'
452 def complete(self, options: GraphCompleteOptions | None = None) -> Graph:
453 """Completes the graph builder and returns the built :obj:`~graph.Graph` object.
455 Parameters
456 ----------
457 options : :obj:`~graph.GraphCompleteOptions`, optional
458 Customizable dataclass for the graph builder completion options.
460 Returns
461 -------
462 graph : :obj:`~graph.Graph`
463 The newly built graph.
465 """
466 GB_check_open(self) 1IJKL)M1TxyUEF6W7VbcdefghijklmnopqzrstuvABCD.*,/+-8X29Y3QNO!Z4#05wPR$%S'
467 if self._state != CAPTURE_ENDED: 1IJKL)M1xyUEF6W7VbcdefghijklmnopqzrstuvABCD.*,/+-8X29Y3QNO!Z4#05wPR$%S'
468 raise RuntimeError("Graph has not finished building.") 1)
470 return _instantiate_graph(as_py(self._h_graph), options) 1IJKL)M1xyUEF6W7VbcdefghijklmnopqzrstuvABCD.*,/+-8X29Y3QNO!Z4#05wPR$%S'
472 def debug_dot_print(self, path: str, options: GraphDebugPrintOptions | None = None) -> None:
473 """Generates a DOT debug file for the graph builder.
475 Parameters
476 ----------
477 path : str
478 File path to use for writting debug DOT output
479 options : :obj:`~graph.GraphDebugPrintOptions`, optional
480 Customizable dataclass for the debug print options.
482 """
483 GB_check_open(self) 1a
484 if self._state != CAPTURE_ENDED: 1a
485 raise RuntimeError("Graph has not finished building.")
486 cdef unsigned int c_flags = options._to_flags() if options else 0 1a
487 cdef cydriver.CUgraph c_graph = as_cu(self._h_graph) 1a
488 cdef bytes b_path = path.encode('utf-8') 1a
489 cdef const char* c_path = b_path 1a
490 with nogil: 1a
491 HANDLE_RETURN(cydriver.cuGraphDebugDotPrint(c_graph, c_path, c_flags)) 1a
493 def split(self, count: int) -> tuple[GraphBuilder, ...]:
494 """Splits the original graph builder into multiple graph builders.
496 The new builders inherit work dependencies from the original builder.
497 The original builder is reused for the split and is returned first in the tuple.
499 Parameters
500 ----------
501 count : int
502 The number of graph builders to split the graph builder into.
504 Returns
505 -------
506 graph_builders : tuple[:obj:`~graph.GraphBuilder`, ...]
507 A tuple of split graph builders. The first graph builder in the tuple
508 is always the original graph builder.
510 """
511 if count < 2: 2T H E F Wbb c d e f g h i j k l m n o p q r s t u v a
512 raise ValueError(f"Invalid split count: expecting >= 2, got {count}") 1E
513 GB_check_open(self) 2T H E F Wbb c d e f g h i j k l m n o p q r s t u v a
514 if self._state != CAPTURING: 2T H E F Wbb c d e f g h i j k l m n o p q r s t u v a
515 raise RuntimeError("Graph builder must be building before it can be split.") 2Wb
517 event = self._stream.record() 1THEFbcdefghijklmnopqrstuva
518 result = [self] 1THEFbcdefghijklmnopqrstuva
519 for i in range(count - 1): 1THEFbcdefghijklmnopqrstuva
520 stream = self._stream.device.create_stream() 1THEFbcdefghijklmnopqrstuva
521 stream.wait(event) 1THEFbcdefghijklmnopqrstuva
522 result.append(GB_init_forked(stream, self._h_graph)) 1THEFbcdefghijklmnopqrstuva
523 event.close() 1THEFbcdefghijklmnopqrstuva
524 return tuple(result) 1THEFbcdefghijklmnopqrstuva
526 @staticmethod
527 def join(*graph_builders: GraphBuilder) -> GraphBuilder:
528 """Joins multiple graph builders into a single graph builder.
530 The returned builder inherits work dependencies from the provided builders.
532 Parameters
533 ----------
534 *graph_builders : :obj:`~graph.GraphBuilder`
535 The graph builders to join.
537 Returns
538 -------
539 graph_builder : :obj:`~graph.GraphBuilder`
540 The newly joined graph builder.
542 """
543 if any(not isinstance(builder, GraphBuilder) for builder in graph_builders): 1THEFbcdefghijklmnopqrstuva
544 raise TypeError("All arguments must be GraphBuilder instances")
545 if len(graph_builders) < 2: 1THEFbcdefghijklmnopqrstuva
546 raise ValueError("Must join with at least two graph builders") 1E
548 # Discover the root builder others should join
549 root_idx = 0 1THEFbcdefghijklmnopqrstuva
550 for i, builder in enumerate(graph_builders): 1THEFbcdefghijklmnopqrstuva
551 if not builder.is_join_required: 1THEFbcdefghijklmnopqrstuva
552 root_idx = i 1THEFbcdefghijklmnopqrstuva
553 break 1THEFbcdefghijklmnopqrstuva
555 # Join all onto the root builder
556 root_bdr = graph_builders[root_idx] 1THEFbcdefghijklmnopqrstuva
557 for idx, builder in enumerate(graph_builders): 1THEFbcdefghijklmnopqrstuva
558 if idx == root_idx: 1THEFbcdefghijklmnopqrstuva
559 continue 1THEFbcdefghijklmnopqrstuva
560 root_bdr.stream.wait(builder.stream) 1THEFbcdefghijklmnopqrstuva
561 builder.close() 1THEFbcdefghijklmnopqrstuva
563 return root_bdr 1THEFbcdefghijklmnopqrstuva
565 def __cuda_stream__(self) -> tuple[int, int]:
566 """Return an instance of a __cuda_stream__ protocol."""
567 GB_check_open(self)
568 return self.stream.__cuda_stream__()
570 def _get_conditional_context(self) -> driver.CUcontext:
571 return self._stream.context.handle 1xGybcdefghijklmnopqzrstuvABCDwa
573 def create_condition(self, default_value: int | None = None) -> GraphCondition:
574 """Create a condition variable for use with conditional nodes.
576 The returned :class:`GraphCondition` object is passed to conditional-node
577 builder methods (:meth:`if_then`, :meth:`if_else`, :meth:`while_loop`,
578 :meth:`switch`). Its value is controlled at runtime by device code via
579 ``cudaGraphSetConditional``.
581 Parameters
582 ----------
583 default_value : int, optional
584 The default value to assign to the condition. If None, no
585 default is assigned.
587 Returns
588 -------
589 GraphCondition
590 A condition variable for controlling conditional execution.
591 """
592 GB_check_open(self) 1xGybcdefghijklmnopqzrstuvABCDwa
593 if cy_driver_version() < (12, 3, 0): 1xGybcdefghijklmnopqzrstuvABCDwa
594 raise RuntimeError(f"Driver version {'.'.join(map(str, cy_driver_version()))} does not support conditional handles")
595 if cy_binding_version() < (12, 3, 0): 1xGybcdefghijklmnopqzrstuvABCDwa
596 raise RuntimeError(f"Binding version {'.'.join(map(str, cy_binding_version()))} does not support conditional handles")
597 if default_value is not None: 1xGybcdefghijklmnopqzrstuvABCDwa
598 flags = driver.CU_GRAPH_COND_ASSIGN_DEFAULT 1xGyABCDw
599 else:
600 default_value = 0 1bcdefghijklmnopqzrstuva
601 flags = 0 1bcdefghijklmnopqzrstuva
603 status, _, graph, *_, _ = handle_return(driver.cuStreamGetCaptureInfo(self._stream.handle)) 1xGybcdefghijklmnopqzrstuvABCDwa
604 if status != driver.CUstreamCaptureStatus.CU_STREAM_CAPTURE_STATUS_ACTIVE: 1xGybcdefghijklmnopqzrstuvABCDwa
605 raise RuntimeError("Cannot create a condition when graph is not being built")
607 raw_handle = handle_return( 1xGybcdefghijklmnopqzrstuvABCDwa
608 driver.cuGraphConditionalHandleCreate(graph, self._get_conditional_context(), default_value, flags) 1xGybcdefghijklmnopqzrstuvABCDwa
609 )
610 return GraphCondition._from_handle(<cydriver.CUgraphConditionalHandle><intptr_t>int(raw_handle)) 1xGybcdefghijklmnopqzrstuvABCDwa
612 def if_then(self, condition: GraphCondition) -> GraphBuilder:
613 """Adds an if condition branch and returns a new graph builder for it.
615 The resulting if graph will only execute the branch if the
616 condition evaluates to true at runtime.
618 The new builder inherits work dependencies from the original builder.
620 Parameters
621 ----------
622 condition : :class:`~graph.GraphCondition`
623 The condition variable from :meth:`create_condition` controlling
624 whether the branch executes.
626 Returns
627 -------
628 graph_builder : :obj:`~graph.GraphBuilder`
629 The newly created conditional graph builder.
631 """
632 GB_check_open(self) 1xGybcdefghizra
633 if cy_driver_version() < (12, 3, 0): 1xGybcdefghizra
634 raise RuntimeError(f"Driver version {'.'.join(map(str, cy_driver_version()))} does not support conditional if")
635 if cy_binding_version() < (12, 3, 0): 1xGybcdefghizra
636 raise RuntimeError(f"Binding version {'.'.join(map(str, cy_binding_version()))} does not support conditional if")
637 if not isinstance(condition, GraphCondition): 1xGybcdefghizra
638 raise TypeError(
639 f"condition must be a GraphCondition object (from "
640 f"GraphBuilder.create_condition()), got {type(condition).__name__}")
641 node_params = driver.CUgraphNodeParams() 1xGybcdefghizra
642 node_params.type = driver.CUgraphNodeType.CU_GRAPH_NODE_TYPE_CONDITIONAL 1xGybcdefghizra
643 node_params.conditional.handle = condition.handle 1xGybcdefghizra
644 node_params.conditional.type = driver.CUgraphConditionalNodeType.CU_GRAPH_COND_TYPE_IF 1xGybcdefghizra
645 node_params.conditional.size = 1 1xGybcdefghizra
646 node_params.conditional.ctx = self._get_conditional_context() 1xGybcdefghizra
647 return GB_cond_with_params(self, node_params)[0] 1xGybcdefghizra
649 def if_else(self, condition: GraphCondition) -> tuple[GraphBuilder, GraphBuilder]:
650 """Adds an if-else condition branch and returns new graph builders for both branches.
652 The resulting if graph will execute the branch if the condition
653 evaluates to true at runtime, otherwise the else branch will execute.
655 The new builders inherit work dependencies from the original builder.
657 Parameters
658 ----------
659 condition : :class:`~graph.GraphCondition`
660 The condition variable from :meth:`create_condition` controlling
661 which branch executes.
663 Returns
664 -------
665 graph_builders : tuple[:obj:`~graph.GraphBuilder`, :obj:`~graph.GraphBuilder`]
666 A tuple of two new graph builders, one for the if branch and one for the else branch.
668 """
669 GB_check_open(self) 1jklmnopq
670 if cy_driver_version() < (12, 8, 0): 1jklmnopq
671 raise RuntimeError(f"Driver version {'.'.join(map(str, cy_driver_version()))} does not support conditional if-else")
672 if cy_binding_version() < (12, 8, 0): 1jklmnopq
673 raise RuntimeError(f"Binding version {'.'.join(map(str, cy_binding_version()))} does not support conditional if-else")
674 if not isinstance(condition, GraphCondition): 1jklmnopq
675 raise TypeError(
676 f"condition must be a GraphCondition object (from "
677 f"GraphBuilder.create_condition()), got {type(condition).__name__}")
678 node_params = driver.CUgraphNodeParams() 1jklmnopq
679 node_params.type = driver.CUgraphNodeType.CU_GRAPH_NODE_TYPE_CONDITIONAL 1jklmnopq
680 node_params.conditional.handle = condition.handle 1jklmnopq
681 node_params.conditional.type = driver.CUgraphConditionalNodeType.CU_GRAPH_COND_TYPE_IF 1jklmnopq
682 node_params.conditional.size = 2 1jklmnopq
683 node_params.conditional.ctx = self._get_conditional_context() 1jklmnopq
684 return GB_cond_with_params(self, node_params) 1jklmnopq
686 def switch(self, condition: GraphCondition, count: int) -> tuple[GraphBuilder, ...]:
687 """Adds a switch condition branch and returns new graph builders for all cases.
689 The resulting switch graph will execute the branch whose case index
690 matches the value of the condition at runtime. If no match is found, no
691 branch will be executed.
693 The new builders inherit work dependencies from the original builder.
695 Parameters
696 ----------
697 condition : :class:`~graph.GraphCondition`
698 The condition variable from :meth:`create_condition` selecting
699 which case executes.
700 count : int
701 The number of cases to add to the switch conditional.
703 Returns
704 -------
705 graph_builders : tuple[:obj:`~graph.GraphBuilder`, ...]
706 A tuple of new graph builders, one for each branch.
708 """
709 GB_check_open(self) 1stuvw
710 if cy_driver_version() < (12, 8, 0): 1stuvw
711 raise RuntimeError(f"Driver version {'.'.join(map(str, cy_driver_version()))} does not support conditional switch")
712 if cy_binding_version() < (12, 8, 0): 1stuvw
713 raise RuntimeError(f"Binding version {'.'.join(map(str, cy_binding_version()))} does not support conditional switch")
714 if not isinstance(condition, GraphCondition): 1stuvw
715 raise TypeError(
716 f"condition must be a GraphCondition object (from "
717 f"GraphBuilder.create_condition()), got {type(condition).__name__}")
718 node_params = driver.CUgraphNodeParams() 1stuvw
719 node_params.type = driver.CUgraphNodeType.CU_GRAPH_NODE_TYPE_CONDITIONAL 1stuvw
720 node_params.conditional.handle = condition.handle 1stuvw
721 node_params.conditional.type = driver.CUgraphConditionalNodeType.CU_GRAPH_COND_TYPE_SWITCH 1stuvw
722 node_params.conditional.size = count 1stuvw
723 node_params.conditional.ctx = self._get_conditional_context() 1stuvw
724 return GB_cond_with_params(self, node_params) 1stuvw
726 def while_loop(self, condition: GraphCondition) -> GraphBuilder:
727 """Adds a while loop and returns a new graph builder for it.
729 The resulting while loop graph will execute the branch repeatedly at runtime
730 until the condition evaluates to false.
732 The new builder inherits work dependencies from the original builder.
734 Parameters
735 ----------
736 condition : :class:`~graph.GraphCondition`
737 The condition variable from :meth:`create_condition` controlling
738 loop continuation.
740 Returns
741 -------
742 graph_builder : :obj:`~graph.GraphBuilder`
743 The newly created while loop graph builder.
745 """
746 GB_check_open(self) 1ABCD
747 if cy_driver_version() < (12, 3, 0): 1ABCD
748 raise RuntimeError(f"Driver version {'.'.join(map(str, cy_driver_version()))} does not support conditional while loop")
749 if cy_binding_version() < (12, 3, 0): 1ABCD
750 raise RuntimeError(f"Binding version {'.'.join(map(str, cy_binding_version()))} does not support conditional while loop")
751 if not isinstance(condition, GraphCondition): 1ABCD
752 raise TypeError(
753 f"condition must be a GraphCondition object (from "
754 f"GraphBuilder.create_condition()), got {type(condition).__name__}")
755 node_params = driver.CUgraphNodeParams() 1ABCD
756 node_params.type = driver.CUgraphNodeType.CU_GRAPH_NODE_TYPE_CONDITIONAL 1ABCD
757 node_params.conditional.handle = condition.handle 1ABCD
758 node_params.conditional.type = driver.CUgraphConditionalNodeType.CU_GRAPH_COND_TYPE_WHILE 1ABCD
759 node_params.conditional.size = 1 1ABCD
760 node_params.conditional.ctx = self._get_conditional_context() 1ABCD
761 return GB_cond_with_params(self, node_params)[0] 1ABCD
763 def embed(self, GraphBuilder child):
764 """Embed a previously-built :obj:`~graph.GraphBuilder` as a child node.
766 Parameters
767 ----------
768 child : :obj:`~graph.GraphBuilder`
769 The child graph builder. Must have finished building.
770 """
771 GB_check_open(self) 1M
772 if child._state != CAPTURE_ENDED: 1M
773 raise ValueError("Child graph has not finished building.")
775 if not self.is_building: 1M
776 raise ValueError("Parent graph is not being built.")
778 stream_handle = self._stream.handle 1M
779 _, _, graph_out, *deps_info_out, num_dependencies_out = handle_return( 1M
780 driver.cuStreamGetCaptureInfo(stream_handle) 1M
781 )
783 # See https://github.com/NVIDIA/cuda-python/pull/879#issuecomment-3211054159
784 # for rationale
785 deps_info_trimmed = deps_info_out[:num_dependencies_out] 1M
786 deps_info_update = [ 1M
787 [ 1M
788 handle_return( 1M
789 driver.cuGraphAddChildGraphNode( 1M
790 graph_out, *deps_info_trimmed, num_dependencies_out, as_py(child._h_graph) 1M
791 )
792 )
793 ]
794 ] + [None] * (len(deps_info_out) - 1) 1M
795 handle_return( 1M
796 driver.cuStreamUpdateCaptureDependencies( 1M
797 stream_handle, 1M
798 *deps_info_update, # dependencies, edgeData
799 1,
800 driver.CUstreamUpdateCaptureDependencies_flags.CU_STREAM_SET_CAPTURE_DEPENDENCIES, 1M
801 )
802 )
804 def callback(self, fn, *, user_data=None) -> None:
805 """Add a host callback to the graph during stream capture.
807 The callback runs on the host CPU when the graph reaches this point
808 in execution. Two modes are supported:
810 - **Python callable**: Pass any callable. The GIL is acquired
811 automatically. The callable must take no arguments; use closures
812 or ``functools.partial`` to bind state.
813 - **ctypes function pointer**: Pass a ``ctypes.CFUNCTYPE`` instance.
814 The function receives a single ``void*`` argument (the
815 ``user_data``). The caller must keep the ctypes wrapper alive
816 for the lifetime of the graph.
818 .. warning::
820 Callbacks must not call CUDA API functions. Doing so may
821 deadlock or corrupt driver state.
823 Parameters
824 ----------
825 fn : callable or ctypes function pointer
826 The callback function.
827 user_data : int or bytes-like, optional
828 Only for ctypes function pointers. If ``int``, passed as a raw
829 pointer (caller manages lifetime). If bytes-like, the data is
830 copied and its lifetime is tied to the graph.
831 """
832 GB_check_open(self) 1IJKL
833 cdef Stream stream = self._stream 1IJKL
834 cdef cydriver.CUstream c_stream = as_cu(stream._h_stream) 1IJKL
835 cdef cydriver.CUstreamCaptureStatus capture_status
837 with nogil: 1IJKL
838 _get_capture_info(c_stream, &capture_status, NULL) 1IJKL
840 if capture_status != cydriver.CU_STREAM_CAPTURE_STATUS_ACTIVE: 1IJKL
841 raise RuntimeError("Cannot add callback when graph is not being built")
843 cdef cydriver.CUhostFn c_fn
844 cdef void* c_user_data = NULL 1IJKL
845 cdef OpaqueHandle fn_owner, data_owner
846 _resolve_host_callback(fn, user_data, &c_fn, &c_user_data, &fn_owner, &data_owner) 1IJKL
848 with nogil: 1IJKL
849 HANDLE_RETURN(cydriver.cuLaunchHostFunc(c_stream, c_fn, c_user_data)) 1IJKL
851 # Capturing the host function added a node to the graph; it is now the
852 # stream's sole capture dependency. Key the callback's owners to it so
853 # they live in the graph's slot table like any explicitly-added node.
854 cdef cydriver.CUgraphNode host_node = _capture_tail_node(c_stream) 1IJKL
855 _attach_host_callback_owners(self._h_graph, host_node, fn_owner, data_owner) 1IJKL
858cdef inline int GB_check_open(GraphBuilder gb) except -1:
859 """Reject operations on a builder that has been closed.
861 A CLOSED builder has reset its stream and graph handles, so any method
862 that dereferences them would read a null handle (or, for the cached
863 Stream, a None typed as cdef Stream). Guarding here yields a clear error
864 instead.
865 """
866 if gb._state == CLOSED: 2[ I J K L ) M 1 T x G y ; Qb? H = U : ^ E _ ] F 6 WbW 7 V 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 . * , / + - 8 X 2 9 Y 3 Q N O ! Z 4 # 0 5 w P R $ % @ S a '
867 raise RuntimeError("Graph builder has been closed.") 1T;
868 return 0 2[ I J K L ) M 1 T x G y ; Qb? H = U : ^ E _ ] F 6 WbW 7 V 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 . * , / + - 8 X 2 9 Y 3 Q N O ! Z 4 # 0 5 w P R $ % @ S a '
871cdef inline int GB_end_capture_if_needed(GraphBuilder gb, bint check_status) except -1 nogil:
872 """End an in-progress capture if this builder owns it.
874 Only a CAPTURING PRIMARY or CONDITIONAL_BODY builder owns the live
875 capture. A FORKED builder must not call cuStreamEndCapture: the driver
876 requires forked streams to be joined first.
878 check_status=True checks the driver return (close()); False ignores it
879 (__dealloc__).
880 """
881 cdef cydriver.CUgraph c_graph
882 cdef cydriver.CUresult err
883 cdef cydriver.CUstream c_stream
884 if gb._h_stream and gb._state == CAPTURING and gb._kind != FORKED: 2[ I J K L ) M 1 T x G y ; Qb? H = U : ^ E _ ] F 6 WbW 7 V 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 . * , / + - 8 X 2 9 Y 3 Q N O ! Z 4 # 0 5 w P R $ % @ S a '
885 c_stream = as_cu(gb._h_stream) 1T_
886 with nogil: 1T_
887 err = cydriver.cuStreamEndCapture(c_stream, &c_graph) 1T_
888 if check_status: 1T_
889 HANDLE_RETURN(err)
890 return 0 2[ I J K L ) M 1 T x G y ; Qb? H = U : ^ E _ ] F 6 WbW 7 V 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 . * , / + - 8 X 2 9 Y 3 Q N O ! Z 4 # 0 5 w P R $ % @ S a '
893cdef inline GraphBuilder GB_init_forked(Stream stream, GraphHandle h_primary_graph):
894 cdef GraphBuilder gb = GraphBuilder.__new__(GraphBuilder) 1THEFbcdefghijklmnopqrstuva
895 # A FORKED builder captures into the primary's CUgraph. It holds the
896 # primary's GraphHandle so conditional bodies created on it (via
897 # GB_init_conditional -> create_graph_handle_ref(cond_graph, parent._h_graph))
898 # have a valid parent handle to pin.
899 gb._h_graph = h_primary_graph 1THEFbcdefghijklmnopqrstuva
900 gb._h_stream = stream._h_stream 1THEFbcdefghijklmnopqrstuva
901 gb._kind = FORKED 1THEFbcdefghijklmnopqrstuva
902 gb._state = CAPTURING 1THEFbcdefghijklmnopqrstuva
903 gb._stream = stream 1THEFbcdefghijklmnopqrstuva
904 return gb 1THEFbcdefghijklmnopqrstuva
907cdef inline GraphBuilder GB_init_conditional(Stream stream, cydriver.CUgraph cond_graph, GraphBuilder parent):
908 cdef GraphBuilder gb = GraphBuilder.__new__(GraphBuilder) 1xGybcdefghijklmnopqzrstuvABCDwa
909 gb._h_graph = create_graph_handle_ref(cond_graph, parent._h_graph) 1xGybcdefghijklmnopqzrstuvABCDwa
910 gb._h_stream = stream._h_stream 1xGybcdefghijklmnopqzrstuvABCDwa
911 gb._kind = CONDITIONAL_BODY 1xGybcdefghijklmnopqzrstuvABCDwa
912 gb._state = CAPTURE_NOT_STARTED 1xGybcdefghijklmnopqzrstuvABCDwa
913 gb._stream = stream 1xGybcdefghijklmnopqzrstuvABCDwa
914 return gb 1xGybcdefghijklmnopqzrstuvABCDwa
917cdef inline int _get_capture_info(
918 cydriver.CUstream stream,
919 cydriver.CUstreamCaptureStatus* status,
920 cydriver.CUgraph* graph) except?-1 nogil:
921 """Thin wrapper around ``cuStreamGetCaptureInfo`` that papers over the
922 CUDA 12 vs 13 signature change.
924 ``status`` must be non-NULL: the driver rejects ``captureStatus_out=NULL``
925 with ``CUDA_ERROR_INVALID_VALUE``. ``graph`` may be NULL when the caller
926 does not need the graph handle.
927 """
928 IF CUDA_CORE_BUILD_MAJOR >= 13:
929 return HANDLE_RETURN(cydriver.cuStreamGetCaptureInfo( 1[IJKL)M1TxGy;?H=U:^E_]F6W7VbcdefghijklmnopqzrstuvABCD.*,/+-8X29Y3QNO!Z4#05wPR$%@Sa'
930 stream, status, NULL, graph, NULL, NULL, NULL))
931 ELSE:
932 return HANDLE_RETURN(cydriver.cuStreamGetCaptureInfo(
933 stream, status, NULL, graph, NULL, NULL))
936cdef inline cydriver.CUgraphNode _capture_tail_node(cydriver.CUstream stream) except *:
937 """Return the node a freshly-captured single-node operation left as the
938 stream's sole capture dependency (e.g. the host node added by
939 ``cuLaunchHostFunc``). The driver advances the stream's dependency set to
940 the new node, so the next captured op would depend on it.
941 """
942 cdef cydriver.CUstreamCaptureStatus status
943 cdef const cydriver.CUgraphNode* deps = NULL 1IJKL
944 cdef size_t num_deps = 0 1IJKL
945 with nogil: 1IJKL
946 IF CUDA_CORE_BUILD_MAJOR >= 13:
947 HANDLE_RETURN(cydriver.cuStreamGetCaptureInfo( 1IJKL
948 stream, &status, NULL, NULL, &deps, NULL, &num_deps))
949 ELSE:
950 HANDLE_RETURN(cydriver.cuStreamGetCaptureInfo(
951 stream, &status, NULL, NULL, &deps, &num_deps))
952 if num_deps != 1: 1IJKL
953 raise RuntimeError(
954 f"expected exactly one capture dependency after a host callback, got {num_deps}")
955 return <cydriver.CUgraphNode>deps[0] 1IJKL
958cdef inline tuple GB_cond_with_params(GraphBuilder gb, node_params):
959 status, _, graph, *deps_info, num_dependencies = handle_return( 1xGybcdefghijklmnopqzrstuvABCDwa
960 driver.cuStreamGetCaptureInfo(gb._stream.handle) 1xGybcdefghijklmnopqzrstuvABCDwa
961 )
962 if status != driver.CUstreamCaptureStatus.CU_STREAM_CAPTURE_STATUS_ACTIVE: 1xGybcdefghijklmnopqzrstuvABCDwa
963 raise RuntimeError("Cannot add conditional node when not actively capturing")
965 deps_info_update = [ 1xGybcdefghijklmnopqzrstuvABCDwa
966 [handle_return(driver.cuGraphAddNode(graph, *deps_info, num_dependencies, node_params))] 1xGybcdefghijklmnopqzrstuvABCDwa
967 ] + [None] * (len(deps_info) - 1) 1xGybcdefghijklmnopqzrstuvABCDwa
969 handle_return( 1xGybcdefghijklmnopqzrstuvABCDwa
970 driver.cuStreamUpdateCaptureDependencies( 1xGybcdefghijklmnopqzrstuvABCDwa
971 gb._stream.handle, 1xGybcdefghijklmnopqzrstuvABCDwa
972 *deps_info_update, # dependencies, edgeData 1xGybcdefghijklmnopqzrstuvABCDwa
973 1, # numDependencies
974 driver.CUstreamUpdateCaptureDependencies_flags.CU_STREAM_SET_CAPTURE_DEPENDENCIES, 1xGybcdefghijklmnopqzrstuvABCDwa
975 )
976 )
978 return tuple( 1xGybcdefghijklmnopqzrstuvABCDwa
979 GB_init_conditional( 1xGybcdefghijklmnopqzrstuvABCDwa
980 gb._stream.device.create_stream(), 1xGybcdefghijklmnopqzrstuvABCDwa
981 <cydriver.CUgraph><intptr_t>int(node_params.conditional.phGraph_out[i]), 1xGybcdefghijklmnopqzrstuvABCDwa
982 gb, 1xGybcdefghijklmnopqzrstuvABCDwa
983 )
984 for i in range(node_params.conditional.size) 1xGybcdefghijklmnopqzrstuvABCDwa
985 )
988cdef class Graph:
989 """An executable graph.
991 A graph groups a set of CUDA kernels and other CUDA operations together and executes
992 them with a specified dependency tree. It speeds up the workflow by combining the
993 driver activities associated with CUDA kernel launches and CUDA API calls.
995 Graphs must be built using a :obj:`~graph.GraphBuilder` object.
997 """
999 def __init__(self):
1000 raise RuntimeError("directly constructing a Graph instance is not supported")
1002 @staticmethod
1003 cdef Graph _init(cydriver.CUgraphExec graph_exec):
1004 cdef Graph self = Graph.__new__(Graph) 2I J K L ) M 1 x y U E F 6 W 7 V 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 jb` kb{ fblbmbnbob| pb} gb~ abqbbbhbcbrbdbibsbRbSbtbTbUbVbybubvbzbAbBbCbDbEbFbGbHbIbJbwbKbLbMbNbObPb. * , / + - 8 X 2 9 Y 3 Q N O ! Z 4 # 0 5 w P ebR $ % S '
1005 self._h_graph_exec = create_graph_exec_handle(graph_exec) 2I J K L ) M 1 x y U E F 6 W 7 V 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 jb` kb{ fblbmbnbob| pb} gb~ abqbbbhbcbrbdbibsbRbSbtbTbUbVbybubvbzbAbBbCbDbEbFbGbHbIbJbwbKbLbMbNbObPb. * , / + - 8 X 2 9 Y 3 Q N O ! Z 4 # 0 5 w P ebR $ % S '
1006 return self 2I J K L ) M 1 x y U E F 6 W 7 V 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 jb` kb{ fblbmbnbob| pb} gb~ abqbbbhbcbrbdbibsbRbSbtbTbUbVbybubvbzbAbBbCbDbEbFbGbHbIbJbwbKbLbMbNbObPb. * , / + - 8 X 2 9 Y 3 Q N O ! Z 4 # 0 5 w P ebR $ % S '
1008 def close(self) -> None:
1009 """Destroy the graph."""
1010 self._h_graph_exec.reset() 117S
1012 @property
1013 def handle(self) -> driver.CUgraphExec:
1014 """Return the underlying ``CUgraphExec`` object.
1016 .. caution::
1018 This handle is a Python object. To get the memory address of the underlying C
1019 handle, call ``int()`` on the returned object.
1021 """
1022 return as_py(self._h_graph_exec) 11
1024 def update(self, source: "GraphBuilder | GraphDefinition") -> None:
1025 """Update the graph using a new graph definition.
1027 The topology of the provided source must be identical to this graph.
1029 Parameters
1030 ----------
1031 source : :obj:`~graph.GraphBuilder` or :obj:`~graph.GraphDefinition`
1032 The graph definition to update from. A GraphBuilder must have
1033 finished building.
1035 """
1036 from cuda.core.graph import GraphDefinition 2V w P ebR $ %
1038 cdef cydriver.CUgraph cu_graph
1039 cdef cydriver.CUgraphExec cu_exec = as_cu(self._h_graph_exec) 2V w P ebR $ %
1041 if isinstance(source, GraphBuilder): 2V w P ebR $ %
1042 if (<GraphBuilder>source)._state == CLOSED: 1VwPR$
1043 raise ValueError("Source graph builder has been closed.") 1V
1044 if (<GraphBuilder>source)._state != CAPTURE_ENDED: 1wPR$
1045 raise ValueError("Graph has not finished building.") 1$
1046 cu_graph = as_cu((<GraphBuilder>source)._h_graph) 1wPR
1047 elif isinstance(source, GraphDefinition): 2eb%
1048 cu_graph = <cydriver.CUgraph><intptr_t>int(source.handle) 2eb
1049 else:
1050 raise TypeError( 1%
1051 f"expected GraphBuilder or GraphDefinition, got {type(source).__name__}") 1%
1053 cdef cydriver.CUgraphExecUpdateResultInfo result_info
1054 cdef cydriver.CUresult err
1055 with nogil: 2w P ebR
1056 err = cydriver.cuGraphExecUpdate(cu_exec, cu_graph, &result_info) 2w P ebR
1057 if err == cydriver.CUresult.CUDA_ERROR_GRAPH_EXEC_UPDATE_FAILURE: 2w P ebR
1058 reason = driver.CUgraphExecUpdateResult(result_info.result) 1R
1059 msg = f"Graph update failed: {reason.__doc__.strip()} ({reason.name})" 1R
1060 raise CUDAError(msg) 1R
1061 HANDLE_RETURN(err) 2w P eb
1063 def upload(self, stream: Stream) -> None:
1064 """Uploads the graph in a stream.
1066 Parameters
1067 ----------
1068 stream : :obj:`~_stream.Stream`
1069 The stream in which to upload the graph
1071 """
1072 cdef cydriver.CUgraphExec c_exec = as_cu(self._h_graph_exec) 2E W jb` kb{ lbmbnbob| pb} ~ abqbbbcbrbdbubvbwb8 X 2 9 Y 3 Q N O ! Z 4 # 0 5
1073 cdef cydriver.CUstream c_stream = <cydriver.CUstream><intptr_t>int(stream.handle) 2E W jb` kb{ lbmbnbob| pb} ~ abqbbbcbrbdbubvbwb8 X 2 9 Y 3 Q N O ! Z 4 # 0 5
1074 with nogil: 2E W jb` kb{ lbmbnbob| pb} ~ abqbbbcbrbdbubvbwb8 X 2 9 Y 3 Q N O ! Z 4 # 0 5
1075 HANDLE_RETURN(cydriver.cuGraphUpload(c_exec, c_stream)) 2E W jb` kb{ lbmbnbob| pb} ~ abqbbbcbrbdbubvbwb8 X 2 9 Y 3 Q N O ! Z 4 # 0 5
1077 def launch(self, stream: Stream) -> None:
1078 """Launches the graph in a stream.
1080 Parameters
1081 ----------
1082 stream : :obj:`~_stream.Stream`
1083 The stream in which to launch the graph.
1085 """
1086 cdef cydriver.CUgraphExec c_exec = as_cu(self._h_graph_exec) 2I J K L M x y U E 6 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 jb` kb{ fblbmbnbob| pb} gb~ abqbbbhbcbrbdbibybubvbzbAbBbCbDbEbFbGbHbIbJbwbKbLbMbNbObPb8 X 2 9 Y 3 Q N O ! Z 4 # 0 5 w P eb'
1087 cdef cydriver.CUstream c_stream = <cydriver.CUstream><intptr_t>int(stream.handle) 2I J K L M x y U E 6 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 jb` kb{ fblbmbnbob| pb} gb~ abqbbbhbcbrbdbibybubvbzbAbBbCbDbEbFbGbHbIbJbwbKbLbMbNbObPb8 X 2 9 Y 3 Q N O ! Z 4 # 0 5 w P eb'
1088 with nogil: 2I J K L M x y U E 6 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 jb` kb{ fblbmbnbob| pb} gb~ abqbbbhbcbrbdbibybubvbzbAbBbCbDbEbFbGbHbIbJbwbKbLbMbNbObPb8 X 2 9 Y 3 Q N O ! Z 4 # 0 5 w P eb'
1089 HANDLE_RETURN(cydriver.cuGraphLaunch(c_exec, c_stream)) 2I J K L M x y U E 6 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 jb` kb{ fblbmbnbob| pb} gb~ abqbbbhbcbrbdbibybubvbzbAbBbCbDbEbFbGbHbIbJbwbKbLbMbNbObPb8 X 2 9 Y 3 Q N O ! Z 4 # 0 5 w P eb'