From 198cbb1a6bbb94609b2b3a4d9578161763c1f417 Mon Sep 17 00:00:00 2001 From: Uday Arora Date: Sun, 19 Jul 2026 03:46:00 -0700 Subject: [PATCH 1/3] cuda.core: validate pinned pool support Reject unsupported host memory pools during allocation instead of allowing a later copy to fail with CUDA_ERROR_INVALID_VALUE. Signed-off-by: Uday Arora --- .../core/_memory/_pinned_memory_resource.pyi | 14 +++++++ .../core/_memory/_pinned_memory_resource.pyx | 42 ++++++++++++++++++- cuda_core/tests/test_memory.py | 18 ++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/cuda_core/cuda/core/_memory/_pinned_memory_resource.pyi b/cuda_core/cuda/core/_memory/_pinned_memory_resource.pyi index a83cd8ea581..9cad97a1d0d 100644 --- a/cuda_core/cuda/core/_memory/_pinned_memory_resource.pyi +++ b/cuda_core/cuda/core/_memory/_pinned_memory_resource.pyi @@ -5,8 +5,11 @@ from __future__ import annotations import uuid from dataclasses import dataclass +from cuda.core._memory._buffer import Buffer from cuda.core._memory._ipc import IPCAllocationHandle from cuda.core._memory._memory_pool import _MemPool +from cuda.core._stream import Stream +from cuda.core.graph import GraphBuilder @dataclass @@ -63,6 +66,14 @@ class PinnedMemoryResource(_MemPool): Notes ----- + The device associated with ``stream`` must support host memory pools. If + ``numa_id`` is set or derived for IPC, it must support host NUMA memory pools. + You can query these capabilities through + ``Device.properties.host_memory_pools_supported`` and + ``Device.properties.host_numa_memory_pools_supported``. If the required pool + is unsupported and stream-ordered allocation is not needed, use + :class:`LegacyPinnedMemoryResource`. + To create an IPC-Enabled memory resource (MR) that is capable of sharing allocations between processes, specify ``ipc_enabled=True`` in the initializer option. When IPC is enabled and ``numa_id`` is not specified, the NUMA node @@ -76,6 +87,9 @@ class PinnedMemoryResource(_MemPool): def __init__(self, options: PinnedMemoryResourceOptions | dict[str, object] | None=None) -> None: ... + def allocate(self, size: int, *, stream: Stream | GraphBuilder) -> Buffer: + """Allocate a host-pinned buffer asynchronously on the supplied stream.""" + def __reduce__(self) -> tuple[object, ...]: ... diff --git a/cuda_core/cuda/core/_memory/_pinned_memory_resource.pyx b/cuda_core/cuda/core/_memory/_pinned_memory_resource.pyx index 4335fbb41c2..2caeaf24de0 100644 --- a/cuda_core/cuda/core/_memory/_pinned_memory_resource.pyx +++ b/cuda_core/cuda/core/_memory/_pinned_memory_resource.pyx @@ -5,9 +5,11 @@ from __future__ import annotations from cuda.bindings cimport cydriver -from cuda.core._memory._memory_pool cimport _MemPool, MP_init_create_pool, MP_init_current_pool +from cuda.core._memory._buffer cimport Buffer +from cuda.core._memory._memory_pool cimport _MemPool, _MP_allocate, MP_init_create_pool, MP_init_current_pool from cuda.core._memory cimport _ipc from cuda.core._memory._ipc cimport IPCAllocationHandle +from cuda.core._stream cimport Stream, Stream_accept from cuda.core._utils.cuda_utils cimport ( check_or_create_options, HANDLE_RETURN, @@ -19,6 +21,12 @@ import platform # no-cython-lint import uuid from cuda.core._utils.cuda_utils import check_multiprocessing_start_method +from cuda.core._utils.cuda_utils import CUDAError # no-cython-lint + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from cuda.core.graph import GraphBuilder __all__ = ['PinnedMemoryResource', 'PinnedMemoryResourceOptions'] @@ -78,6 +86,14 @@ cdef class PinnedMemoryResource(_MemPool): Notes ----- + The device associated with ``stream`` must support host memory pools. If + ``numa_id`` is set or derived for IPC, it must support host NUMA memory pools. + You can query these capabilities through + ``Device.properties.host_memory_pools_supported`` and + ``Device.properties.host_numa_memory_pools_supported``. If the required pool + is unsupported and stream-ordered allocation is not needed, use + :class:`LegacyPinnedMemoryResource`. + To create an IPC-Enabled memory resource (MR) that is capable of sharing allocations between processes, specify ``ipc_enabled=True`` in the initializer option. When IPC is enabled and ``numa_id`` is not specified, the NUMA node @@ -91,6 +107,30 @@ cdef class PinnedMemoryResource(_MemPool): def __init__(self, options: PinnedMemoryResourceOptions | dict[str, object] | None = None) -> None: _PMR_init(self, options) + def allocate(self, size_t size, *, stream: Stream | GraphBuilder) -> Buffer: + """Allocate a host-pinned buffer asynchronously on the supplied stream.""" + if self.is_mapped: + raise TypeError("Cannot allocate from a mapped IPC-enabled memory resource") + cdef Stream s = Stream_accept(stream) + device = s.device + + cdef bint supported + if self._numa_id >= 0: + supported = device.properties.host_numa_memory_pools_supported + else: + IF CUDA_CORE_BUILD_MAJOR >= 13: + supported = device.properties.host_memory_pools_supported + ELSE: + supported = True + + if not supported: + raise CUDAError( + f"CUDA_ERROR_NOT_SUPPORTED: CUDA device {device.device_id} does not " + "support the requested host memory pool for PinnedMemoryResource. " + "Use LegacyPinnedMemoryResource if memory-pool features are not required." + ) + return _MP_allocate(self, size, s) + def __reduce__(self) -> tuple[object, ...]: return PinnedMemoryResource.from_registry, (self.uuid,) diff --git a/cuda_core/tests/test_memory.py b/cuda_core/tests/test_memory.py index 6af4d025b03..8b4889c21f7 100644 --- a/cuda_core/tests/test_memory.py +++ b/cuda_core/tests/test_memory.py @@ -751,6 +751,24 @@ def test_pinned_memory_resource_initialization(init_cuda): buffer.close() +@pytest.mark.agent_authored(model="gpt-5.6") +def test_pinned_memory_resource_rejects_unsupported_host_pool(init_cuda): + device = init_cuda + try: + supported = device.properties.host_memory_pools_supported + except AttributeError: + pytest.skip("Host memory pool capability query requires CUDA 13.0 or later") + if supported: + pytest.skip("Device supports host memory pools") + + mr = PinnedMemoryResource() + try: + with pytest.raises(CUDAError, match="CUDA_ERROR_NOT_SUPPORTED.*LegacyPinnedMemoryResource"): + mr.allocate(1024, stream=device.default_stream) + finally: + mr.close() + + def test_managed_memory_resource_initialization(init_cuda): device = Device() skip_if_managed_memory_unsupported(device) From 5b7c9b1a1781a7ad266557f215a8b2f7655f3d6a Mon Sep 17 00:00:00 2001 From: Andy Jost Date: Thu, 6 Aug 2026 10:32:56 -0700 Subject: [PATCH 2/3] cuda.core: tighten pinned host pool capability check Drop the unnecessary CUDA 12 fence around host_memory_pools_supported, raise RuntimeError instead of a synthetic CUDAError, and keep the regression test hardware-gated for devices without host memory pools. --- .../core/_memory/_pinned_memory_resource.pyx | 23 ++++++++----------- cuda_core/tests/test_memory.py | 13 ++++------- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/cuda_core/cuda/core/_memory/_pinned_memory_resource.pyx b/cuda_core/cuda/core/_memory/_pinned_memory_resource.pyx index 2caeaf24de0..e5f89606330 100644 --- a/cuda_core/cuda/core/_memory/_pinned_memory_resource.pyx +++ b/cuda_core/cuda/core/_memory/_pinned_memory_resource.pyx @@ -21,7 +21,6 @@ import platform # no-cython-lint import uuid from cuda.core._utils.cuda_utils import check_multiprocessing_start_method -from cuda.core._utils.cuda_utils import CUDAError # no-cython-lint from typing import TYPE_CHECKING @@ -113,21 +112,17 @@ cdef class PinnedMemoryResource(_MemPool): raise TypeError("Cannot allocate from a mapped IPC-enabled memory resource") cdef Stream s = Stream_accept(stream) device = s.device - - cdef bint supported - if self._numa_id >= 0: - supported = device.properties.host_numa_memory_pools_supported - else: - IF CUDA_CORE_BUILD_MAJOR >= 13: - supported = device.properties.host_memory_pools_supported - ELSE: - supported = True + cdef bint supported = ( + device.properties.host_numa_memory_pools_supported + if self._numa_id >= 0 + else device.properties.host_memory_pools_supported + ) if not supported: - raise CUDAError( - f"CUDA_ERROR_NOT_SUPPORTED: CUDA device {device.device_id} does not " - "support the requested host memory pool for PinnedMemoryResource. " - "Use LegacyPinnedMemoryResource if memory-pool features are not required." + raise RuntimeError( + f"CUDA device {device.device_id} does not support the requested " + "host memory pool for PinnedMemoryResource. Use " + "LegacyPinnedMemoryResource if memory-pool features are not required." ) return _MP_allocate(self, size, s) diff --git a/cuda_core/tests/test_memory.py b/cuda_core/tests/test_memory.py index 8b4889c21f7..5c98ec668ae 100644 --- a/cuda_core/tests/test_memory.py +++ b/cuda_core/tests/test_memory.py @@ -751,19 +751,16 @@ def test_pinned_memory_resource_initialization(init_cuda): buffer.close() -@pytest.mark.agent_authored(model="gpt-5.6") +@pytest.mark.agent_authored(model="cursor-grok-4.5") def test_pinned_memory_resource_rejects_unsupported_host_pool(init_cuda): + """allocate() must fail on devices without host memory pool support (see #2486).""" device = init_cuda - try: - supported = device.properties.host_memory_pools_supported - except AttributeError: - pytest.skip("Host memory pool capability query requires CUDA 13.0 or later") - if supported: + if device.properties.host_memory_pools_supported: pytest.skip("Device supports host memory pools") - mr = PinnedMemoryResource() + mr = PinnedMemoryResource(PinnedMemoryResourceOptions(max_size=POOL_SIZE)) try: - with pytest.raises(CUDAError, match="CUDA_ERROR_NOT_SUPPORTED.*LegacyPinnedMemoryResource"): + with pytest.raises(RuntimeError, match="does not support.*LegacyPinnedMemoryResource"): mr.allocate(1024, stream=device.default_stream) finally: mr.close() From 9c8528ea540fb70cace82ac887de7beeeb534d89 Mon Sep 17 00:00:00 2001 From: Andy Jost Date: Fri, 7 Aug 2026 12:34:32 -0700 Subject: [PATCH 3/3] cuda.core: gate CUDA 13-only device properties Return safe defaults in CUDA 12 builds and skip the pinned-pool regression test when pool construction is unsupported. --- cuda_core/cuda/core/_device.pyx | 40 +++++++++++++++++++++------------ cuda_core/tests/test_device.py | 18 ++++----------- cuda_core/tests/test_memory.py | 7 +++++- 3 files changed, 36 insertions(+), 29 deletions(-) diff --git a/cuda_core/cuda/core/_device.pyx b/cuda_core/cuda/core/_device.pyx index 29740cb466a..a0a0f472f2b 100644 --- a/cuda_core/cuda/core/_device.pyx +++ b/cuda_core/cuda/core/_device.pyx @@ -924,34 +924,46 @@ cdef class DeviceProperties: @property def host_memory_pools_supported(self) -> bool: """bool: Device supports HOST location with the cuMemAllocAsync and cuMemPool family of APIs.""" - return bool( - self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_HOST_MEMORY_POOLS_SUPPORTED) - ) + IF CUDA_CORE_BUILD_MAJOR < 13: + return False + ELSE: + return bool( + self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_HOST_MEMORY_POOLS_SUPPORTED) + ) @property def host_virtual_memory_management_supported(self) -> bool: """bool: Device supports HOST location with the virtual memory management APIs like cuMemCreate, cuMemMap and related APIs.""" - return bool( - self._get_cached_attribute( - driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_HOST_VIRTUAL_MEMORY_MANAGEMENT_SUPPORTED + IF CUDA_CORE_BUILD_MAJOR < 13: + return False + ELSE: + return bool( + self._get_cached_attribute( + driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_HOST_VIRTUAL_MEMORY_MANAGEMENT_SUPPORTED + ) ) - ) @property def host_alloc_dma_buf_supported(self) -> bool: """bool: Device supports page-locked host memory buffer sharing with dma_buf mechanism.""" - return bool( - self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_HOST_ALLOC_DMA_BUF_SUPPORTED) - ) + IF CUDA_CORE_BUILD_MAJOR < 13: + return False + ELSE: + return bool( + self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_HOST_ALLOC_DMA_BUF_SUPPORTED) + ) @property def only_partial_host_native_atomic_supported(self) -> bool: """bool: Link between the device and the host supports only some native atomic operations.""" - return bool( - self._get_cached_attribute( - driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_ONLY_PARTIAL_HOST_NATIVE_ATOMIC_SUPPORTED + IF CUDA_CORE_BUILD_MAJOR < 13: + return False + ELSE: + return bool( + self._get_cached_attribute( + driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_ONLY_PARTIAL_HOST_NATIVE_ATOMIC_SUPPORTED + ) ) - ) class Device: diff --git a/cuda_core/tests/test_device.py b/cuda_core/tests/test_device.py index 4cbd28398f3..0d2e5e00952 100644 --- a/cuda_core/tests/test_device.py +++ b/cuda_core/tests/test_device.py @@ -9,7 +9,7 @@ from cuda.bindings import driver, runtime from cuda.core import Device from cuda.core._utils.cuda_utils import ComputeCapability, handle_return -from cuda.core._utils.version import binding_version, driver_version +from cuda.core._utils.version import driver_version def test_device_init_disabled(): @@ -299,9 +299,7 @@ def test_arch(): ("only_partial_host_native_atomic_supported", bool), ] -version = binding_version() -if version >= (13, 0, 0): - cuda_base_properties += cuda_13_properties +cuda_base_properties += cuda_13_properties @pytest.mark.parametrize("property_name, expected_type", cuda_base_properties) @@ -315,16 +313,8 @@ def test_device_properties_complete(): live_props = {attr for attr in dir(device.properties) if not attr.startswith("_")} tab_props = {attr for attr, _ in cuda_base_properties} - excluded_props = set() - # Exclude CUDA 13+ specific properties when not available - if version < (13, 0, 0): - excluded_props.update({prop[0] for prop in cuda_13_properties}) - - filtered_tab_props = tab_props - excluded_props - filtered_live_props = live_props - excluded_props - - assert len(filtered_tab_props) == len(cuda_base_properties) # Ensure no duplicates. - assert filtered_tab_props == filtered_live_props # Ensure exact match. + assert len(tab_props) == len(cuda_base_properties) # Ensure no duplicates. + assert tab_props == live_props # Ensure exact match. # ============================================================================ diff --git a/cuda_core/tests/test_memory.py b/cuda_core/tests/test_memory.py index 5c98ec668ae..a4ea2ee01c4 100644 --- a/cuda_core/tests/test_memory.py +++ b/cuda_core/tests/test_memory.py @@ -758,7 +758,12 @@ def test_pinned_memory_resource_rejects_unsupported_host_pool(init_cuda): if device.properties.host_memory_pools_supported: pytest.skip("Device supports host memory pools") - mr = PinnedMemoryResource(PinnedMemoryResourceOptions(max_size=POOL_SIZE)) + try: + mr = PinnedMemoryResource(PinnedMemoryResourceOptions(max_size=POOL_SIZE)) + except CUDAError as exc: + if "CUDA_ERROR_NOT_SUPPORTED" in str(exc): + pytest.skip("PinnedMemoryResource is not supported on this platform/device") + raise try: with pytest.raises(RuntimeError, match="does not support.*LegacyPinnedMemoryResource"): mr.allocate(1024, stream=device.default_stream)