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"):