diff --git a/.github/workflows/build-wheel.yml b/.github/workflows/build-wheel.yml index 283a0d462b..5a0dba3a61 100644 --- a/.github/workflows/build-wheel.yml +++ b/.github/workflows/build-wheel.yml @@ -28,7 +28,6 @@ jobs: fail-fast: false matrix: python-version: - - "3.9" - "3.10" - "3.11" - "3.12" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 183d215865..67bd568d85 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -113,7 +113,7 @@ flowchart TD B2["linux-aarch64
(Self-hosted)"] B3["win-64
(GitHub-hosted)"] end - BUILD_DETAILS["• Python versions: 3.9, 3.10, 3.11, 3.12, 3.13
• CUDA version: 13.0.0 (build-time)
• Components: cuda-core, cuda-bindings,
cuda-pathfinder, cuda-python"] + BUILD_DETAILS["• Python versions: 3.10, 3.11, 3.12, 3.13, 3.14
• CUDA version: 13.0.0 (build-time)
• Components: cuda-core, cuda-bindings,
cuda-pathfinder, cuda-python"] end %% Artifact Storage diff --git a/ci/test-matrix.json b/ci/test-matrix.json index 10721659b8..feea4ebaef 100644 --- a/ci/test-matrix.json +++ b/ci/test-matrix.json @@ -4,8 +4,6 @@ "_notes": "DRIVER: 'earliest' does not work with CUDA 12.9.1 and LOCAL_CTK: 0 does not work with CUDA 12.0.1", "linux": { "pull-request": [ - { "ARCH": "amd64", "PY_VER": "3.9", "CUDA_VER": "12.9.1", "LOCAL_CTK": "0", "GPU": "l4", "DRIVER": "latest" }, - { "ARCH": "amd64", "PY_VER": "3.9", "CUDA_VER": "13.0.1", "LOCAL_CTK": "1", "GPU": "l4", "DRIVER": "latest" }, { "ARCH": "amd64", "PY_VER": "3.10", "CUDA_VER": "12.9.1", "LOCAL_CTK": "1", "GPU": "l4", "DRIVER": "latest" }, { "ARCH": "amd64", "PY_VER": "3.10", "CUDA_VER": "13.0.1", "LOCAL_CTK": "0", "GPU": "l4", "DRIVER": "latest" }, { "ARCH": "amd64", "PY_VER": "3.11", "CUDA_VER": "12.9.1", "LOCAL_CTK": "0", "GPU": "l4", "DRIVER": "latest" }, @@ -17,8 +15,6 @@ { "ARCH": "amd64", "PY_VER": "3.13t", "CUDA_VER": "13.0.1", "LOCAL_CTK": "1", "GPU": "l4", "DRIVER": "latest" }, { "ARCH": "amd64", "PY_VER": "3.14", "CUDA_VER": "13.0.1", "LOCAL_CTK": "1", "GPU": "l4", "DRIVER": "latest" }, { "ARCH": "amd64", "PY_VER": "3.14t", "CUDA_VER": "13.0.1", "LOCAL_CTK": "1", "GPU": "l4", "DRIVER": "latest" }, - { "ARCH": "arm64", "PY_VER": "3.9", "CUDA_VER": "12.9.1", "LOCAL_CTK": "0", "GPU": "a100", "DRIVER": "latest" }, - { "ARCH": "arm64", "PY_VER": "3.9", "CUDA_VER": "13.0.1", "LOCAL_CTK": "1", "GPU": "a100", "DRIVER": "latest" }, { "ARCH": "arm64", "PY_VER": "3.10", "CUDA_VER": "12.9.1", "LOCAL_CTK": "1", "GPU": "a100", "DRIVER": "latest" }, { "ARCH": "arm64", "PY_VER": "3.10", "CUDA_VER": "13.0.1", "LOCAL_CTK": "0", "GPU": "a100", "DRIVER": "latest" }, { "ARCH": "arm64", "PY_VER": "3.11", "CUDA_VER": "12.9.1", "LOCAL_CTK": "0", "GPU": "a100", "DRIVER": "latest" }, @@ -32,11 +28,6 @@ { "ARCH": "arm64", "PY_VER": "3.14t", "CUDA_VER": "13.0.1", "LOCAL_CTK": "1", "GPU": "a100", "DRIVER": "latest" } ], "nightly": [ - { "ARCH": "amd64", "PY_VER": "3.9", "CUDA_VER": "11.8.0", "LOCAL_CTK": "0", "GPU": "l4", "DRIVER": "earliest" }, - { "ARCH": "amd64", "PY_VER": "3.9", "CUDA_VER": "11.8.0", "LOCAL_CTK": "1", "GPU": "l4", "DRIVER": "latest" }, - { "ARCH": "amd64", "PY_VER": "3.9", "CUDA_VER": "12.0.1", "LOCAL_CTK": "1", "GPU": "l4", "DRIVER": "latest" }, - { "ARCH": "amd64", "PY_VER": "3.9", "CUDA_VER": "12.9.1", "LOCAL_CTK": "0", "GPU": "l4", "DRIVER": "latest" }, - { "ARCH": "amd64", "PY_VER": "3.9", "CUDA_VER": "12.9.1", "LOCAL_CTK": "1", "GPU": "l4", "DRIVER": "latest" }, { "ARCH": "amd64", "PY_VER": "3.10", "CUDA_VER": "11.8.0", "LOCAL_CTK": "0", "GPU": "l4", "DRIVER": "earliest" }, { "ARCH": "amd64", "PY_VER": "3.10", "CUDA_VER": "11.8.0", "LOCAL_CTK": "1", "GPU": "l4", "DRIVER": "latest" }, { "ARCH": "amd64", "PY_VER": "3.10", "CUDA_VER": "12.0.1", "LOCAL_CTK": "1", "GPU": "l4", "DRIVER": "latest" }, @@ -57,11 +48,6 @@ { "ARCH": "amd64", "PY_VER": "3.13", "CUDA_VER": "12.0.1", "LOCAL_CTK": "1", "GPU": "l4", "DRIVER": "latest" }, { "ARCH": "amd64", "PY_VER": "3.13", "CUDA_VER": "12.9.1", "LOCAL_CTK": "0", "GPU": "l4", "DRIVER": "latest" }, { "ARCH": "amd64", "PY_VER": "3.13", "CUDA_VER": "12.9.1", "LOCAL_CTK": "1", "GPU": "l4", "DRIVER": "latest" }, - { "ARCH": "arm64", "PY_VER": "3.9", "CUDA_VER": "11.8.0", "LOCAL_CTK": "0", "GPU": "a100", "DRIVER": "earliest" }, - { "ARCH": "arm64", "PY_VER": "3.9", "CUDA_VER": "11.8.0", "LOCAL_CTK": "1", "GPU": "a100", "DRIVER": "latest" }, - { "ARCH": "arm64", "PY_VER": "3.9", "CUDA_VER": "12.0.1", "LOCAL_CTK": "1", "GPU": "a100", "DRIVER": "latest" }, - { "ARCH": "arm64", "PY_VER": "3.9", "CUDA_VER": "12.9.1", "LOCAL_CTK": "0", "GPU": "a100", "DRIVER": "latest" }, - { "ARCH": "arm64", "PY_VER": "3.9", "CUDA_VER": "12.9.1", "LOCAL_CTK": "1", "GPU": "a100", "DRIVER": "latest" }, { "ARCH": "arm64", "PY_VER": "3.10", "CUDA_VER": "11.8.0", "LOCAL_CTK": "0", "GPU": "a100", "DRIVER": "earliest" }, { "ARCH": "arm64", "PY_VER": "3.10", "CUDA_VER": "11.8.0", "LOCAL_CTK": "1", "GPU": "a100", "DRIVER": "latest" }, { "ARCH": "arm64", "PY_VER": "3.10", "CUDA_VER": "12.0.1", "LOCAL_CTK": "1", "GPU": "a100", "DRIVER": "latest" }, diff --git a/cuda_bindings/docs/source/install.rst b/cuda_bindings/docs/source/install.rst index b5181c6a35..02441fd518 100644 --- a/cuda_bindings/docs/source/install.rst +++ b/cuda_bindings/docs/source/install.rst @@ -10,7 +10,7 @@ Runtime Requirements ``cuda.bindings`` supports the same platforms as CUDA. Runtime dependencies are: * Linux (x86-64, arm64) and Windows (x86-64) -* Python 3.9 - 3.13 +* Python 3.10 - 3.14 * Driver: Linux (580.65.06 or later) Windows (580.88 or later) * Optionally, NVRTC, nvJitLink, NVVM, and cuFile from CUDA Toolkit 13.x diff --git a/cuda_bindings/docs/source/support.rst b/cuda_bindings/docs/source/support.rst index a34a5c49e2..4439d963c0 100644 --- a/cuda_bindings/docs/source/support.rst +++ b/cuda_bindings/docs/source/support.rst @@ -19,7 +19,7 @@ The ``cuda.bindings`` module has the following support policy: depends on the underlying driver and the Toolkit versions, as described in the compatibility documentation.) 4. The module supports all Python versions following the `CPython EOL schedule`_. As of writing - Python 3.9 - 3.13 are supported. + Python 3.10 - 3.14 are supported. 5. The module exposes a Cython layer from which types and functions could be ``cimport``'d. While we strive to keep this layer stable, due to Cython limitations a new *minor* release of this module could require Cython layer users to rebuild their projects and update their pinning to diff --git a/cuda_bindings/pyproject.toml b/cuda_bindings/pyproject.toml index 250f8e4076..74ed7b95d5 100644 --- a/cuda_bindings/pyproject.toml +++ b/cuda_bindings/pyproject.toml @@ -9,16 +9,17 @@ name = "cuda-bindings" description = "Python bindings for CUDA" authors = [{name = "NVIDIA Corporation", email = "cuda-python-conduct@nvidia.com"},] license = "LicenseRef-NVIDIA-SOFTWARE-LICENSE" +requires-python = ">=3.10" classifiers = [ "Intended Audience :: Developers", "Topic :: Database", "Topic :: Scientific/Engineering", "Programming Language :: Python", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Environment :: GPU :: NVIDIA CUDA", ] dynamic = [ diff --git a/cuda_core/cuda/core/experimental/__init__.py b/cuda_core/cuda/core/experimental/__init__.py index 94fb0aa083..8a60c031c5 100644 --- a/cuda_core/cuda/core/experimental/__init__.py +++ b/cuda_core/cuda/core/experimental/__init__.py @@ -26,17 +26,6 @@ finally: del cuda.bindings, importlib, subdir, cuda_major, cuda_minor -import sys # noqa: E402 -import warnings # noqa: E402 - -if sys.version_info < (3, 10): - warnings.warn( - "support for Python 3.9 and below is deprecated and subject to future removal", - category=FutureWarning, - stacklevel=1, - ) -del sys, warnings - from cuda.core.experimental import utils # noqa: E402 from cuda.core.experimental._device import Device # noqa: E402 from cuda.core.experimental._event import Event, EventOptions # noqa: E402 diff --git a/cuda_core/cuda/core/experimental/_device.pyx b/cuda_core/cuda/core/experimental/_device.pyx index 1533614adb..20d611a24b 100644 --- a/cuda_core/cuda/core/experimental/_device.pyx +++ b/cuda_core/cuda/core/experimental/_device.pyx @@ -10,7 +10,7 @@ from cuda.bindings cimport cydriver from cuda.core.experimental._utils.cuda_utils cimport HANDLE_RETURN import threading -from typing import Optional, Union +from typing import Union from cuda.core.experimental._context import Context, ContextOptions from cuda.core.experimental._event import Event, EventOptions @@ -949,7 +949,7 @@ class Device: """ __slots__ = ("_id", "_mr", "_has_inited", "_properties") - def __new__(cls, device_id: Optional[int] = None): + def __new__(cls, device_id: int | None = None): global _is_cuInit if _is_cuInit is False: with _lock, nogil: @@ -1221,7 +1221,7 @@ class Device: """ raise NotImplementedError("WIP: https://github.com/NVIDIA/cuda-python/issues/189") - def create_stream(self, obj: Optional[IsStreamT] = None, options: Optional[StreamOptions] = None) -> Stream: + def create_stream(self, obj: IsStreamT | None = None, options: StreamOptions | None = None) -> Stream: """Create a Stream object. New stream objects can be created in two different ways: @@ -1252,7 +1252,7 @@ class Device: self._check_context_initialized() return Stream._init(obj=obj, options=options, device_id=self._id) - def create_event(self, options: Optional[EventOptions] = None) -> Event: + def create_event(self, options: EventOptions | None = None) -> Event: """Create an Event object without recording it to a Stream. Note @@ -1274,7 +1274,7 @@ class Device: ctx = self._get_current_context() return Event._init(self._id, ctx, options) - def allocate(self, size, stream: Optional[Stream] = None) -> Buffer: + def allocate(self, size, stream: Stream | None = None) -> Buffer: """Allocate device memory from a specified stream. Allocates device memory of `size` bytes on the specified `stream` diff --git a/cuda_core/cuda/core/experimental/_launch_config.py b/cuda_core/cuda/core/experimental/_launch_config.py index d82e0ec3a2..c1e08da58d 100644 --- a/cuda_core/cuda/core/experimental/_launch_config.py +++ b/cuda_core/cuda/core/experimental/_launch_config.py @@ -3,7 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 from dataclasses import dataclass -from typing import Optional, Union +from typing import Union from cuda.core.experimental._device import Device from cuda.core.experimental._utils.cuda_utils import ( @@ -68,8 +68,8 @@ class LaunchConfig: grid: Union[tuple, int] = None cluster: Union[tuple, int] = None block: Union[tuple, int] = None - shmem_size: Optional[int] = None - cooperative_launch: Optional[bool] = False + shmem_size: int | None = None + cooperative_launch: bool | None = False def __post_init__(self): _lazy_init() diff --git a/cuda_core/cuda/core/experimental/_linker.py b/cuda_core/cuda/core/experimental/_linker.py index a3fa4b3e48..5c54a88c8c 100644 --- a/cuda_core/cuda/core/experimental/_linker.py +++ b/cuda_core/cuda/core/experimental/_linker.py @@ -343,7 +343,7 @@ def _exception_manager(self): # our constructor could raise, in which case there's no handle available error_log = self.get_error_log() # Starting Python 3.11 we could also use Exception.add_note() for the same purpose, but - # unfortunately we are still supporting Python 3.9/3.10... + # unfortunately we are still supporting Python 3.10... # Here we rely on both CUDAError and nvJitLinkError have the error string placed in .args[0]. e.args = (e.args[0] + (f"\nLinker error log: {error_log}" if error_log else ""), *e.args[1:]) raise e diff --git a/cuda_core/cuda/core/experimental/_module.py b/cuda_core/cuda/core/experimental/_module.py index 2c7ea3a156..f8ce8f95d0 100644 --- a/cuda_core/cuda/core/experimental/_module.py +++ b/cuda_core/cuda/core/experimental/_module.py @@ -4,7 +4,7 @@ import weakref from collections import namedtuple -from typing import Optional, Union +from typing import Union from warnings import warn from cuda.core.experimental._launch_config import LaunchConfig, _to_native_launch_config @@ -310,7 +310,7 @@ def available_dynamic_shared_memory_per_block(self, num_blocks_per_multiprocesso driver.cuOccupancyAvailableDynamicSMemPerBlock(self._handle, num_blocks_per_multiprocessor, block_size) ) - def max_potential_cluster_size(self, config: LaunchConfig, stream: Optional[Stream] = None) -> int: + def max_potential_cluster_size(self, config: LaunchConfig, stream: Stream | None = None) -> int: """Maximum potential cluster size. The maximum potential cluster size for this kernel and given launch configuration. @@ -332,7 +332,7 @@ def max_potential_cluster_size(self, config: LaunchConfig, stream: Optional[Stre drv_cfg.hStream = stream.handle return handle_return(driver.cuOccupancyMaxPotentialClusterSize(self._handle, drv_cfg)) - def max_active_clusters(self, config: LaunchConfig, stream: Optional[Stream] = None) -> int: + def max_active_clusters(self, config: LaunchConfig, stream: Stream | None = None) -> int: """Maximum number of active clusters on the target device. The maximum number of clusters that could concurrently execute on the target device. @@ -469,7 +469,7 @@ def __new__(self, *args, **kwargs): ) @classmethod - def _init(cls, module, code_type, *, name: str = "", symbol_mapping: Optional[dict] = None): + def _init(cls, module, code_type, *, name: str = "", symbol_mapping: dict | None = None): self = super().__new__(cls) assert code_type in self._supported_code_type, f"{code_type=} is not supported" _lazy_init() @@ -496,7 +496,7 @@ def __reduce__(self): return ObjectCode._reduce_helper, (self._module, self._code_type, self._name, self._sym_map) @staticmethod - def from_cubin(module: Union[bytes, str], *, name: str = "", symbol_mapping: Optional[dict] = None) -> "ObjectCode": + def from_cubin(module: Union[bytes, str], *, name: str = "", symbol_mapping: dict | None = None) -> "ObjectCode": """Create an :class:`ObjectCode` instance from an existing cubin. Parameters @@ -514,7 +514,7 @@ def from_cubin(module: Union[bytes, str], *, name: str = "", symbol_mapping: Opt return ObjectCode._init(module, "cubin", name=name, symbol_mapping=symbol_mapping) @staticmethod - def from_ptx(module: Union[bytes, str], *, name: str = "", symbol_mapping: Optional[dict] = None) -> "ObjectCode": + def from_ptx(module: Union[bytes, str], *, name: str = "", symbol_mapping: dict | None = None) -> "ObjectCode": """Create an :class:`ObjectCode` instance from an existing PTX. Parameters @@ -532,7 +532,7 @@ def from_ptx(module: Union[bytes, str], *, name: str = "", symbol_mapping: Optio return ObjectCode._init(module, "ptx", name=name, symbol_mapping=symbol_mapping) @staticmethod - def from_ltoir(module: Union[bytes, str], *, name: str = "", symbol_mapping: Optional[dict] = None) -> "ObjectCode": + def from_ltoir(module: Union[bytes, str], *, name: str = "", symbol_mapping: dict | None = None) -> "ObjectCode": """Create an :class:`ObjectCode` instance from an existing LTOIR. Parameters @@ -550,9 +550,7 @@ def from_ltoir(module: Union[bytes, str], *, name: str = "", symbol_mapping: Opt return ObjectCode._init(module, "ltoir", name=name, symbol_mapping=symbol_mapping) @staticmethod - def from_fatbin( - module: Union[bytes, str], *, name: str = "", symbol_mapping: Optional[dict] = None - ) -> "ObjectCode": + def from_fatbin(module: Union[bytes, str], *, name: str = "", symbol_mapping: dict | None = None) -> "ObjectCode": """Create an :class:`ObjectCode` instance from an existing fatbin. Parameters @@ -570,9 +568,7 @@ def from_fatbin( return ObjectCode._init(module, "fatbin", name=name, symbol_mapping=symbol_mapping) @staticmethod - def from_object( - module: Union[bytes, str], *, name: str = "", symbol_mapping: Optional[dict] = None - ) -> "ObjectCode": + def from_object(module: Union[bytes, str], *, name: str = "", symbol_mapping: dict | None = None) -> "ObjectCode": """Create an :class:`ObjectCode` instance from an existing object code. Parameters @@ -590,9 +586,7 @@ def from_object( return ObjectCode._init(module, "object", name=name, symbol_mapping=symbol_mapping) @staticmethod - def from_library( - module: Union[bytes, str], *, name: str = "", symbol_mapping: Optional[dict] = None - ) -> "ObjectCode": + def from_library(module: Union[bytes, str], *, name: str = "", symbol_mapping: dict | None = None) -> "ObjectCode": """Create an :class:`ObjectCode` instance from an existing library. Parameters diff --git a/cuda_core/cuda/core/experimental/_program.py b/cuda_core/cuda/core/experimental/_program.py index dee6f001e7..1db453fed1 100644 --- a/cuda_core/cuda/core/experimental/_program.py +++ b/cuda_core/cuda/core/experimental/_program.py @@ -49,7 +49,7 @@ def _nvvm_exception_manager(self): except Exception: error_log = "" # Starting Python 3.11 we could also use Exception.add_note() for the same purpose, but - # unfortunately we are still supporting Python 3.9/3.10... + # unfortunately we are still supporting Python 3.10... e.args = (e.args[0] + (f"\nNVVM program log: {error_log}" if error_log else ""), *e.args[1:]) raise e diff --git a/cuda_core/docs/source/install.rst b/cuda_core/docs/source/install.rst index 68ee4176df..e3dbd5209c 100644 --- a/cuda_core/docs/source/install.rst +++ b/cuda_core/docs/source/install.rst @@ -26,7 +26,7 @@ dependencies are as follows: .. [#f1] Including ``cuda-python``. -``cuda.core`` supports Python 3.9 - 3.13, on Linux (x86-64, arm64) and Windows (x86-64). +``cuda.core`` supports Python 3.10 - 3.14, on Linux (x86-64, arm64) and Windows (x86-64). Installing from PyPI -------------------- diff --git a/cuda_core/pyproject.toml b/cuda_core/pyproject.toml index 8bbfca07d6..0de9cb86d0 100644 --- a/cuda_core/pyproject.toml +++ b/cuda_core/pyproject.toml @@ -14,7 +14,7 @@ dynamic = [ "version", "readme", ] -requires-python = '>=3.9' +requires-python = '>=3.10' description = "cuda.core: (experimental) pythonic CUDA module" authors = [ { name = "NVIDIA Corporation" } @@ -32,11 +32,11 @@ classifiers = [ "Topic :: Scientific/Engineering", "Topic :: Software Development :: Libraries", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Programming Language :: Python :: Implementation :: CPython", "Environment :: GPU :: NVIDIA CUDA", "Environment :: GPU :: NVIDIA CUDA :: 12", diff --git a/cuda_pathfinder/cuda/pathfinder/_dynamic_libs/find_nvidia_dynamic_lib.py b/cuda_pathfinder/cuda/pathfinder/_dynamic_libs/find_nvidia_dynamic_lib.py index 75ebec3a85..65c9f4bf3c 100644 --- a/cuda_pathfinder/cuda/pathfinder/_dynamic_libs/find_nvidia_dynamic_lib.py +++ b/cuda_pathfinder/cuda/pathfinder/_dynamic_libs/find_nvidia_dynamic_lib.py @@ -4,7 +4,6 @@ import glob import os from collections.abc import Sequence -from typing import Optional from cuda.pathfinder._dynamic_libs.load_dl_common import DynamicLibNotFoundError from cuda.pathfinder._dynamic_libs.supported_nvidia_libs import ( @@ -29,7 +28,7 @@ def _no_such_file_in_sub_dirs( def _find_so_using_nvidia_lib_dirs( libname: str, so_basename: str, error_messages: list[str], attachments: list[str] -) -> Optional[str]: +) -> str | None: rel_dirs = SITE_PACKAGES_LIBDIRS_LINUX.get(libname) if rel_dirs is not None: sub_dirs_searched = [] @@ -52,7 +51,7 @@ def _find_so_using_nvidia_lib_dirs( return None -def _find_dll_under_dir(dirpath: str, file_wild: str) -> Optional[str]: +def _find_dll_under_dir(dirpath: str, file_wild: str) -> str | None: for path in sorted(glob.glob(os.path.join(dirpath, file_wild))): if not os.path.isfile(path): continue @@ -63,7 +62,7 @@ def _find_dll_under_dir(dirpath: str, file_wild: str) -> Optional[str]: def _find_dll_using_nvidia_bin_dirs( libname: str, lib_searched_for: str, error_messages: list[str], attachments: list[str] -) -> Optional[str]: +) -> str | None: rel_dirs = SITE_PACKAGES_LIBDIRS_WINDOWS.get(libname) if rel_dirs is not None: sub_dirs_searched = [] @@ -79,7 +78,7 @@ def _find_dll_using_nvidia_bin_dirs( return None -def _find_lib_dir_using_anchor_point(libname: str, anchor_point: str, linux_lib_dir: str) -> Optional[str]: +def _find_lib_dir_using_anchor_point(libname: str, anchor_point: str, linux_lib_dir: str) -> str | None: # Resolve paths for the four cases: # Windows/Linux x nvvm yes/no if IS_WINDOWS: @@ -107,14 +106,14 @@ def _find_lib_dir_using_anchor_point(libname: str, anchor_point: str, linux_lib_ return None -def _find_lib_dir_using_cuda_home(libname: str) -> Optional[str]: +def _find_lib_dir_using_cuda_home(libname: str) -> str | None: cuda_home = get_cuda_home_or_path() if cuda_home is None: return None return _find_lib_dir_using_anchor_point(libname, anchor_point=cuda_home, linux_lib_dir="lib64") -def _find_lib_dir_using_conda_prefix(libname: str) -> Optional[str]: +def _find_lib_dir_using_conda_prefix(libname: str) -> str | None: conda_prefix = os.environ.get("CONDA_PREFIX") if not conda_prefix: return None @@ -125,7 +124,7 @@ def _find_lib_dir_using_conda_prefix(libname: str) -> Optional[str]: def _find_so_using_lib_dir( lib_dir: str, so_basename: str, error_messages: list[str], attachments: list[str] -) -> Optional[str]: +) -> str | None: so_name = os.path.join(lib_dir, so_basename) if os.path.isfile(so_name): return so_name @@ -141,7 +140,7 @@ def _find_so_using_lib_dir( def _find_dll_using_lib_dir( lib_dir: str, libname: str, error_messages: list[str], attachments: list[str] -) -> Optional[str]: +) -> str | None: file_wild = libname + "*.dll" dll_name = _find_dll_under_dir(lib_dir, file_wild) if dll_name is not None: @@ -162,9 +161,9 @@ def __init__(self, libname: str): self.lib_searched_for = f"lib{libname}.so" self.error_messages: list[str] = [] self.attachments: list[str] = [] - self.abs_path: Optional[str] = None + self.abs_path: str | None = None - def try_site_packages(self) -> Optional[str]: + def try_site_packages(self) -> str | None: if IS_WINDOWS: return _find_dll_using_nvidia_bin_dirs( self.libname, @@ -180,13 +179,13 @@ def try_site_packages(self) -> Optional[str]: self.attachments, ) - def try_with_conda_prefix(self) -> Optional[str]: + def try_with_conda_prefix(self) -> str | None: return self._find_using_lib_dir(_find_lib_dir_using_conda_prefix(self.libname)) - def try_with_cuda_home(self) -> Optional[str]: + def try_with_cuda_home(self) -> str | None: return self._find_using_lib_dir(_find_lib_dir_using_cuda_home(self.libname)) - def _find_using_lib_dir(self, lib_dir: Optional[str]) -> Optional[str]: + def _find_using_lib_dir(self, lib_dir: str | None) -> str | None: if lib_dir is None: return None if IS_WINDOWS: diff --git a/cuda_pathfinder/cuda/pathfinder/_dynamic_libs/load_dl_common.py b/cuda_pathfinder/cuda/pathfinder/_dynamic_libs/load_dl_common.py index 416718f5a4..35b988ce93 100644 --- a/cuda_pathfinder/cuda/pathfinder/_dynamic_libs/load_dl_common.py +++ b/cuda_pathfinder/cuda/pathfinder/_dynamic_libs/load_dl_common.py @@ -1,8 +1,8 @@ # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 +from collections.abc import Callable from dataclasses import dataclass -from typing import Callable, Optional from cuda.pathfinder._dynamic_libs.supported_nvidia_libs import DIRECT_DEPENDENCIES @@ -13,7 +13,7 @@ class DynamicLibNotFoundError(RuntimeError): @dataclass class LoadedDL: - abs_path: Optional[str] + abs_path: str | None was_already_loaded_from_elsewhere: bool _handle_uint: int # Platform-agnostic unsigned pointer value diff --git a/cuda_pathfinder/cuda/pathfinder/_dynamic_libs/load_dl_linux.py b/cuda_pathfinder/cuda/pathfinder/_dynamic_libs/load_dl_linux.py index a7de858b73..c3ac22dd5d 100644 --- a/cuda_pathfinder/cuda/pathfinder/_dynamic_libs/load_dl_linux.py +++ b/cuda_pathfinder/cuda/pathfinder/_dynamic_libs/load_dl_linux.py @@ -5,7 +5,7 @@ import ctypes import ctypes.util import os -from typing import Optional, cast +from typing import cast from cuda.pathfinder._dynamic_libs.load_dl_common import LoadedDL from cuda.pathfinder._dynamic_libs.supported_nvidia_libs import ( @@ -76,8 +76,8 @@ class _LinkMapLNameView(ctypes.Structure): assert _LinkMapLNameView.l_name.offset == ctypes.sizeof(ctypes.c_void_p) -def _dl_last_error() -> Optional[str]: - msg_bytes = cast(Optional[bytes], LIBDL.dlerror()) +def _dl_last_error() -> str | None: + msg_bytes = cast(bytes | None, LIBDL.dlerror()) if not msg_bytes: return None # no pending error # Never raises; undecodable bytes are mapped to U+DC80..U+DCFF @@ -131,7 +131,7 @@ def get_candidate_sonames(libname: str) -> list[str]: return candidate_sonames -def check_if_already_loaded_from_elsewhere(libname: str, _have_abs_path: bool) -> Optional[LoadedDL]: +def check_if_already_loaded_from_elsewhere(libname: str, _have_abs_path: bool) -> LoadedDL | None: for soname in get_candidate_sonames(libname): try: handle = ctypes.CDLL(soname, mode=os.RTLD_NOLOAD) @@ -149,7 +149,7 @@ def _load_lib(libname: str, filename: str) -> ctypes.CDLL: return ctypes.CDLL(filename, cdll_mode) -def load_with_system_search(libname: str) -> Optional[LoadedDL]: +def load_with_system_search(libname: str) -> LoadedDL | None: """Try to load a library using system search paths. Args: diff --git a/cuda_pathfinder/cuda/pathfinder/_dynamic_libs/load_dl_windows.py b/cuda_pathfinder/cuda/pathfinder/_dynamic_libs/load_dl_windows.py index 5da6d9b84a..d43d699071 100644 --- a/cuda_pathfinder/cuda/pathfinder/_dynamic_libs/load_dl_windows.py +++ b/cuda_pathfinder/cuda/pathfinder/_dynamic_libs/load_dl_windows.py @@ -5,7 +5,6 @@ import ctypes.wintypes import os import struct -from typing import Optional from cuda.pathfinder._dynamic_libs.load_dl_common import LoadedDL from cuda.pathfinder._dynamic_libs.supported_nvidia_libs import ( @@ -100,7 +99,7 @@ def abs_path_for_dynamic_library(libname: str, handle: ctypes.wintypes.HMODULE) return buffer.value -def check_if_already_loaded_from_elsewhere(libname: str, have_abs_path: bool) -> Optional[LoadedDL]: +def check_if_already_loaded_from_elsewhere(libname: str, have_abs_path: bool) -> LoadedDL | None: for dll_name in SUPPORTED_WINDOWS_DLLS.get(libname, ()): handle = kernel32.GetModuleHandleW(dll_name) if handle: @@ -114,7 +113,7 @@ def check_if_already_loaded_from_elsewhere(libname: str, have_abs_path: bool) -> return None -def load_with_system_search(libname: str) -> Optional[LoadedDL]: +def load_with_system_search(libname: str) -> LoadedDL | None: """Try to load a DLL using system search paths. Args: diff --git a/cuda_pathfinder/cuda/pathfinder/_headers/find_nvidia_headers.py b/cuda_pathfinder/cuda/pathfinder/_headers/find_nvidia_headers.py index 535d4b8003..03c4a4412c 100644 --- a/cuda_pathfinder/cuda/pathfinder/_headers/find_nvidia_headers.py +++ b/cuda_pathfinder/cuda/pathfinder/_headers/find_nvidia_headers.py @@ -4,7 +4,6 @@ import functools import glob import os -from typing import Optional from cuda.pathfinder._headers import supported_nvidia_headers from cuda.pathfinder._utils.env_vars import get_cuda_home_or_path @@ -12,7 +11,7 @@ from cuda.pathfinder._utils.platform_aware import IS_WINDOWS -def _abs_norm(path: Optional[str]) -> Optional[str]: +def _abs_norm(path: str | None) -> str | None: if path: return os.path.normpath(os.path.abspath(path)) return None @@ -22,7 +21,7 @@ def _joined_isfile(dirpath: str, basename: str) -> bool: return os.path.isfile(os.path.join(dirpath, basename)) -def _find_nvshmem_header_directory() -> Optional[str]: +def _find_nvshmem_header_directory() -> str | None: if IS_WINDOWS: # nvshmem has no Windows support. return None @@ -47,7 +46,7 @@ def _find_nvshmem_header_directory() -> Optional[str]: return None -def _find_based_on_ctk_layout(libname: str, h_basename: str, anchor_point: str) -> Optional[str]: +def _find_based_on_ctk_layout(libname: str, h_basename: str, anchor_point: str) -> str | None: parts = [anchor_point] if libname == "nvvm": parts.append(libname) @@ -62,7 +61,7 @@ def _find_based_on_ctk_layout(libname: str, h_basename: str, anchor_point: str) return None -def _find_based_on_conda_layout(libname: str, h_basename: str, conda_prefix: str) -> Optional[str]: +def _find_based_on_conda_layout(libname: str, h_basename: str, conda_prefix: str) -> str | None: if IS_WINDOWS: anchor_point = os.path.join(conda_prefix, "Library") if not os.path.isdir(anchor_point): @@ -79,7 +78,7 @@ def _find_based_on_conda_layout(libname: str, h_basename: str, conda_prefix: str return _find_based_on_ctk_layout(libname, h_basename, anchor_point) -def _find_ctk_header_directory(libname: str) -> Optional[str]: +def _find_ctk_header_directory(libname: str) -> str | None: h_basename = supported_nvidia_headers.SUPPORTED_HEADERS_CTK[libname] candidate_dirs = supported_nvidia_headers.SUPPORTED_SITE_PACKAGE_HEADER_DIRS_CTK[libname] @@ -104,7 +103,7 @@ def _find_ctk_header_directory(libname: str) -> Optional[str]: @functools.cache -def find_nvidia_header_directory(libname: str) -> Optional[str]: +def find_nvidia_header_directory(libname: str) -> str | None: """Locate the header directory for a supported NVIDIA library. Args: diff --git a/cuda_pathfinder/cuda/pathfinder/_utils/env_vars.py b/cuda_pathfinder/cuda/pathfinder/_utils/env_vars.py index 3a7de992c0..cf78a627cb 100644 --- a/cuda_pathfinder/cuda/pathfinder/_utils/env_vars.py +++ b/cuda_pathfinder/cuda/pathfinder/_utils/env_vars.py @@ -3,7 +3,6 @@ import os import warnings -from typing import Optional def _paths_differ(a: str, b: str) -> bool: @@ -33,7 +32,7 @@ def _paths_differ(a: str, b: str) -> bool: return True -def get_cuda_home_or_path() -> Optional[str]: +def get_cuda_home_or_path() -> str | None: cuda_home = os.environ.get("CUDA_HOME") cuda_path = os.environ.get("CUDA_PATH") diff --git a/cuda_pathfinder/cuda/pathfinder/_utils/find_site_packages_dll.py b/cuda_pathfinder/cuda/pathfinder/_utils/find_site_packages_dll.py index 2f5695093c..507355727f 100644 --- a/cuda_pathfinder/cuda/pathfinder/_utils/find_site_packages_dll.py +++ b/cuda_pathfinder/cuda/pathfinder/_utils/find_site_packages_dll.py @@ -11,7 +11,12 @@ def find_all_dll_files_via_metadata() -> dict[str, tuple[str, ...]]: results: collections.defaultdict[str, list[str]] = collections.defaultdict(list) # sort dists for deterministic output - for dist in sorted(importlib.metadata.distributions(), key=lambda d: (d.metadata.get("Name", ""), d.version)): + + for dist in sorted( + importlib.metadata.distributions(), + # `get` exists before 3.12, even though the hints only exist for Python >=3.12 + key=lambda d: (d.metadata.get("Name", ""), d.version), # type: ignore[attr-defined] + ): files = dist.files if not files: continue diff --git a/cuda_pathfinder/cuda/pathfinder/_utils/find_site_packages_so.py b/cuda_pathfinder/cuda/pathfinder/_utils/find_site_packages_so.py index 69e7eea3ad..33ee1f1bcf 100644 --- a/cuda_pathfinder/cuda/pathfinder/_utils/find_site_packages_so.py +++ b/cuda_pathfinder/cuda/pathfinder/_utils/find_site_packages_so.py @@ -23,7 +23,11 @@ def find_all_so_files_via_metadata() -> dict[str, dict[str, tuple[str, ...]]]: ) # sort dists for deterministic output - for dist in sorted(importlib.metadata.distributions(), key=lambda d: (d.metadata.get("Name", ""), d.version)): + for dist in sorted( + importlib.metadata.distributions(), + # `get` exists before 3.12, even though the hints only exist for Python >=3.12 + key=lambda d: (d.metadata.get("Name", ""), d.version), # type: ignore[attr-defined] + ): files = dist.files if not files: continue diff --git a/cuda_pathfinder/pyproject.toml b/cuda_pathfinder/pyproject.toml index c2029a0220..ee0a4e794e 100644 --- a/cuda_pathfinder/pyproject.toml +++ b/cuda_pathfinder/pyproject.toml @@ -6,7 +6,7 @@ name = "cuda-pathfinder" description = "Pathfinder for CUDA components" authors = [{ name = "NVIDIA Corporation", email = "cuda-python-conduct@nvidia.com" }] license = "Apache-2.0" -requires-python = ">=3.9" +requires-python = ">=3.10" dynamic = ["version", "readme"] dependencies = [] @@ -94,7 +94,7 @@ inline-quotes = "double" [tool.mypy] # Basic settings -python_version = "3.9" +python_version = "3.10" explicit_package_bases = true warn_return_any = true warn_unused_configs = true diff --git a/cuda_pathfinder/tests/spawned_process_runner.py b/cuda_pathfinder/tests/spawned_process_runner.py index 154178b2a2..f4440743f5 100644 --- a/cuda_pathfinder/tests/spawned_process_runner.py +++ b/cuda_pathfinder/tests/spawned_process_runner.py @@ -5,10 +5,10 @@ import queue # for Empty import sys import traceback -from collections.abc import Sequence +from collections.abc import Callable, Sequence from dataclasses import dataclass from io import StringIO -from typing import Any, Callable, Optional +from typing import Any PROCESS_KILLED = -9 PROCESS_NO_RESULT = -999 @@ -61,9 +61,9 @@ def __call__(self): def run_in_spawned_child_process( target: Callable[..., None], *, - args: Optional[Sequence[Any]] = None, - kwargs: Optional[dict[str, Any]] = None, - timeout: Optional[float] = None, + args: Sequence[Any] | None = None, + kwargs: dict[str, Any] | None = None, + timeout: float | None = None, rethrow: bool = False, ) -> CompletedProcess: """Run `target` in a spawned child process, capturing stdout/stderr. diff --git a/cuda_python/pyproject.toml b/cuda_python/pyproject.toml index fd6cacaf2a..9048f5818b 100644 --- a/cuda_python/pyproject.toml +++ b/cuda_python/pyproject.toml @@ -22,16 +22,18 @@ classifiers = [ "Intended Audience :: Science/Research", "Intended Audience :: End Users/Desktop", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Programming Language :: Python :: Implementation :: CPython", "Environment :: GPU :: NVIDIA CUDA", "Environment :: GPU :: NVIDIA CUDA :: 12", + "Environment :: GPU :: NVIDIA CUDA :: 13", ] dynamic = ["version", "dependencies", "optional-dependencies"] +requires-python = ">=3.10" [project.urls] homepage = "https://nvidia.github.io/cuda-python/" diff --git a/ruff.toml b/ruff.toml index 79c66e862c..6312d3e9ef 100644 --- a/ruff.toml +++ b/ruff.toml @@ -3,7 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 line-length = 120 respect-gitignore = true -target-version = "py39" +target-version = "py310" [format] docstring-code-format = true @@ -40,6 +40,7 @@ ignore = [ "S101", # asserts "S311", # allow use of the random.* even though many are not cryptographically secure "S404", # allow importing the subprocess module + "B905", # preserve the default behavior of `zip` without the explicit `strict` argument ] exclude = ["**/_version.py"] diff --git a/toolshed/make_site_packages_libdirs.py b/toolshed/make_site_packages_libdirs.py index d84d821700..00a495a095 100755 --- a/toolshed/make_site_packages_libdirs.py +++ b/toolshed/make_site_packages_libdirs.py @@ -8,7 +8,7 @@ import argparse import os import re -from typing import Dict, Optional, Set +from typing import Dict, Set _SITE_PACKAGES_RE = re.compile(r"(?i)^.*?/site-packages/") @@ -38,7 +38,7 @@ def parse_lines_linux(lines) -> Dict[str, Set[str]]: return d -def extract_libname_from_dll(fname: str) -> Optional[str]: +def extract_libname_from_dll(fname: str) -> str | None: """Return base libname per the heuristic, or None if not a .dll.""" base = os.path.basename(fname) if not base.lower().endswith(".dll"):