Skip to content

cuda.core: introduce copy options for Buffer.copy_{to/from} - #2636

Merged
juenglin merged 12 commits into
NVIDIA:mainfrom
juenglin:copy-with-attributes
Aug 18, 2026
Merged

cuda.core: introduce copy options for Buffer.copy_{to/from}#2636
juenglin merged 12 commits into
NVIDIA:mainfrom
juenglin:copy-with-attributes

Conversation

@juenglin

@juenglin juenglin commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds an optional options keyword argument to Buffer.copy_to and Buffer.copy_from, exposing the same CopyOptions dataclass introduced by copy_batch (#2593) on the per-buffer path. Also fixes two issues in copy_batch itself, uncovered while aligning the two APIs: it now accepts PER_THREAD_DEFAULT_STREAM (previously rejected, even though the driver accepts it), and it now raises RuntimeError for src_access_order=DURING_API_CALL instead of taking the pre-CUDA-13 fallback path because the fallback cannot emulate the synchronization behavior that cudaMemcpyBatchedAsync guarantees. Closes #2365.

Public API

from cuda.core.utils import CopyOptions, MemcpySrcAccessOrder, MemcpyOverlapMode
from cuda.core import Device, Host

# Source access ordering
buf.copy_to(dst, stream=stream, options=CopyOptions(src_access_order=MemcpySrcAccessOrder.ANY))

# Overlap hint
buf.copy_to(dst, stream=stream, options=CopyOptions(overlap_mode=MemcpyOverlapMode.PREFER_OVERLAP_WITH_COMPUTE))

# Location hints (honored for managed memory; silently ignored otherwise)
buf.copy_to(dst, stream=stream, options=CopyOptions(
    src_access_order=MemcpySrcAccessOrder.STREAM,
    src_location_hint=Device(0),
    dst_location_hint=Host(),
))

options=None (the default) selects the existing cuMemcpyAsync path with no change in behaviour.

Behaviour

  • When options is set, cuMemcpyWithAttributesAsync is used only when cuda.bindings and the driver are both CUDA 13.2 or newer, the stream is not currently capturing, and the stream is not LEGACY_DEFAULT_STREAM.
  • Passing options together with LEGACY_DEFAULT_STREAM or a stream currently in graph capture mode raises TypeError, matching copy_batch. These checks only apply when options is given; options=None never triggers them, so existing code using either a default-stream token or graph capture is unaffected.
  • On an older cuda.bindings/driver install, options.src_access_order values of STREAM and ANY silently fall back to plain cuMemcpyAsync (that fallback already provides stream-ordered access, which is what either value promises). DURING_API_CALL promises all source reads complete before the call returns; a stream-ordered fallback cannot honor that, so it raises RuntimeError. copy_batch's own pre-CUDA-13 fallback loop had the same hazard and is fixed the same way in this PR (see "Relation to copy_batch" below).
  • options=None never raises for stream/capture reasons and always uses cuMemcpyAsync, so existing code is unaffected.
  • dst=None auto-allocation remains valid with options.

Relation to copy_batch

Uses the same CopyOptions dataclass, the shared _reject_unsupported_during_api_call helper (_copy_enums.py) that guards the one hazardous fallback case (DURING_API_CALL), and the _to_cu_memcpy_attributes conversion helper (in the shared _copy_attributes module extracted in the prerequisite refactor PR).

While aligning the two APIs, this PR also fixes copy_batch itself:

  • It previously rejected PER_THREAD_DEFAULT_STREAM unconditionally, even though cuMemcpyBatchAsync accepts it just like Buffer.copy_to/copy_from now do; it now accepts it.
  • Its pre-CUDA-13 fallback loop silently ignored all CopyOptions, including DURING_API_CALL, despite a comment claiming otherwise. It now raises RuntimeError for DURING_API_CALL on that path, via the same shared _reject_unsupported_during_api_call helper.

The single-copy path with options now matches copy_batch's rejection and fallback behavior in every respect but one:

Condition copy_batch Buffer.copy_to/copy_from
Old driver/cuda.bindings Silent fallback, options ignored (except DURING_API_CALLRuntimeError) Silent fallback, options ignored (except DURING_API_CALLRuntimeError)
Graph capture TypeError TypeError (only when options is given)
LEGACY_DEFAULT_STREAM TypeError TypeError (only when options is given)
PER_THREAD_DEFAULT_STREAM Accepted, honored Accepted, honored

The remaining difference is intentional: copy_batch is a brand-new function with no backward-compatibility constraint, so it rejects LEGACY_DEFAULT_STREAM/graph capture unconditionally. Buffer.copy_to/copy_from are existing APIs; options=None must not change behavior for existing callers, so it preserves the pre-#2365 behavior of accepting any default-stream token and remaining capturable into a graph.

Implementation notes

_buffer.pyx cimports the new _with_attributes_available CUDA 13.2 gate from that _copy_attributes; _copy_ops.pyx uses its own separate _batch_entry_point_available gate (CUDA 13.0), since copy_batch and the per-buffer path have different minimum-version requirements.

cydriver.cuMemcpyWithAttributesAsync is absent from cuda.bindings older than 13.2, so _buffer.pyx cannot cimport it directly without breaking builds against older cuda.bindings. It is instead routed through a small C++ function-pointer shim (memcpy_with_attributes_async in _cpp/resource_handles.{cpp,hpp}), resolved at runtime, mirroring the existing sm_resource_split (13.1+) shim pattern.

Tests

tests/memory/test_copy_single_options.py covers:

  • Data correctness for copy_to and copy_from with options=None and each of the three MemcpySrcAccessOrder values.
  • overlap_mode=PREFER_OVERLAP_WITH_COMPUTE does not corrupt data.
  • DURING_API_CALL is honored on the native path, or raises RuntimeError on the fallback path, depending on the installed driver/cuda.bindings.
  • LEGACY_DEFAULT_STREAM with options raises TypeError; PER_THREAD_DEFAULT_STREAM with options is accepted and honored.
  • options=None continues to work under graph capture and with LEGACY_DEFAULT_STREAM, unaffected by the new checks.
  • options with a capturing stream raises TypeError (both methods).
  • Rejects non-CopyOptions values passed as options.
  • Device + host location hints with managed memory (src_location_hint, dst_location_hint).
  • NUMA location hints (Host(numa_id=...), Host.numa_current()), skipped if the system reports no NUMA topology.
  • dst=None auto-allocation with options.

tests/memory/test_copy_batch.py and test_copy_batch_options.py cover the copy_batch fixes:

  • PER_THREAD_DEFAULT_STREAM is accepted and options are honored on it; LEGACY_DEFAULT_STREAM is still rejected.
  • DURING_API_CALL raises RuntimeError on the fallback path, matching the per-buffer behavior.
  • Direct unit tests of the shared _reject_unsupported_during_api_call helper.

tests/test_memory.py adds coverage for Buffer.copy_to/copy_from rejecting mismatched source/destination buffer sizes (a pre-existing check that had no test coverage).

@copy-pr-bot

copy-pr-bot Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions github-actions Bot added the cuda.core Everything related to the cuda.core module label Aug 14, 2026
@juenglin juenglin changed the title Ocuda.core: introduce copy options for Buffer.copy_{to/from} cuda.core: introduce copy options for Buffer.copy_{to/from} Aug 14, 2026
@juenglin

Copy link
Copy Markdown
Contributor Author

/ok to test d835ecf

@juenglin juenglin added the enhancement Any code-related improvements label Aug 14, 2026
@juenglin juenglin added this to the cuda.core 1.2.0 milestone Aug 14, 2026
@juenglin juenglin self-assigned this Aug 14, 2026
@juenglin
juenglin requested review from Andy-Jost and leofang August 14, 2026 22:06
@github-actions

This comment has been minimized.

@juenglin
juenglin force-pushed the copy-with-attributes branch 2 times, most recently from 9bb6d5e to 5080b34 Compare August 17, 2026 18:37
@juenglin juenglin added the P0 High priority - Must do! label Aug 17, 2026
@juenglin

Copy link
Copy Markdown
Contributor Author

/ok to test 5080b34

@juenglin
juenglin marked this pull request as ready for review August 17, 2026 22:12
Comment on lines +21 to +22
is accepted); ``copy_batch`` always rejects graph capture, while
``Buffer.copy_to``/``copy_from`` reject it only when ``options`` is given.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a future version of CUDA adds graph support, this will need to be relaxed. Is the layered check for graph capture only duplicating what the driver already checks, or does it have a separate benefit? If we're trading extra logic and correctness risk only for a nicer error message it might not be worth it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, if support for this becomes available the check for capturing streams would be dropped.

And you are right, the explicit check is to reject a capturing stream with a TypeError instead of a CUDAError, consistent with copy_batch.

Comment thread cuda_core/cuda/core/_memory/_buffer.pyx Outdated
Comment on lines +149 to +156
// cuMemcpyWithAttributesAsync (13.2+ — may be null on older drivers/bindings)
#if CUDA_VERSION >= 13020
extern decltype(&cuMemcpyWithAttributesAsync) p_cuMemcpyWithAttributesAsync;
#else
// cuMemcpyWithAttributesAsync doesn't exist in CUDA < 13.2 headers, so use a
// void* placeholder. The pointer is always null when built against older CUDA.
extern void* p_cuMemcpyWithAttributesAsync;
#endif

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have concerns about (1) minor-version-specific version gating, and (2) scope-creep inherent in using resource_handles for things besides resource lifetime management. I see this merely extends an existing pattern, so neither of these should block merge; but I'd like to follow up.

@juenglin
juenglin force-pushed the copy-with-attributes branch from 090444e to 9e7b50a Compare August 18, 2026 15:01
@juenglin
juenglin enabled auto-merge (squash) August 18, 2026 15:12
@juenglin
juenglin merged commit 6c5ecf9 into NVIDIA:main Aug 18, 2026
109 checks passed
@github-actions

Copy link
Copy Markdown
Doc Preview CI
Preview removed because the pull request was closed or merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cuda.core Everything related to the cuda.core module enhancement Any code-related improvements P0 High priority - Must do!

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEA] cuda.core: support copies with attributes (CUDA 13.2)

2 participants