diff --git a/cuda_core/tests/conftest.py b/cuda_core/tests/conftest.py index db9761a3ce4..8d55a233bf7 100644 --- a/cuda_core/tests/conftest.py +++ b/cuda_core/tests/conftest.py @@ -1,15 +1,16 @@ -# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 +import multiprocessing + import helpers +import pytest try: from cuda.bindings import driver except ImportError: from cuda import cuda as driver -import multiprocessing -import pytest from cuda.core.experimental import Device, DeviceMemoryResource, DeviceMemoryResourceOptions, _device from cuda.core.experimental._utils.cuda_utils import handle_return @@ -85,6 +86,10 @@ def ipc_device(): if not device.properties.handle_type_posix_file_descriptor_supported: pytest.skip("Device does not support IPC") + # Skip on WSL or if driver rejects IPC-enabled mempool creation on this platform/device + if helpers.IS_WSL or not helpers.supports_ipc_mempool(device): + pytest.skip("Driver rejects IPC-enabled mempool creation on this platform") + return device diff --git a/cuda_core/tests/helpers.py b/cuda_core/tests/helpers.py index f039802ca81..10af3dcc223 100644 --- a/cuda_core/tests/helpers.py +++ b/cuda_core/tests/helpers.py @@ -2,6 +2,8 @@ # SPDX-License-Identifier: Apache-2.0 import os +import pathlib +import sys CUDA_PATH = os.environ.get("CUDA_PATH") CUDA_INCLUDE_PATH = None @@ -14,3 +16,18 @@ path = os.path.join(path, "cccl") if os.path.isdir(path): CCCL_INCLUDE_PATHS = (path,) + CCCL_INCLUDE_PATHS + + +try: + import cuda_python_test_helpers +except ImportError: + # Import shared platform helpers for tests across repos + sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2] / "cuda_python_test_helpers")) + import cuda_python_test_helpers + + +IS_WSL = cuda_python_test_helpers.IS_WSL +supports_ipc_mempool = cuda_python_test_helpers.supports_ipc_mempool + + +del cuda_python_test_helpers diff --git a/cuda_core/tests/memory_ipc/test_errors.py b/cuda_core/tests/memory_ipc/test_errors.py index 3e8265b39c7..d1a235603da 100644 --- a/cuda_core/tests/memory_ipc/test_errors.py +++ b/cuda_core/tests/memory_ipc/test_errors.py @@ -8,6 +8,8 @@ from cuda.core.experimental import Buffer, Device, DeviceMemoryResource, DeviceMemoryResourceOptions from cuda.core.experimental._utils.cuda_utils import CUDAError +from cuda_python_test_helpers import supports_ipc_mempool + CHILD_TIMEOUT_SEC = 20 NBYTES = 64 POOL_SIZE = 2097152 @@ -18,6 +20,10 @@ class ChildErrorHarness: PARENT_ACTION, CHILD_ACTION, and ASSERT (see below for examples).""" def test_main(self, ipc_device, ipc_memory_resource): + if not supports_ipc_mempool(ipc_device): + import pytest + + pytest.skip("Driver rejects IPC-enabled mempool creation on this platform") """Parent process that checks child errors.""" # Attach fixtures to this object for convenience. These can be accessed # from PARENT_ACTION. diff --git a/cuda_core/tests/memory_ipc/test_memory_ipc.py b/cuda_core/tests/memory_ipc/test_memory_ipc.py index f766a260238..c980a7ad848 100644 --- a/cuda_core/tests/memory_ipc/test_memory_ipc.py +++ b/cuda_core/tests/memory_ipc/test_memory_ipc.py @@ -6,6 +6,8 @@ from cuda.core.experimental import Buffer, DeviceMemoryResource from utility import IPCBufferTestHelper +from cuda_python_test_helpers import supports_ipc_mempool + CHILD_TIMEOUT_SEC = 20 NBYTES = 64 NWORKERS = 2 @@ -14,6 +16,10 @@ class TestIpcMempool: def test_main(self, ipc_device, ipc_memory_resource): + if not supports_ipc_mempool(ipc_device): + import pytest + + pytest.skip("Driver rejects IPC-enabled mempool creation on this platform") """Test IPC with memory pools.""" # Set up the IPC-enabled memory pool and share it. device = ipc_device @@ -51,6 +57,10 @@ def child_main(self, device, mr, queue): class TestIPCMempoolMultiple: def test_main(self, ipc_device, ipc_memory_resource): + if not supports_ipc_mempool(ipc_device): + import pytest + + pytest.skip("Driver rejects IPC-enabled mempool creation on this platform") """Test IPC with memory pools using multiple processes.""" # Construct an IPC-enabled memory resource and share it with two children. device = ipc_device @@ -99,6 +109,10 @@ def child_main(self, device, mr, idx, queue): class TestIPCSharedAllocationHandleAndBufferDescriptors: def test_main(self, ipc_device, ipc_memory_resource): + if not supports_ipc_mempool(ipc_device): + import pytest + + pytest.skip("Driver rejects IPC-enabled mempool creation on this platform") """ Demonstrate that a memory pool allocation handle can be reused for IPC with multiple processes. Uses buffer descriptors. diff --git a/cuda_core/tests/memory_ipc/test_send_buffers.py b/cuda_core/tests/memory_ipc/test_send_buffers.py index 783030060b3..19685a94f77 100644 --- a/cuda_core/tests/memory_ipc/test_send_buffers.py +++ b/cuda_core/tests/memory_ipc/test_send_buffers.py @@ -8,6 +8,8 @@ from cuda.core.experimental import DeviceMemoryResource, DeviceMemoryResourceOptions from utility import IPCBufferTestHelper +from cuda_python_test_helpers import supports_ipc_mempool + CHILD_TIMEOUT_SEC = 20 NBYTES = 64 NMRS = 3 @@ -18,6 +20,8 @@ @pytest.mark.parametrize("nmrs", (1, NMRS)) def test_ipc_send_buffers(ipc_device, nmrs): """Test passing buffers sourced from multiple memory resources.""" + if not supports_ipc_mempool(ipc_device): + pytest.skip("Driver rejects IPC-enabled mempool creation on this platform") # Set up several IPC-enabled memory pools. device = ipc_device options = DeviceMemoryResourceOptions(max_size=POOL_SIZE, ipc_enabled=True) diff --git a/cuda_core/tests/memory_ipc/test_workerpool.py b/cuda_core/tests/memory_ipc/test_workerpool.py index a54fced4b4d..6372b5668dc 100644 --- a/cuda_core/tests/memory_ipc/test_workerpool.py +++ b/cuda_core/tests/memory_ipc/test_workerpool.py @@ -9,6 +9,8 @@ from cuda.core.experimental import Buffer, Device, DeviceMemoryResource, DeviceMemoryResourceOptions from utility import IPCBufferTestHelper +from cuda_python_test_helpers import supports_ipc_mempool + CHILD_TIMEOUT_SEC = 20 NBYTES = 64 NWORKERS = 2 @@ -28,6 +30,8 @@ class TestIpcWorkerPool: @pytest.mark.parametrize("nmrs", (1, NMRS)) def test_main(self, ipc_device, nmrs): + if not supports_ipc_mempool(ipc_device): + pytest.skip("Driver rejects IPC-enabled mempool creation on this platform") device = ipc_device options = DeviceMemoryResourceOptions(max_size=POOL_SIZE, ipc_enabled=True) mrs = [DeviceMemoryResource(device, options=options) for _ in range(nmrs)] @@ -62,6 +66,8 @@ def init_worker(mrs): @pytest.mark.parametrize("nmrs", (1, NMRS)) def test_main(self, ipc_device, nmrs): + if not supports_ipc_mempool(ipc_device): + pytest.skip("Driver rejects IPC-enabled mempool creation on this platform") device = ipc_device options = DeviceMemoryResourceOptions(max_size=POOL_SIZE, ipc_enabled=True) mrs = [DeviceMemoryResource(device, options=options) for _ in range(nmrs)] @@ -104,6 +110,8 @@ def init_worker(mrs): @pytest.mark.parametrize("nmrs", (1, NMRS)) def test_main(self, ipc_device, nmrs): + if not supports_ipc_mempool(ipc_device): + pytest.skip("Driver rejects IPC-enabled mempool creation on this platform") device = ipc_device options = DeviceMemoryResourceOptions(max_size=POOL_SIZE, ipc_enabled=True) mrs = [DeviceMemoryResource(device, options=options) for _ in range(nmrs)] diff --git a/cuda_core/tests/test_event.py b/cuda_core/tests/test_event.py index cad06679091..746121946db 100644 --- a/cuda_core/tests/test_event.py +++ b/cuda_core/tests/test_event.py @@ -2,8 +2,6 @@ # SPDX-License-Identifier: Apache-2.0 import os -import pathlib -import platform import time import cuda.core.experimental @@ -22,10 +20,7 @@ ) from conftest import skipif_need_cuda_headers - - -def platform_is_wsl(): - return platform.system() == "Linux" and "microsoft" in pathlib.Path("/proc/version").read_text().lower() +from cuda_python_test_helpers import IS_WSL def test_event_init_disabled(): @@ -47,7 +42,7 @@ def test_timing_success(init_cuda): # We only want to exercise the __sub__ method, this test is not meant # to stress-test the CUDA driver or time.sleep(). delay_ms = delay_seconds * 1000 - if os.name == "nt" or platform_is_wsl(): # noqa: SIM108 + if os.name == "nt" or IS_WSL: # noqa: SIM108 # For Python <=3.10, the Windows timer resolution is typically limited to 15.6 ms by default. generous_tolerance = 100 else: diff --git a/cuda_core/tests/test_memory.py b/cuda_core/tests/test_memory.py index 8c980837ecb..cfbc92977a8 100644 --- a/cuda_core/tests/test_memory.py +++ b/cuda_core/tests/test_memory.py @@ -28,6 +28,8 @@ from cuda.core.experimental._utils.cuda_utils import handle_return from cuda.core.experimental.utils import StridedMemoryView +from cuda_python_test_helpers import supports_ipc_mempool + POOL_SIZE = 2097152 # 2MB size @@ -529,6 +531,9 @@ def test_mempool_attributes(ipc_enabled, mempool_device, property_name, expected if platform.system() == "Windows": return # IPC not implemented for Windows + if ipc_enabled and not supports_ipc_mempool(device): + pytest.skip("Driver rejects IPC-enabled mempool creation on this platform") + options = DeviceMemoryResourceOptions(max_size=POOL_SIZE, ipc_enabled=ipc_enabled) mr = DeviceMemoryResource(device, options=options) assert mr.is_ipc_enabled == ipc_enabled @@ -567,6 +572,10 @@ def test_mempool_attributes(ipc_enabled, mempool_device, property_name, expected def test_mempool_attributes_ownership(mempool_device): """Ensure the attributes bundle handles references correctly.""" device = mempool_device + # Skip if IPC mempool is not supported on this platform/device + if not supports_ipc_mempool(device): + pytest.skip("Driver rejects IPC-enabled mempool creation on this platform") + mr = DeviceMemoryResource(device, dict(max_size=POOL_SIZE)) attributes = mr.attributes mr.close() diff --git a/cuda_python_test_helpers/cuda_python_test_helpers/__init__.py b/cuda_python_test_helpers/cuda_python_test_helpers/__init__.py new file mode 100644 index 00000000000..5996498d922 --- /dev/null +++ b/cuda_python_test_helpers/cuda_python_test_helpers/__init__.py @@ -0,0 +1,62 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +import functools +import os +from contextlib import suppress +from typing import Union + +from cuda.core.experimental._utils.cuda_utils import handle_return + + +def _detect_wsl() -> bool: + data = "" + with suppress(Exception), open("/proc/sys/kernel/osrelease") as f: + data = f.read().lower() + if "microsoft" in data or "wsl" in data: + return True + return any(os.environ.get(k) for k in ("WSL_DISTRO_NAME", "WSL_INTEROP")) + + +IS_WSL: bool = _detect_wsl() + + +@functools.cache +def supports_ipc_mempool(device_id: Union[int, object]) -> bool: + """Return True if mempool IPC via POSIX file descriptor is supported. + + Uses cuDeviceGetAttribute(CU_DEVICE_ATTRIBUTE_MEMPOOL_SUPPORTED_HANDLE_TYPES) + to check for CU_MEM_HANDLE_TYPE_POSIX_FILE_DESCRIPTOR support. Does not + require an active CUDA context. + """ + if _detect_wsl(): + return False + + try: + # Lazy import to avoid hard dependency when not running GPU tests + try: + from cuda.bindings import driver # type: ignore + except Exception: + from cuda import cuda as driver # type: ignore + + # Initialize CUDA + handle_return(driver.cuInit(0)) + + # Resolve device id from int or Device-like object + dev_id = int(getattr(device_id, "device_id", device_id)) + + # Query supported mempool handle types bitmask + attr = driver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MEMPOOL_SUPPORTED_HANDLE_TYPES + mask = handle_return(driver.cuDeviceGetAttribute(attr, dev_id)) + + # Check POSIX FD handle type support via bitmask + posix_fd = driver.CUmemAllocationHandleType.CU_MEM_HANDLE_TYPE_POSIX_FILE_DESCRIPTOR + return (int(mask) & int(posix_fd)) != 0 + except Exception: + return False + + +__all__ = [ + "IS_WSL", + "supports_ipc_mempool", +] diff --git a/cuda_python_test_helpers/pyproject.toml b/cuda_python_test_helpers/pyproject.toml new file mode 100644 index 00000000000..85652b61c50 --- /dev/null +++ b/cuda_python_test_helpers/pyproject.toml @@ -0,0 +1,25 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +[build-system] +requires = ["setuptools>=77.0.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "cuda-python-test-helpers" +version = "0.1.0" +description = "Shared test helpers for CUDA Python projects" +readme = {file = "README.md", content-type = "text/markdown"} +authors = [{ name = "NVIDIA Corporation" }] +license = "Apache-2.0" +requires-python = ">=3.9" +classifiers = [ + "Programming Language :: Python :: 3 :: Only", + "Operating System :: POSIX :: Linux", +] + +[tool.setuptools] +packages = ["cuda_python_test_helpers"] + +[project.urls] +repository = "https://github.com/NVIDIA/cuda-python"