diff --git a/cuda_core/cuda/core/_module.pyi b/cuda_core/cuda/core/_module.pyi index 1212bc1f536..3cf52899a1d 100644 --- a/cuda_core/cuda/core/_module.pyi +++ b/cuda_core/cuda/core/_module.pyi @@ -97,6 +97,13 @@ class KernelAttributes: @property def cluster_scheduling_policy_preference(self) -> int: """int : The block scheduling policy of a function.""" + @property + def device_node_update_supported(self) -> bool: + """bool : Whether this kernel can be the target of device-side graph node updates. + + Requires CUDA 13 build and CUDA driver 13.3+. + This attribute is read-only. + """ class KernelOccupancy: """This class offers methods to query occupancy metrics that help determine optimal diff --git a/cuda_core/cuda/core/_module.pyx b/cuda_core/cuda/core/_module.pyx index 52a2794cfa4..dab050faaab 100644 --- a/cuda_core/cuda/core/_module.pyx +++ b/cuda_core/cuda/core/_module.pyx @@ -235,6 +235,23 @@ cdef class KernelAttributes: cydriver.CU_FUNC_ATTRIBUTE_CLUSTER_SCHEDULING_POLICY_PREFERENCE, ) + @property + def device_node_update_supported(self) -> bool: + """bool : Whether this kernel can be the target of device-side graph node updates. + + Requires CUDA 13 build and CUDA driver 13.3+. + This attribute is read-only. + """ + IF CUDA_CORE_BUILD_MAJOR >= 13: + if cy_driver_version() < (13, 3, 0): + raise NotImplementedError("Device node update queries require CUDA driver 13.3 or newer") + return bool(self._get_cached_attribute( + self._effective_device_id(), + cydriver.CU_FUNC_ATTRIBUTE_DEVICE_NODE_UPDATE_SUPPORTED, + )) + ELSE: + raise NotImplementedError("Device node update queries require CUDA driver 13.3 or newer") + MaxPotentialBlockSizeOccupancyResult = namedtuple( "MaxPotentialBlockSizeOccupancyResult", ("min_grid_size", "max_block_size") diff --git a/cuda_core/cuda/core/graph/_graph_builder.pyi b/cuda_core/cuda/core/graph/_graph_builder.pyi index 78d89c3bfcb..7732a93797a 100644 --- a/cuda_core/cuda/core/graph/_graph_builder.pyi +++ b/cuda_core/cuda/core/graph/_graph_builder.pyi @@ -472,6 +472,12 @@ class Graph: def is_closed(self) -> bool: """Whether this executable graph has been closed.""" @property + def id(self) -> int: + """The driver-assigned executable graph ID, as used in GraphViz DOT output. + + Requires CUDA >= 13.1. + """ + @property def handle(self) -> driver.CUgraphExec: """Return the underlying ``CUgraphExec`` object. diff --git a/cuda_core/cuda/core/graph/_graph_builder.pyx b/cuda_core/cuda/core/graph/_graph_builder.pyx index c1a3e287d95..ef094580df1 100644 --- a/cuda_core/cuda/core/graph/_graph_builder.pyx +++ b/cuda_core/cuda/core/graph/_graph_builder.pyx @@ -1256,6 +1256,24 @@ cdef class Graph: """Whether this executable graph has been closed.""" return self._h_graph_exec.get() == NULL + @property + def id(self) -> int: + """The driver-assigned executable graph ID, as used in GraphViz DOT output. + + Requires CUDA >= 13.1. + """ + if self.is_closed: + raise RuntimeError("Graph is closed") + IF CUDA_CORE_BUILD_MAJOR >= 13: + cdef unsigned int graph_id + if cy_driver_version() < (13, 1, 0): + raise NotImplementedError("Graph IDs require CUDA driver 13.1 or newer") + with nogil: + HANDLE_RETURN(cydriver.cuGraphExecGetId(as_cu(self._h_graph_exec), &graph_id)) + return graph_id + ELSE: + raise NotImplementedError("Graph IDs require CUDA driver 13.1 or newer") + @property def handle(self) -> driver.CUgraphExec: """Return the underlying ``CUgraphExec`` object. diff --git a/cuda_core/cuda/core/graph/_graph_definition.pyi b/cuda_core/cuda/core/graph/_graph_definition.pyi index 871201d4bb0..9af0b154826 100644 --- a/cuda_core/cuda/core/graph/_graph_definition.pyi +++ b/cuda_core/cuda/core/graph/_graph_definition.pyi @@ -64,6 +64,12 @@ class GraphDefinition: def __eq__(self, other: object) -> bool: ... def __hash__(self) -> int: ... @property + def id(self) -> int: + """The driver-assigned graph ID, as used in GraphViz DOT output. + + Requires CUDA 13 build and driver 13.1+ + """ + @property def is_valid(self) -> bool: """Whether this graph definition remains valid.""" @property diff --git a/cuda_core/cuda/core/graph/_graph_definition.pyx b/cuda_core/cuda/core/graph/_graph_definition.pyx index ea97c47bc15..079fb9804a5 100644 --- a/cuda_core/cuda/core/graph/_graph_definition.pyx +++ b/cuda_core/cuda/core/graph/_graph_definition.pyx @@ -23,6 +23,8 @@ from cuda.core._rt cimport ( create_graph_node_handle, ) from cuda.core._utils.cuda_utils cimport HANDLE_RETURN +IF CUDA_CORE_BUILD_MAJOR >= 13: + from cuda.core._utils.version cimport cy_driver_version from typing import TYPE_CHECKING @@ -145,6 +147,23 @@ cdef class GraphDefinition: def __hash__(self) -> int: return hash(self._h_graph.get()) + @property + def id(self) -> int: + """The driver-assigned graph ID, as used in GraphViz DOT output. + + Requires CUDA 13 build and driver 13.1+ + """ + GD_check_valid(self) + IF CUDA_CORE_BUILD_MAJOR >= 13: + cdef unsigned int graph_id + if cy_driver_version() < (13, 1, 0): + raise NotImplementedError("Graph IDs require CUDA driver 13.1 or newer") + with nogil: + HANDLE_RETURN(cydriver.cuGraphGetId(as_cu(self._h_graph), &graph_id)) + return graph_id + ELSE: + raise NotImplementedError("Graph IDs require CUDA driver 13.1 or newer") + @property def is_valid(self) -> bool: """Whether this graph definition remains valid.""" diff --git a/cuda_core/cuda/core/graph/_graph_node.pyi b/cuda_core/cuda/core/graph/_graph_node.pyi index ab52cad8910..45498c550b3 100644 --- a/cuda_core/cuda/core/graph/_graph_node.pyi +++ b/cuda_core/cuda/core/graph/_graph_node.pyi @@ -43,6 +43,18 @@ class GraphNode: The node type enum value, or None for the entry node. """ @property + def local_id(self) -> int: + """The node's ID within its graph. + + Requires CUDA 13 build and driver 13.1+ + """ + @property + def tools_id(self) -> int: + """The node's ID reported by CUDA profiling and tooling interfaces. + + Requires CUDA 13 build and driver 13.1+ + """ + @property def graph(self) -> GraphDefinition: """Return the GraphDefinition this node belongs to.""" @property diff --git a/cuda_core/cuda/core/graph/_graph_node.pyx b/cuda_core/cuda/core/graph/_graph_node.pyx index 4e20427ef91..c5bdc1da92b 100644 --- a/cuda_core/cuda/core/graph/_graph_node.pyx +++ b/cuda_core/cuda/core/graph/_graph_node.pyx @@ -65,6 +65,8 @@ from cuda.core._rt cimport ( make_opaque_py, ) from cuda.core._utils.cuda_utils cimport HANDLE_RETURN, _parse_fill_value +IF CUDA_CORE_BUILD_MAJOR >= 13: + from cuda.core._utils.version cimport cy_driver_version from cuda.core._host_callback cimport ( _resolve_host_callback, @@ -145,6 +147,44 @@ cdef class GraphNode: HANDLE_RETURN(cydriver.cuGraphNodeGetType(node, &node_type)) return driver.CUgraphNodeType(node_type) + @property + def local_id(self) -> int: + """The node's ID within its graph. + + Requires CUDA 13 build and driver 13.1+ + """ + GN_check_valid(self) + if self._is_entry: + raise ValueError("The entry node has no driver-assigned ID") + IF CUDA_CORE_BUILD_MAJOR >= 13: + cdef unsigned int node_id + if cy_driver_version() < (13, 1, 0): + raise NotImplementedError("Graph IDs require a CUDA 13 build of cuda.core and CUDA driver 13.1 or newer") + with nogil: + HANDLE_RETURN(cydriver.cuGraphNodeGetLocalId(as_cu(self._h_node), &node_id)) + return node_id + ELSE: + raise NotImplementedError("Graph IDs require a CUDA 13 build of cuda.core and CUDA driver 13.1 or newer") + + @property + def tools_id(self) -> int: + """The node's ID reported by CUDA profiling and tooling interfaces. + + Requires CUDA 13 build and driver 13.1+ + """ + GN_check_valid(self) + if self._is_entry: + raise ValueError("The entry node has no driver-assigned ID") + IF CUDA_CORE_BUILD_MAJOR >= 13: + cdef unsigned long long node_id + if cy_driver_version() < (13, 1, 0): + raise NotImplementedError("Graph IDs require a CUDA 13 build of cuda.core and CUDA driver 13.1 or newer") + with nogil: + HANDLE_RETURN(cydriver.cuGraphNodeGetToolsId(as_cu(self._h_node), &node_id)) + return node_id + ELSE: + raise NotImplementedError("Graph IDs require a CUDA 13 build of cuda.core and CUDA driver 13.1 or newer") + @property def graph(self) -> GraphDefinition: """Return the GraphDefinition this node belongs to.""" diff --git a/cuda_core/docs/source/api.rst b/cuda_core/docs/source/api.rst index c0c99f6befa..bfc9bf042f2 100644 --- a/cuda_core/docs/source/api.rst +++ b/cuda_core/docs/source/api.rst @@ -154,6 +154,15 @@ CPU overhead. Graphs can be constructed in two ways: edges. Both produce an executable :class:`~graph.Graph` that can be launched on a :class:`Stream`. +With CUDA >= 13.1, graph definitions and +executable graphs expose a read-only integer, ``id``. Nodes expose ``local_id`` +(unique within their graph) and ``tools_id`` (for profiler correlation). +Use ``node.graph`` to obtain the containing graph definition; this view +shares ownership of the underlying graph. IDs are ephemeral, driver-assigned. + +``kernel.attributes.device_node_update_supported`` reports whether a kernel +supports device-side node updates. It requires CUDA >= 13.3. + .. currentmodule:: cuda.core .. autosummary:: diff --git a/cuda_core/docs/source/release/1.3.0-notes.rst b/cuda_core/docs/source/release/1.3.0-notes.rst index e17192853d8..14f5870c4d3 100644 --- a/cuda_core/docs/source/release/1.3.0-notes.rst +++ b/cuda_core/docs/source/release/1.3.0-notes.rst @@ -45,6 +45,12 @@ Breaking Changes New features ------------ +- Added read-only ``GraphDefinition.id``, ``Graph.id``, ``GraphNode.local_id`` + and ``GraphNode.tools_id`` properties (CUDA 13 build and driver 13.1+), and + ``KernelAttributes.device_node_update_supported`` (CUDA 13 build and driver 13.3+). + The existing ``GraphNode.graph`` property provides containing-graph navigation. + (`#2360 `__) + - Added :func:`host_launch`, which enqueues a host function on a stream in stream order: a Python callable, or a ctypes function pointer with the ``CUhostFn`` prototype, which ``cuda.core`` passes to the driver as is, so a pointer to diff --git a/cuda_core/tests/graph/test_graph_definition.py b/cuda_core/tests/graph/test_graph_definition.py index 041790a25eb..cd4f5038bd7 100644 --- a/cuda_core/tests/graph/test_graph_definition.py +++ b/cuda_core/tests/graph/test_graph_definition.py @@ -811,6 +811,34 @@ def test_graphdef_handle_valid(init_cuda): assert int(sample_graphdef.handle) != 0 +def test_graph_identity_attributes(init_cuda): + from cuda.core._utils.version import BUILD_CUDA_MAJOR, driver_version + + definition = GraphDefinition() + node = definition.empty() + other = definition.empty() + graph = definition.instantiate() + + try: + if BUILD_CUDA_MAJOR < 13 or driver_version() < (13, 1, 0): + with pytest.raises(NotImplementedError): + _ = definition.id + with pytest.raises(NotImplementedError): + _ = graph.id + with pytest.raises(NotImplementedError): + _ = node.local_id + with pytest.raises(NotImplementedError): + _ = node.tools_id + return + assert isinstance(definition.id, int) + assert isinstance(graph.id, int) + assert node.graph == definition + assert node.local_id != other.local_id + assert node.tools_id != other.tools_id + finally: + graph.close() + + def test_graphdef_entry_is_virtual(init_cuda): """Internal entry node is virtual (no pred/succ, type is None).""" sample_graphdef = GraphDefinition() diff --git a/cuda_core/tests/test_module.py b/cuda_core/tests/test_module.py index c2a912fcf57..0e2ac833124 100644 --- a/cuda_core/tests/test_module.py +++ b/cuda_core/tests/test_module.py @@ -345,6 +345,19 @@ def test_read_only_kernel_attributes(get_saxpy_kernel_cubin, attr, expected_type assert isinstance(value, expected_type), f"Expected {attr} to be of type {expected_type}, but got {type(value)}" +def test_device_node_update_supported(get_saxpy_kernel_cubin): + from cuda.core._utils.version import BUILD_CUDA_MAJOR, driver_version + + kernel, _ = get_saxpy_kernel_cubin + + for device in Device.get_all_devices(): + if BUILD_CUDA_MAJOR < 13 or driver_version() < (13, 3, 0): + with pytest.raises(NotImplementedError): + _ = kernel.attributes[device].device_node_update_supported + else: + assert isinstance(kernel.attributes[device].device_node_update_supported, bool) + + def test_object_code_load_ptx(get_saxpy_kernel_ptx): ptx, mod = get_saxpy_kernel_ptx sym_map = mod.symbol_mapping