Repository navigation
cuda.core: reject stream=None — require explicit stream everywhere #2001
Description
Summary
Several cuda.core APIs accept stream=None and silently fall back to default_stream() (or NULL). This makes the stream choice implicit and environment-dependent (CUDA_PYTHON_CUDA_PER_THREAD_DEFAULT_STREAM), which is error-prone. Users should always pass a stream explicitly — including device.default_stream when they want the default.
Design rule
stream should be a keyword-only argument with no default value (*, stream: Stream), so callers must always pass a stream explicitly.
APIs that already follow this convention:
Buffer.copy_to(dst=None, *, stream: Stream | GraphBuilder)Buffer.copy_from(src, *, stream: Stream | GraphBuilder)Buffer.fill(value, *, stream: Stream | GraphBuilder)
APIs that need to change
stream=None → default_stream() (implicit fallback, remove it)
MemoryPool.allocate()/deallocate()GraphMemoryResource.allocate()/deallocate()GraphicsResource.map()LegacyPinnedMemoryResource.allocate()_SynchronousMemoryResource.allocate()Device.allocate()(delegates to the above)
stream=None → NULL / legacy default (same issue)
Kernel.max_potential_cluster_size(config, stream=None)Kernel.max_active_clusters(config, stream=None)
stream should become keyword-only
GraphBuilder.launch(self, stream: Stream)— currently positional, should be(self, *, stream: Stream)
APIs that are fine (no change needed)
launch(stream, config, kernel, *args)— stream is the 1st positional arg by designBuffer.close(stream=None)— None means "reuse the original stream", by designGraphicsResource.unmap(stream=None)/close(stream=None)— same, reuses the mapping stream
Proposed implementation
- Have
Stream_accept()raiseTypeErrorwhenstream is None, so the check is centralized and cannot be forgotten. - Remove the
= Nonedefault from every affected API signature. - Make
streamkeyword-only where it isn't already (exceptlaunch()where it's intentionally positional).
Prior art: CCCL's cccl-runtime
CCCL's modern CUDA runtime (libcudacxx, cuda:: namespace) follows the same explicit-stream principle:
The CUDA default (NULL) stream is not exposed as a first-class runtime object because it is tied to implicit per-device state and encourages hidden dependencies.
—
docs/libcudacxx/runtime/cudart_interactions.rst
Concretely:
cuda::launch(stream, config, kernel, args...)requires an explicitstream_refas the first parameter, with no default.- The
cuda::mr::resourceconcept requiresallocate(cuda::stream_ref, ...)/deallocate(cuda::stream_ref, ...)— no fallback to a default stream. cuda::stream_refdeletes constructors fromintandnullptr, and deprecates the parameterless (default stream) constructor.
Activity
leofang commentedon May 1, 2026
@Andy-Jost could you take this one too?
cuda.core: also require explicit stream for Buffer.from_ipc_descriptor (
cuda.core: align deallocate signatures and revert Graph.launch (NVIDI…
Andy-Jost commentedon May 4, 2026
I'm not sure
GraphBuilder.launch(self, stream: Stream)should change to keyword-only. It seems better to keep it positional to matchGraphBuilder.upload(self, stream: Stream)and freelaunch(Stream, ...).cuda.core: tighten test mocks and add Stream_accept(None) test (NVIDI…
cuda.core: fix Buffer pickle path broken by kw-only stream (NVIDIA#2001)
cuda.core: relax kw-only TypeError regex for Cython funcs (NVIDIA#2001)
cuda.core: review fixes for NVIDIA#2001 (graph_update.py + _legacy.py)
cuda.core: drop unused stream= kwargs from sync MR call sites (NVIDIA…
cuda.core: fix C++ teardown leak when buffer has no attached stream (N…
cuda.core: require explicit stream for stream-scheduling APIs (#2020)