Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
22 changes: 21 additions & 1 deletion ci/test_cuda_compute_minimal_python.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ fi
# full cu* extras because those pull in numba/numba-cuda.
CUDA_CCCL_WHEEL_PATH="$(ls "${wheelhouse_dir}"/cuda_cccl-*.whl)"
python -m pip install "${CUDA_CCCL_WHEEL_PATH}[minimal-cu${cuda_major_version}]"
python -m pip install pytest pytest-xdist
python -m pip install pytest pytest-xdist pytest-run-parallel

cd "${repo_root}/python/cuda_cccl/tests/"
python -m pytest -n 6 -v compute/test_no_numba.py
Expand All @@ -44,4 +44,24 @@ if [[ "${py_version}" == "3.14t" ]]; then
compute/test_free_threading_stress.py \
compute/test_multi_cc_serialization.py::test_aot_build_result_load_failure_is_shared_and_retryable \
compute/test_multi_cc_serialization.py::test_aot_serialization_waits_for_canonical_first_load

# Broad thread-safety sweep (pytest-run-parallel): re-run the numba-free
# functional suite with each test executed concurrently across threads
# (barrier-synchronized start), stressing the process-wide build cache,
# single-flight coordination, and the Cython bindings from many threads at
# once. Complements test_free_threading_stress.py above, which targets specific
# shared-object scenarios by hand. -n 0 so the threads share one interpreter.
#
# --parallel-threads=2 matches CuPy's free-threading CI (the closest GPU
# precedent); a small fixed count bounds GPU-memory pressure from concurrent
# kernels and stays reproducible across runners, unlike =auto (the runner's
# logical-core count).
#
# Fail fast if the interpreter is not actually GIL-free (wrong build /
# PYTHON_GIL=1): pytest-run-parallel does NOT catch a GIL that is enabled from
# the start -- it would run threads GIL-serialized and pass vacuously. (A GIL
# *re-enabled mid-run* by a non-free-threaded import IS caught by the plugin,
# which is why we do not pass --ignore-gil-enabled.)
python -c "import sys; assert not sys._is_gil_enabled(), 'GIL is enabled; parallel sweep has no signal'"
python -m pytest -n 0 -v --parallel-threads=2 compute/test_no_numba.py
fi
22 changes: 19 additions & 3 deletions python/cuda_cccl/tests/compute/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,21 @@ def cuda_stream() -> Generator[Stream, None, None]:


@pytest.fixture(scope="function", autouse=True)
def verify_sass(request, monkeypatch):
def verify_sass(request):
if request.node.get_closest_marker("no_verify_sass"):
return

if not check_ldl_stl_in_sass:
print("not checking sass")
return

# Pull monkeypatch dynamically rather than as a fixture parameter so this
# autouse fixture does not add monkeypatch to every test's static fixture
# closure. pytest-run-parallel treats monkeypatch as thread-unsafe based on
# that closure, so a parameter here would serialize the entire free-threaded
# parallel sweep -- even though this fixture only patches on the opt-in
# SASS-check path (check_ldl_stl_in_sass, off by default and in CI).
monkeypatch = request.getfixturevalue("monkeypatch")

import cuda.compute._cccl_interop

monkeypatch.setattr(
Expand Down Expand Up @@ -124,8 +131,17 @@ def pytest_collection_modifyitems(config, items):
serialization_skip = pytest.mark.skip(
reason="serialization not supported on v2 (HostJIT) backend"
)
# Under the pytest-run-parallel sweep (--parallel-threads > 1) skip the
# blanket raise_on_numba_import injection: it monkeypatches
# builtins.__import__, which pytest-run-parallel treats as thread-unsafe and
# would serialize every no_numba test, neutering the sweep.
#
# getoption returns the int 1 from the argparse default but a *string* for
# CLI-passed values ("2", "auto", "1"), so normalize to str before comparing
# -- a bare `> 1` would raise TypeError on the "2" string.
running_parallel = str(config.getoption("parallel_threads", 1)) != "1"
for item in items:
if item.get_closest_marker("no_numba"):
if item.get_closest_marker("no_numba") and not running_parallel:
if "raise_on_numba_import" not in item.fixturenames:
item.fixturenames.append("raise_on_numba_import")
if USING_V2 and item.get_closest_marker("serialization"):
Expand Down
18 changes: 9 additions & 9 deletions python/cuda_cccl/tests/compute/test_no_numba.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,11 @@ def _raw_negate_i16_op() -> RawOp:
return _raw_op(source, "no_numba_negate_i16")


def test_import_numba_raises():
def test_import_numba_raises(raise_on_numba_import):
# Request the guard explicitly rather than relying on the no_numba
# auto-injection (skipped under the parallel sweep, see conftest
# pytest_collection_modifyitems): this test exercises the guard directly, and
# the monkeypatch it depends on keeps it single-threaded under the sweep.
with pytest.raises(
ImportError, match="This test is marked 'no_numba' but attempted to import it"
):
Expand Down Expand Up @@ -205,9 +209,8 @@ def test_binary_search_explicit_opkind_less(search, side):
np.testing.assert_array_equal(d_out.copy_to_host(), expected)


def test_segmented_reduce_well_known_plus(monkeypatch):
monkeypatch.setattr(cuda.compute._cccl_interop, "_check_sass", False)

@pytest.mark.no_verify_sass
def test_segmented_reduce_well_known_plus():
h_input = np.asarray([1, 2, 3, 4, 5, 6, 7, 8], dtype=np.uint32)
h_starts = np.asarray([0, 3, 5], dtype=np.int32)
h_ends = np.asarray([3, 5, 8], dtype=np.int32)
Expand Down Expand Up @@ -288,11 +291,8 @@ def test_segmented_sort_keys():
np.testing.assert_array_equal(d_output.copy_to_host(), expected)


def test_unique_by_key_well_known_equal_to(monkeypatch):
cc_major, _ = cuda.compute._cccl_interop.CudaDevice().compute_capability
if cc_major >= 9:
monkeypatch.setattr(cuda.compute._cccl_interop, "_check_sass", False)

@pytest.mark.no_verify_sass
def test_unique_by_key_well_known_equal_to():
Comment thread
NaderAlAwar marked this conversation as resolved.
h_keys = np.asarray([1, 1, 2, 2, 2, 3, 4, 4], dtype=np.int16)
h_values = np.asarray([10, 11, 20, 21, 22, 30, 40, 41], dtype=np.int8)
d_keys = DeviceArray.from_numpy(h_keys)
Expand Down
Loading