From 2801e9888e5c38577173f3affc8f4dc445150a35 Mon Sep 17 00:00:00 2001 From: Keith Kraus Date: Fri, 8 May 2026 12:15:24 -0400 Subject: [PATCH 01/10] Use Python properties for Cython descriptors --- cuda_core/cuda/core/_context.pyx | 5 +- cuda_core/cuda/core/_device_resources.pyx | 5 +- cuda_core/cuda/core/_event.pyx | 5 +- cuda_core/cuda/core/_memory/_buffer.pyx | 5 +- .../core/_memory/_device_memory_resource.pyx | 5 +- .../core/_memory/_pinned_memory_resource.pyx | 5 +- cuda_core/cuda/core/graph/_graph_builder.pyx | 5 +- cuda_core/cuda/core/system/_device.pyx | 30 ++++++---- cuda_core/pixi.toml | 3 - .../tests/test_cython_property_descriptors.py | 59 +++++++++++++++++++ 10 files changed, 98 insertions(+), 29 deletions(-) create mode 100644 cuda_core/tests/test_cython_property_descriptors.py diff --git a/cuda_core/cuda/core/_context.pyx b/cuda_core/cuda/core/_context.pyx index 48dc2c09778..3c256990801 100644 --- a/cuda_core/cuda/core/_context.pyx +++ b/cuda_core/cuda/core/_context.pyx @@ -77,8 +77,7 @@ cdef class Context: return False return get_context_green_ctx(self._h_context).get() != NULL - @property - def resources(self) -> DeviceResources: + def _get_resources(self) -> DeviceResources: """Query the hardware resources provisioned for this context. For green contexts, returns the resources this context was created @@ -91,6 +90,8 @@ cdef class Context: raise RuntimeError("Cannot query resources on a closed context") return DeviceResources._init_from_ctx(self._h_context, self._device_id) + resources = property(_get_resources) + def create_stream(self, options: StreamOptions | None = None): """Create a new stream bound to this green context. diff --git a/cuda_core/cuda/core/_device_resources.pyx b/cuda_core/cuda/core/_device_resources.pyx index 40c0a874d05..7db9c358b86 100644 --- a/cuda_core/cuda/core/_device_resources.pyx +++ b/cuda_core/cuda/core/_device_resources.pyx @@ -622,8 +622,7 @@ cdef class DeviceResources: self._query_sm(&res) return SMResource._from_dev_resource(res, self._device_id) - @property - def workqueue(self) -> WorkqueueResource: + def _get_workqueue(self) -> WorkqueueResource: """Return the :obj:`WorkqueueResource` for this device or context.""" _check_green_ctx_support() _check_workqueue_support() @@ -678,3 +677,5 @@ cdef class DeviceResources: raise RuntimeError( "WorkqueueResource requires cuda.core to be built with CUDA 13.x bindings" ) + + workqueue = property(_get_workqueue) diff --git a/cuda_core/cuda/core/_event.pyx b/cuda_core/cuda/core/_event.pyx index 3f5fb7ace26..7c28cd4cd59 100644 --- a/cuda_core/cuda/core/_event.pyx +++ b/cuda_core/cuda/core/_event.pyx @@ -197,8 +197,7 @@ cdef class Event: def __repr__(self) -> str: return f"" - @property - def ipc_descriptor(self) -> IPCEventDescriptor: + def _get_ipc_descriptor(self) -> IPCEventDescriptor: """Descriptor for sharing this event with other processes.""" if self._ipc_descriptor is not None: return self._ipc_descriptor @@ -211,6 +210,8 @@ cdef class Event: self._ipc_descriptor = IPCEventDescriptor._init(data_b, get_event_is_blocking_sync(self._h_event)) return self._ipc_descriptor + ipc_descriptor = property(_get_ipc_descriptor) + @classmethod def from_ipc_descriptor(cls, ipc_descriptor: IPCEventDescriptor) -> Event: """Import an event that was exported from another process. diff --git a/cuda_core/cuda/core/_memory/_buffer.pyx b/cuda_core/cuda/core/_memory/_buffer.pyx index 5d3bdbb873c..c81920496a7 100644 --- a/cuda_core/cuda/core/_memory/_buffer.pyx +++ b/cuda_core/cuda/core/_memory/_buffer.pyx @@ -190,13 +190,14 @@ cdef class Buffer: """ return _ipc.Buffer_from_ipc_descriptor(cls, mr, ipc_descriptor, stream) - @property - def ipc_descriptor(self) -> IPCBufferDescriptor: + def _get_ipc_descriptor(self) -> IPCBufferDescriptor: """Descriptor for sharing this buffer with other processes.""" if self._ipc_data is None: self._ipc_data = IPCDataForBuffer(_ipc.Buffer_get_ipc_descriptor(self), False) return self._ipc_data.ipc_descriptor + ipc_descriptor = property(_get_ipc_descriptor) + def close(self, stream: Stream | GraphBuilder | None = None): """Deallocate this buffer asynchronously on the given stream. diff --git a/cuda_core/cuda/core/_memory/_device_memory_resource.pyx b/cuda_core/cuda/core/_memory/_device_memory_resource.pyx index b7b8b247a92..a2d1a0fa0d9 100644 --- a/cuda_core/cuda/core/_memory/_device_memory_resource.pyx +++ b/cuda_core/cuda/core/_memory/_device_memory_resource.pyx @@ -190,8 +190,7 @@ cdef class DeviceMemoryResource(_MemPool): mr._dev_id = Device(device_id).device_id return mr - @property - def allocation_handle(self) -> IPCAllocationHandle: + def _get_allocation_handle(self) -> IPCAllocationHandle: """Shareable handle for this memory pool (requires IPC). The handle can be used to share the memory pool with other processes. @@ -201,6 +200,8 @@ cdef class DeviceMemoryResource(_MemPool): raise RuntimeError("Memory resource is not IPC-enabled") return self._ipc_data._alloc_handle + allocation_handle = property(_get_allocation_handle) + @property def device_id(self) -> int: """The associated device ordinal.""" diff --git a/cuda_core/cuda/core/_memory/_pinned_memory_resource.pyx b/cuda_core/cuda/core/_memory/_pinned_memory_resource.pyx index 0b18a1f7e3d..a8c84d3725f 100644 --- a/cuda_core/cuda/core/_memory/_pinned_memory_resource.pyx +++ b/cuda_core/cuda/core/_memory/_pinned_memory_resource.pyx @@ -148,8 +148,7 @@ cdef class PinnedMemoryResource(_MemPool): _ipc.MP_from_allocation_handle(cls, alloc_handle)) return mr - @property - def allocation_handle(self) -> IPCAllocationHandle: + def _get_allocation_handle(self) -> IPCAllocationHandle: """Shareable handle for this memory pool (requires IPC). The handle can be used to share the memory pool with other processes. @@ -159,6 +158,8 @@ cdef class PinnedMemoryResource(_MemPool): raise RuntimeError("Memory resource is not IPC-enabled") return self._ipc_data._alloc_handle + allocation_handle = property(_get_allocation_handle) + @property def device_id(self) -> int: """Return -1. Pinned memory is host memory and is not associated with a specific device.""" diff --git a/cuda_core/cuda/core/graph/_graph_builder.pyx b/cuda_core/cuda/core/graph/_graph_builder.pyx index b745598abab..f934135f776 100644 --- a/cuda_core/cuda/core/graph/_graph_builder.pyx +++ b/cuda_core/cuda/core/graph/_graph_builder.pyx @@ -300,8 +300,7 @@ class GraphBuilder: handle_return(driver.cuStreamBeginCapture(self._mnff.stream.handle, capture_mode)) return self - @property - def is_building(self) -> bool: + def _get_is_building(self) -> bool: """Returns True if the graph builder is currently building.""" capture_status = handle_return(driver.cuStreamGetCaptureInfo(self._mnff.stream.handle))[0] if capture_status == driver.CUstreamCaptureStatus.CU_STREAM_CAPTURE_STATUS_NONE: @@ -315,6 +314,8 @@ class GraphBuilder: else: raise NotImplementedError(f"Unsupported capture status type received: {capture_status}") + is_building = property(_get_is_building) + def end_building(self) -> GraphBuilder: """Ends the building process.""" if not self.is_building: diff --git a/cuda_core/cuda/core/system/_device.pyx b/cuda_core/cuda/core/system/_device.pyx index 9c8224e54aa..f69673dfddf 100644 --- a/cuda_core/cuda/core/system/_device.pyx +++ b/cuda_core/cuda/core/system/_device.pyx @@ -259,8 +259,7 @@ cdef class Device: """ return self.pci_info.bus_id - @property - def numa_node_id(self) -> int: + def _get_numa_node_id(self) -> int: """ The NUMA node of the given GPU device. @@ -268,6 +267,8 @@ cdef class Device: """ return nvml.device_get_numa_node_id(self._handle) + numa_node_id = property(_get_numa_node_id) + @property def arch(self) -> DeviceArch: """ @@ -332,13 +333,14 @@ cdef class Device: """ return nvml.device_get_minor_number(self._handle) - @property - def is_c2c_enabled(self) -> bool: + def _get_is_c2c_enabled(self) -> bool: """ Whether the C2C (Chip-to-Chip) mode is enabled for this device. """ return bool(nvml.device_get_c2c_mode_info_v(self._handle).is_c2c_enabled) + is_c2c_enabled = property(_get_is_c2c_enabled) + @property def is_persistence_mode_enabled(self) -> bool: """ @@ -575,8 +577,7 @@ cdef class Device: """ return ClockInfo(self._handle, clock_type) - @property - def is_auto_boosted_clocks_enabled(self) -> tuple[bool, bool]: + def _get_is_auto_boosted_clocks_enabled(self) -> tuple[bool, bool]: """ Retrieve the current state of auto boosted clocks on a device. @@ -601,8 +602,9 @@ cdef class Device: current, default = nvml.device_get_auto_boosted_clocks_enabled(self._handle) return current == nvml.EnableState.FEATURE_ENABLED, default == nvml.EnableState.FEATURE_ENABLED - @property - def current_clock_event_reasons(self) -> list[ClocksEventReasons]: + is_auto_boosted_clocks_enabled = property(_get_is_auto_boosted_clocks_enabled) + + def _get_current_clock_event_reasons(self) -> list[ClocksEventReasons]: """ Retrieves the current :obj:`~ClocksEventReasons`. @@ -619,8 +621,9 @@ cdef class Device: output_reasons.append(output_reason) return output_reasons - @property - def supported_clock_event_reasons(self) -> list[ClocksEventReasons]: + current_clock_event_reasons = property(_get_current_clock_event_reasons) + + def _get_supported_clock_event_reasons(self) -> list[ClocksEventReasons]: """ Retrieves supported :obj:`~ClocksEventReasons` that can be returned by :meth:`get_current_clock_event_reasons`. @@ -640,6 +643,8 @@ cdef class Device: output_reasons.append(output_reason) return output_reasons + supported_clock_event_reasons = property(_get_supported_clock_event_reasons) + ########################################################################## # COOLER # See external class definitions in _cooler.pxi @@ -655,8 +660,7 @@ cdef class Device: # DEVICE ATTRIBUTES # See external class definitions in _device_attributes.pxi - @property - def attributes(self) -> DeviceAttributes: + def _get_attributes(self) -> DeviceAttributes: """ :obj:`~_device.DeviceAttributes` object with various device attributes. @@ -665,6 +669,8 @@ cdef class Device: """ return DeviceAttributes(nvml.device_get_attributes_v2(self._handle)) + attributes = property(_get_attributes) + ######################################################################### # DISPLAY diff --git a/cuda_core/pixi.toml b/cuda_core/pixi.toml index df5111497fa..1008fe9711f 100644 --- a/cuda_core/pixi.toml +++ b/cuda_core/pixi.toml @@ -186,9 +186,6 @@ numpy = "*" cuda-bindings = "*" cuda-pathfinder = "*" -[package.target.'python_version < "3.11"'.run-dependencies] -"backports.strenum" = "*" - [target.linux.tasks.build-cython-tests] cmd = ["$PIXI_PROJECT_ROOT/tests/cython/build_tests.sh"] diff --git a/cuda_core/tests/test_cython_property_descriptors.py b/cuda_core/tests/test_cython_property_descriptors.py new file mode 100644 index 00000000000..3d6fa793549 --- /dev/null +++ b/cuda_core/tests/test_cython_property_descriptors.py @@ -0,0 +1,59 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +import inspect +import types + +import pytest + +from cuda.core import ( + Buffer, + Context, + DeviceMemoryResource, + DeviceResources, + Event, + PinnedMemoryResource, +) +from cuda.core.graph import GraphBuilder +from cuda.core.system import CUDA_BINDINGS_NVML_IS_COMPATIBLE + +_PYTHON_PROPERTIES = [ + pytest.param(DeviceMemoryResource, "allocation_handle", id="DeviceMemoryResource.allocation_handle"), + pytest.param(PinnedMemoryResource, "allocation_handle", id="PinnedMemoryResource.allocation_handle"), + pytest.param(Event, "ipc_descriptor", id="Event.ipc_descriptor"), + pytest.param(Buffer, "ipc_descriptor", id="Buffer.ipc_descriptor"), + pytest.param(Context, "resources", id="Context.resources"), + pytest.param(DeviceResources, "workqueue", id="DeviceResources.workqueue"), + pytest.param(GraphBuilder, "is_building", id="GraphBuilder.is_building"), +] + +if CUDA_BINDINGS_NVML_IS_COMPATIBLE: + from cuda.core.system import Device as SystemDevice + + _PYTHON_PROPERTIES.extend( + [ + pytest.param(SystemDevice, "attributes", id="system.Device.attributes"), + pytest.param( + SystemDevice, + "is_auto_boosted_clocks_enabled", + id="system.Device.is_auto_boosted_clocks_enabled", + ), + pytest.param(SystemDevice, "is_c2c_enabled", id="system.Device.is_c2c_enabled"), + pytest.param(SystemDevice, "numa_node_id", id="system.Device.numa_node_id"), + pytest.param(SystemDevice, "current_clock_event_reasons", id="system.Device.current_clock_event_reasons"), + pytest.param( + SystemDevice, + "supported_clock_event_reasons", + id="system.Device.supported_clock_event_reasons", + ), + ] + ) + + +@pytest.mark.parametrize("cls, name", _PYTHON_PROPERTIES) +def test_known_public_cython_properties_are_python_properties(cls, name): + descriptor = inspect.getattr_static(cls, name) + + assert isinstance(descriptor, property) + assert not isinstance(descriptor, types.GetSetDescriptorType) From ff862ef61d47c983671967b687619dbd425fc0bf Mon Sep 17 00:00:00 2001 From: Keith Kraus Date: Fri, 8 May 2026 12:20:58 -0400 Subject: [PATCH 02/10] Keep descriptor tests scoped to cdef classes --- cuda_core/cuda/core/graph/_graph_builder.pyx | 5 ++--- cuda_core/tests/test_cython_property_descriptors.py | 2 -- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/cuda_core/cuda/core/graph/_graph_builder.pyx b/cuda_core/cuda/core/graph/_graph_builder.pyx index f934135f776..b745598abab 100644 --- a/cuda_core/cuda/core/graph/_graph_builder.pyx +++ b/cuda_core/cuda/core/graph/_graph_builder.pyx @@ -300,7 +300,8 @@ class GraphBuilder: handle_return(driver.cuStreamBeginCapture(self._mnff.stream.handle, capture_mode)) return self - def _get_is_building(self) -> bool: + @property + def is_building(self) -> bool: """Returns True if the graph builder is currently building.""" capture_status = handle_return(driver.cuStreamGetCaptureInfo(self._mnff.stream.handle))[0] if capture_status == driver.CUstreamCaptureStatus.CU_STREAM_CAPTURE_STATUS_NONE: @@ -314,8 +315,6 @@ class GraphBuilder: else: raise NotImplementedError(f"Unsupported capture status type received: {capture_status}") - is_building = property(_get_is_building) - def end_building(self) -> GraphBuilder: """Ends the building process.""" if not self.is_building: diff --git a/cuda_core/tests/test_cython_property_descriptors.py b/cuda_core/tests/test_cython_property_descriptors.py index 3d6fa793549..8277d3587fa 100644 --- a/cuda_core/tests/test_cython_property_descriptors.py +++ b/cuda_core/tests/test_cython_property_descriptors.py @@ -15,7 +15,6 @@ Event, PinnedMemoryResource, ) -from cuda.core.graph import GraphBuilder from cuda.core.system import CUDA_BINDINGS_NVML_IS_COMPATIBLE _PYTHON_PROPERTIES = [ @@ -25,7 +24,6 @@ pytest.param(Buffer, "ipc_descriptor", id="Buffer.ipc_descriptor"), pytest.param(Context, "resources", id="Context.resources"), pytest.param(DeviceResources, "workqueue", id="DeviceResources.workqueue"), - pytest.param(GraphBuilder, "is_building", id="GraphBuilder.is_building"), ] if CUDA_BINDINGS_NVML_IS_COMPATIBLE: From bd7251a14965cfb573dc627a40d8050c740e8308 Mon Sep 17 00:00:00 2001 From: Keith Kraus Date: Fri, 8 May 2026 12:28:40 -0400 Subject: [PATCH 03/10] Use decorator for Cython Python properties --- cuda_core/cuda/core/_context.pyx | 6 ++-- cuda_core/cuda/core/_device_resources.pyx | 6 ++-- cuda_core/cuda/core/_event.pyx | 6 ++-- cuda_core/cuda/core/_memory/_buffer.pyx | 6 ++-- .../core/_memory/_device_memory_resource.pyx | 6 ++-- .../core/_memory/_pinned_memory_resource.pyx | 6 ++-- cuda_core/cuda/core/_utils/properties.py | 8 +++++ cuda_core/cuda/core/system/_device.pyx | 31 ++++++++----------- cuda_core/pixi.toml | 3 ++ 9 files changed, 42 insertions(+), 36 deletions(-) create mode 100644 cuda_core/cuda/core/_utils/properties.py diff --git a/cuda_core/cuda/core/_context.pyx b/cuda_core/cuda/core/_context.pyx index 3c256990801..2c43a208cbc 100644 --- a/cuda_core/cuda/core/_context.pyx +++ b/cuda_core/cuda/core/_context.pyx @@ -22,6 +22,7 @@ from cuda.core._resource_handles cimport ( ) from cuda.core._stream import Stream, StreamOptions from cuda.core._utils.cuda_utils cimport HANDLE_RETURN +from cuda.core._utils.properties import python_property __all__ = ['Context', 'ContextOptions'] @@ -77,7 +78,8 @@ cdef class Context: return False return get_context_green_ctx(self._h_context).get() != NULL - def _get_resources(self) -> DeviceResources: + @python_property + def resources(self) -> DeviceResources: """Query the hardware resources provisioned for this context. For green contexts, returns the resources this context was created @@ -90,8 +92,6 @@ cdef class Context: raise RuntimeError("Cannot query resources on a closed context") return DeviceResources._init_from_ctx(self._h_context, self._device_id) - resources = property(_get_resources) - def create_stream(self, options: StreamOptions | None = None): """Create a new stream bound to this green context. diff --git a/cuda_core/cuda/core/_device_resources.pyx b/cuda_core/cuda/core/_device_resources.pyx index 7db9c358b86..28a478028d1 100644 --- a/cuda_core/cuda/core/_device_resources.pyx +++ b/cuda_core/cuda/core/_device_resources.pyx @@ -15,6 +15,7 @@ from cuda.bindings cimport cydriver from cuda.core._resource_handles cimport ContextHandle, GreenCtxHandle, as_cu, get_context_green_ctx from cuda.core._utils.cuda_utils cimport check_or_create_options, HANDLE_RETURN from cuda.core._utils.cuda_utils import is_sequence +from cuda.core._utils.properties import python_property from cuda.core._utils.version cimport cy_binding_version, cy_driver_version @@ -622,7 +623,8 @@ cdef class DeviceResources: self._query_sm(&res) return SMResource._from_dev_resource(res, self._device_id) - def _get_workqueue(self) -> WorkqueueResource: + @python_property + def workqueue(self) -> WorkqueueResource: """Return the :obj:`WorkqueueResource` for this device or context.""" _check_green_ctx_support() _check_workqueue_support() @@ -677,5 +679,3 @@ cdef class DeviceResources: raise RuntimeError( "WorkqueueResource requires cuda.core to be built with CUDA 13.x bindings" ) - - workqueue = property(_get_workqueue) diff --git a/cuda_core/cuda/core/_event.pyx b/cuda_core/cuda/core/_event.pyx index 7c28cd4cd59..fa08e0ac4e0 100644 --- a/cuda_core/cuda/core/_event.pyx +++ b/cuda_core/cuda/core/_event.pyx @@ -36,6 +36,7 @@ from cuda.core._utils.cuda_utils import ( CUDAError, check_multiprocessing_start_method, ) +from cuda.core._utils.properties import python_property @dataclass @@ -197,7 +198,8 @@ cdef class Event: def __repr__(self) -> str: return f"" - def _get_ipc_descriptor(self) -> IPCEventDescriptor: + @python_property + def ipc_descriptor(self) -> IPCEventDescriptor: """Descriptor for sharing this event with other processes.""" if self._ipc_descriptor is not None: return self._ipc_descriptor @@ -210,8 +212,6 @@ cdef class Event: self._ipc_descriptor = IPCEventDescriptor._init(data_b, get_event_is_blocking_sync(self._h_event)) return self._ipc_descriptor - ipc_descriptor = property(_get_ipc_descriptor) - @classmethod def from_ipc_descriptor(cls, ipc_descriptor: IPCEventDescriptor) -> Event: """Import an event that was exported from another process. diff --git a/cuda_core/cuda/core/_memory/_buffer.pyx b/cuda_core/cuda/core/_memory/_buffer.pyx index c81920496a7..55c6d8944da 100644 --- a/cuda_core/cuda/core/_memory/_buffer.pyx +++ b/cuda_core/cuda/core/_memory/_buffer.pyx @@ -37,6 +37,7 @@ else: from cuda.core._dlpack import classify_dl_device, make_py_capsule from cuda.core._device import Device +from cuda.core._utils.properties import python_property # ============================================================================= @@ -190,14 +191,13 @@ cdef class Buffer: """ return _ipc.Buffer_from_ipc_descriptor(cls, mr, ipc_descriptor, stream) - def _get_ipc_descriptor(self) -> IPCBufferDescriptor: + @python_property + def ipc_descriptor(self) -> IPCBufferDescriptor: """Descriptor for sharing this buffer with other processes.""" if self._ipc_data is None: self._ipc_data = IPCDataForBuffer(_ipc.Buffer_get_ipc_descriptor(self), False) return self._ipc_data.ipc_descriptor - ipc_descriptor = property(_get_ipc_descriptor) - def close(self, stream: Stream | GraphBuilder | None = None): """Deallocate this buffer asynchronously on the given stream. diff --git a/cuda_core/cuda/core/_memory/_device_memory_resource.pyx b/cuda_core/cuda/core/_memory/_device_memory_resource.pyx index a2d1a0fa0d9..8a196b98522 100644 --- a/cuda_core/cuda/core/_memory/_device_memory_resource.pyx +++ b/cuda_core/cuda/core/_memory/_device_memory_resource.pyx @@ -25,6 +25,7 @@ import uuid from cuda.core._memory._peer_access_utils import PeerAccessibleBySetProxy, replace_peer_accessible_by from cuda.core._utils.cuda_utils import check_multiprocessing_start_method +from cuda.core._utils.properties import python_property __all__ = ['DeviceMemoryResource', 'DeviceMemoryResourceOptions'] @@ -190,7 +191,8 @@ cdef class DeviceMemoryResource(_MemPool): mr._dev_id = Device(device_id).device_id return mr - def _get_allocation_handle(self) -> IPCAllocationHandle: + @python_property + def allocation_handle(self) -> IPCAllocationHandle: """Shareable handle for this memory pool (requires IPC). The handle can be used to share the memory pool with other processes. @@ -200,8 +202,6 @@ cdef class DeviceMemoryResource(_MemPool): raise RuntimeError("Memory resource is not IPC-enabled") return self._ipc_data._alloc_handle - allocation_handle = property(_get_allocation_handle) - @property def device_id(self) -> int: """The associated device ordinal.""" diff --git a/cuda_core/cuda/core/_memory/_pinned_memory_resource.pyx b/cuda_core/cuda/core/_memory/_pinned_memory_resource.pyx index a8c84d3725f..a37a492d166 100644 --- a/cuda_core/cuda/core/_memory/_pinned_memory_resource.pyx +++ b/cuda_core/cuda/core/_memory/_pinned_memory_resource.pyx @@ -19,6 +19,7 @@ import platform # no-cython-lint import uuid from cuda.core._utils.cuda_utils import check_multiprocessing_start_method +from cuda.core._utils.properties import python_property __all__ = ['PinnedMemoryResource', 'PinnedMemoryResourceOptions'] @@ -148,7 +149,8 @@ cdef class PinnedMemoryResource(_MemPool): _ipc.MP_from_allocation_handle(cls, alloc_handle)) return mr - def _get_allocation_handle(self) -> IPCAllocationHandle: + @python_property + def allocation_handle(self) -> IPCAllocationHandle: """Shareable handle for this memory pool (requires IPC). The handle can be used to share the memory pool with other processes. @@ -158,8 +160,6 @@ cdef class PinnedMemoryResource(_MemPool): raise RuntimeError("Memory resource is not IPC-enabled") return self._ipc_data._alloc_handle - allocation_handle = property(_get_allocation_handle) - @property def device_id(self) -> int: """Return -1. Pinned memory is host memory and is not associated with a specific device.""" diff --git a/cuda_core/cuda/core/_utils/properties.py b/cuda_core/cuda/core/_utils/properties.py new file mode 100644 index 00000000000..53f35d1f1ba --- /dev/null +++ b/cuda_core/cuda/core/_utils/properties.py @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 + + +def python_property(func): + """Create a Python property without Cython's cdef-class @property lowering.""" + return property(func) diff --git a/cuda_core/cuda/core/system/_device.pyx b/cuda_core/cuda/core/system/_device.pyx index f69673dfddf..dc6661d990e 100644 --- a/cuda_core/cuda/core/system/_device.pyx +++ b/cuda_core/cuda/core/system/_device.pyx @@ -11,6 +11,7 @@ import warnings from cuda.bindings import nvml +from cuda.core._utils.properties import python_property from ._nvml_context cimport initialize from cuda.core.system.typing import ( AddressingMode, @@ -259,7 +260,8 @@ cdef class Device: """ return self.pci_info.bus_id - def _get_numa_node_id(self) -> int: + @python_property + def numa_node_id(self) -> int: """ The NUMA node of the given GPU device. @@ -267,8 +269,6 @@ cdef class Device: """ return nvml.device_get_numa_node_id(self._handle) - numa_node_id = property(_get_numa_node_id) - @property def arch(self) -> DeviceArch: """ @@ -333,14 +333,13 @@ cdef class Device: """ return nvml.device_get_minor_number(self._handle) - def _get_is_c2c_enabled(self) -> bool: + @python_property + def is_c2c_enabled(self) -> bool: """ Whether the C2C (Chip-to-Chip) mode is enabled for this device. """ return bool(nvml.device_get_c2c_mode_info_v(self._handle).is_c2c_enabled) - is_c2c_enabled = property(_get_is_c2c_enabled) - @property def is_persistence_mode_enabled(self) -> bool: """ @@ -577,7 +576,8 @@ cdef class Device: """ return ClockInfo(self._handle, clock_type) - def _get_is_auto_boosted_clocks_enabled(self) -> tuple[bool, bool]: + @python_property + def is_auto_boosted_clocks_enabled(self) -> tuple[bool, bool]: """ Retrieve the current state of auto boosted clocks on a device. @@ -602,9 +602,8 @@ cdef class Device: current, default = nvml.device_get_auto_boosted_clocks_enabled(self._handle) return current == nvml.EnableState.FEATURE_ENABLED, default == nvml.EnableState.FEATURE_ENABLED - is_auto_boosted_clocks_enabled = property(_get_is_auto_boosted_clocks_enabled) - - def _get_current_clock_event_reasons(self) -> list[ClocksEventReasons]: + @python_property + def current_clock_event_reasons(self) -> list[ClocksEventReasons]: """ Retrieves the current :obj:`~ClocksEventReasons`. @@ -621,9 +620,8 @@ cdef class Device: output_reasons.append(output_reason) return output_reasons - current_clock_event_reasons = property(_get_current_clock_event_reasons) - - def _get_supported_clock_event_reasons(self) -> list[ClocksEventReasons]: + @python_property + def supported_clock_event_reasons(self) -> list[ClocksEventReasons]: """ Retrieves supported :obj:`~ClocksEventReasons` that can be returned by :meth:`get_current_clock_event_reasons`. @@ -643,8 +641,6 @@ cdef class Device: output_reasons.append(output_reason) return output_reasons - supported_clock_event_reasons = property(_get_supported_clock_event_reasons) - ########################################################################## # COOLER # See external class definitions in _cooler.pxi @@ -660,7 +656,8 @@ cdef class Device: # DEVICE ATTRIBUTES # See external class definitions in _device_attributes.pxi - def _get_attributes(self) -> DeviceAttributes: + @python_property + def attributes(self) -> DeviceAttributes: """ :obj:`~_device.DeviceAttributes` object with various device attributes. @@ -669,8 +666,6 @@ cdef class Device: """ return DeviceAttributes(nvml.device_get_attributes_v2(self._handle)) - attributes = property(_get_attributes) - ######################################################################### # DISPLAY diff --git a/cuda_core/pixi.toml b/cuda_core/pixi.toml index 1008fe9711f..df5111497fa 100644 --- a/cuda_core/pixi.toml +++ b/cuda_core/pixi.toml @@ -186,6 +186,9 @@ numpy = "*" cuda-bindings = "*" cuda-pathfinder = "*" +[package.target.'python_version < "3.11"'.run-dependencies] +"backports.strenum" = "*" + [target.linux.tasks.build-cython-tests] cmd = ["$PIXI_PROJECT_ROOT/tests/cython/build_tests.sh"] From 20e6f0676435874662d3c82308d424b2b36b2b08 Mon Sep 17 00:00:00 2001 From: Keith Kraus Date: Fri, 8 May 2026 13:11:32 -0400 Subject: [PATCH 04/10] Convert cuda-core cdef properties to Python properties --- cuda_core/cuda/core/_context.pyx | 6 +- cuda_core/cuda/core/_device.pyx | 287 +++++++++--------- cuda_core/cuda/core/_device_resources.pyx | 14 +- cuda_core/cuda/core/_event.pyx | 14 +- cuda_core/cuda/core/_graphics.pyx | 7 +- cuda_core/cuda/core/_layout.pyx | 31 +- cuda_core/cuda/core/_linker.pyx | 3 +- cuda_core/cuda/core/_memory/_buffer.pyx | 26 +- .../core/_memory/_device_memory_resource.pyx | 12 +- .../core/_memory/_graph_memory_resource.pyx | 25 +- cuda_core/cuda/core/_memory/_ipc.pyx | 17 +- .../core/_memory/_managed_memory_resource.pyx | 11 +- cuda_core/cuda/core/_memory/_memory_pool.pyx | 29 +- .../core/_memory/_pinned_memory_resource.pyx | 8 +- cuda_core/cuda/core/_memoryview.pyx | 11 +- cuda_core/cuda/core/_module.pyx | 55 ++-- cuda_core/cuda/core/_program.pyx | 7 +- cuda_core/cuda/core/_stream.pyx | 13 +- cuda_core/cuda/core/_tensor_map.pyx | 3 +- cuda_core/cuda/core/_utils/properties.py | 4 +- .../cuda/core/graph/_graph_definition.pyx | 7 +- cuda_core/cuda/core/graph/_graph_node.pyx | 21 +- cuda_core/cuda/core/graph/_subclasses.pyx | 63 ++-- cuda_core/cuda/core/system/_clock.pxi | 6 +- cuda_core/cuda/core/system/_cooler.pxi | 4 +- cuda_core/cuda/core/system/_device.pyx | 64 ++-- .../cuda/core/system/_device_attributes.pxi | 18 +- cuda_core/cuda/core/system/_event.pxi | 10 +- cuda_core/cuda/core/system/_fan.pxi | 14 +- cuda_core/cuda/core/system/_field_values.pxi | 10 +- cuda_core/cuda/core/system/_inforom.pxi | 8 +- cuda_core/cuda/core/system/_memory.pxi | 14 +- cuda_core/cuda/core/system/_mig.pxi | 14 +- cuda_core/cuda/core/system/_nvlink.pxi | 4 +- cuda_core/cuda/core/system/_pci_info.pxi | 34 +-- cuda_core/cuda/core/system/_performance.pxi | 10 +- cuda_core/cuda/core/system/_repair_status.pxi | 4 +- cuda_core/cuda/core/system/_system_events.pyx | 7 +- cuda_core/cuda/core/system/_temperature.pxi | 12 +- cuda_core/cuda/core/system/_utilization.pxi | 4 +- cuda_core/pytest.ini | 4 +- cuda_core/tests/conftest.py | 10 +- .../tests/test_cython_property_descriptors.py | 166 +++++++--- pytest.ini | 3 +- 44 files changed, 599 insertions(+), 495 deletions(-) diff --git a/cuda_core/cuda/core/_context.pyx b/cuda_core/cuda/core/_context.pyx index 2c43a208cbc..28267984084 100644 --- a/cuda_core/cuda/core/_context.pyx +++ b/cuda_core/cuda/core/_context.pyx @@ -58,7 +58,7 @@ cdef class Context: raise RuntimeError("Failed to create CUDA context view from green context") return Context._from_handle(cls, h_context, device_id) - @property + @python_property def handle(self): """Return the underlying CUcontext handle.""" if not self._h_context: @@ -67,11 +67,11 @@ cdef class Context: return None return as_py(self._h_context) - @property + @python_property def _handle(self): return self.handle - @property + @python_property def is_green(self) -> bool: """True if this context was created from device resources.""" if not self._h_context: diff --git a/cuda_core/cuda/core/_device.pyx b/cuda_core/cuda/core/_device.pyx index 67255506a2d..1514554054d 100644 --- a/cuda_core/cuda/core/_device.pyx +++ b/cuda_core/cuda/core/_device.pyx @@ -3,6 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 from __future__ import annotations +from cuda.core._utils.properties import python_property cimport cpython @@ -83,467 +84,467 @@ cdef class DeviceProperties: self._cache[attr] = self._get_attribute(attr, default) return self._cache[attr] - @property + @python_property def max_threads_per_block(self) -> int: """int: Maximum number of threads per block.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAX_THREADS_PER_BLOCK) - @property + @python_property def max_block_dim_x(self) -> int: """int: Maximum block dimension X.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAX_BLOCK_DIM_X) - @property + @python_property def max_block_dim_y(self) -> int: """int: Maximum block dimension Y.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAX_BLOCK_DIM_Y) - @property + @python_property def max_block_dim_z(self) -> int: """int: Maximum block dimension Z.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAX_BLOCK_DIM_Z) - @property + @python_property def max_grid_dim_x(self) -> int: """int: Maximum grid dimension X.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAX_GRID_DIM_X) - @property + @python_property def max_grid_dim_y(self) -> int: """int: Maximum grid dimension Y.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAX_GRID_DIM_Y) - @property + @python_property def max_grid_dim_z(self) -> int: """int: Maximum grid dimension Z.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAX_GRID_DIM_Z) - @property + @python_property def max_shared_memory_per_block(self) -> int: """int: Maximum shared memory available per block in bytes.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAX_SHARED_MEMORY_PER_BLOCK) - @property + @python_property def total_constant_memory(self) -> int: """int: Memory available on device for constant variables in a CUDA C kernel in bytes.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_TOTAL_CONSTANT_MEMORY) - @property + @python_property def warp_size(self) -> int: """int: Warp size in threads.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_WARP_SIZE) - @property + @python_property def max_pitch(self) -> int: """int: Maximum pitch in bytes allowed by memory copies.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAX_PITCH) - @property + @python_property def maximum_texture1d_width(self) -> int: """int: Maximum 1D texture width.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE1D_WIDTH) - @property + @python_property def maximum_texture1d_linear_width(self) -> int: """int: Maximum width for a 1D texture bound to linear memory.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE1D_LINEAR_WIDTH) - @property + @python_property def maximum_texture1d_mipmapped_width(self) -> int: """int: Maximum mipmapped 1D texture width.""" return self._get_cached_attribute( driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE1D_MIPMAPPED_WIDTH ) - @property + @python_property def maximum_texture2d_width(self) -> int: """int: Maximum 2D texture width.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_WIDTH) - @property + @python_property def maximum_texture2d_height(self) -> int: """int: Maximum 2D texture height.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_HEIGHT) - @property + @python_property def maximum_texture2d_linear_width(self) -> int: """int: Maximum width for a 2D texture bound to linear memory.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_LINEAR_WIDTH) - @property + @python_property def maximum_texture2d_linear_height(self) -> int: """int: Maximum height for a 2D texture bound to linear memory.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_LINEAR_HEIGHT) - @property + @python_property def maximum_texture2d_linear_pitch(self) -> int: """int: Maximum pitch in bytes for a 2D texture bound to linear memory.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_LINEAR_PITCH) - @property + @python_property def maximum_texture2d_mipmapped_width(self) -> int: """int: Maximum mipmapped 2D texture width.""" return self._get_cached_attribute( driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_MIPMAPPED_WIDTH ) - @property + @python_property def maximum_texture2d_mipmapped_height(self) -> int: """int: Maximum mipmapped 2D texture height.""" return self._get_cached_attribute( driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_MIPMAPPED_HEIGHT ) - @property + @python_property def maximum_texture3d_width(self) -> int: """int: Maximum 3D texture width.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE3D_WIDTH) - @property + @python_property def maximum_texture3d_height(self) -> int: """int: Maximum 3D texture height.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE3D_HEIGHT) - @property + @python_property def maximum_texture3d_depth(self) -> int: """int: Maximum 3D texture depth.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE3D_DEPTH) - @property + @python_property def maximum_texture3d_width_alternate(self) -> int: """int: Alternate maximum 3D texture width, 0 if no alternate maximum 3D texture size is supported.""" return self._get_cached_attribute( driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE3D_WIDTH_ALTERNATE ) - @property + @python_property def maximum_texture3d_height_alternate(self) -> int: """int: Alternate maximum 3D texture height, 0 if no alternate maximum 3D texture size is supported.""" return self._get_cached_attribute( driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE3D_HEIGHT_ALTERNATE ) - @property + @python_property def maximum_texture3d_depth_alternate(self) -> int: """int: Alternate maximum 3D texture depth, 0 if no alternate maximum 3D texture size is supported.""" return self._get_cached_attribute( driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE3D_DEPTH_ALTERNATE ) - @property + @python_property def maximum_texturecubemap_width(self) -> int: """int: Maximum cubemap texture width or height.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURECUBEMAP_WIDTH) - @property + @python_property def maximum_texture1d_layered_width(self) -> int: """int: Maximum 1D layered texture width.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE1D_LAYERED_WIDTH) - @property + @python_property def maximum_texture1d_layered_layers(self) -> int: """int: Maximum layers in a 1D layered texture.""" return self._get_cached_attribute( driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE1D_LAYERED_LAYERS ) - @property + @python_property def maximum_texture2d_layered_width(self) -> int: """int: Maximum 2D layered texture width.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_LAYERED_WIDTH) - @property + @python_property def maximum_texture2d_layered_height(self) -> int: """int: Maximum 2D layered texture height.""" return self._get_cached_attribute( driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_LAYERED_HEIGHT ) - @property + @python_property def maximum_texture2d_layered_layers(self) -> int: """int: Maximum layers in a 2D layered texture.""" return self._get_cached_attribute( driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_LAYERED_LAYERS ) - @property + @python_property def maximum_texturecubemap_layered_width(self) -> int: """int: Maximum cubemap layered texture width or height.""" return self._get_cached_attribute( driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURECUBEMAP_LAYERED_WIDTH ) - @property + @python_property def maximum_texturecubemap_layered_layers(self) -> int: """int: Maximum layers in a cubemap layered texture.""" return self._get_cached_attribute( driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURECUBEMAP_LAYERED_LAYERS ) - @property + @python_property def maximum_surface1d_width(self) -> int: """int: Maximum 1D surface width.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE1D_WIDTH) - @property + @python_property def maximum_surface2d_width(self) -> int: """int: Maximum 2D surface width.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE2D_WIDTH) - @property + @python_property def maximum_surface2d_height(self) -> int: """int: Maximum 2D surface height.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE2D_HEIGHT) - @property + @python_property def maximum_surface3d_width(self) -> int: """int: Maximum 3D surface width.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE3D_WIDTH) - @property + @python_property def maximum_surface3d_height(self) -> int: """int: Maximum 3D surface height.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE3D_HEIGHT) - @property + @python_property def maximum_surface3d_depth(self) -> int: """int: Maximum 3D surface depth.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE3D_DEPTH) - @property + @python_property def maximum_surface1d_layered_width(self) -> int: """int: Maximum 1D layered surface width.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE1D_LAYERED_WIDTH) - @property + @python_property def maximum_surface1d_layered_layers(self) -> int: """int: Maximum layers in a 1D layered surface.""" return self._get_cached_attribute( driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE1D_LAYERED_LAYERS ) - @property + @python_property def maximum_surface2d_layered_width(self) -> int: """int: Maximum 2D layered surface width.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE2D_LAYERED_WIDTH) - @property + @python_property def maximum_surface2d_layered_height(self) -> int: """int: Maximum 2D layered surface height.""" return self._get_cached_attribute( driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE2D_LAYERED_HEIGHT ) - @property + @python_property def maximum_surface2d_layered_layers(self) -> int: """int: Maximum layers in a 2D layered surface.""" return self._get_cached_attribute( driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACE2D_LAYERED_LAYERS ) - @property + @python_property def maximum_surfacecubemap_width(self) -> int: """int: Maximum cubemap surface width.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACECUBEMAP_WIDTH) - @property + @python_property def maximum_surfacecubemap_layered_width(self) -> int: """int: Maximum cubemap layered surface width.""" return self._get_cached_attribute( driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACECUBEMAP_LAYERED_WIDTH ) - @property + @python_property def maximum_surfacecubemap_layered_layers(self) -> int: """int: Maximum layers in a cubemap layered surface.""" return self._get_cached_attribute( driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_SURFACECUBEMAP_LAYERED_LAYERS ) - @property + @python_property def max_registers_per_block(self) -> int: """int: Maximum number of 32-bit registers available to a thread block.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAX_REGISTERS_PER_BLOCK) - @property + @python_property def clock_rate(self) -> int: """int: Typical clock frequency in kilohertz.""" return self._get_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_CLOCK_RATE) - @property + @python_property def texture_alignment(self) -> int: """int: Alignment requirement for textures.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_TEXTURE_ALIGNMENT) - @property + @python_property def texture_pitch_alignment(self) -> int: """int: Pitch alignment requirement for textures.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_TEXTURE_PITCH_ALIGNMENT) - @property + @python_property def gpu_overlap(self) -> bool: """bool: Device can possibly copy memory and execute a kernel concurrently. Deprecated. Use :attr:`~DeviceProperties.async_engine_count` instead.""" return bool(self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_GPU_OVERLAP)) - @property + @python_property def multiprocessor_count(self) -> int: """int: Number of multiprocessors on device.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT) - @property + @python_property def kernel_exec_timeout(self) -> bool: """bool: Specifies whether there is a run time limit on kernels.""" return bool(self._get_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_KERNEL_EXEC_TIMEOUT)) - @property + @python_property def integrated(self) -> bool: """bool: Device is integrated with host memory.""" return bool(self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_INTEGRATED)) - @property + @python_property def can_map_host_memory(self) -> bool: """bool: Device can map host memory into CUDA address space.""" return bool(self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_CAN_MAP_HOST_MEMORY)) - @property + @python_property def compute_mode(self) -> int: """int: Compute mode (See CUcomputemode for details).""" return self._get_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_COMPUTE_MODE) - @property + @python_property def concurrent_kernels(self) -> bool: """bool: Device can possibly execute multiple kernels concurrently.""" return bool(self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_CONCURRENT_KERNELS)) - @property + @python_property def ecc_enabled(self) -> bool: """bool: Device has ECC support enabled.""" return bool(self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_ECC_ENABLED)) - @property + @python_property def pci_bus_id(self) -> int: """int: PCI bus ID of the device.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_PCI_BUS_ID) - @property + @python_property def pci_device_id(self) -> int: """int: PCI device ID of the device.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_PCI_DEVICE_ID) - @property + @python_property def pci_domain_id(self) -> int: """int: PCI domain ID of the device.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_PCI_DOMAIN_ID) - @property + @python_property def tcc_driver(self) -> bool: """bool: Device is using TCC driver model.""" return bool(self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_TCC_DRIVER)) - @property + @python_property def memory_clock_rate(self) -> int: """int: Peak memory clock frequency in kilohertz.""" return self._get_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MEMORY_CLOCK_RATE) - @property + @python_property def global_memory_bus_width(self) -> int: """int: Global memory bus width in bits.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_GLOBAL_MEMORY_BUS_WIDTH) - @property + @python_property def l2_cache_size(self) -> int: """int: Size of L2 cache in bytes.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_L2_CACHE_SIZE) - @property + @python_property def max_threads_per_multiprocessor(self) -> int: """int: Maximum resident threads per multiprocessor.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAX_THREADS_PER_MULTIPROCESSOR) - @property + @python_property def unified_addressing(self) -> bool: """bool: Device shares a unified address space with the host.""" return bool(self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_UNIFIED_ADDRESSING)) - @property + @python_property def compute_capability_major(self) -> int: """int: Major compute capability version number.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR) - @property + @python_property def compute_capability_minor(self) -> int: """int: Minor compute capability version number.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR) - @property + @python_property def global_l1_cache_supported(self) -> bool: """bool: Device supports caching globals in L1.""" return bool(self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_GLOBAL_L1_CACHE_SUPPORTED)) - @property + @python_property def local_l1_cache_supported(self) -> bool: """bool: Device supports caching locals in L1.""" return bool(self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_LOCAL_L1_CACHE_SUPPORTED)) - @property + @python_property def max_shared_memory_per_multiprocessor(self) -> int: """int: Maximum shared memory available per multiprocessor in bytes.""" return self._get_cached_attribute( driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAX_SHARED_MEMORY_PER_MULTIPROCESSOR ) - @property + @python_property def max_registers_per_multiprocessor(self) -> int: """int: Maximum number of 32-bit registers available per multiprocessor.""" return self._get_cached_attribute( driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAX_REGISTERS_PER_MULTIPROCESSOR ) - @property + @python_property def managed_memory(self) -> bool: """bool: Device can allocate managed memory on this system.""" return bool(self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MANAGED_MEMORY)) - @property + @python_property def multi_gpu_board(self) -> bool: """bool: Device is on a multi-GPU board.""" return bool(self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MULTI_GPU_BOARD)) - @property + @python_property def multi_gpu_board_group_id(self) -> int: """int: Unique id for a group of devices on the same multi-GPU board.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MULTI_GPU_BOARD_GROUP_ID) - @property + @python_property def host_native_atomic_supported(self) -> bool: """bool: Link between the device and the host supports all native atomic operations.""" return bool( self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_HOST_NATIVE_ATOMIC_SUPPORTED) ) - @property + @python_property def single_to_double_precision_perf_ratio(self) -> int: """int: Ratio of single precision performance (in floating-point operations per second) to double precision performance.""" return self._get_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_SINGLE_TO_DOUBLE_PRECISION_PERF_RATIO) - @property + @python_property def pageable_memory_access(self) -> bool: """bool: Device supports coherently accessing pageable memory without calling cudaHostRegister on it.""" return bool(self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_PAGEABLE_MEMORY_ACCESS)) - @property + @python_property def concurrent_managed_access(self) -> bool: """bool: Device can coherently access managed memory concurrently with the CPU.""" return bool(self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_CONCURRENT_MANAGED_ACCESS)) - @property + @python_property def compute_preemption_supported(self) -> bool: """bool: Device supports compute preemption.""" return bool( self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_COMPUTE_PREEMPTION_SUPPORTED) ) - @property + @python_property def can_use_host_pointer_for_registered_mem(self) -> bool: """bool: Device can access host registered memory at the same virtual address as the CPU.""" return bool( @@ -554,21 +555,21 @@ cdef class DeviceProperties: # TODO: A few attrs are missing here (NVIDIA/cuda-python#675) - @property + @python_property def cooperative_launch(self) -> bool: """bool: Device supports launching cooperative kernels via cuLaunchCooperativeKernel.""" return bool(self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_COOPERATIVE_LAUNCH)) # TODO: A few attrs are missing here (NVIDIA/cuda-python#675) - @property + @python_property def max_shared_memory_per_block_optin(self) -> int: """int: Maximum optin shared memory per block.""" return self._get_cached_attribute( driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAX_SHARED_MEMORY_PER_BLOCK_OPTIN ) - @property + @python_property def pageable_memory_access_uses_host_page_tables(self) -> bool: """bool: Device accesses pageable memory via the host's page tables.""" return bool( @@ -577,7 +578,7 @@ cdef class DeviceProperties: ) ) - @property + @python_property def direct_managed_mem_access_from_host(self) -> bool: """bool: The host can directly access managed memory on the device without migration.""" return bool( @@ -586,7 +587,7 @@ cdef class DeviceProperties: ) ) - @property + @python_property def virtual_memory_management_supported(self) -> bool: """bool: Device supports virtual memory management APIs like cuMemAddressReserve, cuMemCreate, cuMemMap and related APIs.""" return bool( @@ -595,7 +596,7 @@ cdef class DeviceProperties: ) ) - @property + @python_property def handle_type_posix_file_descriptor_supported(self) -> bool: """bool: Device supports exporting memory to a posix file descriptor with cuMemExportToShareableHandle, if requested via cuMemCreate.""" return bool( @@ -604,14 +605,14 @@ cdef class DeviceProperties: ) ) - @property + @python_property def handle_type_win32_handle_supported(self) -> bool: """bool: Device supports exporting memory to a Win32 NT handle with cuMemExportToShareableHandle, if requested via cuMemCreate.""" return bool( self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_HANDLE_TYPE_WIN32_HANDLE_SUPPORTED) ) - @property + @python_property def handle_type_win32_kmt_handle_supported(self) -> bool: """bool: Device supports exporting memory to a Win32 KMT handle with cuMemExportToShareableHandle, if requested via cuMemCreate.""" return bool( @@ -620,29 +621,29 @@ cdef class DeviceProperties: ) ) - @property + @python_property def max_blocks_per_multiprocessor(self) -> int: """int: Maximum number of blocks per multiprocessor.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAX_BLOCKS_PER_MULTIPROCESSOR) - @property + @python_property def generic_compression_supported(self) -> bool: """bool: Device supports compression of memory.""" return bool( self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_GENERIC_COMPRESSION_SUPPORTED) ) - @property + @python_property def max_persisting_l2_cache_size(self) -> int: """int: Maximum L2 persisting lines capacity setting in bytes.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAX_PERSISTING_L2_CACHE_SIZE) - @property + @python_property def max_access_policy_window_size(self) -> int: """int: Maximum value of CUaccessPolicyWindow.num_bytes.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAX_ACCESS_POLICY_WINDOW_SIZE) - @property + @python_property def gpu_direct_rdma_with_cuda_vmm_supported(self) -> bool: """bool: Device supports specifying the GPUDirect RDMA flag with cuMemCreate.""" return bool( @@ -651,55 +652,55 @@ cdef class DeviceProperties: ) ) - @property + @python_property def reserved_shared_memory_per_block(self) -> int: """int: Shared memory reserved by CUDA driver per block in bytes.""" return self._get_cached_attribute( driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_RESERVED_SHARED_MEMORY_PER_BLOCK ) - @property + @python_property def sparse_cuda_array_supported(self) -> bool: """bool: Device supports sparse CUDA arrays and sparse CUDA mipmapped arrays.""" return bool( self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_SPARSE_CUDA_ARRAY_SUPPORTED) ) - @property + @python_property def read_only_host_register_supported(self) -> bool: """bool: True if device supports using the cuMemHostRegister flag CU_MEMHOSTREGISTER_READ_ONLY to register memory that must be mapped as read-only to the GPU, False if not.""" return bool( self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_READ_ONLY_HOST_REGISTER_SUPPORTED) ) - @property + @python_property def memory_pools_supported(self) -> bool: """bool: Device supports using the cuMemAllocAsync and cuMemPool family of APIs.""" return bool(self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MEMORY_POOLS_SUPPORTED)) - @property + @python_property def gpu_direct_rdma_supported(self) -> bool: """bool: Device supports GPUDirect RDMA APIs, like nvidia_p2p_get_pages (see https://docs.nvidia.com/cuda/gpudirect-rdma for more information).""" return bool(self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_GPU_DIRECT_RDMA_SUPPORTED)) - @property + @python_property def gpu_direct_rdma_flush_writes_options(self) -> int: """int: The returned attribute shall be interpreted as a bitmask, where the individual bits are described by the CUflushGPUDirectRDMAWritesOptions enum.""" return self._get_cached_attribute( driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_GPU_DIRECT_RDMA_FLUSH_WRITES_OPTIONS ) - @property + @python_property def gpu_direct_rdma_writes_ordering(self) -> int: """int: GPUDirect RDMA writes to the device do not need to be flushed for consumers within the scope indicated by the returned attribute. See CUGPUDirectRDMAWritesOrdering for the numerical values returned here.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_GPU_DIRECT_RDMA_WRITES_ORDERING) - @property + @python_property def mempool_supported_handle_types(self) -> int: """int: Handle types supported with mempool based IPC.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MEMPOOL_SUPPORTED_HANDLE_TYPES) - @property + @python_property def deferred_mapping_cuda_array_supported(self) -> bool: """bool: Device supports deferred mapping CUDA arrays and CUDA mipmapped arrays.""" return bool( @@ -708,64 +709,64 @@ cdef class DeviceProperties: ) ) - @property + @python_property def numa_config(self) -> int: """int: NUMA configuration of a device: value is of type CUdeviceNumaConfig enum.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_NUMA_CONFIG) - @property + @python_property def numa_id(self) -> int: """int: NUMA node ID of the GPU memory.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_NUMA_ID) - @property + @python_property def multicast_supported(self) -> bool: """bool: Device supports switch multicast and reduction operations.""" return bool(self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MULTICAST_SUPPORTED)) - @property + @python_property def surface_alignment(self) -> int: """int: Surface alignment requirement in bytes.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_SURFACE_ALIGNMENT) - @property + @python_property def async_engine_count(self) -> int: """int: Number of asynchronous engines.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_ASYNC_ENGINE_COUNT) - @property + @python_property def can_tex2d_gather(self) -> bool: """bool: True if device supports 2D texture gather operations, False if not.""" return bool(self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_CAN_TEX2D_GATHER)) - @property + @python_property def maximum_texture2d_gather_width(self) -> int: """int: Maximum 2D texture gather width.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_GATHER_WIDTH) - @property + @python_property def maximum_texture2d_gather_height(self) -> int: """int: Maximum 2D texture gather height.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAXIMUM_TEXTURE2D_GATHER_HEIGHT) - @property + @python_property def stream_priorities_supported(self) -> bool: """bool: True if device supports stream priorities, False if not.""" return bool( self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_STREAM_PRIORITIES_SUPPORTED) ) - @property + @python_property def can_flush_remote_writes(self) -> bool: """bool: The CU_STREAM_WAIT_VALUE_FLUSH flag and the CU_STREAM_MEM_OP_FLUSH_REMOTE_WRITES MemOp are supported on the device. See Stream Memory Operations for additional details.""" return bool(self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_CAN_FLUSH_REMOTE_WRITES)) - @property + @python_property def host_register_supported(self) -> bool: """bool: Device supports host memory registration via cudaHostRegister.""" return bool(self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_HOST_REGISTER_SUPPORTED)) - @property + @python_property def timeline_semaphore_interop_supported(self) -> bool: """bool: External timeline semaphore interop is supported on the device.""" return bool( @@ -774,92 +775,92 @@ cdef class DeviceProperties: ) ) - @property + @python_property def cluster_launch(self) -> bool: """bool: Indicates device supports cluster launch.""" return bool(self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_CLUSTER_LAUNCH)) - @property + @python_property def can_use_64_bit_stream_mem_ops(self) -> bool: """bool: 64-bit operations are supported in cuStreamBatchMemOp and related MemOp APIs.""" return bool( self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_CAN_USE_64_BIT_STREAM_MEM_OPS) ) - @property + @python_property def can_use_stream_wait_value_nor(self) -> bool: """bool: CU_STREAM_WAIT_VALUE_NOR is supported by MemOp APIs.""" return bool( self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_CAN_USE_STREAM_WAIT_VALUE_NOR) ) - @property + @python_property def dma_buf_supported(self) -> bool: """bool: Device supports buffer sharing with dma_buf mechanism.""" return bool(self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_DMA_BUF_SUPPORTED)) # Start of CUDA 12 device attributes - @property + @python_property def ipc_event_supported(self) -> bool: """bool: Device supports IPC Events.""" return bool(self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_IPC_EVENT_SUPPORTED)) - @property + @python_property def mem_sync_domain_count(self) -> int: """int: Number of memory domains the device supports.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MEM_SYNC_DOMAIN_COUNT, default=1) - @property + @python_property def tensor_map_access_supported(self) -> bool: """bool: Device supports accessing memory using Tensor Map.""" return bool( self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_TENSOR_MAP_ACCESS_SUPPORTED) ) - @property + @python_property def handle_type_fabric_supported(self) -> bool: """bool: Device supports exporting memory to a fabric handle with cuMemExportToShareableHandle() or requested with cuMemCreate().""" return bool( self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_HANDLE_TYPE_FABRIC_SUPPORTED) ) - @property + @python_property def unified_function_pointers(self) -> bool: """bool: Device supports unified function pointers.""" return bool(self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_UNIFIED_FUNCTION_POINTERS)) - @property + @python_property def mps_enabled(self) -> bool: """bool: Indicates if contexts created on this device will be shared via MPS.""" return bool(self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MPS_ENABLED)) - @property + @python_property def host_numa_id(self) -> int: """int: NUMA ID of the host node closest to the device. Returns -1 when system does not support NUMA.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_HOST_NUMA_ID, default=-1) - @property + @python_property def d3d12_cig_supported(self) -> bool: """bool: Device supports CIG with D3D12.""" return bool(self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_D3D12_CIG_SUPPORTED)) - @property + @python_property def mem_decompress_algorithm_mask(self) -> int: """int: The returned value shall be interpreted as a bitmask, where the individual bits are described by the CUmemDecompressAlgorithm enum.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MEM_DECOMPRESS_ALGORITHM_MASK) - @property + @python_property def mem_decompress_maximum_length(self) -> int: """int: The returned value is the maximum length in bytes of a single decompress operation that is allowed.""" return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MEM_DECOMPRESS_MAXIMUM_LENGTH) - @property + @python_property def vulkan_cig_supported(self) -> bool: """bool: Device supports CIG with Vulkan.""" return bool(self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_VULKAN_CIG_SUPPORTED)) - @property + @python_property def gpu_pci_device_id(self) -> int: """int: The combined 16-bit PCI device ID and 16-bit PCI vendor ID. @@ -867,7 +868,7 @@ cdef class DeviceProperties: """ return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_GPU_PCI_DEVICE_ID) - @property + @python_property def gpu_pci_subsystem_id(self) -> int: """int: The combined 16-bit PCI subsystem ID and 16-bit PCI subsystem vendor ID. @@ -875,7 +876,7 @@ cdef class DeviceProperties: """ return self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_GPU_PCI_SUBSYSTEM_ID) - @property + @python_property def host_numa_virtual_memory_management_supported(self) -> bool: """bool: Device supports HOST_NUMA location with the virtual memory management APIs like cuMemCreate, cuMemMap and related APIs.""" return bool( @@ -884,7 +885,7 @@ cdef class DeviceProperties: ) ) - @property + @python_property def host_numa_memory_pools_supported(self) -> bool: """bool: Device supports HOST_NUMA location with the cuMemAllocAsync and cuMemPool family of APIs.""" return bool( @@ -893,21 +894,21 @@ cdef class DeviceProperties: # Start of CUDA 13 device attributes - @property + @python_property def host_numa_multinode_ipc_supported(self) -> bool: """bool: Device supports HOST_NUMA location IPC between nodes in a multi-node system.""" return bool( self._get_cached_attribute(driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_HOST_NUMA_MULTINODE_IPC_SUPPORTED) ) - @property + @python_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) ) - @property + @python_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( @@ -916,14 +917,14 @@ cdef class DeviceProperties: ) ) - @property + @python_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) ) - @property + @python_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( diff --git a/cuda_core/cuda/core/_device_resources.pyx b/cuda_core/cuda/core/_device_resources.pyx index 28a478028d1..fc4025e01f7 100644 --- a/cuda_core/cuda/core/_device_resources.pyx +++ b/cuda_core/cuda/core/_device_resources.pyx @@ -442,27 +442,27 @@ cdef class SMResource: self._is_usable = is_usable return self - @property + @python_property def handle(self) -> int: """Return the address of the underlying ``CUdevResource`` struct.""" return (&self._resource) - @property + @python_property def sm_count(self) -> int: """Total SMs available in this resource.""" return self._sm_count - @property + @python_property def min_partition_size(self) -> int: """Minimum SM count required to create a partition.""" return self._min_partition_size - @property + @python_property def coscheduled_alignment(self) -> int: """Number of SMs guaranteed to be co-scheduled.""" return self._coscheduled_alignment - @property + @python_property def flags(self) -> int: """Raw flags from the underlying SM resource.""" return self._flags @@ -520,7 +520,7 @@ cdef class WorkqueueResource: self._wq_resource = wq return self - @property + @python_property def handle(self) -> int: """Return the address of the underlying config ``CUdevResource`` struct.""" return (&self._wq_config_resource) @@ -614,7 +614,7 @@ cdef class DeviceResources: )) return 0 - @property + @python_property def sm(self) -> SMResource: """Return the :obj:`SMResource` for this device or context.""" _check_green_ctx_support() diff --git a/cuda_core/cuda/core/_event.pyx b/cuda_core/cuda/core/_event.pyx index fa08e0ac4e0..e664c437a45 100644 --- a/cuda_core/cuda/core/_event.pyx +++ b/cuda_core/cuda/core/_event.pyx @@ -238,17 +238,17 @@ cdef class Event: self._ipc_descriptor = ipc_descriptor return self - @property + @python_property def is_ipc_enabled(self) -> bool: """Return True if the event can be shared across process boundaries, otherwise False.""" return get_event_ipc_enabled(self._h_event) - @property + @python_property def is_timing_enabled(self) -> bool: """Return True if the event records timing data, otherwise False.""" return get_event_timing_enabled(self._h_event) - @property + @python_property def is_blocking_sync(self) -> bool: """Return True if the event uses blocking synchronization (the CPU thread blocks on :meth:`sync` instead of busy-waiting), otherwise False. @@ -267,7 +267,7 @@ cdef class Event: with nogil: HANDLE_RETURN(cydriver.cuEventSynchronize(as_cu(self._h_event))) - @property + @python_property def is_done(self) -> bool: """Return True if all captured works have been completed, otherwise False.""" with nogil: @@ -278,7 +278,7 @@ cdef class Event: return False HANDLE_RETURN(result) - @property + @python_property def handle(self) -> cuda.bindings.driver.CUevent: """Return the underlying CUevent object. @@ -289,7 +289,7 @@ cdef class Event: """ return as_py(self._h_event) - @property + @python_property def device(self) -> Device: """Return the :obj:`~_device.Device` singleton associated with this event. @@ -305,7 +305,7 @@ cdef class Event: from ._device import Device # avoid circular import return Device(dev_id) - @property + @python_property def context(self) -> Context: """Return the :obj:`~_context.Context` associated with this event.""" cdef ContextHandle h_ctx = get_event_context(self._h_event) diff --git a/cuda_core/cuda/core/_graphics.pyx b/cuda_core/cuda/core/_graphics.pyx index a377589dfc2..4f5346ee776 100644 --- a/cuda_core/cuda/core/_graphics.pyx +++ b/cuda_core/cuda/core/_graphics.pyx @@ -3,6 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 from __future__ import annotations +from cuda.core._utils.properties import python_property from cuda.bindings cimport cydriver from cuda.core._resource_handles cimport ( @@ -333,17 +334,17 @@ cdef class GraphicsResource: self._context_manager_stream = None self._entered_buffer = None - @property + @python_property def is_mapped(self) -> bool: """Whether the resource is currently mapped for CUDA access.""" return self._get_mapped_buffer() is not None - @property + @python_property def handle(self) -> int: """The raw ``CUgraphicsResource`` handle as a Python int.""" return as_intptr(self._handle) - @property + @python_property def resource_handle(self) -> int: """Alias for :attr:`handle`.""" return self.handle diff --git a/cuda_core/cuda/core/_layout.pyx b/cuda_core/cuda/core/_layout.pyx index 3e2580d11d1..025392dc6d0 100644 --- a/cuda_core/cuda/core/_layout.pyx +++ b/cuda_core/cuda/core/_layout.pyx @@ -2,6 +2,7 @@ # # SPDX-License-Identifier: Apache-2.0 +from cuda.core._utils.properties import python_property cimport cython from libc.stdint cimport int64_t, intptr_t @@ -179,7 +180,7 @@ cdef class _StridedLayout: def __eq__(self : _StridedLayout, other : _StridedLayout) -> bool: return self.itemsize == other.itemsize and self.slice_offset == other.slice_offset and _base_layout_equal(self.base, other.base) - @property + @python_property def ndim(self : _StridedLayout): """ The number of dimensions (length of the shape tuple). @@ -188,7 +189,7 @@ cdef class _StridedLayout: """ return self.base.ndim - @property + @python_property def shape(self : _StridedLayout): """ Shape of the tensor. @@ -197,7 +198,7 @@ cdef class _StridedLayout: """ return self.get_shape_tuple() - @property + @python_property def strides(self : _StridedLayout): """ Strides of the tensor (in **counts**, not bytes). @@ -208,7 +209,7 @@ cdef class _StridedLayout: """ return self.get_strides_tuple() - @property + @python_property def strides_in_bytes(self : _StridedLayout): """ Strides of the tensor (in bytes). @@ -217,7 +218,7 @@ cdef class _StridedLayout: """ return self.get_strides_in_bytes_tuple() - @property + @python_property def stride_order(self : _StridedLayout): """ A permutation of ``tuple(range(ndim))`` describing the @@ -237,7 +238,7 @@ cdef class _StridedLayout: """ return self.get_stride_order_tuple() - @property + @python_property def volume(self : _StridedLayout): """ The number of elements in the tensor, i.e. the product of the shape tuple. @@ -246,7 +247,7 @@ cdef class _StridedLayout: """ return self.get_volume() - @property + @python_property def is_unique(self : _StridedLayout): """ If True, each element of a tensor with this layout is mapped to @@ -267,7 +268,7 @@ cdef class _StridedLayout: """ return self.get_is_unique() - @property + @python_property def is_contiguous_c(self : _StridedLayout): """ True iff the layout is contiguous in C-order, i.e. @@ -288,7 +289,7 @@ cdef class _StridedLayout: """ return self.get_is_contiguous_c() - @property + @python_property def is_contiguous_f(self : _StridedLayout): """ True iff the layout is contiguous in F-order, i.e. @@ -309,7 +310,7 @@ cdef class _StridedLayout: """ return self.get_is_contiguous_f() - @property + @python_property def is_contiguous_any(self : _StridedLayout): """ True iff the layout is contiguous in some axis order, i.e. @@ -349,7 +350,7 @@ cdef class _StridedLayout: """ return self.get_is_contiguous_any() - @property + @python_property def is_dense(self : _StridedLayout): """ A dense layout is contiguous (:attr:`is_contiguous_any` is True) @@ -362,7 +363,7 @@ cdef class _StridedLayout: """ return self.get_is_dense() - @property + @python_property def offset_bounds(self : _StridedLayout): """ The memory offset range ``[min_offset, max_offset]`` (in element counts, not bytes) @@ -395,7 +396,7 @@ cdef class _StridedLayout: self.get_offset_bounds(min_offset, max_offset) return min_offset, max_offset - @property + @python_property def min_offset(self : _StridedLayout): """ See :attr:`offset_bounds` for details. @@ -407,7 +408,7 @@ cdef class _StridedLayout: self.get_offset_bounds(min_offset, max_offset) return min_offset - @property + @python_property def max_offset(self : _StridedLayout): """ See :attr:`offset_bounds` for details. @@ -419,7 +420,7 @@ cdef class _StridedLayout: self.get_offset_bounds(min_offset, max_offset) return max_offset - @property + @python_property def slice_offset_in_bytes(self : _StridedLayout): """ The memory offset (as a number of bytes) of the element at index ``(0,) * ndim``. diff --git a/cuda_core/cuda/core/_linker.pyx b/cuda_core/cuda/core/_linker.pyx index 8f513ce1217..8daed2f7667 100644 --- a/cuda_core/cuda/core/_linker.pyx +++ b/cuda_core/cuda/core/_linker.pyx @@ -9,6 +9,7 @@ configuration. """ from __future__ import annotations +from cuda.core._utils.properties import python_property from cpython.bytearray cimport PyByteArray_AS_STRING from libc.stdint cimport intptr_t, uint32_t @@ -150,7 +151,7 @@ cdef class Linker: else: self._culink_handle.reset() - @property + @python_property def handle(self) -> LinkerHandleT: """Return the underlying handle object. diff --git a/cuda_core/cuda/core/_memory/_buffer.pyx b/cuda_core/cuda/core/_memory/_buffer.pyx index 55c6d8944da..40ffcd76ee5 100644 --- a/cuda_core/cuda/core/_memory/_buffer.pyx +++ b/cuda_core/cuda/core/_memory/_buffer.pyx @@ -361,7 +361,7 @@ cdef class Buffer: # Supporting method paired with __buffer__. raise NotImplementedError("WIP: Buffer.__release_buffer__ hasn't been implemented yet.") - @property + @python_property def device_id(self) -> int: """Return the device ordinal of this buffer.""" if self._memory_resource is not None: @@ -369,7 +369,7 @@ cdef class Buffer: _init_mem_attrs(self) return self._mem_attrs.device_id - @property + @python_property def handle(self) -> DevicePointerType: """Return the buffer handle object. @@ -396,7 +396,7 @@ cdef class Buffer: maybe_is_mapped = " is_mapped=True" if self.is_mapped else "" return f"" - @property + @python_property def is_device_accessible(self) -> bool: """Return True if this buffer can be accessed by the GPU, otherwise False.""" if self._memory_resource is not None: @@ -404,7 +404,7 @@ cdef class Buffer: _init_mem_attrs(self) return self._mem_attrs.is_device_accessible - @property + @python_property def is_host_accessible(self) -> bool: """Return True if this buffer can be accessed by the CPU, otherwise False.""" if self._memory_resource is not None: @@ -412,7 +412,7 @@ cdef class Buffer: _init_mem_attrs(self) return self._mem_attrs.is_host_accessible - @property + @python_property def is_managed(self) -> bool: """Return True if this buffer is CUDA managed (unified) memory, otherwise False.""" _init_mem_attrs(self) @@ -422,23 +422,23 @@ cdef class Buffer: # so fall back to the memory resource when it advertises managed allocations. return self._memory_resource is not None and self._memory_resource.is_managed - @property + @python_property def is_mapped(self) -> bool: """Return True if this buffer is mapped into the process via IPC.""" return getattr(self._ipc_data, "is_mapped", False) - @property + @python_property def memory_resource(self) -> MemoryResource: """Return the memory resource associated with this buffer.""" return self._memory_resource - @property + @python_property def size(self) -> int: """Return the memory size of this buffer.""" return self._size - @property + @python_property def owner(self) -> object: """Return the object holding external allocation.""" return self._owner @@ -554,22 +554,22 @@ cdef class MemoryResource: """ raise TypeError("MemoryResource.deallocate must be implemented by subclasses.") - @property + @python_property def is_device_accessible(self) -> bool: """Whether buffers allocated by this resource are device-accessible.""" raise TypeError("MemoryResource.is_device_accessible must be implemented by subclasses.") - @property + @python_property def is_host_accessible(self) -> bool: """Whether buffers allocated by this resource are host-accessible.""" raise TypeError("MemoryResource.is_host_accessible must be implemented by subclasses.") - @property + @python_property def is_managed(self) -> bool: """Whether buffers allocated by this resource are CUDA managed (unified) memory.""" return False - @property + @python_property def device_id(self) -> int: """Device ID associated with this memory resource, or -1 if not applicable.""" raise TypeError("MemoryResource.device_id must be implemented by subclasses.") diff --git a/cuda_core/cuda/core/_memory/_device_memory_resource.pyx b/cuda_core/cuda/core/_memory/_device_memory_resource.pyx index 8a196b98522..9f1a6626476 100644 --- a/cuda_core/cuda/core/_memory/_device_memory_resource.pyx +++ b/cuda_core/cuda/core/_memory/_device_memory_resource.pyx @@ -202,12 +202,11 @@ cdef class DeviceMemoryResource(_MemPool): raise RuntimeError("Memory resource is not IPC-enabled") return self._ipc_data._alloc_handle - @property + @python_property def device_id(self) -> int: """The associated device ordinal.""" return self._dev_id - @property def peer_accessible_by(self): """ Get or set the devices that can access allocations from this memory @@ -228,16 +227,17 @@ cdef class DeviceMemoryResource(_MemPool): """ return PeerAccessibleBySetProxy(self) - @peer_accessible_by.setter - def peer_accessible_by(self, devices): + def _set_peer_accessible_by(self, devices): replace_peer_accessible_by(self, devices) - @property + peer_accessible_by = python_property(peer_accessible_by, _set_peer_accessible_by) + + @python_property def is_device_accessible(self) -> bool: """Return True. This memory resource provides device-accessible buffers.""" return True - @property + @python_property def is_host_accessible(self) -> bool: """Return False. This memory resource does not provide host-accessible buffers.""" return False diff --git a/cuda_core/cuda/core/_memory/_graph_memory_resource.pyx b/cuda_core/cuda/core/_memory/_graph_memory_resource.pyx index 8fdc324dc59..5ec742e8df7 100644 --- a/cuda_core/cuda/core/_memory/_graph_memory_resource.pyx +++ b/cuda_core/cuda/core/_memory/_graph_memory_resource.pyx @@ -3,6 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 from __future__ import annotations +from cuda.core._utils.properties import python_property from libc.stdint cimport intptr_t @@ -51,14 +52,13 @@ cdef class GraphMemoryResourceAttributes: HANDLE_RETURN(cydriver.cuDeviceSetGraphMemAttribute(self._device_id, attr_enum, value)) return 0 - @property + @python_property def reserved_mem_current(self): """Current amount of backing memory allocated.""" cdef cydriver.cuuint64_t value self._getattribute(cydriver.CUgraphMem_attribute.CU_GRAPH_MEM_ATTR_RESERVED_MEM_CURRENT, &value) return int(value) - @property def reserved_mem_high(self): """ High watermark of backing memory allocated. It can be set to zero to @@ -68,21 +68,21 @@ cdef class GraphMemoryResourceAttributes: self._getattribute(cydriver.CUgraphMem_attribute.CU_GRAPH_MEM_ATTR_RESERVED_MEM_HIGH, &value) return int(value) - @reserved_mem_high.setter - def reserved_mem_high(self, value: int): + def _set_reserved_mem_high(self, value: int): if value != 0: raise AttributeError(f"Attribute 'reserved_mem_high' may only be set to zero (got {value}).") cdef cydriver.cuuint64_t zero = 0 self._setattribute(cydriver.CUgraphMem_attribute.CU_GRAPH_MEM_ATTR_RESERVED_MEM_HIGH, &zero) - @property + reserved_mem_high = python_property(reserved_mem_high, _set_reserved_mem_high) + + @python_property def used_mem_current(self): """Current amount of memory in use.""" cdef cydriver.cuuint64_t value self._getattribute(cydriver.CUgraphMem_attribute.CU_GRAPH_MEM_ATTR_USED_MEM_CURRENT, &value) return int(value) - @property def used_mem_high(self): """ High watermark of memory in use. It can be set to zero to reset it to @@ -92,13 +92,14 @@ cdef class GraphMemoryResourceAttributes: self._getattribute(cydriver.CUgraphMem_attribute.CU_GRAPH_MEM_ATTR_USED_MEM_HIGH, &value) return int(value) - @used_mem_high.setter - def used_mem_high(self, value: int): + def _set_used_mem_high(self, value: int): if value != 0: raise AttributeError(f"Attribute 'used_mem_high' may only be set to zero (got {value}).") cdef cydriver.cuuint64_t zero = 0 self._setattribute(cydriver.CUgraphMem_attribute.CU_GRAPH_MEM_ATTR_USED_MEM_HIGH, &zero) + used_mem_high = python_property(used_mem_high, _set_used_mem_high) + cdef class cyGraphMemoryResource(MemoryResource): def __cinit__(self, int device_id): @@ -127,22 +128,22 @@ cdef class cyGraphMemoryResource(MemoryResource): with nogil: HANDLE_RETURN(cydriver.cuDeviceGraphMemTrim(self._device_id)) - @property + @python_property def attributes(self) -> GraphMemoryResourceAttributes: """Asynchronous allocation attributes related to graphs.""" return GraphMemoryResourceAttributes._init(self._device_id) - @property + @python_property def device_id(self) -> int: """The associated device ordinal.""" return self._device_id - @property + @python_property def is_device_accessible(self) -> bool: """Return True. This memory resource provides device-accessible buffers.""" return True - @property + @python_property def is_host_accessible(self) -> bool: """Return False. This memory resource does not provide host-accessible buffers.""" return False diff --git a/cuda_core/cuda/core/_memory/_ipc.pyx b/cuda_core/cuda/core/_memory/_ipc.pyx index 59414fc1b2e..1e1f17fd50f 100644 --- a/cuda_core/cuda/core/_memory/_ipc.pyx +++ b/cuda_core/cuda/core/_memory/_ipc.pyx @@ -2,6 +2,7 @@ # # SPDX-License-Identifier: Apache-2.0 +from cuda.core._utils.properties import python_property cimport cpython from cuda.bindings cimport cydriver @@ -49,11 +50,11 @@ cdef class IPCDataForBuffer: self._ipc_descriptor = ipc_descriptor self._is_mapped = is_mapped - @property + @python_property def ipc_descriptor(self): return self._ipc_descriptor - @property + @python_property def is_mapped(self): return self._is_mapped @@ -64,15 +65,15 @@ cdef class IPCDataForMR: self._alloc_handle = alloc_handle self._is_mapped = is_mapped - @property + @python_property def alloc_handle(self): return self._alloc_handle - @property + @python_property def is_mapped(self): return self._is_mapped - @property + @python_property def uuid(self): return getattr(self._alloc_handle, 'uuid', None) @@ -93,7 +94,7 @@ cdef class IPCBufferDescriptor: def __reduce__(self): return IPCBufferDescriptor._init, (self._payload, self._size) - @property + @python_property def size(self): return self._size @@ -127,11 +128,11 @@ cdef class IPCAllocationHandle: ) return as_py(self._h_fd) - @property + @python_property def handle(self) -> int: return as_py(self._h_fd) - @property + @python_property def uuid(self) -> uuid.UUID: return self._uuid diff --git a/cuda_core/cuda/core/_memory/_managed_memory_resource.pyx b/cuda_core/cuda/core/_memory/_managed_memory_resource.pyx index f37a4f18ee1..fd000114847 100644 --- a/cuda_core/cuda/core/_memory/_managed_memory_resource.pyx +++ b/cuda_core/cuda/core/_memory/_managed_memory_resource.pyx @@ -3,6 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 from __future__ import annotations +from cuda.core._utils.properties import python_property from cuda.bindings cimport cydriver @@ -91,14 +92,14 @@ cdef class ManagedMemoryResource(_MemPool): def __init__(self, options=None): _MMR_init(self, options) - @property + @python_property def device_id(self) -> int: """The preferred device ordinal, or -1 if the preferred location is not a device.""" if self._pref_loc_type == "device": return self._pref_loc_id return -1 - @property + @python_property def preferred_location(self) -> tuple[ManagedMemoryLocationType, int | None] | None: """The preferred location for managed memory allocations. @@ -113,17 +114,17 @@ cdef class ManagedMemoryResource(_MemPool): return (ManagedMemoryLocationType.HOST, None) return (ManagedMemoryLocationType(self._pref_loc_type), self._pref_loc_id) - @property + @python_property def is_device_accessible(self) -> bool: """Return True. This memory resource provides device-accessible buffers.""" return True - @property + @python_property def is_host_accessible(self) -> bool: """Return True. This memory resource provides host-accessible buffers.""" return True - @property + @python_property def is_managed(self) -> bool: """Return True. This memory resource provides managed (unified) memory buffers.""" return True diff --git a/cuda_core/cuda/core/_memory/_memory_pool.pyx b/cuda_core/cuda/core/_memory/_memory_pool.pyx index 4da5e26ea92..3610c0ee5b5 100644 --- a/cuda_core/cuda/core/_memory/_memory_pool.pyx +++ b/cuda_core/cuda/core/_memory/_memory_pool.pyx @@ -3,6 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 from __future__ import annotations +from cuda.core._utils.properties import python_property from libc.limits cimport ULLONG_MAX from libc.stdint cimport uintptr_t @@ -50,56 +51,56 @@ cdef class _MemPoolAttributes: HANDLE_RETURN(cydriver.cuMemPoolGetAttribute(as_cu(self._h_pool), attr_enum, value)) return 0 - @property + @python_property def reuse_follow_event_dependencies(self): """Allow memory to be reused when there are event dependencies between streams.""" cdef int value self._getattribute(cydriver.CUmemPool_attribute.CU_MEMPOOL_ATTR_REUSE_FOLLOW_EVENT_DEPENDENCIES, &value) return bool(value) - @property + @python_property def reuse_allow_opportunistic(self): """Allow reuse of completed frees without dependencies.""" cdef int value self._getattribute(cydriver.CUmemPool_attribute.CU_MEMPOOL_ATTR_REUSE_ALLOW_OPPORTUNISTIC, &value) return bool(value) - @property + @python_property def reuse_allow_internal_dependencies(self): """Allow insertion of new stream dependencies for memory reuse.""" cdef int value self._getattribute(cydriver.CUmemPool_attribute.CU_MEMPOOL_ATTR_REUSE_ALLOW_INTERNAL_DEPENDENCIES, &value) return bool(value) - @property + @python_property def release_threshold(self): """Amount of reserved memory to hold before OS release.""" cdef cydriver.cuuint64_t value self._getattribute(cydriver.CUmemPool_attribute.CU_MEMPOOL_ATTR_RELEASE_THRESHOLD, &value) return int(value) - @property + @python_property def reserved_mem_current(self): """Current amount of backing memory allocated.""" cdef cydriver.cuuint64_t value self._getattribute(cydriver.CUmemPool_attribute.CU_MEMPOOL_ATTR_RESERVED_MEM_CURRENT, &value) return int(value) - @property + @python_property def reserved_mem_high(self): """High watermark of backing memory allocated.""" cdef cydriver.cuuint64_t value self._getattribute(cydriver.CUmemPool_attribute.CU_MEMPOOL_ATTR_RESERVED_MEM_HIGH, &value) return int(value) - @property + @python_property def used_mem_current(self): """Current amount of memory in use.""" cdef cydriver.cuuint64_t value self._getattribute(cydriver.CUmemPool_attribute.CU_MEMPOOL_ATTR_USED_MEM_CURRENT, &value) return int(value) - @property + @python_property def used_mem_high(self): """High watermark of memory in use.""" cdef cydriver.cuuint64_t value @@ -162,29 +163,29 @@ cdef class _MemPool(MemoryResource): cdef Stream s = Stream_accept(stream) _MP_deallocate(self, ptr, size, s) - @property + @python_property def attributes(self) -> _MemPoolAttributes: """Memory pool attributes.""" if self._attributes is None: self._attributes = _MemPoolAttributes._init(self._h_pool) return self._attributes - @property + @python_property def handle(self) -> object: """Handle to the underlying memory pool.""" return as_py(self._h_pool) - @property + @python_property def is_handle_owned(self) -> bool: """Whether the memory resource handle is owned. If False, ``close`` has no effect.""" return self._mempool_owned - @property + @python_property def is_ipc_enabled(self) -> bool: """Whether this memory resource has IPC enabled.""" return self._ipc_data is not None - @property + @python_property def is_mapped(self) -> bool: """ Whether this is a mapping of an IPC-enabled memory resource from @@ -192,7 +193,7 @@ cdef class _MemPool(MemoryResource): """ return self._ipc_data is not None and self._ipc_data._is_mapped - @property + @python_property def uuid(self) -> uuid.UUID | None: """ A universally unique identifier for this memory resource. Meaningful diff --git a/cuda_core/cuda/core/_memory/_pinned_memory_resource.pyx b/cuda_core/cuda/core/_memory/_pinned_memory_resource.pyx index a37a492d166..23ed4d8ae4d 100644 --- a/cuda_core/cuda/core/_memory/_pinned_memory_resource.pyx +++ b/cuda_core/cuda/core/_memory/_pinned_memory_resource.pyx @@ -160,22 +160,22 @@ cdef class PinnedMemoryResource(_MemPool): raise RuntimeError("Memory resource is not IPC-enabled") return self._ipc_data._alloc_handle - @property + @python_property def device_id(self) -> int: """Return -1. Pinned memory is host memory and is not associated with a specific device.""" return -1 - @property + @python_property def numa_id(self) -> int: """The host NUMA node ID used for pool placement, or -1 for OS-managed placement.""" return self._numa_id - @property + @python_property def is_device_accessible(self) -> bool: """Return True. This memory resource provides device-accessible buffers.""" return True - @property + @python_property def is_host_accessible(self) -> bool: """Return True. This memory resource provides host-accessible buffers.""" return True diff --git a/cuda_core/cuda/core/_memoryview.pyx b/cuda_core/cuda/core/_memoryview.pyx index 3ebde8dcff1..96daf581eb7 100644 --- a/cuda_core/cuda/core/_memoryview.pyx +++ b/cuda_core/cuda/core/_memoryview.pyx @@ -3,6 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 from __future__ import annotations +from cuda.core._utils.properties import python_property from ._dlpack cimport * from ._dlpack import classify_dl_device @@ -484,7 +485,7 @@ cdef class StridedMemoryView: _smv_get_dl_device(self, &device_type, &device_id) return (device_type, int(device_id)) - @property + @python_property def _layout(self) -> _StridedLayout: """ The layout of the tensor. For StridedMemoryView created from DLPack or CAI, @@ -492,25 +493,25 @@ cdef class StridedMemoryView: """ return self.get_layout() - @property + @python_property def size(self) -> int: return self.get_layout().get_volume() - @property + @python_property def shape(self) -> tuple[int, ...]: """ Shape of the tensor. """ return self.get_layout().get_shape_tuple() - @property + @python_property def strides(self) -> tuple[int, ...] | None: """ Strides of the tensor (in **counts**, not bytes). """ return self.get_layout().get_strides_tuple() - @property + @python_property def dtype(self) -> numpy.dtype | None: """ Data type of the tensor. diff --git a/cuda_core/cuda/core/_module.pyx b/cuda_core/cuda/core/_module.pyx index 4a8601f8573..f8ca4c0cd74 100644 --- a/cuda_core/cuda/core/_module.pyx +++ b/cuda_core/cuda/core/_module.pyx @@ -3,6 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 from __future__ import annotations +from cuda.core._utils.properties import python_property from libc.stddef cimport size_t @@ -102,7 +103,7 @@ cdef class KernelAttributes: """ return self._view_for_device(Device(device).device_id) - @property + @python_property def max_threads_per_block(self) -> int: """int : The maximum number of threads per block. This attribute is read-only.""" @@ -110,7 +111,7 @@ cdef class KernelAttributes: self._effective_device_id(), cydriver.CU_FUNC_ATTRIBUTE_MAX_THREADS_PER_BLOCK ) - @property + @python_property def shared_size_bytes(self) -> int: """int : The size in bytes of statically-allocated shared memory required by this function. This attribute is read-only.""" @@ -118,7 +119,7 @@ cdef class KernelAttributes: self._effective_device_id(), cydriver.CU_FUNC_ATTRIBUTE_SHARED_SIZE_BYTES ) - @property + @python_property def const_size_bytes(self) -> int: """int : The size in bytes of user-allocated constant memory required by this function. This attribute is read-only.""" @@ -126,7 +127,7 @@ cdef class KernelAttributes: self._effective_device_id(), cydriver.CU_FUNC_ATTRIBUTE_CONST_SIZE_BYTES ) - @property + @python_property def local_size_bytes(self) -> int: """int : The size in bytes of local memory used by each thread of this function. This attribute is read-only.""" @@ -134,7 +135,7 @@ cdef class KernelAttributes: self._effective_device_id(), cydriver.CU_FUNC_ATTRIBUTE_LOCAL_SIZE_BYTES ) - @property + @python_property def num_regs(self) -> int: """int : The number of registers used by each thread of this function. This attribute is read-only.""" @@ -142,7 +143,7 @@ cdef class KernelAttributes: self._effective_device_id(), cydriver.CU_FUNC_ATTRIBUTE_NUM_REGS ) - @property + @python_property def ptx_version(self) -> int: """int : The PTX virtual architecture version for which the function was compiled. This attribute is read-only.""" @@ -150,7 +151,7 @@ cdef class KernelAttributes: self._effective_device_id(), cydriver.CU_FUNC_ATTRIBUTE_PTX_VERSION ) - @property + @python_property def binary_version(self) -> int: """int : The binary architecture version for which the function was compiled. This attribute is read-only.""" @@ -158,7 +159,7 @@ cdef class KernelAttributes: self._effective_device_id(), cydriver.CU_FUNC_ATTRIBUTE_BINARY_VERSION ) - @property + @python_property def cache_mode_ca(self) -> bool: """bool : Whether the function has been compiled with user specified option "-Xptxas --dlcm=ca" set. This attribute is read-only.""" @@ -168,7 +169,7 @@ cdef class KernelAttributes: ) ) - @property + @python_property def max_dynamic_shared_size_bytes(self) -> int: """int : The maximum size in bytes of dynamically-allocated shared memory that can be used by this function.""" @@ -176,14 +177,14 @@ cdef class KernelAttributes: self._effective_device_id(), cydriver.CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES ) - @property + @python_property def preferred_shared_memory_carveout(self) -> int: """int : The shared memory carveout preference, in percent of the total shared memory.""" return self._get_cached_attribute( self._effective_device_id(), cydriver.CU_FUNC_ATTRIBUTE_PREFERRED_SHARED_MEMORY_CARVEOUT ) - @property + @python_property def cluster_size_must_be_set(self) -> bool: """bool : The kernel must launch with a valid cluster size specified. This attribute is read-only.""" @@ -193,28 +194,28 @@ cdef class KernelAttributes: ) ) - @property + @python_property def required_cluster_width(self) -> int: """int : The required cluster width in blocks.""" return self._get_cached_attribute( self._effective_device_id(), cydriver.CU_FUNC_ATTRIBUTE_REQUIRED_CLUSTER_WIDTH ) - @property + @python_property def required_cluster_height(self) -> int: """int : The required cluster height in blocks.""" return self._get_cached_attribute( self._effective_device_id(), cydriver.CU_FUNC_ATTRIBUTE_REQUIRED_CLUSTER_HEIGHT ) - @property + @python_property def required_cluster_depth(self) -> int: """int : The required cluster depth in blocks.""" return self._get_cached_attribute( self._effective_device_id(), cydriver.CU_FUNC_ATTRIBUTE_REQUIRED_CLUSTER_DEPTH ) - @property + @python_property def non_portable_cluster_size_allowed(self) -> bool: """bool : Whether the function can be launched with non-portable cluster size.""" return bool( @@ -224,7 +225,7 @@ cdef class KernelAttributes: ) ) - @property + @python_property def cluster_scheduling_policy_preference(self) -> int: """int : The block scheduling policy of a function.""" return self._get_cached_attribute( @@ -450,7 +451,7 @@ cdef class Kernel: ker._occupancy = None return ker - @property + @python_property def attributes(self) -> KernelAttributes: """Get the read-only attributes of this kernel.""" if self._attributes is None: @@ -480,26 +481,26 @@ cdef class Kernel: HANDLE_RETURN(err) return arg_pos, param_info_data - @property + @python_property def num_arguments(self) -> int: """int : The number of arguments of this function""" num_args, _ = self._get_arguments_info() return num_args - @property + @python_property def arguments_info(self) -> list[ParamInfo]: """list[ParamInfo]: (offset, size) for each argument of this function""" _, param_info = self._get_arguments_info(param_info=True) return param_info - @property + @python_property def occupancy(self) -> KernelOccupancy: """Get the occupancy information for launching this kernel.""" if self._occupancy is None: self._occupancy = KernelOccupancy._init(self._h_kernel) return self._occupancy - @property + @python_property def handle(self): """Return the underlying kernel handle object. @@ -510,7 +511,7 @@ cdef class Kernel: """ return as_py(self._h_kernel) - @property + @python_property def _handle(self): return self.handle @@ -773,27 +774,27 @@ cdef class ObjectCode: HANDLE_RETURN(get_last_error()) return Kernel._from_handle(h_kernel) - @property + @python_property def code(self) -> CodeTypeT: """Return the underlying code object.""" return self._module - @property + @python_property def name(self) -> str: """Return a human-readable name of this code object.""" return self._name - @property + @python_property def code_type(self) -> str: """Return the type of the underlying code object.""" return self._code_type - @property + @python_property def symbol_mapping(self) -> dict: """Return a copy of the symbol mapping dictionary.""" return dict(self._sym_map) - @property + @python_property def handle(self): """Return the underlying handle object. diff --git a/cuda_core/cuda/core/_program.pyx b/cuda_core/cuda/core/_program.pyx index 2ef38775d1a..19a137a932c 100644 --- a/cuda_core/cuda/core/_program.pyx +++ b/cuda_core/cuda/core/_program.pyx @@ -8,6 +8,7 @@ This module provides :class:`Program` for compiling source code into """ from __future__ import annotations +from cuda.core._utils.properties import python_property from dataclasses import dataclass import threading @@ -224,7 +225,7 @@ cdef class Program: cache[key] = compiled return compiled - @property + @python_property def pch_status(self) -> PCHStatusType | None: """PCH creation outcome from the most recent :meth:`compile` call. @@ -252,12 +253,12 @@ cdef class Program: return None return PCHStatusType(self._pch_status) - @property + @python_property def backend(self) -> CompilerBackendType: """Return this Program instance's underlying :class:`CompilerBackendType`.""" return CompilerBackendType(self._backend) - @property + @python_property def handle(self) -> ProgramHandleT: """Return the underlying handle object. diff --git a/cuda_core/cuda/core/_stream.pyx b/cuda_core/cuda/core/_stream.pyx index f487a0a53e5..89d4a4361ef 100644 --- a/cuda_core/cuda/core/_stream.pyx +++ b/cuda_core/cuda/core/_stream.pyx @@ -3,6 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 from __future__ import annotations +from cuda.core._utils.properties import python_property from libc.stdint cimport uintptr_t, INT32_MIN from libc.stdlib cimport strtol, getenv @@ -213,7 +214,7 @@ cdef class Stream: Stream_ensure_ctx(self) return f"" - @property + @python_property def handle(self) -> cuda.bindings.driver.CUstream: """Return the underlying ``CUstream`` object. @@ -224,7 +225,7 @@ cdef class Stream: """ return as_py(self._h_stream) - @property + @python_property def is_nonblocking(self) -> bool: """Return True if this is a nonblocking stream, otherwise False.""" cdef unsigned int flags @@ -234,7 +235,7 @@ cdef class Stream: self._nonblocking = flags & cydriver.CUstream_flags.CU_STREAM_NON_BLOCKING return bool(self._nonblocking) - @property + @python_property def priority(self) -> int: """Return the stream priority.""" cdef int prio @@ -332,7 +333,7 @@ cdef class Stream: # TODO: support flags other than 0? HANDLE_RETURN(cydriver.cuStreamWaitEvent(as_cu(self._h_stream), as_cu(h_event), 0)) - @property + @python_property def device(self) -> Device: """Return the :obj:`~_device.Device` singleton associated with this stream. @@ -347,14 +348,14 @@ cdef class Stream: Stream_ensure_ctx_device(self) return Device(self._device_id) - @property + @python_property def context(self) -> Context: """Return the :obj:`~_context.Context` associated with this stream.""" Stream_ensure_ctx(self) Stream_ensure_ctx_device(self) return Context._from_handle(Context, self._h_context, self._device_id) - @property + @python_property def resources(self): """Query the hardware resources provisioned for this stream's context. diff --git a/cuda_core/cuda/core/_tensor_map.pyx b/cuda_core/cuda/core/_tensor_map.pyx index e1e5daa9fd7..1b9f8014c72 100644 --- a/cuda_core/cuda/core/_tensor_map.pyx +++ b/cuda_core/cuda/core/_tensor_map.pyx @@ -2,6 +2,7 @@ # # SPDX-License-Identifier: Apache-2.0 +from cuda.core._utils.properties import python_property from libc.stdint cimport intptr_t, int64_t, uint8_t, uint16_t, uint32_t, uint64_t from libc.stddef cimport size_t from cuda.bindings cimport cydriver @@ -500,7 +501,7 @@ cdef class TensorMapDescriptor: f"but current device is {current_dev_id}") return 0 - @property + @python_property def device(self): """Return the :obj:`~cuda.core.Device` associated with this descriptor.""" if self._device_id >= 0: diff --git a/cuda_core/cuda/core/_utils/properties.py b/cuda_core/cuda/core/_utils/properties.py index 53f35d1f1ba..a5e1928d909 100644 --- a/cuda_core/cuda/core/_utils/properties.py +++ b/cuda_core/cuda/core/_utils/properties.py @@ -3,6 +3,6 @@ # SPDX-License-Identifier: Apache-2.0 -def python_property(func): +def python_property(fget=None, fset=None, fdel=None, doc=None): """Create a Python property without Cython's cdef-class @property lowering.""" - return property(func) + return property(fget, fset, fdel, doc) diff --git a/cuda_core/cuda/core/graph/_graph_definition.pyx b/cuda_core/cuda/core/graph/_graph_definition.pyx index 413a17368d8..0e1627deabc 100644 --- a/cuda_core/cuda/core/graph/_graph_definition.pyx +++ b/cuda_core/cuda/core/graph/_graph_definition.pyx @@ -5,6 +5,7 @@ """GraphDefinition: explicit CUDA graph definition.""" from __future__ import annotations +from cuda.core._utils.properties import python_property from libc.stddef cimport size_t @@ -63,7 +64,7 @@ cdef class GraphCondition: def __hash__(self) -> int: return hash(self._c_handle) - @property + @python_property def handle(self) -> driver.CUgraphConditionalHandle: """The raw CUgraphConditionalHandle as an int.""" return self._c_handle @@ -102,7 +103,7 @@ cdef class GraphDefinition: def __hash__(self) -> int: return hash(as_intptr(self._h_graph)) - @property + @python_property def _entry(self) -> "GraphNode": """Return the internal entry-point GraphNode (no dependencies).""" cdef GraphNode n = GraphNode.__new__(GraphNode) @@ -370,7 +371,7 @@ cdef class GraphDefinition: for i in range(num_edges) } - @property + @python_property def handle(self) -> driver.CUgraph: """Return the underlying driver CUgraph handle.""" return as_py(self._h_graph) diff --git a/cuda_core/cuda/core/graph/_graph_node.pyx b/cuda_core/cuda/core/graph/_graph_node.pyx index a5577d134de..f6cb8f1d612 100644 --- a/cuda_core/cuda/core/graph/_graph_node.pyx +++ b/cuda_core/cuda/core/graph/_graph_node.pyx @@ -5,6 +5,7 @@ """GraphNode base class — factory, properties, and builder methods.""" from __future__ import annotations +from cuda.core._utils.properties import python_property from cpython.ref cimport Py_INCREF @@ -108,7 +109,7 @@ cdef class GraphNode: cdef GraphHandle g = graph_node_get_graph(self._h_node) return hash((as_intptr(self._h_node), as_intptr(g))) - @property + @python_property def type(self): """Return the CUDA graph node type. @@ -125,12 +126,12 @@ cdef class GraphNode: HANDLE_RETURN(cydriver.cuGraphNodeGetType(node, &node_type)) return driver.CUgraphNodeType(node_type) - @property + @python_property def graph(self) -> "GraphDefinition": """Return the GraphDefinition this node belongs to.""" return GraphDefinition._from_handle(graph_node_get_graph(self._h_node)) - @property + @python_property def handle(self) -> driver.CUgraphNode: """Return the underlying driver CUgraphNode handle. @@ -138,7 +139,7 @@ cdef class GraphNode: """ return as_py(self._h_node) - @property + @python_property def is_valid(self): """Whether this node is valid (not destroyed). @@ -161,28 +162,28 @@ cdef class GraphNode: _node_registry.pop(self._h_node.get(), None) invalidate_graph_node(self._h_node) - @property def pred(self): """A mutable set-like view of this node's predecessors.""" return AdjacencySetProxy(self, False) - @pred.setter - def pred(self, value): + def _set_pred(self, value): p = AdjacencySetProxy(self, False) p.clear() p.update(value) - @property + pred = python_property(pred, _set_pred) + def succ(self): """A mutable set-like view of this node's successors.""" return AdjacencySetProxy(self, True) - @succ.setter - def succ(self, value): + def _set_succ(self, value): s = AdjacencySetProxy(self, True) s.clear() s.update(value) + succ = python_property(succ, _set_succ) + def launch(self, config: LaunchConfig, kernel: Kernel, *args) -> KernelNode: """Add a kernel launch node depending on this node. diff --git a/cuda_core/cuda/core/graph/_subclasses.pyx b/cuda_core/cuda/core/graph/_subclasses.pyx index 6d15ebc3ff7..57999a44364 100644 --- a/cuda_core/cuda/core/graph/_subclasses.pyx +++ b/cuda_core/cuda/core/graph/_subclasses.pyx @@ -5,6 +5,7 @@ """GraphNode subclasses — EmptyNode through SwitchNode.""" from __future__ import annotations +from cuda.core._utils.properties import python_property from libc.stddef cimport size_t from libc.stdint cimport uintptr_t @@ -128,27 +129,27 @@ cdef class KernelNode(GraphNode): return (f"") - @property + @python_property def grid(self) -> tuple: """Grid dimensions as a 3-tuple (gridDimX, gridDimY, gridDimZ).""" return self._grid - @property + @python_property def block(self) -> tuple: """Block dimensions as a 3-tuple (blockDimX, blockDimY, blockDimZ).""" return self._block - @property + @python_property def shmem_size(self) -> int: """Dynamic shared memory size in bytes.""" return self._shmem_size - @property + @python_property def kernel(self) -> Kernel: """The Kernel object for this launch node.""" return Kernel._from_handle(self._h_kernel) - @property + @python_property def config(self) -> LaunchConfig: """A LaunchConfig reconstructed from this node's grid, block, and shmem_size. @@ -226,27 +227,27 @@ cdef class AllocNode(GraphNode): return (f"") - @property + @python_property def dptr(self) -> int: """The device pointer for the allocation.""" return self._dptr - @property + @python_property def bytesize(self) -> int: """The number of bytes allocated.""" return self._bytesize - @property + @python_property def device_id(self) -> int: """The device on which the allocation was made.""" return self._device_id - @property + @python_property def memory_type(self) -> str: """The type of memory: ``"device"``, ``"host"``, or ``"managed"``.""" return self._memory_type - @property + @python_property def peer_access(self) -> tuple: """Device IDs with read-write access to this allocation.""" return self._peer_access @@ -282,7 +283,7 @@ cdef class FreeNode(GraphNode): def __repr__(self) -> str: return f"" - @property + @python_property def dptr(self) -> int: """The device pointer being freed.""" return self._dptr @@ -338,32 +339,32 @@ cdef class MemsetNode(GraphNode): return (f"") - @property + @python_property def dptr(self) -> int: """The destination device pointer.""" return self._dptr - @property + @python_property def value(self) -> int: """The fill value.""" return self._value - @property + @python_property def element_size(self) -> int: """Element size in bytes (1, 2, or 4).""" return self._element_size - @property + @python_property def width(self) -> int: """Width of the row in elements.""" return self._width - @property + @python_property def height(self) -> int: """Number of rows.""" return self._height - @property + @python_property def pitch(self) -> int: """Pitch in bytes (unused if height is 1).""" return self._pitch @@ -426,17 +427,17 @@ cdef class MemcpyNode(GraphNode): return (f"") - @property + @python_property def dst(self) -> int: """The destination pointer.""" return self._dst - @property + @python_property def src(self) -> int: """The source pointer.""" return self._src - @property + @python_property def size(self) -> int: """The number of bytes copied.""" return self._size @@ -475,7 +476,7 @@ cdef class ChildGraphNode(GraphNode): return (f"") - @property + @python_property def child_graph(self) -> "GraphDefinition": """The embedded graph definition (non-owning wrapper).""" return GraphDefinition._from_handle(self._h_child_graph) @@ -513,7 +514,7 @@ cdef class EventRecordNode(GraphNode): return (f"") - @property + @python_property def event(self) -> Event: """The event being recorded.""" return Event._from_handle(self._h_event) @@ -551,7 +552,7 @@ cdef class EventWaitNode(GraphNode): return (f"") - @property + @python_property def event(self) -> Event: """The event being waited on.""" return Event._from_handle(self._h_event) @@ -601,7 +602,7 @@ cdef class HostCallbackNode(GraphNode): return (f"self._fn:x}>") - @property + @python_property def callback(self): """The Python callable, or None for ctypes function pointer callbacks.""" return self._callable @@ -681,12 +682,12 @@ cdef class ConditionalNode(GraphNode): def __repr__(self) -> str: return f"" - @property + @python_property def condition(self) -> GraphCondition | None: """The condition variable controlling execution.""" return self._condition - @property + @python_property def cond_type(self) -> GraphConditionalType | None: """The conditional type: GraphConditionalType.IF, .WHILE, or .SWITCH @@ -702,7 +703,7 @@ cdef class ConditionalNode(GraphNode): else: return GraphConditionalType("switch") - @property + @python_property def branches(self) -> tuple: """The body graphs for each branch as a tuple of GraphDefinition. @@ -719,7 +720,7 @@ cdef class IfNode(ConditionalNode): return (f"self._condition._c_handle:x}>") - @property + @python_property def then(self) -> "GraphDefinition": """The 'then' branch graph.""" return self._branches[0] @@ -732,12 +733,12 @@ cdef class IfElseNode(ConditionalNode): return (f"self._condition._c_handle:x}>") - @property + @python_property def then(self) -> "GraphDefinition": """The ``then`` branch graph (executed when condition is non-zero).""" return self._branches[0] - @property + @python_property def else_(self) -> "GraphDefinition": """The ``else`` branch graph (executed when condition is zero).""" return self._branches[1] @@ -750,7 +751,7 @@ cdef class WhileNode(ConditionalNode): return (f"self._condition._c_handle:x}>") - @property + @python_property def body(self) -> "GraphDefinition": """The loop body graph.""" return self._branches[0] diff --git a/cuda_core/cuda/core/system/_clock.pxi b/cuda_core/cuda/core/system/_clock.pxi index a1fd940fc43..d9e7067b0e2 100644 --- a/cuda_core/cuda/core/system/_clock.pxi +++ b/cuda_core/cuda/core/system/_clock.pxi @@ -41,21 +41,21 @@ cdef class ClockOffsets: def __init__(self, clock_offset: nvml.ClockOffset): self._clock_offset = clock_offset - @property + @python_property def clock_offset_mhz(self) -> int: """ The current clock offset in MHz. """ return self._clock_offset.clock_offset_m_hz - @property + @python_property def max_offset_mhz(self) -> int: """ The maximum clock offset in MHz. """ return self._clock_offset.max_clock_offset_m_hz - @property + @python_property def min_offset_mhz(self) -> int: """ The minimum clock offset in MHz. diff --git a/cuda_core/cuda/core/system/_cooler.pxi b/cuda_core/cuda/core/system/_cooler.pxi index b24f9252822..5afa02c9110 100644 --- a/cuda_core/cuda/core/system/_cooler.pxi +++ b/cuda_core/cuda/core/system/_cooler.pxi @@ -23,7 +23,7 @@ cdef class CoolerInfo: def __init__(self, cooler_info: nvml.CoolerInfo): self._cooler_info = cooler_info - @property + @python_property def signal_type(self) -> CoolerControl | None: """ The cooler's control signal characteristics. @@ -32,7 +32,7 @@ cdef class CoolerInfo: """ return _COOLER_CONTROL_MAPPING.get(self._cooler_info.signal_type, None) - @property + @python_property def target(self) -> list[CoolerTarget]: """ The target that cooler controls. diff --git a/cuda_core/cuda/core/system/_device.pyx b/cuda_core/cuda/core/system/_device.pyx index dc6661d990e..eed7d91bc3e 100644 --- a/cuda_core/cuda/core/system/_device.pyx +++ b/cuda_core/cuda/core/system/_device.pyx @@ -207,7 +207,7 @@ cdef class Device: ######################################################################### # BASIC PROPERTIES - @property + @python_property def index(self) -> int: """ The NVML index of this device. @@ -226,7 +226,7 @@ cdef class Device: """ return nvml.device_get_index(self._handle) - @property + @python_property def uuid(self) -> str: """ Retrieves the globally unique immutable UUID associated with this @@ -239,7 +239,7 @@ cdef class Device: """ return nvml.device_get_uuid(self._handle) - @property + @python_property def uuid_without_prefix(self) -> str: """ Retrieves the globally unique immutable UUID associated with this @@ -253,7 +253,7 @@ cdef class Device: # NVML UUIDs have a `gpu-` or `mig-` prefix. We remove that here. return nvml.device_get_uuid(self._handle)[4:] - @property + @python_property def pci_bus_id(self) -> str: """ Retrieves the PCI bus ID of this device. @@ -269,7 +269,7 @@ cdef class Device: """ return nvml.device_get_numa_node_id(self._handle) - @property + @python_property def arch(self) -> DeviceArch: """ :obj:`~DeviceArch` device architecture. @@ -284,14 +284,14 @@ cdef class Device: except ValueError: return DeviceArch.UNKNOWN - @property + @python_property def name(self) -> str: """ Name of the device, e.g.: `"Tesla V100-SXM2-32GB"` """ return nvml.device_get_name(self._handle) - @property + @python_property def brand(self) -> str: """ The brand of the device. @@ -300,7 +300,7 @@ cdef class Device: """ return _BRAND_TYPE_MAPPING.get(nvml.device_get_brand(self._handle), "Unknown") - @property + @python_property def serial(self) -> str: """ Retrieves the globally unique board serial number associated with this @@ -310,7 +310,7 @@ cdef class Device: """ return nvml.device_get_serial(self._handle) - @property + @python_property def module_id(self) -> int: """ Get a unique identifier for the device module on the baseboard. @@ -321,7 +321,7 @@ cdef class Device: """ return nvml.device_get_module_id(self._handle) - @property + @python_property def minor_number(self) -> int: """ The minor number of this device. @@ -340,7 +340,6 @@ cdef class Device: """ return bool(nvml.device_get_c2c_mode_info_v(self._handle).is_c2c_enabled) - @property def is_persistence_mode_enabled(self) -> bool: """ Whether persistence mode is enabled for this device. @@ -349,14 +348,17 @@ cdef class Device: """ return nvml.device_get_persistence_mode(self._handle) == nvml.EnableState.FEATURE_ENABLED - @is_persistence_mode_enabled.setter - def is_persistence_mode_enabled(self, enabled: bool) -> None: + def _set_is_persistence_mode_enabled(self, enabled: bool) -> None: nvml.device_set_persistence_mode( self._handle, nvml.EnableState.FEATURE_ENABLED if enabled else nvml.EnableState.FEATURE_DISABLED ) - @property + is_persistence_mode_enabled = python_property( + is_persistence_mode_enabled, _set_is_persistence_mode_enabled + ) + + @python_property def cuda_compute_capability(self) -> tuple[int, int]: """ CUDA compute capability of the device, e.g.: `(7, 0)` for a Tesla V100. @@ -421,7 +423,7 @@ cdef class Device: ######################################################################### # ADDRESSING MODE - @property + @python_property def addressing_mode(self) -> AddressingMode | None: """ Get the :obj:`~AddressingMode` of the device. @@ -431,7 +433,7 @@ cdef class Device: ######################################################################### # MIG (MULTI-INSTANCE GPU) DEVICES - @property + @python_property def mig(self) -> MigInfo: """ Get :obj:`~MigInfo` accessor for MIG (Multi-Instance GPU) information. @@ -645,7 +647,7 @@ cdef class Device: # COOLER # See external class definitions in _cooler.pxi - @property + @python_property def cooler(self) -> CoolerInfo: """ :obj:`~_device.CoolerInfo` object with cooler information for the device. @@ -669,7 +671,7 @@ cdef class Device: ######################################################################### # DISPLAY - @property + @python_property def is_display_connected(self) -> bool: """ The display mode for this device. @@ -679,7 +681,7 @@ cdef class Device: """ return nvml.device_get_display_mode(self._handle) == nvml.EnableState.FEATURE_ENABLED - @property + @python_property def is_display_active(self) -> bool: """ The display active status for this device. @@ -773,7 +775,7 @@ cdef class Device: raise ValueError(f"Fan index {fan} is out of range [0, {self.num_fans})") return FanInfo(self._handle, fan) - @property + @python_property def num_fans(self) -> int: """ The number of fans on the device. @@ -838,7 +840,7 @@ cdef class Device: # INFOROM # See external class definitions in _inforom.pxi - @property + @python_property def inforom(self) -> InforomInfo: """ :obj:`~_device.InforomInfo` object with InfoROM information. @@ -851,7 +853,7 @@ cdef class Device: # MEMORY # See external class definitions in _memory.pxi - @property + @python_property def bar1_memory_info(self) -> BAR1MemoryInfo: """ :obj:`~_device.BAR1MemoryInfo` object with BAR1 memory information. @@ -862,7 +864,7 @@ cdef class Device: """ return BAR1MemoryInfo(nvml.device_get_bar1_memory_info(self._handle)) - @property + @python_property def memory_info(self) -> MemoryInfo: """ :obj:`~_device.MemoryInfo` object with memory information. @@ -887,7 +889,7 @@ cdef class Device: # PCI INFO # See external class definitions in _pci_info.pxi - @property + @python_property def pci_info(self) -> PciInfo: """ :obj:`~_device.PciInfo` object with the PCI attributes of this device. @@ -898,7 +900,7 @@ cdef class Device: # PERFORMANCE # See external class definitions in _performance.pxi - @property + @python_property def performance_state(self) -> int | None: """ The current performance state of the device. @@ -914,14 +916,14 @@ cdef class Device: """ return _pstate_to_int(nvml.device_get_performance_state(self._handle)) - @property + @python_property def dynamic_pstates_info(self) -> GpuDynamicPstatesInfo: """ :obj:`~_device.GpuDynamicPstatesInfo` object with performance monitor samples from the associated subdevice. """ return GpuDynamicPstatesInfo(nvml.device_get_dynamic_pstates_info(self._handle)) - @property + @python_property def supported_pstates(self) -> list[int]: """ Get all supported Performance States (P-States) for the device. @@ -953,7 +955,7 @@ cdef class Device: # PROCESS # See external class definitions in _process.pxi - @property + @python_property def compute_running_processes(self) -> list[ProcessInfo]: """ Get information about processes with a compute context on a device @@ -980,7 +982,7 @@ cdef class Device: # REPAIR STATUS # See external class definitions in _repair_status.pxi - @property + @python_property def repair_status(self) -> RepairStatus: """ :obj:`~_device.RepairStatus` object with TPC/Channel repair status. @@ -993,7 +995,7 @@ cdef class Device: # TEMPERATURE # See external class definitions in _temperature.pxi - @property + @python_property def temperature(self) -> Temperature: """ :obj:`~_device.Temperature` object with temperature information for the device. @@ -1035,7 +1037,7 @@ cdef class Device: ####################################################################### # UTILIZATION - @property + @python_property def utilization(self) -> Utilization: """ Retrieves the current :obj:`~Utilization` rates for the device's major diff --git a/cuda_core/cuda/core/system/_device_attributes.pxi b/cuda_core/cuda/core/system/_device_attributes.pxi index c18a7be35df..b95729e5a53 100644 --- a/cuda_core/cuda/core/system/_device_attributes.pxi +++ b/cuda_core/cuda/core/system/_device_attributes.pxi @@ -10,63 +10,63 @@ cdef class DeviceAttributes: def __init__(self, attributes: nvml.DeviceAttributes): self._attributes = attributes - @property + @python_property def multiprocessor_count(self) -> int: """ The streaming multiprocessor count """ return self._attributes.multiprocessor_count - @property + @python_property def shared_copy_engine_count(self) -> int: """ The shared copy engine count """ return self._attributes.shared_copy_engine_count - @property + @python_property def shared_decoder_count(self) -> int: """ The shared decoder engine count """ return self._attributes.shared_decoder_count - @property + @python_property def shared_encoder_count(self) -> int: """ The shared encoder engine count """ return self._attributes.shared_encoder_count - @property + @python_property def shared_jpeg_count(self) -> int: """ The shared JPEG engine count """ return self._attributes.shared_jpeg_count - @property + @python_property def shared_ofa_count(self) -> int: """ The shared optical flow accelerator (OFA) engine count """ return self._attributes.shared_ofa_count - @property + @python_property def gpu_instance_slice_count(self) -> int: """ The GPU instance slice count """ return self._attributes.gpu_instance_slice_count - @property + @python_property def compute_instance_slice_count(self) -> int: """ The compute instance slice count """ return self._attributes.compute_instance_slice_count - @property + @python_property def memory_size_mb(self) -> int: """ Device memory size in MiB diff --git a/cuda_core/cuda/core/system/_event.pxi b/cuda_core/cuda/core/system/_event.pxi index a1f92338037..e024ef1de10 100644 --- a/cuda_core/cuda/core/system/_event.pxi +++ b/cuda_core/cuda/core/system/_event.pxi @@ -32,7 +32,7 @@ cdef class EventData: def __init__(self, event_data: nvml.EventData): self._event_data = event_data - @property + @python_property def device(self) -> Device: """ The device on which the event occurred. @@ -41,14 +41,14 @@ cdef class EventData: device._handle = self._event_data.device return device - @property + @python_property def event_type(self) -> EventType: """ The type of event that was triggered. """ return _EVENT_TYPE_MAPPING[self._event_data.event_type] - @property + @python_property def event_data(self) -> int: """ Returns Xid error for the device in the event of @@ -60,7 +60,7 @@ cdef class EventData: raise ValueError("event_data is only available for Xid critical error events.") return self._event_data.event_data - @property + @python_property def gpu_instance_id(self) -> int: """ The GPU instance ID for MIG devices. @@ -73,7 +73,7 @@ cdef class EventData: raise ValueError("gpu_instance_id is only available for Xid critical error events.") return self._event_data.gpu_instance_id - @property + @python_property def compute_instance_id(self) -> int: """ The Compute instance ID for MIG devices. diff --git a/cuda_core/cuda/core/system/_fan.pxi b/cuda_core/cuda/core/system/_fan.pxi index 2bedc745202..f1fe7648dbe 100644 --- a/cuda_core/cuda/core/system/_fan.pxi +++ b/cuda_core/cuda/core/system/_fan.pxi @@ -21,7 +21,6 @@ cdef class FanInfo: self._handle = handle self._fan = fan - @property def speed(self) -> int: """ Get/set the intended operating speed of the device's fan. @@ -37,11 +36,12 @@ cdef class FanInfo: """ return nvml.device_get_fan_speed_v2(self._handle, self._fan) - @speed.setter - def speed(self, speed: int): + def _set_speed(self, speed: int): nvml.device_set_fan_speed_v2(self._handle, self._fan, speed) - @property + speed = python_property(speed, _set_speed) + + @python_property def speed_rpm(self) -> int: """ The intended operating speed of the device's fan in rotations per minute @@ -57,7 +57,7 @@ cdef class FanInfo: """ return nvml.device_get_fan_speed_rpm(self._handle, self._fan) - @property + @python_property def target_speed(self) -> int: """ Retrieves the intended target speed of the device's specified fan. @@ -74,7 +74,7 @@ cdef class FanInfo: """ return nvml.device_get_target_fan_speed(self._handle, self._fan) - @property + @python_property def min_max_speed(self) -> tuple[int, int]: """ Retrieves the minimum and maximum fan speed all of the device's fans. @@ -88,7 +88,7 @@ cdef class FanInfo: """ return nvml.device_get_min_max_fan_speed(self._handle) - @property + @python_property def control_policy(self) -> FanControlPolicy: """ The current fan control policy. diff --git a/cuda_core/cuda/core/system/_field_values.pxi b/cuda_core/cuda/core/system/_field_values.pxi index 4a9e5cc748f..558fe7cd1dc 100644 --- a/cuda_core/cuda/core/system/_field_values.pxi +++ b/cuda_core/cuda/core/system/_field_values.pxi @@ -15,14 +15,14 @@ cdef class FieldValue: assert len(field_value) == 1 self._field_value = field_value - @property + @python_property def field_id(self) -> FieldId: """ The field ID. """ return FieldId(self._field_value.field_id) - @property + @python_property def scope_id(self) -> int: """ The scope ID. @@ -30,7 +30,7 @@ cdef class FieldValue: # Explicit int() cast required because this is a Numpy type return int(self._field_value.scope_id) - @property + @python_property def timestamp(self) -> int: """ The CPU timestamp (in microseconds since 1970) at which the value was @@ -39,7 +39,7 @@ cdef class FieldValue: # Explicit int() cast required because this is a Numpy type return int(self._field_value.timestamp) - @property + @python_property def latency_usec(self) -> int: """ How long this field value took to update (in usec) within NVML. This may @@ -49,7 +49,7 @@ cdef class FieldValue: # Explicit int() cast required because this is a Numpy type return int(self._field_value.latency_usec) - @property + @python_property def value(self) -> int | float: """ The field value. diff --git a/cuda_core/cuda/core/system/_inforom.pxi b/cuda_core/cuda/core/system/_inforom.pxi index f5ab70408d2..007a48c1687 100644 --- a/cuda_core/cuda/core/system/_inforom.pxi +++ b/cuda_core/cuda/core/system/_inforom.pxi @@ -45,7 +45,7 @@ cdef class InforomInfo: ) from None return nvml.device_get_inforom_version(self._device._handle, inforom_enum) - @property + @python_property def image_version(self) -> str: """ Retrieves the global InfoROM image version. @@ -63,7 +63,7 @@ cdef class InforomInfo: """ return nvml.device_get_inforom_image_version(self._device._handle) - @property + @python_property def configuration_checksum(self) -> int: """ Retrieves the checksum of the configuration stored in the device's InfoROM. @@ -95,7 +95,7 @@ cdef class InforomInfo: """ nvml.device_validate_inforom(self._device._handle) - @property + @python_property def bbx_flush_time(self) -> tuple[int, int]: """ Retrieves the timestamp and duration of the last flush of the BBX @@ -111,7 +111,7 @@ cdef class InforomInfo: """ return nvml.device_get_last_bbx_flush_time(self._device._handle) - @property + @python_property def board_part_number(self) -> str: """ The device board part number which is programmed into the board's InfoROM. diff --git a/cuda_core/cuda/core/system/_memory.pxi b/cuda_core/cuda/core/system/_memory.pxi index e9142a30ec4..5378d9c3bb0 100644 --- a/cuda_core/cuda/core/system/_memory.pxi +++ b/cuda_core/cuda/core/system/_memory.pxi @@ -12,28 +12,28 @@ cdef class MemoryInfo: def __init__(self, memory_info: nvml.Memory_v2): self._memory_info = memory_info - @property + @python_property def free(self) -> int: """ Unallocated device memory (in bytes) """ return self._memory_info.free - @property + @python_property def total(self) -> int: """ Total physical device memory (in bytes) """ return self._memory_info.total - @property + @python_property def used(self) -> int: """ Allocated device memory (in bytes) """ return self._memory_info.used - @property + @python_property def reserved(self) -> int: """ Device memory (in bytes) reserved for system use (driver or firmware) @@ -50,21 +50,21 @@ cdef class BAR1MemoryInfo(MemoryInfo): def __init__(self, memory_info: nvml.BAR1Memory): self._memory_info = memory_info - @property + @python_property def free(self) -> int: """ Unallocated BAR1 memory (in bytes) """ return self._memory_info.bar1_free - @property + @python_property def total(self) -> int: """ Total BAR1 memory (in bytes) """ return self._memory_info.bar1_total - @property + @python_property def used(self) -> int: """ Allocated used memory (in bytes) diff --git a/cuda_core/cuda/core/system/_mig.pxi b/cuda_core/cuda/core/system/_mig.pxi index 8fa6b9d780d..2866c0ac18e 100644 --- a/cuda_core/cuda/core/system/_mig.pxi +++ b/cuda_core/cuda/core/system/_mig.pxi @@ -12,7 +12,7 @@ cdef class MigInfo: def __init__(self, device: Device): self._device = device - @property + @python_property def is_mig_device(self) -> bool: """ Whether this device is a MIG (Multi-Instance GPU) device. @@ -26,7 +26,6 @@ cdef class MigInfo: """ return bool(nvml.device_is_mig_device_handle(self._device._handle)) - @property def mode(self) -> bool: """ Get current MIG mode for the device. @@ -44,8 +43,7 @@ cdef class MigInfo: current, _ = nvml.device_get_mig_mode(self._device._handle) return current == nvml.EnableState.FEATURE_ENABLED - @mode.setter - def mode(self, mode: bool): + def _set_mode(self, mode: bool): """ Set the MIG mode for the device. @@ -64,7 +62,9 @@ cdef class MigInfo: nvml.EnableState.FEATURE_ENABLED if mode else nvml.EnableState.FEATURE_DISABLED ) - @property + mode = python_property(mode, _set_mode) + + @python_property def pending_mode(self) -> bool: """ Get pending MIG mode for the device. @@ -84,7 +84,7 @@ cdef class MigInfo: _, pending = nvml.device_get_mig_mode(self._device._handle) return pending == nvml.EnableState.FEATURE_ENABLED - @property + @python_property def device_count(self) -> int: """ Get the maximum number of MIG devices that can exist under this device. @@ -100,7 +100,7 @@ cdef class MigInfo: """ return nvml.device_get_max_mig_device_count(self._device._handle) - @property + @python_property def parent(self) -> Device: """ For MIG devices, get the parent GPU device. diff --git a/cuda_core/cuda/core/system/_nvlink.pxi b/cuda_core/cuda/core/system/_nvlink.pxi index ad246b8364f..3a9942d5abf 100644 --- a/cuda_core/cuda/core/system/_nvlink.pxi +++ b/cuda_core/cuda/core/system/_nvlink.pxi @@ -25,7 +25,7 @@ cdef class NvlinkInfo: self._device = device self._link = link - @property + @python_property def version(self) -> tuple[int, int]: """ Retrieves the NvLink version for the device and link. @@ -45,7 +45,7 @@ cdef class NvlinkInfo: except KeyError: raise RuntimeError(f"Unknown NvLink version {version} returned for device") from None - @property + @python_property def state(self) -> bool: """ Retrieves the state of the device's Nvlink for the device and link specified. diff --git a/cuda_core/cuda/core/system/_pci_info.pxi b/cuda_core/cuda/core/system/_pci_info.pxi index 55922b767ad..3b4e44e73cb 100644 --- a/cuda_core/cuda/core/system/_pci_info.pxi +++ b/cuda_core/cuda/core/system/_pci_info.pxi @@ -15,70 +15,70 @@ cdef class PciInfo: self._pci_info_ext = pci_info_ext self._handle = handle - @property + @python_property def bus(self) -> int: """ The bus on which the device resides, 0 to 255 """ return self._pci_info_ext.bus - @property + @python_property def bus_id(self) -> str: """ The tuple domain:bus:device.function PCI identifier string """ return self._pci_info_ext.bus_id - @property + @python_property def device(self) -> int: """ The device's id on the bus, 0 to 31 """ return self._pci_info_ext.device_ - @property + @python_property def domain(self) -> int: """ The PCI domain on which the device's bus resides, 0 to 0xffffffff """ return self._pci_info_ext.domain - @property + @python_property def vendor_id(self) -> int: """ The PCI vendor id of the device """ return self._pci_info_ext.pci_device_id & 0xFFFF - @property + @python_property def device_id(self) -> int: """ The PCI device id of the device """ return self._pci_info_ext.pci_device_id >> 16 - @property + @python_property def subsystem_id(self) -> int: """ The subsystem device ID """ return self._pci_info_ext.pci_sub_system_id - @property + @python_property def base_class(self) -> int: """ The 8-bit PCI base class code """ return self._pci_info_ext.base_class - @property + @python_property def sub_class(self) -> int: """ The 8-bit PCI sub class code """ return self._pci_info_ext.sub_class - @property + @python_property def link_generation(self) -> int: """ Retrieve the maximum PCIe link generation possible with this device and system. @@ -91,7 +91,7 @@ cdef class PciInfo: """ return nvml.device_get_max_pcie_link_generation(self._handle) - @property + @python_property def max_link_generation(self) -> int: """ Retrieve the maximum PCIe link generation supported by this GPU device. @@ -100,7 +100,7 @@ cdef class PciInfo: """ return nvml.device_get_gpu_max_pcie_link_generation(self._handle) - @property + @python_property def max_link_width(self) -> int: """ Retrieve the maximum PCIe link width possible with this device and system. @@ -113,7 +113,7 @@ cdef class PciInfo: """ return nvml.device_get_max_pcie_link_width(self._handle) - @property + @python_property def current_link_generation(self) -> int: """ Retrieve the current PCIe link generation. @@ -122,7 +122,7 @@ cdef class PciInfo: """ return nvml.device_get_curr_pcie_link_generation(self._handle) - @property + @python_property def current_link_width(self) -> int: """ Retrieve the current PCIe link width. @@ -131,7 +131,7 @@ cdef class PciInfo: """ return nvml.device_get_curr_pcie_link_width(self._handle) - @property + @python_property def rx_throughput(self) -> int: """ Retrieve PCIe reception throughput, in KB/s. @@ -146,7 +146,7 @@ cdef class PciInfo: """ return nvml.device_get_pcie_throughput(self._handle, nvml.PcieUtilCounter.PCIE_UTIL_RX_BYTES) - @property + @python_property def tx_throughput(self) -> int: """ Retrieve PCIe transmission throughput, in KB/s. @@ -161,7 +161,7 @@ cdef class PciInfo: """ return nvml.device_get_pcie_throughput(self._handle, nvml.PcieUtilCounter.PCIE_UTIL_TX_BYTES) - @property + @python_property def replay_counter(self) -> int: """ Retrieve the PCIe replay counter. diff --git a/cuda_core/cuda/core/system/_performance.pxi b/cuda_core/cuda/core/system/_performance.pxi index 6ba1d40f9c2..a8875dd0e6e 100644 --- a/cuda_core/cuda/core/system/_performance.pxi +++ b/cuda_core/cuda/core/system/_performance.pxi @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # # SPDX-License-Identifier: Apache-2.0 @@ -24,28 +24,28 @@ cdef class GpuDynamicPstatesUtilization: self._ptr = <_GpuDynamicPstatesUtilization *>ptr self._owner = owner - @property + @python_property def is_present(self) -> bool: """ Set if the utilization domain is present on this GPU. """ return bool(self._ptr[0].bIsPresent) - @property + @python_property def percentage(self) -> int: """ Percentage of time where the domain is considered busy in the last 1-second interval. """ return self._ptr[0].percentage - @property + @python_property def inc_threshold(self) -> int: """ Utilization threshold that can trigger a perf-increasing P-State change when crossed. """ return self._ptr[0].incThreshold - @property + @python_property def dec_threshold(self) -> int: """ Utilization threshold that can trigger a perf-decreasing P-State change when crossed. diff --git a/cuda_core/cuda/core/system/_repair_status.pxi b/cuda_core/cuda/core/system/_repair_status.pxi index b7d7de765e6..c64465eee35 100644 --- a/cuda_core/cuda/core/system/_repair_status.pxi +++ b/cuda_core/cuda/core/system/_repair_status.pxi @@ -12,14 +12,14 @@ cdef class RepairStatus: def __init__(self, handle: int): self._repair_status = nvml.device_get_repair_status(handle) - @property + @python_property def channel_repair_pending(self) -> bool: """ `True` if a channel repair is pending. """ return bool(self._repair_status.b_channel_repair_pending) - @property + @python_property def tpc_repair_pending(self) -> bool: """ `True` if a TPC repair is pending. diff --git a/cuda_core/cuda/core/system/_system_events.pyx b/cuda_core/cuda/core/system/_system_events.pyx index ad9d6c174e3..336004cfe9e 100644 --- a/cuda_core/cuda/core/system/_system_events.pyx +++ b/cuda_core/cuda/core/system/_system_events.pyx @@ -3,6 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 +from cuda.core._utils.properties import python_property from libc.stdint cimport intptr_t from cuda.bindings import nvml @@ -30,21 +31,21 @@ cdef class SystemEvent: assert len(event_data) == 1 self._event_data = event_data - @property + @python_property def event_type(self) -> SystemEventType: """ The :obj:`~SystemEventType` that was triggered. """ return _SYSTEM_EVENT_TYPE_MAPPING[self._event_data.event_type] - @property + @python_property def gpu_id(self) -> int: """ The GPU ID in PCI ID format. """ return self._event_data.gpu_id - @property + @python_property def device(self) -> _device.Device: """ The :obj:`~_device.Device` associated with this event. diff --git a/cuda_core/cuda/core/system/_temperature.pxi b/cuda_core/cuda/core/system/_temperature.pxi index 577494b7f59..b2beb298ae2 100644 --- a/cuda_core/cuda/core/system/_temperature.pxi +++ b/cuda_core/cuda/core/system/_temperature.pxi @@ -75,23 +75,23 @@ cdef class ThermalSensor: self._ptr = <_ThermalSensor *>ptr self._owner = owner - @property + @python_property def controller(self) -> ThermalController: return _THERMAL_CONTROLLER_MAPPING.get(self._ptr[0].controller, ThermalController.UNKNOWN) - @property + @python_property def default_min_temp(self) -> int: return self._ptr[0].defaultMinTemp - @property + @python_property def default_max_temp(self) -> int: return self._ptr[0].defaultMaxTemp - @property + @python_property def current_temp(self) -> int: return self._ptr[0].currentTemp - @property + @python_property def target(self) -> ThermalTarget: return _THERMAL_TARGET_MAPPING.get(self._ptr[0].target, ThermalTarget.NONE) @@ -184,7 +184,7 @@ cdef class Temperature: ) return nvml.device_get_temperature_threshold(self._handle, threshold_type_enum) - @property + @python_property def margin(self) -> int: """ The thermal margin temperature (distance to nearest slowdown threshold) for the device. diff --git a/cuda_core/cuda/core/system/_utilization.pxi b/cuda_core/cuda/core/system/_utilization.pxi index 689b7dc67f2..896c0cfa70f 100644 --- a/cuda_core/cuda/core/system/_utilization.pxi +++ b/cuda_core/cuda/core/system/_utilization.pxi @@ -14,14 +14,14 @@ cdef class Utilization: def __init__(self, utilization: nvml.Utilization): self._utilization = utilization - @property + @python_property def gpu(self) -> int: """ Percent of time over the past sample period during which one or more kernels was executing on the GPU. """ return self._utilization.gpu - @property + @python_property def memory(self) -> int: """ Percent of time over the past sample period during which global (device) memory was being read or written. diff --git a/cuda_core/pytest.ini b/cuda_core/pytest.ini index 41bf0d9c4fd..f2a06b3b3b0 100644 --- a/cuda_core/pytest.ini +++ b/cuda_core/pytest.ini @@ -1,7 +1,9 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # # SPDX-License-Identifier: Apache-2.0 [pytest] addopts = --showlocals norecursedirs = cython +markers = + no_cuda: tests that do not require CUDA driver initialization diff --git a/cuda_core/tests/conftest.py b/cuda_core/tests/conftest.py index 9f48686c30c..b7c406bd842 100644 --- a/cuda_core/tests/conftest.py +++ b/cuda_core/tests/conftest.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 import multiprocessing @@ -122,9 +122,11 @@ def _device_id_from_resource_options(device, args, kwargs): @pytest.fixture(scope="session", autouse=True) -def session_setup(): - # Always init CUDA. - handle_return(driver.cuInit(0)) +def session_setup(request): + no_cuda_tests = request.session.items and all(item.get_closest_marker("no_cuda") for item in request.session.items) + if not no_cuda_tests: + # Always init CUDA. + handle_return(driver.cuInit(0)) # Never fork processes. multiprocessing.set_start_method("spawn", force=True) diff --git a/cuda_core/tests/test_cython_property_descriptors.py b/cuda_core/tests/test_cython_property_descriptors.py index 8277d3587fa..748b5c53316 100644 --- a/cuda_core/tests/test_cython_property_descriptors.py +++ b/cuda_core/tests/test_cython_property_descriptors.py @@ -2,56 +2,136 @@ # # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + +import importlib import inspect +import re import types +from dataclasses import dataclass +from pathlib import Path import pytest -from cuda.core import ( - Buffer, - Context, - DeviceMemoryResource, - DeviceResources, - Event, - PinnedMemoryResource, -) from cuda.core.system import CUDA_BINDINGS_NVML_IS_COMPATIBLE -_PYTHON_PROPERTIES = [ - pytest.param(DeviceMemoryResource, "allocation_handle", id="DeviceMemoryResource.allocation_handle"), - pytest.param(PinnedMemoryResource, "allocation_handle", id="PinnedMemoryResource.allocation_handle"), - pytest.param(Event, "ipc_descriptor", id="Event.ipc_descriptor"), - pytest.param(Buffer, "ipc_descriptor", id="Buffer.ipc_descriptor"), - pytest.param(Context, "resources", id="Context.resources"), - pytest.param(DeviceResources, "workqueue", id="DeviceResources.workqueue"), -] - -if CUDA_BINDINGS_NVML_IS_COMPATIBLE: - from cuda.core.system import Device as SystemDevice - - _PYTHON_PROPERTIES.extend( - [ - pytest.param(SystemDevice, "attributes", id="system.Device.attributes"), - pytest.param( - SystemDevice, - "is_auto_boosted_clocks_enabled", - id="system.Device.is_auto_boosted_clocks_enabled", - ), - pytest.param(SystemDevice, "is_c2c_enabled", id="system.Device.is_c2c_enabled"), - pytest.param(SystemDevice, "numa_node_id", id="system.Device.numa_node_id"), - pytest.param(SystemDevice, "current_clock_event_reasons", id="system.Device.current_clock_event_reasons"), - pytest.param( - SystemDevice, - "supported_clock_event_reasons", - id="system.Device.supported_clock_event_reasons", - ), - ] - ) - - -@pytest.mark.parametrize("cls, name", _PYTHON_PROPERTIES) -def test_known_public_cython_properties_are_python_properties(cls, name): - descriptor = inspect.getattr_static(cls, name) +pytestmark = pytest.mark.no_cuda + +_CORE_ROOT = Path(__file__).resolve().parents[1] / "cuda" / "core" +_CDEF_CLASS_RE = re.compile(r"^cdef\s+class\s+([A-Za-z_]\w*)\b") +_DEF_RE = re.compile(r"def\s+([A-Za-z_]\w*)\s*\(") +_PROPERTY_ASSIGN_RE = re.compile(r"([A-Za-z_]\w*)\s*=\s*python_property\(") + + +@dataclass(frozen=True) +class CythonProperty: + module: str + source: Path + line: int + class_name: str + name: str + decorator: str + + +def _module_for_source(source: Path) -> str: + relative = source.relative_to(_CORE_ROOT) + if source.suffix == ".pyx": + return "cuda.core." + ".".join(relative.with_suffix("").parts) + if relative.parts[0] == "system": + return "cuda.core.system._device" + raise ValueError(f"No module mapping for {source}") + + +def _iter_cython_properties(): + for source in sorted((*_CORE_ROOT.rglob("*.pyx"), *_CORE_ROOT.rglob("*.pxi"))): + module = _module_for_source(source) + class_name = None + pending_decorator = None + pending_line = None + for line_number, line in enumerate(source.read_text(encoding="utf-8").splitlines(), start=1): + class_match = _CDEF_CLASS_RE.match(line) + if class_match is not None: + class_name = class_match.group(1) + pending_decorator = None + pending_line = None + continue + + if class_name is not None and line and not line[0].isspace() and line.strip() and not line.startswith("#"): + class_name = None + pending_decorator = None + pending_line = None + + if class_name is None: + continue + + stripped = line.strip() + if stripped in {"@property", "@python_property"}: + pending_decorator = stripped + pending_line = line_number + continue + + assignment_match = _PROPERTY_ASSIGN_RE.match(stripped) + if assignment_match is not None: + yield CythonProperty( + module=module, + source=source, + line=line_number, + class_name=class_name, + name=assignment_match.group(1), + decorator="python_property", + ) + pending_decorator = None + pending_line = None + continue + + if pending_decorator is not None: + def_match = _DEF_RE.match(stripped) + if def_match is not None: + yield CythonProperty( + module=module, + source=source, + line=pending_line, + class_name=class_name, + name=def_match.group(1), + decorator=pending_decorator, + ) + pending_decorator = None + pending_line = None + + +_CYTHON_PROPERTIES = tuple(_iter_cython_properties()) + + +def _property_id(cython_property: CythonProperty) -> str: + return f"{cython_property.module}.{cython_property.class_name}.{cython_property.name}" + + +def test_cython_cdef_class_getters_use_python_property_decorator(): + assert _CYTHON_PROPERTIES + cython_properties = [ + f"{prop.source.relative_to(_CORE_ROOT)}:{prop.line}: {prop.class_name}.{prop.name}" + for prop in _CYTHON_PROPERTIES + if prop.decorator == "@property" + ] + + assert not cython_properties + + +@pytest.mark.parametrize( + "cython_property", + [ + pytest.param(cython_property, id=_property_id(cython_property)) + for cython_property in _CYTHON_PROPERTIES + if cython_property.decorator != "@property" + ], +) +def test_cython_properties_are_python_properties(cython_property: CythonProperty): + if cython_property.module.startswith("cuda.core.system.") and not CUDA_BINDINGS_NVML_IS_COMPATIBLE: + pytest.skip("cuda.core.system extension modules require NVML-compatible bindings") + + module = importlib.import_module(cython_property.module) + cls = getattr(module, cython_property.class_name) + descriptor = inspect.getattr_static(cls, cython_property.name) assert isinstance(descriptor, property) assert not isinstance(descriptor, types.GetSetDescriptorType) diff --git a/pytest.ini b/pytest.ini index d1a82feb749..ca4432c1513 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 [pytest] @@ -19,5 +19,6 @@ markers = bindings: tests for cuda_bindings core: tests for cuda_core cython: cython tests + no_cuda: tests that do not require CUDA driver initialization smoke: meta-level smoke tests flaky: mark test as flaky (provided by pytest-rerunfailures) From c2dab7fe7bcb34795bc5b55ecbea0724deb2bac2 Mon Sep 17 00:00:00 2001 From: Keith Kraus Date: Fri, 8 May 2026 15:45:53 -0400 Subject: [PATCH 05/10] Address cuda-core property review feedback --- cuda_core/cuda/core/_utils/properties.py | 5 +- .../tests/test_cython_property_descriptors.py | 181 ++++++------------ 2 files changed, 64 insertions(+), 122 deletions(-) diff --git a/cuda_core/cuda/core/_utils/properties.py b/cuda_core/cuda/core/_utils/properties.py index a5e1928d909..1716d9d81dd 100644 --- a/cuda_core/cuda/core/_utils/properties.py +++ b/cuda_core/cuda/core/_utils/properties.py @@ -2,7 +2,4 @@ # # SPDX-License-Identifier: Apache-2.0 - -def python_property(fget=None, fset=None, fdel=None, doc=None): - """Create a Python property without Cython's cdef-class @property lowering.""" - return property(fget, fset, fdel, doc) +python_property = property diff --git a/cuda_core/tests/test_cython_property_descriptors.py b/cuda_core/tests/test_cython_property_descriptors.py index 748b5c53316..e958de76c43 100644 --- a/cuda_core/tests/test_cython_property_descriptors.py +++ b/cuda_core/tests/test_cython_property_descriptors.py @@ -6,132 +6,77 @@ import importlib import inspect -import re +import pkgutil import types -from dataclasses import dataclass -from pathlib import Path import pytest -from cuda.core.system import CUDA_BINDINGS_NVML_IS_COMPATIBLE +import cuda.core +import cuda.core.graph +import cuda.core.system pytestmark = pytest.mark.no_cuda -_CORE_ROOT = Path(__file__).resolve().parents[1] / "cuda" / "core" -_CDEF_CLASS_RE = re.compile(r"^cdef\s+class\s+([A-Za-z_]\w*)\b") -_DEF_RE = re.compile(r"def\s+([A-Za-z_]\w*)\s*\(") -_PROPERTY_ASSIGN_RE = re.compile(r"([A-Za-z_]\w*)\s*=\s*python_property\(") - - -@dataclass(frozen=True) -class CythonProperty: - module: str - source: Path - line: int - class_name: str - name: str - decorator: str - - -def _module_for_source(source: Path) -> str: - relative = source.relative_to(_CORE_ROOT) - if source.suffix == ".pyx": - return "cuda.core." + ".".join(relative.with_suffix("").parts) - if relative.parts[0] == "system": - return "cuda.core.system._device" - raise ValueError(f"No module mapping for {source}") - - -def _iter_cython_properties(): - for source in sorted((*_CORE_ROOT.rglob("*.pyx"), *_CORE_ROOT.rglob("*.pxi"))): - module = _module_for_source(source) - class_name = None - pending_decorator = None - pending_line = None - for line_number, line in enumerate(source.read_text(encoding="utf-8").splitlines(), start=1): - class_match = _CDEF_CLASS_RE.match(line) - if class_match is not None: - class_name = class_match.group(1) - pending_decorator = None - pending_line = None - continue - - if class_name is not None and line and not line[0].isspace() and line.strip() and not line.startswith("#"): - class_name = None - pending_decorator = None - pending_line = None - if class_name is None: +_OPTIONAL_IMPORT_FAILURES = {"cuda.core._tensor_bridge"} + +_GETSET_FIELD_ALLOWLIST = { + ("cuda.core._kernel_arg_handler", "ParamHolder", "ptr"), + ("cuda.core._layout", "_StridedLayout", "itemsize"), + ("cuda.core._layout", "_StridedLayout", "slice_offset"), + ("cuda.core._memoryview", "StridedMemoryView", "device_id"), + ("cuda.core._memoryview", "StridedMemoryView", "exporting_obj"), + ("cuda.core._memoryview", "StridedMemoryView", "is_device_accessible"), + ("cuda.core._memoryview", "StridedMemoryView", "ptr"), + ("cuda.core._memoryview", "StridedMemoryView", "readonly"), + ("cuda.core._memoryview", "_StridedMemoryViewProxy", "has_dlpack"), + ("cuda.core._memoryview", "_StridedMemoryViewProxy", "obj"), +} + + +def _iter_cuda_core_modules(): + roots = (cuda.core, cuda.core.graph, cuda.core.system) + module_names = set() + for root in roots: + for info in pkgutil.walk_packages(root.__path__, root.__name__ + "."): + module_names.add(info.name) + + for module_name in sorted(module_names): + try: + yield importlib.import_module(module_name) + except ImportError: + if module_name in _OPTIONAL_IMPORT_FAILURES: continue - - stripped = line.strip() - if stripped in {"@property", "@python_property"}: - pending_decorator = stripped - pending_line = line_number - continue - - assignment_match = _PROPERTY_ASSIGN_RE.match(stripped) - if assignment_match is not None: - yield CythonProperty( - module=module, - source=source, - line=line_number, - class_name=class_name, - name=assignment_match.group(1), - decorator="python_property", - ) - pending_decorator = None - pending_line = None - continue - - if pending_decorator is not None: - def_match = _DEF_RE.match(stripped) - if def_match is not None: - yield CythonProperty( - module=module, - source=source, - line=pending_line, - class_name=class_name, - name=def_match.group(1), - decorator=pending_decorator, - ) - pending_decorator = None - pending_line = None - - -_CYTHON_PROPERTIES = tuple(_iter_cython_properties()) - - -def _property_id(cython_property: CythonProperty) -> str: - return f"{cython_property.module}.{cython_property.class_name}.{cython_property.name}" - - -def test_cython_cdef_class_getters_use_python_property_decorator(): - assert _CYTHON_PROPERTIES - cython_properties = [ - f"{prop.source.relative_to(_CORE_ROOT)}:{prop.line}: {prop.class_name}.{prop.name}" - for prop in _CYTHON_PROPERTIES - if prop.decorator == "@property" + raise + + +def _iter_cuda_core_classes(): + for module in _iter_cuda_core_modules(): + for _, cls in inspect.getmembers(module, inspect.isclass): + if cls.__module__ == module.__name__: + yield cls + + +def _is_allowed_getset_descriptor(cls, name, descriptor) -> bool: + if name in {"__dict__", "__weakref__"}: + return True + # Typed cdef fields generate getset descriptors too, but their docs follow + # this compact "field: type" form rather than a property docstring. + doc = descriptor.__doc__ + if doc is not None and doc.startswith(f"{name}:"): + return True + return (cls.__module__, cls.__qualname__, name) in _GETSET_FIELD_ALLOWLIST + + +def test_cuda_core_classes_do_not_expose_cython_property_getset_descriptors(): + classes = tuple(_iter_cuda_core_classes()) + unexpected_getsets = [ + f"{cls.__module__}.{cls.__qualname__}.{name}" + for cls in classes + for name, descriptor in vars(cls).items() + if isinstance(descriptor, types.GetSetDescriptorType) + and not _is_allowed_getset_descriptor(cls, name, descriptor) ] - assert not cython_properties - - -@pytest.mark.parametrize( - "cython_property", - [ - pytest.param(cython_property, id=_property_id(cython_property)) - for cython_property in _CYTHON_PROPERTIES - if cython_property.decorator != "@property" - ], -) -def test_cython_properties_are_python_properties(cython_property: CythonProperty): - if cython_property.module.startswith("cuda.core.system.") and not CUDA_BINDINGS_NVML_IS_COMPATIBLE: - pytest.skip("cuda.core.system extension modules require NVML-compatible bindings") - - module = importlib.import_module(cython_property.module) - cls = getattr(module, cython_property.class_name) - descriptor = inspect.getattr_static(cls, cython_property.name) - - assert isinstance(descriptor, property) - assert not isinstance(descriptor, types.GetSetDescriptorType) + assert classes + assert not unexpected_getsets From 4ff946b34198c48a3551c28c87da0592806f8edc Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Fri, 8 May 2026 19:32:22 -0400 Subject: [PATCH 06/10] Fix tests --- cuda_core/cuda/core/_memoryview.pyx | 3 +-- .../tests/graph/test_graph_memory_resource.py | 4 +-- cuda_core/tests/test_utils.py | 26 +++++++++---------- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/cuda_core/cuda/core/_memoryview.pyx b/cuda_core/cuda/core/_memoryview.pyx index 96daf581eb7..34dd940c981 100644 --- a/cuda_core/cuda/core/_memoryview.pyx +++ b/cuda_core/cuda/core/_memoryview.pyx @@ -485,8 +485,7 @@ cdef class StridedMemoryView: _smv_get_dl_device(self, &device_type, &device_id) return (device_type, int(device_id)) - @python_property - def _layout(self) -> _StridedLayout: + def _get_layout(self) -> _StridedLayout: """ The layout of the tensor. For StridedMemoryView created from DLPack or CAI, the layout is inferred from the tensor object's metadata. diff --git a/cuda_core/tests/graph/test_graph_memory_resource.py b/cuda_core/tests/graph/test_graph_memory_resource.py index cdf694e3230..4043c2b2686 100644 --- a/cuda_core/tests/graph/test_graph_memory_resource.py +++ b/cuda_core/tests/graph/test_graph_memory_resource.py @@ -223,13 +223,13 @@ def test_graph_mem_set_attributes(mempool_device, mode): assert gmr.attributes.used_mem_high > 0 # Incorrect attribute usage. - with pytest.raises(AttributeError, match=r"attribute 'reserved_mem_current' .* is not writable"): + with pytest.raises(AttributeError, match=r"can't set attribute 'reserved_mem_current'"): gmr.attributes.reserved_mem_current = 0 with pytest.raises(AttributeError, match=r"Attribute 'reserved_mem_high' may only be set to zero \(got 1\)\."): gmr.attributes.reserved_mem_high = 1 - with pytest.raises(AttributeError, match=r"attribute 'used_mem_current' .* is not writable"): + with pytest.raises(AttributeError, match=r"can't set attribute 'used_mem_current'"): gmr.attributes.used_mem_current = 0 with pytest.raises(AttributeError, match=r"Attribute 'used_mem_high' may only be set to zero \(got 1\)\."): diff --git a/cuda_core/tests/test_utils.py b/cuda_core/tests/test_utils.py index 18379cd7a24..f7f4f18cb53 100644 --- a/cuda_core/tests/test_utils.py +++ b/cuda_core/tests/test_utils.py @@ -396,7 +396,7 @@ def test_from_buffer(shape, dtype, stride_order, readonly): buffer = dev.memory_resource.allocate(required_size, stream=dev.default_stream) view = StridedMemoryView.from_buffer(buffer, shape=shape, strides=layout.strides, dtype=dtype, is_readonly=readonly) assert view.exporting_obj is buffer - assert view._layout == layout + assert view._get_layout() == layout assert view.ptr == int(buffer.handle) assert view.shape == shape assert view.strides == _dense_strides(shape, stride_order) @@ -435,8 +435,8 @@ def test_from_buffer_sliced(stride_order): sliced_view = view.view(layout[:-2, 3:]) assert sliced_view.shape == (3, 4) expected_offset = 3 if stride_order == "C" else 3 * 5 - assert sliced_view._layout.slice_offset == expected_offset - assert sliced_view._layout.slice_offset_in_bytes == expected_offset * 2 + assert sliced_view._get_layout().slice_offset == expected_offset + assert sliced_view._get_layout().slice_offset_in_bytes == expected_offset * 2 assert sliced_view.ptr == view.ptr + expected_offset * 2 assert int(buffer.handle) + expected_offset * 2 == sliced_view.ptr @@ -506,7 +506,7 @@ def test_view_sliced_external(init_cuda, shape, slices, stride_order, view_as): pytest.skip("CuPy is not installed") a = cp.arange(math.prod(shape), dtype=cp.int32).reshape(shape, order=stride_order) view = StridedMemoryView.from_cuda_array_interface(_EnforceCAIView(a), -1) - layout = view._layout + layout = view._get_layout() assert layout.is_dense assert layout.required_size_in_bytes() == a.nbytes assert view.ptr == _get_ptr(a) @@ -519,11 +519,11 @@ def test_view_sliced_external(init_cuda, shape, slices, stride_order, view_as): assert 0 <= sliced_layout.required_size_in_bytes() <= a.nbytes assert not sliced_layout.is_dense - assert sliced_view._layout is sliced_layout + assert sliced_view._get_layout() is sliced_layout assert view.dtype == sliced_view.dtype - assert sliced_view._layout.itemsize == a_sliced.itemsize == layout.itemsize + assert sliced_view._get_layout().itemsize == a_sliced.itemsize == layout.itemsize assert sliced_view.shape == a_sliced.shape - assert sliced_view._layout.strides_in_bytes == a_sliced.strides + assert sliced_view._get_layout().strides_in_bytes == a_sliced.strides @pytest.mark.parametrize( @@ -544,7 +544,7 @@ def test_view_sliced_external_negative_offset(init_cuda, stride_order, view_as): a = cp.arange(math.prod(shape), dtype=cp.int32).reshape(shape, order=stride_order) a = a[::-1] view = StridedMemoryView.from_cuda_array_interface(_EnforceCAIView(a), -1) - layout = view._layout + layout = view._get_layout() assert not layout.is_dense assert layout.strides == (-1,) assert view.ptr == _get_ptr(a) @@ -556,11 +556,11 @@ def test_view_sliced_external_negative_offset(init_cuda, stride_order, view_as): assert sliced_view.ptr == view.ptr - 3 * a.itemsize assert not sliced_layout.is_dense - assert sliced_view._layout is sliced_layout + assert sliced_view._get_layout() is sliced_layout assert view.dtype == sliced_view.dtype - assert sliced_view._layout.itemsize == a_sliced.itemsize == layout.itemsize + assert sliced_view._get_layout().itemsize == a_sliced.itemsize == layout.itemsize assert sliced_view.shape == a_sliced.shape - assert sliced_view._layout.strides_in_bytes == a_sliced.strides + assert sliced_view._get_layout().strides_in_bytes == a_sliced.strides @pytest.mark.parametrize( @@ -826,7 +826,7 @@ def test_strided_memory_view_get_layout_error(): with pytest.warns(DeprecationWarning, match="deprecated"): view = StridedMemoryView() with pytest.raises(ValueError, match="Cannot infer layout"): - _ = view._layout + _ = view._get_layout() @pytest.mark.skipif(cp is None, reason="CuPy is not installed") @@ -876,7 +876,7 @@ def test_strided_memory_view_view_with_dtype_only(): view = StridedMemoryView.from_any_interface(src, stream_ptr=-1) viewed = view.view(dtype=np.dtype("int32")) assert viewed.dtype == np.dtype("int32") - assert viewed._layout == view._layout + assert viewed._get_layout() == view._get_layout() def test_dlpack_export_structured_dtype_raises(): From 6b3a3cbc1c693ad9e3cf38f235a06af15001c1c4 Mon Sep 17 00:00:00 2001 From: Leo Fang Date: Fri, 8 May 2026 21:15:04 -0400 Subject: [PATCH 07/10] fix test error --- cuda_core/tests/test_cython_property_descriptors.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/cuda_core/tests/test_cython_property_descriptors.py b/cuda_core/tests/test_cython_property_descriptors.py index e958de76c43..a2e9924c755 100644 --- a/cuda_core/tests/test_cython_property_descriptors.py +++ b/cuda_core/tests/test_cython_property_descriptors.py @@ -18,7 +18,7 @@ pytestmark = pytest.mark.no_cuda -_OPTIONAL_IMPORT_FAILURES = {"cuda.core._tensor_bridge"} +_NOT_ALLOWED_TO_IMPORT = {"cuda.core._tensor_bridge"} _GETSET_FIELD_ALLOWLIST = { ("cuda.core._kernel_arg_handler", "ParamHolder", "ptr"), @@ -41,13 +41,9 @@ def _iter_cuda_core_modules(): for info in pkgutil.walk_packages(root.__path__, root.__name__ + "."): module_names.add(info.name) + module_names -= _NOT_ALLOWED_TO_IMPORT for module_name in sorted(module_names): - try: - yield importlib.import_module(module_name) - except ImportError: - if module_name in _OPTIONAL_IMPORT_FAILURES: - continue - raise + yield importlib.import_module(module_name) def _iter_cuda_core_classes(): From 9eb4a8f3141f002eddabdfd23508f26f730fd926 Mon Sep 17 00:00:00 2001 From: Leo Fang Date: Sat, 9 May 2026 01:52:40 +0000 Subject: [PATCH 08/10] Fix _tensor_bridge exclusion for versioned subpackages In CI, _tensor_bridge lives under a versioned subpackage (cuda.core.cu12._tensor_bridge), but the exclusion set only matched the unversioned name. Switch to suffix matching so that all versioned variants are excluded. --- cuda_core/tests/test_cython_property_descriptors.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/cuda_core/tests/test_cython_property_descriptors.py b/cuda_core/tests/test_cython_property_descriptors.py index a2e9924c755..d0ca0f304f8 100644 --- a/cuda_core/tests/test_cython_property_descriptors.py +++ b/cuda_core/tests/test_cython_property_descriptors.py @@ -18,7 +18,11 @@ pytestmark = pytest.mark.no_cuda -_NOT_ALLOWED_TO_IMPORT = {"cuda.core._tensor_bridge"} +# Suffixes of module names that must not be imported (e.g. _tensor_bridge +# depends on PyTorch symbols that are unavailable in most test environments). +# Matched against the end of each discovered module name so that versioned +# subpackages like cuda.core.cu12._tensor_bridge are also excluded. +_NOT_ALLOWED_TO_IMPORT_SUFFIXES = {"._tensor_bridge"} _GETSET_FIELD_ALLOWLIST = { ("cuda.core._kernel_arg_handler", "ParamHolder", "ptr"), @@ -41,7 +45,10 @@ def _iter_cuda_core_modules(): for info in pkgutil.walk_packages(root.__path__, root.__name__ + "."): module_names.add(info.name) - module_names -= _NOT_ALLOWED_TO_IMPORT + module_names = { + n for n in module_names + if not any(n.endswith(s) for s in _NOT_ALLOWED_TO_IMPORT_SUFFIXES) + } for module_name in sorted(module_names): yield importlib.import_module(module_name) From 6e182ef9adf9f372708deabab6bc06254573bc02 Mon Sep 17 00:00:00 2001 From: Leo Fang Date: Sat, 9 May 2026 01:54:35 +0000 Subject: [PATCH 09/10] Fix _tensor_bridge exclusion for versioned subpackages In CI, _tensor_bridge lives under a versioned subpackage (cuda.core.cu12._tensor_bridge), but the exclusion set only matched the unversioned name. Switch to suffix matching so that all versioned variants are excluded. --- cuda_core/tests/test_cython_property_descriptors.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cuda_core/tests/test_cython_property_descriptors.py b/cuda_core/tests/test_cython_property_descriptors.py index d0ca0f304f8..e5ae33d70b1 100644 --- a/cuda_core/tests/test_cython_property_descriptors.py +++ b/cuda_core/tests/test_cython_property_descriptors.py @@ -45,10 +45,7 @@ def _iter_cuda_core_modules(): for info in pkgutil.walk_packages(root.__path__, root.__name__ + "."): module_names.add(info.name) - module_names = { - n for n in module_names - if not any(n.endswith(s) for s in _NOT_ALLOWED_TO_IMPORT_SUFFIXES) - } + module_names = {n for n in module_names if not any(n.endswith(s) for s in _NOT_ALLOWED_TO_IMPORT_SUFFIXES)} for module_name in sorted(module_names): yield importlib.import_module(module_name) From 7c5271306760a1657d3a986425992e9cbebbe76c Mon Sep 17 00:00:00 2001 From: Leo Fang Date: Sat, 9 May 2026 02:22:20 +0000 Subject: [PATCH 10/10] Fix read-only property error message match across Python versions Python 3.10 uses "property '...' of '...' object has no setter" while 3.11+ uses "can't set attribute '...'". Relax the regex to accept both. --- cuda_core/tests/graph/test_graph_memory_resource.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cuda_core/tests/graph/test_graph_memory_resource.py b/cuda_core/tests/graph/test_graph_memory_resource.py index 4043c2b2686..862551a7bcd 100644 --- a/cuda_core/tests/graph/test_graph_memory_resource.py +++ b/cuda_core/tests/graph/test_graph_memory_resource.py @@ -223,13 +223,13 @@ def test_graph_mem_set_attributes(mempool_device, mode): assert gmr.attributes.used_mem_high > 0 # Incorrect attribute usage. - with pytest.raises(AttributeError, match=r"can't set attribute 'reserved_mem_current'"): + with pytest.raises(AttributeError, match=r"(can.t set|no setter)"): gmr.attributes.reserved_mem_current = 0 with pytest.raises(AttributeError, match=r"Attribute 'reserved_mem_high' may only be set to zero \(got 1\)\."): gmr.attributes.reserved_mem_high = 1 - with pytest.raises(AttributeError, match=r"can't set attribute 'used_mem_current'"): + with pytest.raises(AttributeError, match=r"(can.t set|no setter)"): gmr.attributes.used_mem_current = 0 with pytest.raises(AttributeError, match=r"Attribute 'used_mem_high' may only be set to zero \(got 1\)\."):