Skip to content

A few validation fixups - #8066

Merged
rapids-bot[bot] merged 6 commits into
NVIDIA:mainfrom
jcrist:validation-fixups
May 8, 2026
Merged

A few validation fixups#8066
rapids-bot[bot] merged 6 commits into
NVIDIA:mainfrom
jcrist:validation-fixups

Conversation

@jcrist

@jcrist jcrist commented May 7, 2026

Copy link
Copy Markdown
Member

Removes some lingering old input_to_*/CumlArray/SparseCumlArray calls in some modules that were already addressed. These just slipped through the cracks in review.

Fixes #7997.

@jcrist jcrist self-assigned this May 7, 2026
@jcrist
jcrist requested a review from a team as a code owner May 7, 2026 16:45
@jcrist
jcrist requested a review from divyegala May 7, 2026 16:45
@jcrist jcrist added improvement Improvement / enhancement to an existing function non-breaking Non-breaking change sklearn-api-compat Issues around cuml matching sklearn API conventions/standards labels May 7, 2026
@github-actions github-actions Bot added the Cython / Python Cython or Python issue label May 7, 2026


@reflect
def spectral_embedding(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Functions like this should be based on the estimator (not the estimator based on them). This eases validation and avoids double processing.

I only moved the definition down after the class for readability (since the function depends on the class, not other way around).

@pytest.mark.filterwarnings("ignore:The number of bins.*:UserWarning")
@pytest.mark.filterwarnings("ignore::pytest.PytestUnraisableExceptionWarning")
def test_sklearn_compatible_estimator(estimator, check):
# Check that all estimators pass the "common estimator" checks

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

With this we no longer have any skipped tests, no more known crashes!

@coderabbitai

coderabbitai Bot commented May 7, 2026

Copy link
Copy Markdown

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 3ae60405-a8f7-41a8-8834-41ed6345414b

📥 Commits

Reviewing files that changed from the base of the PR and between 74c7253 and ddcaa78.

📒 Files selected for processing (2)
  • python/cuml/cuml/manifold/umap/umap.pyx
  • python/cuml/tests/test_sklearn_compatibility.py
✅ Files skipped from review due to trivial changes (1)
  • python/cuml/cuml/manifold/umap/umap.pyx

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes & Improvements

    • Clearer error messages for non-finite inputs in Spectral Embedding (NaN vs Inf)
    • Spectral Embedding rejects non-square precomputed affinity matrices
    • UMAP: tighter validation for custom init shapes with clearer errors; inverse-transform now returns GPU-backed embeddings
    • Naive Bayes: stricter validation of prior probabilities
    • General modernization of input validation across estimators for more robust behavior
  • Tests

    • Updated compatibility and unit tests to reflect stricter validation and new error messages

Walkthrough

This PR centralizes input validation using check_inputs/check_array across SpectralEmbedding, UMAP, and GaussianNB; moves affinity preprocessing into SpectralEmbedding.fit with a thin module-level wrapper; updates pointer extraction and return wrappers; and tightens related tests' error-message assertions.

Changes

SpectralEmbedding Refactoring and Function Wrapping

Layer / File(s) Summary
Setup and Import Cleanup
python/cuml/cuml/manifold/spectral_embedding.pyx
Top-level NumPy/SciPy and legacy input conversion imports removed; preprocessing moved into the estimator.
Estimator Internal Flags
python/cuml/cuml/manifold/spectral_embedding.pyx
Add private _drop_first and _norm_laplacian attributes set by the wrapper.
Estimator fit Method with Affinity Handling
python/cuml/cuml/manifold/spectral_embedding.pyx
fit uses check_inputs (float32, conditional sparse), branches on affinity: "nearest_neighbors" uses dense pointer; "precomputed" coerces to float32 COO, enforces square input, removes diagonal entries, and builds (data,row,col,nnz) vector views for C++ transform.
Wrapper Function Implementation
python/cuml/cuml/manifold/spectral_embedding.pyx
Top-level spectral_embedding(...) now constructs SpectralEmbedding, sets _drop_first/_norm_laplacian, and delegates to fit_transform.
Tests
python/cuml/tests/test_spectral_embedding.py
Non-finite message matching is value-specific; added test asserting non-square precomputed affinity raises expected ValueError.

UMAP Input Validation Modernization

Layer / File(s) Summary
Import Update for Validation
python/cuml/cuml/manifold/umap/umap.pyx
Replace input_to_cuml_array usage with check_array/check_inputs-based validation and keep is_array_like.
Sparse Array Conversion Pattern
python/cuml/cuml/manifold/umap/umap.pyx
Sparse _attrs_from_cpu uses check_array(..., accept_sparse='csr', dtype='float32') before wrapping with SparseCumlArray.
Custom init Array Validation in fit
python/cuml/cuml/manifold/umap/umap.pyx
fit validates X upfront with check_inputs(mem_type=None, return_index=True, reset=True), coerces with check_array only when needed; init validated with check_array(mem_type=None) and shape enforced; embeddings_buffer reset using validated pointer and nbytes.
fuzzy_simplicial_set Input Validation
python/cuml/cuml/manifold/umap/umap.pyx
X, knn_indices, and knn_dists validated via check_array with explicit dtypes (float32/int64) and C order; dims derived from validated arrays and .data.ptr used for pointers.
simplicial_set_embedding Input Validation
python/cuml/cuml/manifold/umap/umap.pyx
data and array-like init validated with check_array(..., dtype='float32', return_index=True); pointers use .data.ptr; function returns CumlArray(data=embedding, index=index).
Return Type Changes and inverse_transform
python/cuml/cuml/manifold/umap/umap.pyx
simplicial_set_embedding and inverse_transform now return CumlArray wrappers (no forced order='C').
Tests & sklearn compatibility
python/cuml/tests/test_umap.py, python/cuml/tests/test_sklearn_compatibility.py
Tighten init-shape error expectations; remove special-case skip for UMAP in sklearn compatibility tests so full checks run.

GaussianNB Priors Validation Enhancement

Layer / File(s) Summary
Priors Array Validation
python/cuml/cuml/naive_bayes/naive_bayes.py
GaussianNB._partial_fit validates self.priors via check_array (dtype matching X), enforces length equals number of classes, sum ≈ 1.0, and non-negativity before assigning to self.class_prior_.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • rapidsai/cuml#8042: Both PRs modify python/cuml/cuml/manifold/spectral_embedding.pyx to move/introduce validation via check_inputs into SpectralEmbedding.fit and adjust wrapper behavior.
  • rapidsai/cuml#7984: Related refactor moving affinity preprocessing/validation into estimator.fit and converting top-level functions to thin wrappers.

Suggested reviewers

  • divyegala
  • csadorf
🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (1 warning, 2 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is vague and generic, using non-descriptive terms like 'fixups' that do not convey specific information about the primary changes in the changeset. Consider a more specific title that describes the main refactoring, such as 'Refactor spectral_embedding to use new input validation' or 'Remove legacy input_to_* calls from manifold modules'.
Out of Scope Changes check ❓ Inconclusive Changes to test files and UMAP validation updates align with the modernization objectives, though UMAP updates go slightly beyond the manifold-specific scope mentioned in #7997. Clarify whether UMAP validation modernization was intended as part of this PR or should be separated, as the linked issue focuses on cuml.manifold updates.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The description accurately identifies the main objective: removing lingering legacy validation utility calls from modules where those were already partially addressed.
Linked Issues check ✅ Passed The PR addresses the stated objective of updating cuml.manifold to new input validation by removing legacy input_to_* calls and refactoring spectral_embedding to use check_inputs.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
python/cuml/cuml/manifold/umap/umap.pyx (1)

1884-1957: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Same memory type concern applies here.

Similar to fuzzy_simplicial_set, this function calls check_array without mem_type (lines 1884, 1926) and then accesses .data.ptr (lines 1955-1956). This will fail if numpy arrays are returned. Apply the same fix as recommended above.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/cuml/cuml/manifold/umap/umap.pyx` around lines 1884 - 1957, The
check_array calls that produce X and embedding must request device (CuPy) memory
so subsequent .data.ptr access is valid; update the two check_array invocations
(the one that assigns X, index and the one that validates init/creates
embedding) to include mem_type="device" (or the project's equivalent device
mem_type constant) so they return cupy arrays, then keep using
embedding.data.ptr and X.data.ptr for RaftCOO.from_cupy_coo and downstream code.
python/cuml/cuml/manifold/spectral_embedding.pyx (1)

229-237: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove ensure_min_features=2 to match scikit-learn validation.

scikit-learn's SpectralEmbedding.fit() calls _validate_data() with only ensure_min_samples=2 and no feature minimum, allowing single-feature inputs (n_features=1). The nearest_neighbors affinity uses NearestNeighbors which supports 1D data, and rbf affinity works with single features. cuML's ensure_min_features=2 rejects valid inputs that sklearn accepts, violating sklearn parity. Change to ensure_min_features=1.

Suggested change
         X = check_inputs(
             self,
             X,
             dtype="float32",
             order="C",
             accept_sparse="coo" if self.affinity == "precomputed" else False,
             ensure_min_samples=2,
-            ensure_min_features=2,
+            ensure_min_features=1,
             reset=True,
         )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/cuml/cuml/manifold/spectral_embedding.pyx` around lines 229 - 237, The
SpectralEmbedding.fit input validation currently forces ensure_min_features=2 in
the check_inputs call, which rejects valid single-feature inputs; update the
check_inputs invocation in SpectralEmbedding.fit (the call named check_inputs)
to remove ensure_min_features=2 or change it to ensure_min_features=1 so it
matches scikit-learn behavior and allows n_features=1 (keep other args like
ensure_min_samples=2 unchanged).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@python/cuml/cuml/manifold/spectral_embedding.pyx`:
- Around line 247-274: In the precomputed affinity branch of
spectral_embedding.pyx (the block handling affinity == "precomputed"), add a
check that the input sparse matrix X is square (X.shape[0] == X.shape[1]) before
extracting COO indices (affinity_rows, affinity_cols, affinity_data) and
computing affinity_nnz; if not square raise a clear ValueError explaining that
precomputed affinity must be n_samples × n_samples so the downstream
transform(...) overload that expects row/col/value vectors and n_samples cannot
receive invalid column indices. Update the control flow so the diagonal-removal
and pointer extraction (affinity_data_ptr, affinity_rows_ptr, affinity_cols_ptr)
happen only after the square check, and add a small regression test that passes
a rectangular precomputed matrix to spectral_embedding to assert it raises the
new ValueError.

---

Outside diff comments:
In `@python/cuml/cuml/manifold/spectral_embedding.pyx`:
- Around line 229-237: The SpectralEmbedding.fit input validation currently
forces ensure_min_features=2 in the check_inputs call, which rejects valid
single-feature inputs; update the check_inputs invocation in
SpectralEmbedding.fit (the call named check_inputs) to remove
ensure_min_features=2 or change it to ensure_min_features=1 so it matches
scikit-learn behavior and allows n_features=1 (keep other args like
ensure_min_samples=2 unchanged).

In `@python/cuml/cuml/manifold/umap/umap.pyx`:
- Around line 1884-1957: The check_array calls that produce X and embedding must
request device (CuPy) memory so subsequent .data.ptr access is valid; update the
two check_array invocations (the one that assigns X, index and the one that
validates init/creates embedding) to include mem_type="device" (or the project's
equivalent device mem_type constant) so they return cupy arrays, then keep using
embedding.data.ptr and X.data.ptr for RaftCOO.from_cupy_coo and downstream code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 095ceb19-1119-4e25-8c28-dc0d7a18580b

📥 Commits

Reviewing files that changed from the base of the PR and between b0a5270 and 9bf6390.

📒 Files selected for processing (6)
  • python/cuml/cuml/manifold/spectral_embedding.pyx
  • python/cuml/cuml/manifold/umap/umap.pyx
  • python/cuml/cuml/naive_bayes/naive_bayes.py
  • python/cuml/tests/test_sklearn_compatibility.py
  • python/cuml/tests/test_spectral_embedding.py
  • python/cuml/tests/test_umap.py
💤 Files with no reviewable changes (1)
  • python/cuml/tests/test_sklearn_compatibility.py

Comment thread python/cuml/cuml/manifold/spectral_embedding.pyx
@jcrist
jcrist force-pushed the validation-fixups branch from dd41ee8 to 74c7253 Compare May 7, 2026 22:43

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
python/cuml/cuml/manifold/spectral_embedding.pyx (1)

229-236: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Change ensure_min_features=2 to ensure_min_features=1 to allow single-feature inputs.

scikit-learn's SpectralEmbedding with affinity="nearest_neighbors" accepts input arrays of shape (n_samples, 1). The current validation requirement of 2 features rejects valid inputs and introduces a regression from sklearn-compatible behavior.

Suggested fix
         X = check_inputs(
             self,
             X,
             dtype="float32",
             order="C",
             accept_sparse="coo" if self.affinity == "precomputed" else False,
             ensure_min_samples=2,
-            ensure_min_features=2,
+            ensure_min_features=1,
             reset=True,
         )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/cuml/cuml/manifold/spectral_embedding.pyx` around lines 229 - 236, The
input validation in spectral_embedding uses check_inputs with
ensure_min_features=2 which incorrectly rejects single-feature arrays; update
the call in spectral_embedding (the check_inputs invocation inside
spectral_embedding.pyx) to use ensure_min_features=1 so inputs of shape
(n_samples, 1) are accepted, preserving sklearn-compatible behavior for
affinity="nearest_neighbors".
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@python/cuml/cuml/manifold/spectral_embedding.pyx`:
- Around line 229-236: The input validation in spectral_embedding uses
check_inputs with ensure_min_features=2 which incorrectly rejects single-feature
arrays; update the call in spectral_embedding (the check_inputs invocation
inside spectral_embedding.pyx) to use ensure_min_features=1 so inputs of shape
(n_samples, 1) are accepted, preserving sklearn-compatible behavior for
affinity="nearest_neighbors".

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 8a770bab-edfb-4295-a29e-61f9e9be055e

📥 Commits

Reviewing files that changed from the base of the PR and between dd41ee8 and 74c7253.

📒 Files selected for processing (6)
  • python/cuml/cuml/manifold/spectral_embedding.pyx
  • python/cuml/cuml/manifold/umap/umap.pyx
  • python/cuml/cuml/naive_bayes/naive_bayes.py
  • python/cuml/tests/test_sklearn_compatibility.py
  • python/cuml/tests/test_spectral_embedding.py
  • python/cuml/tests/test_umap.py
💤 Files with no reviewable changes (1)
  • python/cuml/tests/test_sklearn_compatibility.py
✅ Files skipped from review due to trivial changes (1)
  • python/cuml/cuml/naive_bayes/naive_bayes.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • python/cuml/cuml/manifold/umap/umap.pyx

@jcrist
jcrist force-pushed the validation-fixups branch from 74c7253 to ddcaa78 Compare May 7, 2026 22:58
if len(X.shape) != 2:
raise ValueError("Reshape your data: data should be two dimensional")
# Normalize X as cheaply as possible to minimize copies and work
X, index = check_inputs(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We never want to touch X (if we can avoid it) before it's validated.

The logic here is updated to do a cheap pre-validation pass of X, which should normalize it to a numpy/cupy type with minimal-to-no copies or extra work.

We then determine the required mem_type, and after that do a full validation and coercion pass.

This minimizes work, while keeping the logic readable and relying on standard tools.

# Determine the required mem_type based on params and X
if X_is_sparse:
mem_type = base_mem_type
mem_type = "device"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The old logic didn't do what the comment said (coerce sparse inputs to device). Things were only saved by SparseCumlArray coercing to device memory implicitly later on.

BernoulliNB(),
MultinomialNB(),
UMAP(),
UMAP(n_neighbors=5),

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The data sizes here can be small, the default of n_neighbors=15 would work but lead to some warnings in the tests. Lowering it silences the warnings, same as done for *RandomProjection.

@jcrist
jcrist requested a review from csadorf May 8, 2026 12:00

@csadorf csadorf left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

:shipit:

@jcrist

jcrist commented May 8, 2026

Copy link
Copy Markdown
Member Author

/merge

@rapids-bot
rapids-bot Bot merged commit 79084c3 into NVIDIA:main May 8, 2026
93 checks passed
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 sklearn-api-compat Issues around cuml matching sklearn API conventions/standards

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Update cuml.manifold to new input validation

3 participants