From dc2d2f7c761903a2bc4726a6be42cb70134e3c67 Mon Sep 17 00:00:00 2001 From: Jim Crist-Harif Date: Tue, 3 Feb 2026 19:26:27 -0600 Subject: [PATCH 1/4] Remove deprecated `cuml.internals.memory_utils` --- python/cuml/cuml/internals/__init__.py | 7 ++----- python/cuml/cuml/internals/memory_utils.py | 20 -------------------- python/cuml/tests/test_reflection.py | 11 ----------- 3 files changed, 2 insertions(+), 36 deletions(-) delete mode 100644 python/cuml/cuml/internals/memory_utils.py diff --git a/python/cuml/cuml/internals/__init__.py b/python/cuml/cuml/internals/__init__.py index 07b4cdc896..692250835f 100644 --- a/python/cuml/cuml/internals/__init__.py +++ b/python/cuml/cuml/internals/__init__.py @@ -1,9 +1,6 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 2019-2025, NVIDIA CORPORATION. +# SPDX-FileCopyrightText: Copyright (c) 2019-2026, NVIDIA CORPORATION. # SPDX-License-Identifier: Apache-2.0 -# -# TODO: remove in 26.04 -import cuml.internals.memory_utils + from cuml.internals.base import Base, get_handle from cuml.internals.internals import GraphBasedDimRedCallback from cuml.internals.outputs import ( diff --git a/python/cuml/cuml/internals/memory_utils.py b/python/cuml/cuml/internals/memory_utils.py deleted file mode 100644 index d8aa2f16d8..0000000000 --- a/python/cuml/cuml/internals/memory_utils.py +++ /dev/null @@ -1,20 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION. -# SPDX-License-Identifier: Apache-2.0 -# -def __getattr__(name): - import warnings - - if name in ("set_global_output_type", "using_output_type"): - warnings.warn( - f"Accessing {name!r} from the `cuml.internals.memory_utils` " - f"namespace is deprecated and will be removed in 26.04. Please " - f"use `cuml.{name}` instead.", - FutureWarning, - ) - import cuml.internals.outputs as mod - - return getattr(mod, name) - raise AttributeError( - f"module 'cuml.internals.memory_utils' has no attribute {name!r}" - ) diff --git a/python/cuml/tests/test_reflection.py b/python/cuml/tests/test_reflection.py index 06daf73669..9931e4e432 100644 --- a/python/cuml/tests/test_reflection.py +++ b/python/cuml/tests/test_reflection.py @@ -102,17 +102,6 @@ def returns_array_one_arg(n): return cp.ones(n) -def test_deprecated_memory_utils(): - for name in ["set_global_output_type", "using_output_type"]: - with pytest.warns(FutureWarning, match=name): - func = getattr(cuml.internals.memory_utils, name) - assert func is getattr(cuml, name) - - # Unknown attributes error - with pytest.raises(AttributeError, match="not_a_real_attr"): - cuml.internals.memory_utils.not_a_real_attr - - def test_set_global_output_type(): gs = GlobalSettings() assert gs.output_type is None From 2bff4bbd9622cd383ebecf993ebbcbe2d21a6801 Mon Sep 17 00:00:00 2001 From: Jim Crist-Harif Date: Tue, 3 Feb 2026 19:29:57 -0600 Subject: [PATCH 2/4] Remove deprecated TotalIters --- python/cuml/cuml/svm/svm_base.pyx | 42 ++----------------------------- python/cuml/tests/test_svm.py | 6 ----- 2 files changed, 2 insertions(+), 46 deletions(-) diff --git a/python/cuml/cuml/svm/svm_base.pyx b/python/cuml/cuml/svm/svm_base.pyx index 6603bd7108..d295ee52aa 100644 --- a/python/cuml/cuml/svm/svm_base.pyx +++ b/python/cuml/cuml/svm/svm_base.pyx @@ -1,8 +1,5 @@ # SPDX-FileCopyrightText: Copyright (c) 2019-2026, NVIDIA CORPORATION. # SPDX-License-Identifier: Apache-2.0 -# -import warnings - import cupy as cp import cupyx.scipy.sparse import numpy as np @@ -137,19 +134,6 @@ cdef class _SVMModel: return support, support_vectors, dual_coef, intercept -class TotalIters(int): - """Indicates the maximum number of total iterations the solver may run. - - .. deprecated:: 26.02 - - TotalIters was deprecated in 26.02 and will be removed in 26.04. - The `max_iter` parameter now always places a limit on total iterations, - wrapping with `TotalIters` is no longer necessary. - """ - def __repr__(self): - return f"TotalIters({int(self)})" - - class SVMBase(Base, InteropMixin, FMajorInputTagMixin, @@ -200,11 +184,6 @@ class SVMBase(Base, } def _params_to_cpu(self): - if isinstance(self.max_iter, TotalIters): - max_iter = int(self.max_iter) - else: - max_iter = self.max_iter - return { "kernel": self.kernel, "degree": self.degree, @@ -213,7 +192,7 @@ class SVMBase(Base, "tol": self.tol, "C": self.C, "cache_size": self.cache_size, - "max_iter": max_iter, + "max_iter": self.max_iter, "epsilon": self.epsilon, } @@ -418,21 +397,8 @@ class SVMBase(Base, param.verbosity = self._verbose_level param.epsilon = self.epsilon param.svmType = lib.SvmType.C_SVC if is_classifier else lib.SvmType.EPSILON_SVR - param.max_outer_iter = -1 - if isinstance(self.max_iter, TotalIters): - warnings.warn( - ( - "Passing `TotalIters` to `max_iter` was deprecated in 26.02 " - "and will be removed in 26.04. `max_iter` now always places a " - "limit on total iterations, please pass an integer directly " - "instead of wrapping with `TotalIters`." - ), - FutureWarning, - ) - param.max_iter = int(self.max_iter) - else: - param.max_iter = self.max_iter + param.max_iter = self.max_iter handle = get_handle(model=self) cdef handle_t* handle_ = handle.getHandle() @@ -679,7 +645,3 @@ class SVMBase(Base, handle.sync() return out - - -# Add TotalIters to the SVC/SVR class for easier access -SVMBase.TotalIters = TotalIters diff --git a/python/cuml/tests/test_svm.py b/python/cuml/tests/test_svm.py index 1290cd1c08..6d1bf3ab83 100644 --- a/python/cuml/tests/test_svm.py +++ b/python/cuml/tests/test_svm.py @@ -613,12 +613,6 @@ def test_max_iter_n_iter(classifier): model = cls(max_iter=5).fit(X, y) assert (model.n_iter_.item() if classifier else model.n_iter_) == 5 - # Using TotalIters results in the same behavior, but warns - model = cls(max_iter=cls.TotalIters(5)) - with pytest.warns(FutureWarning, match="TotalIters"): - model.fit(X, y) - assert (model.n_iter_.item() if classifier else model.n_iter_) == 5 - def test_svc_multiclass_n_iter(): X, y = make_classification(random_state=42, n_classes=3, n_informative=4) From 0a2c5276f4838005bcc3fd76b8ba7798f498d90a Mon Sep 17 00:00:00 2001 From: Jim Crist-Harif Date: Tue, 3 Feb 2026 19:37:06 -0600 Subject: [PATCH 3/4] Remove deprecations in train_test_split --- python/cuml/cuml/model_selection/_split.py | 65 ----------- python/cuml/tests/test_train_test_split.py | 129 --------------------- 2 files changed, 194 deletions(-) diff --git a/python/cuml/cuml/model_selection/_split.py b/python/cuml/cuml/model_selection/_split.py index 3b39d802e1..73762f41b1 100644 --- a/python/cuml/cuml/model_selection/_split.py +++ b/python/cuml/cuml/model_selection/_split.py @@ -3,7 +3,6 @@ # from __future__ import annotations -import warnings from abc import ABC, abstractmethod import cudf @@ -20,7 +19,6 @@ def train_test_split( *arrays, - y="deprecated", test_size=None, train_size=None, random_state=None, @@ -37,14 +35,6 @@ def train_test_split( arrays, numpy arrays, pandas DataFrames/Series, or any array-like objects with a shape attribute. - y : str, default="deprecated" - The name of the column that contains the target variable. - - .. deprecated:: 26.02 - The ``y`` parameter is deprecated and will be removed in 26.04. - Extract the column manually: - ``X, y = df.drop('col', axis=1), df['col']`` - test_size : float or int, default=None If float, should be between 0.0 and 1.0 and represent the proportion of the dataset to include in the test split. If int, represents the @@ -87,65 +77,10 @@ def train_test_split( >>> X_train, X_test, y_train, y_test = train_test_split( ... X, y, test_size=0.2, random_state=42 ... ) - - Notes - ----- - .. versionchanged:: 26.02 - The names and the order of the optional keyword arguments was changed to - match the scikit-learn equivalent function. The ``y`` parameter was - deprecated (see above). - - .. versionchanged:: 26.02 - Output types now consistently match input types. Previously, pandas - inputs were converted to cudf outputs. Now pandas inputs return pandas - outputs, cudf inputs return cudf outputs. """ if len(arrays) == 0: raise ValueError("At least one array required as input") - # Handle deprecated y parameter usage - # Case 1: y passed as keyword: train_test_split(df, y=...) - # Case 2: column name passed as second positional arg: train_test_split(df, "col") - y_is_column_name_positional = len(arrays) == 2 and isinstance( - arrays[1], str - ) - # Use isinstance check to avoid ambiguous truth value with array-like y - y_was_passed = not (isinstance(y, str) and y == "deprecated") - - if y_was_passed or y_is_column_name_positional: - warnings.warn( - "The explicit 'y' parameter is deprecated and will be " - "removed in 26.04. Extract the column manually: " - "X, y = df.drop('col', axis=1), df['col']", - FutureWarning, - stacklevel=2, - ) - - if y_is_column_name_positional: - # User passed: train_test_split(df, "colname") - X = arrays[0] - col_name = arrays[1] - X, y = X.drop(col_name, axis=1), X[col_name] - arrays = (X, y) - elif isinstance(y, str): - # User passed: train_test_split(df, y="colname") - X = arrays[0] - if not hasattr(X, "drop"): - raise TypeError( - "X must be a DataFrame when y is a column name string" - ) - X, y = X.drop(y, axis=1), X[y] - arrays = (X, y) - else: - # User passed: train_test_split(X, y=array) - if len(arrays) > 1: - raise ValueError( - "Cannot use deprecated 'y' parameter with multiple " - "positional arrays. Pass all arrays as positional " - "arguments instead: train_test_split(X, y, ...)" - ) - arrays = (arrays[0], y) - # Validate arrays have consistent first dimension n_samples = arrays[0].shape[0] for i, arr in enumerate(arrays[1:], 1): diff --git a/python/cuml/tests/test_train_test_split.py b/python/cuml/tests/test_train_test_split.py index 7ea23fb538..b1c20bab66 100644 --- a/python/cuml/tests/test_train_test_split.py +++ b/python/cuml/tests/test_train_test_split.py @@ -87,95 +87,6 @@ def test_split_dataframe_array(y_type): assert isinstance(y_test, cudf.Series) -def test_split_column(): - """Test deprecated y=str column extraction (suppress FutureWarning).""" - y = cudf.Series(([0] * (100 // 2)) + ([1] * (100 // 2))) - data = cudf.DataFrame( - { - "x": range(100), - "y": ([0] * (100 // 2)) + ([1] * (100 // 2)), - } - ) - train_size = 0.8 - - # No warning when passing a series for y - X_train, X_test, y_train, y_test = train_test_split( - data, y, train_size=train_size - ) - assert ( - len(X_train) == len(y_train) == pytest.approx(train_size * len(data)) - ) - assert ( - len(X_test) - == len(y_test) - == pytest.approx((1 - train_size) * len(data)) - ) - # Column "y" is not removed because we passed a series for y - assert "y" in X_train.columns - assert isinstance(y_train, cudf.Series) - - warning_message = "The explicit 'y' parameter is deprecated" - - # Pass a series for y using keyword argument - with pytest.warns(FutureWarning, match=warning_message): - X_train, X_test, y_train, y_test = train_test_split( - data, y=y, train_size=train_size - ) - assert ( - len(X_train) == len(y_train) == pytest.approx(train_size * len(data)) - ) - assert ( - len(X_test) - == len(y_test) - == pytest.approx((1 - train_size) * len(data)) - ) - # Column "y" is not removed because we passed a series for y - assert "y" in X_train.columns - assert isinstance(y_train, cudf.Series) - - # Pass a column name for y by position - with pytest.warns(FutureWarning, match=warning_message): - X_train, X_test, y_train, y_test = train_test_split( - data, "y", train_size=train_size - ) - assert ( - len(X_train) == len(y_train) == pytest.approx(train_size * len(data)) - ) - assert ( - len(X_test) - == len(y_test) - == pytest.approx((1 - train_size) * len(data)) - ) - # Column "y" is removed because we passed a column name for y - assert "y" not in X_train.columns - assert isinstance(y_train, cudf.Series) - - # Pass a column name for y using keyword argument - with pytest.warns(FutureWarning, match=warning_message): - X_train, X_test, y_train, y_test = train_test_split( - data, y="y", train_size=train_size - ) - assert ( - len(X_train) == len(y_train) == pytest.approx(train_size * len(data)) - ) - assert ( - len(X_test) - == len(y_test) - == pytest.approx((1 - train_size) * len(data)) - ) - # Column "y" is removed because we passed a column name for y - assert "y" not in X_train.columns - assert isinstance(y_train, cudf.Series) - - X_reconstructed = cudf.concat([X_train, X_test]).sort_values(by=["x"]) - y_reconstructed = cudf.concat([y_train, y_test]).sort_values() - - assert all( - data - == X_reconstructed.assign(y=y_reconstructed).reset_index(drop=True) - ) - - def test_split_size_mismatch(): X = cudf.DataFrame({"x": range(3)}) y = cudf.Series([0, 1]) @@ -659,46 +570,6 @@ def test_integer_sizes(): assert X_test.shape[0] == 30 -def test_y_string_column_deprecation_warning(): - """Test that using y as column name string emits deprecation warning.""" - import warnings - - df = cudf.DataFrame({"x": range(100), "y": [0, 1] * 50}) - - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - X_train, X_test, y_train, y_test = train_test_split( - df, "y", train_size=0.8 - ) - # Check that a FutureWarning was raised - assert len(w) >= 1 - assert any( - issubclass(warning.category, FutureWarning) for warning in w - ) - assert any( - "deprecated" in str(warning.message).lower() for warning in w - ) - - -def test_y_string_column_still_works(): - """Test that y=str still works despite deprecation.""" - import warnings - - df = cudf.DataFrame({"x": range(100), "target": [0, 1] * 50}) - - with warnings.catch_warnings(): - warnings.simplefilter("ignore", FutureWarning) - X_train, X_test, y_train, y_test = train_test_split( - df, "target", train_size=0.8 - ) - - # Verify it still works correctly - assert len(X_train) == 80 - assert len(y_train) == 80 - assert "target" not in X_train.columns - assert "x" in X_train.columns - - def test_single_array_split(): """Test splitting a single array without y.""" X = cp.random.rand(100, 10) From beb6829310d20d01fdb5486da060af94dcf7cc14 Mon Sep 17 00:00:00 2001 From: Jim Crist-Harif Date: Tue, 3 Feb 2026 19:46:26 -0600 Subject: [PATCH 4/4] Remove deprecations in UMAP --- python/cuml/cuml/manifold/umap/umap.pyx | 42 ++++++++----------------- 1 file changed, 13 insertions(+), 29 deletions(-) diff --git a/python/cuml/cuml/manifold/umap/umap.pyx b/python/cuml/cuml/manifold/umap/umap.pyx index 8916c64b0e..ebd1f4ac87 100644 --- a/python/cuml/cuml/manifold/umap/umap.pyx +++ b/python/cuml/cuml/manifold/umap/umap.pyx @@ -560,25 +560,8 @@ cdef init_params(self, lib.UMAPParams ¶ms, n_rows, is_sparse=False, is_fit=T ) build_kwds = self.build_kwds or {} - if "nnd_n_clusters" in build_kwds: - warnings.warn( - "`nnd_n_clusters` was deprecated in 26.02 and will be changed to " - "`knn_n_clusters` in 26.04." - ) - n_clusters = build_kwds.get("nnd_n_clusters", 1) - else: - n_clusters = build_kwds.get("knn_n_clusters", 1) - if "nnd_overlap_factor" in build_kwds: - warnings.warn( - "`nnd_overlap_factor` was deprecated in 26.02 and will be changed to " - "`knn_overlap_factor` in 26.04." - ) - overlap_factor = build_kwds.get("nnd_overlap_factor", 2) - else: - overlap_factor = build_kwds.get("knn_overlap_factor", 2) - - params.build_params.n_clusters = n_clusters - params.build_params.overlap_factor = overlap_factor + n_clusters = build_kwds.get("knn_n_clusters", 1) + overlap_factor = build_kwds.get("knn_overlap_factor", 2) if n_clusters < 1: raise ValueError(f"Expected `knn_n_clusters >= 1`, got {n_clusters}") @@ -588,12 +571,14 @@ cdef init_params(self, lib.UMAPParams ¶ms, n_rows, is_sparse=False, is_fit=T f"knn_overlap_factor ({overlap_factor})`" ) - # Supported metrics: L2Expanded, L2SqrtExpanded, CosineExpanded, InnerProduct - all_neighbors_supported_metrics = ['l2', 'euclidean', 'sqeuclidean', 'cosine', - 'inner_product'] - if (build_algo == "brute_force_knn" and - n_clusters > 1 and - self.metric.lower() not in all_neighbors_supported_metrics): + all_neighbors_supported_metrics = [ + 'l2', 'euclidean', 'sqeuclidean', 'cosine', 'inner_product' + ] + if ( + build_algo == "brute_force_knn" and + n_clusters > 1 and + self.metric.lower() not in all_neighbors_supported_metrics + ): warnings.warn( f"metric='{self.metric}' is not supported for batched knn build with " f"knn_n_clusters > 1. Supported metrics are: {all_neighbors_supported_metrics}. " @@ -601,6 +586,9 @@ cdef init_params(self, lib.UMAPParams ¶ms, n_rows, is_sparse=False, is_fit=T f"(without batching) will be used instead." ) + params.build_params.n_clusters = n_clusters + params.build_params.overlap_factor = overlap_factor + if build_algo == "brute_force_knn": params.build_algo = lib.graph_build_algo.BRUTE_FORCE_KNN else: @@ -835,10 +823,6 @@ class UMAP(Base, InteropMixin, CMajorInputTagMixin, SparseInputTagMixin): memory usage. This is independent from knn_overlap_factor as long as 'knn_overlap_factor' < 'knn_n_clusters'. - .. deprecated:: 26.02 - The `nnd_n_clusters` and `nnd_overlap_factor` was deprecated in 26.02 and - will be changed to `knn_n_clusters` and `knn_overlap_factor` in 26.04. - device_ids : list[int], "all", or None, default=None The device IDs to use during fitting (only used when `build_algo=nn_descent` and `knn_n_clusters > 1`). May be a list of