From 1efbf486e59fd538f827110f405a5ea4b4e294c4 Mon Sep 17 00:00:00 2001 From: adityaanikam Date: 2026年8月13日 23:02:41 +0530 Subject: [PATCH 1/7] Add cuml.accel support for sklearn.ensemble.IsolationForest --- docs/source/cuml-accel/compatibility.rst | 19 +++ .../cuml/accel/_overrides/sklearn/ensemble.py | 59 ++++++++- .../test_sklearn_isolation_forest.py | 118 ++++++++++++++++++ 3 files changed, 194 insertions(+), 2 deletions(-) create mode 100644 python/cuml/cuml_accel_tests/integration/test_sklearn_isolation_forest.py diff --git a/docs/source/cuml-accel/compatibility.rst b/docs/source/cuml-accel/compatibility.rst index ed3c267ea7..a9ad7502e1 100644 --- a/docs/source/cuml-accel/compatibility.rst +++ b/docs/source/cuml-accel/compatibility.rst @@ -225,6 +225,25 @@ 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 ``offset_``, ``max_samples_``, + ``estimators_``, ``estimators_features_``, or ``estimators_samples_`` + on a GPU-fitted model raises an ``AttributeError`` explaining this, + rather than the value silently differing from scikit-learn's. + + sklearn.kernel_ridge ~~~~~~~~~~~~~~~~~~~~ diff --git a/python/cuml/cuml/accel/_overrides/sklearn/ensemble.py b/python/cuml/cuml/accel/_overrides/sklearn/ensemble.py index 67a6f7abfb..4b84ebc469 100644 --- a/python/cuml/cuml/accel/_overrides/sklearn/ensemble.py +++ b/python/cuml/cuml/accel/_overrides/sklearn/ensemble.py @@ -5,10 +5,10 @@ import cuml.ensemble from cuml.accel.estimator_proxy import ProxyBase -from cuml.internals.interop import UnsupportedOnGPU +from cuml.internals.interop import UnsupportedOnCPU, UnsupportedOnGPU from cuml.internals.validation import check_array -__all__ = ("RandomForestRegressor", "RandomForestClassifier") +__all__ = ("RandomForestRegressor", "RandomForestClassifier", "IsolationForest") class _RandomForestMixin: @@ -88,3 +88,58 @@ def __iter__(self): def __getitem__(self, index): return self._call_method("__getitem__", index) + + +class IsolationForest(ProxyBase): + _gpu_class = cuml.ensemble.IsolationForest + # Conversion of a fitted cuML IsolationForest to CPU is not yet + # supported (tracked in #8420). These attributes stay inaccessible + # until that lands, rather than crashing on any *_ access. + _not_implemented_attributes = frozenset( + ( + "offset_", + "max_samples_", + "estimators_", + "estimators_features_", + "estimators_samples_", + ) + ) + + def _sync_attrs_to_cpu(self) -> None: + try: + super()._sync_attrs_to_cpu() + except UnsupportedOnCPU: + self._synced = True + + @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) diff --git a/python/cuml/cuml_accel_tests/integration/test_sklearn_isolation_forest.py b/python/cuml/cuml_accel_tests/integration/test_sklearn_isolation_forest.py new file mode 100644 index 0000000000..1f174096e9 --- /dev/null +++ b/python/cuml/cuml_accel_tests/integration/test_sklearn_isolation_forest.py @@ -0,0 +1,118 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +import pickle + +import numpy as np +import pytest +import scipy.sparse +from sklearn.datasets import make_blobs +from sklearn.ensemble import IsolationForest + +from cuml.accel import is_proxy +from cuml.ensemble import IsolationForest as CumlIsolationForest + +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_is_a_proxy(): + assert is_proxy(IsolationForest) + assert IsolationForest._cpu_class is CPUIsolationForest + assert ( + IsolationForest._gpu_class._cpu_class_path + == "sklearn.ensemble.IsolationForest" + ) + assert CumlIsolationForest._cpu_class_path == ( + "sklearn.ensemble.IsolationForest" + ) + + +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_decision_function_and_score_samples( + blobs_with_outliers, +): + X = blobs_with_outliers + result = IsolationForest(n_estimators=100, random_state=0).fit(X) + assert result._gpu is not None + + scores = result.score_samples(X) + decision = result.decision_function(X) + assert scores.shape == decision.shape == (len(X),) + # decision_function is score_samples shifted by a constant offset. + np.testing.assert_allclose( + decision, scores - (scores - decision)[0], atol=1e-4 + ) + + +def test_isolation_forest_not_implemented_attributes_give_friendly_error( + blobs_with_outliers, +): + result = IsolationForest(n_estimators=50, random_state=0).fit( + blobs_with_outliers + ) + assert result._gpu is not None + + with pytest.raises(AttributeError, match="not yet implemented"): + result.offset_ + + with pytest.raises(AttributeError, match="not yet implemented"): + result.estimators_ + + +def test_isolation_forest_pickle_after_gpu_fit_does_not_crash( + blobs_with_outliers, +): + result = IsolationForest(n_estimators=50, random_state=0).fit( + blobs_with_outliers + ) + assert result._gpu is not None + + # Must not raise UnsupportedOnCPU: pickling falls back to whatever + # the CPU estimator has synced, the not-implemented attributes are + # simply absent from the unpickled copy. + restored = pickle.loads(pickle.dumps(result)) + assert type(restored) is CPUIsolationForest + + +def test_isolation_forest_falls_back_on_nan_input(blobs_with_outliers): + X = blobs_with_outliers.copy() + X[0, 0] = np.nan + + result = IsolationForest(n_estimators=50, random_state=0).fit(X) + assert result._gpu is None + assert type(result._cpu) is CPUIsolationForest + + +def test_isolation_forest_falls_back_on_sparse_input(blobs_with_outliers): + sparse_X = scipy.sparse.csr_matrix(blobs_with_outliers) + + result = IsolationForest(n_estimators=50, random_state=0).fit(sparse_X) + assert result._gpu is None + assert type(result._cpu) is CPUIsolationForest \ No newline at end of file From 0d3d714d28e790a07a012b6d77c64d9876312919 Mon Sep 17 00:00:00 2001 From: adityaanikam Date: 2026年8月14日 13:47:21 +0530 Subject: [PATCH 2/7] Address review: fail loudly instead of silently on CPU conversion, trim redundant tests --- docs/source/cuml-accel/compatibility.rst | 9 +- .../cuml/accel/_overrides/sklearn/ensemble.py | 20 +-- .../integration/test_isolation_forest.py | 53 ++++++++ .../test_sklearn_isolation_forest.py | 118 ------------------ 4 files changed, 59 insertions(+), 141 deletions(-) create mode 100644 python/cuml/cuml_accel_tests/integration/test_isolation_forest.py delete mode 100644 python/cuml/cuml_accel_tests/integration/test_sklearn_isolation_forest.py diff --git a/docs/source/cuml-accel/compatibility.rst b/docs/source/cuml-accel/compatibility.rst index a9ad7502e1..41052a8fd8 100644 --- a/docs/source/cuml-accel/compatibility.rst +++ b/docs/source/cuml-accel/compatibility.rst @@ -238,10 +238,11 @@ To compare results between estimators, we recommend comparing scores like Additional notes: - Conversion of a fitted GPU ``IsolationForest`` back to a CPU estimator - is not yet supported. Accessing ``offset_``, ``max_samples_``, - ``estimators_``, ``estimators_features_``, or ``estimators_samples_`` - on a GPU-fitted model raises an ``AttributeError`` explaining this, - rather than the value silently differing from scikit-learn's. + 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 diff --git a/python/cuml/cuml/accel/_overrides/sklearn/ensemble.py b/python/cuml/cuml/accel/_overrides/sklearn/ensemble.py index 4b84ebc469..0b0530c4bb 100644 --- a/python/cuml/cuml/accel/_overrides/sklearn/ensemble.py +++ b/python/cuml/cuml/accel/_overrides/sklearn/ensemble.py @@ -5,7 +5,7 @@ import cuml.ensemble from cuml.accel.estimator_proxy import ProxyBase -from cuml.internals.interop import UnsupportedOnCPU, UnsupportedOnGPU +from cuml.internals.interop import UnsupportedOnGPU from cuml.internals.validation import check_array __all__ = ("RandomForestRegressor", "RandomForestClassifier", "IsolationForest") @@ -92,24 +92,6 @@ def __getitem__(self, index): class IsolationForest(ProxyBase): _gpu_class = cuml.ensemble.IsolationForest - # Conversion of a fitted cuML IsolationForest to CPU is not yet - # supported (tracked in #8420). These attributes stay inaccessible - # until that lands, rather than crashing on any *_ access. - _not_implemented_attributes = frozenset( - ( - "offset_", - "max_samples_", - "estimators_", - "estimators_features_", - "estimators_samples_", - ) - ) - - def _sync_attrs_to_cpu(self) -> None: - try: - super()._sync_attrs_to_cpu() - except UnsupportedOnCPU: - self._synced = True @staticmethod def _validate_input(X): diff --git a/python/cuml/cuml_accel_tests/integration/test_isolation_forest.py b/python/cuml/cuml_accel_tests/integration/test_isolation_forest.py new file mode 100644 index 0000000000..9c55dbadce --- /dev/null +++ b/python/cuml/cuml_accel_tests/integration/test_isolation_forest.py @@ -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_ \ No newline at end of file diff --git a/python/cuml/cuml_accel_tests/integration/test_sklearn_isolation_forest.py b/python/cuml/cuml_accel_tests/integration/test_sklearn_isolation_forest.py deleted file mode 100644 index 1f174096e9..0000000000 --- a/python/cuml/cuml_accel_tests/integration/test_sklearn_isolation_forest.py +++ /dev/null @@ -1,118 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -import pickle - -import numpy as np -import pytest -import scipy.sparse -from sklearn.datasets import make_blobs -from sklearn.ensemble import IsolationForest - -from cuml.accel import is_proxy -from cuml.ensemble import IsolationForest as CumlIsolationForest - -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_is_a_proxy(): - assert is_proxy(IsolationForest) - assert IsolationForest._cpu_class is CPUIsolationForest - assert ( - IsolationForest._gpu_class._cpu_class_path - == "sklearn.ensemble.IsolationForest" - ) - assert CumlIsolationForest._cpu_class_path == ( - "sklearn.ensemble.IsolationForest" - ) - - -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_decision_function_and_score_samples( - blobs_with_outliers, -): - X = blobs_with_outliers - result = IsolationForest(n_estimators=100, random_state=0).fit(X) - assert result._gpu is not None - - scores = result.score_samples(X) - decision = result.decision_function(X) - assert scores.shape == decision.shape == (len(X),) - # decision_function is score_samples shifted by a constant offset. - np.testing.assert_allclose( - decision, scores - (scores - decision)[0], atol=1e-4 - ) - - -def test_isolation_forest_not_implemented_attributes_give_friendly_error( - blobs_with_outliers, -): - result = IsolationForest(n_estimators=50, random_state=0).fit( - blobs_with_outliers - ) - assert result._gpu is not None - - with pytest.raises(AttributeError, match="not yet implemented"): - result.offset_ - - with pytest.raises(AttributeError, match="not yet implemented"): - result.estimators_ - - -def test_isolation_forest_pickle_after_gpu_fit_does_not_crash( - blobs_with_outliers, -): - result = IsolationForest(n_estimators=50, random_state=0).fit( - blobs_with_outliers - ) - assert result._gpu is not None - - # Must not raise UnsupportedOnCPU: pickling falls back to whatever - # the CPU estimator has synced, the not-implemented attributes are - # simply absent from the unpickled copy. - restored = pickle.loads(pickle.dumps(result)) - assert type(restored) is CPUIsolationForest - - -def test_isolation_forest_falls_back_on_nan_input(blobs_with_outliers): - X = blobs_with_outliers.copy() - X[0, 0] = np.nan - - result = IsolationForest(n_estimators=50, random_state=0).fit(X) - assert result._gpu is None - assert type(result._cpu) is CPUIsolationForest - - -def test_isolation_forest_falls_back_on_sparse_input(blobs_with_outliers): - sparse_X = scipy.sparse.csr_matrix(blobs_with_outliers) - - result = IsolationForest(n_estimators=50, random_state=0).fit(sparse_X) - assert result._gpu is None - assert type(result._cpu) is CPUIsolationForest \ No newline at end of file From e326371612981420106f2b2ad46303165c96d243 Mon Sep 17 00:00:00 2001 From: Simon Adorf Date: 2026年8月14日 11:15:50 -0500 Subject: [PATCH 3/7] Fix IsolationForest proxy test lint --- .../cuml/cuml_accel_tests/integration/test_isolation_forest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/cuml/cuml_accel_tests/integration/test_isolation_forest.py b/python/cuml/cuml_accel_tests/integration/test_isolation_forest.py index 9c55dbadce..06e2f8de6b 100644 --- a/python/cuml/cuml_accel_tests/integration/test_isolation_forest.py +++ b/python/cuml/cuml_accel_tests/integration/test_isolation_forest.py @@ -50,4 +50,4 @@ def test_isolation_forest_gpu_fit_attrs_raise_until_conversion_supported( assert result._gpu is not None with pytest.raises(ValueError, match="not supported"): - result.offset_ \ No newline at end of file + _ = result.offset_ From 909c968706ce299d962b63bb8df6d43cf26eb13c Mon Sep 17 00:00:00 2001 From: Simon Adorf Date: 2026年8月14日 11:17:11 -0500 Subject: [PATCH 4/7] Apply lint fixes to ensemble overrides --- python/cuml/cuml/accel/_overrides/sklearn/ensemble.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/python/cuml/cuml/accel/_overrides/sklearn/ensemble.py b/python/cuml/cuml/accel/_overrides/sklearn/ensemble.py index 0b0530c4bb..a9a024ef13 100644 --- a/python/cuml/cuml/accel/_overrides/sklearn/ensemble.py +++ b/python/cuml/cuml/accel/_overrides/sklearn/ensemble.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION. +# SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # @@ -8,7 +8,11 @@ from cuml.internals.interop import UnsupportedOnGPU from cuml.internals.validation import check_array -__all__ = ("RandomForestRegressor", "RandomForestClassifier", "IsolationForest") +__all__ = ( + "RandomForestRegressor", + "RandomForestClassifier", + "IsolationForest", +) class _RandomForestMixin: From f31863c9505fb7d79429c6d67cc6336e1d4e1c20 Mon Sep 17 00:00:00 2001 From: adityaanikam Date: 2026年8月19日 15:06:53 +0530 Subject: [PATCH 5/7] Update conversion test and docs now that #8483 landed --- docs/source/cuml-accel/compatibility.rst | 10 ++++---- .../integration/test_isolation_forest.py | 25 +++++++++++-------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/docs/source/cuml-accel/compatibility.rst b/docs/source/cuml-accel/compatibility.rst index b6e771630f..6fc54ceae6 100644 --- a/docs/source/cuml-accel/compatibility.rst +++ b/docs/source/cuml-accel/compatibility.rst @@ -238,11 +238,11 @@ To compare results between estimators, we recommend comparing scores like 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. + is supported. Accessing fit attributes (``offset_``, ``max_samples_``, + ``estimators_``, and others) or pickling a GPU-fitted model triggers + this conversion automatically. + - ``estimators_samples_`` is not available on the converted model, + since cuML does not record per-tree sample indices. sklearn.kernel_ridge diff --git a/python/cuml/cuml_accel_tests/integration/test_isolation_forest.py b/python/cuml/cuml_accel_tests/integration/test_isolation_forest.py index 06e2f8de6b..b33394f507 100644 --- a/python/cuml/cuml_accel_tests/integration/test_isolation_forest.py +++ b/python/cuml/cuml_accel_tests/integration/test_isolation_forest.py @@ -37,17 +37,22 @@ def test_isolation_forest_fit_predict_agreement(blobs_with_outliers): assert np.mean(expected_labels == result_labels)>= 0.9 -def test_isolation_forest_gpu_fit_attrs_raise_until_conversion_supported( +def test_isolation_forest_gpu_fit_attrs_available_after_conversion( 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 - ) + # Regression test: conversion of a fitted GPU IsolationForest back to a + # CPU estimator is now supported (landed in #8483, tracked by #8420). + # Accessing fit attributes on a GPU-fitted proxy must trigger that + # conversion and expose the real synced values instead of raising. + X = blobs_with_outliers + result = IsolationForest(n_estimators=50, random_state=0).fit(X) assert result._gpu is not None - with pytest.raises(ValueError, match="not supported"): - _ = result.offset_ + gpu_scores = result.decision_function(X) # dispatched to GPU + + assert len(result.estimators_) == 50 + assert result.offset_ == pytest.approx(float(result._gpu.offset_)) + + np.testing.assert_allclose( + result._cpu.decision_function(X), gpu_scores, atol=1e-5 + ) From 6dcc1af575fb6ce1f521d5e470ee2c7a82ce906e Mon Sep 17 00:00:00 2001 From: adityaanikam Date: 2026年8月27日 16:06:10 +0530 Subject: [PATCH 6/7] FIX Do not forward sample_weight to IsolationForest GPU fit cuml.ensemble.IsolationForest.fit() and fit_predict() have no sample_weight parameter, so forwarding it unconditionally raised TypeError on GPU even when the caller passed sample_weight=None, breaking every accelerated fit. Raise UnsupportedOnGPU when sample_weight is not None so those calls fall back to the CPU estimator instead of crashing, and drop the keyword when calling the GPU methods otherwise. Added regression coverage for both fit() and fit_predict(). Also trims the inline comment on the conversion-attrs regression test per review. --- .../cuml/accel/_overrides/sklearn/ensemble.py | 8 ++++-- .../integration/test_isolation_forest.py | 28 +++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/python/cuml/cuml/accel/_overrides/sklearn/ensemble.py b/python/cuml/cuml/accel/_overrides/sklearn/ensemble.py index a9a024ef13..1e7f9d7f96 100644 --- a/python/cuml/cuml/accel/_overrides/sklearn/ensemble.py +++ b/python/cuml/cuml/accel/_overrides/sklearn/ensemble.py @@ -112,11 +112,15 @@ def _validate_input(X): 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) + if sample_weight is not None: + raise UnsupportedOnGPU("sample_weight is not supported") + return self._gpu.fit(X, y=y) 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) + if sample_weight is not None: + raise UnsupportedOnGPU("sample_weight is not supported") + return self._gpu.fit_predict(X, y=y) def _gpu_predict(self, X): self._validate_input(X) diff --git a/python/cuml/cuml_accel_tests/integration/test_isolation_forest.py b/python/cuml/cuml_accel_tests/integration/test_isolation_forest.py index b33394f507..d3fb95b067 100644 --- a/python/cuml/cuml_accel_tests/integration/test_isolation_forest.py +++ b/python/cuml/cuml_accel_tests/integration/test_isolation_forest.py @@ -37,6 +37,34 @@ def test_isolation_forest_fit_predict_agreement(blobs_with_outliers): assert np.mean(expected_labels == result_labels)>= 0.9 +def test_isolation_forest_fit_sample_weight_falls_back_to_cpu( + blobs_with_outliers, +): + # cuml.ensemble.IsolationForest.fit() has no sample_weight parameter, + # so a non-None sample_weight cannot be honored on GPU. + X = blobs_with_outliers + sample_weight = np.ones(len(X)) + + result = IsolationForest(n_estimators=10, random_state=0).fit( + X, sample_weight=sample_weight + ) + + assert result._gpu is None + + +def test_isolation_forest_fit_predict_sample_weight_falls_back_to_cpu( + blobs_with_outliers, +): + # Same as above, for fit_predict(). + X = blobs_with_outliers + sample_weight = np.ones(len(X)) + + result = IsolationForest(n_estimators=10, random_state=0) + result.fit_predict(X, sample_weight=sample_weight) + + assert result._gpu is None + + def test_isolation_forest_gpu_fit_attrs_available_after_conversion( blobs_with_outliers, ): From b0c2d2a66ac542b728b97b897d672a4f10ad6794 Mon Sep 17 00:00:00 2001 From: adityaanikam Date: 2026年8月27日 22:10:07 +0530 Subject: [PATCH 7/7] Match CPU fit_predict signature in IsolationForest GPU override --- python/cuml/cuml/accel/_overrides/sklearn/ensemble.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/python/cuml/cuml/accel/_overrides/sklearn/ensemble.py b/python/cuml/cuml/accel/_overrides/sklearn/ensemble.py index 1e7f9d7f96..231e8ac898 100644 --- a/python/cuml/cuml/accel/_overrides/sklearn/ensemble.py +++ b/python/cuml/cuml/accel/_overrides/sklearn/ensemble.py @@ -116,9 +116,14 @@ def _gpu_fit(self, X, y=None, sample_weight=None): raise UnsupportedOnGPU("sample_weight is not supported") return self._gpu.fit(X, y=y) - def _gpu_fit_predict(self, X, y=None, sample_weight=None): + def _gpu_fit_predict(self, X, y=None, **kwargs): + # IsolationForest.fit_predict() doesn't declare sample_weight itself; + # it inherits OutlierMixin.fit_predict(self, X, y=None, **kwargs), + # which forwards kwargs straight to fit(). Match that signature here + # (rather than declaring sample_weight explicitly) so the proxy stays + # signature-compatible with the CPU method. self._validate_input(X) - if sample_weight is not None: + if kwargs.get("sample_weight") is not None: raise UnsupportedOnGPU("sample_weight is not supported") return self._gpu.fit_predict(X, y=y)

AltStyle によって変換されたページ (->オリジナル) /