Skip to content

Forward-merge release/26.04 into main - #7894

Merged
jameslamb merged 4 commits into
NVIDIA:mainfrom
csadorf:main-merge-release/26.04
Mar 13, 2026
Merged

Forward-merge release/26.04 into main#7894
jameslamb merged 4 commits into
NVIDIA:mainfrom
csadorf:main-merge-release/26.04

Conversation

@csadorf

@csadorf csadorf commented Mar 13, 2026

Copy link
Copy Markdown
Contributor

No description provided.

jcrist and others added 3 commits March 13, 2026 19:58
`sklearn` requires that X is 2-dimensional, and errors nicely otherwise.

`cuml` doesn't _uniformly_ require that X is 2-dimensional. Some estimators error on non-2D-X, but they usually do so accidentally, not as part of the input validation code. This leads to a bunch of xfailed tests in our sklearn compatibility test suite (both in the `cuml.accel` upstream tests, as well as in `test_sklearn_compatibility.py`).

This PR:

- Adds a deprecation warning to all estimators when `X` is non-2-dimensional. In 26.06 we'll remove the deprecation warning and error instead.
- If `cuml.accel` is enabled, this warning is an error matching the sklearn error message instead. This lets us un-xfail a bunch of tests right now.
- Updates our test suite to not trigger the warning, ensuring we're always passing in 2-dimensional X in tests. This was most common for `TargetEncoder`, only a few other locations needed it. Since it was so common for `TargetEncoder`, I added a deprecation test there as well to check that everything still worked on 1D inputs.
- Updates `reflect` to support `reset="type"`, for setting the type on fit-like functions alone (and not `n_features_in_`/`feature_names_in_`). This was needed for 2 "transformers" sklearn (and cuml) supports that are meant to operate on `y` instead of `X` (`LabelEncoder` and `LabelBinarizer`). These estimators operate on `y` alone and shouldn't support `n_features_in_`/`feature_names_in_`. They also shouldn't validate that the array input is 2 dimensional, since `y` can be 1D. This is a stop-gap solution as we refactor our validation functions - in the long run we might remove `reset` entirely from `reflect` and instead move setting the input type to the validation functions (with the `reflect` decorator only remaining for coercing outputs).

As per our deprecation policy, I've marked this PR as "breaking" since it adds a new deprecation warning around non-2-dimensional X. All prior working code should continue to work, users providing 1D X should just see a warning.

Authors:
  - Jim Crist-Harif (https://github.com/jcrist)

Approvers:
  - Simon Adorf (https://github.com/csadorf)

URL: NVIDIA#7889
@csadorf
csadorf requested a review from a team as a code owner March 13, 2026 21:00
@csadorf
csadorf requested a review from jcrist March 13, 2026 21:00
@github-actions github-actions Bot added the Cython / Python Cython or Python issue label Mar 13, 2026
else:
warnings.warn(
"Support for passing non-2-dimensional X was deprecated in 26.04 "
"and will be removed in 26.06. In cuml 26.06 this will error "

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gah, looks like another version string snuck in. I think this will fix it:

Suggested change
"and will be removed in 26.06. In cuml 26.06 this will error "
"and will be removed in 26.06. In cuml version 26.06 this will error "

reiterating that I find this linter a bit more annoying than it's worth.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine to fix this right here in a forward-merger PR, let's do it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gah, that didn't appease the regex gods.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just pulled this branch down to work on this. Pushed 92757ff with a fix that worked for me (pre-commit run --all-files) locally.

Force-pushed over the inline one here so we only add 1 more commit to main for this.

@jameslamb jameslamb added improvement Improvement / enhancement to an existing function non-breaking Non-breaking change labels Mar 13, 2026
@coderabbitai

coderabbitai Bot commented Mar 13, 2026

Copy link
Copy Markdown
📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Stricter validation for non-2D inputs with clearer errors and a deprecation warning path.
    • Updated decorator reset semantics: reset may be bool or "type", with runtime validation of values.
  • Documentation

    • TargetEncoder example updated to show single-column 2D inputs.
  • Tests

    • Added/adjusted tests for 2D input expectations and no-features behavior; removed several outdated xfail markers.

Walkthrough

reflect(reset) now accepts bool or the string "type"; _get_n_features treats non-2D inputs specially (3D -> error; 1D -> FutureWarning + fallback), many tests/docs updated to prefer 2D inputs, and numerous upstream xfail entries removed.

Changes

Cohort / File(s) Summary
Core output & validation
python/cuml/cuml/internals/outputs.py, python/cuml/cuml/internals/validation.py
reflect(reset) extended to accept True/False or "type" with runtime validation and altered internal handling; _get_n_features now distinguishes ndim, raises for 3D or when accel enabled, and warns+falls back for 1D.
Decorator tweak
python/cuml/cuml/preprocessing/label.py
Changed LabelBinarizer.fit decorator from @reflect(reset=True) to @reflect(reset="type").
Docstring example
python/cuml/cuml/preprocessing/TargetEncoder.py
Example usage updated to show 2D (one-column DataFrame) inputs instead of 1D Series.
Test input-shape updates
python/cuml/tests/test_coordinate_descent.py, python/cuml/tests/test_dbscan.py, python/cuml/tests/test_tsne.py
Tests adjusted to supply 2D inputs (e.g., column vectors or shape (1,0) empty arrays) to align with stricter 2D validation.
Feature-infrastructure tests
python/cuml/tests/test_label_binarizer.py, python/cuml/tests/test_label_encoder.py
Added tests asserting LabelBinarizer/LabelEncoder do not get n_features_in_.
TargetEncoder tests
python/cuml/tests/test_target_encoder.py
Refactored to pass 2D inputs, added FutureWarning expectations for non-2D inputs, and updated assertions accordingly.
Validation tests
python/cuml/tests/test_validation.py
Added tests: 3D input raises ValueError and parametrized 1D inputs produce FutureWarning and fallback to 1 feature.
Upstream xfail list
python/cuml/cuml_accel_tests/upstream/scikit-learn/xfail-list.yaml
Removed many xfail entries across estimator/test suites; updated TSNE xfail reason/marker.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • csadorf
  • dantegd
🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.93% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive No description was provided by the author, making it impossible to assess whether the description relates to the changeset. Add a pull request description explaining the purpose and scope of this forward-merge to help reviewers understand the changes being integrated.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main objective: a forward-merge of the release/26.04 branch into main, which aligns with the changeset's comprehensive updates across multiple modules.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@python/cuml/cuml/preprocessing/TargetEncoder.py`:
- Around line 177-178: Update the TargetEncoder class docstrings for the public
methods fit, fit_transform, and transform to reflect that X must be 2D (e.g., a
single-column DataFrame or 2D array) rather than a 1D Series; locate the
parameter docs for X in TargetEncoder.fit, TargetEncoder.fit_transform, and
TargetEncoder.transform and change the type/description to mention DataFrame/2D
array inputs (and mark Series/1D X as deprecated if the project uses a
deprecation policy), add an explicit scikit-learn compatibility note stating 2D
inputs are required, and ensure the example in the class-level doc and these
method docstrings matches the 1-column DataFrame usage shown in the example
block.

In `@python/cuml/tests/test_target_encoder.py`:
- Line 36: Update the typo in the test comment: change the comment string "#
Warns in tarnsform" to "# Warns in transform" in
python/cuml/tests/test_target_encoder.py (look for that exact comment near the
top of the test file) to satisfy the codespell guideline.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fa06e6b5-da1b-4a5a-8b90-5aa7764386a6

📥 Commits

Reviewing files that changed from the base of the PR and between 6d31aba and 9afada5.

📒 Files selected for processing (12)
  • python/cuml/cuml/internals/outputs.py
  • python/cuml/cuml/internals/validation.py
  • python/cuml/cuml/preprocessing/TargetEncoder.py
  • python/cuml/cuml/preprocessing/label.py
  • python/cuml/cuml_accel_tests/upstream/scikit-learn/xfail-list.yaml
  • python/cuml/tests/test_coordinate_descent.py
  • python/cuml/tests/test_dbscan.py
  • python/cuml/tests/test_label_binarizer.py
  • python/cuml/tests/test_label_encoder.py
  • python/cuml/tests/test_target_encoder.py
  • python/cuml/tests/test_tsne.py
  • python/cuml/tests/test_validation.py

Comment on lines +84 to +115
ndim = len(shape)

if ndim != 2:
import cuml.accel

if isinstance(X, (cudf.Series, pd.Series)):
msg = (
f"Expected a 2-dimensional container but got {type(X).__name__} "
"instead. Pass a DataFrame containing a single row (i.e. "
"single sample) or a single column (i.e. single feature) "
"instead."
)
else:
kind = "scalar" if ndim == 0 else f"{ndim}D"
msg = (
f"Expected 2D array, got {kind} array instead. Reshape your data "
"using array.reshape(-1, 1) if your data has a single feature, "
"or array.reshape(1, -1) if it contains a single sample."
)

if cuml.accel.enabled() or ndim > 2:
raise ValueError(msg)
else:
warnings.warn(
"Support for passing non-2-dimensional X was deprecated in 26.04 "
"and will be removed in 26.06. In cuml 26.06 this will error "
f"with the following message:\n\n{msg}",
FutureWarning,
)
# Fallback to 1 feature until the deprecation is completed
return 1
return shape[1]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

3D Python sequences still bypass this new ndim check.

This branch only runs after shape inference, but _get_n_features() still returns early for nested list/tuple inputs above. A value like [[[1], [2]]] currently reports 2 features instead of raising for non-2D input, so the stricter validation is still incomplete for one of the supported raw input forms.

As per coding guidelines, python/**/**/cuml/**/*.py: Missing input dimension checks (n_samples, n_features) and not handling edge cases (empty datasets, single sample) must be addressed in Python estimators.

🧰 Tools
🪛 Ruff (0.15.5)

[warning] 107-107: No explicit stacklevel keyword argument found

Set stacklevel=2

(B028)

Comment on lines +177 to +178
>>> train_encoded = encoder.fit_transform(train[["category"]], train.label)
>>> test_encoded = encoder.transform(test[["category"]])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Finish the doc cleanup for 2D X inputs.

The example now correctly uses a 1-column DataFrame, but the fit, fit_transform, and transform parameter docs below still advertise Series/1D X inputs. That now contradicts the new deprecation path and will steer users into warnings.

As per coding guidelines, python/**/**/cuml/**/*.py: Missing docstrings for public methods, undocumented hyperparameters, or missing scikit-learn compatibility notes in documentation must be addressed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/cuml/cuml/preprocessing/TargetEncoder.py` around lines 177 - 178,
Update the TargetEncoder class docstrings for the public methods fit,
fit_transform, and transform to reflect that X must be 2D (e.g., a single-column
DataFrame or 2D array) rather than a 1D Series; locate the parameter docs for X
in TargetEncoder.fit, TargetEncoder.fit_transform, and TargetEncoder.transform
and change the type/description to mention DataFrame/2D array inputs (and mark
Series/1D X as deprecated if the project uses a deprecation policy), add an
explicit scikit-learn compatibility note stating 2D inputs are required, and
ensure the example in the class-level doc and these method docstrings matches
the 1-column DataFrame usage shown in the example block.

with pytest.warns(FutureWarning, match="non-2-dimensional X"):
encoder.fit(df.category, df.label)

# Warns in tarnsform

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix typo in test comment (tarnsformtransform).

✏️ Suggested patch
-    # Warns in tarnsform
+    # Warns in transform

As per coding guidelines, "Check for spelling mistakes using codespell".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Warns in tarnsform
# Warns in transform
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/cuml/tests/test_target_encoder.py` at line 36, Update the typo in the
test comment: change the comment string "# Warns in tarnsform" to "# Warns in
transform" in python/cuml/tests/test_target_encoder.py (look for that exact
comment near the top of the test file) to satisfy the codespell guideline.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
python/cuml/cuml/internals/validation.py (1)

84-105: ⚠️ Potential issue | 🟠 Major

Nested 3D Python sequences still bypass this new ndim guard

The new ndim != 2 path won’t run for nested list/tuple inputs because the fast-path returns at Line 71 first. Example: [[[1], [2]]] still reports 2 features instead of raising on >2D.

💡 Proposed fix
@@
     if isinstance(X, (list, tuple)):
         if len(X) == 0:
             return 0
         row = X[0]
@@
         if not isinstance(row, (str, bytes, dict)):
             try:
-                return len(row)
+                row_len = len(row)
             except Exception:
                 pass
+            else:
+                # If row elements are themselves sequence-like, defer to shape-based
+                # ndim handling below so >2D inputs can be rejected.
+                if row_len > 0:
+                    first = row[0]
+                    if not isinstance(first, (str, bytes, dict)):
+                        try:
+                            len(first)
+                        except Exception:
+                            return row_len
+                    else:
+                        return row_len
+                else:
+                    return row_len

As per coding guidelines, python/**/**/cuml/**/*.py: Missing input dimension checks (n_samples, n_features) and not handling edge cases (empty datasets, single sample) must be addressed in Python estimators.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@python/cuml/cuml/internals/validation.py`:
- Around line 107-112: The FutureWarning emitted in validation.py (the
warnings.warn(...) call that warns about non-2-dimensional X deprecation) should
include a stacklevel so the warning points at the user's callsite; update that
warnings.warn invocation to pass stacklevel=2 (or an appropriate value to reach
the external caller) while keeping the message and category unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 11d89197-ffe4-4346-af15-98ce2727c801

📥 Commits

Reviewing files that changed from the base of the PR and between 9afada5 and 9a13181.

📒 Files selected for processing (1)
  • python/cuml/cuml/internals/validation.py

Comment on lines +107 to +112
warnings.warn(
"Support for passing non-2-dimensional X was deprecated in 26.04 "
"and will be removed in 26.06. In cuml version 26.06 this will error "
f"with the following message:\n\n{msg}",
FutureWarning,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify warnings.warn callsites and whether stacklevel is set in this file.
rg -n "warnings\.warn\(" python/cuml/cuml/internals/validation.py -A5 -B1

Repository: rapidsai/cuml

Length of output: 1277


Set warning stacklevel so deprecation points to caller

Line 107 emits warnings.warn(...) without stacklevel, so users get an internal location instead of their callsite (also matches Ruff B028).

💡 Proposed fix
         else:
             warnings.warn(
                 "Support for passing non-2-dimensional X was deprecated in 26.04 "
                 "and will be removed in 26.06. In cuml version 26.06 this will error "
                 f"with the following message:\n\n{msg}",
                 FutureWarning,
+                stacklevel=2,
             )
🧰 Tools
🪛 Ruff (0.15.5)

[warning] 107-107: No explicit stacklevel keyword argument found

Set stacklevel=2

(B028)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/cuml/cuml/internals/validation.py` around lines 107 - 112, The
FutureWarning emitted in validation.py (the warnings.warn(...) call that warns
about non-2-dimensional X deprecation) should include a stacklevel so the
warning points at the user's callsite; update that warnings.warn invocation to
pass stacklevel=2 (or an appropriate value to reach the external caller) while
keeping the message and category unchanged.

@jameslamb
jameslamb force-pushed the main-merge-release/26.04 branch from 9a13181 to 92757ff Compare March 13, 2026 21:18

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
python/cuml/cuml/internals/validation.py (2)

107-112: ⚠️ Potential issue | 🟡 Minor

Add stacklevel to this FutureWarning

Line 107 emits warnings.warn(...) without stacklevel, so warning attribution points to internals instead of caller code.

#!/bin/bash
# Verify warnings.warn callsites and whether stacklevel is present
rg -n "warnings\.warn\(" python/cuml/cuml/internals/validation.py -A6 -B1
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/cuml/cuml/internals/validation.py` around lines 107 - 112, The
FutureWarning emitted via warnings.warn in validation.py is missing a
stacklevel, causing the warning to point to internal code; update the
warnings.warn call (the one that emits the deprecation about non-2-dimensional
X) to include an appropriate stacklevel (e.g., stacklevel=2) so the warning
attributes to the caller site instead of internals; keep the existing message
and warning class (FutureWarning) and only add the stacklevel argument to that
warnings.warn invocation.

60-73: ⚠️ Potential issue | 🟠 Major

Nested Python 3D inputs still bypass the new non-2D guard

Because of the early return at Line 71, inputs like [[[1], [2]]] can return a feature count instead of reaching the ndim > 2 error path. This leaves non-2D validation incomplete for list/tuple inputs.

Suggested fix
 def _get_n_features(X):
     if isinstance(X, (list, tuple)):
         if len(X) == 0:
             return 0
         row = X[0]
@@
         if not isinstance(row, (str, bytes, dict)):
             try:
-                return len(row)
+                row_len = len(row)
             except Exception:
                 pass
+            else:
+                # Don't bypass ndim validation for nested sequences (>2D)
+                if row_len > 0 and not isinstance(row[0], (str, bytes, dict)):
+                    try:
+                        len(row[0])
+                    except Exception:
+                        return row_len
+                else:
+                    return row_len

As per coding guidelines, python/**/**/cuml/**/*.py: “Missing input dimension checks (n_samples, n_features) and not handling edge cases (empty datasets, single sample) must be addressed in Python estimators”.

Also applies to: 84-105

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/cuml/cuml/internals/validation.py` around lines 60 - 73, The early
return inside the list/tuple branch returns len(row) for inputs like X =
[[[1],[2]]] and thus bypasses the ndim>2 guard; update the branch handling X in
validation.py (the block that inspects X, row and returns len(row)) to detect
nested sequence elements (e.g., row elements that are themselves
list/tuple/ndarray-like) and, if those nested sequences exist, raise or route to
the existing non-2D error path instead of returning a feature count; ensure you
still handle empty X and non-sequence row types (str/bytes/dict) the same way so
other callers of this logic (the X/row check) remain correct.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@python/cuml/cuml/internals/validation.py`:
- Around line 107-112: The FutureWarning emitted via warnings.warn in
validation.py is missing a stacklevel, causing the warning to point to internal
code; update the warnings.warn call (the one that emits the deprecation about
non-2-dimensional X) to include an appropriate stacklevel (e.g., stacklevel=2)
so the warning attributes to the caller site instead of internals; keep the
existing message and warning class (FutureWarning) and only add the stacklevel
argument to that warnings.warn invocation.
- Around line 60-73: The early return inside the list/tuple branch returns
len(row) for inputs like X = [[[1],[2]]] and thus bypasses the ndim>2 guard;
update the branch handling X in validation.py (the block that inspects X, row
and returns len(row)) to detect nested sequence elements (e.g., row elements
that are themselves list/tuple/ndarray-like) and, if those nested sequences
exist, raise or route to the existing non-2D error path instead of returning a
feature count; ensure you still handle empty X and non-sequence row types
(str/bytes/dict) the same way so other callers of this logic (the X/row check)
remain correct.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cd770466-4c05-42f9-b391-d3ed3deb9408

📥 Commits

Reviewing files that changed from the base of the PR and between 9a13181 and 92757ff.

📒 Files selected for processing (1)
  • python/cuml/cuml/internals/validation.py

@jameslamb

Copy link
Copy Markdown
Member

Admin-merging this to save CI resources and so the automatic forward-merger PR doesn't pick up any more commits.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Cython / Python Cython or Python issue improvement Improvement / enhancement to an existing function non-breaking Non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants