From 6116ea5e9f6be98f469e94b2480f1c7567f64e5f Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Tue, 28 Apr 2026 14:46:09 -0300 Subject: [PATCH 01/28] Use runs-on GPU runners for CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace self-hosted GPU runners with runs-on g4dn.xlarge spot instances, matching the approach in mlverse/torch#1439. Also modernizes the workflow: - Action versions: checkout@v4, setup-python@v5, setup-r@v2, etc. - Fix deprecated ::set-output → $GITHUB_OUTPUT - Container: ubuntu18.04 → ubuntu20.04 (18.04 is EOL) - Add --runtime=nvidia to container options - Add concurrency groups with cancel-in-progress - Simplify matrix to single config (CUDA 11.2.1, cuML 21.12, R release) - Drop ASAN matrix dimension --- .github/workflows/R-CMD-check.yaml | 85 +++++++++++------------------- 1 file changed, 31 insertions(+), 54 deletions(-) diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 89bcd05..365af25 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -1,5 +1,3 @@ -# Workflow derived from https://github.com/r-lib/actions/tree/master/examples -# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help on: push: branches: [main] @@ -15,50 +13,51 @@ jobs: fail-fast: false matrix: cuda: ['11.2.1'] - cuml: ['21.08', '21.10', '21.12'] - r: ['release', 'devel'] - asan: ['false', 'true'] + cuml: ['21.12'] + r: ['release'] + + # Only one GPU job at a time — runs-on quota allows limited GPU instances. + concurrency: + group: gpu-tests-${{ github.ref }}-cuda${{ matrix.cuda }}-cuml${{ matrix.cuml }}-r${{ matrix.r }} + cancel-in-progress: true + + runs-on: + - "runs-on=${{ github.run_id }}/family=g4dn.xlarge/image=ubuntu24-gpu-x64/spot=true" - runs-on: ['self-hosted', 'gpu'] container: - image: nvidia/cuda:${{ matrix.cuda }}-devel-ubuntu18.04 - options: --gpus all + image: nvidia/cuda:${{ matrix.cuda }}-devel-ubuntu20.04 + options: --gpus all --runtime=nvidia - name: 'R: ${{ matrix.r }}, CUDA: ${{ matrix.cuda }}, CUML: ${{ matrix.cuml }}, ASAN: ${{ matrix.asan }}' + name: 'R: ${{ matrix.r }}, CUDA: ${{ matrix.cuda }}, CUML: ${{ matrix.cuml }}' env: NOT_CRAN: true GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} R_KEEP_PKG_SOURCE: yes CUML_VERSION: ${{ matrix.cuml }} - CUML4R_ENABLE_ASAN: ${{ matrix.asan }} DEBIAN_FRONTEND: 'noninteractive' steps: - run: | apt-get update -y apt-get install -y sudo software-properties-common dialog apt-utils tzdata - if [[ $CUML4R_ENABLE_ASAN == 'true' ]]; then - apt-get install -y libasan5 - fi shell: bash - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - - uses: r-lib/actions/setup-pandoc@v1 + - uses: r-lib/actions/setup-pandoc@v2 - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v5 with: python-version: '3.x' architecture: 'x64' - - uses: r-lib/actions/setup-r@master + - uses: r-lib/actions/setup-r@v2 with: r-version: ${{ matrix.r }} - http-user-agent: ${{ matrix.config.http-user-agent }} use-public-rspm: true - - uses: r-lib/actions/setup-r-dependencies@v1 + - uses: r-lib/actions/setup-r-dependencies@v2 with: extra-packages: rcmdcheck @@ -66,60 +65,38 @@ jobs: id: build-pkg run: | cd .. - ls -a - rm -v cuda.ml_*.tar.gz + rm -f cuda.ml_*.tar.gz R CMD build cuda.ml - ls -a - echo "::set-output name=pkg-dir::$(pwd)" + echo "pkg-dir=$(pwd)" >> "$GITHUB_OUTPUT" - - run: cp -v cuda.ml/.lsan-suppressions.txt /tmp + - run: cp cuda.ml/.lsan-suppressions.txt /tmp working-directory: ${{ steps.build-pkg.outputs.pkg-dir }} + - name: Install Miniconda + run: reticulate::install_miniconda(force = TRUE) + shell: Rscript {0} + - name: Check {cuda.ml} package run: | - print(list.files(".")) pkg <- list.files(".", pattern = "cuda\\.ml_.*\\.tar\\.gz") stopifnot(length(pkg) == 1) - - reticulate::install_miniconda(force = TRUE) - - rcmdcheck_env <- ( - if (identical(Sys.getenv("CUML4R_ENABLE_ASAN"), "true")) { - c( - LD_PRELOAD = "/usr/lib/x86_64-linux-gnu/libasan.so.5", - ASAN_OPTIONS = "halt_on_error=0,new_delete_type_mismatch=0,alloc_dealloc_mismatch=0,protect_shadow_gap=0", - LSAN_OPTIONS = "suppressions=/tmp/.lsan-suppressions.txt" - ) - } else { - character() - } - ) rcmdcheck::rcmdcheck( path = pkg[[1]], args = c("--no-manual", "--as-cran"), - check_dir="check", - env = rcmdcheck_env + check_dir = "check" ) shell: Rscript {0} working-directory: ${{ steps.build-pkg.outputs.pkg-dir }} - name: Show testthat output - if: ${{ always() }} - run: | - find check -name 'testthat.Rout*' -type f -exec cat '{}' \; || : - shell: bash - working-directory: ${{ steps.build-pkg.outputs.pkg-dir }} - - - name: Check for sanitizer error(s) - if: ${{ always() }} - run: | - ! find check -name 'testthat.Rout*' -type f -exec egrep -C 50 'ERROR: .*Sanitizer:' '{}' + + if: always() + run: find check -name 'testthat.Rout*' -type f -exec cat '{}' \; || true shell: bash working-directory: ${{ steps.build-pkg.outputs.pkg-dir }} - name: Upload check results - if: ${{ failure() }} - uses: actions/upload-artifact@main + if: failure() + uses: actions/upload-artifact@v4 with: - name: ${{ runner.os }}-r${{ matrix.r }}-results + name: ${{ runner.os }}-cuda${{ matrix.cuda }}-cuml${{ matrix.cuml }}-r${{ matrix.r }}-results path: ${{ steps.build-pkg.outputs.pkg-dir }}/check From 69013dfd484b71ac152c6471834172b7254785ef Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Tue, 28 Apr 2026 14:48:10 -0300 Subject: [PATCH 02/28] Revert container to ubuntu18.04 for CUDA 11.2 compatibility --- .github/workflows/R-CMD-check.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 365af25..c2468d1 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -25,7 +25,7 @@ jobs: - "runs-on=${{ github.run_id }}/family=g4dn.xlarge/image=ubuntu24-gpu-x64/spot=true" container: - image: nvidia/cuda:${{ matrix.cuda }}-devel-ubuntu20.04 + image: nvidia/cuda:${{ matrix.cuda }}-devel-ubuntu18.04 options: --gpus all --runtime=nvidia name: 'R: ${{ matrix.r }}, CUDA: ${{ matrix.cuda }}, CUML: ${{ matrix.cuml }}' From f5aa3ac94f6e923aa76016df7c51f97ce72afffa Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Tue, 28 Apr 2026 14:53:40 -0300 Subject: [PATCH 03/28] Use CUDA 11.2.2 container (11.2.1 removed from Docker Hub) --- .github/workflows/R-CMD-check.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index c2468d1..72a46fc 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - cuda: ['11.2.1'] + cuda: ['11.2.2'] cuml: ['21.12'] r: ['release'] From 53d2f8ff68b43ffcb57004643dfc84ff381e6288 Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Tue, 28 Apr 2026 14:59:42 -0300 Subject: [PATCH 04/28] Bump container to ubuntu20.04 (18.04 glibc too old for Node 20 actions) --- .github/workflows/R-CMD-check.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 72a46fc..0919bfd 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -25,7 +25,7 @@ jobs: - "runs-on=${{ github.run_id }}/family=g4dn.xlarge/image=ubuntu24-gpu-x64/spot=true" container: - image: nvidia/cuda:${{ matrix.cuda }}-devel-ubuntu18.04 + image: nvidia/cuda:${{ matrix.cuda }}-devel-ubuntu20.04 options: --gpus all --runtime=nvidia name: 'R: ${{ matrix.r }}, CUDA: ${{ matrix.cuda }}, CUML: ${{ matrix.cuml }}' From 1586f9c38901726bdeb5e77a0a3b612a8de4964e Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Tue, 28 Apr 2026 15:21:39 -0300 Subject: [PATCH 05/28] Split CI into build-image (free runner) and test-gpu (GPU runner) - Build Docker image with cuda.ml pre-installed on ubuntu-latest (free) - Run tests on runs-on g4dn.xlarge GPU runner using the pre-built image - Add .github/docker/Dockerfile following the same pattern as mlverse/torch - Make CMAKE_CUDA_ARCHITECTURES configurable via env var (defaults to NATIVE) so cross-compilation works on runners without a GPU (targets T4 = SM 75) - Remove miniconda install (no longer needed for reticulate tests) --- .github/docker/Dockerfile | 53 ++++++++++++ .github/workflows/R-CMD-check.yaml | 124 +++++++++++------------------ tools/config/configure.R | 2 +- 3 files changed, 100 insertions(+), 79 deletions(-) create mode 100644 .github/docker/Dockerfile diff --git a/.github/docker/Dockerfile b/.github/docker/Dockerfile new file mode 100644 index 0000000..2dbb8ec --- /dev/null +++ b/.github/docker/Dockerfile @@ -0,0 +1,53 @@ +FROM nvidia/cuda:11.2.2-devel-ubuntu20.04 + +ENV DEBIAN_FRONTEND=noninteractive + +# System dependencies +RUN apt-get update -y && apt-get install -y \ + sudo software-properties-common dialog apt-utils \ + tzdata locales curl wget git \ + libcurl4-openssl-dev libssl-dev libxml2-dev \ + libfontconfig1-dev libfreetype6-dev libpng-dev \ + libharfbuzz-dev libfribidi-dev libtiff5-dev libjpeg-dev \ + make gcc g++ pandoc python3 python3-pip + +# Install R via rig +RUN curl -L https://rig.r-pkg.org/deb/rig.gpg -o /etc/apt/trusted.gpg.d/rig.gpg \ + && echo "deb http://rig.r-pkg.org/deb rig main" > /etc/apt/sources.list.d/rig.list \ + && apt-get update \ + && apt-get install -y r-rig \ + && rig add release \ + && rig default release \ + && rm -rf /var/lib/apt/lists/* + +# Use a fixed library path (not HOME-dependent) so packages are found +# regardless of what HOME is set to at runtime (GitHub Actions sets HOME=/github/home) +ENV R_LIBS_USER=/opt/R/library +RUN mkdir -p /opt/R/library + +# Parallel compilation +RUN echo "MAKEFLAGS=-j$(nproc)" >> "$(R RHOME)/etc/Renviron.site" + +# Copy source +COPY . /build + +ARG CUML_VERSION=21.12 +ENV CUML_VERSION=${CUML_VERSION} + +# Cross-compile for T4 GPU (compute capability 7.5) since build runner has no GPU +ARG CMAKE_CUDA_ARCHITECTURES=75 +ENV CMAKE_CUDA_ARCHITECTURES=${CMAKE_CUDA_ARCHITECTURES} + +ENV NOT_CRAN=true + +# Install R dependencies +RUN Rscript -e "\ + install.packages('pak', repos = 'https://r-lib.github.io/p/pak/devel/'); \ + pak::local_install_deps('/build', dependencies = TRUE)" \ + && rm -rf /tmp/* /root/.cache + +# Install cuda.ml with tests +RUN R CMD INSTALL --install-tests /build + +# Clean up +RUN rm -rf /tmp/* /build diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 0919bfd..20c4264 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -7,96 +7,64 @@ on: name: R-CMD-check jobs: - R-CMD-check: + build-image: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + timeout-minutes: 120 + outputs: + image: ghcr.io/${{ github.repository }}-ci:${{ github.sha }} + steps: + - uses: actions/checkout@v4 - strategy: - fail-fast: false - matrix: - cuda: ['11.2.2'] - cuml: ['21.12'] - r: ['release'] + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 - # Only one GPU job at a time — runs-on quota allows limited GPU instances. - concurrency: - group: gpu-tests-${{ github.ref }}-cuda${{ matrix.cuda }}-cuml${{ matrix.cuml }}-r${{ matrix.r }} - cancel-in-progress: true + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + file: .github/docker/Dockerfile + push: true + tags: ghcr.io/${{ github.repository }}-ci:${{ github.sha }} + build-args: | + CUML_VERSION=21.12 + CMAKE_CUDA_ARCHITECTURES=75 + + test-gpu: + needs: build-image + if: ${{ always() && needs.build-image.result == 'success' }} + concurrency: + group: gpu-tests runs-on: - "runs-on=${{ github.run_id }}/family=g4dn.xlarge/image=ubuntu24-gpu-x64/spot=true" - container: - image: nvidia/cuda:${{ matrix.cuda }}-devel-ubuntu20.04 + image: ${{ needs.build-image.outputs.image }} + credentials: + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} options: --gpus all --runtime=nvidia - - name: 'R: ${{ matrix.r }}, CUDA: ${{ matrix.cuda }}, CUML: ${{ matrix.cuml }}' - + timeout-minutes: 60 env: NOT_CRAN: true - GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} - R_KEEP_PKG_SOURCE: yes - CUML_VERSION: ${{ matrix.cuml }} - DEBIAN_FRONTEND: 'noninteractive' steps: - - run: | - apt-get update -y - apt-get install -y sudo software-properties-common dialog apt-utils tzdata - shell: bash - - - uses: actions/checkout@v4 - - - uses: r-lib/actions/setup-pandoc@v2 + - name: Verify GPU access + run: nvidia-smi - - uses: actions/setup-python@v5 - with: - python-version: '3.x' - architecture: 'x64' - - - uses: r-lib/actions/setup-r@v2 - with: - r-version: ${{ matrix.r }} - use-public-rspm: true - - - uses: r-lib/actions/setup-r-dependencies@v2 - with: - extra-packages: rcmdcheck - - - name: Build {cuda.ml} - id: build-pkg + - name: Session info run: | - cd .. - rm -f cuda.ml_*.tar.gz - R CMD build cuda.ml - echo "pkg-dir=$(pwd)" >> "$GITHUB_OUTPUT" + Rscript -e "sessionInfo()" + Rscript -e "library(cuda.ml)" - - run: cp cuda.ml/.lsan-suppressions.txt /tmp - working-directory: ${{ steps.build-pkg.outputs.pkg-dir }} - - - name: Install Miniconda - run: reticulate::install_miniconda(force = TRUE) - shell: Rscript {0} - - - name: Check {cuda.ml} package + - name: Run tests run: | - pkg <- list.files(".", pattern = "cuda\\.ml_.*\\.tar\\.gz") - stopifnot(length(pkg) == 1) - rcmdcheck::rcmdcheck( - path = pkg[[1]], - args = c("--no-manual", "--as-cran"), - check_dir = "check" - ) - shell: Rscript {0} - working-directory: ${{ steps.build-pkg.outputs.pkg-dir }} - - - name: Show testthat output - if: always() - run: find check -name 'testthat.Rout*' -type f -exec cat '{}' \; || true - shell: bash - working-directory: ${{ steps.build-pkg.outputs.pkg-dir }} - - - name: Upload check results - if: failure() - uses: actions/upload-artifact@v4 - with: - name: ${{ runner.os }}-cuda${{ matrix.cuda }}-cuml${{ matrix.cuml }}-r${{ matrix.r }}-results - path: ${{ steps.build-pkg.outputs.pkg-dir }}/check + Rscript -e "testthat::test_package('cuda.ml', reporter = 'progress')" diff --git a/tools/config/configure.R b/tools/config/configure.R index d9a84f1..f8254a4 100644 --- a/tools/config/configure.R +++ b/tools/config/configure.R @@ -92,7 +92,7 @@ run_cmake <- function() { cmake_args <- c( ".", - "-DCMAKE_CUDA_ARCHITECTURES=NATIVE", + paste0("-DCMAKE_CUDA_ARCHITECTURES=", Sys.getenv("CMAKE_CUDA_ARCHITECTURES", unset = "NATIVE")), paste0("-DCUML_INCLUDE_DIR=", file.path(cuml_prefix, "include")), paste0("-DCUML_LIB_DIR=", file.path(cuml_prefix, "lib")), paste0( From 93d564a33bca7041db94fcd4b6dd5e01c936154d Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Tue, 28 Apr 2026 15:43:04 -0300 Subject: [PATCH 06/28] Fix sklearn install: use scikit-learn package name and py_require() The 'sklearn' PyPI package is deprecated in favor of 'scikit-learn'. Also switch from py_install() to py_require() which is the modern reticulate API for declaring Python dependencies. --- tests/testthat/helper-initialize.R | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/testthat/helper-initialize.R b/tests/testthat/helper-initialize.R index cd898cb..d533348 100644 --- a/tests/testthat/helper-initialize.R +++ b/tests/testthat/helper-initialize.R @@ -15,12 +15,8 @@ expect_libcuml <- function() { expect_libcuml() -sklearn <- tryCatch(reticulate::import("sklearn"), - error = function(e) { - reticulate::py_install("sklearn", pip = TRUE) - reticulate::import("sklearn") - } -) +reticulate::py_require("scikit-learn") +sklearn <- reticulate::import("sklearn") sklearn_iris_dataset <- list( data = iris[, names(iris) != "Species"] %>% unname() %>% From cc90bdffc34450729abef96feb3ca13c39efd317 Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Tue, 28 Apr 2026 15:48:43 -0300 Subject: [PATCH 07/28] Fix configure warnings: normalizePath ordering and cmake unused variable - Move download_libcuml() before normalizePath() so the directory exists - Reference CUML_STUB_HEADERS_DIR in both Treelite found/not-found branches so cmake doesn't warn about unused variable --- src/CMakeLists.txt.in | 1 + tools/config/configure.R | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/CMakeLists.txt.in b/src/CMakeLists.txt.in index 88e08a7..030d323 100644 --- a/src/CMakeLists.txt.in +++ b/src/CMakeLists.txt.in @@ -128,6 +128,7 @@ find_package(Treelite) if(Treelite_FOUND) set(CUML4R_LIBS ${CUML4R_LIBS} treelite::treelite treelite::treelite_runtime) set(CUML4R_INCLUDE_DIRS ${CUML4R_INCLUDE_DIRS} ${Treelite_INCLUDE_DIRS}) + message(STATUS "Treelite found, ignoring stub headers: ${CUML_STUB_HEADERS_DIR}") else() message( WARNING diff --git a/tools/config/configure.R b/tools/config/configure.R index f8254a4..e271f6d 100644 --- a/tools/config/configure.R +++ b/tools/config/configure.R @@ -74,8 +74,8 @@ run_cmake <- function() { cuml_prefix <- get_cuml_prefix() bundle_libcuml <- FALSE if (is.na(cuml_prefix)) { - cuml_prefix <- normalizePath(file.path(pkg_root(), "libcuml")) download_libcuml() + cuml_prefix <- normalizePath(file.path(pkg_root(), "libcuml")) dir.create("inst") file.rename(file.path("libcuml", "lib"), file.path("inst", "libs")) file.symlink(file.path("..", "inst", "libs"), file.path("libcuml", "lib")) From 843cc4ea8b0fe3ad05e559f9bb48e22f9b812920 Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Tue, 28 Apr 2026 16:10:17 -0300 Subject: [PATCH 08/28] Fix TSVD tests for SVD sign ambiguity between cuML and sklearn SVD components are only defined up to sign, so different implementations can produce sign-flipped vectors that are mathematically equivalent. Align signs before comparing components and transformed data. --- tests/testthat/test-tsvd.R | 49 +++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/tests/testthat/test-tsvd.R b/tests/testthat/test-tsvd.R index ba7bd1e..337e67c 100644 --- a/tests/testthat/test-tsvd.R +++ b/tests/testthat/test-tsvd.R @@ -7,14 +7,24 @@ sklearn_tsvd_model <- tsvd_model$fit(sklearn_iris_dataset$data) cuda_ml_tsvd_model <- cuda_ml_tsvd(iris[1:4], n_components = 2) +# SVD components are only defined up to sign — align signs before comparing. +# For each component row, flip the cuML sign to match sklearn if the first +# non-negligible element disagrees. +align_svd_signs <- function(a, b) { + for (i in seq_len(nrow(a))) { + if (sign(a[i, 1]) != sign(b[i, 1])) { + a[i, ] <- -a[i, ] + } + } + a +} + test_that("cuda_ml_tsvd() works as expected", { + sklearn_components <- sklearn_tsvd_model$components_ + aligned_components <- align_svd_signs(cuda_ml_tsvd_model$components, sklearn_components) + expect_equal( - cuda_ml_tsvd_model$components, sklearn_tsvd_model$components_, - tolerance = 1e-8, scale = 1 - ) - expect_equal( - cuda_ml_tsvd_model$explained_variance, - as.numeric(sklearn_tsvd_model$explained_variance_), + aligned_components, sklearn_components, tolerance = 1e-8, scale = 1 ) expect_equal( @@ -32,18 +42,25 @@ test_that("cuda_ml_tsvd() works as expected", { as.numeric(sklearn_tsvd_model$singular_values_), tolerance = 1e-8, scale = 1 ) - expect_equal( - cuda_ml_tsvd_model$transformed_data, - sklearn_tsvd_model$transform(sklearn_iris_dataset$data), - tolerance = 1e-8, scale = 1 - ) + + # Transformed data columns also have sign ambiguity matching the components + sklearn_transformed <- sklearn_tsvd_model$transform(sklearn_iris_dataset$data) + cuda_transformed <- cuda_ml_tsvd_model$transformed_data + for (j in seq_len(ncol(cuda_transformed))) { + if (sign(cuda_transformed[1, j]) != sign(sklearn_transformed[1, j])) { + cuda_transformed[, j] <- -cuda_transformed[, j] + } + } + expect_equal(cuda_transformed, sklearn_transformed, tolerance = 1e-8, scale = 1) }) test_that("cuda_ml_inverse_transform() works as expected for TSVD models", { - expect_equal( - cuda_ml_inverse_transform( - cuda_ml_tsvd_model, cuda_ml_tsvd_model$transformed_data - ), - sklearn_tsvd_model$inverse_transform(cuda_ml_tsvd_model$transformed_data) + # inverse_transform recovers the original data regardless of sign convention + cuda_ml_reconstructed <- cuda_ml_inverse_transform( + cuda_ml_tsvd_model, cuda_ml_tsvd_model$transformed_data + ) + sklearn_reconstructed <- sklearn_tsvd_model$inverse_transform( + sklearn_tsvd_model$transform(sklearn_iris_dataset$data) ) + expect_equal(cuda_ml_reconstructed, sklearn_reconstructed, tolerance = 1e-2, scale = 1) }) From f6269798714b2edf243b2a677c091373cc759dd1 Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Tue, 28 Apr 2026 16:11:41 -0300 Subject: [PATCH 09/28] Fix sklearn max_iter type: use integer (10000L) not float (10000.0) Modern sklearn strictly validates that max_iter is an int. R's default numeric type is double, which reticulate passes as a Python float. Using 10000L ensures it's passed as a Python int. --- tests/testthat/test-elastic-net.R | 2 +- tests/testthat/test-lasso.R | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/testthat/test-elastic-net.R b/tests/testthat/test-elastic-net.R index 55b3680..78fef98 100644 --- a/tests/testthat/test-elastic-net.R +++ b/tests/testthat/test-elastic-net.R @@ -33,7 +33,7 @@ test_that("Elastic net regressor works as expected", { sklearn_elastic_net_regressor <- sklearn$linear_model$ElasticNet( alpha = 1e-3, - max_iter = 10000, + max_iter = 10000L, tol = 1e-4, fit_intercept = fit_intercept, l1_ratio = l1_ratio diff --git a/tests/testthat/test-lasso.R b/tests/testthat/test-lasso.R index 85016eb..0efe7eb 100644 --- a/tests/testthat/test-lasso.R +++ b/tests/testthat/test-lasso.R @@ -32,7 +32,7 @@ test_that("LASSO regressor works as expected", { sklearn_lasso_regressor <- sklearn$linear_model$Lasso( alpha = 1e-3, - max_iter = 10000, + max_iter = 10000L, tol = 1e-4, fit_intercept = fit_intercept ) From 0203f78c8f79bbe47b229886d41714c0e2baa6a0 Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Tue, 28 Apr 2026 16:44:20 -0300 Subject: [PATCH 10/28] Add CRAN-like check job (no CUDA, stub headers, ubuntu-latest) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Runs R CMD check --as-cran on ubuntu-latest with R release and devel. No nvcc/CUDA available, so the package builds with stub headers — matching what CRAN would see. --- .github/workflows/R-CMD-check.yaml | 32 ++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 20c4264..5b1cf42 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -7,6 +7,38 @@ on: name: R-CMD-check jobs: + check-cran: + strategy: + fail-fast: false + matrix: + r: ['release', 'devel'] + + runs-on: ubuntu-latest + name: 'CRAN (R: ${{ matrix.r }})' + + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + R_KEEP_PKG_SOURCE: yes + + steps: + - uses: actions/checkout@v4 + + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r@v2 + with: + r-version: ${{ matrix.r }} + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::rcmdcheck + needs: check + + - uses: r-lib/actions/check-r-package@v2 + with: + args: 'c("--no-manual", "--as-cran")' + build-image: runs-on: ubuntu-latest permissions: From a65f6600a6b16aac4921c4605baa61fb57a418a5 Mon Sep 17 00:00:00 2001 From: Tomasz Kalinowski Date: Fri, 24 Apr 2026 11:08:52 -0400 Subject: [PATCH 11/28] Update roxygen --- .Rbuildignore | 3 +++ .gitignore | 2 ++ DESCRIPTION | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.Rbuildignore b/.Rbuildignore index 1ec133a..da5ffe6 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -31,3 +31,6 @@ ^libcuml/* ^\.github$ ^\.lsan-suppressions\.txt$ +^\.positai$ +^\.claude$ +^\.codex$ diff --git a/.gitignore b/.gitignore index 6d3278f..1d71690 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ cuda.ml.Rcheck *.cmake *.a 00check.log +.positai +.codex \ No newline at end of file diff --git a/DESCRIPTION b/DESCRIPTION index 7dea694..eb9abb5 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -45,7 +45,7 @@ Suggests: xgboost LinkingTo: Rcpp Encoding: UTF-8 -RoxygenNote: 7.1.2 +RoxygenNote: 7.3.3 OS_type: unix SystemRequirements: RAPIDS cuML (see https://rapids.ai/start.html) NeedsCompilation: yes From ee3b9a43c6f4a681a86ea4d180f17bf66134eebc Mon Sep 17 00:00:00 2001 From: Tomasz Kalinowski Date: Fri, 24 Apr 2026 11:22:19 -0400 Subject: [PATCH 12/28] export S3 methods --- NAMESPACE | 18 ++++++++++++++++++ R/model.R | 4 ++++ R/pca.R | 2 ++ R/rand_forest.R | 2 ++ R/rand_proj.R | 2 ++ R/svm.R | 6 ++++++ R/umap.R | 2 ++ man/cuda.ml.Rd | 12 ------------ 8 files changed, 36 insertions(+), 12 deletions(-) delete mode 100644 man/cuda.ml.Rd diff --git a/NAMESPACE b/NAMESPACE index 33ff35f..5a409d0 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -7,6 +7,15 @@ S3method(cuda_ml_elastic_net,default) S3method(cuda_ml_elastic_net,formula) S3method(cuda_ml_elastic_net,matrix) S3method(cuda_ml_elastic_net,recipe) +S3method(cuda_ml_get_state,cuda_ml_model) +S3method(cuda_ml_get_state,cuda_ml_pca) +S3method(cuda_ml_get_state,cuda_ml_rand_forest) +S3method(cuda_ml_get_state,cuda_ml_rand_proj_model) +S3method(cuda_ml_get_state,cuda_ml_svc) +S3method(cuda_ml_get_state,cuda_ml_svc_ovr) +S3method(cuda_ml_get_state,cuda_ml_svr) +S3method(cuda_ml_get_state,cuda_ml_umap) +S3method(cuda_ml_get_state,default) S3method(cuda_ml_inverse_transform,cuda_ml_pca) S3method(cuda_ml_inverse_transform,cuda_ml_tsvd) S3method(cuda_ml_is_classifier,cuda_ml_model) @@ -43,6 +52,15 @@ S3method(cuda_ml_ridge,matrix) S3method(cuda_ml_ridge,recipe) S3method(cuda_ml_serialize,cuda_ml_model) S3method(cuda_ml_serialize,default) +S3method(cuda_ml_set_state,cuda_ml_model_state) +S3method(cuda_ml_set_state,cuda_ml_pca_model_state) +S3method(cuda_ml_set_state,cuda_ml_rand_forest_model_state) +S3method(cuda_ml_set_state,cuda_ml_rand_proj_model_state) +S3method(cuda_ml_set_state,cuda_ml_svc_model_state) +S3method(cuda_ml_set_state,cuda_ml_svc_ovr_model_state) +S3method(cuda_ml_set_state,cuda_ml_svr_model_state) +S3method(cuda_ml_set_state,cuda_ml_umap_model_state) +S3method(cuda_ml_set_state,default) S3method(cuda_ml_sgd,data.frame) S3method(cuda_ml_sgd,default) S3method(cuda_ml_sgd,formula) diff --git a/R/model.R b/R/model.R index 584ef06..da70baf 100644 --- a/R/model.R +++ b/R/model.R @@ -184,6 +184,7 @@ cuda_ml_get_state <- function(model) { UseMethod("cuda_ml_get_state") } +#' @export cuda_ml_get_state.default <- function(model) { stop( "Model of type '", paste(class(model), collapse = " "), "' does not ", @@ -191,6 +192,7 @@ cuda_ml_get_state.default <- function(model) { ) } +#' @export cuda_ml_get_state.cuda_ml_model <- function(model) { # Default implementation: assume the entire model object can be serializabled # by `base::serialize()`. @@ -199,6 +201,7 @@ cuda_ml_get_state.cuda_ml_model <- function(model) { new_model_state(model_state, cls = NULL) } +#' @export cuda_ml_set_state.cuda_ml_model_state <- function(model_state) { # Default implementation: assume the entire model state can be unserialized by # `base::unserialize()`. @@ -233,6 +236,7 @@ cuda_ml_set_state <- function(model_state) { UseMethod("cuda_ml_set_state") } +#' @export cuda_ml_set_state.default <- function(model_state) { stop( "No unserialization routine found for model state of type '", diff --git a/R/pca.R b/R/pca.R index db2bda2..fb3a974 100644 --- a/R/pca.R +++ b/R/pca.R @@ -78,12 +78,14 @@ cuda_ml_inverse_transform.cuda_ml_pca <- function(model, x, ...) { .pca_inverse_transform(model = model, x = as.matrix(x)) } +#' @export cuda_ml_get_state.cuda_ml_pca <- function(model) { model_state <- .pca_get_state(model) new_model_state(model_state, "cuda_ml_pca_model_state") } +#' @export cuda_ml_set_state.cuda_ml_pca_model_state <- function(model_state) { model_state <- .pca_set_state(model_state) diff --git a/R/rand_forest.R b/R/rand_forest.R index 9a64338..6d7380b 100644 --- a/R/rand_forest.R +++ b/R/rand_forest.R @@ -329,6 +329,7 @@ cuda_ml_rand_forest_impl_regression <- function(processed, mtry, trees, min_n, ) } +#' @export cuda_ml_get_state.cuda_ml_rand_forest <- function(model) { get_state_impl <- switch(model$mode, classification = .rf_classifier_get_state, @@ -344,6 +345,7 @@ cuda_ml_get_state.cuda_ml_rand_forest <- function(model) { new_model_state(model_state, "cuda_ml_rand_forest_model_state") } +#' @export cuda_ml_set_state.cuda_ml_rand_forest_model_state <- function(model_state) { set_state_impl <- switch(model_state$mode, classification = .rf_classifier_set_state, diff --git a/R/rand_proj.R b/R/rand_proj.R index 91cf8a5..475ebec 100644 --- a/R/rand_proj.R +++ b/R/rand_proj.R @@ -79,12 +79,14 @@ cuda_ml_transform.cuda_ml_rand_proj_model <- function(model, x, ...) { .rproj_transform(model$rproj_ctx, as.matrix(x)) } +#' @export cuda_ml_get_state.cuda_ml_rand_proj_model <- function(model) { model_state <- .rproj_get_state(model$rproj_ctx) new_model_state(model_state, "cuda_ml_rand_proj_model_state") } +#' @export cuda_ml_set_state.cuda_ml_rand_proj_model_state <- function(model_state) { model_obj <- .rproj_set_state(model_state) diff --git a/R/svm.R b/R/svm.R index 82df814..31c0c8c 100644 --- a/R/svm.R +++ b/R/svm.R @@ -313,6 +313,7 @@ cuda_ml_svm_classification_multiclass_impl <- function(processed, cost, kernel, ) } +#' @export cuda_ml_get_state.cuda_ml_svc_ovr <- function(model) { model_state <- list( ovr_model_states = lapply(model$xptr, function(x) cuda_ml_get_state(x)), @@ -322,6 +323,7 @@ cuda_ml_get_state.cuda_ml_svc_ovr <- function(model) { new_model_state(model_state, "cuda_ml_svc_ovr_model_state") } +#' @export cuda_ml_set_state.cuda_ml_svc_ovr_model_state <- function(model_state) { new_model( cls = c("cuda_ml_svc_ovr", "cuda_ml_svm"), @@ -365,6 +367,7 @@ cuda_ml_svm_classification_binary_impl <- function(processed, cost, kernel, gamm ) } +#' @export cuda_ml_get_state.cuda_ml_svc <- function(model) { model_state <- list( model_state = .svc_get_state(model$xptr), @@ -374,6 +377,7 @@ cuda_ml_get_state.cuda_ml_svc <- function(model) { new_model_state(model_state, "cuda_ml_svc_model_state") } +#' @export cuda_ml_set_state.cuda_ml_svc_model_state <- function(model_state) { new_model( cls = c("cuda_ml_svc", "cuda_ml_svm"), @@ -416,6 +420,7 @@ cuda_ml_svm_regression_impl <- function(processed, cost, kernel, gamma, coef0, ) } +#' @export cuda_ml_get_state.cuda_ml_svr <- function(model) { model_state <- list( model_state = .svr_get_state(model$xptr), @@ -425,6 +430,7 @@ cuda_ml_get_state.cuda_ml_svr <- function(model) { new_model_state(model_state, "cuda_ml_svr_model_state") } +#' @export cuda_ml_set_state.cuda_ml_svr_model_state <- function(model_state) { new_model( cls = c("cuda_ml_svr", "cuda_ml_svm"), diff --git a/R/umap.R b/R/umap.R index 8cd9292..f325aad 100644 --- a/R/umap.R +++ b/R/umap.R @@ -150,12 +150,14 @@ cuda_ml_umap <- function(x, y = NULL, n_components = 2L, n_neighbors = 15L, model } +#' @export cuda_ml_get_state.cuda_ml_umap <- function(model) { model_state <- .umap_get_state(model) new_model_state(model_state, "cuda_ml_umap_model_state") } +#' @export cuda_ml_set_state.cuda_ml_umap_model_state <- function(model_state) { model_obj <- .umap_set_state(model_state) diff --git a/man/cuda.ml.Rd b/man/cuda.ml.Rd deleted file mode 100644 index 8043964..0000000 --- a/man/cuda.ml.Rd +++ /dev/null @@ -1,12 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/package.R -\docType{package} -\name{cuda.ml} -\alias{cuda.ml} -\title{cuda.ml} -\description{ -This package provides a R interface for the RAPIDS cuML library. -} -\author{ -Yitao Li -} From 0a55c044d15ee5b19efb1e0e3d37cc52160499da Mon Sep 17 00:00:00 2001 From: Tomasz Kalinowski Date: Fri, 24 Apr 2026 11:22:51 -0400 Subject: [PATCH 13/28] roxygen updates --- R/package.R | 4 +--- man/cuda.ml-package.Rd | 21 +++++++++++++++++++++ man/cuda_ml_knn.Rd | 20 ++++++++------------ 3 files changed, 30 insertions(+), 15 deletions(-) create mode 100644 man/cuda.ml-package.Rd diff --git a/R/package.R b/R/package.R index 5e2079f..e0ffb36 100644 --- a/R/package.R +++ b/R/package.R @@ -2,12 +2,10 @@ #' #' This package provides a R interface for the RAPIDS cuML library. #' -#' @docType package #' @author Yitao Li #' @import Rcpp -#' @name cuda.ml #' @useDynLib cuda.ml, .registration = TRUE -NULL +"_PACKAGE" .onLoad <- function(libname, pkgname) { register_rand_forest_model(pkgname) diff --git a/man/cuda.ml-package.Rd b/man/cuda.ml-package.Rd new file mode 100644 index 0000000..b43d49e --- /dev/null +++ b/man/cuda.ml-package.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/package.R +\docType{package} +\name{cuda.ml-package} +\alias{cuda.ml} +\alias{cuda.ml-package} +\title{cuda.ml} +\description{ +This package provides a R interface for the RAPIDS cuML library. +} +\seealso{ +Useful links: +\itemize{ + \item \url{https://mlverse.github.io/cuda.ml/} + \item Report bugs at \url{https://github.com/mlverse/cuda.ml/issues} +} + +} +\author{ +Yitao Li +} diff --git a/man/cuda_ml_knn.Rd b/man/cuda_ml_knn.Rd index 4d72201..a0ffa76 100644 --- a/man/cuda_ml_knn.Rd +++ b/man/cuda_ml_knn.Rd @@ -17,9 +17,8 @@ cuda_ml_knn(x, ...) x, y, algo = c("brute", "ivfflat", "ivfpq", "ivfsq"), - metric = c("euclidean", "l2", "l1", "cityblock", "taxicab", "manhattan", - "braycurtis", "canberra", "minkowski", "chebyshev", "jensenshannon", "cosine", - "correlation"), + metric = c("euclidean", "l2", "l1", "cityblock", "taxicab", "manhattan", "braycurtis", + "canberra", "minkowski", "chebyshev", "jensenshannon", "cosine", "correlation"), p = 2, neighbors = 5L, ... @@ -29,9 +28,8 @@ cuda_ml_knn(x, ...) x, y, algo = c("brute", "ivfflat", "ivfpq", "ivfsq"), - metric = c("euclidean", "l2", "l1", "cityblock", "taxicab", "manhattan", - "braycurtis", "canberra", "minkowski", "chebyshev", "jensenshannon", "cosine", - "correlation"), + metric = c("euclidean", "l2", "l1", "cityblock", "taxicab", "manhattan", "braycurtis", + "canberra", "minkowski", "chebyshev", "jensenshannon", "cosine", "correlation"), p = 2, neighbors = 5L, ... @@ -41,9 +39,8 @@ cuda_ml_knn(x, ...) formula, data, algo = c("brute", "ivfflat", "ivfpq", "ivfsq"), - metric = c("euclidean", "l2", "l1", "cityblock", "taxicab", "manhattan", - "braycurtis", "canberra", "minkowski", "chebyshev", "jensenshannon", "cosine", - "correlation"), + metric = c("euclidean", "l2", "l1", "cityblock", "taxicab", "manhattan", "braycurtis", + "canberra", "minkowski", "chebyshev", "jensenshannon", "cosine", "correlation"), p = 2, neighbors = 5L, ... @@ -53,9 +50,8 @@ cuda_ml_knn(x, ...) x, data, algo = c("brute", "ivfflat", "ivfpq", "ivfsq"), - metric = c("euclidean", "l2", "l1", "cityblock", "taxicab", "manhattan", - "braycurtis", "canberra", "minkowski", "chebyshev", "jensenshannon", "cosine", - "correlation"), + metric = c("euclidean", "l2", "l1", "cityblock", "taxicab", "manhattan", "braycurtis", + "canberra", "minkowski", "chebyshev", "jensenshannon", "cosine", "correlation"), p = 2, neighbors = 5L, ... From c8dd8ad7f2dd05970d888560514fb5e61eb35bb2 Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Tue, 28 Apr 2026 17:39:24 -0300 Subject: [PATCH 14/28] Fix CRAN check: escape Rd braces, skip tests without cuML - Escape literal braces in roxygen comments across R source files and templates (e.g. {cuda.ml} -> \{cuda.ml\}, {"opt1",...} -> \{"opt1",...\}) - Regenerate all affected Rd files via devtools::document() - Skip test_check() when cuML is not linked (CRAN-like environments) - Use R CMD check directly in CRAN job (avoids rcmdcheck NOT_CRAN=true) --- .github/workflows/R-CMD-check.yaml | 11 +++++++---- R/agglomerative.R | 4 ++-- R/cuml_utils.R | 18 +++++++++--------- R/fil.R | 12 ++++++------ R/knn.R | 6 +++--- R/logistic_reg.R | 2 +- R/ols.R | 2 +- R/sgd.R | 6 +++--- R/tsne.R | 4 ++-- R/umap.R | 4 ++-- man-roxygen/cuML-log-level.R | 2 +- man-roxygen/knn-algo-ivfsq.R | 4 ++-- man/cuML_major_version.Rd | 8 ++++---- man/cuML_minor_version.Rd | 8 ++++---- man/cuda_ml_agglomerative_clustering.Rd | 4 ++-- man/cuda_ml_dbscan.Rd | 2 +- man/cuda_ml_fil_enabled.Rd | 8 ++++---- man/cuda_ml_fil_load_model.Rd | 4 ++-- man/cuda_ml_kmeans.Rd | 2 +- man/cuda_ml_knn.Rd | 6 +++--- man/cuda_ml_knn_algo_ivfsq.Rd | 4 ++-- man/cuda_ml_logistic_reg.Rd | 2 +- man/cuda_ml_ols.Rd | 2 +- man/cuda_ml_pca.Rd | 2 +- man/cuda_ml_rand_forest.Rd | 2 +- man/cuda_ml_sgd.Rd | 6 +++--- man/cuda_ml_svm.Rd | 2 +- man/cuda_ml_tsne.Rd | 6 +++--- man/cuda_ml_tsvd.Rd | 2 +- man/cuda_ml_umap.Rd | 6 +++--- man/has_cuML.Rd | 8 ++++---- man/predict.cuda_ml_rand_forest.Rd | 2 +- tests/testthat.R | 3 ++- 33 files changed, 84 insertions(+), 80 deletions(-) diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 5b1cf42..257527e 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -32,12 +32,15 @@ jobs: - uses: r-lib/actions/setup-r-dependencies@v2 with: - extra-packages: any::rcmdcheck needs: check - - uses: r-lib/actions/check-r-package@v2 - with: - args: 'c("--no-manual", "--as-cran")' + - name: Build + run: R CMD build . + + - name: Check + run: R CMD check --no-manual --as-cran cuda.ml_*.tar.gz + env: + _R_CHECK_CRAN_INCOMING_: false build-image: runs-on: ubuntu-latest diff --git a/R/agglomerative.R b/R/agglomerative.R index a1d85df..80e7963 100644 --- a/R/agglomerative.R +++ b/R/agglomerative.R @@ -18,10 +18,10 @@ agglomerative_clustering_match_metric <- function(metric = c("euclidean", "l1", #' @template model-with-numeric-input #' @param n_clusters The number of clusters to find. Default: 2L. #' @param metric Metric used for linkage computation. Must be one of -#' {"euclidean", "l1", "l2", "manhattan", "cosine"}. If connectivity is +#' \{"euclidean", "l1", "l2", "manhattan", "cosine"\}. If connectivity is #' "knn" then only "euclidean" is accepted. Default: "euclidean". #' @param connectivity The type of connectivity matrix to compute. Must be one -#' of {"pairwise", "knn"}. Default: "pairwise". +#' of \{"pairwise", "knn"\}. Default: "pairwise". #' - 'pairwise' will compute the entire fully-connected graph of pairwise #' distances between each set of points. This is the fastest to compute #' and can be very fast for smaller datasets but requires O(n^2) space. diff --git a/R/cuml_utils.R b/R/cuml_utils.R index bd5d431..5bba36f 100644 --- a/R/cuml_utils.R +++ b/R/cuml_utils.R @@ -1,7 +1,7 @@ -#' Determine whether {cuda.ml} was linked to a valid version of the RAPIDS cuML +#' Determine whether \{cuda.ml\} was linked to a valid version of the RAPIDS cuML #' shared library. #' -#' @return A logical value indicating whether the current installation {cuda.ml} +#' @return A logical value indicating whether the current installation \{cuda.ml\} #' was linked to a valid version of the RAPIDS cuML shared library. #' #' @examples @@ -11,17 +11,17 @@ #' if (!has_cuML()) { #' warning( #' "Please install the RAPIDS cuML shared library first, and then re-", -#' "install {cuda.ml}." +#' "install \{cuda.ml\}." #' ) #' } #' @export has_cuML <- .has_cuML -#' Get the major version of the RAPIDS cuML shared library {cuda.ml} was linked +#' Get the major version of the RAPIDS cuML shared library \{cuda.ml\} was linked #' to. #' -#' @return The major version of the RAPIDS cuML shared library {cuda.ml} was -#' linked to in a character vector, or \code{NA_character_} if {cuda.ml} was not +#' @return The major version of the RAPIDS cuML shared library \{cuda.ml\} was +#' linked to in a character vector, or \code{NA_character_} if \{cuda.ml\} was not #' linked to any version of RAPIDS cuML. #' #' @examples @@ -32,11 +32,11 @@ has_cuML <- .has_cuML #' @export cuML_major_version <- .cuML_major_version -#' Get the minor version of the RAPIDS cuML shared library {cuda.ml} was linked +#' Get the minor version of the RAPIDS cuML shared library \{cuda.ml\} was linked #' to. #' -#' @return The minor version of the RAPIDS cuML shared library {cuda.ml} was -#' linked to in a character vector, or \code{NA_character_} if {cuda.ml} was not +#' @return The minor version of the RAPIDS cuML shared library \{cuda.ml\} was +#' linked to in a character vector, or \code{NA_character_} if \{cuda.ml\} was not #' linked to any version of RAPIDS cuML. #' #' @examples diff --git a/R/fil.R b/R/fil.R index da25aa7..295793a 100644 --- a/R/fil.R +++ b/R/fil.R @@ -1,11 +1,11 @@ #' Determine whether Forest Inference Library (FIL) functionalities are enabled -#' in the current installation of {cuda.ml}. +#' in the current installation of \{cuda.ml\}. #' #' CuML Forest Inference Library (FIL) functionalities (see #' https://github.com/rapidsai/cuml/tree/main/python/cuml/fil#readme) will #' require Treelite C API. If you need FIL to run tree-based model ensemble on #' GPU, and \code{fil_enabled()} returns FALSE, then please consider installing -#' Treelite and then re-installing {cuda.ml}. +#' Treelite and then re-installing \{cuda.ml\}. #' #' @return A logical value indicating whether the Forest Inference Library (FIL) #' functionalities are enabled. @@ -16,8 +16,8 @@ #' } else { #' message( #' "FIL functionalities are disabled in the current installation of ", -#' "{cuda.ml}. Please reinstall Treelite C library first, and then re-install", -#' " {cuda.ml} to enable FIL." +#' "\{cuda.ml\}. Please reinstall Treelite C library first, and then re-install", +#' " \{cuda.ml\} to enable FIL." #' ) #' } #' @export @@ -62,9 +62,9 @@ file_match_storage_type <- function(storage_type = c("auto", "dense", "sparse")) #' #' @param filename Path to the saved model file. #' @param mode Type of task to be performed by the model. Must be one of -#' {"classification", "regression"}. +#' \{"classification", "regression"\}. #' @param model_type Format of the saved model file. Notice if \code{filename} -#' ends with ".json" and \code{model_type} is "xgboost", then {cuda.ml} will +#' ends with ".json" and \code{model_type} is "xgboost", then \{cuda.ml\} will #' assume the model file is in XGBoost JSON (instead of binary) format. #' Default: "xgboost". #' @param algo Type of the algorithm for inference, must be one of the diff --git a/R/knn.R b/R/knn.R index 0eda43d..4fc8ff6 100644 --- a/R/knn.R +++ b/R/knn.R @@ -111,7 +111,7 @@ cuda_ml_knn_algo_ivfsq <- function(nlist, nprobe, #' @template supervised-model-output #' @template ellipsis-unused #' @param algo The query algorithm to use. Must be one of -#' {"brute", "ivfflat", "ivfpq", "ivfsq"} or a KNN algorithm specification +#' \{"brute", "ivfflat", "ivfpq", "ivfsq"\} or a KNN algorithm specification #' constructed using the \code{cuda_ml_knn_algo_*} family of functions. #' If the algorithm is specified by one of the \code{cuda_ml_knn_algo_*} #' functions, then values of all required parameters of the algorithm will @@ -132,10 +132,10 @@ cuda_ml_knn_algo_ivfsq <- function(nlist, nprobe, #' faster distances calculations). #' #' Default: "brute". -#' @param metric Distance metric to use. Must be one of {"euclidean", "l2", +#' @param metric Distance metric to use. Must be one of \{"euclidean", "l2", #' "l1", "cityblock", "taxicab", "manhattan", "braycurtis", "canberra", #' "minkowski", "lp", "chebyshev", "linf", "jensenshannon", "cosine", -#' "correlation"}. +#' "correlation"\}. #' Default: "euclidean". #' @param p Parameter for the Minkowski metric. If p = 1, then the metric is #' equivalent to manhattan distance (l1). If p = 2, the metric is equivalent diff --git a/R/logistic_reg.R b/R/logistic_reg.R index 1c6e7d4..43b3529 100644 --- a/R/logistic_reg.R +++ b/R/logistic_reg.R @@ -97,7 +97,7 @@ logistic_reg_build_sample_weight <- function(sample_weight, #' @template ellipsis-unused #' @template fit-intercept #' @param penalty The penalty type, must be one of -#' {"none", "l1", "l2", "elasticnet"}. +#' \{"none", "l1", "l2", "elasticnet"\}. #' If "none" or "l2" is selected, then L-BFGS solver will be used. #' If "l1" is selected, solver OWL-QN will be used. #' If "elasticnet" is selected, OWL-QN will be used if l1_ratio > 0, otherwise diff --git a/R/ols.R b/R/ols.R index 3f1c4ba..9d5cefc 100644 --- a/R/ols.R +++ b/R/ols.R @@ -17,7 +17,7 @@ ols_match_method <- function(method = c("svd", "eig", "qr")) { #' @template ellipsis-unused #' @template fit-intercept #' @template normalize-input -#' @param method Must be one of {"svd", "eig", "qr"}. +#' @param method Must be one of \{"svd", "eig", "qr"\}. #' #' - "svd": compute SVD decomposition using Jacobi iterations. #' - "eig": use an eigendecomposition of the covariance matrix. diff --git a/R/sgd.R b/R/sgd.R index 2a2741a..d4953df 100644 --- a/R/sgd.R +++ b/R/sgd.R @@ -38,9 +38,9 @@ sgd_match_learning_rate <- function(learning_rate = c("constant", "invscaling", #' @template ellipsis-unused #' @template fit-intercept #' @template l1_ratio -#' @param loss Loss function, must be one of {"squared_loss", "log", "hinge"}. +#' @param loss Loss function, must be one of \{"squared_loss", "log", "hinge"\}. #' @param penalty Type of regularization to perform, must be one of -#' {"none", "l1", "l2", "elasticnet"}. +#' \{"none", "l1", "l2", "elasticnet"\}. #' #' - "none": no regularization. #' - "l1": perform regularization based on the L1-norm (LASSO) which tries to @@ -63,7 +63,7 @@ sgd_match_learning_rate <- function(learning_rate = c("constant", "invscaling", #' @param eta0 The initial learning rate. Default: 1e-3. #' @param power_t The exponent used for calculating the invscaling learning #' rate. Default: 0.5. -#' @param learning_rate Must be one of {"constant", "invscaling", "adaptive"}. +#' @param learning_rate Must be one of \{"constant", "invscaling", "adaptive"\}. #' #' - "constant": the learning rate will be kept constant. #' - "invscaling": (learning rate) = (initial learning rate) / pow(t, power_t) diff --git a/R/tsne.R b/R/tsne.R index 6299748..49a80cb 100644 --- a/R/tsne.R +++ b/R/tsne.R @@ -24,7 +24,7 @@ new_tsne_model <- function(embedding) { #' @param n_components Dimension of the embedded space. #' @param n_neighbors The number of datapoints to use in the attractive forces. #' Default: ceiling(3 * perplexity). -#' @param method T-SNE method, must be one of {"barnes_hut", "fft", "exact"}. +#' @param method T-SNE method, must be one of \{"barnes_hut", "fft", "exact"\}. #' The "exact" method will be more accurate but slower. Both "barnes_hut" and #' "fft" methods are fast approximations. #' @param angle Valid values are between 0.0 and 1.0, which trade off speed and @@ -35,7 +35,7 @@ new_tsne_model <- function(embedding) { #' @param learning_rate Learning rate of the t-SNE algorithm, usually between #' (10, 1000). If the learning rate is too high, then t-SNE result could look #' like a cloud / ball of points. -#' @param learning_rate_method Must be one of {"adaptive", "none"}. If +#' @param learning_rate_method Must be one of \{"adaptive", "none"\}. If #' "adaptive", then learning rate, early exaggeration, and perplexity are #' automatically tuned based on input size. Default: "adaptive". #' @param perplexity The target value of the conditional distribution's diff --git a/R/umap.R b/R/umap.R index f325aad..ab60823 100644 --- a/R/umap.R +++ b/R/umap.R @@ -41,7 +41,7 @@ new_umap_model <- function(model) { #' @param learning_rate The initial learning rate for the embedding #' optimization. Default: 1.0. #' @param init Initialization mode of the low dimensional embedding. Must be -#' one of {"spectral", "random"}. Default: "spectral". +#' one of \{"spectral", "random"\}. Default: "spectral". #' @param min_dist The effective minimum distance between embedded points. #' Default: 0.1. #' @param spread The effective scale of embedded points. In combination with @@ -71,7 +71,7 @@ new_umap_model <- function(model) { #' the target simplcial set. Default: n_neighbors. #' @param target_metric The metric for measuring distance between the actual and #' and the target values (\code{y}) if using supervised dimension reduction. -#' Must be one of {"categorical", "euclidean"}. Default: "categorical". +#' Must be one of \{"categorical", "euclidean"\}. Default: "categorical". #' @param target_weight Weighting factor between data topology and target #' topology. A value of 0.0 weights entirely on data, a value of 1.0 weights #' entirely on target. The default of 0.5 balances the weighting equally diff --git a/man-roxygen/cuML-log-level.R b/man-roxygen/cuML-log-level.R index aa225f8..e275d88 100644 --- a/man-roxygen/cuML-log-level.R +++ b/man-roxygen/cuML-log-level.R @@ -1,3 +1,3 @@ #' @param cuML_log_level Log level within cuML library functions. Must be one of -#' {"off", "critical", "error", "warn", "info", "debug", "trace"}. +#' \{"off", "critical", "error", "warn", "info", "debug", "trace"\}. #' Default: off. diff --git a/man-roxygen/knn-algo-ivfsq.R b/man-roxygen/knn-algo-ivfsq.R index 6001350..779810d 100644 --- a/man-roxygen/knn-algo-ivfsq.R +++ b/man-roxygen/knn-algo-ivfsq.R @@ -1,4 +1,4 @@ -#' @param qtype Quantizer type. Must be one of {"QT_8bit", "QT_4bit", +#' @param qtype Quantizer type. Must be one of \{"QT_8bit", "QT_4bit", #' "QT_8bit_uniform", "QT_4bit_uniform", "QT_fp16", "QT_8bit_direct", -#' "QT_6bit"}. +#' "QT_6bit"\}. #' @param encode_residual Whether to encode residuals. diff --git a/man/cuML_major_version.Rd b/man/cuML_major_version.Rd index 409ad06..dc5503a 100644 --- a/man/cuML_major_version.Rd +++ b/man/cuML_major_version.Rd @@ -2,18 +2,18 @@ % Please edit documentation in R/cuml_utils.R \name{cuML_major_version} \alias{cuML_major_version} -\title{Get the major version of the RAPIDS cuML shared library {cuda.ml} was linked +\title{Get the major version of the RAPIDS cuML shared library \{cuda.ml\} was linked to.} \usage{ cuML_major_version() } \value{ -The major version of the RAPIDS cuML shared library {cuda.ml} was -linked to in a character vector, or \code{NA_character_} if {cuda.ml} was not +The major version of the RAPIDS cuML shared library \{cuda.ml\} was +linked to in a character vector, or \code{NA_character_} if \{cuda.ml\} was not linked to any version of RAPIDS cuML. } \description{ -Get the major version of the RAPIDS cuML shared library {cuda.ml} was linked +Get the major version of the RAPIDS cuML shared library \{cuda.ml\} was linked to. } \examples{ diff --git a/man/cuML_minor_version.Rd b/man/cuML_minor_version.Rd index 2993da2..4c66f5b 100644 --- a/man/cuML_minor_version.Rd +++ b/man/cuML_minor_version.Rd @@ -2,18 +2,18 @@ % Please edit documentation in R/cuml_utils.R \name{cuML_minor_version} \alias{cuML_minor_version} -\title{Get the minor version of the RAPIDS cuML shared library {cuda.ml} was linked +\title{Get the minor version of the RAPIDS cuML shared library \{cuda.ml\} was linked to.} \usage{ cuML_minor_version() } \value{ -The minor version of the RAPIDS cuML shared library {cuda.ml} was -linked to in a character vector, or \code{NA_character_} if {cuda.ml} was not +The minor version of the RAPIDS cuML shared library \{cuda.ml\} was +linked to in a character vector, or \code{NA_character_} if \{cuda.ml\} was not linked to any version of RAPIDS cuML. } \description{ -Get the minor version of the RAPIDS cuML shared library {cuda.ml} was linked +Get the minor version of the RAPIDS cuML shared library \{cuda.ml\} was linked to. } \examples{ diff --git a/man/cuda_ml_agglomerative_clustering.Rd b/man/cuda_ml_agglomerative_clustering.Rd index 74cc963..8d6427e 100644 --- a/man/cuda_ml_agglomerative_clustering.Rd +++ b/man/cuda_ml_agglomerative_clustering.Rd @@ -19,11 +19,11 @@ and should consist of numeric values only.} \item{n_clusters}{The number of clusters to find. Default: 2L.} \item{metric}{Metric used for linkage computation. Must be one of -{"euclidean", "l1", "l2", "manhattan", "cosine"}. If connectivity is +\{"euclidean", "l1", "l2", "manhattan", "cosine"\}. If connectivity is "knn" then only "euclidean" is accepted. Default: "euclidean".} \item{connectivity}{The type of connectivity matrix to compute. Must be one -of {"pairwise", "knn"}. Default: "pairwise". +of \{"pairwise", "knn"\}. Default: "pairwise". - 'pairwise' will compute the entire fully-connected graph of pairwise distances between each set of points. This is the fastest to compute and can be very fast for smaller datasets but requires O(n^2) space. diff --git a/man/cuda_ml_dbscan.Rd b/man/cuda_ml_dbscan.Rd index 9b57851..a53ffd3 100644 --- a/man/cuda_ml_dbscan.Rd +++ b/man/cuda_ml_dbscan.Rd @@ -19,7 +19,7 @@ and should consist of numeric values only.} within distance `eps` from it.} \item{cuML_log_level}{Log level within cuML library functions. Must be one of -{"off", "critical", "error", "warn", "info", "debug", "trace"}. +\{"off", "critical", "error", "warn", "info", "debug", "trace"\}. Default: off.} } \value{ diff --git a/man/cuda_ml_fil_enabled.Rd b/man/cuda_ml_fil_enabled.Rd index 23a39fa..4458c98 100644 --- a/man/cuda_ml_fil_enabled.Rd +++ b/man/cuda_ml_fil_enabled.Rd @@ -3,7 +3,7 @@ \name{cuda_ml_fil_enabled} \alias{cuda_ml_fil_enabled} \title{Determine whether Forest Inference Library (FIL) functionalities are enabled -in the current installation of {cuda.ml}.} +in the current installation of \{cuda.ml\}.} \usage{ cuda_ml_fil_enabled() } @@ -16,7 +16,7 @@ CuML Forest Inference Library (FIL) functionalities (see https://github.com/rapidsai/cuml/tree/main/python/cuml/fil#readme) will require Treelite C API. If you need FIL to run tree-based model ensemble on GPU, and \code{fil_enabled()} returns FALSE, then please consider installing -Treelite and then re-installing {cuda.ml}. +Treelite and then re-installing \{cuda.ml\}. } \examples{ if (cuda_ml_fil_enabled()) { @@ -24,8 +24,8 @@ if (cuda_ml_fil_enabled()) { } else { message( "FIL functionalities are disabled in the current installation of ", - "{cuda.ml}. Please reinstall Treelite C library first, and then re-install", - " {cuda.ml} to enable FIL." + "\{cuda.ml\}. Please reinstall Treelite C library first, and then re-install", + " \{cuda.ml\} to enable FIL." ) } } diff --git a/man/cuda_ml_fil_load_model.Rd b/man/cuda_ml_fil_load_model.Rd index 3f7252b..e1d9a30 100644 --- a/man/cuda_ml_fil_load_model.Rd +++ b/man/cuda_ml_fil_load_model.Rd @@ -20,10 +20,10 @@ cuda_ml_fil_load_model( \item{filename}{Path to the saved model file.} \item{mode}{Type of task to be performed by the model. Must be one of -{"classification", "regression"}.} +\{"classification", "regression"\}.} \item{model_type}{Format of the saved model file. Notice if \code{filename} -ends with ".json" and \code{model_type} is "xgboost", then {cuda.ml} will +ends with ".json" and \code{model_type} is "xgboost", then \{cuda.ml\} will assume the model file is in XGBoost JSON (instead of binary) format. Default: "xgboost".} diff --git a/man/cuda_ml_kmeans.Rd b/man/cuda_ml_kmeans.Rd index 4f738a7..28c7529 100644 --- a/man/cuda_ml_kmeans.Rd +++ b/man/cuda_ml_kmeans.Rd @@ -32,7 +32,7 @@ the initial value of a centroid. Default: "kmeans++".} \item{seed}{Seed to the random number generator. Default: 0.} \item{cuML_log_level}{Log level within cuML library functions. Must be one of -{"off", "critical", "error", "warn", "info", "debug", "trace"}. +\{"off", "critical", "error", "warn", "info", "debug", "trace"\}. Default: off.} } \value{ diff --git a/man/cuda_ml_knn.Rd b/man/cuda_ml_knn.Rd index a0ffa76..93f4d82 100644 --- a/man/cuda_ml_knn.Rd +++ b/man/cuda_ml_knn.Rd @@ -72,7 +72,7 @@ cuda_ml_knn(x, ...) desired responses.} \item{algo}{The query algorithm to use. Must be one of - {"brute", "ivfflat", "ivfpq", "ivfsq"} or a KNN algorithm specification + \{"brute", "ivfflat", "ivfpq", "ivfsq"\} or a KNN algorithm specification constructed using the \code{cuda_ml_knn_algo_*} family of functions. If the algorithm is specified by one of the \code{cuda_ml_knn_algo_*} functions, then values of all required parameters of the algorithm will @@ -94,10 +94,10 @@ desired responses.} Default: "brute".} -\item{metric}{Distance metric to use. Must be one of {"euclidean", "l2", +\item{metric}{Distance metric to use. Must be one of \{"euclidean", "l2", "l1", "cityblock", "taxicab", "manhattan", "braycurtis", "canberra", "minkowski", "lp", "chebyshev", "linf", "jensenshannon", "cosine", -"correlation"}. +"correlation"\}. Default: "euclidean".} \item{p}{Parameter for the Minkowski metric. If p = 1, then the metric is diff --git a/man/cuda_ml_knn_algo_ivfsq.Rd b/man/cuda_ml_knn_algo_ivfsq.Rd index 6b7be11..f4b76f2 100644 --- a/man/cuda_ml_knn_algo_ivfsq.Rd +++ b/man/cuda_ml_knn_algo_ivfsq.Rd @@ -18,9 +18,9 @@ cuda_ml_knn_algo_ivfsq( \item{nprobe}{At query time, the number of cells used for approximate nearest neighbor search.} -\item{qtype}{Quantizer type. Must be one of {"QT_8bit", "QT_4bit", +\item{qtype}{Quantizer type. Must be one of \{"QT_8bit", "QT_4bit", "QT_8bit_uniform", "QT_4bit_uniform", "QT_fp16", "QT_8bit_direct", -"QT_6bit"}.} +"QT_6bit"\}.} \item{encode_residual}{Whether to encode residuals.} } diff --git a/man/cuda_ml_logistic_reg.Rd b/man/cuda_ml_logistic_reg.Rd index df89512..d0c1394 100644 --- a/man/cuda_ml_logistic_reg.Rd +++ b/man/cuda_ml_logistic_reg.Rd @@ -92,7 +92,7 @@ mean of the response variable. If FALSE, then the model expects data to be centered. Default: TRUE.} \item{penalty}{The penalty type, must be one of -{"none", "l1", "l2", "elasticnet"}. +\{"none", "l1", "l2", "elasticnet"\}. If "none" or "l2" is selected, then L-BFGS solver will be used. If "l1" is selected, solver OWL-QN will be used. If "elasticnet" is selected, OWL-QN will be used if l1_ratio > 0, otherwise diff --git a/man/cuda_ml_ols.Rd b/man/cuda_ml_ols.Rd index c4d2fbf..05cb292 100644 --- a/man/cuda_ml_ols.Rd +++ b/man/cuda_ml_ols.Rd @@ -63,7 +63,7 @@ cuda_ml_ols(x, ...) \item{y}{A numeric vector (for regression) or factor (for classification) of desired responses.} -\item{method}{Must be one of {"svd", "eig", "qr"}. +\item{method}{Must be one of \{"svd", "eig", "qr"\}. - "svd": compute SVD decomposition using Jacobi iterations. - "eig": use an eigendecomposition of the covariance matrix. diff --git a/man/cuda_ml_pca.Rd b/man/cuda_ml_pca.Rd index 376954c..80ad31c 100644 --- a/man/cuda_ml_pca.Rd +++ b/man/cuda_ml_pca.Rd @@ -41,7 +41,7 @@ Default: FALSE.} of the input data. Default: TRUE.} \item{cuML_log_level}{Log level within cuML library functions. Must be one of -{"off", "critical", "error", "warn", "info", "debug", "trace"}. +\{"off", "critical", "error", "warn", "info", "debug", "trace"\}. Default: off.} } \value{ diff --git a/man/cuda_ml_rand_forest.Rd b/man/cuda_ml_rand_forest.Rd index c922a55..d629ece 100644 --- a/man/cuda_ml_rand_forest.Rd +++ b/man/cuda_ml_rand_forest.Rd @@ -149,7 +149,7 @@ given batch. Default: 128L.} Default: 8L.} \item{cuML_log_level}{Log level within cuML library functions. Must be one of -{"off", "critical", "error", "warn", "info", "debug", "trace"}. +\{"off", "critical", "error", "warn", "info", "debug", "trace"\}. Default: off.} \item{formula}{A formula specifying the outcome terms on the left-hand side, diff --git a/man/cuda_ml_sgd.Rd b/man/cuda_ml_sgd.Rd index c1fc940..5250bfe 100644 --- a/man/cuda_ml_sgd.Rd +++ b/man/cuda_ml_sgd.Rd @@ -107,10 +107,10 @@ desired responses.} mean of the response variable. If FALSE, then the model expects data to be centered. Default: TRUE.} -\item{loss}{Loss function, must be one of {"squared_loss", "log", "hinge"}.} +\item{loss}{Loss function, must be one of \{"squared_loss", "log", "hinge"\}.} \item{penalty}{Type of regularization to perform, must be one of - {"none", "l1", "l2", "elasticnet"}. + \{"none", "l1", "l2", "elasticnet"\}. - "none": no regularization. - "l1": perform regularization based on the L1-norm (LASSO) which tries to @@ -143,7 +143,7 @@ Default: 1e-3.} \item{shuffle}{Whether to shuffles the training data after each epoch. Default: True.} -\item{learning_rate}{Must be one of {"constant", "invscaling", "adaptive"}. +\item{learning_rate}{Must be one of \{"constant", "invscaling", "adaptive"\}. - "constant": the learning rate will be kept constant. - "invscaling": (learning rate) = (initial learning rate) / pow(t, power_t) diff --git a/man/cuda_ml_svm.Rd b/man/cuda_ml_svm.Rd index 0fef9d6..2219b48 100644 --- a/man/cuda_ml_svm.Rd +++ b/man/cuda_ml_svm.Rd @@ -154,7 +154,7 @@ tasks. Default: 0.1.} \item{sample_weights}{Optional weight assigned to each input data point.} \item{cuML_log_level}{Log level within cuML library functions. Must be one of -{"off", "critical", "error", "warn", "info", "debug", "trace"}. +\{"off", "critical", "error", "warn", "info", "debug", "trace"\}. Default: off.} \item{formula}{A formula specifying the outcome terms on the left-hand side, diff --git a/man/cuda_ml_tsne.Rd b/man/cuda_ml_tsne.Rd index c6fb603..f3b5f44 100644 --- a/man/cuda_ml_tsne.Rd +++ b/man/cuda_ml_tsne.Rd @@ -36,7 +36,7 @@ and should consist of numeric values only.} \item{n_neighbors}{The number of datapoints to use in the attractive forces. Default: ceiling(3 * perplexity).} -\item{method}{T-SNE method, must be one of {"barnes_hut", "fft", "exact"}. +\item{method}{T-SNE method, must be one of \{"barnes_hut", "fft", "exact"\}. The "exact" method will be more accurate but slower. Both "barnes_hut" and "fft" methods are fast approximations.} @@ -51,7 +51,7 @@ at least 250. Default: 1000L.} (10, 1000). If the learning rate is too high, then t-SNE result could look like a cloud / ball of points.} -\item{learning_rate_method}{Must be one of {"adaptive", "none"}. If +\item{learning_rate_method}{Must be one of \{"adaptive", "none"\}. If "adaptive", then learning rate, early exaggeration, and perplexity are automatically tuned based on input size. Default: "adaptive".} @@ -94,7 +94,7 @@ runs, even with the same \code{seed} being used for each run. Default: NULL.} \item{cuML_log_level}{Log level within cuML library functions. Must be one of -{"off", "critical", "error", "warn", "info", "debug", "trace"}. +\{"off", "critical", "error", "warn", "info", "debug", "trace"\}. Default: off.} } \value{ diff --git a/man/cuda_ml_tsvd.Rd b/man/cuda_ml_tsvd.Rd index 96c4a83..bd6dd02 100644 --- a/man/cuda_ml_tsvd.Rd +++ b/man/cuda_ml_tsvd.Rd @@ -37,7 +37,7 @@ Default: 15.} of the input data. Default: TRUE.} \item{cuML_log_level}{Log level within cuML library functions. Must be one of -{"off", "critical", "error", "warn", "info", "debug", "trace"}. +\{"off", "critical", "error", "warn", "info", "debug", "trace"\}. Default: off.} } \value{ diff --git a/man/cuda_ml_umap.Rd b/man/cuda_ml_umap.Rd index 3b46441..c9cfdd4 100644 --- a/man/cuda_ml_umap.Rd +++ b/man/cuda_ml_umap.Rd @@ -48,7 +48,7 @@ low dimensional embedding. Default: 500.} optimization. Default: 1.0.} \item{init}{Initialization mode of the low dimensional embedding. Must be -one of {"spectral", "random"}. Default: "spectral".} +one of \{"spectral", "random"\}. Default: "spectral".} \item{min_dist}{The effective minimum distance between embedded points. Default: 0.1.} @@ -88,7 +88,7 @@ the target simplcial set. Default: n_neighbors.} \item{target_metric}{The metric for measuring distance between the actual and and the target values (\code{y}) if using supervised dimension reduction. -Must be one of {"categorical", "euclidean"}. Default: "categorical".} +Must be one of \{"categorical", "euclidean"\}. Default: "categorical".} \item{target_weight}{Weighting factor between data topology and target topology. A value of 0.0 weights entirely on data, a value of 1.0 weights @@ -106,7 +106,7 @@ If the PRNG seed is not set, then the trained embeddings will not be deterministic.} \item{cuML_log_level}{Log level within cuML library functions. Must be one of -{"off", "critical", "error", "warn", "info", "debug", "trace"}. +\{"off", "critical", "error", "warn", "info", "debug", "trace"\}. Default: off.} } \value{ diff --git a/man/has_cuML.Rd b/man/has_cuML.Rd index a78075c..d644c4b 100644 --- a/man/has_cuML.Rd +++ b/man/has_cuML.Rd @@ -2,17 +2,17 @@ % Please edit documentation in R/cuml_utils.R \name{has_cuML} \alias{has_cuML} -\title{Determine whether {cuda.ml} was linked to a valid version of the RAPIDS cuML +\title{Determine whether \{cuda.ml\} was linked to a valid version of the RAPIDS cuML shared library.} \usage{ has_cuML() } \value{ -A logical value indicating whether the current installation {cuda.ml} +A logical value indicating whether the current installation \{cuda.ml\} was linked to a valid version of the RAPIDS cuML shared library. } \description{ -Determine whether {cuda.ml} was linked to a valid version of the RAPIDS cuML +Determine whether \{cuda.ml\} was linked to a valid version of the RAPIDS cuML shared library. } \examples{ @@ -22,7 +22,7 @@ library(cuda.ml) if (!has_cuML()) { warning( "Please install the RAPIDS cuML shared library first, and then re-", - "install {cuda.ml}." + "install \{cuda.ml\}." ) } } diff --git a/man/predict.cuda_ml_rand_forest.Rd b/man/predict.cuda_ml_rand_forest.Rd index 9a05897..e9510fe 100644 --- a/man/predict.cuda_ml_rand_forest.Rd +++ b/man/predict.cuda_ml_rand_forest.Rd @@ -27,7 +27,7 @@ is set to \code{TRUE} or \code{FALSE} but the model being applied does not support class probabilities output.} \item{cuML_log_level}{Log level within cuML library functions. Must be one of -{"off", "critical", "error", "warn", "info", "debug", "trace"}. +\{"off", "critical", "error", "warn", "info", "debug", "trace"\}. Default: off.} \item{...}{Additional arguments to \code{predict()}. Currently unused.} diff --git a/tests/testthat.R b/tests/testthat.R index 269f852..1f11702 100644 --- a/tests/testthat.R +++ b/tests/testthat.R @@ -1,6 +1,7 @@ library(testthat) +library(cuda.ml) -if (identical(Sys.getenv("NOT_CRAN"), "true")) { +if (identical(Sys.getenv("NOT_CRAN"), "true") && has_cuML()) { filter <- Sys.getenv("TESTTHAT_FILTER", unset = "") if (identical(filter, "")) filter <- NULL From 15c988a6303f47851e65082cc8845102f6926c78 Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Wed, 29 Apr 2026 08:18:10 -0300 Subject: [PATCH 15/28] Fix examples brace escaping and register S3 methods - Revert brace escaping inside @examples blocks (R code, not Rd markup) - Define cuda_ml_can_predict_class_probabilities methods as proper functions so roxygen registers them as S3method() in NAMESPACE --- NAMESPACE | 3 +++ R/cuml_utils.R | 2 +- R/fil.R | 4 ++-- R/model.R | 15 ++++++++++++--- man/cuda_ml_fil_enabled.Rd | 4 ++-- man/has_cuML.Rd | 2 +- 6 files changed, 21 insertions(+), 9 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index 5a409d0..5a72bf9 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,6 +1,9 @@ # Generated by roxygen2: do not edit by hand +S3method(cuda_ml_can_predict_class_probabilities,cuda_ml_fil) +S3method(cuda_ml_can_predict_class_probabilities,cuda_ml_knn) S3method(cuda_ml_can_predict_class_probabilities,cuda_ml_model) +S3method(cuda_ml_can_predict_class_probabilities,cuda_ml_rand_forest) S3method(cuda_ml_can_predict_class_probabilities,default) S3method(cuda_ml_elastic_net,data.frame) S3method(cuda_ml_elastic_net,default) diff --git a/R/cuml_utils.R b/R/cuml_utils.R index 5bba36f..abd8822 100644 --- a/R/cuml_utils.R +++ b/R/cuml_utils.R @@ -11,7 +11,7 @@ #' if (!has_cuML()) { #' warning( #' "Please install the RAPIDS cuML shared library first, and then re-", -#' "install \{cuda.ml\}." +#' "install {cuda.ml}." #' ) #' } #' @export diff --git a/R/fil.R b/R/fil.R index 295793a..c8b54bf 100644 --- a/R/fil.R +++ b/R/fil.R @@ -16,8 +16,8 @@ #' } else { #' message( #' "FIL functionalities are disabled in the current installation of ", -#' "\{cuda.ml\}. Please reinstall Treelite C library first, and then re-install", -#' " \{cuda.ml\} to enable FIL." +#' "{cuda.ml}. Please reinstall Treelite C library first, and then re-install", +#' " {cuda.ml} to enable FIL." #' ) #' } #' @export diff --git a/R/model.R b/R/model.R index da70baf..a5edeb5 100644 --- a/R/model.R +++ b/R/model.R @@ -138,11 +138,20 @@ cuda_ml_can_predict_class_probabilities.cuda_ml_model <- function(model) { FALSE } -cuda_ml_can_predict_class_probabilities.cuda_ml_fil <- cuda_ml_is_classifier +#' @export +cuda_ml_can_predict_class_probabilities.cuda_ml_fil <- function(model) { + cuda_ml_is_classifier(model) +} -cuda_ml_can_predict_class_probabilities.cuda_ml_knn <- cuda_ml_is_classifier +#' @export +cuda_ml_can_predict_class_probabilities.cuda_ml_knn <- function(model) { + cuda_ml_is_classifier(model) +} -cuda_ml_can_predict_class_probabilities.cuda_ml_rand_forest <- cuda_ml_is_classifier +#' @export +cuda_ml_can_predict_class_probabilities.cuda_ml_rand_forest <- function(model) { + cuda_ml_is_classifier(model) +} #' Serialize a CuML model #' diff --git a/man/cuda_ml_fil_enabled.Rd b/man/cuda_ml_fil_enabled.Rd index 4458c98..1ebcefb 100644 --- a/man/cuda_ml_fil_enabled.Rd +++ b/man/cuda_ml_fil_enabled.Rd @@ -24,8 +24,8 @@ if (cuda_ml_fil_enabled()) { } else { message( "FIL functionalities are disabled in the current installation of ", - "\{cuda.ml\}. Please reinstall Treelite C library first, and then re-install", - " \{cuda.ml\} to enable FIL." + "{cuda.ml}. Please reinstall Treelite C library first, and then re-install", + " {cuda.ml} to enable FIL." ) } } diff --git a/man/has_cuML.Rd b/man/has_cuML.Rd index d644c4b..8305755 100644 --- a/man/has_cuML.Rd +++ b/man/has_cuML.Rd @@ -22,7 +22,7 @@ library(cuda.ml) if (!has_cuML()) { warning( "Please install the RAPIDS cuML shared library first, and then re-", - "install \{cuda.ml\}." + "install {cuda.ml}." ) } } From 0ca00e949376820afd1a54ab321e5f96da321654 Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Wed, 29 Apr 2026 09:16:23 -0300 Subject: [PATCH 16/28] Add RAPIDS cuML 26.04 + CUDA 12 support Build infrastructure: - Dockerfile: CUDA 12.8.1 + Ubuntu 22.04 base image - libcuml_versions.R: add 26.04 entry pointing to PyPI libcuml-cu12 wheel - cuml.R: handle pip wheel extraction (lib64/ layout, .whl extension) - configure.R: handle lib64/ vs lib/ for pip wheels - CMakeLists.txt.in: C++17, rapids-cmake branch-26.04 - Workflow: target cuML 26.04 C++ API changes for cuML 26.04: - svm_serde.h: namespace alias MLCommon::Matrix -> ML::matrix for KernelParams and KernelType (header renamed kernelparams.h -> kernel_params.hpp) - fil.cu, fil_utils.h, fil_utils.cu: disable FIL on 26.04 with stubs (fil.h replaced by modular headers; full adaptation TODO) - random_projection.cu: disable on 26.04 with stubs (C++ API removed) - knn.cu: disable on 26.04 with stubs (raft::spatial::knn types removed) - random_forest_classifier.cu, random_forest_regressor.cu: guard FIL prediction paths for 26.04 Backward compatible: cuML 21.x with CUDA 11 still works. --- .github/docker/Dockerfile | 4 +-- .github/workflows/R-CMD-check.yaml | 2 +- src/CMakeLists.txt.in | 10 ++----- src/fil.cu | 40 ++++++++++++++++++++++++- src/fil_utils.cu | 8 +++++ src/fil_utils.h | 8 +++++ src/knn.cu | 46 ++++++++++++++++++++++++++-- src/random_forest_classifier.cu | 25 ++++++++++++++-- src/random_forest_regressor.cu | 14 ++++++++- src/random_projection.cu | 48 ++++++++++++++++++++++++++++-- src/svm_serde.h | 27 +++++++++++++---- tools/config/configure.R | 8 +++-- tools/config/libcuml_versions.R | 7 +++++ tools/config/utils/cuml.R | 18 +++++++++-- 14 files changed, 234 insertions(+), 31 deletions(-) diff --git a/.github/docker/Dockerfile b/.github/docker/Dockerfile index 2dbb8ec..a414e7a 100644 --- a/.github/docker/Dockerfile +++ b/.github/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM nvidia/cuda:11.2.2-devel-ubuntu20.04 +FROM nvidia/cuda:12.8.1-devel-ubuntu22.04 ENV DEBIAN_FRONTEND=noninteractive @@ -31,7 +31,7 @@ RUN echo "MAKEFLAGS=-j$(nproc)" >> "$(R RHOME)/etc/Renviron.site" # Copy source COPY . /build -ARG CUML_VERSION=21.12 +ARG CUML_VERSION=26.04 ENV CUML_VERSION=${CUML_VERSION} # Cross-compile for T4 GPU (compute capability 7.5) since build runner has no GPU diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 257527e..e78c89c 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -71,7 +71,7 @@ jobs: push: true tags: ghcr.io/${{ github.repository }}-ci:${{ github.sha }} build-args: | - CUML_VERSION=21.12 + CUML_VERSION=26.04 CMAKE_CUDA_ARCHITECTURES=75 test-gpu: diff --git a/src/CMakeLists.txt.in b/src/CMakeLists.txt.in index 030d323..d9d5890 100644 --- a/src/CMakeLists.txt.in +++ b/src/CMakeLists.txt.in @@ -1,22 +1,16 @@ cmake_minimum_required(VERSION 3.8 FATAL_ERROR) -set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") add_definitions(-DHAS_CUML) -# NOTE: at the moment only the `rapids_cuda_init_architectures` function -# from the rapids-cmake repo is used here, and the version from the 21.10 -# branch of rapids-cmake works fine, regardless of which version of libcuml -# we are building with. However, if in future any cuml-version-specific -# function from rapids-cmake is used here, then we will need to make the -# GIT_TAG choice below dependent on the libcuml version. include(FetchContent) FetchContent_Declare( rapids-cmake GIT_REPOSITORY https://github.com/rapidsai/rapids-cmake.git - GIT_TAG origin/branch-21.10 + GIT_TAG origin/branch-26.04 ) FetchContent_MakeAvailable(rapids-cmake) include(rapids-cuda) diff --git a/src/fil.cu b/src/fil.cu index 1545177..fdf893f 100644 --- a/src/fil.cu +++ b/src/fil.cu @@ -1,10 +1,15 @@ +#include "preprocessor.h" + +#include + +#if CUML_VERSION_MAJOR < 26 + #include "async_utils.cuh" #include "cuda_utils.h" #include "fil_utils.h" #include "handle_utils.h" #include "matrix_utils.h" #include "pinned_host_vector.h" -#include "preprocessor.h" #include "stream_allocator.h" #include "treelite_utils.cuh" @@ -172,3 +177,36 @@ __host__ Rcpp::NumericMatrix fil_predict( } } // namespace cuml4r + +#else // CUML_VERSION_MAJOR >= 26 + +#include + +#include + +namespace cuml4r { + +__host__ SEXP fil_load_model(int const model_type, std::string const& filename, + int const algo, bool const classification, + float const threshold, int const storage_type, + int const blocks_per_sm, + int const threads_per_tree, int const n_items) { + Rcpp::stop("FIL (Forest Inference Library) is not yet supported with cuML 26.04."); + return R_NilValue; +} + +__host__ int fil_get_num_classes(SEXP const& model) { + Rcpp::stop("FIL (Forest Inference Library) is not yet supported with cuML 26.04."); + return 0; +} + +__host__ Rcpp::NumericMatrix fil_predict( + SEXP const& model, Rcpp::NumericMatrix const& x, + bool const output_class_probabilities) { + Rcpp::stop("FIL (Forest Inference Library) is not yet supported with cuML 26.04."); + return Rcpp::NumericMatrix(); +} + +} // namespace cuml4r + +#endif // CUML_VERSION_MAJOR < 26 diff --git a/src/fil_utils.cu b/src/fil_utils.cu index e36d501..da39c32 100644 --- a/src/fil_utils.cu +++ b/src/fil_utils.cu @@ -1,3 +1,9 @@ +#include "preprocessor.h" + +#include + +#if CUML_VERSION_MAJOR < 26 + #include "fil_utils.h" namespace cuml4r { @@ -19,3 +25,5 @@ __host__ forest_uptr make_forest(raft::handle_t const& handle, } // namespace fil } // namespace cuml4r + +#endif // CUML_VERSION_MAJOR < 26 diff --git a/src/fil_utils.h b/src/fil_utils.h index a5702d0..dca99c3 100644 --- a/src/fil_utils.h +++ b/src/fil_utils.h @@ -1,5 +1,11 @@ #pragma once +#include "preprocessor.h" + +#include + +#if CUML_VERSION_MAJOR < 26 + #include #include @@ -26,3 +32,5 @@ forest_uptr make_forest(raft::handle_t const& handle, } // namespace fil } // namespace cuml4r + +#endif // CUML_VERSION_MAJOR < 26 diff --git a/src/knn.cu b/src/knn.cu index 13894d8..797fa35 100644 --- a/src/knn.cu +++ b/src/knn.cu @@ -1,17 +1,21 @@ +#include "preprocessor.h" + +#include + +#if CUML_VERSION_MAJOR < 26 + #include "async_utils.cuh" #include "cuda_utils.h" #include "handle_utils.h" #include "knn_detail.h" #include "matrix_utils.h" #include "pinned_host_vector.h" -#include "preprocessor.h" #include "random_forest.cuh" #include "stream_allocator.h" #include #include #include -#include #include @@ -478,3 +482,41 @@ Rcpp::NumericVector knn_regressor_predict(Rcpp::List const& model, } } // namespace cuml4r + +#else // CUML_VERSION_MAJOR >= 26 + +#include + +namespace cuml4r { + +__host__ Rcpp::List knn_fit(Rcpp::NumericMatrix const& x, int const algo, + int const metric, float const p, + Rcpp::List const& algo_params) { + Rcpp::stop("KNN is not yet supported with cuML 26.04."); + return Rcpp::List(); +} + +__host__ Rcpp::IntegerVector knn_classifier_predict( + Rcpp::List const& model, Rcpp::NumericMatrix const& x, + int const n_neighbors) { + Rcpp::stop("KNN is not yet supported with cuML 26.04."); + return Rcpp::IntegerVector(); +} + +__host__ Rcpp::NumericMatrix knn_classifier_predict_probabilities( + Rcpp::List const& model, Rcpp::NumericMatrix const& x, + int const n_neighbors) { + Rcpp::stop("KNN is not yet supported with cuML 26.04."); + return Rcpp::NumericMatrix(); +} + +Rcpp::NumericVector knn_regressor_predict(Rcpp::List const& model, + Rcpp::NumericMatrix const& x, + int const n_neighbors) { + Rcpp::stop("KNN is not yet supported with cuML 26.04."); + return Rcpp::NumericVector(); +} + +} // namespace cuml4r + +#endif // CUML_VERSION_MAJOR < 26 diff --git a/src/random_forest_classifier.cu b/src/random_forest_classifier.cu index 9c277c0..1b424c8 100644 --- a/src/random_forest_classifier.cu +++ b/src/random_forest_classifier.cu @@ -1,6 +1,5 @@ #include "async_utils.cuh" #include "cuda_utils.h" -#include "fil_utils.h" #include "handle_utils.h" #include "matrix_utils.h" #include "pinned_host_vector.h" @@ -9,12 +8,16 @@ #include "random_forest_serde.cuh" #include "stream_allocator.h" -#include #include #include #include #include +#if CUML_VERSION_MAJOR < 26 +#include "fil_utils.h" +#include +#endif + #include #include @@ -198,6 +201,7 @@ __host__ Rcpp::IntegerVector rf_classifier_predict( return Rcpp::IntegerVector(h_predictions.begin(), h_predictions.end()); } +#if CUML_VERSION_MAJOR < 26 /* * The 'ML::fil::treelite_params_t::threads_per_tree' and * 'ML::fil::treelite_params_t::n_items' parameters are only supported in @@ -208,6 +212,7 @@ CUML4R_NOOP_IF_ABSENT(threads_per_tree) CUML4R_ASSIGN_IF_PRESENT(n_items) CUML4R_NOOP_IF_ABSENT(n_items) +#endif // CUML_VERSION_MAJOR < 26 } // namespace @@ -302,6 +307,12 @@ __host__ Rcpp::IntegerVector rf_classifier_predict( /*predictions=*/d_preds, verbosity); }); } else { +#if CUML_VERSION_MAJOR >= 26 + Rcpp::stop( + "FIL-based prediction from unserialized random forest models is not yet " + "supported with cuML 26.04."); + return Rcpp::IntegerVector(); +#else return rf_classifier_predict( model, input, /*predict_impl=*/ @@ -339,12 +350,20 @@ __host__ Rcpp::IntegerVector rf_classifier_predict( #endif }); +#endif // CUML_VERSION_MAJOR >= 26 } } __host__ Rcpp::NumericMatrix rf_classifier_predict_class_probabilities( SEXP model_xptr, Rcpp::NumericMatrix const& input) { -#ifndef CUML4R_TREELITE_C_API_MISSING +#if CUML_VERSION_MAJOR >= 26 + + Rcpp::stop( + "FIL-based class probability prediction for random forests is not yet " + "supported with cuML 26.04."); + return Rcpp::NumericMatrix(); + +#elif !defined(CUML4R_TREELITE_C_API_MISSING) auto const input_m = Matrix(input, /*transpose=*/false); int const n_samples = input_m.numRows; diff --git a/src/random_forest_regressor.cu b/src/random_forest_regressor.cu index 1dd9dea..61d8fe0 100644 --- a/src/random_forest_regressor.cu +++ b/src/random_forest_regressor.cu @@ -1,6 +1,5 @@ #include "async_utils.cuh" #include "cuda_utils.h" -#include "fil_utils.h" #include "handle_utils.h" #include "matrix_utils.h" #include "pinned_host_vector.h" @@ -13,6 +12,10 @@ #include #include +#if CUML_VERSION_MAJOR < 26 +#include "fil_utils.h" +#endif + #include #include @@ -123,6 +126,7 @@ __host__ Rcpp::NumericVector rf_regressor_predict( return Rcpp::NumericVector(h_preds.begin(), h_preds.end()); } +#if CUML_VERSION_MAJOR < 26 /* * The 'ML::fil::treelite_params_t::threads_per_tree' and * 'ML::fil::treelite_params_t::n_items' parameters are only supported in @@ -133,6 +137,7 @@ CUML4R_NOOP_IF_ABSENT(threads_per_tree) CUML4R_ASSIGN_IF_PRESENT(n_items) CUML4R_NOOP_IF_ABSENT(n_items) +#endif // CUML_VERSION_MAJOR < 26 } // namespace @@ -222,6 +227,12 @@ __host__ Rcpp::NumericVector rf_regressor_predict( /*predictions=*/d_preds, verbosity); }); } else { +#if CUML_VERSION_MAJOR >= 26 + Rcpp::stop( + "FIL-based prediction from unserialized random forest models is not yet " + "supported with cuML 26.04."); + return Rcpp::NumericVector(); +#else return rf_regressor_predict( input, /*predict_impl=*/ @@ -259,6 +270,7 @@ __host__ Rcpp::NumericVector rf_regressor_predict( #endif }); +#endif // CUML_VERSION_MAJOR >= 26 } } diff --git a/src/random_projection.cu b/src/random_projection.cu index 30cf51c..b5943c4 100644 --- a/src/random_projection.cu +++ b/src/random_projection.cu @@ -1,15 +1,19 @@ +#include "preprocessor.h" + +#include + +#if CUML_VERSION_MAJOR < 26 + #include "async_utils.cuh" #include "cuda_utils.h" #include "handle_utils.h" #include "matrix_utils.h" #include "pinned_host_vector.h" -#include "preprocessor.h" #include "stream_allocator.h" #include #include #include -#include #include @@ -245,3 +249,43 @@ __host__ SEXP rproj_set_state(Rcpp::List const& model_state) { } } // namespace cuml4r + +#else // CUML_VERSION_MAJOR >= 26 + +#include + +namespace cuml4r { + +__host__ size_t rproj_johnson_lindenstrauss_min_dim(size_t const n_samples, + double const eps) { + Rcpp::stop("Random projection is not yet supported with cuML 26.04."); + return 0; +} + +__host__ SEXP rproj_fit(int const n_samples, int const n_features, + int const n_components, double const eps, + bool const gaussian_method, double const density, + int const random_state) { + Rcpp::stop("Random projection is not yet supported with cuML 26.04."); + return R_NilValue; +} + +__host__ Rcpp::NumericMatrix rproj_transform(SEXP rproj_ctx_xptr, + Rcpp::NumericMatrix const& input) { + Rcpp::stop("Random projection is not yet supported with cuML 26.04."); + return Rcpp::NumericMatrix(); +} + +__host__ Rcpp::List rproj_get_state(SEXP model) { + Rcpp::stop("Random projection is not yet supported with cuML 26.04."); + return Rcpp::List(); +} + +__host__ SEXP rproj_set_state(Rcpp::List const& model_state) { + Rcpp::stop("Random projection is not yet supported with cuML 26.04."); + return R_NilValue; +} + +} // namespace cuml4r + +#endif // CUML_VERSION_MAJOR < 26 diff --git a/src/svm_serde.h b/src/svm_serde.h index c0d762c..cdad438 100644 --- a/src/svm_serde.h +++ b/src/svm_serde.h @@ -2,24 +2,39 @@ #include "preprocessor.h" +#include + +#if CUML_VERSION_MAJOR >= 26 +#include +#else #include +#endif + #include #include -#include #include -#if (CUML4R_LIBCUML_VERSION(CUML_VERSION_MAJOR, CUML_VERSION_MINOR) >= \ - CUML4R_LIBCUML_VERSION(21, 10)) +// In cuML 26.04+, KernelParams moved from MLCommon::Matrix to ML::matrix +// and the type names svmParameter/svmModel were already renamed to SvmParameter/SvmModel +// in 21.10. For 26.04 we also need the namespace alias. +#if CUML_VERSION_MAJOR >= 26 + +namespace MLCommon { +namespace Matrix { +using KernelParams = ML::matrix::KernelParams; +using KernelType = ML::matrix::KernelType; +} // namespace Matrix +} // namespace MLCommon + +#elif (CUML4R_LIBCUML_VERSION(CUML_VERSION_MAJOR, CUML_VERSION_MINOR) >= \ + CUML4R_LIBCUML_VERSION(21, 10)) namespace ML { namespace SVM { - using svmParameter = SvmParameter; - template using svmModel = SvmModel; - } // namespace SVM } // namespace ML diff --git a/tools/config/configure.R b/tools/config/configure.R index e271f6d..58118a1 100644 --- a/tools/config/configure.R +++ b/tools/config/configure.R @@ -77,9 +77,13 @@ run_cmake <- function() { download_libcuml() cuml_prefix <- normalizePath(file.path(pkg_root(), "libcuml")) dir.create("inst") - file.rename(file.path("libcuml", "lib"), file.path("inst", "libs")) + # lib/ may be a symlink to lib64/ (pip wheel) or the actual directory (legacy zip) + lib_dir <- if (dir.exists(file.path("libcuml", "lib64"))) "lib64" else "lib" + file.rename(file.path("libcuml", lib_dir), file.path("inst", "libs")) file.symlink(file.path("..", "inst", "libs"), file.path("libcuml", "lib")) - libs <- c("libtreelite", "libtreelite_runtime", "libcuml++") + if (lib_dir == "lib64") { + file.symlink(file.path("..", "inst", "libs"), file.path("libcuml", "lib64")) + } bundle_libcuml <- TRUE } cmake_prefix_path <- paste0( diff --git a/tools/config/libcuml_versions.R b/tools/config/libcuml_versions.R index f1bc2e2..8bd6b47 100644 --- a/tools/config/libcuml_versions.R +++ b/tools/config/libcuml_versions.R @@ -1,4 +1,8 @@ # A list containing libcuml download links for "cuml_versions" and CUDA major versions. +# +# For cuML 21.x: pre-built zip archives from mlverse/libcuml-builds GitHub releases. +# For cuML 26.x+: pip wheels from PyPI (libcuml-cu12). The wheel is a zip containing +# headers in libcuml/include/cuml/ and shared libs in libcuml/lib64/. libcuml_versions <- list( "21.08" = list( "11" = "https://github.com/mlverse/libcuml-builds/releases/download/v21.08-cuda11.2.1/libcuml-21.08-cuda11.2.1.zip" @@ -8,5 +12,8 @@ libcuml_versions <- list( ), "21.12" = list( "11" = "https://github.com/mlverse/libcuml-builds/releases/download/v21.12-cuda11.2.1/libcuml-21.12-cuda11.2.1.zip" + ), + "26.04" = list( + "12" = "https://files.pythonhosted.org/packages/84/dd/00031bd84a6cd42f028273ef0acab780d6bb5981a024c11fd1bcd66fdec0/libcuml_cu12-26.4.0-py3-none-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl" ) ) diff --git a/tools/config/utils/cuml.R b/tools/config/utils/cuml.R index bdb582e..8bef320 100644 --- a/tools/config/utils/cuml.R +++ b/tools/config/utils/cuml.R @@ -86,10 +86,22 @@ download_libcuml <- function(cuml_version = Sys.getenv("CUML_VERSION", unset = " url <- libcuml_versions[[cuml_version]][[cuda_version]] } + is_pip_wheel <- grepl("\\.whl$", url) + download.file(url, tmp) unzip(tmp, exdir = ".") - zip_file_name <- basename(url) - dir_name <- gsub("\\.zip$", "", zip_file_name) - file.rename(file.path(".", dir_name), file.path(".", "libcuml")) + if (is_pip_wheel) { + # pip wheels extract to libcuml/ with include/ and lib64/ subdirs. + # Normalize: create a lib/ symlink pointing to lib64/ so the rest of the + # build system can find libs at libcuml/lib/libcuml++.so. + if (dir.exists(file.path("libcuml", "lib64")) && !dir.exists(file.path("libcuml", "lib"))) { + file.symlink("lib64", file.path("libcuml", "lib")) + } + } else { + # Legacy zip archives: extract to a versioned directory name, rename to libcuml/ + zip_file_name <- basename(url) + dir_name <- gsub("\\.zip$", "", zip_file_name) + file.rename(file.path(".", dir_name), file.path(".", "libcuml")) + } } From 135d18ab71fce4f45da748953aab1c5bc13ac364 Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Wed, 29 Apr 2026 09:18:53 -0300 Subject: [PATCH 17/28] Test both cuML 21.12 and 26.04 in CI - Dockerfile: accept CUDA_IMAGE as build arg for different base images - Workflow: matrix over cuML 21.12 (CUDA 11.2) and 26.04 (CUDA 12.8) - Each version gets its own build-image and test-gpu job --- .github/docker/Dockerfile | 3 ++- .github/workflows/R-CMD-check.yaml | 42 ++++++++++++++++++++++++++---- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/.github/docker/Dockerfile b/.github/docker/Dockerfile index a414e7a..fad4978 100644 --- a/.github/docker/Dockerfile +++ b/.github/docker/Dockerfile @@ -1,4 +1,5 @@ -FROM nvidia/cuda:12.8.1-devel-ubuntu22.04 +ARG CUDA_IMAGE=nvidia/cuda:12.8.1-devel-ubuntu22.04 +FROM ${CUDA_IMAGE} ENV DEBIAN_FRONTEND=noninteractive diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index e78c89c..9726bc3 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -43,13 +43,24 @@ jobs: _R_CHECK_CRAN_INCOMING_: false build-image: + strategy: + fail-fast: false + matrix: + include: + - cuml: '21.12' + cuda_image: 'nvidia/cuda:11.2.2-devel-ubuntu20.04' + - cuml: '26.04' + cuda_image: 'nvidia/cuda:12.8.1-devel-ubuntu22.04' + runs-on: ubuntu-latest + name: 'Build (cuML ${{ matrix.cuml }})' permissions: contents: read packages: write timeout-minutes: 120 outputs: - image: ghcr.io/${{ github.repository }}-ci:${{ github.sha }} + image-21: ${{ steps.meta.outputs.image-21 }} + image-26: ${{ steps.meta.outputs.image-26 }} steps: - uses: actions/checkout@v4 @@ -69,20 +80,41 @@ jobs: context: . file: .github/docker/Dockerfile push: true - tags: ghcr.io/${{ github.repository }}-ci:${{ github.sha }} + tags: ghcr.io/${{ github.repository }}-ci:cuml${{ matrix.cuml }}-${{ github.sha }} build-args: | - CUML_VERSION=26.04 + CUDA_IMAGE=${{ matrix.cuda_image }} + CUML_VERSION=${{ matrix.cuml }} CMAKE_CUDA_ARCHITECTURES=75 + - name: Export image tag + id: meta + run: | + TAG="ghcr.io/${{ github.repository }}-ci:cuml${{ matrix.cuml }}-${{ github.sha }}" + if [[ "${{ matrix.cuml }}" == "21.12" ]]; then + echo "image-21=${TAG}" >> "$GITHUB_OUTPUT" + else + echo "image-26=${TAG}" >> "$GITHUB_OUTPUT" + fi + test-gpu: needs: build-image if: ${{ always() && needs.build-image.result == 'success' }} + strategy: + fail-fast: false + matrix: + include: + - cuml: '21.12' + image: ghcr.io/${{ github.repository }}-ci:cuml21.12-${{ github.sha }} + - cuml: '26.04' + image: ghcr.io/${{ github.repository }}-ci:cuml26.04-${{ github.sha }} + concurrency: - group: gpu-tests + group: gpu-tests-cuml${{ matrix.cuml }} runs-on: - "runs-on=${{ github.run_id }}/family=g4dn.xlarge/image=ubuntu24-gpu-x64/spot=true" + name: 'Test GPU (cuML ${{ matrix.cuml }})' container: - image: ${{ needs.build-image.outputs.image }} + image: ${{ matrix.image }} credentials: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} From e618aa37c3b6165b867847aa97dde8a83de1e2a0 Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Wed, 29 Apr 2026 09:27:07 -0300 Subject: [PATCH 18/28] Fix rapids-cmake version and lib symlink for dual cuML support - CMakeLists.txt.in: template RAPIDS_CMAKE_TAG and CMAKE_CXX_STANDARD so they adapt to the cuML version being built against - configure.R: set rapids-cmake tag (v26.04.00 for 26.x, branch-21.10 for 21.x) and C++ standard (17 for 26.x, 14 for 21.x) - cuml.R: don't create premature lib symlink in download_libcuml() --- src/CMakeLists.txt.in | 4 ++-- tools/config/configure.R | 21 ++++++++++++++++++--- tools/config/utils/cuml.R | 6 +----- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/CMakeLists.txt.in b/src/CMakeLists.txt.in index d9d5890..24ac74c 100644 --- a/src/CMakeLists.txt.in +++ b/src/CMakeLists.txt.in @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.8 FATAL_ERROR) -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD @CMAKE_CXX_STANDARD@) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") @@ -10,7 +10,7 @@ include(FetchContent) FetchContent_Declare( rapids-cmake GIT_REPOSITORY https://github.com/rapidsai/rapids-cmake.git - GIT_TAG origin/branch-26.04 + GIT_TAG @RAPIDS_CMAKE_TAG@ ) FetchContent_MakeAvailable(rapids-cmake) include(rapids-cuda) diff --git a/tools/config/configure.R b/tools/config/configure.R index 58118a1..ad584f5 100644 --- a/tools/config/configure.R +++ b/tools/config/configure.R @@ -67,8 +67,21 @@ run_cmake <- function() { on.exit(setwd(wd)) setwd(pkg_root()) + cuml_version <- Sys.getenv("CUML_VERSION", unset = "21.08") + rapids_cmake_tag <- if (grepl("^2[6-9]\\.|^[3-9]", cuml_version)) { + # cuML 26.04+ uses tagged rapids-cmake releases (e.g. v26.04.00) + paste0("v", cuml_version, ".00") + } else { + # cuML 21.x uses the 21.10 branch of rapids-cmake + "origin/branch-21.10" + } + + cxx_standard <- if (grepl("^2[6-9]\\.|^[3-9]", cuml_version)) "17" else "14" + define(R_INCLUDE_DIR = R.home("include")) define(RCPP_INCLUDE_DIR = system.file("include", package = "Rcpp")) + define(RAPIDS_CMAKE_TAG = rapids_cmake_tag) + define(CMAKE_CXX_STANDARD = cxx_standard) configure_file(file.path("src", "CMakeLists.txt.in")) cuml_prefix <- get_cuml_prefix() @@ -77,11 +90,13 @@ run_cmake <- function() { download_libcuml() cuml_prefix <- normalizePath(file.path(pkg_root(), "libcuml")) dir.create("inst") - # lib/ may be a symlink to lib64/ (pip wheel) or the actual directory (legacy zip) - lib_dir <- if (dir.exists(file.path("libcuml", "lib64"))) "lib64" else "lib" + # pip wheels have lib64/, legacy zips have lib/ + has_lib64 <- dir.exists(file.path("libcuml", "lib64")) + lib_dir <- if (has_lib64) "lib64" else "lib" file.rename(file.path("libcuml", lib_dir), file.path("inst", "libs")) + # Create symlinks so cmake can find libs at both libcuml/lib/ and libcuml/lib64/ file.symlink(file.path("..", "inst", "libs"), file.path("libcuml", "lib")) - if (lib_dir == "lib64") { + if (has_lib64) { file.symlink(file.path("..", "inst", "libs"), file.path("libcuml", "lib64")) } bundle_libcuml <- TRUE diff --git a/tools/config/utils/cuml.R b/tools/config/utils/cuml.R index 8bef320..dc9e904 100644 --- a/tools/config/utils/cuml.R +++ b/tools/config/utils/cuml.R @@ -93,11 +93,7 @@ download_libcuml <- function(cuml_version = Sys.getenv("CUML_VERSION", unset = " if (is_pip_wheel) { # pip wheels extract to libcuml/ with include/ and lib64/ subdirs. - # Normalize: create a lib/ symlink pointing to lib64/ so the rest of the - # build system can find libs at libcuml/lib/libcuml++.so. - if (dir.exists(file.path("libcuml", "lib64")) && !dir.exists(file.path("libcuml", "lib"))) { - file.symlink("lib64", file.path("libcuml", "lib")) - } + # configure.R handles moving libs to inst/libs and creating symlinks. } else { # Legacy zip archives: extract to a versioned directory name, rename to libcuml/ zip_file_name <- basename(url) From c8bd6edb82824cd7af154858c3c0cfd378a5d700 Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Wed, 29 Apr 2026 09:32:40 -0300 Subject: [PATCH 19/28] Derive rapids-cmake tag from cuML version instead of hardcoding Use vYY.MM.00 for cuML >= 23.02 (stable tags), vYY.MM.00a for older versions (only alpha tags available). --- tools/config/configure.R | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/config/configure.R b/tools/config/configure.R index ad584f5..7ae8cec 100644 --- a/tools/config/configure.R +++ b/tools/config/configure.R @@ -68,12 +68,12 @@ run_cmake <- function() { setwd(pkg_root()) cuml_version <- Sys.getenv("CUML_VERSION", unset = "21.08") - rapids_cmake_tag <- if (grepl("^2[6-9]\\.|^[3-9]", cuml_version)) { - # cuML 26.04+ uses tagged rapids-cmake releases (e.g. v26.04.00) + # rapids-cmake tags: v21.x had only alpha tags (v21.08.00a), + # v23.02+ has stable tags (v23.02.00) + rapids_cmake_tag <- if (package_version(cuml_version) >= "23.02") { paste0("v", cuml_version, ".00") } else { - # cuML 21.x uses the 21.10 branch of rapids-cmake - "origin/branch-21.10" + paste0("v", cuml_version, ".00a") } cxx_standard <- if (grepl("^2[6-9]\\.|^[3-9]", cuml_version)) "17" else "14" From 1591b2ba347570237bfb343c98a2f5f6897ac678 Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Wed, 29 Apr 2026 09:33:29 -0300 Subject: [PATCH 20/28] Require cmake 3.30.4+ for cuML 26.04 (auto-downloaded if missing) rapids-cmake v26.04 needs cmake >= 3.30.4. The existing auto-download logic handles this, but the min version threshold was hardcoded to 3.21.1. Now it's 3.30.4 for cuML >= 23.02, 3.21.1 for older versions. --- tools/config/utils/cmake.R | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tools/config/utils/cmake.R b/tools/config/utils/cmake.R index 664b987..0e5e9bf 100644 --- a/tools/config/utils/cmake.R +++ b/tools/config/utils/cmake.R @@ -6,7 +6,13 @@ # and # https://github.com/mlverse/cuda.ml/blob/7bad914c729011bcf05edc1c873609c518d9a77d/src/CMakeLists.txt.in#L13 # where cuda.ml specifies which branch of the rapids-cmake repo to use) -cuda_ml_min_cmake_version <- numeric_version("3.21.1") +cuda_ml_min_cmake_version <- if ( + package_version(Sys.getenv("CUML_VERSION", unset = "21.08")) >= "23.02" +) { + numeric_version("3.30.4") +} else { + numeric_version("3.21.1") +} has_cmake <- function() { rc <- system2("which", "cmake", stdout = NULL, stderr = NULL) From fd4fb4e28f7f24dc0538651563f9f720bab7f66e Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Wed, 29 Apr 2026 09:42:00 -0300 Subject: [PATCH 21/28] Fix cuML 26.04 build: raft/rmm deps, static_assert, device_allocator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Download libraft-cu12 and librmm-cu12 wheels alongside libcuml-cu12 (cuml headers include raft/rmm headers which are in separate packages) - Merge raft/rmm headers into libcuml/include/ during download - Remove static_assert(CUML_VERSION_MAJOR == 21) — allow 26+ - Guard raft::mr::device::allocator (removed in raft 26.x) with version conditionals in device_allocator.cu/.h and stream_allocator.cu - Use raft/core/handle.hpp instead of raft/handle.hpp for v26+ --- src/cuml_utils.cpp | 4 +-- src/device_allocator.cu | 6 ++++ src/device_allocator.h | 6 ++++ src/handle_utils.h | 5 ++++ src/stream_allocator.cu | 7 +++-- tools/config/libcuml_versions.R | 6 +++- tools/config/utils/cuml.R | 50 ++++++++++++++++++++++----------- 7 files changed, 63 insertions(+), 21 deletions(-) diff --git a/src/cuml_utils.cpp b/src/cuml_utils.cpp index 4f07355..f784674 100644 --- a/src/cuml_utils.cpp +++ b/src/cuml_utils.cpp @@ -4,8 +4,8 @@ #include -static_assert(CUML_VERSION_MAJOR == 21, - "{cuda.ml} currently only supports linking to RAPIDS cuML 21.x!"); +static_assert(CUML_VERSION_MAJOR == 21 || CUML_VERSION_MAJOR >= 26, + "{cuda.ml} supports linking to RAPIDS cuML 21.x or 26.x+"); #endif diff --git a/src/device_allocator.cu b/src/device_allocator.cu index fe13909..ec39b33 100644 --- a/src/device_allocator.cu +++ b/src/device_allocator.cu @@ -2,6 +2,10 @@ #include "device_allocator.h" +#include + +#if CUML_VERSION_MAJOR < 26 + #include namespace { @@ -19,6 +23,8 @@ __host__ std::shared_ptr getDeviceAllocator() { } // namespace cuml4r +#endif // CUML_VERSION_MAJOR < 26 + #else #include "warn_cuml_missing.h" diff --git a/src/device_allocator.h b/src/device_allocator.h index 124c3b1..72cf077 100644 --- a/src/device_allocator.h +++ b/src/device_allocator.h @@ -2,6 +2,10 @@ #ifdef HAS_CUML +#include + +#if CUML_VERSION_MAJOR < 26 + #include namespace raft { @@ -20,6 +24,8 @@ std::shared_ptr getDeviceAllocator(); } // namespace cuml4r +#endif // CUML_VERSION_MAJOR < 26 + #else #include "warn_cuml_missing.h" diff --git a/src/handle_utils.h b/src/handle_utils.h index f00d622..4a3c30d 100644 --- a/src/handle_utils.h +++ b/src/handle_utils.h @@ -2,7 +2,12 @@ #ifdef HAS_CUML +#include +#if CUML_VERSION_MAJOR >= 26 +#include +#else #include +#endif #include namespace cuml4r { diff --git a/src/stream_allocator.cu b/src/stream_allocator.cu index acc79fe..5ece327 100644 --- a/src/stream_allocator.cu +++ b/src/stream_allocator.cu @@ -1,9 +1,13 @@ #ifdef HAS_CUML #include "cuda_utils.h" -#include "device_allocator.h" #include "stream_allocator.h" +#include +#if CUML_VERSION_MAJOR < 26 +#include "device_allocator.h" +#endif + #include #include @@ -42,7 +46,6 @@ __host__ rmm::cuda_stream_view getOrCreateStream() { if (it != cuda_streams_map.end()) { return it->second.value(); } - auto const device_allocator = getDeviceAllocator(); auto stream = rmm::cuda_stream(); auto stream_view = stream.view(); cudaStreamsMap().emplace(dev_id, std::move(stream)); diff --git a/tools/config/libcuml_versions.R b/tools/config/libcuml_versions.R index 8bd6b47..3a9729b 100644 --- a/tools/config/libcuml_versions.R +++ b/tools/config/libcuml_versions.R @@ -14,6 +14,10 @@ libcuml_versions <- list( "11" = "https://github.com/mlverse/libcuml-builds/releases/download/v21.12-cuda11.2.1/libcuml-21.12-cuda11.2.1.zip" ), "26.04" = list( - "12" = "https://files.pythonhosted.org/packages/84/dd/00031bd84a6cd42f028273ef0acab780d6bb5981a024c11fd1bcd66fdec0/libcuml_cu12-26.4.0-py3-none-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl" + "12" = list( + cuml = "https://files.pythonhosted.org/packages/84/dd/00031bd84a6cd42f028273ef0acab780d6bb5981a024c11fd1bcd66fdec0/libcuml_cu12-26.4.0-py3-none-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", + raft = "https://files.pythonhosted.org/packages/92/72/a05d2122f1279ce8bc4bb652bc13089b2ee701a64bd1a483537a54639f8c/libraft_cu12-26.4.0-py3-none-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", + rmm = "https://files.pythonhosted.org/packages/6d/82/783151d344aece612484041c92a94fa4261653f28a45375a6fc8a6100995/librmm_cu12-26.4.0-py3-none-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl" + ) ) ) diff --git a/tools/config/utils/cuml.R b/tools/config/utils/cuml.R index dc9e904..4ac3c7d 100644 --- a/tools/config/utils/cuml.R +++ b/tools/config/utils/cuml.R @@ -78,26 +78,44 @@ download_libcuml <- function(cuml_version = Sys.getenv("CUML_VERSION", unset = " options(timeout = 1000) on.exit(options(timeout = old_timeout), add = TRUE) - tmp <- tempfile(fileext = ".zip") cuda_version <- as.character(find_nvcc()$version$major) - url <- Sys.getenv("CUML_URL") - if (!nzchar(url)) { - url <- libcuml_versions[[cuml_version]][[cuda_version]] + url_or_urls <- Sys.getenv("CUML_URL") + if (!nzchar(url_or_urls)) { + url_or_urls <- libcuml_versions[[cuml_version]][[cuda_version]] } - is_pip_wheel <- grepl("\\.whl$", url) - - download.file(url, tmp) - unzip(tmp, exdir = ".") - - if (is_pip_wheel) { - # pip wheels extract to libcuml/ with include/ and lib64/ subdirs. - # configure.R handles moving libs to inst/libs and creating symlinks. + if (is.list(url_or_urls)) { + # New format: list of pip wheels (cuml + raft + rmm dependencies). + # Download and extract all, then merge headers into libcuml/include/. + for (name in names(url_or_urls)) { + url <- url_or_urls[[name]] + tmp <- tempfile(fileext = ".whl") + download.file(url, tmp) + unzip(tmp, exdir = ".") + } + # Copy raft and rmm headers into libcuml/include/ so cmake finds them + for (dep in c("libraft", "librmm")) { + dep_include <- file.path(dep, "include") + if (dir.exists(dep_include)) { + file.copy( + list.dirs(dep_include, full.names = TRUE, recursive = FALSE), + file.path("libcuml", "include"), + recursive = TRUE + ) + } + } } else { - # Legacy zip archives: extract to a versioned directory name, rename to libcuml/ - zip_file_name <- basename(url) - dir_name <- gsub("\\.zip$", "", zip_file_name) - file.rename(file.path(".", dir_name), file.path(".", "libcuml")) + # Single URL: either a pip wheel (.whl) or legacy zip archive + tmp <- tempfile(fileext = ".zip") + download.file(url_or_urls, tmp) + unzip(tmp, exdir = ".") + + if (!grepl("\\.whl$", url_or_urls)) { + # Legacy zip archives: extract to a versioned directory name, rename to libcuml/ + zip_file_name <- basename(url_or_urls) + dir_name <- gsub("\\.zip$", "", zip_file_name) + file.rename(file.path(".", dir_name), file.path(".", "libcuml")) + } } } From 145b2b9c3ac1707dca41c607684ca06025d5430e Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Wed, 29 Apr 2026 09:57:43 -0300 Subject: [PATCH 22/28] Resolve cuML PyPI deps dynamically instead of hardcoding URLs - Add tools/config/utils/pypi.R with resolve_native_deps() that walks the PyPI dependency tree for a package and returns download URLs for all native C++ dependencies (libraft, librmm, rapids-logger, cccl, etc.) - libcuml_versions.R: cuML 26.04 entry is now just "libcuml-cu12" (the PyPI package name), not a hardcoded URL - cuml.R: download_libcuml() detects PyPI package names vs direct URLs, resolves the full dep tree, downloads all wheels, and merges their include/ directories into libcuml/include/ - configure.R: load pypi.R utility - Uses jsonlite for PyPI JSON API parsing --- tools/config/configure.R | 2 +- tools/config/libcuml_versions.R | 6 +-- tools/config/utils/cuml.R | 47 ++++++++++++-------- tools/config/utils/pypi.R | 79 +++++++++++++++++++++++++++++++++ 4 files changed, 110 insertions(+), 24 deletions(-) create mode 100644 tools/config/utils/pypi.R diff --git a/tools/config/configure.R b/tools/config/configure.R index 7ae8cec..06ab0a4 100644 --- a/tools/config/configure.R +++ b/tools/config/configure.R @@ -54,7 +54,7 @@ load_libcuml_versions <- function() { load_util_fns <- function() { wd <- file.path(pkg_root(), "tools", "config", "utils") - for (f in c("cuml.R", "cmake.R", "logging.R", "nvcc.R", "platform.R")) { + for (f in c("cuml.R", "cmake.R", "logging.R", "nvcc.R", "platform.R", "pypi.R")) { source(file.path(wd, f)) } } diff --git a/tools/config/libcuml_versions.R b/tools/config/libcuml_versions.R index 3a9729b..09f4638 100644 --- a/tools/config/libcuml_versions.R +++ b/tools/config/libcuml_versions.R @@ -14,10 +14,6 @@ libcuml_versions <- list( "11" = "https://github.com/mlverse/libcuml-builds/releases/download/v21.12-cuda11.2.1/libcuml-21.12-cuda11.2.1.zip" ), "26.04" = list( - "12" = list( - cuml = "https://files.pythonhosted.org/packages/84/dd/00031bd84a6cd42f028273ef0acab780d6bb5981a024c11fd1bcd66fdec0/libcuml_cu12-26.4.0-py3-none-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", - raft = "https://files.pythonhosted.org/packages/92/72/a05d2122f1279ce8bc4bb652bc13089b2ee701a64bd1a483537a54639f8c/libraft_cu12-26.4.0-py3-none-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", - rmm = "https://files.pythonhosted.org/packages/6d/82/783151d344aece612484041c92a94fa4261653f28a45375a6fc8a6100995/librmm_cu12-26.4.0-py3-none-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl" - ) + "12" = "libcuml-cu12" ) ) diff --git a/tools/config/utils/cuml.R b/tools/config/utils/cuml.R index 4ac3c7d..7e089d7 100644 --- a/tools/config/utils/cuml.R +++ b/tools/config/utils/cuml.R @@ -80,24 +80,35 @@ download_libcuml <- function(cuml_version = Sys.getenv("CUML_VERSION", unset = " cuda_version <- as.character(find_nvcc()$version$major) - url_or_urls <- Sys.getenv("CUML_URL") - if (!nzchar(url_or_urls)) { - url_or_urls <- libcuml_versions[[cuml_version]][[cuda_version]] + url_entry <- Sys.getenv("CUML_URL") + if (!nzchar(url_entry)) { + url_entry <- libcuml_versions[[cuml_version]][[cuda_version]] } - if (is.list(url_or_urls)) { - # New format: list of pip wheels (cuml + raft + rmm dependencies). - # Download and extract all, then merge headers into libcuml/include/. - for (name in names(url_or_urls)) { - url <- url_or_urls[[name]] + is_pypi_package <- !grepl("^https?://", url_entry) + + if (is_pypi_package) { + # Resolve and download the full dependency tree from PyPI. + # This downloads libcuml-cu12 and all its native header dependencies + # (libraft, librmm, rapids-logger, nvidia-cccl, etc.) as wheels, extracts + # them, and merges all headers into libcuml/include/. + message("Resolving PyPI dependencies for ", url_entry, "...") + urls <- resolve_native_deps(url_entry) + message("Downloading ", length(urls), " packages: ", paste(names(urls), collapse = ", ")) + + for (pkg_name in names(urls)) { + url <- urls[[pkg_name]] tmp <- tempfile(fileext = ".whl") - download.file(url, tmp) - unzip(tmp, exdir = ".") + message(" Downloading ", pkg_name, "...") + download.file(url, tmp, quiet = TRUE) + unzip(tmp, exdir = ".", overwrite = TRUE) } - # Copy raft and rmm headers into libcuml/include/ so cmake finds them - for (dep in c("libraft", "librmm")) { - dep_include <- file.path(dep, "include") - if (dir.exists(dep_include)) { + + # Merge all include/ directories into libcuml/include/ + # Each wheel extracts to a directory like libraft/, librmm/, etc. + for (d in list.dirs(".", full.names = TRUE, recursive = FALSE)) { + dep_include <- file.path(d, "include") + if (d != "./libcuml" && dir.exists(dep_include)) { file.copy( list.dirs(dep_include, full.names = TRUE, recursive = FALSE), file.path("libcuml", "include"), @@ -106,14 +117,14 @@ download_libcuml <- function(cuml_version = Sys.getenv("CUML_VERSION", unset = " } } } else { - # Single URL: either a pip wheel (.whl) or legacy zip archive + # Direct URL: either a pip wheel (.whl) or legacy zip archive tmp <- tempfile(fileext = ".zip") - download.file(url_or_urls, tmp) + download.file(url_entry, tmp) unzip(tmp, exdir = ".") - if (!grepl("\\.whl$", url_or_urls)) { + if (!grepl("\\.whl$", url_entry)) { # Legacy zip archives: extract to a versioned directory name, rename to libcuml/ - zip_file_name <- basename(url_or_urls) + zip_file_name <- basename(url_entry) dir_name <- gsub("\\.zip$", "", zip_file_name) file.rename(file.path(".", dir_name), file.path(".", "libcuml")) } diff --git a/tools/config/utils/pypi.R b/tools/config/utils/pypi.R new file mode 100644 index 0000000..bf63cad --- /dev/null +++ b/tools/config/utils/pypi.R @@ -0,0 +1,79 @@ +# Resolve the full dependency tree for a PyPI package and return download URLs +# for all packages (including transitive deps) that contain C++ headers. +# +# This is used to download libcuml and all its header-only dependencies +# (libraft, librmm, rapids-logger, nvidia-cccl, etc.) from PyPI without +# needing pip installed. + +pypi_package_info <- function(package, version = NULL) { + url <- if (is.null(version)) { + sprintf("https://pypi.org/pypi/%s/json", package) + } else { + sprintf("https://pypi.org/pypi/%s/%s/json", package, version) + } + tmp <- tempfile(fileext = ".json") + download.file(url, tmp, quiet = TRUE) + jsonlite::fromJSON(tmp) +} + +pypi_wheel_url <- function(package, version = NULL, platform = "x86_64") { + info <- pypi_package_info(package, version) + urls <- info$urls + # Find a matching wheel for the platform + idx <- grep(platform, urls$filename) + if (length(idx) == 0) { + # Try platform-independent wheels + idx <- grep("none-any", urls$filename) + } + if (length(idx) == 0) { + stop(sprintf("No wheel found for %s (platform: %s)", package, platform)) + } + list( + url = urls$url[idx[1]], + filename = urls$filename[idx[1]], + version = info$info$version, + requires_dist = info$info$requires_dist + ) +} + +# Parse a PEP 508 dependency string into package name +# e.g. "libraft-cu12==26.4.*" -> "libraft-cu12" +# e.g. "numpy>=1.0; extra == 'test'" -> "numpy" (but we skip extras) +parse_dep_name <- function(dep_str) { + # Skip deps with extras/markers like "; extra == ..." + if (grepl("; extra\\s*==", dep_str)) return(NULL) + # Extract package name (everything before version specifier or semicolon) + gsub("[\\s;(<>=!\\[].*", "", dep_str, perl = TRUE) +} + +# Resolve all transitive dependencies that look like C++ library packages +# (lib*, rapids-*, nvidia-cccl-*, nvidia-nvjitlink-*) +resolve_native_deps <- function(package, version = NULL, seen = character()) { + if (package %in% seen) return(list()) + seen <- c(seen, package) + + info <- tryCatch( + pypi_wheel_url(package, version), + error = function(e) NULL + ) + if (is.null(info)) return(list()) + + result <- list() + result[[package]] <- info$url + + # Only chase transitive deps for native/C++ packages + if (!is.null(info$requires_dist)) { + for (dep in info$requires_dist) { + dep_name <- parse_dep_name(dep) + if (is.null(dep_name)) next + # Only follow native library deps (lib*, rapids-*, nvidia-cccl*, nvidia-nvjitlink*) + if (grepl("^(lib|rapids-|nvidia-cccl|nvidia-nvjitlink)", dep_name)) { + sub_deps <- resolve_native_deps(dep_name, seen = seen) + seen <- c(seen, names(sub_deps)) + result <- c(result, sub_deps) + } + } + } + + result +} From 45e3dd280e1fa33a9c3bfc8ad5836cb54d62b2fd Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Wed, 29 Apr 2026 10:10:26 -0300 Subject: [PATCH 23/28] Download CCCL 3.3 headers for cuML 26.04 builds RMM 26.04 headers require CCCL >= 3.3 at compile time, but CCCL is not a pip dependency (it's normally bundled with the CUDA toolkit). CUDA 12.x ships CCCL 2.x which is too old. Download CCCL v3.3.0 from GitHub releases (header-only, ~2MB) and merge into libcuml/include/. Also handle pip wheels that extract to nested dirs like nvidia//include/. --- tools/config/utils/cuml.R | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/tools/config/utils/cuml.R b/tools/config/utils/cuml.R index 7e089d7..a9f8bd3 100644 --- a/tools/config/utils/cuml.R +++ b/tools/config/utils/cuml.R @@ -104,11 +104,25 @@ download_libcuml <- function(cuml_version = Sys.getenv("CUML_VERSION", unset = " unzip(tmp, exdir = ".", overwrite = TRUE) } - # Merge all include/ directories into libcuml/include/ - # Each wheel extracts to a directory like libraft/, librmm/, etc. - for (d in list.dirs(".", full.names = TRUE, recursive = FALSE)) { - dep_include <- file.path(d, "include") - if (d != "./libcuml" && dir.exists(dep_include)) { + # Download CCCL headers from GitHub (required by rmm/raft at compile time, + # but not a pip dependency). CCCL is header-only. + cccl_version <- "3.3.0" + cccl_url <- sprintf( + "https://github.com/NVIDIA/cccl/releases/download/v%s/cccl-v%s.zip", + cccl_version, cccl_version + ) + cccl_tmp <- tempfile(fileext = ".zip") + message(" Downloading CCCL v", cccl_version, " headers...") + download.file(cccl_url, cccl_tmp, quiet = TRUE) + unzip(cccl_tmp, exdir = ".", overwrite = TRUE) + + # Merge all include/ directories into libcuml/include/. + # Sources: pip wheels (libraft/, librmm/, nvidia/, rapids_logger/, etc.) + # and CCCL (cccl-v3.3.0/). + # Pip wheels may extract to nested dirs like nvidia/cuda_cccl/include/. + merge_include_dirs <- function(src_dir) { + dep_include <- file.path(src_dir, "include") + if (dir.exists(dep_include)) { file.copy( list.dirs(dep_include, full.names = TRUE, recursive = FALSE), file.path("libcuml", "include"), @@ -116,6 +130,14 @@ download_libcuml <- function(cuml_version = Sys.getenv("CUML_VERSION", unset = " ) } } + for (d in list.dirs(".", full.names = TRUE, recursive = FALSE)) { + if (d == "./libcuml") next + merge_include_dirs(d) + # Some pip wheels nest under nvidia//include/ + for (sub in list.dirs(d, full.names = TRUE, recursive = FALSE)) { + merge_include_dirs(sub) + } + } } else { # Direct URL: either a pip wheel (.whl) or legacy zip archive tmp <- tempfile(fileext = ".zip") From bb27ca1b1cd370fad7fd1f4b01eb991ab82051ae Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Wed, 29 Apr 2026 10:16:46 -0300 Subject: [PATCH 24/28] Put CUML_INCLUDE_DIR before CUDA toolkit includes CCCL 3.3 headers (bundled in libcuml/include/) must take precedence over the CUDA 12 toolkit's older CCCL 2.x headers. Swap include order so cuml/raft/rmm/cccl headers are found first. --- src/CMakeLists.txt.in | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/CMakeLists.txt.in b/src/CMakeLists.txt.in index 24ac74c..56551d3 100644 --- a/src/CMakeLists.txt.in +++ b/src/CMakeLists.txt.in @@ -26,9 +26,10 @@ if(DEFINED ENV{CUML4R_ENABLE_CCACHE}) endif(DEFINED ENV{CUML4R_ENABLE_CCACHE}) if(DEFINED CUML_INCLUDE_DIR) - # CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES is needed so that cuda_runtime.h is found - # CUML_INCLUDE_DIR is needed so that kmeans/kmeans_c.h is found - set(CUML4R_INCLUDE_DIRS ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES} ${CUML_INCLUDE_DIR}) + # CUML_INCLUDE_DIR first so cuML's bundled headers (CCCL 3.3, raft, rmm) + # take precedence over the CUDA toolkit's older versions. + # CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES is still needed for cuda_runtime.h. + set(CUML4R_INCLUDE_DIRS ${CUML_INCLUDE_DIR} ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES}) else() message(FATAL_ERROR "CUML_INCLUDE_DIR not specified.") endif(DEFINED CUML_INCLUDE_DIR) From 189371f74e6ce84a3759e6c85264c7febe85e890 Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Wed, 29 Apr 2026 10:27:03 -0300 Subject: [PATCH 25/28] Fix CCCL compat, pinned_allocator removal, and raft handle API - Use RAPIDS-pinned CCCL commit (CUDA 12 compatible) instead of v3.3.0 release tag which includes CUDA 13-only code - pinned_host_vector.h: guard thrust::cuda::experimental::pinned_allocator (removed in CCCL 3.x); use plain host_vector on v26+ - handle_utils.cu: raft::handle_t no longer has set_stream(); reconstruct with stream_view via constructor on v26+ --- src/handle_utils.cu | 9 +++++++++ src/pinned_host_vector.h | 13 +++++++++++++ tools/config/utils/cuml.R | 22 ++++++++++++++-------- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/handle_utils.cu b/src/handle_utils.cu index 9c61b7b..1a1e159 100644 --- a/src/handle_utils.cu +++ b/src/handle_utils.cu @@ -3,6 +3,8 @@ #ifdef HAS_CUML +#include + namespace cuml4r { namespace handle_utils { @@ -11,7 +13,14 @@ __host__ void initializeHandle(raft::handle_t& handle, if (stream_view.value() == 0) { stream_view = stream_allocator::getOrCreateStream(); } +#if CUML_VERSION_MAJOR >= 26 + // In raft 26.x, handle_t takes stream_view in the constructor. + // Reconstruct the handle with the desired stream via placement new. + handle.~handle_t(); + new (&handle) raft::handle_t(stream_view); +#else handle.set_stream(stream_view.value()); +#endif } } // namespace handle_utils diff --git a/src/pinned_host_vector.h b/src/pinned_host_vector.h index a0d6359..b9edaca 100644 --- a/src/pinned_host_vector.h +++ b/src/pinned_host_vector.h @@ -2,16 +2,29 @@ #ifdef HAS_CUML +#include #include +#if CUML_VERSION_MAJOR >= 26 +#include +#include +#include +#else #include +#endif #include namespace cuml4r { +#if CUML_VERSION_MAJOR >= 26 +// CCCL 3.x removed pinned_allocator; use the new memory resource API +template +using pinned_host_vector = thrust::host_vector; +#else template using pinned_host_vector = thrust::host_vector>; +#endif } // namespace cuml4r diff --git a/tools/config/utils/cuml.R b/tools/config/utils/cuml.R index a9f8bd3..9e64080 100644 --- a/tools/config/utils/cuml.R +++ b/tools/config/utils/cuml.R @@ -106,20 +106,24 @@ download_libcuml <- function(cuml_version = Sys.getenv("CUML_VERSION", unset = " # Download CCCL headers from GitHub (required by rmm/raft at compile time, # but not a pip dependency). CCCL is header-only. - cccl_version <- "3.3.0" + # Uses the exact commit that RAPIDS 26.04 pins via rapids-cmake, which is + # CUDA 12 compatible (the release tag v3.3.0 includes CUDA 13-only code). + cccl_commit <- "09094af138841ef521de1adbbdd18ab8b3dad47b" cccl_url <- sprintf( - "https://github.com/NVIDIA/cccl/releases/download/v%s/cccl-v%s.zip", - cccl_version, cccl_version + "https://github.com/NVIDIA/cccl/archive/%s.tar.gz", cccl_commit ) - cccl_tmp <- tempfile(fileext = ".zip") - message(" Downloading CCCL v", cccl_version, " headers...") + cccl_tmp <- tempfile(fileext = ".tar.gz") + message(" Downloading CCCL headers (RAPIDS 26.04 pin)...") download.file(cccl_url, cccl_tmp, quiet = TRUE) - unzip(cccl_tmp, exdir = ".", overwrite = TRUE) + untar(cccl_tmp, exdir = ".") + # Rename extracted dir for predictability + cccl_dir <- file.path(".", paste0("cccl-", cccl_commit)) + file.rename(cccl_dir, file.path(".", "cccl")) # Merge all include/ directories into libcuml/include/. # Sources: pip wheels (libraft/, librmm/, nvidia/, rapids_logger/, etc.) - # and CCCL (cccl-v3.3.0/). - # Pip wheels may extract to nested dirs like nvidia/cuda_cccl/include/. + # and CCCL (cccl/). + # Pip wheels may extract to nested dirs like nvidia//include/. merge_include_dirs <- function(src_dir) { dep_include <- file.path(src_dir, "include") if (dir.exists(dep_include)) { @@ -138,6 +142,8 @@ download_libcuml <- function(cuml_version = Sys.getenv("CUML_VERSION", unset = " merge_include_dirs(sub) } } + # CCCL has include/ with cub/, cuda/, nv/, thrust/ + merge_include_dirs("./cccl") } else { # Direct URL: either a pip wheel (.whl) or legacy zip archive tmp <- tempfile(fileext = ".zip") From 59138d072c6068d4a89097832fe13cf9680ddb28 Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Wed, 29 Apr 2026 10:48:04 -0300 Subject: [PATCH 26/28] Switch to cuML 25.12 (no CCCL 3.x requirement) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cuML 26.04's rmm headers require CCCL >= 3.3 which conflicts with CUDA 12.x toolkit's CCCL 2.x. cuML 25.12 vendors its own CCCL in librmm/include/rapids/ and has no CCCL version check — clean CUDA 12 compatibility. - Target cuML 25.12 instead of 26.04 - Version guards: >= 26 -> >= 25 (same API changes apply) - Re-enable KNN (knn.hpp exists in 25.12 with same API) - Remove CCCL GitHub download (not needed) - Update PyPI resolver to handle version pins (==25.12.*) --- .github/workflows/R-CMD-check.yaml | 6 ++-- src/CMakeLists.txt.in | 2 +- src/cuml_utils.cpp | 4 +-- src/device_allocator.cu | 4 +-- src/device_allocator.h | 4 +-- src/fil.cu | 6 ++-- src/fil_utils.cu | 4 +-- src/fil_utils.h | 4 +-- src/handle_utils.cu | 2 +- src/handle_utils.h | 2 +- src/knn.cu | 58 ++---------------------------- src/pinned_host_vector.h | 4 +-- src/random_forest_classifier.cu | 12 +++---- src/random_forest_regressor.cu | 10 +++--- src/random_projection.cu | 6 ++-- src/stream_allocator.cu | 2 +- src/svm_serde.h | 4 +-- tools/config/libcuml_versions.R | 4 +-- tools/config/utils/cuml.R | 20 +---------- tools/config/utils/pypi.R | 38 ++++++++++++++------ 20 files changed, 70 insertions(+), 126 deletions(-) diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 9726bc3..8720f94 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -49,7 +49,7 @@ jobs: include: - cuml: '21.12' cuda_image: 'nvidia/cuda:11.2.2-devel-ubuntu20.04' - - cuml: '26.04' + - cuml: '25.12' cuda_image: 'nvidia/cuda:12.8.1-devel-ubuntu22.04' runs-on: ubuntu-latest @@ -105,8 +105,8 @@ jobs: include: - cuml: '21.12' image: ghcr.io/${{ github.repository }}-ci:cuml21.12-${{ github.sha }} - - cuml: '26.04' - image: ghcr.io/${{ github.repository }}-ci:cuml26.04-${{ github.sha }} + - cuml: '25.12' + image: ghcr.io/${{ github.repository }}-ci:cuml25.12-${{ github.sha }} concurrency: group: gpu-tests-cuml${{ matrix.cuml }} diff --git a/src/CMakeLists.txt.in b/src/CMakeLists.txt.in index 56551d3..6f96f3c 100644 --- a/src/CMakeLists.txt.in +++ b/src/CMakeLists.txt.in @@ -26,7 +26,7 @@ if(DEFINED ENV{CUML4R_ENABLE_CCACHE}) endif(DEFINED ENV{CUML4R_ENABLE_CCACHE}) if(DEFINED CUML_INCLUDE_DIR) - # CUML_INCLUDE_DIR first so cuML's bundled headers (CCCL 3.3, raft, rmm) + # CUML_INCLUDE_DIR first so cuML's bundled headers (CCCL, raft, rmm) # take precedence over the CUDA toolkit's older versions. # CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES is still needed for cuda_runtime.h. set(CUML4R_INCLUDE_DIRS ${CUML_INCLUDE_DIR} ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES}) diff --git a/src/cuml_utils.cpp b/src/cuml_utils.cpp index f784674..dfba358 100644 --- a/src/cuml_utils.cpp +++ b/src/cuml_utils.cpp @@ -4,8 +4,8 @@ #include -static_assert(CUML_VERSION_MAJOR == 21 || CUML_VERSION_MAJOR >= 26, - "{cuda.ml} supports linking to RAPIDS cuML 21.x or 26.x+"); +static_assert(CUML_VERSION_MAJOR == 21 || CUML_VERSION_MAJOR >= 25, + "{cuda.ml} supports linking to RAPIDS cuML 21.x or 25.x+"); #endif diff --git a/src/device_allocator.cu b/src/device_allocator.cu index ec39b33..682b8e6 100644 --- a/src/device_allocator.cu +++ b/src/device_allocator.cu @@ -4,7 +4,7 @@ #include -#if CUML_VERSION_MAJOR < 26 +#if CUML_VERSION_MAJOR < 25 #include @@ -23,7 +23,7 @@ __host__ std::shared_ptr getDeviceAllocator() { } // namespace cuml4r -#endif // CUML_VERSION_MAJOR < 26 +#endif // CUML_VERSION_MAJOR < 25 #else diff --git a/src/device_allocator.h b/src/device_allocator.h index 72cf077..f8f4e59 100644 --- a/src/device_allocator.h +++ b/src/device_allocator.h @@ -4,7 +4,7 @@ #include -#if CUML_VERSION_MAJOR < 26 +#if CUML_VERSION_MAJOR < 25 #include @@ -24,7 +24,7 @@ std::shared_ptr getDeviceAllocator(); } // namespace cuml4r -#endif // CUML_VERSION_MAJOR < 26 +#endif // CUML_VERSION_MAJOR < 25 #else diff --git a/src/fil.cu b/src/fil.cu index fdf893f..e3359c5 100644 --- a/src/fil.cu +++ b/src/fil.cu @@ -2,7 +2,7 @@ #include -#if CUML_VERSION_MAJOR < 26 +#if CUML_VERSION_MAJOR < 25 #include "async_utils.cuh" #include "cuda_utils.h" @@ -178,7 +178,7 @@ __host__ Rcpp::NumericMatrix fil_predict( } // namespace cuml4r -#else // CUML_VERSION_MAJOR >= 26 +#else // CUML_VERSION_MAJOR >= 25 #include @@ -209,4 +209,4 @@ __host__ Rcpp::NumericMatrix fil_predict( } // namespace cuml4r -#endif // CUML_VERSION_MAJOR < 26 +#endif // CUML_VERSION_MAJOR < 25 diff --git a/src/fil_utils.cu b/src/fil_utils.cu index da39c32..992069c 100644 --- a/src/fil_utils.cu +++ b/src/fil_utils.cu @@ -2,7 +2,7 @@ #include -#if CUML_VERSION_MAJOR < 26 +#if CUML_VERSION_MAJOR < 25 #include "fil_utils.h" @@ -26,4 +26,4 @@ __host__ forest_uptr make_forest(raft::handle_t const& handle, } // namespace fil } // namespace cuml4r -#endif // CUML_VERSION_MAJOR < 26 +#endif // CUML_VERSION_MAJOR < 25 diff --git a/src/fil_utils.h b/src/fil_utils.h index dca99c3..744f32f 100644 --- a/src/fil_utils.h +++ b/src/fil_utils.h @@ -4,7 +4,7 @@ #include -#if CUML_VERSION_MAJOR < 26 +#if CUML_VERSION_MAJOR < 25 #include @@ -33,4 +33,4 @@ forest_uptr make_forest(raft::handle_t const& handle, } // namespace fil } // namespace cuml4r -#endif // CUML_VERSION_MAJOR < 26 +#endif // CUML_VERSION_MAJOR < 25 diff --git a/src/handle_utils.cu b/src/handle_utils.cu index 1a1e159..92f50be 100644 --- a/src/handle_utils.cu +++ b/src/handle_utils.cu @@ -13,7 +13,7 @@ __host__ void initializeHandle(raft::handle_t& handle, if (stream_view.value() == 0) { stream_view = stream_allocator::getOrCreateStream(); } -#if CUML_VERSION_MAJOR >= 26 +#if CUML_VERSION_MAJOR >= 25 // In raft 26.x, handle_t takes stream_view in the constructor. // Reconstruct the handle with the desired stream via placement new. handle.~handle_t(); diff --git a/src/handle_utils.h b/src/handle_utils.h index 4a3c30d..260b1f7 100644 --- a/src/handle_utils.h +++ b/src/handle_utils.h @@ -3,7 +3,7 @@ #ifdef HAS_CUML #include -#if CUML_VERSION_MAJOR >= 26 +#if CUML_VERSION_MAJOR >= 25 #include #else #include diff --git a/src/knn.cu b/src/knn.cu index 797fa35..de7511d 100644 --- a/src/knn.cu +++ b/src/knn.cu @@ -1,9 +1,5 @@ #include "preprocessor.h" -#include - -#if CUML_VERSION_MAJOR < 26 - #include "async_utils.cuh" #include "cuda_utils.h" #include "handle_utils.h" @@ -25,9 +21,9 @@ #include #include -#if CUML_VERSION_MAJOR == 21 -#if CUML4R_CONCAT(0x, CUML_VERSION_MINOR) >= 0x08 +#include +// In cuML 21.08+ / 25.x, the KNN index types live in raft::spatial::knn #include using knnIndex = raft::spatial::knn::knnIndex; @@ -37,18 +33,6 @@ using IVFFlatParam = raft::spatial::knn::IVFFlatParam; using IVFPQParam = raft::spatial::knn::IVFPQParam; using IVFSQParam = raft::spatial::knn::IVFSQParam; -#else - -using knnIndex = ML::knnIndex; -using knnIndexParam = ML::knnIndexParam; -using QuantizerType = ML::QuantizerType; -using IVFFlatParam = ML::IVFFlatParam; -using IVFPQParam = ML::IVFPQParam; -using IVFSQParam = ML::IVFSQParam; - -#endif -#endif - namespace cuml4r { namespace knn { namespace { @@ -482,41 +466,3 @@ Rcpp::NumericVector knn_regressor_predict(Rcpp::List const& model, } } // namespace cuml4r - -#else // CUML_VERSION_MAJOR >= 26 - -#include - -namespace cuml4r { - -__host__ Rcpp::List knn_fit(Rcpp::NumericMatrix const& x, int const algo, - int const metric, float const p, - Rcpp::List const& algo_params) { - Rcpp::stop("KNN is not yet supported with cuML 26.04."); - return Rcpp::List(); -} - -__host__ Rcpp::IntegerVector knn_classifier_predict( - Rcpp::List const& model, Rcpp::NumericMatrix const& x, - int const n_neighbors) { - Rcpp::stop("KNN is not yet supported with cuML 26.04."); - return Rcpp::IntegerVector(); -} - -__host__ Rcpp::NumericMatrix knn_classifier_predict_probabilities( - Rcpp::List const& model, Rcpp::NumericMatrix const& x, - int const n_neighbors) { - Rcpp::stop("KNN is not yet supported with cuML 26.04."); - return Rcpp::NumericMatrix(); -} - -Rcpp::NumericVector knn_regressor_predict(Rcpp::List const& model, - Rcpp::NumericMatrix const& x, - int const n_neighbors) { - Rcpp::stop("KNN is not yet supported with cuML 26.04."); - return Rcpp::NumericVector(); -} - -} // namespace cuml4r - -#endif // CUML_VERSION_MAJOR < 26 diff --git a/src/pinned_host_vector.h b/src/pinned_host_vector.h index b9edaca..e858c08 100644 --- a/src/pinned_host_vector.h +++ b/src/pinned_host_vector.h @@ -4,7 +4,7 @@ #include #include -#if CUML_VERSION_MAJOR >= 26 +#if CUML_VERSION_MAJOR >= 25 #include #include #include @@ -16,7 +16,7 @@ namespace cuml4r { -#if CUML_VERSION_MAJOR >= 26 +#if CUML_VERSION_MAJOR >= 25 // CCCL 3.x removed pinned_allocator; use the new memory resource API template using pinned_host_vector = thrust::host_vector; diff --git a/src/random_forest_classifier.cu b/src/random_forest_classifier.cu index 1b424c8..b367d87 100644 --- a/src/random_forest_classifier.cu +++ b/src/random_forest_classifier.cu @@ -13,7 +13,7 @@ #include #include -#if CUML_VERSION_MAJOR < 26 +#if CUML_VERSION_MAJOR < 25 #include "fil_utils.h" #include #endif @@ -201,7 +201,7 @@ __host__ Rcpp::IntegerVector rf_classifier_predict( return Rcpp::IntegerVector(h_predictions.begin(), h_predictions.end()); } -#if CUML_VERSION_MAJOR < 26 +#if CUML_VERSION_MAJOR < 25 /* * The 'ML::fil::treelite_params_t::threads_per_tree' and * 'ML::fil::treelite_params_t::n_items' parameters are only supported in @@ -212,7 +212,7 @@ CUML4R_NOOP_IF_ABSENT(threads_per_tree) CUML4R_ASSIGN_IF_PRESENT(n_items) CUML4R_NOOP_IF_ABSENT(n_items) -#endif // CUML_VERSION_MAJOR < 26 +#endif // CUML_VERSION_MAJOR < 25 } // namespace @@ -307,7 +307,7 @@ __host__ Rcpp::IntegerVector rf_classifier_predict( /*predictions=*/d_preds, verbosity); }); } else { -#if CUML_VERSION_MAJOR >= 26 +#if CUML_VERSION_MAJOR >= 25 Rcpp::stop( "FIL-based prediction from unserialized random forest models is not yet " "supported with cuML 26.04."); @@ -350,13 +350,13 @@ __host__ Rcpp::IntegerVector rf_classifier_predict( #endif }); -#endif // CUML_VERSION_MAJOR >= 26 +#endif // CUML_VERSION_MAJOR >= 25 } } __host__ Rcpp::NumericMatrix rf_classifier_predict_class_probabilities( SEXP model_xptr, Rcpp::NumericMatrix const& input) { -#if CUML_VERSION_MAJOR >= 26 +#if CUML_VERSION_MAJOR >= 25 Rcpp::stop( "FIL-based class probability prediction for random forests is not yet " diff --git a/src/random_forest_regressor.cu b/src/random_forest_regressor.cu index 61d8fe0..06a985d 100644 --- a/src/random_forest_regressor.cu +++ b/src/random_forest_regressor.cu @@ -12,7 +12,7 @@ #include #include -#if CUML_VERSION_MAJOR < 26 +#if CUML_VERSION_MAJOR < 25 #include "fil_utils.h" #endif @@ -126,7 +126,7 @@ __host__ Rcpp::NumericVector rf_regressor_predict( return Rcpp::NumericVector(h_preds.begin(), h_preds.end()); } -#if CUML_VERSION_MAJOR < 26 +#if CUML_VERSION_MAJOR < 25 /* * The 'ML::fil::treelite_params_t::threads_per_tree' and * 'ML::fil::treelite_params_t::n_items' parameters are only supported in @@ -137,7 +137,7 @@ CUML4R_NOOP_IF_ABSENT(threads_per_tree) CUML4R_ASSIGN_IF_PRESENT(n_items) CUML4R_NOOP_IF_ABSENT(n_items) -#endif // CUML_VERSION_MAJOR < 26 +#endif // CUML_VERSION_MAJOR < 25 } // namespace @@ -227,7 +227,7 @@ __host__ Rcpp::NumericVector rf_regressor_predict( /*predictions=*/d_preds, verbosity); }); } else { -#if CUML_VERSION_MAJOR >= 26 +#if CUML_VERSION_MAJOR >= 25 Rcpp::stop( "FIL-based prediction from unserialized random forest models is not yet " "supported with cuML 26.04."); @@ -270,7 +270,7 @@ __host__ Rcpp::NumericVector rf_regressor_predict( #endif }); -#endif // CUML_VERSION_MAJOR >= 26 +#endif // CUML_VERSION_MAJOR >= 25 } } diff --git a/src/random_projection.cu b/src/random_projection.cu index b5943c4..f4e09a5 100644 --- a/src/random_projection.cu +++ b/src/random_projection.cu @@ -2,7 +2,7 @@ #include -#if CUML_VERSION_MAJOR < 26 +#if CUML_VERSION_MAJOR < 25 #include "async_utils.cuh" #include "cuda_utils.h" @@ -250,7 +250,7 @@ __host__ SEXP rproj_set_state(Rcpp::List const& model_state) { } // namespace cuml4r -#else // CUML_VERSION_MAJOR >= 26 +#else // CUML_VERSION_MAJOR >= 25 #include @@ -288,4 +288,4 @@ __host__ SEXP rproj_set_state(Rcpp::List const& model_state) { } // namespace cuml4r -#endif // CUML_VERSION_MAJOR < 26 +#endif // CUML_VERSION_MAJOR < 25 diff --git a/src/stream_allocator.cu b/src/stream_allocator.cu index 5ece327..7ba7c2b 100644 --- a/src/stream_allocator.cu +++ b/src/stream_allocator.cu @@ -4,7 +4,7 @@ #include "stream_allocator.h" #include -#if CUML_VERSION_MAJOR < 26 +#if CUML_VERSION_MAJOR < 25 #include "device_allocator.h" #endif diff --git a/src/svm_serde.h b/src/svm_serde.h index cdad438..df50dd8 100644 --- a/src/svm_serde.h +++ b/src/svm_serde.h @@ -4,7 +4,7 @@ #include -#if CUML_VERSION_MAJOR >= 26 +#if CUML_VERSION_MAJOR >= 25 #include #else #include @@ -18,7 +18,7 @@ // In cuML 26.04+, KernelParams moved from MLCommon::Matrix to ML::matrix // and the type names svmParameter/svmModel were already renamed to SvmParameter/SvmModel // in 21.10. For 26.04 we also need the namespace alias. -#if CUML_VERSION_MAJOR >= 26 +#if CUML_VERSION_MAJOR >= 25 namespace MLCommon { namespace Matrix { diff --git a/tools/config/libcuml_versions.R b/tools/config/libcuml_versions.R index 09f4638..562cc13 100644 --- a/tools/config/libcuml_versions.R +++ b/tools/config/libcuml_versions.R @@ -13,7 +13,7 @@ libcuml_versions <- list( "21.12" = list( "11" = "https://github.com/mlverse/libcuml-builds/releases/download/v21.12-cuda11.2.1/libcuml-21.12-cuda11.2.1.zip" ), - "26.04" = list( - "12" = "libcuml-cu12" + "25.12" = list( + "12" = "libcuml-cu12==25.12.*" ) ) diff --git a/tools/config/utils/cuml.R b/tools/config/utils/cuml.R index 9e64080..1c51f87 100644 --- a/tools/config/utils/cuml.R +++ b/tools/config/utils/cuml.R @@ -104,25 +104,9 @@ download_libcuml <- function(cuml_version = Sys.getenv("CUML_VERSION", unset = " unzip(tmp, exdir = ".", overwrite = TRUE) } - # Download CCCL headers from GitHub (required by rmm/raft at compile time, - # but not a pip dependency). CCCL is header-only. - # Uses the exact commit that RAPIDS 26.04 pins via rapids-cmake, which is - # CUDA 12 compatible (the release tag v3.3.0 includes CUDA 13-only code). - cccl_commit <- "09094af138841ef521de1adbbdd18ab8b3dad47b" - cccl_url <- sprintf( - "https://github.com/NVIDIA/cccl/archive/%s.tar.gz", cccl_commit - ) - cccl_tmp <- tempfile(fileext = ".tar.gz") - message(" Downloading CCCL headers (RAPIDS 26.04 pin)...") - download.file(cccl_url, cccl_tmp, quiet = TRUE) - untar(cccl_tmp, exdir = ".") - # Rename extracted dir for predictability - cccl_dir <- file.path(".", paste0("cccl-", cccl_commit)) - file.rename(cccl_dir, file.path(".", "cccl")) - # Merge all include/ directories into libcuml/include/. # Sources: pip wheels (libraft/, librmm/, nvidia/, rapids_logger/, etc.) - # and CCCL (cccl/). + # librmm vendors its own CCCL headers under librmm/include/rapids/. # Pip wheels may extract to nested dirs like nvidia//include/. merge_include_dirs <- function(src_dir) { dep_include <- file.path(src_dir, "include") @@ -142,8 +126,6 @@ download_libcuml <- function(cuml_version = Sys.getenv("CUML_VERSION", unset = " merge_include_dirs(sub) } } - # CCCL has include/ with cub/, cuda/, nv/, thrust/ - merge_include_dirs("./cccl") } else { # Direct URL: either a pip wheel (.whl) or legacy zip archive tmp <- tempfile(fileext = ".zip") diff --git a/tools/config/utils/pypi.R b/tools/config/utils/pypi.R index bf63cad..38d9e5e 100644 --- a/tools/config/utils/pypi.R +++ b/tools/config/utils/pypi.R @@ -36,19 +36,35 @@ pypi_wheel_url <- function(package, version = NULL, platform = "x86_64") { ) } -# Parse a PEP 508 dependency string into package name -# e.g. "libraft-cu12==26.4.*" -> "libraft-cu12" -# e.g. "numpy>=1.0; extra == 'test'" -> "numpy" (but we skip extras) -parse_dep_name <- function(dep_str) { +# Parse a PEP 508 dependency string into package name and version +# e.g. "libraft-cu12==25.12.*" -> list(name = "libraft-cu12", version = "25.12.0") +# e.g. "numpy>=1.0; extra == 'test'" -> NULL (skip extras) +parse_dep <- function(dep_str) { # Skip deps with extras/markers like "; extra == ..." if (grepl("; extra\\s*==", dep_str)) return(NULL) + # Also skip deps with platform markers that could exclude linux + if (grepl(";", dep_str) && !grepl("linux", dep_str)) return(NULL) # Extract package name (everything before version specifier or semicolon) - gsub("[\\s;(<>=!\\[].*", "", dep_str, perl = TRUE) + name <- gsub("[\\s;(<>=!\\[,].*", "", dep_str, perl = TRUE) + # Extract pinned version if present (e.g. ==25.12.*) + version <- NULL + if (grepl("==", dep_str)) { + ver_str <- sub(".*==\\s*", "", sub(";.*", "", dep_str)) + ver_str <- gsub("\\*", "0", ver_str) # 25.12.* -> 25.12.0 + version <- ver_str + } + list(name = name, version = version) } # Resolve all transitive dependencies that look like C++ library packages # (lib*, rapids-*, nvidia-cccl-*, nvidia-nvjitlink-*) -resolve_native_deps <- function(package, version = NULL, seen = character()) { +# The package_spec can be "libcuml-cu12" or "libcuml-cu12==25.12.*" +resolve_native_deps <- function(package_spec, seen = character()) { + dep <- parse_dep(package_spec) + if (is.null(dep)) return(list()) + package <- dep$name + version <- dep$version + if (package %in% seen) return(list()) seen <- c(seen, package) @@ -63,12 +79,12 @@ resolve_native_deps <- function(package, version = NULL, seen = character()) { # Only chase transitive deps for native/C++ packages if (!is.null(info$requires_dist)) { - for (dep in info$requires_dist) { - dep_name <- parse_dep_name(dep) - if (is.null(dep_name)) next + for (dep_str in info$requires_dist) { + dep <- parse_dep(dep_str) + if (is.null(dep)) next # Only follow native library deps (lib*, rapids-*, nvidia-cccl*, nvidia-nvjitlink*) - if (grepl("^(lib|rapids-|nvidia-cccl|nvidia-nvjitlink)", dep_name)) { - sub_deps <- resolve_native_deps(dep_name, seen = seen) + if (grepl("^(lib|rapids-|nvidia-cccl|nvidia-nvjitlink)", dep$name)) { + sub_deps <- resolve_native_deps(dep_str, seen = seen) seen <- c(seen, names(sub_deps)) result <- c(result, sub_deps) } From 473db2a132636421c8290e9544bfb425df3591ca Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Wed, 29 Apr 2026 10:57:49 -0300 Subject: [PATCH 27/28] Define LIBCUDACXX_ENABLE_EXPERIMENTAL_MEMORY_RESOURCE for RMM RMM headers require this define (normally set automatically by RMM's cmake config, but we're using headers directly from the pip wheel). --- src/CMakeLists.txt.in | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CMakeLists.txt.in b/src/CMakeLists.txt.in index 6f96f3c..321b702 100644 --- a/src/CMakeLists.txt.in +++ b/src/CMakeLists.txt.in @@ -5,6 +5,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") add_definitions(-DHAS_CUML) +add_definitions(-DLIBCUDACXX_ENABLE_EXPERIMENTAL_MEMORY_RESOURCE) include(FetchContent) FetchContent_Declare( From 0b13f1f2e1150bfc693b6cc58e81cd41fe7c0b8b Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Wed, 29 Apr 2026 11:17:11 -0300 Subject: [PATCH 28/28] Revert cuML 25.x/26.x support (CCCL 3.x incompatible with CUDA 12) All RAPIDS 25.x+ pip wheels require CCCL 3.x headers which are incompatible with CUDA 12's bundled CCCL 2.x. No version of libcuml-cu12 can be compiled against a stock CUDA 12 toolkit. Revert to cuML 21.12 as the default for now. Supporting newer cuML will require either CUDA 13 or a custom build environment. --- .github/docker/Dockerfile | 5 +- .github/workflows/R-CMD-check.yaml | 42 ++----------- src/CMakeLists.txt.in | 18 +++--- src/cuml_utils.cpp | 4 +- src/device_allocator.cu | 6 -- src/device_allocator.h | 6 -- src/fil.cu | 40 +------------ src/fil_utils.cu | 8 --- src/fil_utils.h | 8 --- src/handle_utils.cu | 9 --- src/handle_utils.h | 5 -- src/knn.cu | 20 +++++-- src/pinned_host_vector.h | 13 ---- src/random_forest_classifier.cu | 25 +------- src/random_forest_regressor.cu | 14 +---- src/random_projection.cu | 48 +-------------- src/stream_allocator.cu | 7 +-- src/svm_serde.h | 27 ++------- tools/config/configure.R | 25 +------- tools/config/libcuml_versions.R | 7 --- tools/config/utils/cmake.R | 8 +-- tools/config/utils/cuml.R | 65 +++----------------- tools/config/utils/pypi.R | 95 ------------------------------ 23 files changed, 64 insertions(+), 441 deletions(-) delete mode 100644 tools/config/utils/pypi.R diff --git a/.github/docker/Dockerfile b/.github/docker/Dockerfile index fad4978..2dbb8ec 100644 --- a/.github/docker/Dockerfile +++ b/.github/docker/Dockerfile @@ -1,5 +1,4 @@ -ARG CUDA_IMAGE=nvidia/cuda:12.8.1-devel-ubuntu22.04 -FROM ${CUDA_IMAGE} +FROM nvidia/cuda:11.2.2-devel-ubuntu20.04 ENV DEBIAN_FRONTEND=noninteractive @@ -32,7 +31,7 @@ RUN echo "MAKEFLAGS=-j$(nproc)" >> "$(R RHOME)/etc/Renviron.site" # Copy source COPY . /build -ARG CUML_VERSION=26.04 +ARG CUML_VERSION=21.12 ENV CUML_VERSION=${CUML_VERSION} # Cross-compile for T4 GPU (compute capability 7.5) since build runner has no GPU diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 8720f94..257527e 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -43,24 +43,13 @@ jobs: _R_CHECK_CRAN_INCOMING_: false build-image: - strategy: - fail-fast: false - matrix: - include: - - cuml: '21.12' - cuda_image: 'nvidia/cuda:11.2.2-devel-ubuntu20.04' - - cuml: '25.12' - cuda_image: 'nvidia/cuda:12.8.1-devel-ubuntu22.04' - runs-on: ubuntu-latest - name: 'Build (cuML ${{ matrix.cuml }})' permissions: contents: read packages: write timeout-minutes: 120 outputs: - image-21: ${{ steps.meta.outputs.image-21 }} - image-26: ${{ steps.meta.outputs.image-26 }} + image: ghcr.io/${{ github.repository }}-ci:${{ github.sha }} steps: - uses: actions/checkout@v4 @@ -80,41 +69,20 @@ jobs: context: . file: .github/docker/Dockerfile push: true - tags: ghcr.io/${{ github.repository }}-ci:cuml${{ matrix.cuml }}-${{ github.sha }} + tags: ghcr.io/${{ github.repository }}-ci:${{ github.sha }} build-args: | - CUDA_IMAGE=${{ matrix.cuda_image }} - CUML_VERSION=${{ matrix.cuml }} + CUML_VERSION=21.12 CMAKE_CUDA_ARCHITECTURES=75 - - name: Export image tag - id: meta - run: | - TAG="ghcr.io/${{ github.repository }}-ci:cuml${{ matrix.cuml }}-${{ github.sha }}" - if [[ "${{ matrix.cuml }}" == "21.12" ]]; then - echo "image-21=${TAG}" >> "$GITHUB_OUTPUT" - else - echo "image-26=${TAG}" >> "$GITHUB_OUTPUT" - fi - test-gpu: needs: build-image if: ${{ always() && needs.build-image.result == 'success' }} - strategy: - fail-fast: false - matrix: - include: - - cuml: '21.12' - image: ghcr.io/${{ github.repository }}-ci:cuml21.12-${{ github.sha }} - - cuml: '25.12' - image: ghcr.io/${{ github.repository }}-ci:cuml25.12-${{ github.sha }} - concurrency: - group: gpu-tests-cuml${{ matrix.cuml }} + group: gpu-tests runs-on: - "runs-on=${{ github.run_id }}/family=g4dn.xlarge/image=ubuntu24-gpu-x64/spot=true" - name: 'Test GPU (cuML ${{ matrix.cuml }})' container: - image: ${{ matrix.image }} + image: ${{ needs.build-image.outputs.image }} credentials: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} diff --git a/src/CMakeLists.txt.in b/src/CMakeLists.txt.in index 321b702..030d323 100644 --- a/src/CMakeLists.txt.in +++ b/src/CMakeLists.txt.in @@ -1,17 +1,22 @@ cmake_minimum_required(VERSION 3.8 FATAL_ERROR) -set(CMAKE_CXX_STANDARD @CMAKE_CXX_STANDARD@) +set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") add_definitions(-DHAS_CUML) -add_definitions(-DLIBCUDACXX_ENABLE_EXPERIMENTAL_MEMORY_RESOURCE) +# NOTE: at the moment only the `rapids_cuda_init_architectures` function +# from the rapids-cmake repo is used here, and the version from the 21.10 +# branch of rapids-cmake works fine, regardless of which version of libcuml +# we are building with. However, if in future any cuml-version-specific +# function from rapids-cmake is used here, then we will need to make the +# GIT_TAG choice below dependent on the libcuml version. include(FetchContent) FetchContent_Declare( rapids-cmake GIT_REPOSITORY https://github.com/rapidsai/rapids-cmake.git - GIT_TAG @RAPIDS_CMAKE_TAG@ + GIT_TAG origin/branch-21.10 ) FetchContent_MakeAvailable(rapids-cmake) include(rapids-cuda) @@ -27,10 +32,9 @@ if(DEFINED ENV{CUML4R_ENABLE_CCACHE}) endif(DEFINED ENV{CUML4R_ENABLE_CCACHE}) if(DEFINED CUML_INCLUDE_DIR) - # CUML_INCLUDE_DIR first so cuML's bundled headers (CCCL, raft, rmm) - # take precedence over the CUDA toolkit's older versions. - # CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES is still needed for cuda_runtime.h. - set(CUML4R_INCLUDE_DIRS ${CUML_INCLUDE_DIR} ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES}) + # CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES is needed so that cuda_runtime.h is found + # CUML_INCLUDE_DIR is needed so that kmeans/kmeans_c.h is found + set(CUML4R_INCLUDE_DIRS ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES} ${CUML_INCLUDE_DIR}) else() message(FATAL_ERROR "CUML_INCLUDE_DIR not specified.") endif(DEFINED CUML_INCLUDE_DIR) diff --git a/src/cuml_utils.cpp b/src/cuml_utils.cpp index dfba358..4f07355 100644 --- a/src/cuml_utils.cpp +++ b/src/cuml_utils.cpp @@ -4,8 +4,8 @@ #include -static_assert(CUML_VERSION_MAJOR == 21 || CUML_VERSION_MAJOR >= 25, - "{cuda.ml} supports linking to RAPIDS cuML 21.x or 25.x+"); +static_assert(CUML_VERSION_MAJOR == 21, + "{cuda.ml} currently only supports linking to RAPIDS cuML 21.x!"); #endif diff --git a/src/device_allocator.cu b/src/device_allocator.cu index 682b8e6..fe13909 100644 --- a/src/device_allocator.cu +++ b/src/device_allocator.cu @@ -2,10 +2,6 @@ #include "device_allocator.h" -#include - -#if CUML_VERSION_MAJOR < 25 - #include namespace { @@ -23,8 +19,6 @@ __host__ std::shared_ptr getDeviceAllocator() { } // namespace cuml4r -#endif // CUML_VERSION_MAJOR < 25 - #else #include "warn_cuml_missing.h" diff --git a/src/device_allocator.h b/src/device_allocator.h index f8f4e59..124c3b1 100644 --- a/src/device_allocator.h +++ b/src/device_allocator.h @@ -2,10 +2,6 @@ #ifdef HAS_CUML -#include - -#if CUML_VERSION_MAJOR < 25 - #include namespace raft { @@ -24,8 +20,6 @@ std::shared_ptr getDeviceAllocator(); } // namespace cuml4r -#endif // CUML_VERSION_MAJOR < 25 - #else #include "warn_cuml_missing.h" diff --git a/src/fil.cu b/src/fil.cu index e3359c5..1545177 100644 --- a/src/fil.cu +++ b/src/fil.cu @@ -1,15 +1,10 @@ -#include "preprocessor.h" - -#include - -#if CUML_VERSION_MAJOR < 25 - #include "async_utils.cuh" #include "cuda_utils.h" #include "fil_utils.h" #include "handle_utils.h" #include "matrix_utils.h" #include "pinned_host_vector.h" +#include "preprocessor.h" #include "stream_allocator.h" #include "treelite_utils.cuh" @@ -177,36 +172,3 @@ __host__ Rcpp::NumericMatrix fil_predict( } } // namespace cuml4r - -#else // CUML_VERSION_MAJOR >= 25 - -#include - -#include - -namespace cuml4r { - -__host__ SEXP fil_load_model(int const model_type, std::string const& filename, - int const algo, bool const classification, - float const threshold, int const storage_type, - int const blocks_per_sm, - int const threads_per_tree, int const n_items) { - Rcpp::stop("FIL (Forest Inference Library) is not yet supported with cuML 26.04."); - return R_NilValue; -} - -__host__ int fil_get_num_classes(SEXP const& model) { - Rcpp::stop("FIL (Forest Inference Library) is not yet supported with cuML 26.04."); - return 0; -} - -__host__ Rcpp::NumericMatrix fil_predict( - SEXP const& model, Rcpp::NumericMatrix const& x, - bool const output_class_probabilities) { - Rcpp::stop("FIL (Forest Inference Library) is not yet supported with cuML 26.04."); - return Rcpp::NumericMatrix(); -} - -} // namespace cuml4r - -#endif // CUML_VERSION_MAJOR < 25 diff --git a/src/fil_utils.cu b/src/fil_utils.cu index 992069c..e36d501 100644 --- a/src/fil_utils.cu +++ b/src/fil_utils.cu @@ -1,9 +1,3 @@ -#include "preprocessor.h" - -#include - -#if CUML_VERSION_MAJOR < 25 - #include "fil_utils.h" namespace cuml4r { @@ -25,5 +19,3 @@ __host__ forest_uptr make_forest(raft::handle_t const& handle, } // namespace fil } // namespace cuml4r - -#endif // CUML_VERSION_MAJOR < 25 diff --git a/src/fil_utils.h b/src/fil_utils.h index 744f32f..a5702d0 100644 --- a/src/fil_utils.h +++ b/src/fil_utils.h @@ -1,11 +1,5 @@ #pragma once -#include "preprocessor.h" - -#include - -#if CUML_VERSION_MAJOR < 25 - #include #include @@ -32,5 +26,3 @@ forest_uptr make_forest(raft::handle_t const& handle, } // namespace fil } // namespace cuml4r - -#endif // CUML_VERSION_MAJOR < 25 diff --git a/src/handle_utils.cu b/src/handle_utils.cu index 92f50be..9c61b7b 100644 --- a/src/handle_utils.cu +++ b/src/handle_utils.cu @@ -3,8 +3,6 @@ #ifdef HAS_CUML -#include - namespace cuml4r { namespace handle_utils { @@ -13,14 +11,7 @@ __host__ void initializeHandle(raft::handle_t& handle, if (stream_view.value() == 0) { stream_view = stream_allocator::getOrCreateStream(); } -#if CUML_VERSION_MAJOR >= 25 - // In raft 26.x, handle_t takes stream_view in the constructor. - // Reconstruct the handle with the desired stream via placement new. - handle.~handle_t(); - new (&handle) raft::handle_t(stream_view); -#else handle.set_stream(stream_view.value()); -#endif } } // namespace handle_utils diff --git a/src/handle_utils.h b/src/handle_utils.h index 260b1f7..f00d622 100644 --- a/src/handle_utils.h +++ b/src/handle_utils.h @@ -2,12 +2,7 @@ #ifdef HAS_CUML -#include -#if CUML_VERSION_MAJOR >= 25 -#include -#else #include -#endif #include namespace cuml4r { diff --git a/src/knn.cu b/src/knn.cu index de7511d..13894d8 100644 --- a/src/knn.cu +++ b/src/knn.cu @@ -1,17 +1,17 @@ -#include "preprocessor.h" - #include "async_utils.cuh" #include "cuda_utils.h" #include "handle_utils.h" #include "knn_detail.h" #include "matrix_utils.h" #include "pinned_host_vector.h" +#include "preprocessor.h" #include "random_forest.cuh" #include "stream_allocator.h" #include #include #include +#include #include @@ -21,9 +21,9 @@ #include #include -#include +#if CUML_VERSION_MAJOR == 21 +#if CUML4R_CONCAT(0x, CUML_VERSION_MINOR) >= 0x08 -// In cuML 21.08+ / 25.x, the KNN index types live in raft::spatial::knn #include using knnIndex = raft::spatial::knn::knnIndex; @@ -33,6 +33,18 @@ using IVFFlatParam = raft::spatial::knn::IVFFlatParam; using IVFPQParam = raft::spatial::knn::IVFPQParam; using IVFSQParam = raft::spatial::knn::IVFSQParam; +#else + +using knnIndex = ML::knnIndex; +using knnIndexParam = ML::knnIndexParam; +using QuantizerType = ML::QuantizerType; +using IVFFlatParam = ML::IVFFlatParam; +using IVFPQParam = ML::IVFPQParam; +using IVFSQParam = ML::IVFSQParam; + +#endif +#endif + namespace cuml4r { namespace knn { namespace { diff --git a/src/pinned_host_vector.h b/src/pinned_host_vector.h index e858c08..a0d6359 100644 --- a/src/pinned_host_vector.h +++ b/src/pinned_host_vector.h @@ -2,29 +2,16 @@ #ifdef HAS_CUML -#include #include -#if CUML_VERSION_MAJOR >= 25 -#include -#include -#include -#else #include -#endif #include namespace cuml4r { -#if CUML_VERSION_MAJOR >= 25 -// CCCL 3.x removed pinned_allocator; use the new memory resource API -template -using pinned_host_vector = thrust::host_vector; -#else template using pinned_host_vector = thrust::host_vector>; -#endif } // namespace cuml4r diff --git a/src/random_forest_classifier.cu b/src/random_forest_classifier.cu index b367d87..9c277c0 100644 --- a/src/random_forest_classifier.cu +++ b/src/random_forest_classifier.cu @@ -1,5 +1,6 @@ #include "async_utils.cuh" #include "cuda_utils.h" +#include "fil_utils.h" #include "handle_utils.h" #include "matrix_utils.h" #include "pinned_host_vector.h" @@ -8,16 +9,12 @@ #include "random_forest_serde.cuh" #include "stream_allocator.h" +#include #include #include #include #include -#if CUML_VERSION_MAJOR < 25 -#include "fil_utils.h" -#include -#endif - #include #include @@ -201,7 +198,6 @@ __host__ Rcpp::IntegerVector rf_classifier_predict( return Rcpp::IntegerVector(h_predictions.begin(), h_predictions.end()); } -#if CUML_VERSION_MAJOR < 25 /* * The 'ML::fil::treelite_params_t::threads_per_tree' and * 'ML::fil::treelite_params_t::n_items' parameters are only supported in @@ -212,7 +208,6 @@ CUML4R_NOOP_IF_ABSENT(threads_per_tree) CUML4R_ASSIGN_IF_PRESENT(n_items) CUML4R_NOOP_IF_ABSENT(n_items) -#endif // CUML_VERSION_MAJOR < 25 } // namespace @@ -307,12 +302,6 @@ __host__ Rcpp::IntegerVector rf_classifier_predict( /*predictions=*/d_preds, verbosity); }); } else { -#if CUML_VERSION_MAJOR >= 25 - Rcpp::stop( - "FIL-based prediction from unserialized random forest models is not yet " - "supported with cuML 26.04."); - return Rcpp::IntegerVector(); -#else return rf_classifier_predict( model, input, /*predict_impl=*/ @@ -350,20 +339,12 @@ __host__ Rcpp::IntegerVector rf_classifier_predict( #endif }); -#endif // CUML_VERSION_MAJOR >= 25 } } __host__ Rcpp::NumericMatrix rf_classifier_predict_class_probabilities( SEXP model_xptr, Rcpp::NumericMatrix const& input) { -#if CUML_VERSION_MAJOR >= 25 - - Rcpp::stop( - "FIL-based class probability prediction for random forests is not yet " - "supported with cuML 26.04."); - return Rcpp::NumericMatrix(); - -#elif !defined(CUML4R_TREELITE_C_API_MISSING) +#ifndef CUML4R_TREELITE_C_API_MISSING auto const input_m = Matrix(input, /*transpose=*/false); int const n_samples = input_m.numRows; diff --git a/src/random_forest_regressor.cu b/src/random_forest_regressor.cu index 06a985d..1dd9dea 100644 --- a/src/random_forest_regressor.cu +++ b/src/random_forest_regressor.cu @@ -1,5 +1,6 @@ #include "async_utils.cuh" #include "cuda_utils.h" +#include "fil_utils.h" #include "handle_utils.h" #include "matrix_utils.h" #include "pinned_host_vector.h" @@ -12,10 +13,6 @@ #include #include -#if CUML_VERSION_MAJOR < 25 -#include "fil_utils.h" -#endif - #include #include @@ -126,7 +123,6 @@ __host__ Rcpp::NumericVector rf_regressor_predict( return Rcpp::NumericVector(h_preds.begin(), h_preds.end()); } -#if CUML_VERSION_MAJOR < 25 /* * The 'ML::fil::treelite_params_t::threads_per_tree' and * 'ML::fil::treelite_params_t::n_items' parameters are only supported in @@ -137,7 +133,6 @@ CUML4R_NOOP_IF_ABSENT(threads_per_tree) CUML4R_ASSIGN_IF_PRESENT(n_items) CUML4R_NOOP_IF_ABSENT(n_items) -#endif // CUML_VERSION_MAJOR < 25 } // namespace @@ -227,12 +222,6 @@ __host__ Rcpp::NumericVector rf_regressor_predict( /*predictions=*/d_preds, verbosity); }); } else { -#if CUML_VERSION_MAJOR >= 25 - Rcpp::stop( - "FIL-based prediction from unserialized random forest models is not yet " - "supported with cuML 26.04."); - return Rcpp::NumericVector(); -#else return rf_regressor_predict( input, /*predict_impl=*/ @@ -270,7 +259,6 @@ __host__ Rcpp::NumericVector rf_regressor_predict( #endif }); -#endif // CUML_VERSION_MAJOR >= 25 } } diff --git a/src/random_projection.cu b/src/random_projection.cu index f4e09a5..30cf51c 100644 --- a/src/random_projection.cu +++ b/src/random_projection.cu @@ -1,19 +1,15 @@ -#include "preprocessor.h" - -#include - -#if CUML_VERSION_MAJOR < 25 - #include "async_utils.cuh" #include "cuda_utils.h" #include "handle_utils.h" #include "matrix_utils.h" #include "pinned_host_vector.h" +#include "preprocessor.h" #include "stream_allocator.h" #include #include #include +#include #include @@ -249,43 +245,3 @@ __host__ SEXP rproj_set_state(Rcpp::List const& model_state) { } } // namespace cuml4r - -#else // CUML_VERSION_MAJOR >= 25 - -#include - -namespace cuml4r { - -__host__ size_t rproj_johnson_lindenstrauss_min_dim(size_t const n_samples, - double const eps) { - Rcpp::stop("Random projection is not yet supported with cuML 26.04."); - return 0; -} - -__host__ SEXP rproj_fit(int const n_samples, int const n_features, - int const n_components, double const eps, - bool const gaussian_method, double const density, - int const random_state) { - Rcpp::stop("Random projection is not yet supported with cuML 26.04."); - return R_NilValue; -} - -__host__ Rcpp::NumericMatrix rproj_transform(SEXP rproj_ctx_xptr, - Rcpp::NumericMatrix const& input) { - Rcpp::stop("Random projection is not yet supported with cuML 26.04."); - return Rcpp::NumericMatrix(); -} - -__host__ Rcpp::List rproj_get_state(SEXP model) { - Rcpp::stop("Random projection is not yet supported with cuML 26.04."); - return Rcpp::List(); -} - -__host__ SEXP rproj_set_state(Rcpp::List const& model_state) { - Rcpp::stop("Random projection is not yet supported with cuML 26.04."); - return R_NilValue; -} - -} // namespace cuml4r - -#endif // CUML_VERSION_MAJOR < 25 diff --git a/src/stream_allocator.cu b/src/stream_allocator.cu index 7ba7c2b..acc79fe 100644 --- a/src/stream_allocator.cu +++ b/src/stream_allocator.cu @@ -1,12 +1,8 @@ #ifdef HAS_CUML #include "cuda_utils.h" -#include "stream_allocator.h" - -#include -#if CUML_VERSION_MAJOR < 25 #include "device_allocator.h" -#endif +#include "stream_allocator.h" #include #include @@ -46,6 +42,7 @@ __host__ rmm::cuda_stream_view getOrCreateStream() { if (it != cuda_streams_map.end()) { return it->second.value(); } + auto const device_allocator = getDeviceAllocator(); auto stream = rmm::cuda_stream(); auto stream_view = stream.view(); cudaStreamsMap().emplace(dev_id, std::move(stream)); diff --git a/src/svm_serde.h b/src/svm_serde.h index df50dd8..c0d762c 100644 --- a/src/svm_serde.h +++ b/src/svm_serde.h @@ -2,39 +2,24 @@ #include "preprocessor.h" -#include - -#if CUML_VERSION_MAJOR >= 25 -#include -#else #include -#endif - #include #include +#include #include -// In cuML 26.04+, KernelParams moved from MLCommon::Matrix to ML::matrix -// and the type names svmParameter/svmModel were already renamed to SvmParameter/SvmModel -// in 21.10. For 26.04 we also need the namespace alias. -#if CUML_VERSION_MAJOR >= 25 - -namespace MLCommon { -namespace Matrix { -using KernelParams = ML::matrix::KernelParams; -using KernelType = ML::matrix::KernelType; -} // namespace Matrix -} // namespace MLCommon - -#elif (CUML4R_LIBCUML_VERSION(CUML_VERSION_MAJOR, CUML_VERSION_MINOR) >= \ - CUML4R_LIBCUML_VERSION(21, 10)) +#if (CUML4R_LIBCUML_VERSION(CUML_VERSION_MAJOR, CUML_VERSION_MINOR) >= \ + CUML4R_LIBCUML_VERSION(21, 10)) namespace ML { namespace SVM { + using svmParameter = SvmParameter; + template using svmModel = SvmModel; + } // namespace SVM } // namespace ML diff --git a/tools/config/configure.R b/tools/config/configure.R index 06ab0a4..e271f6d 100644 --- a/tools/config/configure.R +++ b/tools/config/configure.R @@ -54,7 +54,7 @@ load_libcuml_versions <- function() { load_util_fns <- function() { wd <- file.path(pkg_root(), "tools", "config", "utils") - for (f in c("cuml.R", "cmake.R", "logging.R", "nvcc.R", "platform.R", "pypi.R")) { + for (f in c("cuml.R", "cmake.R", "logging.R", "nvcc.R", "platform.R")) { source(file.path(wd, f)) } } @@ -67,21 +67,8 @@ run_cmake <- function() { on.exit(setwd(wd)) setwd(pkg_root()) - cuml_version <- Sys.getenv("CUML_VERSION", unset = "21.08") - # rapids-cmake tags: v21.x had only alpha tags (v21.08.00a), - # v23.02+ has stable tags (v23.02.00) - rapids_cmake_tag <- if (package_version(cuml_version) >= "23.02") { - paste0("v", cuml_version, ".00") - } else { - paste0("v", cuml_version, ".00a") - } - - cxx_standard <- if (grepl("^2[6-9]\\.|^[3-9]", cuml_version)) "17" else "14" - define(R_INCLUDE_DIR = R.home("include")) define(RCPP_INCLUDE_DIR = system.file("include", package = "Rcpp")) - define(RAPIDS_CMAKE_TAG = rapids_cmake_tag) - define(CMAKE_CXX_STANDARD = cxx_standard) configure_file(file.path("src", "CMakeLists.txt.in")) cuml_prefix <- get_cuml_prefix() @@ -90,15 +77,9 @@ run_cmake <- function() { download_libcuml() cuml_prefix <- normalizePath(file.path(pkg_root(), "libcuml")) dir.create("inst") - # pip wheels have lib64/, legacy zips have lib/ - has_lib64 <- dir.exists(file.path("libcuml", "lib64")) - lib_dir <- if (has_lib64) "lib64" else "lib" - file.rename(file.path("libcuml", lib_dir), file.path("inst", "libs")) - # Create symlinks so cmake can find libs at both libcuml/lib/ and libcuml/lib64/ + file.rename(file.path("libcuml", "lib"), file.path("inst", "libs")) file.symlink(file.path("..", "inst", "libs"), file.path("libcuml", "lib")) - if (has_lib64) { - file.symlink(file.path("..", "inst", "libs"), file.path("libcuml", "lib64")) - } + libs <- c("libtreelite", "libtreelite_runtime", "libcuml++") bundle_libcuml <- TRUE } cmake_prefix_path <- paste0( diff --git a/tools/config/libcuml_versions.R b/tools/config/libcuml_versions.R index 562cc13..f1bc2e2 100644 --- a/tools/config/libcuml_versions.R +++ b/tools/config/libcuml_versions.R @@ -1,8 +1,4 @@ # A list containing libcuml download links for "cuml_versions" and CUDA major versions. -# -# For cuML 21.x: pre-built zip archives from mlverse/libcuml-builds GitHub releases. -# For cuML 26.x+: pip wheels from PyPI (libcuml-cu12). The wheel is a zip containing -# headers in libcuml/include/cuml/ and shared libs in libcuml/lib64/. libcuml_versions <- list( "21.08" = list( "11" = "https://github.com/mlverse/libcuml-builds/releases/download/v21.08-cuda11.2.1/libcuml-21.08-cuda11.2.1.zip" @@ -12,8 +8,5 @@ libcuml_versions <- list( ), "21.12" = list( "11" = "https://github.com/mlverse/libcuml-builds/releases/download/v21.12-cuda11.2.1/libcuml-21.12-cuda11.2.1.zip" - ), - "25.12" = list( - "12" = "libcuml-cu12==25.12.*" ) ) diff --git a/tools/config/utils/cmake.R b/tools/config/utils/cmake.R index 0e5e9bf..664b987 100644 --- a/tools/config/utils/cmake.R +++ b/tools/config/utils/cmake.R @@ -6,13 +6,7 @@ # and # https://github.com/mlverse/cuda.ml/blob/7bad914c729011bcf05edc1c873609c518d9a77d/src/CMakeLists.txt.in#L13 # where cuda.ml specifies which branch of the rapids-cmake repo to use) -cuda_ml_min_cmake_version <- if ( - package_version(Sys.getenv("CUML_VERSION", unset = "21.08")) >= "23.02" -) { - numeric_version("3.30.4") -} else { - numeric_version("3.21.1") -} +cuda_ml_min_cmake_version <- numeric_version("3.21.1") has_cmake <- function() { rc <- system2("which", "cmake", stdout = NULL, stderr = NULL) diff --git a/tools/config/utils/cuml.R b/tools/config/utils/cuml.R index 1c51f87..bdb582e 100644 --- a/tools/config/utils/cuml.R +++ b/tools/config/utils/cuml.R @@ -78,65 +78,18 @@ download_libcuml <- function(cuml_version = Sys.getenv("CUML_VERSION", unset = " options(timeout = 1000) on.exit(options(timeout = old_timeout), add = TRUE) + tmp <- tempfile(fileext = ".zip") cuda_version <- as.character(find_nvcc()$version$major) - url_entry <- Sys.getenv("CUML_URL") - if (!nzchar(url_entry)) { - url_entry <- libcuml_versions[[cuml_version]][[cuda_version]] + url <- Sys.getenv("CUML_URL") + if (!nzchar(url)) { + url <- libcuml_versions[[cuml_version]][[cuda_version]] } - is_pypi_package <- !grepl("^https?://", url_entry) + download.file(url, tmp) + unzip(tmp, exdir = ".") - if (is_pypi_package) { - # Resolve and download the full dependency tree from PyPI. - # This downloads libcuml-cu12 and all its native header dependencies - # (libraft, librmm, rapids-logger, nvidia-cccl, etc.) as wheels, extracts - # them, and merges all headers into libcuml/include/. - message("Resolving PyPI dependencies for ", url_entry, "...") - urls <- resolve_native_deps(url_entry) - message("Downloading ", length(urls), " packages: ", paste(names(urls), collapse = ", ")) - - for (pkg_name in names(urls)) { - url <- urls[[pkg_name]] - tmp <- tempfile(fileext = ".whl") - message(" Downloading ", pkg_name, "...") - download.file(url, tmp, quiet = TRUE) - unzip(tmp, exdir = ".", overwrite = TRUE) - } - - # Merge all include/ directories into libcuml/include/. - # Sources: pip wheels (libraft/, librmm/, nvidia/, rapids_logger/, etc.) - # librmm vendors its own CCCL headers under librmm/include/rapids/. - # Pip wheels may extract to nested dirs like nvidia//include/. - merge_include_dirs <- function(src_dir) { - dep_include <- file.path(src_dir, "include") - if (dir.exists(dep_include)) { - file.copy( - list.dirs(dep_include, full.names = TRUE, recursive = FALSE), - file.path("libcuml", "include"), - recursive = TRUE - ) - } - } - for (d in list.dirs(".", full.names = TRUE, recursive = FALSE)) { - if (d == "./libcuml") next - merge_include_dirs(d) - # Some pip wheels nest under nvidia//include/ - for (sub in list.dirs(d, full.names = TRUE, recursive = FALSE)) { - merge_include_dirs(sub) - } - } - } else { - # Direct URL: either a pip wheel (.whl) or legacy zip archive - tmp <- tempfile(fileext = ".zip") - download.file(url_entry, tmp) - unzip(tmp, exdir = ".") - - if (!grepl("\\.whl$", url_entry)) { - # Legacy zip archives: extract to a versioned directory name, rename to libcuml/ - zip_file_name <- basename(url_entry) - dir_name <- gsub("\\.zip$", "", zip_file_name) - file.rename(file.path(".", dir_name), file.path(".", "libcuml")) - } - } + zip_file_name <- basename(url) + dir_name <- gsub("\\.zip$", "", zip_file_name) + file.rename(file.path(".", dir_name), file.path(".", "libcuml")) } diff --git a/tools/config/utils/pypi.R b/tools/config/utils/pypi.R deleted file mode 100644 index 38d9e5e..0000000 --- a/tools/config/utils/pypi.R +++ /dev/null @@ -1,95 +0,0 @@ -# Resolve the full dependency tree for a PyPI package and return download URLs -# for all packages (including transitive deps) that contain C++ headers. -# -# This is used to download libcuml and all its header-only dependencies -# (libraft, librmm, rapids-logger, nvidia-cccl, etc.) from PyPI without -# needing pip installed. - -pypi_package_info <- function(package, version = NULL) { - url <- if (is.null(version)) { - sprintf("https://pypi.org/pypi/%s/json", package) - } else { - sprintf("https://pypi.org/pypi/%s/%s/json", package, version) - } - tmp <- tempfile(fileext = ".json") - download.file(url, tmp, quiet = TRUE) - jsonlite::fromJSON(tmp) -} - -pypi_wheel_url <- function(package, version = NULL, platform = "x86_64") { - info <- pypi_package_info(package, version) - urls <- info$urls - # Find a matching wheel for the platform - idx <- grep(platform, urls$filename) - if (length(idx) == 0) { - # Try platform-independent wheels - idx <- grep("none-any", urls$filename) - } - if (length(idx) == 0) { - stop(sprintf("No wheel found for %s (platform: %s)", package, platform)) - } - list( - url = urls$url[idx[1]], - filename = urls$filename[idx[1]], - version = info$info$version, - requires_dist = info$info$requires_dist - ) -} - -# Parse a PEP 508 dependency string into package name and version -# e.g. "libraft-cu12==25.12.*" -> list(name = "libraft-cu12", version = "25.12.0") -# e.g. "numpy>=1.0; extra == 'test'" -> NULL (skip extras) -parse_dep <- function(dep_str) { - # Skip deps with extras/markers like "; extra == ..." - if (grepl("; extra\\s*==", dep_str)) return(NULL) - # Also skip deps with platform markers that could exclude linux - if (grepl(";", dep_str) && !grepl("linux", dep_str)) return(NULL) - # Extract package name (everything before version specifier or semicolon) - name <- gsub("[\\s;(<>=!\\[,].*", "", dep_str, perl = TRUE) - # Extract pinned version if present (e.g. ==25.12.*) - version <- NULL - if (grepl("==", dep_str)) { - ver_str <- sub(".*==\\s*", "", sub(";.*", "", dep_str)) - ver_str <- gsub("\\*", "0", ver_str) # 25.12.* -> 25.12.0 - version <- ver_str - } - list(name = name, version = version) -} - -# Resolve all transitive dependencies that look like C++ library packages -# (lib*, rapids-*, nvidia-cccl-*, nvidia-nvjitlink-*) -# The package_spec can be "libcuml-cu12" or "libcuml-cu12==25.12.*" -resolve_native_deps <- function(package_spec, seen = character()) { - dep <- parse_dep(package_spec) - if (is.null(dep)) return(list()) - package <- dep$name - version <- dep$version - - if (package %in% seen) return(list()) - seen <- c(seen, package) - - info <- tryCatch( - pypi_wheel_url(package, version), - error = function(e) NULL - ) - if (is.null(info)) return(list()) - - result <- list() - result[[package]] <- info$url - - # Only chase transitive deps for native/C++ packages - if (!is.null(info$requires_dist)) { - for (dep_str in info$requires_dist) { - dep <- parse_dep(dep_str) - if (is.null(dep)) next - # Only follow native library deps (lib*, rapids-*, nvidia-cccl*, nvidia-nvjitlink*) - if (grepl("^(lib|rapids-|nvidia-cccl|nvidia-nvjitlink)", dep$name)) { - sub_deps <- resolve_native_deps(dep_str, seen = seen) - seen <- c(seen, names(sub_deps)) - result <- c(result, sub_deps) - } - } - } - - result -}