Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion conda/environments/all_cuda-129_arch-aarch64.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ dependencies:
- nltk
- numba-cuda>=0.22.2,<0.29.0
- numba>=0.60.0,<0.65.0
- numpy>=1.23,<3.0
- numpy>=1.26,<3.0
- numpydoc
- numpydoc<1.9
- nvidia-ml-py>=12
Expand Down
2 changes: 1 addition & 1 deletion conda/environments/all_cuda-129_arch-x86_64.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ dependencies:
- nltk
- numba-cuda>=0.22.2,<0.29.0
- numba>=0.60.0,<0.65.0
- numpy>=1.23,<3.0
- numpy>=1.26,<3.0
- numpydoc
- numpydoc<1.9
- nvidia-ml-py>=12
Expand Down
2 changes: 1 addition & 1 deletion conda/environments/all_cuda-132_arch-aarch64.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ dependencies:
- nltk
- numba-cuda>=0.22.2,<0.29.0
- numba>=0.60.0,<0.65.0
- numpy>=1.23,<3.0
- numpy>=1.26,<3.0
- numpydoc
- numpydoc<1.9
- nvidia-ml-py>=12
Expand Down
2 changes: 1 addition & 1 deletion conda/environments/all_cuda-132_arch-x86_64.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ dependencies:
- nltk
- numba-cuda>=0.22.2,<0.29.0
- numba>=0.60.0,<0.65.0
- numpy>=1.23,<3.0
- numpy>=1.26,<3.0
- numpydoc
- numpydoc<1.9
- nvidia-ml-py>=12
Expand Down
2 changes: 1 addition & 1 deletion conda/recipes/cuml/recipe.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ requirements:
- libcuml =${{ version }}
- numba >=0.60.0,<0.65.0
- numba-cuda >=0.22.2,<0.29.0
- numpy >=1.23,<3.0
- numpy >=1.26,<3.0
- scikit-learn >=1.4
- scipy >=1.14.0
- packaging
Expand Down
4 changes: 3 additions & 1 deletion dependencies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ files:
- cuda_wheels
- depends_on_libcuvs
- depends_on_libraft
- depends_on_librmm
- depends_on_rapids_logger
channels:
- rapidsai-nightly
Expand Down Expand Up @@ -323,7 +324,7 @@ dependencies:
packages:
- joblib>=0.11
- numba>=0.60.0,<0.65.0
- &numpy numpy>=1.23,<3.0
- &numpy numpy>=1.26,<3.0
- scipy>=1.14.0
- packaging
- rich
Expand Down Expand Up @@ -530,6 +531,7 @@ dependencies:
- scikit-learn==1.5.0
- umap-learn==0.5.7
- hdbscan==0.8.39
- numpy==1.26
- matrix: {dependencies: "intermediate"}
packages:
- scikit-learn==1.7.2
Expand Down
1 change: 1 addition & 0 deletions docs/source/cuml-accel/examples/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ examples in this section is available in the cuML GitHub repository at `examples
getting_started.ipynb
profiling.ipynb
plot_kmeans_digits.ipynb
third-party-apps.rst
onnx_export.ipynb
117 changes: 117 additions & 0 deletions docs/source/cuml-accel/examples/third-party-apps.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
Accelerating Third-Party Applications
======================================

The ``CUML_ACCEL_ENABLED`` environment variable lets you GPU-accelerate any
Python application that uses ``sklearn``, ``umap``, or ``hdbscan``.
Even applications whose code you cannot modify. This is useful for
installed CLI tools, applications, and third-party libraries.

.. code-block:: console

CUML_ACCEL_ENABLED=1 some-third-party-tool [args...]

When :ref:`CUML_ACCEL_ENABLED=1 is defined <cuml-accel-env-var>`,
`cuml.accel` will be enabled as part of the normal Python interpreter
startup, letting you accelerate Python applications without modification

This means you do not need access to an application's source code: set the
environment variable and the acceleration applies automatically.

Example: Embedding Visualization with embedding-atlas
-----------------------------------------------------

`embedding-atlas <https://github.com/apple/embedding-atlas>`_ is Apple's
open-source tool for interactive visualization of large embedding datasets.
Given a text dataset, it computes sentence embeddings, projects them to 2D
using `UMAP <https://umap-learn.readthedocs.io/>`_, and launches a
browser-based explorer.

Install it alongside ``cuml``:

.. code-block:: console

pip install embedding-atlas

Run it on a Hugging Face dataset. The example below uses
`TinyStories <https://huggingface.co/datasets/roneneldan/TinyStories>`_,
a dataset of 2M+ short stories:

.. code-block:: console

# CPU -- UMAP runs on CPU
embedding-atlas roneneldan/TinyStories --text text \
--split train --sample 1000000

# GPU -- set environment variable; no other changes needed
CUML_ACCEL_ENABLED=1 embedding-atlas roneneldan/TinyStories --text text \
--split train --sample 1000000

The only change between the two commands is the environment variable.
``embedding-atlas`` computes embeddings with sentence-transformers (which
already uses the GPU), then runs UMAP for dimensionality reduction.
``cuml.accel`` intercepts the ``umap.UMAP`` call inside ``embedding-atlas``
and dispatches ``fit_transform`` to cuML's GPU implementation.

Use a smaller ``--sample`` value (e.g. 250000) for a quicker test run.
The UMAP speedup grows with dataset size.

To confirm GPU dispatch, add ``CUML_ACCEL_LOG_LEVEL=info``:

.. code-block:: console

CUML_ACCEL_ENABLED=1 CUML_ACCEL_LOG_LEVEL=info embedding-atlas \
roneneldan/TinyStories --text text --split train --sample 1000000

You should see the following messages amongst the other output:

.. code-block:: text

[cuml.accel] Accelerator installed.
[cuml.accel] `UMAP.fit_transform` ran on GPU

Results
~~~~~~~

At the time of writing and on the hardware the author used the
``fit_transform`` step saw a roughly **~4x speedup** because cuML's GPU
UMAP replaces the CPU optimization. The KNN step (``nearest_neighbors``)
is a standalone function call that ``cuml.accel`` does not currently
intercept, so it runs on CPU in both cases. Despite this, the overall
UMAP step is still **~2x faster**.

At smaller scales (< 100K rows) the UMAP step is already fast on CPU and
the speedup is less pronounced. The benefit grows with dataset size.


Identifying Acceleratable Applications
---------------------------------------

Any Python tool that calls one of the following is a candidate for
``CUML_ACCEL_ENABLED``:

- ``sklearn`` estimators (KMeans, PCA, DBSCAN, RandomForest,
LogisticRegression, NearestNeighbors, and
:doc:`many more <../faq>`)
- ``umap.UMAP``
- ``hdbscan.HDBSCAN``

A quick way to check: search an application's dependencies for
``scikit-learn``, ``umap-learn``, or ``hdbscan``, or run with
``CUML_ACCEL_LOG_LEVEL=info`` and look for ``ran on GPU`` messages
in the output.

Checking for CPU Fallbacks
--------------------------

Not all parameter combinations are supported on the GPU. When
``cuml.accel`` encounters an unsupported configuration, it silently
falls back to CPU execution. To detect this, set the log level to
``info`` or ``debug``:

.. code-block:: console

CUML_ACCEL_ENABLED=1 CUML_ACCEL_LOG_LEVEL=info python app.py

Lines containing ``ran on GPU`` confirm GPU execution. Lines
containing ``falling back to CPU`` indicate a fallback, along with
the reason. See :doc:`../logging-and-profiling` for more detail.
6 changes: 6 additions & 0 deletions docs/source/cuml-accel/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ executing the following line magic at the top (before other imports):
You can see an example of this in :doc:`this example
<examples/plot_kmeans_digits>`.

.. _cuml-accel-env-var:

Environment Variable
~~~~~~~~~~~~~~~~~~~~

Expand All @@ -75,6 +77,10 @@ environment variable to ``1`` or ``true`` (case insensitive).
Note that any python program running with the environment defined this way
will load the accelerator, which may result in a measurable startup overhead.

This approach is especially useful for accelerating
:doc:`third-party applications <examples/third-party-apps>` whose code you do not
control.

Additionally, if ``cuml`` is not installed properly in your environment, the
``CUML_ACCEL_ENABLED`` environment variable will be silently ignored (and
normal CPU execution will occur). For this reason one of the other methods
Expand Down
25 changes: 25 additions & 0 deletions python/cuml/cuml/accel/_patches/sklearn/compose.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION.
# SPDX-License-Identifier: Apache-2.0
import functools

from sklearn.compose import ColumnTransformer

from cuml.internals.outputs import using_output_type

__all__ = ("ColumnTransformer",)


def patch_method(name):
"""Patch a ColumnTransformer method to ensure results returned as numpy."""
orig_method = getattr(ColumnTransformer, name)

@functools.wraps(orig_method)
def method(self, *args, **kwargs):
with using_output_type("numpy"):
return orig_method(self, *args, **kwargs)

setattr(ColumnTransformer, name, method)


for method_name in ["fit", "fit_transform", "transform"]:
patch_method(method_name)
24 changes: 20 additions & 4 deletions python/cuml/cuml/accel/_patches/sklearn/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@

import cupy as cp
from cupyx.scipy.sparse import issparse as is_cp_sparse
from sklearn.pipeline import Pipeline
from sklearn.pipeline import FeatureUnion, Pipeline
from sklearn.utils.metaestimators import available_if

from cuml.accel.estimator_proxy import is_proxy
from cuml.internals.global_settings import GlobalSettings
from cuml.internals.outputs import using_output_type

__all__ = ("Pipeline",)
__all__ = ("Pipeline", "FeatureUnion")
Comment thread
jcrist marked this conversation as resolved.


def get_output_type(pipeline, reverse=False):
Expand Down Expand Up @@ -54,7 +54,7 @@ def flat_steps(pipeline):
return "cupy"


def patch_method(name):
def patch_pipeline_method(name):
"""Patch a sklearn Pipeline method to reduce device<->host transfers."""
orig_method = inspect.getattr_static(Pipeline, name)
# Unwrap @available_if decorated methods
Expand Down Expand Up @@ -105,4 +105,20 @@ def method(self, *args, **kwargs):
"score_samples",
"transform",
]:
patch_method(method_name)
patch_pipeline_method(method_name)


def patch_feature_union_method(name):
"""Patch a FeatureUnion method to ensure results returned as numpy."""
orig_method = getattr(FeatureUnion, name)

@functools.wraps(orig_method)
def method(self, *args, **kwargs):
with using_output_type("numpy"):
return orig_method(self, *args, **kwargs)

setattr(FeatureUnion, name, method)


for method_name in ["fit", "fit_transform", "transform"]:
patch_feature_union_method(method_name)
1 change: 1 addition & 0 deletions python/cuml/cuml/accel/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ def debug(self, msg: str) -> None:

_PATCHES = {
"sklearn.pipeline",
"sklearn.compose",
"sklearn.utils",
"sklearn.utils._array_api",
"sklearn.utils.discovery",
Expand Down
7 changes: 5 additions & 2 deletions python/cuml/cuml/internals/outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import inspect

import numpy as np
from cupy.cuda import Stream

# TODO: Try to resolve circular import that makes this necessary:
from cuml.internals import input_utils as iu
Expand Down Expand Up @@ -212,7 +213,8 @@ def enter_internal_context():
gs._external_output_type = gs.output_type
gs.output_type = "mirror"
try:
yield True
with Stream.ptds:
yield True
finally:
gs.output_type = gs._external_output_type
gs._external_output_type = False
Expand Down Expand Up @@ -451,6 +453,7 @@ def inner(*args, **kwargs):
# We're internal, return as cuml
output_type = "cuml"

return coerce_arrays(res, output_type)
with enter_internal_context():
return coerce_arrays(res, output_type)

return inner
Loading
Loading