Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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 @@ -81,5 +81,5 @@ dependencies:
- statsmodels
- sysroot_linux-aarch64==2.28
- treelite>=4.6.1,<5.0.0
- umap-learn==0.5.7
- umap-learn>=0.5.7,<0.5.12
Comment thread
csadorf marked this conversation as resolved.
name: all_cuda-129_arch-aarch64
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 @@ -81,5 +81,5 @@ dependencies:
- statsmodels
- sysroot_linux-64==2.28
- treelite>=4.6.1,<5.0.0
- umap-learn==0.5.7
- umap-learn>=0.5.7,<0.5.12
name: all_cuda-129_arch-x86_64
2 changes: 1 addition & 1 deletion conda/environments/all_cuda-131_arch-aarch64.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,5 @@ dependencies:
- statsmodels
- sysroot_linux-aarch64==2.28
- treelite>=4.6.1,<5.0.0
- umap-learn==0.5.7
- umap-learn>=0.5.7,<0.5.12
name: all_cuda-131_arch-aarch64
2 changes: 1 addition & 1 deletion conda/environments/all_cuda-131_arch-x86_64.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,5 @@ dependencies:
- statsmodels
- sysroot_linux-64==2.28
- treelite>=4.6.1,<5.0.0
- umap-learn==0.5.7
- umap-learn>=0.5.7,<0.5.12
name: all_cuda-131_arch-x86_64
4 changes: 3 additions & 1 deletion dependencies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -455,9 +455,11 @@ dependencies:
- matrix: {dependencies: "oldest"}
packages:
- scikit-learn==1.5.0
- umap-learn==0.5.7
- matrix: {dependencies: "intermediate"}
packages:
- scikit-learn==1.7.2
- umap-learn==0.5.8
- ipython==8.10.0
- matrix: {dependencies: "nightly"}
packages:
Expand Down Expand Up @@ -512,7 +514,7 @@ dependencies:
- pytest-xdist
- seaborn
- statsmodels
- umap-learn==0.5.7
- umap-learn>=0.5.7,<0.5.12
- pynndescent
- output_types: conda
packages:
Expand Down
2 changes: 1 addition & 1 deletion docs/source/supported_versions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ The following dependencies are optional and provide additional functionality:

* **xgboost**: >=2.1.0 (for gradient boosting algorithms)
* **hdbscan**: >=0.8.39,<0.8.40 (for hierarchical density-based clustering)
* **umap-learn**: ==0.5.7 (for dimensionality reduction)
* **umap-learn**: >=0.5.7,<0.5.12 (for dimensionality reduction)
* **pynndescent**: (for approximate nearest neighbor search)

RAPIDS Dependencies
Expand Down
57 changes: 42 additions & 15 deletions python/cuml/cuml/accel/_overrides/umap.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,57 @@
# SPDX-License-Identifier: Apache-2.0
#

from packaging.version import Version

import cuml.manifold
from cuml.accel.estimator_proxy import ProxyBase

try:
import umap as _umap_module

_UMAP_LT_058 = Version(_umap_module.__version__) < Version("0.5.8")
except ImportError:
_UMAP_LT_058 = False

__all__ = ("UMAP",)


class UMAP(ProxyBase):
_gpu_class = cuml.manifold.UMAP

def _gpu_fit(self, X, y=None, force_all_finite=True, **kwargs):
# **kwargs is here for signature compatibility - umap.UMAP has them,
# but ignores all but the ones named here.
# TODO: cuml.UMAP currently doesn't handle non-finite inputs.
# force_alL_finite is in here for _signature_ compatibility
# with umap.UMAP, but we don't properly implement it (yet).
return self._gpu.fit(X, y=y)

def _gpu_fit_transform(self, X, y=None, force_all_finite=True, **kwargs):
# **kwargs is here for signature compatibility - umap.UMAP has them,
# but ignores all but the ones named here.
return self._gpu.fit_transform(X, y=y)

def _gpu_transform(self, X, force_all_finite=True):
return self._gpu.transform(X)
if _UMAP_LT_058:
# We support the old signature for backwards compatibility prior to umap-learn 0.5.8

def _gpu_fit(self, X, y=None, force_all_finite=True, **kwargs):
return self._gpu.fit(X, y=y)

def _gpu_fit_transform(
self, X, y=None, force_all_finite=True, **kwargs
):
return self._gpu.fit_transform(X, y=y)

def _gpu_transform(self, X, force_all_finite=True):
return self._gpu.transform(X)

else:

def _gpu_fit(self, X, y=None, ensure_all_finite=True, **kwargs):
# **kwargs is here for signature compatibility - umap.UMAP has them,
# but ignores all but the ones named here.
# TODO: cuml.UMAP currently doesn't handle non-finite inputs.
# ensure_all_finite is in here for _signature_ compatibility
# with umap.UMAP, but we don't properly implement it (yet).
return self._gpu.fit(X, y=y)

def _gpu_fit_transform(
self, X, y=None, ensure_all_finite=True, **kwargs
):
# **kwargs is here for signature compatibility - umap.UMAP has them,
# but ignores all but the ones named here.
return self._gpu.fit_transform(X, y=y)

def _gpu_transform(self, X, ensure_all_finite=True):
return self._gpu.transform(X)

def _gpu_inverse_transform(self, X):
return self._gpu.inverse_transform(X)
38 changes: 28 additions & 10 deletions python/cuml/cuml/accel/pytest_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,28 +43,46 @@ def pytest_addoption(parser):
)


def _evaluate_single_condition(condition_str: str) -> bool:
"""Evaluate a single package version condition.

Args:
condition_str: String in format 'package[comparison]version',
e.g. 'scikit-learn>=1.5.2'

Returns:
bool: True if the condition is met, False otherwise
"""
try:
req = Requirement(condition_str.strip())
installed_version = version(req.name)
return req.specifier.contains(installed_version, prereleases=True)
except Exception:
return False


def create_version_condition(condition_str: str) -> bool:
"""Evaluate a version condition immediately.

Supports a single package specifier or multiple specifiers joined by 'and',
in which case all clauses must be satisfied.

Args:
condition_str: String in format 'package[comparison]version'
For example:
condition_str: A version condition string. Examples:
- 'scikit-learn>=1.5.2'
- 'numpy<2.0.0'
- 'pandas==2.1.0'
- 'umap-learn<=0.5.8 and scikit-learn>=1.6'

Returns:
bool: True if the condition is met, False otherwise
bool: True if all conditions are met, False otherwise
"""
if not condition_str:
return True

try:
req = Requirement(condition_str)
installed_version = version(req.name)
return req.specifier.contains(installed_version, prereleases=True)
except Exception:
return False
return all(
_evaluate_single_condition(clause)
for clause in condition_str.split(" and ")
)


def pytest_collection_modifyitems(config, items):
Expand Down
18 changes: 2 additions & 16 deletions python/cuml/cuml_accel_tests/integration/test_umap.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,10 @@
#

import pytest
import sklearn
from packaging.version import Version
from sklearn.datasets import make_swiss_roll
from sklearn.manifold import trustworthiness
from umap import UMAP

if Version(sklearn.__version__) >= Version("1.8.0.dev0"):
pytest.skip("umap requires sklearn < 1.8.0.dev0", allow_module_level=True)

# Ignore FutureWarning from third-party umap-learn package calling
# sklearn.utils.validation.check_array with deprecated 'force_all_finite'
# parameter. This is not in cuml's control. Note: this will break when
# sklearn 1.8 removes the deprecated parameter entirely - umap-learn will
# need to be updated at that point.
# See also https://github.com/lmcinnes/umap/issues/1174
pytestmark = pytest.mark.filterwarnings(
"ignore:'force_all_finite' was renamed to 'ensure_all_finite':FutureWarning:sklearn"
)


@pytest.fixture(scope="module")
def manifold_data():
Expand Down Expand Up @@ -65,6 +50,7 @@ def test_umap_min_dist(manifold_data, min_dist):
"russellrao",
"kulsinski",
"dice",
# Require 2D data for these metrics
"wminkowski",
"mahalanobis",
"haversine",
Expand All @@ -82,7 +68,7 @@ def test_umap_min_dist(manifold_data, min_dist):
def test_umap_metric(manifold_data, metric):
X = manifold_data
# haversine only works for 2D data
if metric == "haversine":
if metric in ["haversine", "wminkowski", "mahalanobis"]:
X = X[:, :2]

umap = UMAP(metric=metric, random_state=42)
Expand Down
10 changes: 7 additions & 3 deletions python/cuml/cuml_accel_tests/test_core.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION.
# SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION.
# SPDX-License-Identifier: Apache-2.0
import importlib
Comment thread
coderabbitai[bot] marked this conversation as resolved.
import importlib.metadata
import multiprocessing
from inspect import Parameter, signature

import pytest
from packaging.version import Version

import cuml.accel
from cuml.accel.estimator_proxy import ProxyBase
Expand Down Expand Up @@ -80,17 +82,19 @@ def iter_proxy_class_methods():
if not name.startswith("_") and callable(
getattr(cls._cpu_class, name)
):
# XXX: xfail umap.UMAP.get_feature_names_out for now
# XXX: xfail umap.UMAP.get_feature_names_out for umap-learn < 0.5.8
if (
cls._cpu_class.__name__ == "UMAP"
and name == "get_feature_names_out"
and Version(importlib.metadata.version("umap-learn"))
< Version("0.5.8")
Comment thread
coderabbitai[bot] marked this conversation as resolved.
):
yield pytest.param(
cls,
name,
marks=[
pytest.mark.xfail(
reason="umap-learn <= 0.5.7 doesn't implement `get_feature_names_out` properly",
reason="umap-learn < 0.5.8 doesn't implement `get_feature_names_out` properly",
strict=True,
)
],
Expand Down
2 changes: 2 additions & 0 deletions python/cuml/cuml_accel_tests/upstream/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ When tests fail only on specific versions of a dependency, use the `--condition`

This ensures xfails only apply to the relevant versions, keeping the test suite accurate across different dependency combinations.

You can combine multiple version requirements in a condition using "and". For example: `umap-learn<=0.5.8 and scikit-learn>=1.6` will only xfail tests when both package constraints are met.

### Handling Unmatched Test IDs

The pytest plugin validates that all test IDs in the xfail list correspond to actual tests. When tests don't exist, a `UnmatchedXfailTests` warning is issued.
Expand Down
24 changes: 6 additions & 18 deletions python/cuml/cuml_accel_tests/upstream/umap/run-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,18 @@

set -eu

UMAP_TAG="release-0.5.7"

# Skip tests for scikit-learn >= 1.8 -- umap-learn is not compatible with scikit-learn 1.8 yet
python -c "
import sys
from packaging.version import Version
import sklearn
sys.exit(
int(
Version(sklearn.__version__) >= Version('1.8')
)
)
" || {
echo "Skipping umap tests for scikit-learn >= 1.8"
exit 0
}
UMAP_VERSION=$(python -c "import umap; print(umap.__version__)")
UMAP_TAG="release-${UMAP_VERSION}"
Comment thread
coderabbitai[bot] marked this conversation as resolved.

THIS_DIRECTORY=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )
UMAP_REPO="${THIS_DIRECTORY}/umap-upstream"

# Shallow clone the tag if not already cloned
# Clone if not already present, then check out the matching tag
if [ ! -d "$UMAP_REPO" ]; then
git clone --branch $UMAP_TAG --depth 1 "https://github.com/lmcinnes/umap.git" "$UMAP_REPO"
git clone "https://github.com/lmcinnes/umap.git" "$UMAP_REPO"
fi
git -C "$UMAP_REPO" fetch --tags
git -C "$UMAP_REPO" checkout "$UMAP_TAG"

# Run upstream tests
pytest -p cuml.accel \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,6 @@
- "umap.tests.test_umap_ops::test_umap_transform_embedding_stability"
- reason: Test fails with newer sklearn
marker: cuml_accel_sklearn_pin
condition: scikit-learn>=1.6
condition: umap-learn<=0.5.8 and scikit-learn>=1.6
tests:
- "umap.tests.test_umap_validation_params::test_umap_custom_distance_w_grad"
Comment thread
csadorf marked this conversation as resolved.
17 changes: 9 additions & 8 deletions python/cuml/cuml_accel_tests/upstream/xfail_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from typing import Any, Dict, List, Optional, Union

import yaml
from packaging.requirements import Requirement
from packaging.requirements import InvalidRequirement, Requirement


class QuoteTestID(str):
Expand Down Expand Up @@ -358,13 +358,14 @@ def validate_conditions(self) -> List[str]:
errors = []
for i, group in enumerate(self.groups):
if group.condition:
try:
Requirement(group.condition)
except Exception as e:
errors.append(
f"Group {i} has invalid condition "
f"'{group.condition}': {e}"
)
for clause in group.condition.split(" and "):
try:
Requirement(clause.strip())
except InvalidRequirement as e:
errors.append(
f"Group {i} has invalid condition "
f"'{group.condition}': {e}"
)
return errors

def _merge_identical_groups(self) -> int:
Expand Down
2 changes: 1 addition & 1 deletion python/cuml/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ test = [
"seaborn",
"skl2onnx",
"statsmodels",
"umap-learn==0.5.7",
"umap-learn>=0.5.7,<0.5.12",
"xgboost>=2.1.0",
] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit ../../dependencies.yaml and run `rapids-dependency-file-generator`.
dask = [
Expand Down
11 changes: 0 additions & 11 deletions python/cuml/tests/test_sklearn_import_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,17 +477,6 @@ def test_svc_multiclass_unsupported(random_state):

@pytest.mark.parametrize("sparse", [False, True])
@pytest.mark.parametrize("supervised", [False, True])
@pytest.mark.skipif(SKLEARN_18, reason="umap requires sklearn < 1.8.0")
# Ignore FutureWarning from third-party umap-learn package calling
# sklearn.utils.validation.check_array with deprecated 'force_all_finite'
# parameter. This is not in cuml's control. Note: this will break when
# sklearn 1.8 removes the deprecated parameter entirely - umap-learn will
# need to be updated at that point.
# See also https://github.com/lmcinnes/umap/issues/1174
@pytest.mark.filterwarnings(
"ignore:'force_all_finite' was renamed to "
"'ensure_all_finite':FutureWarning:sklearn"
)
def test_umap(random_state, sparse, supervised):
n_neighbors = 10
X, y = make_blobs(n_samples=200, random_state=random_state)
Expand Down
Loading
Loading