Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 26 additions & 14 deletions cuda_core/cuda/core/_device.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
14 changes: 14 additions & 0 deletions cuda_core/cuda/core/_memory/_pinned_memory_resource.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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, ...]:
...

Expand Down
37 changes: 36 additions & 1 deletion cuda_core/cuda/core/_memory/_pinned_memory_resource.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -20,6 +22,11 @@ import uuid

from cuda.core._utils.cuda_utils import check_multiprocessing_start_method

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from cuda.core.graph import GraphBuilder

__all__ = ['PinnedMemoryResource', 'PinnedMemoryResourceOptions']


Expand Down Expand Up @@ -78,6 +85,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
Expand All @@ -91,6 +106,26 @@ 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 = (
device.properties.host_numa_memory_pools_supported
if self._numa_id >= 0
else device.properties.host_memory_pools_supported
)

if not supported:
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)

def __reduce__(self) -> tuple[object, ...]:
return PinnedMemoryResource.from_registry, (self.uuid,)

Expand Down
18 changes: 4 additions & 14 deletions cuda_core/tests/test_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down Expand Up @@ -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)
Expand All @@ -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.


# ============================================================================
Expand Down
20 changes: 20 additions & 0 deletions cuda_core/tests/test_memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,26 @@ def test_pinned_memory_resource_initialization(init_cuda):
buffer.close()


@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
if device.properties.host_memory_pools_supported:
pytest.skip("Device supports host memory pools")

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)
finally:
mr.close()


def test_managed_memory_resource_initialization(init_cuda):
device = Device()
skip_if_managed_memory_unsupported(device)
Expand Down
Loading