diff --git a/cuda_core/cuda/core/_context.pyx b/cuda_core/cuda/core/_context.pyx index 48dc2c0977..2826798408 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'] @@ -57,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: @@ -66,18 +67,18 @@ 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: return False return get_context_green_ctx(self._h_context).get() != NULL - @property + @python_property def resources(self) -> DeviceResources: """Query the hardware resources provisioned for this context. diff --git a/cuda_core/cuda/core/_device.pyx b/cuda_core/cuda/core/_device.pyx index 67255506a2..1514554054 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 40c0a874d0..fc4025e01f 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 @@ -441,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 @@ -519,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) @@ -613,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() @@ -622,7 +623,7 @@ cdef class DeviceResources: self._query_sm(&res) return SMResource._from_dev_resource(res, self._device_id) - @property + @python_property def workqueue(self) -> WorkqueueResource: """Return the :obj:`WorkqueueResource` 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 3f5fb7ace2..e664c437a4 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,7 @@ cdef class Event: def __repr__(self) -> str: return f"" - @property + @python_property def ipc_descriptor(self) -> IPCEventDescriptor: """Descriptor for sharing this event with other processes.""" if self._ipc_descriptor is not None: @@ -237,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. @@ -266,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: @@ -277,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. @@ -288,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. @@ -304,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 a377589dfc..4f5346ee77 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 3e2580d11d..025392dc6d 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 8f513ce121..8daed2f766 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 5d3bdbb873..40ffcd76ee 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,7 +191,7 @@ cdef class Buffer: """ return _ipc.Buffer_from_ipc_descriptor(cls, mr, ipc_descriptor, stream) - @property + @python_property def ipc_descriptor(self) -> IPCBufferDescriptor: """Descriptor for sharing this buffer with other processes.""" if self._ipc_data is None: @@ -360,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: @@ -368,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. @@ -395,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: @@ -403,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: @@ -411,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) @@ -421,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 @@ -553,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 b7b8b247a9..9f1a662647 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,7 @@ cdef class DeviceMemoryResource(_MemPool): mr._dev_id = Device(device_id).device_id return mr - @property + @python_property def allocation_handle(self) -> IPCAllocationHandle: """Shareable handle for this memory pool (requires IPC). @@ -201,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 @@ -227,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 8fdc324dc5..5ec742e8df 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 59414fc1b2..1e1f17fd50 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 f37a4f18ee..fd00011484 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 4da5e26ea9..3610c0ee5b 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 0b18a1f7e3..23ed4d8ae4 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,7 @@ cdef class PinnedMemoryResource(_MemPool): _ipc.MP_from_allocation_handle(cls, alloc_handle)) return mr - @property + @python_property def allocation_handle(self) -> IPCAllocationHandle: """Shareable handle for this memory pool (requires IPC). @@ -159,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 3ebde8dcff..34dd940c98 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,33 +485,32 @@ cdef class StridedMemoryView: _smv_get_dl_device(self, &device_type, &device_id) return (device_type, int(device_id)) - @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. """ 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 4a8601f857..f8ca4c0cd7 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 2ef38775d1..19a137a932 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 f487a0a53e..89d4a4361e 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 e1e5daa9fd..1b9f8014c7 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 new file mode 100644 index 0000000000..1716d9d81d --- /dev/null +++ b/cuda_core/cuda/core/_utils/properties.py @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +python_property = property diff --git a/cuda_core/cuda/core/graph/_graph_definition.pyx b/cuda_core/cuda/core/graph/_graph_definition.pyx index 413a17368d..0e1627deab 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 a5577d134d..f6cb8f1d61 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 6d15ebc3ff..57999a4436 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 a1fd940fc4..d9e7067b0e 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 b24f925282..5afa02c911 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 9c8224e54a..eed7d91bc3 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, @@ -206,7 +207,7 @@ cdef class Device: ######################################################################### # BASIC PROPERTIES - @property + @python_property def index(self) -> int: """ The NVML index of this device. @@ -225,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 @@ -238,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 @@ -252,14 +253,14 @@ 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. """ return self.pci_info.bus_id - @property + @python_property def numa_node_id(self) -> int: """ The NUMA node of the given GPU device. @@ -268,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. @@ -283,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. @@ -299,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 @@ -309,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. @@ -320,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. @@ -332,14 +333,13 @@ cdef class Device: """ return nvml.device_get_minor_number(self._handle) - @property + @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) - @property def is_persistence_mode_enabled(self) -> bool: """ Whether persistence mode is enabled for this device. @@ -348,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. @@ -420,7 +423,7 @@ cdef class Device: ######################################################################### # ADDRESSING MODE - @property + @python_property def addressing_mode(self) -> AddressingMode | None: """ Get the :obj:`~AddressingMode` of the device. @@ -430,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. @@ -575,7 +578,7 @@ cdef class Device: """ return ClockInfo(self._handle, clock_type) - @property + @python_property def is_auto_boosted_clocks_enabled(self) -> tuple[bool, bool]: """ Retrieve the current state of auto boosted clocks on a device. @@ -601,7 +604,7 @@ 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 + @python_property def current_clock_event_reasons(self) -> list[ClocksEventReasons]: """ Retrieves the current :obj:`~ClocksEventReasons`. @@ -619,7 +622,7 @@ cdef class Device: output_reasons.append(output_reason) return output_reasons - @property + @python_property def supported_clock_event_reasons(self) -> list[ClocksEventReasons]: """ Retrieves supported :obj:`~ClocksEventReasons` that can be returned by @@ -644,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. @@ -655,7 +658,7 @@ cdef class Device: # DEVICE ATTRIBUTES # See external class definitions in _device_attributes.pxi - @property + @python_property def attributes(self) -> DeviceAttributes: """ :obj:`~_device.DeviceAttributes` object with various device attributes. @@ -668,7 +671,7 @@ cdef class Device: ######################################################################### # DISPLAY - @property + @python_property def is_display_connected(self) -> bool: """ The display mode for this device. @@ -678,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. @@ -772,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. @@ -837,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. @@ -850,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. @@ -861,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. @@ -886,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. @@ -897,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. @@ -913,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. @@ -952,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 @@ -979,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. @@ -992,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. @@ -1034,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 c18a7be35d..b95729e5a5 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 a1f9233803..e024ef1de1 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 2bedc74520..f1fe7648db 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 4a9e5cc748..558fe7cd1d 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 f5ab70408d..007a48c168 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 e9142a30ec..5378d9c3bb 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 8fa6b9d780..2866c0ac18 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 ad246b8364..3a9942d5ab 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 55922b767a..3b4e44e73c 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 6ba1d40f9c..a8875dd0e6 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 b7d7de765e..c64465eee3 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 ad9d6c174e..336004cfe9 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 577494b7f5..b2beb298ae 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 689b7dc67f..896c0cfa70 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 41bf0d9c4f..f2a06b3b3b 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 9f48686c30..b7c406bd84 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/graph/test_graph_memory_resource.py b/cuda_core/tests/graph/test_graph_memory_resource.py index cdf694e323..862551a7bc 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|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"attribute 'used_mem_current' .* is not writable"): + 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\)\."): 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 0000000000..e5ae33d70b --- /dev/null +++ b/cuda_core/tests/test_cython_property_descriptors.py @@ -0,0 +1,82 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import importlib +import inspect +import pkgutil +import types + +import pytest + +import cuda.core +import cuda.core.graph +import cuda.core.system + +pytestmark = pytest.mark.no_cuda + + +# 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"), + ("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) + + 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) + + +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 classes + assert not unexpected_getsets diff --git a/cuda_core/tests/test_utils.py b/cuda_core/tests/test_utils.py index 18379cd7a2..f7f4f18cb5 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(): diff --git a/pytest.ini b/pytest.ini index d1a82feb74..ca4432c151 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)