Repository navigation
Conversation
Add cuda.core.host_launch(stream, fn, *, user_data=None), the stream-side counterpart of GraphBuilder.callback. It enqueues a Python callable or a ctypes function pointer with the CUhostFn prototype on a stream through cuLaunchHostFunc; a GraphBuilder argument forwards to its callback. The callable and a copied user_data stay alive until the function has run and are released afterwards on the main thread through the deferred-cleanup queue, never on the driver thread. A plain stream under active capture is rejected, because the owners could not be attached to the captured node. The shared callback helper moves from cuda/core/graph/_host_callback to cuda/core/_host_callback, since a core module must not import the graph package at module level. The launch record and trampoline live in _cpp/rt/host_launch.cpp, and cuLaunchHostFunc joins the driver table. Written by Codex (gpt-6.1-sol); reviewed by Claude. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
📝 Summary
Priority: Change: Feature Merge Risk:
|
State that a ctypes callback that wraps a Python function acquires the GIL through ctypes, so the deadlock rule applies to it; complete the Raises section; say the driver may reject CUDA calls from the callback rather than that it does. Upcast the launch record to the cleanup base before the void* parameter, as the graph producers do. Prove the user_data copy by changing the caller's buffer while the stream is held, pin the unraisable type and message, and link the example from the examples page. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
There was a problem hiding this comment.
Actionable comments posted: 1

Review info
-
Run configuration-
- Source mode: Auto
- Repository: NVIDIA/cuda-python
- Branch: main
- File: .coderabbit.yaml
- Commit: 94625c1
- Layers: Repository YAML
- Review profile: CHILL
- Plan: Enterprise
- Run ID:
7a78139b-bfca-4094-acd9-991e49bd39f7
Files selected for processing: 8-
cuda_core/cuda/core/_cpp/rt/DESIGN.mdcuda_core/cuda/core/_cpp/rt/GRAPH_ATTACHMENTS.mdcuda_core/cuda/core/_cpp/rt/host_launch.cppcuda_core/cuda/core/_launcher.pyicuda_core/cuda/core/_launcher.pyxcuda_core/docs/source/examples.rstcuda_core/docs/source/release/1.3.0-notes.rstcuda_core/tests/test_host_launch.py
Files skipped from review as they are similar to previous changes: 3-
- cuda_core/cuda/core/_cpp/rt/DESIGN.md
- cuda_core/cuda/core/_cpp/rt/GRAPH_ATTACHMENTS.md
- cuda_core/docs/source/release/1.3.0-notes.rst
Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 10 remain after this review.
-
| .. warning:: | ||
|
|
||
| The function must not call CUDA, directly or through a library. The | ||
| driver does not permit such calls and may reject them with an error. |
There was a problem hiding this comment.
suggestion: State that CUDA calls from the callback can deadlock.
The driver does not guarantee an error for a CUDA call from a host callback. Such a call can also deadlock. State that consequence here and regenerate the matching stub documentation. (docs.nvidia.com)
Summary
cuda.corehad host callbacks for graphs but no way to enqueue one on a plain stream, so mixed host and device ordering neededcuda.bindings. This PR addscuda.core.host_launch(stream, fn, *, user_data=None), the stream-side counterpart ofGraphBuilder.callback. It enqueues a host function on a stream in stream order throughcuLaunchHostFunc: a Python callable, which runs with the GIL, or a ctypes function pointer with theCUhostFnprototype, whichcuda.corepasses to the driver as is, so a pointer to compiled code runs without the GIL (a ctypes callback that wraps a Python function still acquires it through ctypes). Thefnanduser_dataforms and their validation are the ones the graph callback methods already use, and aGraphBuilderargument forwards to itscallback, so the same call works under stream capture.cuda.coreowns the callable and a copieduser_datauntil the function has run. The trampoline hands the launch record to the existing deferred-cleanup queue after the user function returns, so the owners are released on the main thread, never on the driver's callback thread, where releasing a Python object would take the GIL and a finalizer could call CUDA. A plainStreamunder active capture raisesRuntimeErrorand points toGraphBuilder.callback, because the record's owners could not be attached to the captured node and the record must not be reused by graph replays. A Python exception inside the callback is reported throughsys.unraisablehookand the stream continues, as on the graph path. The docstring states the driver's rules: the function must not call CUDA, and a thread that holds the GIL while blocked in a CUDA wait on the stream deadlocks with a Python callback.Changes
cuda_core/cuda/core/_launcher.pyx:host_launch, exported fromcuda.core.cuda_core/cuda/core/_host_callback.pyx,.pxd: the shared callback helper, moved fromcuda/core/graph/unchanged; a core module must not import the graph package at module level. The three graph cimports follow.cuda_core/cuda/core/_cpp/rt/host_launch.cpp: the launch record and trampoline behind one entry point,launch_host_function, declared inapi.hpp,_rt.pxd, and_rt.pyx;cuLaunchHostFuncjoins the driver table indriver_api.hpp.cuda_core/docs/source/api.rst:host_launchnext tolaunch;_cpp/rt/DESIGN.mdandGRAPH_ATTACHMENTS.mdname the new file and the queue's second producer.cuda_core/examples/host_launch.py: a host function ordered between two kernel launches, linked fromdocs/source/examples.rst.cuda_core/tests/test_host_launch.py: ordering between prior and later stream work, release of the callable after it ran, copied bytesuser_data, pass-through of an intuser_data, theTypeErrorandValueErrorcases, forwarding of aGraphBuilderwith two graph launches, rejection of a capturing stream without invalidating the capture, unraisable reporting with the stream still usable, two streams keeping their own data, rejection ofNone, and the deprecated stream-protocol path.Related Work
Closes #2058. The host copy function requested in #3256 builds on this.
🤖 Generated with Claude Code