Skip to content

Commit e0a2d1a

Browse files
lijinf2isVoid
andauthored
test(cuda.core): cover PDL same-stream overlap via GraphBuilder capture (#2524)
* test(cuda.core): verify PDL GraphBuilder capture and overlap Cover GraphBuilder stream-capture for programmatic_stream_serialization: functional launch, programmatic dependency edge mapping, and Hopper+ overlap (xfail if opportunistic), plus 1.2.0 release notes. * docs(cuda.core): use runtime PDL attribute name in 1.2.0 notes Refer to cudaLaunchAttributeProgrammaticStreamSerialization instead of the driver-style CU_LAUNCH_ATTRIBUTE_* spelling. * resolve pre-commit errors * test(cuda.core): skip PDL overlap graph capture on NumPy < 2.2.5 The test writes host buffers from np.from_dlpack, which are read-only before NumPy 2.2.5 (GH #28632). * test(cuda.core): use init_cuda in PDL overlap graph capture test Align with other graph builder tests so context setup and teardown stay consistent. * test(cuda.core): clarify PDL graph capture doc references Drop fragile Programming Guide section numbers and note Driver vs Runtime enum name equivalence at the edge asserts. * test(cuda.core): share PDL overlap protocol via helper runner Extract kernels and the same-stream / graph-capture overlap check into run_pdl_overlap_check so launcher and GraphBuilder tests stay in sync. * test(cuda.core): rename PDL overlap tests to emphasize same-stream Both direct and graph-capture paths are same-stream; update names and docs accordingly. * test(cuda.core): tidy PDL overlap helper import order and docstring * test(cuda.core): drop shared PDL overlap helper for clearer per-path tests Keep the stream overlap case self-contained in test_launcher, and inline the graph capture/overlap checks in test_graph_builder so each path stays simple. * test(cuda.core): rename PDL graph-capture kernels to dummy_kernel Avoid implying an in-kernel producer/consumer dependency; the test only needs two launches for the programmatic edge assert. * test(cuda.core): handle versioned graph edge API * test(cuda.core): skip PDL edge assert on bindings with #1804 UAF Published cuda-bindings <13.3.0 (or <12.9.7 on 12.x) returns dangling CUgraphEdgeData from cuGraphGetEdges; also fix release-note RST indent. --------- Co-authored-by: Michael Wang <[email protected]>
1 parent ebc2c95 commit e0a2d1a

2 files changed

Lines changed: 156 additions & 1 deletion

File tree

‎cuda_core/docs/source/release/1.2.0-notes.rst‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ New features
1717
loop with options silently ignored. Graph capture and default-stream tokens
1818
are rejected. Copies within a batch must not alias.
1919
(`#1333 <https://github.com/NVIDIA/cuda-python/issues/1333>`__)
20+
- Added the ``programmatic_stream_serialization`` option to
21+
:class:`LaunchConfig`, which sets
22+
``cudaLaunchAttributeProgrammaticStreamSerialization`` so a kernel can
23+
begin executing before the preceding kernel in the same stream has fully
24+
completed (programmatic dependent launch, PDL). Available starting with
25+
devices of compute capability 9.0.
26+
(`#2456 <https://github.com/NVIDIA/cuda-python/pull/2456>`__,
27+
`#1334 <https://github.com/NVIDIA/cuda-python/issues/1334>`__)
2028

2129
Fixes and enhancements
2230
----------------------

‎cuda_core/tests/graph/test_graph_builder.py‎

Lines changed: 148 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@
77
import time
88
import weakref
99

10+
import helpers
1011
import numpy as np
1112
import pytest
13+
from conftest import skipif_need_cuda_headers
1214
from cuda_python_test_helpers.marks import requires_module
1315
from helpers.graph_kernels import compile_common_kernels, compile_conditional_kernels
1416
from helpers.misc import try_create_condition
17+
from packaging.version import Version
1518

16-
from cuda.core import Device, LaunchConfig, LegacyPinnedMemoryResource, launch
19+
import cuda.bindings
20+
from cuda.core import Device, LaunchConfig, LegacyPinnedMemoryResource, Program, ProgramOptions, launch
1721
from cuda.core.graph import GraphBuilder, GraphDefinition
1822
from cuda.core.graph._graph_builder import (
1923
_capture_callback_with_tail_failure_for_testing,
@@ -709,3 +713,146 @@ def test_graph_definition_conditional_body_during_capture_raises(init_cuda):
709713
finally:
710714
body_gb.end_building()
711715
gb.end_building()
716+
717+
718+
@requires_module(np, "2.2.5", reason="need numpy 2.2.5+ (numpy GH #28632)")
719+
def test_pdl_launch_graph_capture(init_cuda):
720+
"""PDL LaunchConfig is graph-compatible via GraphBuilder stream capture.
721+
722+
Captures a first then a secondary launch with
723+
``programmatic_stream_serialization=True``, instantiates, and launches.
724+
Asserts functional correctness and that capture maps to a programmatic
725+
dependency edge (see Programming Guide, Programmatic Dependent Launch) —
726+
not kernel overlap.
727+
"""
728+
729+
def _assert_programmatic_dependency_edge(graph_definition):
730+
"""Assert capture of ProgrammaticStreamSerialization produced a programmatic edge.
731+
732+
Per Programming Guide (Programmatic Dependent Launch): stream-capturing a
733+
secondary launch with ``cudaLaunchAttributeProgrammaticStreamSerialization``
734+
maps to a programmatic dependency edge from the programmatic kernel port.
735+
"""
736+
from cuda.bindings import driver
737+
738+
# cuda.bindings before 13.3.0 (before 12.9.7 on the 12.x branch) returned
739+
# CUgraphEdgeData wrappers backed by a scratch buffer that was freed before the
740+
# call returned, so every field reads back as freed heap memory (#1804).
741+
version = Version(cuda.bindings.__version__)
742+
if version < Version("13.3.0" if version.major >= 13 else "12.9.7"):
743+
pytest.skip(f"cuda.bindings {version} returns dangling graph edge data (#1804)")
744+
745+
h_graph = graph_definition.handle
746+
if driver.CUDA_VERSION >= 13000:
747+
get_edges = driver.cuGraphGetEdges
748+
else:
749+
get_edges = driver.cuGraphGetEdges_v2
750+
751+
err, _, _, _, num_edges = get_edges(h_graph)
752+
assert err == driver.CUresult.CUDA_SUCCESS, err
753+
err, _, _, edge_data, num_edges = get_edges(h_graph, num_edges)
754+
assert err == driver.CUresult.CUDA_SUCCESS, err
755+
assert num_edges == 1, f"expected 1 edge, got {num_edges}"
756+
ed = edge_data[0]
757+
# Driver (cuda.h) ↔ Runtime / Programming Guide (driver_types.h):
758+
# CU_GRAPH_DEPENDENCY_TYPE_PROGRAMMATIC ↔ cudaGraphDependencyTypeProgrammatic
759+
# CU_GRAPH_KERNEL_NODE_PORT_PROGRAMMATIC ↔ cudaGraphKernelNodePortProgrammatic
760+
assert ed.type == driver.CUgraphDependencyType.CU_GRAPH_DEPENDENCY_TYPE_PROGRAMMATIC, ed.type
761+
assert ed.from_port == driver.CU_GRAPH_KERNEL_NODE_PORT_PROGRAMMATIC, ed.from_port
762+
763+
mod = compile_common_kernels()
764+
dummy_kernel = mod.get_kernel("add_one")
765+
766+
stream = Device().create_stream()
767+
mr = LegacyPinnedMemoryResource()
768+
buf = mr.allocate(4)
769+
arr = np.from_dlpack(buf).view(np.int32)
770+
arr[0] = 0
771+
772+
cfg = LaunchConfig(grid=1, block=1)
773+
pdl = LaunchConfig(grid=1, block=1, programmatic_stream_serialization=True)
774+
775+
gb = stream.create_graph_builder().begin_building()
776+
launch(gb, cfg, dummy_kernel, arr.ctypes.data)
777+
launch(gb, pdl, dummy_kernel, arr.ctypes.data)
778+
gb.end_building()
779+
_assert_programmatic_dependency_edge(gb.graph_definition)
780+
graph = gb.complete()
781+
782+
graph.launch(stream)
783+
stream.sync()
784+
assert arr[0] == 2
785+
786+
buf.close()
787+
stream.close()
788+
789+
790+
@skipif_need_cuda_headers
791+
@requires_module(np, "2.2.5", reason="need numpy 2.2.5+ (numpy GH #28632)")
792+
def test_pdl_same_stream_primary_secondary_overlap_via_graph(init_cuda):
793+
"""Same-stream PDL overlap via GraphBuilder stream capture on Hopper+."""
794+
dev = Device()
795+
if dev.compute_capability < (9, 0):
796+
pytest.skip("Programmatic Dependent Launch requires compute capability >= 9.0")
797+
798+
code = r"""
799+
#include <cuda_device_runtime_api.h>
800+
801+
extern "C" __global__ void primary_kernel(int* secondary_started, int* overlapped) {
802+
cudaTriggerProgrammaticLaunchCompletion();
803+
804+
const long long deadline = clock64() + 100000000LL; // ~50ms @ ~2GHz
805+
if (threadIdx.x == 0 && blockIdx.x == 0) {
806+
while (clock64() < deadline) {
807+
if (atomicAdd(secondary_started, 0) != 0) {
808+
atomicExch(overlapped, 1);
809+
return;
810+
}
811+
__nanosleep(1000);
812+
}
813+
}
814+
}
815+
816+
extern "C" __global__ void secondary_kernel(int* secondary_started) {
817+
if (threadIdx.x == 0 && blockIdx.x == 0) {
818+
atomicExch(secondary_started, 1);
819+
}
820+
}
821+
"""
822+
823+
arch = "".join(f"{i}" for i in dev.compute_capability)
824+
options = ProgramOptions(std="c++17", arch=f"sm_{arch}", include_path=helpers.CUDA_INCLUDE_PATH)
825+
module = Program(code, code_type="c++", options=options).compile("cubin")
826+
primary = module.get_kernel("primary_kernel")
827+
secondary = module.get_kernel("secondary_kernel")
828+
829+
stream = dev.create_stream(options={"nonblocking": True})
830+
mr = LegacyPinnedMemoryResource()
831+
secondary_started = np.from_dlpack(mr.allocate(4)).view(np.int32)
832+
overlapped = np.from_dlpack(mr.allocate(4)).view(np.int32)
833+
primary_cfg = LaunchConfig(grid=1, block=1)
834+
secondary_cfg = LaunchConfig(grid=1, block=1, programmatic_stream_serialization=True)
835+
836+
saw_overlap = False
837+
for _ in range(5):
838+
secondary_started[0] = 0
839+
overlapped[0] = 0
840+
841+
gb = stream.create_graph_builder().begin_building()
842+
launch(gb, primary_cfg, primary, secondary_started.ctypes.data, overlapped.ctypes.data)
843+
launch(gb, secondary_cfg, secondary, secondary_started.ctypes.data)
844+
graph = gb.end_building().complete()
845+
graph.launch(stream)
846+
stream.sync()
847+
graph.close()
848+
gb.close()
849+
850+
if overlapped[0] == 1:
851+
saw_overlap = True
852+
break
853+
854+
if not saw_overlap:
855+
pytest.xfail(
856+
"PDL (Programmatic Dependent Launch) graph overlap was not observed. "
857+
"If this keeps xfailing in CI, manually re-check on a quiet Hopper+ GPU."
858+
)

0 commit comments

Comments
 (0)