Coverage for cuda/core/graph/_graph_builder.pyx: 88.39%
491 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-03 02:41 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-03 02:41 +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 (
13 GraphCondition,
14 GraphDefinition,
15 GD_check_valid,
16)
17from cuda.core.graph._graph_node cimport GraphNode, GN_check_valid
18from cuda.core.graph._host_callback cimport _resolve_host_callback
19from cuda.core.graph._subclasses cimport (
20 ExecutableGraphNode,
21 create_executable_node_view,
22)
23from cuda.core._resource_handles cimport (
24 GraphExecHandle,
25 GraphHandle,
26 OpaqueHandle,
27 PreparedAttachment,
28 as_cu, as_py,
29 create_child_graph_handle, create_graph_exec_handle, create_graph_handle,
30 get_last_error,
31 graph_clone_attachments,
32 graph_commit_attachment,
33 graph_exec_update,
34 graph_prepare_attachment,
35 invalidate_child_graph_state,
36 retry_deferred_cleanup,
37)
38from cuda.core._stream cimport Stream, Stream_accept
39from cuda.core._utils.cuda_utils cimport HANDLE_RETURN
40from cuda.core._utils.version cimport cy_binding_version, cy_driver_version
42from cuda.core._utils.cuda_utils import (
43 CUDAError,
44 driver,
45 handle_return,
46)
48if TYPE_CHECKING:
49 from cuda.core.graph._graph_definition import GraphDefinition
51__all__ = ['Graph', 'GraphBuilder', 'GraphCompleteOptions', 'GraphDebugPrintOptions']
54@dataclass
55class GraphDebugPrintOptions:
56 """Options for debug_dot_print().
58 Attributes
59 ----------
60 verbose : bool
61 Output all debug data as if every debug flag is enabled (Default to False)
62 runtime_types : bool
63 Use CUDA Runtime structures for output (Default to False)
64 kernel_node_params : bool
65 Adds kernel parameter values to output (Default to False)
66 memcpy_node_params : bool
67 Adds memcpy parameter values to output (Default to False)
68 memset_node_params : bool
69 Adds memset parameter values to output (Default to False)
70 host_node_params : bool
71 Adds host parameter values to output (Default to False)
72 event_node_params : bool
73 Adds event parameter values to output (Default to False)
74 ext_semas_signal_node_params : bool
75 Adds external semaphore signal parameter values to output (Default to False)
76 ext_semas_wait_node_params : bool
77 Adds external semaphore wait parameter values to output (Default to False)
78 kernel_node_attributes : bool
79 Adds kernel node attributes to output (Default to False)
80 handles : bool
81 Adds node handles and every kernel function handle to output (Default to False)
82 mem_alloc_node_params : bool
83 Adds memory alloc parameter values to output (Default to False)
84 mem_free_node_params : bool
85 Adds memory free parameter values to output (Default to False)
86 batch_mem_op_node_params : bool
87 Adds batch mem op parameter values to output (Default to False)
88 extra_topo_info : bool
89 Adds edge numbering information (Default to False)
90 conditional_node_params : bool
91 Adds conditional node parameter values to output (Default to False)
93 """
95 verbose: bool = False
96 runtime_types: bool = False
97 kernel_node_params: bool = False
98 memcpy_node_params: bool = False
99 memset_node_params: bool = False
100 host_node_params: bool = False
101 event_node_params: bool = False
102 ext_semas_signal_node_params: bool = False
103 ext_semas_wait_node_params: bool = False
104 kernel_node_attributes: bool = False
105 handles: bool = False
106 mem_alloc_node_params: bool = False
107 mem_free_node_params: bool = False
108 batch_mem_op_node_params: bool = False
109 extra_topo_info: bool = False
110 conditional_node_params: bool = False
112 def _to_flags(self) -> int:
113 """Convert options to CUDA driver API flags (internal use)."""
114 flags = 0 2Jca
115 if self.verbose: 2. Jca
116 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_VERBOSE 2Jca
117 if self.runtime_types: 2Jca
118 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_RUNTIME_TYPES 1a
119 if self.kernel_node_params: 2Jca
120 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_KERNEL_NODE_PARAMS 1a
121 if self.memcpy_node_params: 2Jca
122 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_MEMCPY_NODE_PARAMS 1a
123 if self.memset_node_params: 2Jca
124 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_MEMSET_NODE_PARAMS 1a
125 if self.host_node_params: 2Jca
126 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_HOST_NODE_PARAMS 1a
127 if self.event_node_params: 2Jca
128 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_EVENT_NODE_PARAMS 1a
129 if self.ext_semas_signal_node_params: 2Jca
130 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_EXT_SEMAS_SIGNAL_NODE_PARAMS 1a
131 if self.ext_semas_wait_node_params: 2Jca
132 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_EXT_SEMAS_WAIT_NODE_PARAMS 1a
133 if self.kernel_node_attributes: 2. Jca
134 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_KERNEL_NODE_ATTRIBUTES 1a
135 if self.handles: 2Jca
136 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_HANDLES 2Jca
137 if self.mem_alloc_node_params: 2Jca
138 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_MEM_ALLOC_NODE_PARAMS 1a
139 if self.mem_free_node_params: 2Jca
140 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_MEM_FREE_NODE_PARAMS 1a
141 if self.batch_mem_op_node_params: 2Jca
142 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_BATCH_MEM_OP_NODE_PARAMS 1a
143 if self.extra_topo_info: 2Jca
144 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_EXTRA_TOPO_INFO 1a
145 if self.conditional_node_params: 2Jca
146 flags |= driver.CUgraphDebugDot_flags.CU_GRAPH_DEBUG_DOT_FLAGS_CONDITIONAL_NODE_PARAMS 1a
147 return flags 2Jca
150@dataclass
151class GraphCompleteOptions:
152 """Options for graph instantiation.
154 Attributes
155 ----------
156 auto_free_on_launch : bool, optional
157 Automatically free memory allocated in a graph before relaunching. (Default to False)
158 upload_stream : Stream, optional
159 Stream to use to automatically upload the graph after completion. (Default to None)
160 device_launch : bool, optional
161 Configure the graph to be launchable from the device. This flag can only
162 be used on platforms which support unified addressing. This flag cannot be
163 used in conjunction with auto_free_on_launch. (Default to False)
164 use_node_priority : bool, optional
165 Run the graph using the per-node priority attributes rather than the
166 priority of the stream it is launched into. (Default to False)
168 """
170 auto_free_on_launch: bool = False
171 upload_stream: Stream | None = None
172 device_launch: bool = False
173 use_node_priority: bool = False
176def _instantiate_graph(source, options: GraphCompleteOptions | None = None) -> Graph:
177 cdef GraphHandle h_graph
178 cdef GraphExecHandle h_exec
179 cdef cydriver.CUresult status
181 if isinstance(source, GraphBuilder): 2M [ H I J K / G $ x y T E NcF # U % 5 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 AbBbCbDbEbbbFbcbtbGbHbIbJbdbKbebubfbgbLbhbvbibMbjbwb!bDcEc#bFcGcHc$b%b'brbNbIc(b8b)b*b+b,b-b.b/b:b;b=b?bOb@b[b]b^b_b`b{b= , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 QbPb|b}b~bacbcccdcecfcgchcicjcRbzcAcBcSbTbUbVbWbXbYbZb0b1b2b3b4bCc5bkclcmcncocpcqcrcsctcucvcwc~ lbxcyc6b7bmbobpbw S xbyb! * + @ 4 ' (
182 GB_check_open(<GraphBuilder>source) 1MHIJK/G$xyTEF#U%5VbcdefghijklmnopqzrstuvABCD=,:?-;6W07X1QOP8Y29Z3wS!*+@4'(
183 h_graph = (<GraphBuilder>source)._h_graph 1MHIJK/G$xyTEF#U%5VbcdefghijklmnopqzrstuvABCD=,:?-;6W07X1QOP8Y29Z3wS!*+@4'(
184 elif isinstance(source, GraphDefinition): 2[ NcAbBbCbDbEbbbFbcbtbGbHbIbJbdbKbebubfbgbLbhbvbibMbjbwb!bDcEc#bFcGcHc$b%b'brbNbIc(b8b)b*b+b,b-b.b/b:b;b=b?bOb@b[b]b^b_b`b{bQbPb|b}b~bacbcccdcecfcgchcicjcRbzcAcBcSbTbUbVbWbXbYbZb0b1b2b3b4bCc5bkclcmcncocpcqcrcsctcucvcwc~ lbxcyc6b7bmbobpbxbyb
185 GD_check_valid(<GraphDefinition>source) 2[ NcAbBbCbDbEbbbFbcbtbGbHbIbJbdbKbebubfbgbLbhbvbibMbjbwb!bDcEc#bFcGcHc$b%b'brbNbIc(b8b)b*b+b,b-b.b/b:b;b=b?bOb@b[b]b^b_b`b{bQbPb|b}b~bacbcccdcecfcgchcicjcRbzcAcBcSbTbUbVbWbXbYbZb0b1b2b3b4bCc5bkclcmcncocpcqcrcsctcucvcwc~ lbxcyc6b7bmbobpbxbyb
186 h_graph = (<GraphDefinition>source)._h_graph 2[ NcAbBbCbDbEbbbFbcbtbGbHbIbJbdbKbebubfbgbLbhbvbibMbjbwb!bDcEc#bFcGcHc$b%b'brbNbIc(b8b)b*b+b,b-b.b/b:b;b=b?bOb@b[b]b^b_b`b{bQbPb|b}b~bacbcccdcecfcgchcicjcRbzcAcBcSbTbUbVbWbXbYbZb0b1b2b3b4bCc5bkclcmcncocpcqcrcsctcucvcwc~ lbxcyc6b7bmbobpbxbyb
187 else:
188 raise TypeError(
189 f"expected GraphBuilder or GraphDefinition, got {type(source).__name__}")
191 cdef cydriver.CUDA_GRAPH_INSTANTIATE_PARAMS params = cydriver.CUDA_GRAPH_INSTANTIATE_PARAMS( 2M [ H I J K / G $ x y T E NcF # U % 5 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 AbBbCbDbEbbbFbcbtbGbHbIbJbdbKbebubfbgbLbhbvbibMbjbwb!bDcEc#bFcGcHc$b%b'brbNbIc(b8b)b*b+b,b-b.b/b:b;b=b?bOb@b[b]b^b_b`b{b= , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 QbPb|b}b~bacbcccdcecfcgchcicjcRbzcAcBcSbTbUbVbWbXbYbZb0b1b2b3b4bCc5bkclcmcncocpcqcrcsctcucvcwc~ lbxcyc6b7bmbobpbw S xbyb! * + @ 4 ' (
192 flags=0,
193 hUploadStream=<cydriver.CUstream>NULL, 2M [ H I J K / G $ x y T E NcF # U % 5 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 AbBbCbDbEbbbFbcbtbGbHbIbJbdbKbebubfbgbLbhbvbibMbjbwb!bDcEc#bFcGcHc$b%b'brbNbIc(b8b)b*b+b,b-b.b/b:b;b=b?bOb@b[b]b^b_b`b{b= , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 QbPb|b}b~bacbcccdcecfcgchcicjcRbzcAcBcSbTbUbVbWbXbYbZb0b1b2b3b4bCc5bkclcmcncocpcqcrcsctcucvcwc~ lbxcyc6b7bmbobpbw S xbyb! * + @ 4 ' (
194 hErrNode_out=<cydriver.CUgraphNode>NULL, 2M [ H I J K / G $ x y T E NcF # U % 5 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 AbBbCbDbEbbbFbcbtbGbHbIbJbdbKbebubfbgbLbhbvbibMbjbwb!bDcEc#bFcGcHc$b%b'brbNbIc(b8b)b*b+b,b-b.b/b:b;b=b?bOb@b[b]b^b_b`b{b= , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 QbPb|b}b~bacbcccdcecfcgchcicjcRbzcAcBcSbTbUbVbWbXbYbZb0b1b2b3b4bCc5bkclcmcncocpcqcrcsctcucvcwc~ lbxcyc6b7bmbobpbw S xbyb! * + @ 4 ' (
195 result_out=cydriver.CUgraphInstantiateResult.CUDA_GRAPH_INSTANTIATE_SUCCESS, 2M [ H I J K / G $ x y T E NcF # U % 5 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 AbBbCbDbEbbbFbcbtbGbHbIbJbdbKbebubfbgbLbhbvbibMbjbwb!bDcEc#bFcGcHc$b%b'brbNbIc(b8b)b*b+b,b-b.b/b:b;b=b?bOb@b[b]b^b_b`b{b= , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 QbPb|b}b~bacbcccdcecfcgchcicjcRbzcAcBcSbTbUbVbWbXbYbZb0b1b2b3b4bCc5bkclcmcncocpcqcrcsctcucvcwc~ lbxcyc6b7bmbobpbw S xbyb! * + @ 4 ' (
196 )
197 if options: 2M [ H I J K / G $ x y T E NcF # U % 5 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 AbBbCbDbEbbbFbcbtbGbHbIbJbdbKbebubfbgbLbhbvbibMbjbwb!bDcEc#bFcGcHc$b%b'brbNbIc(b8b)b*b+b,b-b.b/b:b;b=b?bOb@b[b]b^b_b`b{b= , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 QbPb|b}b~bacbcccdcecfcgchcicjcRbzcAcBcSbTbUbVbWbXbYbZb0b1b2b3b4bCc5bkclcmcncocpcqcrcsctcucvcwc~ lbxcyc6b7bmbobpbw S xbyb! * + @ 4 ' (
198 flags = 0 2Ncbbcbtbdbebubfbgbhbvbibjbwb!b#bQ O P @ 4
199 if options.auto_free_on_launch: 2Ncbbcbtbdbebubfbgbhbvbibjbwb!b#bQ O P @ 4
200 flags |= driver.CUgraphInstantiate_flags.CUDA_GRAPH_INSTANTIATE_FLAG_AUTO_FREE_ON_LAUNCH 2bbdbgbib!b#bQ O P @ 4
201 if options.upload_stream is not None: 2Ncbbcbtbdbebubfbgbhbvbibjbwb!b#bQ O P @ 4
202 flags |= driver.CUgraphInstantiate_flags.CUDA_GRAPH_INSTANTIATE_FLAG_UPLOAD 2Nctbubvbwb4
203 params.hUploadStream = as_cu(Stream_accept(options.upload_stream)._h_stream) 2Nctbubvbwb4
204 if options.device_launch: 2bbcbtbdbebubfbgbhbvbibjbwb!b#bQ O P @ 4
205 flags |= driver.CUgraphInstantiate_flags.CUDA_GRAPH_INSTANTIATE_FLAG_DEVICE_LAUNCH 2fb@ 4
206 if options.use_node_priority: 2bbcbtbdbebubfbgbhbvbibjbwb!b#bQ O P @ 4
207 flags |= driver.CUgraphInstantiate_flags.CUDA_GRAPH_INSTANTIATE_FLAG_USE_NODE_PRIORITY 2cbebhbjb!b#b4
208 params.flags = flags 2bbcbtbdbebubfbgbhbvbibjbwb!b#bQ O P @ 4
210 # The exec is adopted only when result_out reports success, so the
211 # diagnostics below run before the handle is checked.
212 h_exec = create_graph_exec_handle(h_graph, ¶ms) 2M [ H I J K / G $ x y T E F # U % 5 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 AbBbCbDbEbbbFbcbtbGbHbIbJbdbKbebubfbgbLbhbvbibMbjbwb!bDcEc#bFcGcHc$b%b'brbNbIc(b8b)b*b+b,b-b.b/b:b;b=b?bOb@b[b]b^b_b`b{b= , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 QbPb|b}b~bacbcccdcecfcgchcicjcRbzcAcBcSbTbUbVbWbXbYbZb0b1b2b3b4bCc5bkclcmcncocpcqcrcsctcucvcwc~ lbxcyc6b7bmbobpbw S xbyb! * + @ 4 ' (
213 status = get_last_error() 2M [ H I J K / G $ x y T E F # U % 5 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 AbBbCbDbEbbbFbcbtbGbHbIbJbdbKbebubfbgbLbhbvbibMbjbwb!bDcEc#bFcGcHc$b%b'brbNbIc(b8b)b*b+b,b-b.b/b:b;b=b?bOb@b[b]b^b_b`b{b= , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 QbPb|b}b~bacbcccdcecfcgchcicjcRbzcAcBcSbTbUbVbWbXbYbZb0b1b2b3b4bCc5bkclcmcncocpcqcrcsctcucvcwc~ lbxcyc6b7bmbobpbw S xbyb! * + @ 4 ' (
214 if params.result_out == driver.CUgraphInstantiateResult.CUDA_GRAPH_INSTANTIATE_ERROR: 2M [ H I J K / G $ x y T E F # U % 5 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 AbBbCbDbEbbbFbcbtbGbHbIbJbdbKbebubfbgbLbhbvbibMbjbwb!bDcEc#bFcGcHc$b%b'brbNbIc(b8b)b*b+b,b-b.b/b:b;b=b?bOb@b[b]b^b_b`b{b= , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 QbPb|b}b~bacbcccdcecfcgchcicjcRbzcAcBcSbTbUbVbWbXbYbZb0b1b2b3b4bCc5bkclcmcncocpcqcrcsctcucvcwc~ lbxcyc6b7bmbobpbw S xbyb! * + @ 4 ' (
215 # HANDLE_RETURN raises CUDAError with the CUresult name and message (e.g. CUDA_ERROR_INVALID_VALUE)
216 # when status is not CUDA_SUCCESS.
217 HANDLE_RETURN(status) 1@
218 raise RuntimeError(
219 "CUDA graph instantiation failed, but cuGraphInstantiateWithParams "
220 "returned CUDA_SUCCESS; no driver error details are available."
221 )
222 elif params.result_out == driver.CUgraphInstantiateResult.CUDA_GRAPH_INSTANTIATE_INVALID_STRUCTURE: 2M [ H I J K / G $ x y T E F # U % 5 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 AbBbCbDbEbbbFbcbtbGbHbIbJbdbKbebubfbgbLbhbvbibMbjbwb!bDcEc#bFcGcHc$b%b'brbNbIc(b8b)b*b+b,b-b.b/b:b;b=b?bOb@b[b]b^b_b`b{b= , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 QbPb|b}b~bacbcccdcecfcgchcicjcRbzcAcBcSbTbUbVbWbXbYbZb0b1b2b3b4bCc5bkclcmcncocpcqcrcsctcucvcwc~ lbxcyc6b7bmbobpbw S xbyb! * + 4 ' (
223 raise RuntimeError("Instantiation failed due to invalid structure, such as cycles.")
224 elif params.result_out == driver.CUgraphInstantiateResult.CUDA_GRAPH_INSTANTIATE_NODE_OPERATION_NOT_SUPPORTED: 2M [ H I J K / G $ x y T E F # U % 5 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 AbBbCbDbEbbbFbcbtbGbHbIbJbdbKbebubfbgbLbhbvbibMbjbwb!bDcEc#bFcGcHc$b%b'brbNbIc(b8b)b*b+b,b-b.b/b:b;b=b?bOb@b[b]b^b_b`b{b= , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 QbPb|b}b~bacbcccdcecfcgchcicjcRbzcAcBcSbTbUbVbWbXbYbZb0b1b2b3b4bCc5bkclcmcncocpcqcrcsctcucvcwc~ lbxcyc6b7bmbobpbw S xbyb! * + 4 ' (
225 raise RuntimeError(
226 "Instantiation for device launch failed because the graph contained an unsupported operation."
227 )
228 elif params.result_out == driver.CUgraphInstantiateResult.CUDA_GRAPH_INSTANTIATE_MULTIPLE_CTXS_NOT_SUPPORTED: 2M [ H I J K / G $ x y T E F # U % 5 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 AbBbCbDbEbbbFbcbtbGbHbIbJbdbKbebubfbgbLbhbvbibMbjbwb!bDcEc#bFcGcHc$b%b'brbNbIc(b8b)b*b+b,b-b.b/b:b;b=b?bOb@b[b]b^b_b`b{b= , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 QbPb|b}b~bacbcccdcecfcgchcicjcRbzcAcBcSbTbUbVbWbXbYbZb0b1b2b3b4bCc5bkclcmcncocpcqcrcsctcucvcwc~ lbxcyc6b7bmbobpbw S xbyb! * + 4 ' (
229 raise RuntimeError("Instantiation for device launch failed due to the nodes belonging to different contexts.")
230 elif ( 2M [ H I J K / G $ x y T E F # U % 5 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 AbBbCbDbEbbbFbcbtbGbHbIbJbdbKbebubfbgbLbhbvbibMbjbwb!bDcEc#bFcGcHc$b%b'brbNbIc(b8b)b*b+b,b-b.b/b:b;b=b?bOb@b[b]b^b_b`b{b= , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 QbPb|b}b~bacbcccdcecfcgchcicjcRbzcAcBcSbTbUbVbWbXbYbZb0b1b2b3b4bCc5bkclcmcncocpcqcrcsctcucvcwc~ lbxcyc6b7bmbobpbw S xbyb! * + 4 ' (
231 cy_binding_version() >= (12, 8, 0) 2M [ H I J K / G $ x y T E F # U % 5 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 AbBbCbDbEbbbFbcbtbGbHbIbJbdbKbebubfbgbLbhbvbibMbjbwb!bDcEc#bFcGcHc$b%b'brbNbIc(b8b)b*b+b,b-b.b/b:b;b=b?bOb@b[b]b^b_b`b{b= , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 QbPb|b}b~bacbcccdcecfcgchcicjcRbzcAcBcSbTbUbVbWbXbYbZb0b1b2b3b4bCc5bkclcmcncocpcqcrcsctcucvcwc~ lbxcyc6b7bmbobpbw S xbyb! * + 4 ' (
232 and params.result_out == driver.CUgraphInstantiateResult.CUDA_GRAPH_INSTANTIATE_CONDITIONAL_HANDLE_UNUSED 2M [ H I J K / G $ x y T E F # U % 5 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 AbBbCbDbEbbbFbcbtbGbHbIbJbdbKbebubfbgbLbhbvbibMbjbwb!bDcEc#bFcGcHc$b%b'brbNbIc(b8b)b*b+b,b-b.b/b:b;b=b?bOb@b[b]b^b_b`b{b= , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 QbPb|b}b~bacbcccdcecfcgchcicjcRbzcAcBcSbTbUbVbWbXbYbZb0b1b2b3b4bCc5bkclcmcncocpcqcrcsctcucvcwc~ lbxcyc6b7bmbobpbw S xbyb! * + 4 ' (
233 ):
234 raise RuntimeError("One or more conditional handles are not associated with conditional builders.")
235 elif params.result_out != driver.CUgraphInstantiateResult.CUDA_GRAPH_INSTANTIATE_SUCCESS: 2M [ H I J K / G $ x y T E F # U % 5 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 AbBbCbDbEbbbFbcbtbGbHbIbJbdbKbebubfbgbLbhbvbibMbjbwb!bDcEc#bFcGcHc$b%b'brbNbIc(b8b)b*b+b,b-b.b/b:b;b=b?bOb@b[b]b^b_b`b{b= , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 QbPb|b}b~bacbcccdcecfcgchcicjcRbzcAcBcSbTbUbVbWbXbYbZb0b1b2b3b4bCc5bkclcmcncocpcqcrcsctcucvcwc~ lbxcyc6b7bmbobpbw S xbyb! * + 4 ' (
236 raise RuntimeError(f"Graph instantiation failed with unexpected error code: {params.result_out}")
238 if as_cu(h_exec) == NULL: 2M [ H I J K / G $ x y T E F # U % 5 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 AbBbCbDbEbbbFbcbtbGbHbIbJbdbKbebubfbgbLbhbvbibMbjbwb!bDcEc#bFcGcHc$b%b'brbNbIc(b8b)b*b+b,b-b.b/b:b;b=b?bOb@b[b]b^b_b`b{b= , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 QbPb|b}b~bacbcccdcecfcgchcicjcRbzcAcBcSbTbUbVbWbXbYbZb0b1b2b3b4bCc5bkclcmcncocpcqcrcsctcucvcwc~ lbxcyc6b7bmbobpbw S xbyb! * + 4 ' (
239 HANDLE_RETURN(status)
240 return Graph._init(h_exec) 2M [ H I J K / G $ x y T E F # U % 5 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 AbBbCbDbEbbbFbcbtbGbHbIbJbdbKbebubfbgbLbhbvbibMbjbwb!bDcEc#bFcGcHc$b%b'brbNbIc(b8b)b*b+b,b-b.b/b:b;b=b?bOb@b[b]b^b_b`b{b= , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 QbPb|b}b~bacbcccdcecfcgchcicjcRbzcAcBcSbTbUbVbWbXbYbZb0b1b2b3b4bCc5bkclcmcncocpcqcrcsctcucvcwc~ lbxcyc6b7bmbobpbw S xbyb! * + 4 ' (
243# Distinguishes the three kinds of GraphBuilder, which differ in how they
244# begin/end stream capture and whether they own the resulting CUgraph.
245# Each kind progresses through _CaptureState as follows:
246#
247# PRIMARY: NOT_STARTED -> CAPTURING -> ENDED
248# FORKED: CAPTURING (never transitions; joined and closed)
249# CONDITIONAL_BODY: NOT_STARTED -> CAPTURING -> ENDED
250#
251cdef enum _BuilderKind:
252 # PRIMARY: The top-level builder created by Device or Stream. Owns the
253 # captured CUgraph via an owning GraphHandle. Progresses through all three
254 # capture states; responsible for ending capture if destroyed early.
255 PRIMARY = 0
256 # FORKED: Created by split(). Captures on a private stream forked from the
257 # primary. Starts in CAPTURING state and never transitions; the user joins
258 # it back to the primary via join(), which closes the builder. Must NOT
259 # call cuStreamEndCapture (the driver requires all forked streams to be
260 # joined first).
261 FORKED = 1
262 # CONDITIONAL_BODY: Created by if_then/if_else/switch/while_loop. Captures
263 # into a non-owned body graph via cuStreamBeginCaptureToGraph. The body
264 # graph's lifetime is tied to a parent graph. Progresses through all three
265 # capture states like PRIMARY.
266 CONDITIONAL_BODY = 2
269# Tracks the capture lifecycle of a GraphBuilder.
270cdef enum _CaptureState:
271 CAPTURE_NOT_STARTED = 0
272 CAPTURING = 1
273 CAPTURE_ENDED = 2 # Finished, valid handle
274 CLOSED = 3 # No valid handle
277cdef class GraphBuilder:
278 """A graph under construction by stream capture.
280 A graph groups a set of CUDA kernels and other CUDA operations together and executes
281 them with a specified dependency tree. It speeds up the workflow by combining the
282 driver activities associated with CUDA kernel launches and CUDA API calls.
284 Directly creating a :obj:`~graph.GraphBuilder` is not supported due
285 to ambiguity. New graph builders should instead be created through a
286 :obj:`~_device.Device`, or a :obj:`~_stream.stream` object.
288 .. note::
290 Operations recorded during capture reference your memory but do not
291 take ownership of it. As with ordinary stream work, you must keep the
292 operands alive for as long as the completed graph may execute -- for
293 example, the :obj:`~_memory.Buffer` objects passed to :func:`~launch`
294 or :meth:`~_memory.Buffer.copy_to`. Host callbacks added with
295 :meth:`callback` are the exception: the callable (and any copied
296 ``user_data``) are retained for the graph's lifetime. This differs from
297 building a graph explicitly with :class:`~graph.GraphDefinition`, which
298 retains the operands it is given.
300 """
302 def __init__(self):
303 raise NotImplementedError(
304 "directly creating a GraphBuilder object can be ambiguous. Please either "
305 "call Device.create_graph_builder() or stream.create_graph_builder()"
306 )
308 def __dealloc__(self):
309 GB_end_capture_if_needed(self, False) 2M [ qb_ H I J K / G $ ) x L y ^ LcnbR ` T ] zbE 9bsbF # McU % 5 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 N rb= , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' Kc(
311 @staticmethod
312 def _init(Stream stream):
313 cdef GraphBuilder self = GraphBuilder.__new__(GraphBuilder) 2M [ qb_ H I J K / G $ ) x L y ^ LcnbR ` T ] zbE 9bsbF # McU % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' Kc(
314 # _h_graph set by begin_building
315 self._h_stream = stream._h_stream 2M [ qb_ H I J K / G $ ) x L y ^ LcnbR ` T ] zbE 9bsbF # McU % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' Kc(
316 self._kind = PRIMARY 2M [ qb_ H I J K / G $ ) x L y ^ LcnbR ` T ] zbE 9bsbF # McU % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' Kc(
317 self._state = CAPTURE_NOT_STARTED 2M [ qb_ H I J K / G $ ) x L y ^ LcnbR ` T ] zbE 9bsbF # McU % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' Kc(
318 self._stream = stream 2M [ qb_ H I J K / G $ ) x L y ^ LcnbR ` T ] zbE 9bsbF # McU % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' Kc(
319 return self 2M [ qb_ H I J K / G $ ) x L y ^ LcnbR ` T ] zbE 9bsbF # McU % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' Kc(
321 def close(self):
322 """Destroy the graph builder."""
323 GB_end_capture_if_needed(self, True) 2[ $ ) ^ R ] E F % 5 b c d e f g h i j k l m n o p q r s t u v a { ab| } Kc
324 self._h_graph.reset() 2[ $ ) ^ R ] E F % 5 b c d e f g h i j k l m n o p q r s t u v a { ab| } Kc
325 self._h_stream.reset() 2[ $ ) ^ R ] E F % 5 b c d e f g h i j k l m n o p q r s t u v a { ab| } Kc
326 retry_deferred_cleanup() 2[ $ ) ^ R ] E F % 5 b c d e f g h i j k l m n o p q r s t u v a { ab| } Kc
327 self._state = CLOSED 2[ $ ) ^ R ] E F % 5 b c d e f g h i j k l m n o p q r s t u v a { ab| } Kc
328 self._stream = None 2[ $ ) ^ R ] E F % 5 b c d e f g h i j k l m n o p q r s t u v a { ab| } Kc
330 @property
331 def is_closed(self) -> bool:
332 """Whether this graph builder has been closed."""
333 return self._state == CLOSED 2[ Kc
335 @property
336 def stream(self) -> Stream:
337 """Returns the stream associated with the graph builder."""
338 GB_check_open(self) 2G $ ) x y ^ R ` T ] E 9bF # U % 5 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 = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { | } ' Kc(
339 return self._stream 2G $ ) x y ^ R ` T ] E 9bF # U % 5 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 = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { | } ' (
341 @property
342 def is_join_required(self) -> bool:
343 """Returns True if this graph builder must be joined before building is ended."""
344 return self._kind == FORKED 1)REFbcdefghijklmnopqrstuva
346 @property
347 def graph_definition(self) -> GraphDefinition:
348 """The captured graph as an explicit :class:`~graph.GraphDefinition`.
350 .. versionadded:: 1.1.0
352 The returned :class:`~graph.GraphDefinition` is a view of the same
353 graph this builder is producing: nodes added through it appear in
354 subsequent :meth:`complete` and :meth:`debug_dot_print` calls, and
355 the view stays valid even after the builder is closed.
357 This lets you mix the capture and explicit APIs on a single graph,
358 for example to inspect what was captured, augment it with extra
359 nodes, or build a conditional body entirely with the explicit API.
361 Availability:
363 - **Primary builders** (created by :meth:`Device.create_graph_builder`
364 or :meth:`Stream.create_graph_builder`): only after
365 :meth:`end_building`.
367 - **Conditional-body builders** (returned by :meth:`if_then`,
368 :meth:`if_else`, :meth:`while_loop`, :meth:`switch`): both before
369 :meth:`begin_building` and after :meth:`end_building`. The body
370 graph already exists when the conditional is created, so you may
371 populate it through this view without ever calling
372 :meth:`begin_building` on the body builder.
374 - **Forked builders** (returned by :meth:`split`): never. Forked
375 builders share the primary builder's graph; access it through the
376 primary instead.
378 Returns
379 -------
380 GraphDefinition
381 A view of the graph being built.
383 Raises
384 ------
385 RuntimeError
386 If the builder is closed, forked, currently building, or (for
387 primary builders) has not started building yet. A
388 :class:`~graph.GraphDefinition` obtained before :meth:`close`
389 keeps working; only fresh access through this property is
390 rejected once the builder is closed.
391 """
392 GB_check_open(self) 2x L y ^ LcnbR ` T ] V N
393 if self._kind == FORKED: 2x L y LcnbR ` T ] V N
394 raise RuntimeError( 1R
395 "graph_definition is unavailable on forked graph builders; "
396 "access it through the primary builder instead."
397 )
398 elif self._state == CAPTURING: 2x L y Lcnb` T ] V N
399 raise RuntimeError( 2L nb
400 "graph_definition is unavailable while capture is in "
401 "progress; call end_building() first."
402 )
403 elif self._kind == PRIMARY: 2x y Lc` T ] V N
404 if self._state == CAPTURE_NOT_STARTED: 2Lc` T ] V N
405 raise RuntimeError( 2Lc
406 "graph_definition is unavailable before begin_building() on "
407 "a primary builder; no graph has been created yet."
408 )
409 return GraphDefinition._from_handle(self._h_graph) 1xy`T]VN
411 def begin_building(self, mode: str | None = "relaxed") -> GraphBuilder:
412 """Begins the building process.
414 Build `mode` for controlling interaction with other API calls must be one of the following:
416 - `global` : Prohibit potentially unsafe operations across all streams in the process.
417 - `thread_local` : Prohibit potentially unsafe operations in streams created by the current thread.
418 - `relaxed` : The local thread is not prohibited from potentially unsafe operations.
420 Parameters
421 ----------
422 mode : str, optional
423 Build mode to control the interaction with other API calls that are porentially unsafe.
424 Default set to use relaxed.
426 """
427 GB_check_open(self) 2M qb_ H I J K / G $ ) x L y ^ nbR ` T ] zbE 9bsbF # U % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' (
428 if self._state != CAPTURE_NOT_STARTED: 2M qb_ H I J K / G $ ) x L y ^ nbR ` T ] zbE 9bsbF # U % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' (
429 if self._state == CAPTURING: 2qb#
430 raise RuntimeError("Graph builder is already building.") 2qb
431 else:
432 raise RuntimeError("Cannot resume building after building has ended.") 1#
433 cdef cydriver.CUstreamCaptureMode c_mode
434 if mode == "global": 2M qb_ H I J K / G $ ) x L y ^ nbR ` T ] zbE 9bsbF # U % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' (
435 c_mode = cydriver.CU_STREAM_CAPTURE_MODE_GLOBAL 2= ? 6 7 Q 8 9 kb
436 elif mode == "thread_local": 2M qb_ H I J K / G $ ) x L y ^ nbR ` T ] zbE 9bsbF # U % 5 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 N , : - ; W 0 X 1 O P Y 2 Z 3 w S ! * + kb@ 4 a { ab| } ' (
437 c_mode = cydriver.CU_STREAM_CAPTURE_MODE_THREAD_LOCAL 2: ; 0 1 P 2 3 kb
438 elif mode == "relaxed": 2M qb_ H I J K / G $ ) x L y ^ nbR ` T ] zbE 9bsbF # U % 5 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 N , - W X O Y Z w S ! * + kb@ 4 a { ab| } ' (
439 c_mode = cydriver.CU_STREAM_CAPTURE_MODE_RELAXED 2M qb_ H I J K / G $ ) x L y ^ nbR ` T ] zbE 9bsbF # U % 5 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 N , - W X O Y Z w S ! * + kb@ 4 a { ab| } ' (
440 else:
441 raise ValueError(f"Unsupported build mode: {mode}") 2kb
443 cdef cydriver.CUstream c_stream = as_cu(self._h_stream) 2M qb_ H I J K / G $ ) x L y ^ nbR ` T ] zbE 9bsbF # U % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' (
444 cdef cydriver.CUgraph c_graph
445 cdef cydriver.CUstreamCaptureStatus c_status
446 if self._kind == CONDITIONAL_BODY: 2M qb_ H I J K / G $ ) x L y ^ nbR ` T ] zbE 9bsbF # U % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' (
447 c_graph = as_cu(self._h_graph) 1xLbcdefghijklmnopqzrstuvABCDwa
448 with nogil: 1xLbcdefghijklmnopqzrstuvABCDwa
449 HANDLE_RETURN(cydriver.cuStreamBeginCaptureToGraph( 1xLbcdefghijklmnopqzrstuvABCDwa
450 c_stream, c_graph, NULL, NULL, 0, c_mode))
451 self._state = CAPTURING 1xLbcdefghijklmnopqzrstuvABCDwa
452 else:
453 with nogil: 2M qb_ H I J K / G $ ) x L y ^ nbR ` T ] zbE 9bsbF # U % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' (
454 HANDLE_RETURN(cydriver.cuStreamBeginCapture(c_stream, c_mode)) 2M qb_ H I J K / G $ ) x L y ^ nbR ` T ] zbE 9bsbF # U % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' (
455 # Capture is active now; set CAPTURING before the calls below so a
456 # failure in _get_capture_info/create_graph_handle still lets
457 # cleanup end the capture rather than leaving the stream poisoned.
458 self._state = CAPTURING 2M qb_ H I J K / G $ ) x L y ^ nbR ` T ] zbE 9bsbF # U % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' (
459 with nogil: 2M qb_ H I J K / G $ ) x L y ^ nbR ` T ] zbE 9bsbF # U % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' (
460 # The driver rejects a NULL captureStatus_out, so pass a
461 # stack-local even though we only want the graph handle.
462 _get_capture_info(c_stream, &c_status, &c_graph) 2M qb_ H I J K / G $ ) x L y ^ nbR ` T ] zbE 9bsbF # U % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' (
463 self._h_graph = create_graph_handle(c_graph) 2M qb_ H I J K / G $ ) x L y ^ nbR ` T ] zbE 9bsbF # U % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' (
464 return self 2M qb_ H I J K / G $ ) x L y ^ nbR ` T ] zbE 9bsbF # U % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' (
466 @property
467 def is_building(self) -> bool:
468 """Returns True if the graph builder is currently building."""
469 GB_check_open(self) 2M qb_ H I J K / G $ x L y ^ nbR ` T ] zbE sbF # U % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' (
470 cdef cydriver.CUstream c_stream = as_cu(self._h_stream) 2M qb_ H I J K / G $ x L y ^ nbR ` T ] zbE sbF # U % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' (
471 cdef cydriver.CUstreamCaptureStatus status
472 with nogil: 2M qb_ H I J K / G $ x L y ^ nbR ` T ] zbE sbF # U % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' (
473 _get_capture_info(c_stream, &status, NULL) 2M qb_ H I J K / G $ x L y ^ nbR ` T ] zbE sbF # U % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' (
474 if status == cydriver.CU_STREAM_CAPTURE_STATUS_NONE: 2M qb_ H I J K / G $ x L y ^ nbR ` T ] zbE sbF # U % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' (
475 return False 2sb
476 elif status == cydriver.CU_STREAM_CAPTURE_STATUS_ACTIVE:
477 return True 2M qb_ H I J K / G $ x L y ^ nbR ` T ] zbE sbF # U % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' (
478 elif status == cydriver.CU_STREAM_CAPTURE_STATUS_INVALIDATED:
479 raise RuntimeError(
480 "Build process encountered an error and has been invalidated. Build process must now be ended."
481 )
482 else:
483 raise NotImplementedError(f"Unsupported capture status type received: {status}")
485 def end_building(self) -> GraphBuilder:
486 """Ends the building process."""
487 GB_check_open(self) 2M qb_ H I J K / G $ x L y ^ nbR ` T ] zbE sbF # U % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' (
488 if not self.is_building: 2M qb_ H I J K / G $ x L y ^ nbR ` T ] zbE sbF # U % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' (
489 raise RuntimeError("Graph builder is not building.")
490 cdef cydriver.CUstream c_stream = as_cu(self._h_stream) 2M qb_ H I J K / G $ x L y ^ nbR ` T ] zbE sbF # U % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' (
491 cdef cydriver.CUgraph c_graph
492 with nogil: 2M qb_ H I J K / G $ x L y ^ nbR ` T ] zbE sbF # U % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' (
493 HANDLE_RETURN(cydriver.cuStreamEndCapture(c_stream, &c_graph)) 2M qb_ H I J K / G $ x L y ^ nbR ` T ] zbE sbF # U % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' (
495 # TODO: Resolving https://github.com/NVIDIA/cuda-python/issues/617 would allow us to
496 # resume the build process after the first call to end_building()
497 self._state = CAPTURE_ENDED 2M qb_ H I J K / G $ x L y ^ nbR ` T ] zbE sbF # U % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' (
498 return self 2M qb_ H I J K / G $ x L y ^ nbR ` T ] zbE sbF # U % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' (
500 def complete(self, options: GraphCompleteOptions | None = None) -> Graph:
501 """Completes the graph builder and returns the built :obj:`~graph.Graph` object.
503 Parameters
504 ----------
505 options : :obj:`~graph.GraphCompleteOptions`, optional
506 Customizable dataclass for the graph builder completion options.
508 Returns
509 -------
510 graph : :obj:`~graph.Graph`
511 The newly built graph.
513 """
514 GB_check_open(self) 1MHIJK/G$)xyTEF#U%5VbcdefghijklmnopqzrstuvABCD=,:?-;6W07X1QOP8Y29Z3wS!*+@4'(
515 if self._state != CAPTURE_ENDED: 1MHIJK/G$xyTEF#U%5VbcdefghijklmnopqzrstuvABCD=,:?-;6W07X1QOP8Y29Z3wS!*+@4'(
516 raise RuntimeError("Graph has not finished building.") 1/
518 return _instantiate_graph(self, options) 1MHIJK/G$xyTEF#U%5VbcdefghijklmnopqzrstuvABCD=,:?-;6W07X1QOP8Y29Z3wS!*+@4'(
520 def debug_dot_print(self, path: str, options: GraphDebugPrintOptions | None = None) -> None:
521 """Generates a DOT debug file for the graph builder.
523 Parameters
524 ----------
525 path : str
526 File path to use for writting debug DOT output
527 options : :obj:`~graph.GraphDebugPrintOptions`, optional
528 Customizable dataclass for the debug print options.
530 """
531 GB_check_open(self) 1a
532 if self._state != CAPTURE_ENDED: 1a
533 raise RuntimeError("Graph has not finished building.")
534 cdef unsigned int c_flags = options._to_flags() if options else 0 1a
535 cdef cydriver.CUgraph c_graph = as_cu(self._h_graph) 1a
536 cdef bytes b_path = path.encode('utf-8') 1a
537 cdef const char* c_path = b_path 1a
538 with nogil: 1a
539 HANDLE_RETURN(cydriver.cuGraphDebugDotPrint(c_graph, c_path, c_flags)) 1a
541 def split(self, count: int) -> tuple[GraphBuilder, ...]:
542 """Splits the original graph builder into multiple graph builders.
544 The new builders inherit work dependencies from the original builder.
545 The original builder is reused for the split and is returned first in the tuple.
547 Parameters
548 ----------
549 count : int
550 The number of graph builders to split the graph builder into.
552 Returns
553 -------
554 graph_builders : tuple[:obj:`~graph.GraphBuilder`, ...]
555 A tuple of split graph builders. The first graph builder in the tuple
556 is always the original graph builder.
558 """
559 if count < 2: 2) R E F Mcb c d e f g h i j k l m n o p q r s t u v a
560 raise ValueError(f"Invalid split count: expecting >= 2, got {count}") 1E
561 GB_check_open(self) 2) R E F Mcb c d e f g h i j k l m n o p q r s t u v a
562 if self._state != CAPTURING: 2) R E F Mcb c d e f g h i j k l m n o p q r s t u v a
563 raise RuntimeError("Graph builder must be building before it can be split.") 2Mc
565 event = self._stream.record() 1)REFbcdefghijklmnopqrstuva
566 result = [self] 1)REFbcdefghijklmnopqrstuva
567 for i in range(count - 1): 1)REFbcdefghijklmnopqrstuva
568 stream = self._stream.device.create_stream() 1)REFbcdefghijklmnopqrstuva
569 stream.wait(event) 1)REFbcdefghijklmnopqrstuva
570 result.append(GB_init_forked(stream, self._h_graph)) 1)REFbcdefghijklmnopqrstuva
571 event.close() 1)REFbcdefghijklmnopqrstuva
572 return tuple(result) 1)REFbcdefghijklmnopqrstuva
574 @staticmethod
575 def join(*graph_builders: GraphBuilder) -> GraphBuilder:
576 """Joins multiple graph builders into a single graph builder.
578 The returned builder inherits work dependencies from the provided builders.
580 Parameters
581 ----------
582 *graph_builders : :obj:`~graph.GraphBuilder`
583 The graph builders to join.
585 Returns
586 -------
587 graph_builder : :obj:`~graph.GraphBuilder`
588 The newly joined graph builder.
590 """
591 if any(not isinstance(builder, GraphBuilder) for builder in graph_builders): 1[)REFbcdefghijklmnopqrstuva
592 raise TypeError("All arguments must be GraphBuilder instances")
593 if len(graph_builders) < 2: 1[)REFbcdefghijklmnopqrstuva
594 raise ValueError("Must join with at least two graph builders") 1E
595 for builder in graph_builders: 1[)REFbcdefghijklmnopqrstuva
596 GB_check_open(builder) 1[)REFbcdefghijklmnopqrstuva
598 # Discover the root builder others should join
599 root_idx = 0 1)REFbcdefghijklmnopqrstuva
600 for i, builder in enumerate(graph_builders): 1)REFbcdefghijklmnopqrstuva
601 if not builder.is_join_required: 1)REFbcdefghijklmnopqrstuva
602 root_idx = i 1)REFbcdefghijklmnopqrstuva
603 break 1)REFbcdefghijklmnopqrstuva
605 # Join all onto the root builder
606 root_bdr = graph_builders[root_idx] 1)REFbcdefghijklmnopqrstuva
607 for idx, builder in enumerate(graph_builders): 1)REFbcdefghijklmnopqrstuva
608 if idx == root_idx: 1)REFbcdefghijklmnopqrstuva
609 continue 1)REFbcdefghijklmnopqrstuva
610 root_bdr.stream.wait(builder.stream) 1)REFbcdefghijklmnopqrstuva
611 builder.close() 1)REFbcdefghijklmnopqrstuva
613 return root_bdr 1)REFbcdefghijklmnopqrstuva
615 def __cuda_stream__(self) -> tuple[int, int]:
616 """Return an instance of a __cuda_stream__ protocol."""
617 GB_check_open(self)
618 return self.stream.__cuda_stream__()
620 def _get_conditional_context(self) -> driver.CUcontext:
621 return self._stream.context.handle 1xLybcdefghijklmnopqzrstuvABCDwa
623 def create_condition(self, default_value: int | None = None) -> GraphCondition:
624 """Create a condition variable for use with conditional nodes.
626 The returned :class:`GraphCondition` object is passed to conditional-node
627 builder methods (:meth:`if_then`, :meth:`if_else`, :meth:`while_loop`,
628 :meth:`switch`). Its value is controlled at runtime by device code via
629 ``cudaGraphSetConditional``.
631 Parameters
632 ----------
633 default_value : int, optional
634 The default value to assign to the condition. If None, no
635 default is assigned.
637 Returns
638 -------
639 GraphCondition
640 A condition variable for controlling conditional execution.
641 """
642 GB_check_open(self) 1xLybcdefghijklmnopqzrstuvABCDwa
643 if cy_driver_version() < (12, 3, 0): 1xLybcdefghijklmnopqzrstuvABCDwa
644 raise RuntimeError(f"Driver version {'.'.join(map(str, cy_driver_version()))} does not support conditional handles")
645 if cy_binding_version() < (12, 3, 0): 1xLybcdefghijklmnopqzrstuvABCDwa
646 raise RuntimeError(f"Binding version {'.'.join(map(str, cy_binding_version()))} does not support conditional handles")
647 if default_value is not None: 1xLybcdefghijklmnopqzrstuvABCDwa
648 flags = driver.CU_GRAPH_COND_ASSIGN_DEFAULT 1xLyABCDw
649 else:
650 default_value = 0 1bcdefghijklmnopqzrstuva
651 flags = 0 1bcdefghijklmnopqzrstuva
653 status, _, graph, *_, _ = handle_return(driver.cuStreamGetCaptureInfo(self._stream.handle)) 1xLybcdefghijklmnopqzrstuvABCDwa
654 if status != driver.CUstreamCaptureStatus.CU_STREAM_CAPTURE_STATUS_ACTIVE: 1xLybcdefghijklmnopqzrstuvABCDwa
655 raise RuntimeError("Cannot create a condition when graph is not being built")
657 raw_handle = handle_return( 1xLybcdefghijklmnopqzrstuvABCDwa
658 driver.cuGraphConditionalHandleCreate(graph, self._get_conditional_context(), default_value, flags) 1xLybcdefghijklmnopqzrstuvABCDwa
659 )
660 return GraphCondition._from_handle(<cydriver.CUgraphConditionalHandle><intptr_t>int(raw_handle)) 1xLybcdefghijklmnopqzrstuvABCDwa
662 def if_then(self, condition: GraphCondition) -> GraphBuilder:
663 """Adds an if condition branch and returns a new graph builder for it.
665 The resulting if graph will only execute the branch if the
666 condition evaluates to true at runtime.
668 The new builder inherits work dependencies from the original builder.
670 Parameters
671 ----------
672 condition : :class:`~graph.GraphCondition`
673 The condition variable from :meth:`create_condition` controlling
674 whether the branch executes.
676 Returns
677 -------
678 graph_builder : :obj:`~graph.GraphBuilder`
679 The newly created conditional graph builder.
681 """
682 GB_check_open(self) 1xLybcdefghizra
683 if cy_driver_version() < (12, 3, 0): 1xLybcdefghizra
684 raise RuntimeError(f"Driver version {'.'.join(map(str, cy_driver_version()))} does not support conditional if")
685 if cy_binding_version() < (12, 3, 0): 1xLybcdefghizra
686 raise RuntimeError(f"Binding version {'.'.join(map(str, cy_binding_version()))} does not support conditional if")
687 if not isinstance(condition, GraphCondition): 1xLybcdefghizra
688 raise TypeError(
689 f"condition must be a GraphCondition object (from "
690 f"GraphBuilder.create_condition()), got {type(condition).__name__}")
691 node_params = driver.CUgraphNodeParams() 1xLybcdefghizra
692 node_params.type = driver.CUgraphNodeType.CU_GRAPH_NODE_TYPE_CONDITIONAL 1xLybcdefghizra
693 node_params.conditional.handle = condition.handle 1xLybcdefghizra
694 node_params.conditional.type = driver.CUgraphConditionalNodeType.CU_GRAPH_COND_TYPE_IF 1xLybcdefghizra
695 node_params.conditional.size = 1 1xLybcdefghizra
696 node_params.conditional.ctx = self._get_conditional_context() 1xLybcdefghizra
697 return GB_cond_with_params(self, node_params)[0] 1xLybcdefghizra
699 def if_else(self, condition: GraphCondition) -> tuple[GraphBuilder, GraphBuilder]:
700 """Adds an if-else condition branch and returns new graph builders for both branches.
702 The resulting if graph will execute the branch if the condition
703 evaluates to true at runtime, otherwise the else branch will execute.
705 The new builders inherit work dependencies from the original builder.
707 Parameters
708 ----------
709 condition : :class:`~graph.GraphCondition`
710 The condition variable from :meth:`create_condition` controlling
711 which branch executes.
713 Returns
714 -------
715 graph_builders : tuple[:obj:`~graph.GraphBuilder`, :obj:`~graph.GraphBuilder`]
716 A tuple of two new graph builders, one for the if branch and one for the else branch.
718 """
719 GB_check_open(self) 1jklmnopq
720 if cy_driver_version() < (12, 8, 0): 1jklmnopq
721 raise RuntimeError(f"Driver version {'.'.join(map(str, cy_driver_version()))} does not support conditional if-else")
722 if cy_binding_version() < (12, 8, 0): 1jklmnopq
723 raise RuntimeError(f"Binding version {'.'.join(map(str, cy_binding_version()))} does not support conditional if-else")
724 if not isinstance(condition, GraphCondition): 1jklmnopq
725 raise TypeError(
726 f"condition must be a GraphCondition object (from "
727 f"GraphBuilder.create_condition()), got {type(condition).__name__}")
728 node_params = driver.CUgraphNodeParams() 1jklmnopq
729 node_params.type = driver.CUgraphNodeType.CU_GRAPH_NODE_TYPE_CONDITIONAL 1jklmnopq
730 node_params.conditional.handle = condition.handle 1jklmnopq
731 node_params.conditional.type = driver.CUgraphConditionalNodeType.CU_GRAPH_COND_TYPE_IF 1jklmnopq
732 node_params.conditional.size = 2 1jklmnopq
733 node_params.conditional.ctx = self._get_conditional_context() 1jklmnopq
734 return GB_cond_with_params(self, node_params) 1jklmnopq
736 def switch(self, condition: GraphCondition, count: int) -> tuple[GraphBuilder, ...]:
737 """Adds a switch condition branch and returns new graph builders for all cases.
739 The resulting switch graph will execute the branch whose case index
740 matches the value of the condition at runtime. If no match is found, no
741 branch will be executed.
743 The new builders inherit work dependencies from the original builder.
745 Parameters
746 ----------
747 condition : :class:`~graph.GraphCondition`
748 The condition variable from :meth:`create_condition` selecting
749 which case executes.
750 count : int
751 The number of cases to add to the switch conditional.
753 Returns
754 -------
755 graph_builders : tuple[:obj:`~graph.GraphBuilder`, ...]
756 A tuple of new graph builders, one for each branch.
758 """
759 GB_check_open(self) 1stuvw
760 if cy_driver_version() < (12, 8, 0): 1stuvw
761 raise RuntimeError(f"Driver version {'.'.join(map(str, cy_driver_version()))} does not support conditional switch")
762 if cy_binding_version() < (12, 8, 0): 1stuvw
763 raise RuntimeError(f"Binding version {'.'.join(map(str, cy_binding_version()))} does not support conditional switch")
764 if not isinstance(condition, GraphCondition): 1stuvw
765 raise TypeError(
766 f"condition must be a GraphCondition object (from "
767 f"GraphBuilder.create_condition()), got {type(condition).__name__}")
768 node_params = driver.CUgraphNodeParams() 1stuvw
769 node_params.type = driver.CUgraphNodeType.CU_GRAPH_NODE_TYPE_CONDITIONAL 1stuvw
770 node_params.conditional.handle = condition.handle 1stuvw
771 node_params.conditional.type = driver.CUgraphConditionalNodeType.CU_GRAPH_COND_TYPE_SWITCH 1stuvw
772 node_params.conditional.size = count 1stuvw
773 node_params.conditional.ctx = self._get_conditional_context() 1stuvw
774 return GB_cond_with_params(self, node_params) 1stuvw
776 def while_loop(self, condition: GraphCondition) -> GraphBuilder:
777 """Adds a while loop and returns a new graph builder for it.
779 The resulting while loop graph will execute the branch repeatedly at runtime
780 until the condition evaluates to false.
782 The new builder inherits work dependencies from the original builder.
784 Parameters
785 ----------
786 condition : :class:`~graph.GraphCondition`
787 The condition variable from :meth:`create_condition` controlling
788 loop continuation.
790 Returns
791 -------
792 graph_builder : :obj:`~graph.GraphBuilder`
793 The newly created while loop graph builder.
795 """
796 GB_check_open(self) 1ABCD
797 if cy_driver_version() < (12, 3, 0): 1ABCD
798 raise RuntimeError(f"Driver version {'.'.join(map(str, cy_driver_version()))} does not support conditional while loop")
799 if cy_binding_version() < (12, 3, 0): 1ABCD
800 raise RuntimeError(f"Binding version {'.'.join(map(str, cy_binding_version()))} does not support conditional while loop")
801 if not isinstance(condition, GraphCondition): 1ABCD
802 raise TypeError(
803 f"condition must be a GraphCondition object (from "
804 f"GraphBuilder.create_condition()), got {type(condition).__name__}")
805 node_params = driver.CUgraphNodeParams() 1ABCD
806 node_params.type = driver.CUgraphNodeType.CU_GRAPH_NODE_TYPE_CONDITIONAL 1ABCD
807 node_params.conditional.handle = condition.handle 1ABCD
808 node_params.conditional.type = driver.CUgraphConditionalNodeType.CU_GRAPH_COND_TYPE_WHILE 1ABCD
809 node_params.conditional.size = 1 1ABCD
810 node_params.conditional.ctx = self._get_conditional_context() 1ABCD
811 return GB_cond_with_params(self, node_params)[0] 1ABCD
813 def embed(self, GraphBuilder child):
814 """Embed a previously-built :obj:`~graph.GraphBuilder` as a child node.
816 Parameters
817 ----------
818 child : :obj:`~graph.GraphBuilder`
819 The child graph builder. Must have finished building.
820 """
821 GB_check_open(self) 1GN
822 GB_check_open(child) 1GN
823 if child._state != CAPTURE_ENDED: 1GN
824 raise ValueError("Child graph has not finished building.")
826 if not self.is_building: 1GN
827 raise ValueError("Parent graph is not being built.")
829 stream_handle = self._stream.handle 1GN
830 _, _, graph_out, *deps_info_out, num_dependencies_out = handle_return( 1GN
831 driver.cuStreamGetCaptureInfo(stream_handle) 1GN
832 )
834 # See https://github.com/NVIDIA/cuda-python/pull/879#issuecomment-3211054159
835 # for rationale
836 dependencies_out = deps_info_out[0] 1GN
837 new_node = handle_return( 1GN
838 driver.cuGraphAddChildGraphNode( 1GN
839 graph_out, dependencies_out,
840 num_dependencies_out, as_py(child._h_graph) 1GN
841 )
842 )
843 cdef cydriver.CUgraphNode c_new_node = (
844 <cydriver.CUgraphNode><intptr_t>int(new_node) 1GN
845 )
846 cdef cydriver.CUgraph embedded_graph = NULL 1GN
847 cdef cydriver.CUresult rollback_status
848 cdef GraphHandle h_embedded
849 try: 1GN
850 with nogil: 1GN
851 HANDLE_RETURN(cydriver.cuGraphChildGraphNodeGetGraph( 1GN
852 c_new_node, &embedded_graph))
853 h_embedded = create_child_graph_handle( 1GN
854 embedded_graph, self._h_graph, c_new_node)
855 HANDLE_RETURN(graph_clone_attachments( 1GN
856 h_embedded, child._h_graph))
857 except:
858 with nogil:
859 rollback_status = cydriver.cuGraphDestroyNode(c_new_node)
860 if rollback_status == cydriver.CUDA_SUCCESS:
861 invalidate_child_graph_state(
862 self._h_graph, c_new_node)
863 raise
865 deps_info_update = [[new_node]] + [None] * (len(deps_info_out) - 1) 1GN
866 handle_return( 1GN
867 driver.cuStreamUpdateCaptureDependencies( 1GN
868 stream_handle, 1GN
869 *deps_info_update, # dependencies, edgeData
870 1,
871 driver.CUstreamUpdateCaptureDependencies_flags.CU_STREAM_SET_CAPTURE_DEPENDENCIES, 1GN
872 )
873 )
875 def callback(self, fn, *, user_data=None) -> None:
876 """Add a host callback to the graph during stream capture.
878 The callback runs on the host CPU when the graph reaches this point
879 in execution. Two modes are supported:
881 - **Python callable**: Pass any callable. The GIL is acquired
882 automatically. The callable must take no arguments; use closures
883 or ``functools.partial`` to bind state.
884 - **ctypes function pointer**: The function receives a single
885 ``void*`` argument (the ``user_data``), and the caller must keep
886 the ctypes wrapper alive for the lifetime of the graph. Its
887 declared prototype must match the driver's ``CUhostFn``
888 (``void (*)(void*)``): ``ctypes.CFUNCTYPE(None, ctypes.c_void_p)``,
889 or ``ctypes.WINFUNCTYPE(None, ctypes.c_void_p)`` on Windows.
891 .. warning::
893 Callbacks must not call CUDA API functions. Doing so may
894 deadlock or corrupt driver state.
896 Use caution when a Python callback retains an object that owns a
897 graph. Any reference cycle involving the callback and a graph that
898 retains it cannot be broken by Python's cyclic garbage collector.
899 Use a weak reference to break such cycles.
901 Parameters
902 ----------
903 fn : callable or ctypes function pointer
904 The callback function.
905 user_data : int or bytes-like, optional
906 Only for ctypes function pointers. If ``int``, passed as a raw
907 pointer (caller manages lifetime). If bytes-like, the data is
908 copied and its lifetime is tied to the graph.
910 Raises
911 ------
912 TypeError
913 If ``fn`` is a ctypes function pointer whose declared prototype
914 does not match ``CUhostFn``.
915 ValueError
916 If ``user_data`` is given for a Python callable.
917 """
918 GB_callback(self, fn, user_data, False) 1_HIJKN
921cdef inline void GB_callback(
922 GraphBuilder gb, object fn, object user_data,
923 bint fail_tail_discovery_for_testing) except *:
924 GB_check_open(gb) 1M_HIJKN
925 cdef Stream stream = gb._stream 1M_HIJKN
926 cdef cydriver.CUstream c_stream = as_cu(stream._h_stream) 1M_HIJKN
927 cdef cydriver.CUstreamCaptureStatus capture_status
929 with nogil: 1M_HIJKN
930 _get_capture_info(c_stream, &capture_status, NULL) 1M_HIJKN
932 if capture_status != cydriver.CU_STREAM_CAPTURE_STATUS_ACTIVE: 1M_HIJKN
933 raise RuntimeError("Cannot add callback when graph is not being built")
935 cdef cydriver.CUhostFn c_fn
936 cdef void* c_user_data = NULL 1M_HIJKN
937 cdef OpaqueHandle fn_owner, data_owner
938 cdef PreparedAttachment prepared
939 _resolve_host_callback( 1M_HIJKN
940 fn, user_data, &c_fn, &c_user_data, &fn_owner, &data_owner)
941 HANDLE_RETURN(graph_prepare_attachment( 1MHIJKN
942 gb._h_graph, fn_owner, data_owner, &prepared))
944 with nogil: 1MHIJKN
945 HANDLE_RETURN(cydriver.cuLaunchHostFunc(c_stream, c_fn, c_user_data)) 1MHIJKN
947 # Capturing the host function added a node to the graph; it is now the
948 # stream's sole capture dependency. Attach the callback's owners to it.
949 cdef cydriver.CUgraphNode host_node
950 cdef cydriver.CUresult commit_status
951 try: 1MHIJKN
952 if fail_tail_discovery_for_testing: 1MHIJKN
953 raise RuntimeError("forced capture tail discovery failure") 1M
954 host_node = _capture_tail_node(c_stream) 1HIJKN
955 except BaseException as orig_exc: 1M
956 # CUDA added the callback, but its node cannot be identified.
957 # Retain its owners anonymously to prevent dangling pointers.
958 commit_status = graph_commit_attachment(prepared, NULL) 1M
959 try: 1M
960 HANDLE_RETURN(commit_status) 1M
961 except Exception as commit_exc:
962 raise commit_exc from orig_exc
963 raise 1M
964 HANDLE_RETURN(graph_commit_attachment(prepared, host_node)) 1HIJKN
967def _capture_callback_with_tail_failure_for_testing(
968 GraphBuilder gb, fn, *, user_data=None):
969 """Exercise anonymous attachment retention after node discovery fails."""
970 GB_callback(gb, fn, user_data, True) 1M
973cdef inline int GB_check_open(GraphBuilder gb) except -1:
974 """Reject operations on a builder that has been closed.
976 A CLOSED builder has reset its stream and graph handles, so any method
977 that dereferences them would read a null handle (or, for the cached
978 Stream, a None typed as cdef Stream). Guarding here yields a clear error
979 instead.
980 """
981 if gb._state == CLOSED: 2M [ qb_ H I J K / G $ ) x L y ^ LcnbR ` T ] zbE 9bsbF # McU % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' Kc(
982 raise RuntimeError("GraphBuilder has been closed") 2[ ) ^ 5 Kc
983 return 0 2M qb_ H I J K / G $ ) x L y ^ LcnbR ` T ] zbE 9bsbF # McU % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' (
986cdef inline int GB_end_capture_if_needed(GraphBuilder gb, bint check_status) except -1 nogil:
987 """End an in-progress capture if this builder owns it.
989 Only a CAPTURING PRIMARY or CONDITIONAL_BODY builder owns the live
990 capture. A FORKED builder must not call cuStreamEndCapture: the driver
991 requires forked streams to be joined first.
993 check_status=True checks the driver return (close()); False ignores it
994 (__dealloc__).
995 """
996 cdef cydriver.CUgraph c_graph
997 cdef cydriver.CUresult err
998 cdef cydriver.CUstream c_stream
999 if gb._h_stream and gb._state == CAPTURING and gb._kind != FORKED: 2M [ qb_ H I J K / G $ ) x L y ^ LcnbR ` T ] zbE 9bsbF # McU % 5 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 N rb= , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' Kc(
1000 c_stream = as_cu(gb._h_stream) 2) 9b
1001 with nogil: 2) 9b
1002 err = cydriver.cuStreamEndCapture(c_stream, &c_graph) 2) 9b
1003 if check_status: 2) 9b
1004 HANDLE_RETURN(err)
1005 return 0 2M [ qb_ H I J K / G $ ) x L y ^ LcnbR ` T ] zbE 9bsbF # McU % 5 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 N rb= , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' Kc(
1008cdef inline GraphBuilder GB_init_forked(Stream stream, GraphHandle h_primary_graph):
1009 cdef GraphBuilder gb = GraphBuilder.__new__(GraphBuilder) 1)REFbcdefghijklmnopqrstuva
1010 # A FORKED builder captures into the primary's CUgraph. It holds the
1011 # primary's GraphHandle so conditional bodies share its graph hierarchy.
1012 gb._h_graph = h_primary_graph 1)REFbcdefghijklmnopqrstuva
1013 gb._h_stream = stream._h_stream 1)REFbcdefghijklmnopqrstuva
1014 gb._kind = FORKED 1)REFbcdefghijklmnopqrstuva
1015 gb._state = CAPTURING 1)REFbcdefghijklmnopqrstuva
1016 gb._stream = stream 1)REFbcdefghijklmnopqrstuva
1017 return gb 1)REFbcdefghijklmnopqrstuva
1020cdef inline GraphBuilder GB_init_conditional(
1021 Stream stream, cydriver.CUgraph cond_graph,
1022 GraphBuilder parent, cydriver.CUgraphNode owner_node):
1023 cdef GraphBuilder gb = GraphBuilder.__new__(GraphBuilder) 1xLybcdefghijklmnopqzrstuvABCDwa
1024 gb._h_graph = create_child_graph_handle( 1xLybcdefghijklmnopqzrstuvABCDwa
1025 cond_graph, parent._h_graph, owner_node)
1026 gb._h_stream = stream._h_stream 1xLybcdefghijklmnopqzrstuvABCDwa
1027 gb._kind = CONDITIONAL_BODY 1xLybcdefghijklmnopqzrstuvABCDwa
1028 gb._state = CAPTURE_NOT_STARTED 1xLybcdefghijklmnopqzrstuvABCDwa
1029 gb._stream = stream 1xLybcdefghijklmnopqzrstuvABCDwa
1030 return gb 1xLybcdefghijklmnopqzrstuvABCDwa
1033cdef inline int _get_capture_info(
1034 cydriver.CUstream stream,
1035 cydriver.CUstreamCaptureStatus* status,
1036 cydriver.CUgraph* graph) except?-1 nogil:
1037 """Thin wrapper around ``cuStreamGetCaptureInfo`` that papers over the
1038 CUDA 12 vs 13 signature change.
1040 ``status`` must be non-NULL: the driver rejects ``captureStatus_out=NULL``
1041 with ``CUDA_ERROR_INVALID_VALUE``. ``graph`` may be NULL when the caller
1042 does not need the graph handle.
1043 """
1044 IF CUDA_CORE_BUILD_MAJOR >= 13:
1045 return HANDLE_RETURN(cydriver.cuStreamGetCaptureInfo( 2M qb_ H I J K / G $ ) x L y ^ nbR ` T ] zbE 9bsbF # U % 5 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 N = , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 w S ! * + kb@ 4 a { ab| } ' (
1046 stream, status, NULL, graph, NULL, NULL, NULL))
1047 ELSE:
1048 return HANDLE_RETURN(cydriver.cuStreamGetCaptureInfo(
1049 stream, status, NULL, graph, NULL, NULL))
1052cdef inline cydriver.CUgraphNode _capture_tail_node(cydriver.CUstream stream) except *:
1053 """Return the node a freshly-captured single-node operation left as the
1054 stream's sole capture dependency (e.g. the host node added by
1055 ``cuLaunchHostFunc``). The driver advances the stream's dependency set to
1056 the new node, so the next captured op would depend on it.
1057 """
1058 cdef cydriver.CUstreamCaptureStatus status
1059 cdef const cydriver.CUgraphNode* deps = NULL 1HIJKN
1060 cdef size_t num_deps = 0 1HIJKN
1061 with nogil: 1HIJKN
1062 IF CUDA_CORE_BUILD_MAJOR >= 13:
1063 HANDLE_RETURN(cydriver.cuStreamGetCaptureInfo( 1HIJKN
1064 stream, &status, NULL, NULL, &deps, NULL, &num_deps))
1065 ELSE:
1066 HANDLE_RETURN(cydriver.cuStreamGetCaptureInfo(
1067 stream, &status, NULL, NULL, &deps, &num_deps))
1068 if num_deps != 1: 1HIJKN
1069 raise RuntimeError(
1070 f"expected exactly one capture dependency after a host callback, got {num_deps}")
1071 return <cydriver.CUgraphNode>deps[0] 1HIJKN
1074cdef inline tuple GB_cond_with_params(GraphBuilder gb, node_params):
1075 status, _, graph, *deps_info, num_dependencies = handle_return( 1xLybcdefghijklmnopqzrstuvABCDwa
1076 driver.cuStreamGetCaptureInfo(gb._stream.handle) 1xLybcdefghijklmnopqzrstuvABCDwa
1077 )
1078 if status != driver.CUstreamCaptureStatus.CU_STREAM_CAPTURE_STATUS_ACTIVE: 1xLybcdefghijklmnopqzrstuvABCDwa
1079 raise RuntimeError("Cannot add conditional node when not actively capturing")
1081 new_node = handle_return( 1xLybcdefghijklmnopqzrstuvABCDwa
1082 driver.cuGraphAddNode(graph, *deps_info, num_dependencies, node_params)) 1xLybcdefghijklmnopqzrstuvABCDwa
1083 deps_info_update = [[new_node]] + [None] * (len(deps_info) - 1) 1xLybcdefghijklmnopqzrstuvABCDwa
1085 handle_return( 1xLybcdefghijklmnopqzrstuvABCDwa
1086 driver.cuStreamUpdateCaptureDependencies( 1xLybcdefghijklmnopqzrstuvABCDwa
1087 gb._stream.handle, 1xLybcdefghijklmnopqzrstuvABCDwa
1088 *deps_info_update, # dependencies, edgeData 1xLybcdefghijklmnopqzrstuvABCDwa
1089 1, # numDependencies
1090 driver.CUstreamUpdateCaptureDependencies_flags.CU_STREAM_SET_CAPTURE_DEPENDENCIES, 1xLybcdefghijklmnopqzrstuvABCDwa
1091 )
1092 )
1094 return tuple( 1xLybcdefghijklmnopqzrstuvABCDwa
1095 GB_init_conditional( 1xLybcdefghijklmnopqzrstuvABCDwa
1096 gb._stream.device.create_stream(), 1xLybcdefghijklmnopqzrstuvABCDwa
1097 <cydriver.CUgraph><intptr_t>int(node_params.conditional.phGraph_out[i]), 1xLybcdefghijklmnopqzrstuvABCDwa
1098 gb, 1xLybcdefghijklmnopqzrstuvABCDwa
1099 <cydriver.CUgraphNode><intptr_t>int(new_node), 1xLybcdefghijklmnopqzrstuvABCDwa
1100 )
1101 for i in range(node_params.conditional.size) 1xLybcdefghijklmnopqzrstuvABCDwa
1102 )
1105cdef class Graph:
1106 """An executable graph.
1108 A graph groups a set of CUDA kernels and other CUDA operations together and executes
1109 them with a specified dependency tree. It speeds up the workflow by combining the
1110 driver activities associated with CUDA kernel launches and CUDA API calls.
1112 Graphs must be built using a :obj:`~graph.GraphBuilder` object.
1114 """
1116 def __init__(self):
1117 raise RuntimeError("directly constructing a Graph instance is not supported")
1119 @staticmethod
1120 cdef Graph _init(GraphExecHandle h_graph_exec):
1121 cdef Graph self = Graph.__new__(Graph) 2M [ H I J K / G $ x y T E F # U % 5 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 AbBbCbDbEbbbFbcbtbGbHbIbJbdbKbebubfbgbLbhbvbibMbjbwb!bDcEc#bFcGcHc$b%b'brbNbIc(b8b)b*b+b,b-b.b/b:b;b=b?bOb@b[b]b^b_b`b{b= , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 QbPb|b}b~bacbcccdcecfcgchcicjcRbzcAcBcSbTbUbVbWbXbYbZb0b1b2b3b4bCc5bkclcmcncocpcqcrcsctcucvcwc~ lbxcyc6b7bmbobpbw S xbyb! * + 4 ' (
1122 self._h_graph_exec = h_graph_exec 2M [ H I J K / G $ x y T E F # U % 5 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 AbBbCbDbEbbbFbcbtbGbHbIbJbdbKbebubfbgbLbhbvbibMbjbwb!bDcEc#bFcGcHc$b%b'brbNbIc(b8b)b*b+b,b-b.b/b:b;b=b?bOb@b[b]b^b_b`b{b= , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 QbPb|b}b~bacbcccdcecfcgchcicjcRbzcAcBcSbTbUbVbWbXbYbZb0b1b2b3b4bCc5bkclcmcncocpcqcrcsctcucvcwc~ lbxcyc6b7bmbobpbw S xbyb! * + 4 ' (
1123 return self 2M [ H I J K / G $ x y T E F # U % 5 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 AbBbCbDbEbbbFbcbtbGbHbIbJbdbKbebubfbgbLbhbvbibMbjbwb!bDcEc#bFcGcHc$b%b'brbNbIc(b8b)b*b+b,b-b.b/b:b;b=b?bOb@b[b]b^b_b`b{b= , : ? - ; 6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 QbPb|b}b~bacbcccdcecfcgchcicjcRbzcAcBcSbTbUbVbWbXbYbZb0b1b2b3b4bCc5bkclcmcncocpcqcrcsctcucvcwc~ lbxcyc6b7bmbobpbw S xbyb! * + 4 ' (
1125 def close(self) -> None:
1126 """Destroy the graph."""
1127 self._h_graph_exec.reset() 2[ $ % 8bPb4
1128 retry_deferred_cleanup() 2[ $ % 8bPb4
1130 @property
1131 def is_closed(self) -> bool:
1132 """Whether this executable graph has been closed."""
1133 return self._h_graph_exec.get() == NULL 1[
1135 @property
1136 def handle(self) -> driver.CUgraphExec:
1137 """Return the underlying ``CUgraphExec`` object.
1139 .. caution::
1141 This handle is a Python object. To get the memory address of the underlying C
1142 handle, call ``int()`` on the returned object.
1144 """
1145 return as_py(self._h_graph_exec) 1$
1147 def __getitem__(self, node: GraphNode) -> ExecutableGraphNode:
1148 """Return a view for updating *node* in this executable graph.
1150 *node* is a definition node from the graph used to instantiate this
1151 executable. Call ``update()`` on the returned view to replace that
1152 node's parameters for future launches. Kernel, memcpy, and memset
1153 views also support enabling and disabling the node.
1154 """
1155 Graph_check_open(self) 2[ QbPbRbzcAcBcSbTbUbVbWbXbYbZb0b1b2b3b4bCc5b~ lb6b7bmb
1156 GN_check_valid(node) 2QbPbRbzcAcBcSbTbUbVbWbXbYbZb0b1b2b3b4bCc5b~ lb6b7bmb
1157 return create_executable_node_view( 2QbPbRbzcAcBcSbTbUbVbWbXbYbZb0b1b2b3b4bCc5b~ lb6b7bmb
1158 self._h_graph_exec, node) 2QbPbRbzcAcBcSbTbUbVbWbXbYbZb0b1b2b3b4bCc5b~ lb6b7bmb
1160 def update(self, source: "GraphBuilder | GraphDefinition") -> None:
1161 """Update the graph using a new graph definition.
1163 The topology of the provided source must be identical to this graph.
1165 Parameters
1166 ----------
1167 source : :obj:`~graph.GraphBuilder` or :obj:`~graph.GraphDefinition`
1168 The graph definition to update from. A GraphBuilder must have
1169 finished building.
1171 """
1172 Graph_check_open(self) 2[ 5 ~ lbmbobpbw S xbyb! * +
1173 cdef GraphHandle h_source
1175 if isinstance(source, GraphBuilder): 25 ~ lbmbobpbw S xbyb! * +
1176 GB_check_open(<GraphBuilder>source) 15wS!*
1177 if (<GraphBuilder>source)._state != CAPTURE_ENDED: 1wS!*
1178 raise ValueError("Graph has not finished building.") 1*
1179 h_source = (<GraphBuilder>source)._h_graph 1wS!
1180 elif isinstance(source, GraphDefinition): 2~ lbmbobpbxbyb+
1181 GD_check_valid(<GraphDefinition>source) 2~ lbmbobpbxbyb
1182 h_source = (<GraphDefinition>source)._h_graph 2~ lbmbobpbxbyb
1183 else:
1184 raise TypeError( 1+
1185 f"expected GraphBuilder or GraphDefinition, got {type(source).__name__}") 1+
1187 cdef cydriver.CUgraphExecUpdateResultInfo result_info
1188 cdef cydriver.CUresult err = graph_exec_update( 2~ lbmbobpbw S xbyb!
1189 self._h_graph_exec, h_source, &result_info)
1190 if err == cydriver.CUresult.CUDA_ERROR_GRAPH_EXEC_UPDATE_FAILURE: 2~ lbmbobpbw S xbyb!
1191 reason = driver.CUgraphExecUpdateResult(result_info.result) 2~ obpb!
1192 msg = f"Graph update failed: {reason.__doc__.strip()} ({reason.name})" 2~ obpb!
1193 raise CUDAError(msg) 2~ obpb!
1194 HANDLE_RETURN(err) 2lbmbw S xbyb
1196 def upload(self, stream: Stream) -> None:
1197 """Uploads the graph in a stream.
1199 Parameters
1200 ----------
1201 stream : :obj:`~_stream.Stream`
1202 The stream in which to upload the graph
1204 """
1205 Graph_check_open(self) 2[ E U AbBbCbDbEbbbFbcbGbHbIbJbdbKbebfbgbLbhbibMbjbrbNbOb6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3
1206 cdef Stream s = Stream_accept(stream) 2E U AbBbCbDbEbbbFbcbGbHbIbJbdbKbebfbgbLbhbibMbjbrbNbOb6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3
1207 cdef cydriver.CUgraphExec c_exec = as_cu(self._h_graph_exec) 2E U AbBbCbDbEbbbFbcbGbHbIbJbdbKbebfbgbLbhbibMbjbrbNbOb6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3
1208 cdef cydriver.CUstream c_stream = as_cu(s._h_stream) 2E U AbBbCbDbEbbbFbcbGbHbIbJbdbKbebfbgbLbhbibMbjbrbNbOb6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3
1209 with nogil: 2E U AbBbCbDbEbbbFbcbGbHbIbJbdbKbebfbgbLbhbibMbjbrbNbOb6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3
1210 HANDLE_RETURN(cydriver.cuGraphUpload(c_exec, c_stream)) 2E U AbBbCbDbEbbbFbcbGbHbIbJbdbKbebfbgbLbhbibMbjbrbNbOb6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3
1212 def launch(self, stream: Stream) -> None:
1213 """Launches the graph in a stream.
1215 Parameters
1216 ----------
1217 stream : :obj:`~_stream.Stream`
1218 The stream in which to launch the graph.
1220 """
1221 Graph_check_open(self) 2M [ H I J K G x y T E # U 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 AbBbCbDbEbbbFbcbtbGbHbIbJbdbKbebubfbgbLbhbvbibMbjbwb$b%b'brbNb(b8b)b*b+b,b-b.b/b:b;b=b?bOb@b[b]b^b_b`b{b6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 QbPb|b}b~bacbcccdcecfcgchcicjcRbSbTbUbVbWbXbYbZb0b1b2b3b4b5bkclcmcncocpcqcrcsctcucvcwc~ lbxcyc6b7bmbobpbw S xbyb' (
1222 cdef Stream s = Stream_accept(stream) 2M H I J K G x y T E # U 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 AbBbCbDbEbbbFbcbtbGbHbIbJbdbKbebubfbgbLbhbvbibMbjbwb$b%b'brbNb(b8b)b*b+b,b-b.b/b:b;b=b?bOb@b[b]b^b_b`b{b6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 QbPb|b}b~bacbcccdcecfcgchcicjcRbSbTbUbVbWbXbYbZb0b1b2b3b4b5bkclcmcncocpcqcrcsctcucvcwc~ lbxcyc6b7bmbobpbw S xbyb' (
1223 cdef cydriver.CUgraphExec c_exec = as_cu(self._h_graph_exec) 2M H I J K G x y T E # U 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 AbBbCbDbEbbbFbcbtbGbHbIbJbdbKbebubfbgbLbhbvbibMbjbwb$b%b'brbNb(b8b)b*b+b,b-b.b/b:b;b=b?bOb@b[b]b^b_b`b{b6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 QbPb|b}b~bacbcccdcecfcgchcicjcRbSbTbUbVbWbXbYbZb0b1b2b3b4b5bkclcmcncocpcqcrcsctcucvcwc~ lbxcyc6b7bmbobpbw S xbyb' (
1224 cdef cydriver.CUstream c_stream = as_cu(s._h_stream) 2M H I J K G x y T E # U 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 AbBbCbDbEbbbFbcbtbGbHbIbJbdbKbebubfbgbLbhbvbibMbjbwb$b%b'brbNb(b8b)b*b+b,b-b.b/b:b;b=b?bOb@b[b]b^b_b`b{b6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 QbPb|b}b~bacbcccdcecfcgchcicjcRbSbTbUbVbWbXbYbZb0b1b2b3b4b5bkclcmcncocpcqcrcsctcucvcwc~ lbxcyc6b7bmbobpbw S xbyb' (
1225 with nogil: 2M H I J K G x y T E # U 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 AbBbCbDbEbbbFbcbtbGbHbIbJbdbKbebubfbgbLbhbvbibMbjbwb$b%b'brbNb(b8b)b*b+b,b-b.b/b:b;b=b?bOb@b[b]b^b_b`b{b6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 QbPb|b}b~bacbcccdcecfcgchcicjcRbSbTbUbVbWbXbYbZb0b1b2b3b4b5bkclcmcncocpcqcrcsctcucvcwc~ lbxcyc6b7bmbobpbw S xbyb' (
1226 HANDLE_RETURN(cydriver.cuGraphLaunch(c_exec, c_stream)) 2M H I J K G x y T E # U 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 AbBbCbDbEbbbFbcbtbGbHbIbJbdbKbebubfbgbLbhbvbibMbjbwb$b%b'brbNb(b8b)b*b+b,b-b.b/b:b;b=b?bOb@b[b]b^b_b`b{b6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 QbPb|b}b~bacbcccdcecfcgchcicjcRbSbTbUbVbWbXbYbZb0b1b2b3b4b5bkclcmcncocpcqcrcsctcucvcwc~ lbxcyc6b7bmbobpbw S xbyb' (
1229cdef inline int Graph_check_open(Graph self) except -1:
1230 if not self._h_graph_exec: 2M [ H I J K G x y T E # U 5 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 AbBbCbDbEbbbFbcbtbGbHbIbJbdbKbebubfbgbLbhbvbibMbjbwb$b%b'brbNb(b8b)b*b+b,b-b.b/b:b;b=b?bOb@b[b]b^b_b`b{b6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 QbPb|b}b~bacbcccdcecfcgchcicjcRbzcAcBcSbTbUbVbWbXbYbZb0b1b2b3b4bCc5bkclcmcncocpcqcrcsctcucvcwc~ lbxcyc6b7bmbobpbw S xbyb! * + ' (
1231 raise RuntimeError("Graph has been closed") 1[
1232 return 0 2M H I J K G x y T E # U 5 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 AbBbCbDbEbbbFbcbtbGbHbIbJbdbKbebubfbgbLbhbvbibMbjbwb$b%b'brbNb(b8b)b*b+b,b-b.b/b:b;b=b?bOb@b[b]b^b_b`b{b6 W 0 7 X 1 Q O P 8 Y 2 9 Z 3 QbPb|b}b~bacbcccdcecfcgchcicjcRbzcAcBcSbTbUbVbWbXbYbZb0b1b2b3b4bCc5bkclcmcncocpcqcrcsctcucvcwc~ lbxcyc6b7bmbobpbw S xbyb! * + ' (