Skip to content

[FEA] Add host-streaming support to single-GPU KMeans - #8248

Merged
rapids-bot[bot] merged 18 commits into
rapidsai:release/26.08from
tarang-jain:ooc-kmeans
Jul 27, 2026
Merged

[FEA] Add host-streaming support to single-GPU KMeans#8248
rapids-bot[bot] merged 18 commits into
rapidsai:release/26.08from
tarang-jain:ooc-kmeans

Conversation

@tarang-jain

@tarang-jain tarang-jain commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Adds device_buffer_samples to single-GPU KMeans for host-resident input data.

For host-resident inputs, positive values select host-streamed fitting, avoiding an up-front full-device copy of X. Device-resident inputs continue to use the standard device path and ignore device_buffer_samples. The PR wires device_buffer_samples and init_size through C++ and Python, rejects positive device_buffer_samples values for multi-GPU use, computes labels and inertia in chunks for the host path, and adds weighted and unweighted parity tests against the existing device path.

Depends on NVIDIA/cuvs#2328 (merged).

Follow-up: NVIDIA/cuvs#2359

Closes #8202

@tarang-jain
tarang-jain requested review from a team as code owners June 10, 2026 14:19
@github-actions github-actions Bot added Cython / Python Cython or Python issue CUDA/C++ labels Jun 10, 2026
@tarang-jain tarang-jain added feature request New feature or request non-breaking Non-breaking change labels Jun 10, 2026
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

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
📝 Walkthrough

Walkthrough

Adds streaming_batch_size to KMeans, updates C++ and Python residency handling for fit, adds host-chunked prediction for host inputs, and validates host/device result parity with a new test.

Changes

Host-Resident KMeans Streaming

Layer / File(s) Summary
Parameter contract and cross-language mapping
cpp/include/cuml/cluster/kmeans_params.hpp, cpp/src/kmeans/kmeans_params.cpp, cpp/include/cuml/cluster/kmeans.hpp, python/cuml/cuml/cluster/cpp/kmeans.pxd
Adds streaming_batch_size to the shared KMeans parameter types, includes <cstdint>, updates to_cuvs(), and rewrites the kmeans::fit residency documentation.
C++ fit host/device dispatch
cpp/src/kmeans/kmeans_fit.cu
Adds separate host and device fit helpers and routes fit() based on whether X is host-resident or device/managed.
Python estimator API and parameter wiring
python/cuml/cuml/cluster/kmeans.pyx
Adds streaming_batch_size to the estimator API, docstring, and parameter wiring, and stores it on the Python object.
Fit residency checks and pointer derivation
python/cuml/cuml/cluster/kmeans.pyx
Updates _kmeans_fit and KMeans.fit to branch on host/device inputs, derive pointers from NumPy or CuPy data, and validate streaming-related constraints.
Chunked prediction flow and memory path dispatch
python/cuml/cuml/cluster/kmeans.pyx
Extends prediction with a normalize_weights flag, adds host chunked prediction, and dispatches to it when host streaming is enabled.
Streaming behavior and result consistency testing
python/cuml/tests/test_kmeans.py
Adds a parameterized test comparing host-streamed and device-resident KMeans results.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • rapidsai/cuml#7984: Also changes python/cuml/cuml/cluster/kmeans.pyx fit-time input handling and validation around KMeans.fit.

Suggested labels

improvement

Suggested reviewers

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

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% 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
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: host-streaming support for single-GPU KMeans.
Description check ✅ Passed The description is on-topic and describes the same host-streaming KMeans changes and tests.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@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 `@cpp/src/kmeans/kmeans_fit.cu`:
- Around line 36-41: The current ptr_is_host logic treats any
cudaPointerGetAttributes failure as a host pointer; instead, change the handling
so that only a successful cudaPointerGetAttributes that yields attr.type ==
cudaMemoryTypeHost classifies the pointer as host. In ptr_is_host
(kmeans_fit.cu) check if err == cudaSuccess and then return true only when
attr.type == cudaMemoryTypeHost (or equivalent field for host memory); if
cudaPointerGetAttributes returns an error, clear the sticky error with
cudaGetLastError() but do NOT assume host—return false or propagate the error so
the CUDA-path dispatch is not chosen incorrectly.
🪄 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: 3c495c39-bb68-4250-93e1-1c3e8cbe3b1e

📥 Commits

Reviewing files that changed from the base of the PR and between f2776c1 and 3ebad63.

📒 Files selected for processing (7)
  • cpp/include/cuml/cluster/kmeans.hpp
  • cpp/include/cuml/cluster/kmeans_params.hpp
  • cpp/src/kmeans/kmeans_fit.cu
  • cpp/src/kmeans/kmeans_params.cpp
  • python/cuml/cuml/cluster/cpp/kmeans.pxd
  • python/cuml/cuml/cluster/kmeans.pyx
  • python/cuml/tests/test_kmeans.py

Comment thread cpp/src/kmeans/kmeans_fit.cu Outdated
Comment thread python/cuml/cuml/cluster/kmeans.pyx Outdated

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

Just a quick superficial early review.

Comment thread python/cuml/cuml/cluster/kmeans.pyx Outdated
Comment thread python/cuml/cuml/cluster/kmeans.pyx Outdated
Comment thread cpp/src/kmeans/kmeans_fit.cu Outdated

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

Thanks!

Comment thread python/cuml/cuml/cluster/kmeans.pyx Outdated
Comment thread python/cuml/cuml/cluster/kmeans.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.

Caution

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

⚠️ Outside diff range comments (1)
cpp/src/kmeans/kmeans_fit.cu (1)

38-50: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

CRITICAL: sample_weight residency is inferred from X and can build the wrong RAFT view.

Line 38 picks the host/device path from X alone, then Lines 47-50 and Lines 66-69 unconditionally wrap sample_weight as a host or device vector in that same branch. A host X with device sample_weight (or the reverse) will hand cuVS the wrong view type, which can turn into invalid reads or a crash.

As per coding guidelines, avoid host/device confusion and validate raw-pointer inputs before building RAFT views.

Also applies to: 66-69

🤖 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 `@cpp/src/kmeans/kmeans_fit.cu` around lines 38 - 50, The code incorrectly
infers sample_weight residency from X and builds RAFT views for sample_weight
using X's path; instead, check the residency of sample_weight independently
(e.g. call ML::is_device_or_managed_type(sample_weight) or equivalent) and
construct the matching RAFT view (raft::make_host_vector_view or
raft::make_device_vector_view) for sample_weight only when sample_weight !=
nullptr; apply the same fix to the equivalent branch around the device-path code
(the block creating sw and the one at lines 66-69), and add a short
validation/error path when the pointer residency cannot be determined to avoid
handing cuVS a mismatched view.

Source: Coding guidelines

🤖 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 `@cpp/src/kmeans/kmeans_fit.cu`:
- Around line 38-50: The code incorrectly infers sample_weight residency from X
and builds RAFT views for sample_weight using X's path; instead, check the
residency of sample_weight independently (e.g. call
ML::is_device_or_managed_type(sample_weight) or equivalent) and construct the
matching RAFT view (raft::make_host_vector_view or
raft::make_device_vector_view) for sample_weight only when sample_weight !=
nullptr; apply the same fix to the equivalent branch around the device-path code
(the block creating sw and the one at lines 66-69), and add a short
validation/error path when the pointer residency cannot be determined to avoid
handing cuVS a mismatched view.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 36ed9256-4d1a-4edf-8a38-b718fecd659e

📥 Commits

Reviewing files that changed from the base of the PR and between 3ebad63 and 8474d87.

📒 Files selected for processing (3)
  • cpp/include/cuml/cluster/kmeans.hpp
  • cpp/src/kmeans/kmeans_fit.cu
  • python/cuml/cuml/cluster/kmeans.pyx

Comment thread python/cuml/cuml/cluster/kmeans.pyx
Comment thread cpp/include/cuml/cluster/kmeans.hpp Outdated
@csadorf csadorf changed the title [FEA] Expose streaming_batch_size to CUML for OOC KMeans [FEA] Add host-streaming support to single-GPU KMeans Jun 24, 2026

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

Looks good generally, just a few minor change requests.

Comment thread python/cuml/cuml/cluster/kmeans.pyx Outdated
Comment thread python/cuml/cuml/cluster/kmeans.pyx Outdated
Comment thread python/cuml/tests/test_kmeans.py Outdated
Comment thread cpp/src/kmeans/kmeans_fit.cu 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 `@cpp/src/kmeans/kmeans_fit.cu`:
- Around line 90-96: The new host/device dispatch is only implemented in the
float,int overload, while the double and int64_t overloads still reference the
removed fit_impl path and must be updated to call fit_impl_device or
fit_impl_host based on ML::is_device_or_managed_type(X). Also ensure the
Python-facing int64_t overloads are routed through the same host/device
branching so host-resident inputs do not bypass the new split; update the
affected kmeans_fit overloads to use the same dispatch logic consistently.
🪄 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: fe1a74d1-5516-4478-b8fb-485550890c66

📥 Commits

Reviewing files that changed from the base of the PR and between 65de1c9 and 4c12f9a.

📒 Files selected for processing (3)
  • cpp/src/kmeans/kmeans_fit.cu
  • python/cuml/cuml/cluster/kmeans.pyx
  • python/cuml/tests/test_kmeans.py
💤 Files with no reviewable changes (1)
  • python/cuml/tests/test_kmeans.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • python/cuml/cuml/cluster/kmeans.pyx

Comment thread cpp/src/kmeans/kmeans_fit.cu
Comment thread python/cuml/cuml/cluster/kmeans.pyx Outdated
Comment thread python/cuml/cuml/cluster/kmeans.pyx Outdated
int& n_iter)
{
fit_impl(handle, params, X, n_samples, n_features, sample_weight, centroids, inertia, n_iter);
if (ML::is_device_or_managed_type(X)) {

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.

is_device_or_managed_type calls on memory_type to determine the memory type. But, the memory_type function always returns att.type despite it being undefined when cudaErrorInvalidValue. The err == cudaErrorInvalidValue branch should probably return cudaMemoryTypeUnregistered.

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.

In that case, if it turns out to be unregistered (i.e. errors out, the host kmeans fit path would still be called here. How should we handle that?

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.

By unregistered I mean pageable host memory, not an invalid pointer. There is something a bit odd about this function. It seems to accept a fail from cudaPointerGetAttributes, but still returns the undefined att.type in this case.

The cases should be:

  • cudaSuccess + cudaMemoryTypeDevice or Managed → use device fit.
  • cudaSuccess + cudaMemoryTypeHost or Unregistered → use host fit.
  • cudaErrorInvalidValueatt.type is undefined and should be treated as an error. Unless explicitly supporting legacy CUDA (pre CUDA 11) where valid unregistered host memory produced this error. Up to you, but RAPIDS does not support CUDA 11 anymore.

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.

In that case, that function just needs to be modified to throw an error when we get cudaErrorInvalidValue

@csadorf

csadorf commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

@tarang-jain Is this ready for another review iteration? I have the impression that some of the open threads have not yet been resolved.

@tarang-jain

Copy link
Copy Markdown
Contributor Author

@csadorf ready for next round of reviews

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

Great work, thanks! Just some minor comments.

Comment thread python/cuml/tests/test_kmeans.py Outdated

common_kwargs = dict(
n_clusters=n_clusters,
init=init,

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.

Here the initial centroids are systematically provided. Could we check that everything works fine too when using the default initialization of both paths?

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.

Done!

int& n_iter)
{
fit_impl(handle, params, X, n_samples, n_features, sample_weight, centroids, inertia, n_iter);
if (ML::is_device_or_managed_type(X)) {

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.

By unregistered I mean pageable host memory, not an invalid pointer. There is something a bit odd about this function. It seems to accept a fail from cudaPointerGetAttributes, but still returns the undefined att.type in this case.

The cases should be:

  • cudaSuccess + cudaMemoryTypeDevice or Managed → use device fit.
  • cudaSuccess + cudaMemoryTypeHost or Unregistered → use host fit.
  • cudaErrorInvalidValueatt.type is undefined and should be treated as an error. Unless explicitly supporting legacy CUDA (pre CUDA 11) where valid unregistered host memory produced this error. Up to you, but RAPIDS does not support CUDA 11 anymore.

Comment thread python/cuml/cuml/cluster/kmeans.pyx Outdated
Comment thread python/cuml/cuml/cluster/kmeans.pyx 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.

A few small nits, but this mostly looks good to me!

Comment thread python/cuml/cuml/cluster/kmeans.pyx Outdated
Comment thread python/cuml/cuml/cluster/kmeans.pyx Outdated
Comment thread python/cuml/cuml/cluster/kmeans.pyx Outdated
@csadorf

csadorf commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

@tarang-jain Please address the compilation errors (it appears to be an incompatibility with cuVS). Please avoid force-pushes if possible, it makes it harder to (re)review. Thanks!

Comment thread cpp/include/cuml/cluster/kmeans_params.hpp Outdated
Comment thread python/cuml/cuml/cluster/kmeans.pyx Outdated
Comment thread python/cuml/cuml/cluster/kmeans.pyx Outdated
@tarang-jain

Copy link
Copy Markdown
Contributor Author

the compilation errors are intentional. We are waiting for NVIDIA/cuvs#2328 to be merged before merging this PR, so that cuml does not break when that happens.

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

Thanks, great work!

Comment thread python/cuml/cuml/cluster/kmeans.pyx
Comment thread python/cuml/cuml/cluster/kmeans.pyx
Comment thread python/cuml/cuml/cluster/kmeans.pyx 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.

A few small comments, but this mostly LGTM!

Comment thread python/cuml/cuml/cluster/kmeans.pyx Outdated
Comment thread python/cuml/cuml/cluster/kmeans.pyx Outdated
Comment thread python/cuml/cuml/cluster/kmeans.pyx

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

Thanks! LGTM

@csadorf csadorf linked an issue Jul 27, 2026 that may be closed by this pull request

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

Thanks for working through the remaining feedback. The device_buffer_samples behavior and documentation are now consistent. The remaining follow-up work on the KMeans++ default behavior divergence can be addressed as part of NVIDIA/cuvs#2359. LGTM.

@tarang-jain

Copy link
Copy Markdown
Contributor Author

/merge

@rapids-bot
rapids-bot Bot merged commit fe25659 into rapidsai:release/26.08 Jul 27, 2026
109 checks passed
rapids-bot Bot pushed a commit that referenced this pull request Jul 28, 2026
NVIDIA/cuvs#2066 allows passing host partitions directly to cuVS KMeans (MNMG). Merge this PR after NVIDIA/cuvs#2066 and #8248
Closes #8201

Authors:
  - Tarang Jain (https://github.com/tarang-jain)

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

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

Labels

conda conda issue CUDA/C++ Cython / Python Cython or Python issue feature request New feature or request non-breaking Non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEA] Scaling Single GPU KMeans (Out of Core)

5 participants