Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
20 changes: 20 additions & 0 deletions docs/source/cuml-accel/compatibility.rst
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,26 @@ To compare results between estimators, we recommend comparing scores like
- If ``y`` is a multi-output target.


.. dropdown:: ``IsolationForest``
:name: isolationforest

``IsolationForest`` will fall back to CPU in the following cases:

- If ``warm_start=True``.
- If ``sample_weight`` is passed to ``fit`` or ``fit_predict``.
- If ``X`` is sparse.
- If ``X`` contains missing or non-finite values.

Additional notes:

- Conversion of a fitted GPU ``IsolationForest`` back to a CPU estimator
is not yet supported. Accessing fit attributes (``offset_``,
``max_samples_``, ``estimators_``, and others) or pickling a
GPU-fitted model raises a clear ``ValueError`` explaining this,
rather than silently returning results from an unfitted CPU
estimator.


sklearn.kernel_ridge
~~~~~~~~~~~~~~~~~~~~

Expand Down
39 changes: 38 additions & 1 deletion python/cuml/cuml/accel/_overrides/sklearn/ensemble.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from cuml.internals.interop import UnsupportedOnGPU
from cuml.internals.validation import check_array

__all__ = ("RandomForestRegressor", "RandomForestClassifier")
__all__ = ("RandomForestRegressor", "RandomForestClassifier", "IsolationForest")


class _RandomForestMixin:
Expand Down Expand Up @@ -88,3 +88,40 @@ def __iter__(self):

def __getitem__(self, index):
return self._call_method("__getitem__", index)


class IsolationForest(ProxyBase):
_gpu_class = cuml.ensemble.IsolationForest

@staticmethod
def _validate_input(X):
# cuML's IsolationForest requires dense, finite input and raises
# ValueError (NaN/inf) or TypeError (sparse) otherwise. Convert
# those into UnsupportedOnGPU so callers fall back to CPU instead
# of crashing.
try:
check_array(
X, mem_type=None, order=None, ensure_2d=False, input_name="X"
)
except (ValueError, TypeError) as exc:
raise UnsupportedOnGPU(str(exc)) from None

def _gpu_fit(self, X, y=None, sample_weight=None):
self._validate_input(X)
return self._gpu.fit(X, y=y, sample_weight=sample_weight)

def _gpu_fit_predict(self, X, y=None, sample_weight=None):
self._validate_input(X)
return self._gpu.fit_predict(X, y=y, sample_weight=sample_weight)

def _gpu_predict(self, X):
self._validate_input(X)
return self._gpu.predict(X)

def _gpu_decision_function(self, X):
self._validate_input(X)
return self._gpu.decision_function(X)

def _gpu_score_samples(self, X):
self._validate_input(X)
return self._gpu.score_samples(X)
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

import numpy as np
import pytest
from sklearn.datasets import make_blobs
from sklearn.ensemble import IsolationForest

CPUIsolationForest = IsolationForest._cpu_class


@pytest.fixture(scope="module")
def blobs_with_outliers():
X, _ = make_blobs(
n_samples=200,
centers=1,
cluster_std=0.5,
random_state=42,
)
rng = np.random.RandomState(42)
outliers = rng.uniform(low=-10, high=10, size=(20, X.shape[1]))
return np.vstack([X, outliers])


def test_isolation_forest_fit_predict_agreement(blobs_with_outliers):
X = blobs_with_outliers
params = {"n_estimators": 100, "random_state": 0}

expected = CPUIsolationForest(**params).fit(X)
result = IsolationForest(**params).fit(X)

assert result._gpu is not None

expected_labels = expected.predict(X)
result_labels = result.predict(X)
assert set(np.unique(result_labels)) <= {-1, 1}
assert np.mean(expected_labels == result_labels) >= 0.9


def test_isolation_forest_gpu_fit_attrs_raise_until_conversion_supported(
blobs_with_outliers,
):
# Conversion of a fitted cuML IsolationForest back to a CPU estimator
# is not yet supported (tracked in #8420). Accessing fit attributes or
# pickling a GPU-fitted proxy must raise clearly rather than silently
# operating on an unfitted CPU estimator.
result = IsolationForest(n_estimators=50, random_state=0).fit(
blobs_with_outliers
)
assert result._gpu is not None

with pytest.raises(ValueError, match="not supported"):
result.offset_
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
Loading