From 644e7db7f0e73c0f4b95b5c4d7543b9740b9a94a Mon Sep 17 00:00:00 2001 From: Tim Head Date: Mon, 29 Jun 2026 13:27:52 +0200 Subject: [PATCH 1/7] Fix testing of bertopic integration --- ci/test_wheel_integrations.sh | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/ci/test_wheel_integrations.sh b/ci/test_wheel_integrations.sh index 1fcd20b0a8..68f247c7c6 100755 --- a/ci/test_wheel_integrations.sh +++ b/ci/test_wheel_integrations.sh @@ -40,14 +40,16 @@ print('✓ Import test passed') " # Test 2: Run minimal end-to-end example -rapids-logger "Running BERTopic end-to-end smoke test" -timeout -v 20m python -c " +rapids-logger "Running BERTopic end-to-end smoke test (cuml.accel)" +timeout -v 20m python -m cuml.accel -c " import warnings warnings.filterwarnings('ignore') import random from bertopic import BERTopic +import cuml.accel + # Generate synthetic documents with topic-like word clusters random.seed(42) topics = [ @@ -62,13 +64,18 @@ for i in range(100): doc = ' '.join(random.choices(topic_words, k=random.randint(10, 30))) docs.append(doc) -# Initialize BERTopic with cuML UMAP backend -# BERTopic will automatically use cuML's UMAP if available topic_model = BERTopic(verbose=False, calculate_probabilities=False) # Fit the model topics, probs = topic_model.fit_transform(docs) +# Verify cuML actually backed BERTopic's UMAP and HDBSCAN steps. +assert cuml.accel.enabled(), 'cuml.accel is not enabled' +assert cuml.accel.is_proxy(topic_model.umap_model), \ + f'UMAP not accelerated by cuML: {type(topic_model.umap_model)}' +assert cuml.accel.is_proxy(topic_model.hdbscan_model), \ + f'HDBSCAN not accelerated by cuML: {type(topic_model.hdbscan_model)}' + print(f'✓ BERTopic smoke test passed - processed {len(docs)} documents, found {len(set(topics))} topics') " From 8f7ae45a5ceb5b84223d74d4b2564639fdd7f238 Mon Sep 17 00:00:00 2001 From: Tim Head Date: Mon, 29 Jun 2026 13:44:59 +0200 Subject: [PATCH 2/7] Use CPU pytorch as we don't need a GPU version This resolves the problem of torch pulling in a CUDA 13 stack which results in some packages using CUDA 12 and some 13. --- ci/test_wheel_integrations.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ci/test_wheel_integrations.sh b/ci/test_wheel_integrations.sh index 68f247c7c6..0f9de98065 100755 --- a/ci/test_wheel_integrations.sh +++ b/ci/test_wheel_integrations.sh @@ -27,7 +27,12 @@ rapids-pip-retry install \ "${LIBCUML_WHEELHOUSE}"/libcuml*.whl \ "${CUML_WHEELHOUSE}"/cuml*.whl -# Step 2: Install BERTopic +# Step 2: Install CPU-only PyTorch first so BERTopic's transitive torch +# dependency does not pull a CUDA 13 stack on top of cuML's +rapids-logger "Installing CPU-only PyTorch" +rapids-pip-retry install --index-url https://download.pytorch.org/whl/cpu torch + +# Step 3: Install BERTopic (reuses the already-installed CPU torch) rapids-logger "Installing BERTopic" rapids-pip-retry install --prefer-binary bertopic From 152b8249f794672dae29547d2903416002d117e2 Mon Sep 17 00:00:00 2001 From: Tim Head Date: Mon, 29 Jun 2026 15:26:16 +0200 Subject: [PATCH 3/7] Use profiler to assert GPU execution Cute use of our profiler instead of grepping the output. --- ci/test_wheel_integrations.sh | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/ci/test_wheel_integrations.sh b/ci/test_wheel_integrations.sh index 0f9de98065..9af8b33bae 100755 --- a/ci/test_wheel_integrations.sh +++ b/ci/test_wheel_integrations.sh @@ -71,15 +71,21 @@ for i in range(100): topic_model = BERTopic(verbose=False, calculate_probabilities=False) -# Fit the model -topics, probs = topic_model.fit_transform(docs) - -# Verify cuML actually backed BERTopic's UMAP and HDBSCAN steps. -assert cuml.accel.enabled(), 'cuml.accel is not enabled' -assert cuml.accel.is_proxy(topic_model.umap_model), \ - f'UMAP not accelerated by cuML: {type(topic_model.umap_model)}' -assert cuml.accel.is_proxy(topic_model.hdbscan_model), \ - f'HDBSCAN not accelerated by cuML: {type(topic_model.hdbscan_model)}' +# Inspect the profiler to confirm the UMAP/HDBSCAN steps actually ran on GPU +with cuml.accel.profile() as prof: + topics, probs = topic_model.fit_transform(docs) + +def assert_ran_on_gpu(prefix): + stats = {n: s for n, s in prof.method_calls.items() if n.startswith(prefix)} + assert stats, f'no {prefix}* calls were recorded by cuml.accel' + gpu_calls = sum(s.gpu_calls for s in stats.values()) + cpu_calls = sum(s.cpu_calls for s in stats.values()) + reasons = sorted({r for s in stats.values() for r in s.fallback_reasons}) + assert gpu_calls > 0, f'{prefix}* never ran on GPU (recorded: {sorted(stats)})' + assert cpu_calls == 0, f'{prefix}* fell back to CPU: {reasons}' + +assert_ran_on_gpu('UMAP.') +assert_ran_on_gpu('HDBSCAN.') print(f'✓ BERTopic smoke test passed - processed {len(docs)} documents, found {len(set(topics))} topics') " From 4a8f672b3d8c241852bb74d82e7777e32e52d270 Mon Sep 17 00:00:00 2001 From: James Lamb Date: Tue, 30 Jun 2026 10:30:58 -0500 Subject: [PATCH 4/7] consolidate environment solve, make 'torch' dependency stricter --- .github/workflows/pr.yaml | 17 +++++++++++++++++ ci/test_wheel_integrations.sh | 24 ++++++++++-------------- dependencies.yaml | 27 +++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 14 deletions(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index aaa252de4e..ba861a02df 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -31,6 +31,7 @@ jobs: - wheel-build-cuml - wheel-tests-cuml - wheel-tests-cuml-dask + - wheel-tests-integrations - devcontainer permissions: actions: read @@ -538,6 +539,22 @@ jobs: with: build_type: pull-request script: ci/test_wheel_dask.sh + wheel-tests-integrations: + needs: [wheel-build-cuml, changed-files] + permissions: + actions: read + contents: read + id-token: write + packages: read + pull-requests: read + secrets: inherit # zizmor: ignore[secrets-inherit] + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@main + with: + build_type: pull-request + script: ci/test_wheel_integrations.sh + # Test all CUDA major versions with latest dependencies and respective latest Python version + matrix_filter: 'map(select(.DEPENDENCIES == "latest")) | group_by(.CUDA_VER|split(".")|.[0]) | map(max_by(.PY_VER|split(".")|map(tonumber)))' + continue-on-error: true devcontainer: needs: telemetry-setup permissions: diff --git a/ci/test_wheel_integrations.sh b/ci/test_wheel_integrations.sh index 9af8b33bae..53420ca87f 100755 --- a/ci/test_wheel_integrations.sh +++ b/ci/test_wheel_integrations.sh @@ -8,8 +8,6 @@ source rapids-init-pip LIBCUML_WHEELHOUSE=$(rapids-download-from-github "$(rapids-artifact-name wheel_cpp libcuml cuml --cuda "$RAPIDS_CUDA_VERSION")") CUML_WHEELHOUSE=$(rapids-download-from-github "$(rapids-artifact-name wheel_python cuml cuml --stable --cuda "$RAPIDS_CUDA_VERSION")") -RAPIDS_TESTS_DIR=${RAPIDS_TESTS_DIR:-"${PWD}/test-results"} -mkdir -p "${RAPIDS_TESTS_DIR}" EXITCODE=0 trap "EXITCODE=1" ERR @@ -20,21 +18,19 @@ set +e # rapids-logger "===== Testing BERTopic Integration =====" -# Step 1: Install cuML wheels first (two-step workaround for issue #7374) -rapids-logger "Installing cuML wheels" +rapids-logger "Generating testing dependencies" +rapids-dependency-file-generator \ + --output requirements \ + --file-key test_integration_bertopic \ + --matrix "cuda=${RAPIDS_CUDA_VERSION%.*};arch=$(arch);py=${RAPIDS_PY_VERSION};dependencies=${RAPIDS_DEPENDENCIES}" \ +| tee ./requirements.txt + +rapids-logger "Installing cuML, BERTopic, and dependencies" rapids-pip-retry install \ --prefer-binary \ "${LIBCUML_WHEELHOUSE}"/libcuml*.whl \ - "${CUML_WHEELHOUSE}"/cuml*.whl - -# Step 2: Install CPU-only PyTorch first so BERTopic's transitive torch -# dependency does not pull a CUDA 13 stack on top of cuML's -rapids-logger "Installing CPU-only PyTorch" -rapids-pip-retry install --index-url https://download.pytorch.org/whl/cpu torch - -# Step 3: Install BERTopic (reuses the already-installed CPU torch) -rapids-logger "Installing BERTopic" -rapids-pip-retry install --prefer-binary bertopic + "${CUML_WHEELHOUSE}"/cuml*.whl \ + -r ./requirements.txt # Test 1: Verify imports rapids-logger "Testing imports" diff --git a/dependencies.yaml b/dependencies.yaml index 1ff19fe629..2d8b5ac860 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -120,6 +120,10 @@ files: - depends_on_libnvforest - test_libcuml - test_cpp + test_integration_bertopic: + output: none + includes: + - test_bertopic test_python: output: none includes: @@ -545,6 +549,29 @@ dependencies: - matrix: packages: - python>=3.11 + test_bertopic: + common: + - output_types: requirements + packages: + - bertopic>=0.17.4 + specific: + # The 'pytorch.org' indices referenced in --extra-index-url below host CPU-only variants too, + # so requirements like '>=' are not safe. + # + # Using '==' and a version with the CUDA specifier like '+cu132' is the most reliable way to ensure + # the packages we want are pulled (at the expense of needing to maintain this list). + - output_types: requirements + matrices: + - matrix: + cuda: "12.*" + packages: + - --extra-index-url=https://download.pytorch.org/whl/cu129 + - torch==2.10.0+cu129 + - matrix: + cuda: "13.*" + packages: + - --extra-index-url=https://download.pytorch.org/whl/cu132 + - torch==2.12.1+cu132 test_libcuml: common: - output_types: conda From 3aa45826072a8285842a4172e940be726349f95c Mon Sep 17 00:00:00 2001 From: Tim Head Date: Thu, 2 Jul 2026 14:49:21 +0200 Subject: [PATCH 5/7] Explicitly pass cuml instances to bertopic --- ci/test_wheel_integrations.sh | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/ci/test_wheel_integrations.sh b/ci/test_wheel_integrations.sh index 53420ca87f..0479dab8ad 100755 --- a/ci/test_wheel_integrations.sh +++ b/ci/test_wheel_integrations.sh @@ -41,15 +41,16 @@ print('✓ Import test passed') " # Test 2: Run minimal end-to-end example -rapids-logger "Running BERTopic end-to-end smoke test (cuml.accel)" -timeout -v 20m python -m cuml.accel -c " +rapids-logger "Running BERTopic end-to-end smoke test" +timeout -v 20m python -c " import warnings warnings.filterwarnings('ignore') import random from bertopic import BERTopic -import cuml.accel +from cuml.cluster import HDBSCAN +from cuml.manifold import UMAP # Generate synthetic documents with topic-like word clusters random.seed(42) @@ -65,23 +66,15 @@ for i in range(100): doc = ' '.join(random.choices(topic_words, k=random.randint(10, 30))) docs.append(doc) -topic_model = BERTopic(verbose=False, calculate_probabilities=False) +hdbscan_model = HDBSCAN(min_samples=10, gen_min_span_tree=True, prediction_data=True) +umap_model = UMAP(n_components=5, n_neighbors=15, min_dist=0.0) -# Inspect the profiler to confirm the UMAP/HDBSCAN steps actually ran on GPU -with cuml.accel.profile() as prof: - topics, probs = topic_model.fit_transform(docs) +topic_model = BERTopic(verbose=False, + calculate_probabilities=False, + hdbscan_model=hdbscan_model, + umap_model=umap_model) -def assert_ran_on_gpu(prefix): - stats = {n: s for n, s in prof.method_calls.items() if n.startswith(prefix)} - assert stats, f'no {prefix}* calls were recorded by cuml.accel' - gpu_calls = sum(s.gpu_calls for s in stats.values()) - cpu_calls = sum(s.cpu_calls for s in stats.values()) - reasons = sorted({r for s in stats.values() for r in s.fallback_reasons}) - assert gpu_calls > 0, f'{prefix}* never ran on GPU (recorded: {sorted(stats)})' - assert cpu_calls == 0, f'{prefix}* fell back to CPU: {reasons}' - -assert_ran_on_gpu('UMAP.') -assert_ran_on_gpu('HDBSCAN.') +topics, probs = topic_model.fit_transform(docs) print(f'✓ BERTopic smoke test passed - processed {len(docs)} documents, found {len(set(topics))} topics') " From d4718ca4c522a7e1216d299ede2cafc5289ed13a Mon Sep 17 00:00:00 2001 From: Tim Head Date: Tue, 7 Jul 2026 10:42:04 +0200 Subject: [PATCH 6/7] Remove temporary CI config --- .github/workflows/pr.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index ba861a02df..69645f451a 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -31,7 +31,6 @@ jobs: - wheel-build-cuml - wheel-tests-cuml - wheel-tests-cuml-dask - - wheel-tests-integrations - devcontainer permissions: actions: read From 5b56654a2bd71d981a11f0a81cc5f70d56ee263b Mon Sep 17 00:00:00 2001 From: Simon Adorf Date: Tue, 7 Jul 2026 14:23:18 +0000 Subject: [PATCH 7/7] fixup! Remove temporary CI config --- .github/workflows/pr.yaml | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 69645f451a..aaa252de4e 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -538,22 +538,6 @@ jobs: with: build_type: pull-request script: ci/test_wheel_dask.sh - wheel-tests-integrations: - needs: [wheel-build-cuml, changed-files] - permissions: - actions: read - contents: read - id-token: write - packages: read - pull-requests: read - secrets: inherit # zizmor: ignore[secrets-inherit] - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@main - with: - build_type: pull-request - script: ci/test_wheel_integrations.sh - # Test all CUDA major versions with latest dependencies and respective latest Python version - matrix_filter: 'map(select(.DEPENDENCIES == "latest")) | group_by(.CUDA_VER|split(".")|.[0]) | map(max_by(.PY_VER|split(".")|map(tonumber)))' - continue-on-error: true devcontainer: needs: telemetry-setup permissions: