Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
bdd3f14
DEV: run_tests.sh uses pytest-run-parllel and install it on 3.14t and…
seberg Jun 10, 2026
e70ca02
TST: Mark tests as thread-unsafe or limit the number of threads
seberg Jun 10, 2026
8803ddb
TST: use tmp_path fixture in cufile (and mark some as unsafe)
seberg Jun 10, 2026
1c6cc15
TST: Move graph definnitions inline and mark "global" ones as thread-…
seberg Jun 10, 2026
9f766f2
TST: Fixup memory tests, mostly work around issue when tearing down m…
seberg Jun 10, 2026
a305fd8
TST: Thread unsafe markers for test_managed_ops
seberg Jun 10, 2026
2acae48
Avoid interactive backend when using run_tests.sh locally
seberg Jun 10, 2026
57a2737
Use indirect fixtures for a nicer pattern and avoid thread issues
seberg Jun 10, 2026
2d28ac9
Make latch-kernel helper compile only once
seberg Jun 11, 2026
54c4f3d
Install pytest-run-parallel explicitly in CI
seberg Jun 11, 2026
d10264d
Move pytest-run-parallel setup (and hopefully actually make it work)
seberg Jun 11, 2026
03b8cdf
Revert SMResource splitting fix, the problem was the test
seberg Jul 8, 2026
94d7367
TST: Reorganize saxpy_rdc into fixture for thread-safety
seberg Jul 8, 2026
4f2cc7a
Limit threads for event test that otherwise seems to ptentially fail
seberg Jul 8, 2026
7ee0cf2
fix style issues (had to update some pre-commit stuff)
seberg Jul 8, 2026
1f55216
TST: Mark device-persistence-mode test as thread-unsafe
seberg Jul 8, 2026
897ad61
Add identity preserving pattern to critical_sections
seberg Jul 14, 2026
bed34b2
TST: Test fails in CI, assume that few enough threads eventually pass...
seberg Jul 14, 2026
705deac
Merge branch 'main' into ft-testing
seberg Jul 14, 2026
86cdae8
TST: Add another sync to guard against potential deadlocks (seems I m…
seberg Jul 14, 2026
aee59a9
TST: Limit threads for another LatchKernel test
seberg Jul 14, 2026
36af61c
TST: Force test_helpers to single threaded on windows to avoid crash
seberg Jul 14, 2026
1de0de0
TST: Another test that cannot run threaded on windows
seberg Jul 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 14 additions & 11 deletions ci/tools/run-tests
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env bash

# 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

Expand All @@ -20,6 +20,14 @@ fi

test_module=${1}

FREE_THREADING=""
PYTEST_PARALLEL_ARGS=()
if python -c 'import sys; assert not sys._is_gil_enabled()' 2> /dev/null; then
FREE_THREADING="-ft"
PYTEST_PARALLEL_ARGS=(--parallel-threads=4)
pip install pytest-run-parallel
fi

# For standard modes, install pathfinder up front (it is a direct dependency
# of bindings, and a transitive dependency of core). Nightly modes install
# all wheels together in a single pip call further below.
Expand All @@ -36,7 +44,7 @@ if [[ "${test_module}" == "pathfinder" ]]; then
"LD:${CUDA_PATHFINDER_TEST_LOAD_NVIDIA_DYNAMIC_LIB_STRICTNESS} " \
"FH:${CUDA_PATHFINDER_TEST_FIND_NVIDIA_HEADERS_STRICTNESS} " \
"BC:${CUDA_PATHFINDER_TEST_FIND_NVIDIA_BITCODE_LIB_STRICTNESS}"
pytest -ra -s -v --durations=0 tests/ |& tee /tmp/pathfinder_test_log.txt
pytest -ra -s -v --durations=0 "${PYTEST_PARALLEL_ARGS[@]}" tests/ |& tee /tmp/pathfinder_test_log.txt
# Report the number of "INFO test_" lines (including zero)
# to support quick validations based on GHA log archives.
line_count=$(awk '/^INFO test_/ {count++} END {print count+0}' /tmp/pathfinder_test_log.txt)
Expand All @@ -51,21 +59,16 @@ elif [[ "${test_module}" == "bindings" ]]; then
pip install $(ls "${CUDA_BINDINGS_ARTIFACTS_DIR}"/*.whl)[all] --group test
fi
echo "Running bindings tests"
${SANITIZER_CMD} pytest -rxXs -v --durations=0 --randomly-dont-reorganize tests/
${SANITIZER_CMD} pytest -rxXs -v --durations=0 --randomly-dont-reorganize "${PYTEST_PARALLEL_ARGS[@]}" tests/
if [[ "${SKIP_CYTHON_TEST}" == 0 ]]; then
${SANITIZER_CMD} pytest -rxXs -v --durations=0 --randomly-dont-reorganize tests/cython
${SANITIZER_CMD} pytest -rxXs -v --durations=0 --randomly-dont-reorganize "${PYTEST_PARALLEL_ARGS[@]}" tests/cython
fi
popd
elif [[ "${test_module}" == "core" || "${test_module}" == nightly-* ]]; then
# Shared setup for core and nightly modes.
TEST_CUDA_MAJOR="$(cut -d '.' -f 1 <<< ${CUDA_VER})"
TEST_CUDA_MAJOR_MINOR="$(cut -d '.' -f 1-2 <<< "${CUDA_VER}")"

FREE_THREADING=""
if python -c 'import sys; assert not sys._is_gil_enabled()' 2> /dev/null; then
FREE_THREADING+="-ft"
fi

# Resolve bindings based on BINDINGS_SOURCE (set by env-vars):
# main/backport → local wheel from artifacts dir
# published → install from PyPI by version
Expand Down Expand Up @@ -105,11 +108,11 @@ elif [[ "${test_module}" == "core" || "${test_module}" == nightly-* ]]; then
echo "Installed packages before core tests:"
pip list
echo "Running core tests"
${SANITIZER_CMD} pytest -rxXs -v --durations=0 --randomly-dont-reorganize tests/
${SANITIZER_CMD} pytest -rxXs -v --durations=0 --randomly-dont-reorganize "${PYTEST_PARALLEL_ARGS[@]}" tests/
# Currently our CI always installs the latest bindings (from either major version).
# This is not compatible with the test requirements.
if [[ "${SKIP_CYTHON_TEST}" == 0 ]]; then
${SANITIZER_CMD} pytest -rxXs -v --durations=0 --randomly-dont-reorganize tests/cython
${SANITIZER_CMD} pytest -rxXs -v --durations=0 --randomly-dont-reorganize "${PYTEST_PARALLEL_ARGS[@]}" tests/cython
fi
popd
elif [[ "${test_module}" == "nightly-cuda-core" ]]; then
Expand Down
2 changes: 1 addition & 1 deletion cuda_bindings/tests/test_cufile.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def ctx():
(err,) = cuda.cuCtxSetCurrent(ctx)
assert err == cuda.CUresult.CUDA_SUCCESS

yield
yield ctx

cuda.cuDevicePrimaryCtxRelease(device)

Expand Down
1 change: 1 addition & 0 deletions cuda_bindings/tests/test_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def test_example(example):

env = os.environ.copy()
env["CUDA_BINDINGS_SKIP_EXAMPLE"] = "100"
env["MPLBACKEND"] = "Agg" # avoid plt.show() from blocking

process = subprocess.run([sys.executable, example], capture_output=True, env=env) # noqa: S603
# returncode is a special value used in the examples to indicate that system requirements are not met.
Expand Down
3 changes: 0 additions & 3 deletions cuda_core/cuda/core/_device_resources.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
#
# SPDX-License-Identifier: Apache-2.0

cimport cython

from cuda.bindings cimport cydriver
from cuda.core._resource_handles cimport ContextHandle, GreenCtxHandle

Expand All @@ -17,7 +15,6 @@ cdef class SMResource:
unsigned int _flags
bint _is_usable
object __weakref__
cython.pymutex _split_mutex

@staticmethod
cdef SMResource _from_dev_resource(cydriver.CUdevResource res, int device_id)
Expand Down
9 changes: 4 additions & 5 deletions cuda_core/cuda/core/_device_resources.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -520,11 +520,10 @@ cdef class SMResource:
)
_resolve_group_count(opts)
_check_green_ctx_support()
with self._split_mutex:
if _can_use_structured_sm_split():
return _split_with_general_api(self, opts, dry_run)
# SplitByCount requires the same 12.4+ as green ctx support (already checked above)
return _split_with_count_api(self, opts, dry_run)
if _can_use_structured_sm_split():
return _split_with_general_api(self, opts, dry_run)
# SplitByCount requires the same 12.4+ as green ctx support (already checked above)
return _split_with_count_api(self, opts, dry_run)


cdef class WorkqueueResource:
Expand Down
5 changes: 4 additions & 1 deletion cuda_core/cuda/core/_memory/_buffer.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,11 @@ cdef class Buffer:
@cython.critical_section
def ipc_descriptor(self) -> IPCBufferDescriptor:
"""Descriptor for sharing this buffer with other processes."""
cdef object ipc_data
if self._ipc_data is None:
self._ipc_data = IPCDataForBuffer(_ipc.Buffer_get_ipc_descriptor(self), False)
ipc_data = IPCDataForBuffer(_ipc.Buffer_get_ipc_descriptor(self), False)
if self._ipc_data is None:
self._ipc_data = ipc_data
return self._ipc_data.ipc_descriptor

def close(self, stream: Stream | GraphBuilder | None = None) -> None:
Expand Down
5 changes: 4 additions & 1 deletion cuda_core/cuda/core/_memory/_memory_pool.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,11 @@ cdef class _MemPool(MemoryResource):
@cython.critical_section
def attributes(self) -> _MemPoolAttributes:
"""Memory pool attributes."""
cdef _MemPoolAttributes attributes
if self._attributes is None:
self._attributes = _MemPoolAttributes._init(self._h_pool)
attributes = _MemPoolAttributes._init(self._h_pool)
if self._attributes is None:
self._attributes = attributes
return self._attributes

@property
Expand Down
24 changes: 17 additions & 7 deletions cuda_core/cuda/core/_memoryview.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -542,13 +542,16 @@ cdef class StridedMemoryView:

@cython.critical_section
cdef inline _StridedLayout get_layout(self):
cdef _StridedLayout layout
if self._layout is None:
if self.dl_tensor:
self._layout = layout_from_dlpack(self.dl_tensor)
layout = layout_from_dlpack(self.dl_tensor)
elif self.metadata is not None:
self._layout = layout_from_cai(self.metadata)
layout = layout_from_cai(self.metadata)
else:
raise ValueError("Cannot infer layout from the exporting object")
if self._layout is None:
self._layout = layout
return self._layout

@cython.critical_section
Expand All @@ -558,24 +561,31 @@ cdef class StridedMemoryView:
If the SMV was created from a Buffer, it will return the same Buffer instance.
Otherwise, it will create a new instance with owner set to the exporting object.
"""
cdef object buffer
if self._buffer is None:
if isinstance(self.exporting_obj, Buffer):
self._buffer = self.exporting_obj
buffer = self.exporting_obj
else:
self._buffer = Buffer.from_handle(self.ptr, 0, owner=self.exporting_obj)
buffer = Buffer.from_handle(self.ptr, 0, owner=self.exporting_obj)
if self._buffer is None:
self._buffer = buffer
return self._buffer

@cython.critical_section
cdef inline object get_dtype(self):
cdef object dtype
if self._dtype is None:
dtype = None
if self.dl_tensor != NULL:
self._dtype = dtype_dlpack_to_numpy(&self.dl_tensor.dtype)
dtype = dtype_dlpack_to_numpy(&self.dl_tensor.dtype)
elif isinstance(self.metadata, int):
# AOTI dtype code stored by the torch tensor bridge
self._dtype = _get_tensor_bridge().resolve_aoti_dtype(
dtype = _get_tensor_bridge().resolve_aoti_dtype(
self.metadata)
elif self.metadata is not None:
self._dtype = _typestr2dtype(self.metadata["typestr"])
dtype = _typestr2dtype(self.metadata["typestr"])
if self._dtype is None:
self._dtype = dtype
return self._dtype


Expand Down
22 changes: 16 additions & 6 deletions cuda_core/cuda/core/_module.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -458,8 +458,11 @@ cdef class Kernel:
@cython.critical_section
def attributes(self) -> KernelAttributes:
"""Get the read-only attributes of this kernel."""
cdef KernelAttributes attributes
if self._attributes is None:
self._attributes = KernelAttributes._init(self._h_kernel)
attributes = KernelAttributes._init(self._h_kernel)
if self._attributes is None:
self._attributes = attributes
return self._attributes

cdef tuple _get_arguments_info(self, bint param_info=False):
Expand Down Expand Up @@ -506,8 +509,11 @@ cdef class Kernel:
@cython.critical_section
def occupancy(self) -> KernelOccupancy:
"""Get the occupancy information for launching this kernel."""
cdef KernelOccupancy occupancy
if self._occupancy is None:
self._occupancy = KernelOccupancy._init(self._h_kernel)
occupancy = KernelOccupancy._init(self._h_kernel)
if self._occupancy is None:
self._occupancy = occupancy
return self._occupancy

@property
Expand Down Expand Up @@ -747,24 +753,28 @@ cdef class ObjectCode:

@cython.critical_section
cdef int _lazy_load_module(self) except -1:
cdef LibraryHandle _h_library
if self._h_library:
return 0
module = self._module
cdef bytes path_bytes
if isinstance(module, str):
path_bytes = module.encode()
self._h_library = create_library_handle_from_file(<const char*>path_bytes)
_h_library = create_library_handle_from_file(<const char*>path_bytes)
elif isinstance(module, (bytes, bytearray)):
self._h_library = create_library_handle_from_data(<const void*><char*>module)
_h_library = create_library_handle_from_data(<const void*><char*>module)
elif isinstance(module, PathLike):
path_bytes = fsencode(module)
self._h_library = create_library_handle_from_file(<const char*>path_bytes)
_h_library = create_library_handle_from_file(<const char*>path_bytes)
else:
assert_type_str_or_bytes_like(module)
raise_code_path_meant_to_be_unreachable()
return -1
if not self._h_library:
if not _h_library:
HANDLE_RETURN(get_last_error())

if not self._h_library:
self._h_library = _h_library
return 0

def get_kernel(self, name: str | bytes) -> Kernel:
Expand Down
12 changes: 12 additions & 0 deletions cuda_core/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,18 @@ def wrapper(*args, **kwargs):
kwargs["mempool_device_x2"] = _mempool_device_impl(2)
if "mempool_device_x3" in kwargs:
kwargs["mempool_device_x3"] = _mempool_device_impl(3)

# These are used by test_green_context.py. The original fixtures include
# pytest.skip() but that should have correctly fired by this time.
if "sm_resource" in kwargs:
kwargs["sm_resource"] = device.resources.sm
if "wq_resource" in kwargs:
kwargs["wq_resource"] = device.resources.workqueue
if "green_ctx" in kwargs:
from cuda.core import ContextOptions, SMResourceOptions

groups, _ = device.resources.sm.split(SMResourceOptions(count=None))
kwargs["green_ctx"] = device.create_context(ContextOptions(resources=[groups[0]]))
return func(*args, **kwargs)

wrapper._cuda_core_worker_cuda_wrapped = True
Expand Down
Loading
Loading