Skip to content

Commit daa6d49

Browse files
committed
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.
1 parent ef9ecb8 commit daa6d49

3 files changed

Lines changed: 129 additions & 163 deletions

File tree

cuda_core/tests/graph/test_graph_builder.py

Lines changed: 3 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@
77
import time
88
import weakref
99

10-
import helpers
1110
import numpy as np
1211
import pytest
1312
from conftest import skipif_need_cuda_headers
1413
from cuda_python_test_helpers.marks import requires_module
1514
from helpers.graph_kernels import compile_common_kernels, compile_conditional_kernels
1615
from helpers.misc import try_create_condition
16+
from helpers.pdl_kernels import run_pdl_overlap_check
1717

18-
from cuda.core import Device, LaunchConfig, LegacyPinnedMemoryResource, Program, ProgramOptions, launch
18+
from cuda.core import Device, LaunchConfig, LegacyPinnedMemoryResource, launch
1919
from cuda.core.graph import GraphBuilder, GraphDefinition
2020
from cuda.core.graph._graph_builder import (
2121
_capture_callback_with_tail_failure_for_testing,
@@ -783,85 +783,4 @@ def test_pdl_primary_secondary_overlap_graph_capture(init_cuda):
783783
but launches are captured into a CUDA graph (see CUDA Programming Guide,
784784
Programmatic Dependent Launch). Overlap is opportunistic → miss is xfail.
785785
"""
786-
dev = Device()
787-
if dev.compute_capability < (9, 0):
788-
pytest.skip("Programmatic Dependent Launch requires compute capability >= 9.0")
789-
stream = dev.create_stream(options={"nonblocking": True})
790-
791-
# clock64 budgets are in GPU cycles; keep the post-trigger window long enough
792-
# for the secondary to boot, but short enough for a unit test.
793-
code = r"""
794-
#include <cuda_device_runtime_api.h>
795-
796-
extern "C" __global__ void primary_kernel(int* secondary_started, int* overlapped) {
797-
cudaTriggerProgrammaticLaunchCompletion();
798-
799-
const long long deadline = clock64() + 100000000LL; // ~50ms @ ~2GHz
800-
if (threadIdx.x == 0 && blockIdx.x == 0) {
801-
while (clock64() < deadline) {
802-
if (atomicAdd(secondary_started, 0) != 0) {
803-
atomicExch(overlapped, 1);
804-
return;
805-
}
806-
__nanosleep(1000);
807-
}
808-
}
809-
}
810-
811-
extern "C" __global__ void secondary_kernel(int* secondary_started) {
812-
if (threadIdx.x == 0 && blockIdx.x == 0) {
813-
atomicExch(secondary_started, 1);
814-
}
815-
}
816-
"""
817-
818-
arch = "".join(f"{i}" for i in dev.compute_capability)
819-
pro_opts = ProgramOptions(std="c++17", arch=f"sm_{arch}", include_path=helpers.CUDA_INCLUDE_PATH)
820-
prog = Program(code, code_type="c++", options=pro_opts)
821-
mod = prog.compile("cubin")
822-
primary = mod.get_kernel("primary_kernel")
823-
secondary = mod.get_kernel("secondary_kernel")
824-
825-
mr = LegacyPinnedMemoryResource()
826-
secondary_started = np.from_dlpack(mr.allocate(4)).view(np.int32)
827-
overlapped = np.from_dlpack(mr.allocate(4)).view(np.int32)
828-
829-
primary_cfg = LaunchConfig(grid=1, block=1)
830-
secondary_cfg = LaunchConfig(grid=1, block=1, programmatic_stream_serialization=True)
831-
secondary_serial_cfg = LaunchConfig(grid=1, block=1)
832-
833-
def _run(secondary_launch_cfg: LaunchConfig) -> int:
834-
secondary_started[0] = 0
835-
overlapped[0] = 0
836-
gb = stream.create_graph_builder().begin_building()
837-
launch(gb, primary_cfg, primary, secondary_started.ctypes.data, overlapped.ctypes.data)
838-
launch(gb, secondary_launch_cfg, secondary, secondary_started.ctypes.data)
839-
graph = gb.end_building().complete()
840-
try:
841-
graph.launch(stream)
842-
stream.sync()
843-
finally:
844-
graph.close()
845-
gb.close()
846-
return int(overlapped[0])
847-
848-
# Without the PDL attribute, same-stream kernels stay serialized even via graph.
849-
assert _run(secondary_serial_cfg) == 0, "Expected no overlap when programmatic_stream_serialization is False"
850-
851-
# PDL overlap is opportunistic; retry a few times on a quiet GPU.
852-
saw_overlap = False
853-
for _ in range(5):
854-
if _run(secondary_cfg) == 1:
855-
saw_overlap = True
856-
break
857-
858-
if not saw_overlap:
859-
pytest.xfail(
860-
"PDL (Programmatic Dependent Launch) graph-capture overlap was not observed. "
861-
"If this keeps xfailing in CI, manually re-check on a quiet Hopper+ GPU."
862-
)
863-
864-
print(
865-
f"PDL graph-capture overlap verified on {dev.name} compute capability {dev.compute_capability}",
866-
flush=True,
867-
)
786+
run_pdl_overlap_check(Device(), via_graph=True)
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
"""Shared helpers for Programmatic Dependent Launch overlap tests."""
5+
6+
import helpers
7+
import numpy as np
8+
import pytest
9+
10+
from cuda.core import LaunchConfig, LegacyPinnedMemoryResource, Program, ProgramOptions, launch
11+
12+
13+
def compile_pdl_overlap_kernels(device):
14+
"""Compile primary/secondary kernels used to detect PDL same-stream overlap.
15+
16+
The primary triggers programmatic launch completion then spins briefly looking
17+
for a flag written by the secondary. Seeing that flag proves both grids were
18+
resident at once. clock64 budgets are in GPU cycles: long enough for the
19+
secondary to boot, short enough for a unit test.
20+
21+
Returns:
22+
(primary_kernel, secondary_kernel)
23+
"""
24+
code = r"""
25+
#include <cuda_device_runtime_api.h>
26+
27+
extern "C" __global__ void primary_kernel(int* secondary_started, int* overlapped) {
28+
cudaTriggerProgrammaticLaunchCompletion();
29+
30+
const long long deadline = clock64() + 100000000LL; // ~50ms @ ~2GHz
31+
if (threadIdx.x == 0 && blockIdx.x == 0) {
32+
while (clock64() < deadline) {
33+
if (atomicAdd(secondary_started, 0) != 0) {
34+
atomicExch(overlapped, 1);
35+
return;
36+
}
37+
__nanosleep(1000);
38+
}
39+
}
40+
}
41+
42+
extern "C" __global__ void secondary_kernel(int* secondary_started) {
43+
if (threadIdx.x == 0 && blockIdx.x == 0) {
44+
atomicExch(secondary_started, 1);
45+
}
46+
}
47+
"""
48+
arch = "".join(f"{i}" for i in device.compute_capability)
49+
pro_opts = ProgramOptions(std="c++17", arch=f"sm_{arch}", include_path=helpers.CUDA_INCLUDE_PATH)
50+
prog = Program(code, code_type="c++", options=pro_opts)
51+
mod = prog.compile("cubin")
52+
return mod.get_kernel("primary_kernel"), mod.get_kernel("secondary_kernel")
53+
54+
55+
def run_pdl_overlap_check(device, *, via_graph: bool = False):
56+
"""Run the shared primary/secondary PDL overlap protocol.
57+
58+
Asserts no overlap without ``programmatic_stream_serialization``, then retries
59+
a few times with it enabled. Overlap is opportunistic → miss is xfail.
60+
61+
Args:
62+
device: Current CUDA device (compute capability >= 9.0 required).
63+
via_graph: If True, capture launches into a CUDA graph and launch the
64+
graph; otherwise launch kernels directly on the stream.
65+
"""
66+
if device.compute_capability < (9, 0):
67+
pytest.skip("Programmatic Dependent Launch requires compute capability >= 9.0")
68+
69+
stream = device.create_stream(options={"nonblocking": True})
70+
primary, secondary = compile_pdl_overlap_kernels(device)
71+
72+
mr = LegacyPinnedMemoryResource()
73+
secondary_started = np.from_dlpack(mr.allocate(4)).view(np.int32)
74+
overlapped = np.from_dlpack(mr.allocate(4)).view(np.int32)
75+
76+
primary_cfg = LaunchConfig(grid=1, block=1)
77+
secondary_cfg = LaunchConfig(grid=1, block=1, programmatic_stream_serialization=True)
78+
secondary_serial_cfg = LaunchConfig(grid=1, block=1)
79+
80+
def _run(secondary_launch_cfg: LaunchConfig) -> int:
81+
secondary_started[0] = 0
82+
overlapped[0] = 0
83+
if via_graph:
84+
gb = stream.create_graph_builder().begin_building()
85+
launch(gb, primary_cfg, primary, secondary_started.ctypes.data, overlapped.ctypes.data)
86+
launch(gb, secondary_launch_cfg, secondary, secondary_started.ctypes.data)
87+
graph = gb.end_building().complete()
88+
try:
89+
graph.launch(stream)
90+
stream.sync()
91+
finally:
92+
graph.close()
93+
gb.close()
94+
else:
95+
launch(stream, primary_cfg, primary, secondary_started.ctypes.data, overlapped.ctypes.data)
96+
launch(stream, secondary_launch_cfg, secondary, secondary_started.ctypes.data)
97+
stream.sync()
98+
return int(overlapped[0])
99+
100+
path = "graph-capture" if via_graph else "same-stream"
101+
assert _run(secondary_serial_cfg) == 0, (
102+
f"Expected no overlap when programmatic_stream_serialization is False ({path})"
103+
)
104+
105+
saw_overlap = False
106+
for _ in range(5):
107+
if _run(secondary_cfg) == 1:
108+
saw_overlap = True
109+
break
110+
111+
if not saw_overlap:
112+
# Overlap is never guaranteed by the driver, so a miss is reported as an
113+
# expected failure rather than turning a busy GPU into a red CI run.
114+
pytest.xfail(
115+
f"PDL (Programmatic Dependent Launch) {path} overlap was not observed. "
116+
"If this keeps xfailing in CI, manually re-check on a quiet Hopper+ GPU."
117+
)
118+
119+
print(
120+
f"PDL {path} overlap verified on {device.name} compute capability {device.compute_capability}",
121+
flush=True,
122+
)

cuda_core/tests/test_launcher.py

Lines changed: 4 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import helpers
77
from cuda_python_test_helpers.marks import requires_module
88
from helpers.misc import StreamWrapper
9+
from helpers.pdl_kernels import run_pdl_overlap_check
910

1011
try:
1112
import cupy as cp
@@ -203,7 +204,8 @@ def test_to_native_launch_config_pdl():
203204

204205

205206
@skipif_need_cuda_headers
206-
def test_pdl_primary_secondary_overlap_same_stream():
207+
@requires_module(np, "2.2.5", reason="need numpy 2.2.5+ (numpy GH #28632)")
208+
def test_pdl_primary_secondary_overlap_same_stream(init_cuda):
207209
"""Primary + secondary PDL launch on one stream can overlap on Hopper+.
208210
209211
Secondary is launched with ``programmatic_stream_serialization=True``. After
@@ -214,84 +216,7 @@ def test_pdl_primary_secondary_overlap_same_stream():
214216
Note concurrency is opportunistic, so a missing overlap execution is reported as
215217
an expected failure.
216218
"""
217-
dev = Device()
218-
if dev.compute_capability < (9, 0):
219-
pytest.skip("Programmatic Dependent Launch requires compute capability >= 9.0")
220-
dev.set_current()
221-
stream = dev.create_stream(options={"nonblocking": True})
222-
223-
# clock64 budgets are in GPU cycles; keep the post-trigger window long enough
224-
# for the secondary to boot, but short enough for a unit test.
225-
code = r"""
226-
#include <cuda_device_runtime_api.h>
227-
228-
extern "C" __global__ void primary_kernel(int* secondary_started, int* overlapped) {
229-
cudaTriggerProgrammaticLaunchCompletion();
230-
231-
const long long deadline = clock64() + 100000000LL; // ~50ms @ ~2GHz
232-
if (threadIdx.x == 0 && blockIdx.x == 0) {
233-
while (clock64() < deadline) {
234-
if (atomicAdd(secondary_started, 0) != 0) {
235-
atomicExch(overlapped, 1);
236-
return;
237-
}
238-
__nanosleep(1000);
239-
}
240-
}
241-
}
242-
243-
extern "C" __global__ void secondary_kernel(int* secondary_started) {
244-
if (threadIdx.x == 0 && blockIdx.x == 0) {
245-
atomicExch(secondary_started, 1);
246-
}
247-
}
248-
"""
249-
250-
arch = "".join(f"{i}" for i in dev.compute_capability)
251-
pro_opts = ProgramOptions(std="c++17", arch=f"sm_{arch}", include_path=helpers.CUDA_INCLUDE_PATH)
252-
prog = Program(code, code_type="c++", options=pro_opts)
253-
mod = prog.compile("cubin")
254-
primary = mod.get_kernel("primary_kernel")
255-
secondary = mod.get_kernel("secondary_kernel")
256-
257-
mr = LegacyPinnedMemoryResource()
258-
secondary_started = np.from_dlpack(mr.allocate(4)).view(np.int32)
259-
overlapped = np.from_dlpack(mr.allocate(4)).view(np.int32)
260-
261-
primary_cfg = LaunchConfig(grid=1, block=1)
262-
secondary_cfg = LaunchConfig(grid=1, block=1, programmatic_stream_serialization=True)
263-
secondary_serial_cfg = LaunchConfig(grid=1, block=1)
264-
265-
def _run(secondary_launch_cfg: LaunchConfig) -> int:
266-
secondary_started[0] = 0
267-
overlapped[0] = 0
268-
launch(stream, primary_cfg, primary, secondary_started.ctypes.data, overlapped.ctypes.data)
269-
launch(stream, secondary_launch_cfg, secondary, secondary_started.ctypes.data)
270-
stream.sync()
271-
return int(overlapped[0])
272-
273-
# Without the PDL attribute, same-stream kernels stay serialized.
274-
assert _run(secondary_serial_cfg) == 0, "Expected no overlap when programmatic_stream_serialization is False"
275-
276-
# PDL overlap is opportunistic; retry a few times on a quiet GPU.
277-
saw_overlap = False
278-
for _ in range(5):
279-
if _run(secondary_cfg) == 1:
280-
saw_overlap = True
281-
break
282-
283-
if not saw_overlap:
284-
# Overlap is never guaranteed by the driver, so a miss is reported as an
285-
# expected failure rather than turning a busy GPU into a red CI run.
286-
pytest.xfail(
287-
"PDL (Programmatic Dependent Launch) overlap was not observed. "
288-
"If this keeps xfailing in CI, manually re-check on a quiet Hopper+ GPU."
289-
)
290-
291-
print(
292-
f"PDL (Programmatic Dependent Launch) overlap verified on {dev.name} compute capability {dev.compute_capability}",
293-
flush=True,
294-
)
219+
run_pdl_overlap_check(Device(), via_graph=False)
295220

296221

297222
def test_launch_config_cluster_accepts_hopper_cc(monkeypatch):

0 commit comments

Comments
 (0)