Skip to content

Apply new validation to cuml.neighbors - #8019

Merged
rapids-bot[bot] merged 7 commits into
NVIDIA:mainfrom
viclafargue:validation-neighbors
May 6, 2026
Merged

Apply new validation to cuml.neighbors#8019
rapids-bot[bot] merged 7 commits into
NVIDIA:mainfrom
viclafargue:validation-neighbors

Conversation

@viclafargue

Copy link
Copy Markdown
Contributor

Closes #8001

@viclafargue
viclafargue requested a review from a team as a code owner April 28, 2026 15:18
@viclafargue
viclafargue requested a review from betatim April 28, 2026 15:18
@viclafargue viclafargue self-assigned this Apr 28, 2026
@github-actions github-actions Bot added the Cython / Python Cython or Python issue label Apr 28, 2026
@viclafargue viclafargue added improvement Improvement / enhancement to an existing function non-breaking Non-breaking change and removed Cython / Python Cython or Python issue labels Apr 28, 2026
@coderabbitai

coderabbitai Bot commented Apr 28, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

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: 69ffa8d5-e41a-4f65-8bcd-585cbfa762b6

📥 Commits

Reviewing files that changed from the base of the PR and between ca566fe and d9e334e.

📒 Files selected for processing (1)
  • python/cuml/cuml/neighbors/kernel_density.py

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Stronger, earlier input validation and clearer error messages for KernelDensity and k-neighbors (bandwidth, kernel, sample weights, dtypes and shapes).
  • Refactor
    • Neighbor-query and array handling simplified for consistent device behavior, contiguous outputs, and stable ordering.
  • Tests
    • Updated test expectations and removed several xfails to reflect improved sklearn compatibility.

Walkthrough

Refactors neighbors code to use centralized input validators and raw CuPy buffers for C++ interop (using .data.ptr), changes @reflect decorator reset to "type", tightens y/sample_weight validation, shifts KernelDensity bandwidth estimation to use fitted X shape, and removes related test xfails.

Changes

KernelDensity & tests

Layer / File(s) Summary
Validation imports
python/cuml/cuml/neighbors/kernel_density.py
Replace legacy marshalling with check_inputs, add check_non_negative and other validation utilities.
Fit: inputs & bandwidth
python/cuml/cuml/neighbors/kernel_density.py
fit validates kernel/bandwidth early, uses check_inputs(..., order="C", convert_dtype=...) to produce self._X and self._sample_weight, checks non-negativity, computes "scott"/"silverman" bandwidth from self._X.shape.
API wiring / decorator
python/cuml/cuml/neighbors/kernel_density.py
@reflect on fit changed from reset=True to reset="type".
Scoring inputs
python/cuml/cuml/neighbors/kernel_density.py
score_samples now uses check_inputs constrained to dtype=[self._X.dtype] and order="C".
Tests
python/cuml/tests/test_kernel_density.py
Adjusted error-message assertions for invalid sample_weight cases.
Test config / xfails
python/cuml/tests/test_sklearn_compatibility.py, python/cuml_accel_tests/upstream/scikit-learn/xfail-list.yaml
Removed xfails that tracked prior validation/behavior differences for KernelDensity.

KNN / NearestNeighbors refactor

Layer / File(s) Summary
Validation & imports
python/cuml/cuml/neighbors/*.pyx
Remove input_to_cuml_array usage; adopt check_inputs/check_array/check_y, and direct cupy as cp usage.
Fit / y handling
python/cuml/cuml/neighbors/kneighbors_classifier.pyx, python/cuml/cuml/neighbors/kneighbors_regressor.pyx
fit validates/normalizes y via check_y (dtype/order enforced), checks length against the fitted X, and assigns self._y; @reflect changed to reset="type".
Internal buffers & interop
python/cuml/cuml/neighbors/nearest_neighbors.pyx, python/cuml/cuml/neighbors/kneighbors_*.pyx
Neighbor queries flow through raw CuPy arrays end-to-end; indices/distances made contiguous via cp.ascontiguousarray/cp.asarray, pointers passed as .data.ptr, and weight computations consume CuPy distance arrays.
Index/precision correction
python/cuml/cuml/neighbors/nearest_neighbors.pyx
Two-pass precision/order correction recomputes on CuPy buffers and returns contiguous CuPy arrays instead of rebuilding CumlArray intermediates.
Output wrapping
python/cuml/cuml/neighbors/nearest_neighbors.pyx, python/cuml/cuml/neighbors/kneighbors_*.pyx
Public boundaries wrap final CuPy outputs once into CumlArray; RBC/Approx indices allocate with cp.empty and return CuPy arrays directly.
CPU/GPU attribute transfer
python/cuml/cuml/neighbors/kneighbors_classifier.pyx, python/cuml/cuml/neighbors/kneighbors_regressor.pyx
Transfer of _y between CPU/GPU uses cp.asarray(..., order="F", dtype=...) and cp.asnumpy(...) instead of prior helpers.
Tests / xfails adjustment
python/cuml/tests/test_sklearn_compatibility.py, python/cuml_accel_tests/upstream/scikit-learn/xfail-list.yaml
Removed xfails for KNeighborsClassifier, KNeighborsRegressor, and NearestNeighbors tied to prior validation/behavior differences.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • rapidsai/cuml#7768: Overlapping changes to xfail/test-compatibility mappings for neighbors estimators.
  • rapidsai/cuml#7811: Related NearestNeighbors/RBC/index binding and CuPy pointer handling refactors.
  • rapidsai/cuml#8009: Similar input-validation refactor replacing legacy marshalling helpers and @reflect reset changes.

Suggested labels

sklearn-api-compat

Suggested reviewers

  • betatim
  • jcrist
  • hcho3
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and specifically describes the main change: applying new validation to the cuml.neighbors module, which aligns with all file changes in the changeset.
Description check ✅ Passed The PR description references the linked issue (#8001) which provides context for the validation updates. Although minimal, it is related to the changeset.
Linked Issues check ✅ Passed The PR implements systematic input validation improvements across multiple KNeighbors and KernelDensity classes using check_inputs and check_y, aligning with the issue's objective to update validation in cuml.neighbors.
Out of Scope Changes check ✅ Passed All changes are scoped to validation refactoring in the neighbors module: validation function updates, decorator changes (reset parameter), error message expectations, and xfail removal for now-passing tests.

✏️ 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

🤖 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/neighbors/nearest_neighbors.pyx`:
- Around line 598-609: In __setstate__, the code may pass a None X_cp (derived
from self._fit_X) into RBCIndex.build or ApproxIndex.build causing
AttributeError; before calling RBCIndex.build(...) or ApproxIndex.build(...),
check that X_cp is not None (i.e. self._fit_X was present) and either skip index
building or raise a clear ValueError/TypeError indicating the pickled state is
missing _fit_X; reference the symbols __setstate__, self._fit_X, X_cp,
RBCIndex.build, ApproxIndex.build, fit_method and effective_metric_ when adding
the guard and error message so the failure is informative and prevents calling
.data on None.
🪄 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: 2a4b80dd-ad6b-4635-b66c-0cd08589a77f

📥 Commits

Reviewing files that changed from the base of the PR and between ae8b901 and 2d69ab7.

📒 Files selected for processing (6)
  • python/cuml/cuml/neighbors/kernel_density.py
  • python/cuml/cuml/neighbors/kneighbors_classifier.pyx
  • python/cuml/cuml/neighbors/kneighbors_regressor.pyx
  • python/cuml/cuml/neighbors/nearest_neighbors.pyx
  • python/cuml/cuml_accel_tests/upstream/scikit-learn/xfail-list.yaml
  • python/cuml/tests/test_kernel_density.py
💤 Files with no reviewable changes (1)
  • python/cuml/cuml_accel_tests/upstream/scikit-learn/xfail-list.yaml

Comment on lines +598 to 609
fit_X = getattr(self, "_fit_X", None)
X_cp = fit_X.to_output("cupy") if fit_X is not None else None
if fit_method == "rbc":
self._index = RBCIndex.build(X, self.effective_metric_)
self._index = RBCIndex.build(X_cp, self.effective_metric_)
else:
self._index = ApproxIndex.build(
X,
X_cp,
self.effective_metric_,
fit_method,
params=self.algo_params,
p=self._effective_p,
)

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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if there are any guards or guarantees that _fit_X is never None when __setstate__ runs
rg -n -A5 '__setstate__|_fit_X.*None' python/cuml/cuml/neighbors/nearest_neighbors.pyx

Repository: rapidsai/cuml

Length of output: 854


🏁 Script executed:

rg -n "class RBCIndex|class ApproxIndex" python/cuml/cuml/neighbors/

Repository: rapidsai/cuml

Length of output: 208


🏁 Script executed:

rg -n "def build" python/cuml/cuml/neighbors/ -A3 | head -40

Repository: rapidsai/cuml

Length of output: 771


🏁 Script executed:

# Look for where RBCIndex and ApproxIndex are imported/defined
rg -n "RBCIndex|ApproxIndex" python/cuml/cuml/neighbors/nearest_neighbors.pyx | head -20

Repository: rapidsai/cuml

Length of output: 839


🏁 Script executed:

sed -n '278,310p' python/cuml/cuml/neighbors/nearest_neighbors.pyx

Repository: rapidsai/cuml

Length of output: 1094


🏁 Script executed:

sed -n '417,455p' python/cuml/cuml/neighbors/nearest_neighbors.pyx

Repository: rapidsai/cuml

Length of output: 1353


Add None check before building index in __setstate__.

If a pickle file is corrupted and missing _fit_X, the code sets X_cp = None but then passes it to RBCIndex.build() or ApproxIndex.build(), both of which immediately access X.data.ptr without a None guard, causing an AttributeError.

Check that X_cp is not None before calling either build method, or raise an informative error if the pickled state is invalid.

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

In `@python/cuml/cuml/neighbors/nearest_neighbors.pyx` around lines 598 - 609, In
__setstate__, the code may pass a None X_cp (derived from self._fit_X) into
RBCIndex.build or ApproxIndex.build causing AttributeError; before calling
RBCIndex.build(...) or ApproxIndex.build(...), check that X_cp is not None (i.e.
self._fit_X was present) and either skip index building or raise a clear
ValueError/TypeError indicating the pickled state is missing _fit_X; reference
the symbols __setstate__, self._fit_X, X_cp, RBCIndex.build, ApproxIndex.build,
fit_method and effective_metric_ when adding the guard and error message so the
failure is informative and prevents calling .data on None.

@github-actions github-actions Bot added the Cython / Python Cython or Python issue label Apr 29, 2026

@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: 2

♻️ Duplicate comments (1)
python/cuml/cuml/neighbors/nearest_neighbors.pyx (1)

598-605: ⚠️ Potential issue | 🔴 Critical

Guard __setstate__ against missing _fit_X before rebuilding indices.

Line 599 can produce X_cp = None, but Lines 601/603-604 still call RBCIndex.build/ApproxIndex.build, which dereference X.data.ptr and crash with a non-actionable error.

💡 Proposed fix
         if (fit_method := state.get("_fit_method")) in ("rbc", "ivfpq", "ivfflat"):
             # TODO: These index types currently aren't pickleable. For now we
             # recreate them on load.
             fit_X = getattr(self, "_fit_X", None)
             X_cp = cp.asarray(fit_X) if fit_X is not None else None
+            if X_cp is None:
+                raise ValueError(
+                    "Invalid pickled NearestNeighbors state: missing `_fit_X` "
+                    f"required to rebuild index for fit_method={fit_method!r}."
+                )
             if fit_method == "rbc":
                 self._index = RBCIndex.build(X_cp, self.effective_metric_)
             else:
                 self._index = ApproxIndex.build(
                     X_cp,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/cuml/cuml/neighbors/nearest_neighbors.pyx` around lines 598 - 605, In
__setstate__, avoid calling RBCIndex.build or ApproxIndex.build when self._fit_X
is missing or None: check getattr(self, "_fit_X", None) (or X_cp after
cp.asarray) and only call RBCIndex.build or ApproxIndex.build with X_cp and
self.effective_metric_ when X_cp is not None; otherwise set self._index = None
(or skip rebuilding) so RBCIndex.build/ApproxIndex.build are never invoked on a
None/empty array that would dereference X.data.ptr.
🤖 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/neighbors/nearest_neighbors.pyx`:
- Around line 879-888: The code in nearest_neighbors.pyx recomputes distances as
squared L2 and returns them (variables distances, indices), which breaks
semantics for euclidean/l2/minkowski p=2; modify the return path to take the
square root of distances whenever the metric is "euclidean" or "l2" or when
metric == "minkowski" and p == 2 before converting to contiguous cp.float32
(i.e., replace returning squared distances with cp.sqrt(distances) in those
cases), ensuring you still call cp.ascontiguousarray on the final distances and
indices; locate the block that computes self_diff, distances, and correct_order
and add the conditional sqrt there.
- Around line 924-927: The sparse kneighbors indices are allocated as int32
(indices_cp) but _drop_self_edges invokes swap_kernel which expects long long
int*; to fix, allocate indices_cp as np.int64 (order="C") and change the C
pointer variable to the matching C type (e.g., cdef long long int* indices_ptr =
<long long int *><uintptr_t>indices_cp.data.ptr) so swap_kernel receives 64-bit
indices before any later conversion; update any related declarations that
reference indices_ptr (and any calls into swap_kernel) to use the new long long
pointer type to prevent the unsafe int32→int64 mismatch.

---

Duplicate comments:
In `@python/cuml/cuml/neighbors/nearest_neighbors.pyx`:
- Around line 598-605: In __setstate__, avoid calling RBCIndex.build or
ApproxIndex.build when self._fit_X is missing or None: check getattr(self,
"_fit_X", None) (or X_cp after cp.asarray) and only call RBCIndex.build or
ApproxIndex.build with X_cp and self.effective_metric_ when X_cp is not None;
otherwise set self._index = None (or skip rebuilding) so
RBCIndex.build/ApproxIndex.build are never invoked on a None/empty array that
would dereference X.data.ptr.
🪄 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: f13e8ffe-cc2b-4914-8be5-f4d05d23fc17

📥 Commits

Reviewing files that changed from the base of the PR and between 2d69ab7 and 0ab1aa7.

📒 Files selected for processing (2)
  • python/cuml/cuml/neighbors/nearest_neighbors.pyx
  • python/cuml/tests/test_sklearn_compatibility.py
💤 Files with no reviewable changes (1)
  • python/cuml/tests/test_sklearn_compatibility.py

Comment on lines +879 to 888
self_diff = X[indices] - X[:, cp.newaxis, :]
distances = cp.sum(self_diff * self_diff, axis=2)
correct_order = cp.argsort(distances, axis=1)

self_diff = X_cp[indices_cp] - X_cp[:, cp.newaxis, :]
distances_cp = cp.sum(self_diff * self_diff, axis=2)
correct_order = cp.argsort(distances_cp, axis=1)
distances = cp.take_along_axis(distances, correct_order, axis=1)
indices = cp.take_along_axis(indices, correct_order, axis=1)

distances_cp = cp.take_along_axis(distances_cp, correct_order, axis=1)
indices_cp = cp.take_along_axis(indices_cp, correct_order, axis=1)

distances = CumlArray(
data=cp.ascontiguousarray(distances_cp, dtype=cp.float32), index=index
)
indices = CumlArray(
data=cp.ascontiguousarray(indices_cp, dtype=cp.int64), index=index
)
distances = cp.ascontiguousarray(distances, dtype=cp.float32)
indices = cp.ascontiguousarray(indices, dtype=cp.int64)
return distances, indices

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 | 🔴 Critical

Two-pass precision returns squared distances for euclidean/l2 metrics.

At Lines 879-888, distances are recomputed as squared L2 and returned directly. For euclidean/l2 (and minkowski with p=2), this changes output semantics from distance to squared distance.

💡 Proposed fix
-        self_diff = X[indices] - X[:, cp.newaxis, :]
-        distances = cp.sum(self_diff * self_diff, axis=2)
-        correct_order = cp.argsort(distances, axis=1)
+        self_diff = X[indices] - X[:, cp.newaxis, :]
+        sq_distances = cp.sum(self_diff * self_diff, axis=2)
+        correct_order = cp.argsort(sq_distances, axis=1)

-        distances = cp.take_along_axis(distances, correct_order, axis=1)
+        sq_distances = cp.take_along_axis(sq_distances, correct_order, axis=1)
         indices = cp.take_along_axis(indices, correct_order, axis=1)
 
-        distances = cp.ascontiguousarray(distances, dtype=cp.float32)
+        if metric == DistanceType.L2Expanded:
+            distances = sq_distances
+        else:
+            distances = cp.sqrt(sq_distances)
+        distances = cp.ascontiguousarray(distances, dtype=cp.float32)
         indices = cp.ascontiguousarray(indices, dtype=cp.int64)
         return distances, indices
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/cuml/cuml/neighbors/nearest_neighbors.pyx` around lines 879 - 888, The
code in nearest_neighbors.pyx recomputes distances as squared L2 and returns
them (variables distances, indices), which breaks semantics for
euclidean/l2/minkowski p=2; modify the return path to take the square root of
distances whenever the metric is "euclidean" or "l2" or when metric ==
"minkowski" and p == 2 before converting to contiguous cp.float32 (i.e., replace
returning squared distances with cp.sqrt(distances) in those cases), ensuring
you still call cp.ascontiguousarray on the final distances and indices; locate
the block that computes self_diff, distances, and correct_order and add the
conditional sqrt there.

Comment on lines +924 to +927
indices_cp = cp.empty((X_n_rows, n_neighbors), dtype=np.int32, order="C")
distances_cp = cp.empty((X_n_rows, n_neighbors), dtype=np.float32, order="C")
cdef int* indices_ptr = <int *><uintptr_t>indices_cp.data.ptr
cdef float* distances_ptr = <float *><uintptr_t>distances_cp.data.ptr

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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the type mismatch is present in this file:
# - swap_kernel expects long long int* (int64)
# - sparse kneighbors allocates int32 indices
# - _drop_self_edges is called for training-data queries
rg -n -C3 'swap_kernel\(long long int\* I|indices_cp = cp.empty\(\(X_n_rows, n_neighbors\), dtype=np.int32|_drop_self_edges\(' python/cuml/cuml/neighbors/nearest_neighbors.pyx

Repository: rapidsai/cuml

Length of output: 1239


🏁 Script executed:

sed -n '198,230p' python/cuml/cuml/neighbors/nearest_neighbors.pyx

Repository: rapidsai/cuml

Length of output: 1177


🏁 Script executed:

sed -n '900,935p' python/cuml/cuml/neighbors/nearest_neighbors.pyx

Repository: rapidsai/cuml

Length of output: 1559


🏁 Script executed:

sed -n '860,930p' python/cuml/cuml/neighbors/nearest_neighbors.pyx | grep -E "def |cdef |return |_drop_self_edges"

Repository: rapidsai/cuml

Length of output: 1510


🏁 Script executed:

sed -n '900,1000p' python/cuml/cuml/neighbors/nearest_neighbors.pyx | tail -100

Repository: rapidsai/cuml

Length of output: 3711


🏁 Script executed:

sed -n '770,795p' python/cuml/cuml/neighbors/nearest_neighbors.pyx

Repository: rapidsai/cuml

Length of output: 1111


Fix type mismatch in sparse kneighbors indices passed to CUDA kernel.

Lines 924-927 allocate sparse indices_cp as int32, but _drop_self_edges calls swap_kernel (declared with long long int* at line 172) while indices are still int32. The conversion to int64 at line 229 happens after swap_kernel runs, risking memory corruption when querying training data (use_training_data=True).

Proposed fix
-        indices_cp = cp.empty((X_n_rows, n_neighbors), dtype=np.int32, order="C")
+        # C++ sparse knn writes int32, then upcast for downstream parity
+        indices_cp = cp.empty((X_n_rows, n_neighbors), dtype=np.int32, order="C")
         distances_cp = cp.empty((X_n_rows, n_neighbors), dtype=np.float32, order="C")
         cdef int* indices_ptr = <int *><uintptr_t>indices_cp.data.ptr
         cdef float* distances_ptr = <float *><uintptr_t>distances_cp.data.ptr
@@
-        return distances_cp, indices_cp
+        indices_cp = cp.ascontiguousarray(indices_cp, dtype=np.int64)
+        return distances_cp, indices_cp
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/cuml/cuml/neighbors/nearest_neighbors.pyx` around lines 924 - 927, The
sparse kneighbors indices are allocated as int32 (indices_cp) but
_drop_self_edges invokes swap_kernel which expects long long int*; to fix,
allocate indices_cp as np.int64 (order="C") and change the C pointer variable to
the matching C type (e.g., cdef long long int* indices_ptr = <long long int
*><uintptr_t>indices_cp.data.ptr) so swap_kernel receives 64-bit indices before
any later conversion; update any related declarations that reference indices_ptr
(and any calls into swap_kernel) to use the new long long pointer type to
prevent the unsafe int32→int64 mismatch.

@viclafargue
viclafargue requested a review from jcrist April 30, 2026 08:42

@jcrist jcrist left a comment

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.

Thanks for working on this! Just a couple fixups, but nice to see all the xfails this removes!

raise ValueError(f"kernel={self.kernel!r} is not supported")

self._X, n_rows, n_cols, _ = input_to_cupy_array(
check_features(self, X, reset=True)

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.

You could use check_inputs here instead of check_features + check_array + check_sample_weight + check_consistent_length. A bit shorter code, and ensures things are handled consistently across our estimators.

@viclafargue viclafargue May 5, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The check_inputs function does not have a sample_weight_ensure_non_negative argument making it impossible to merge everything into a single operation. But, I could merge check_features + check_array.

@jcrist jcrist May 5, 2026

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.

You could also use check_inputs, and call check_non_negative on sample weights after too. Could also add the plumbing for ensure_sample_weight_non_negative if ya want. Up to you.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for pointing out the existence of this function! Just updated the code with it.

Comment thread python/cuml/cuml/neighbors/kernel_density.py
Comment thread python/cuml/cuml/neighbors/kneighbors_classifier.pyx Outdated
Comment thread python/cuml/cuml/neighbors/kneighbors_regressor.pyx Outdated

@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

🤖 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/neighbors/kernel_density.py`:
- Around line 303-332: The code currently assigns learned attributes (self._X,
self.bandwidth_, self._sample_weight) before all validations complete; instead,
call check_inputs(...) and compute bandwidth_ into local variables (e.g.,
X_valid, bandwidth_val), call check_sample_weight(...) and
check_consistent_length(X_valid, sample_weight_valid), and only after all
validations succeed assign self._X = X_valid, self.bandwidth_ = bandwidth_val,
and self._sample_weight = sample_weight_valid; reference the functions
check_inputs, check_sample_weight, check_consistent_length and the attributes
self._X, self.bandwidth_, self._sample_weight when making the change.
🪄 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: 2f32b8ab-b63a-4378-b437-e1a13cba5e68

📥 Commits

Reviewing files that changed from the base of the PR and between 0ab1aa7 and b3fa4a7.

📒 Files selected for processing (3)
  • python/cuml/cuml/neighbors/kernel_density.py
  • python/cuml/cuml/neighbors/kneighbors_classifier.pyx
  • python/cuml/cuml/neighbors/kneighbors_regressor.pyx
🚧 Files skipped from review as they are similar to previous changes (1)
  • python/cuml/cuml/neighbors/kneighbors_regressor.pyx

Comment thread python/cuml/cuml/neighbors/kernel_density.py Outdated

@jcrist jcrist left a comment

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.

:shipit:

index=X.index,
)
distances_cp = cp.empty((X.shape[0], n_neighbors), dtype=np.float32, order="C")
indices_cp = cp.empty((X.shape[0], n_neighbors), dtype=np.int64, order="C")

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.

nit: adding a _cp suffix is noisy and shouldn't be necessary in our new world of only one array type. Not a blocker for now, but please don't add more of these.

@jcrist

jcrist commented May 6, 2026

Copy link
Copy Markdown
Member

/merge

@rapids-bot
rapids-bot Bot merged commit 4c30f68 into NVIDIA:main May 6, 2026
149 of 154 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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Update cuml.neighbors to new input validation

4 participants