Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
442b1fb
Add out-of-core (streaming) support to KMeans
tarang-jain Jul 17, 2026
9e5b1a6
fix compilation errors
tarang-jain Jul 19, 2026
928b11b
update test n_rows
tarang-jain Jul 19, 2026
cf84182
change param name
tarang-jain Jul 20, 2026
188d824
Merge branch 'release/26.08' into ooc-kmeans
tarang-jain Jul 20, 2026
ae92bf6
first commit
tarang-jain Jul 20, 2026
37979af
clean up docs
tarang-jain Jul 20, 2026
7b92f6f
.asnumpy
tarang-jain Jul 22, 2026
2a199a6
Merge branch 'release/26.08' of https://github.com/rapidsai/cuml into…
tarang-jain Jul 22, 2026
818cccc
Merge branch 'ooc-kmeans' of https://github.com/tarang-jain/cuml into…
tarang-jain Jul 22, 2026
238fd6c
rename streaming_batch_size
tarang-jain Jul 22, 2026
3b54f43
style
tarang-jain Jul 22, 2026
8b8a885
update docs and behaviour
tarang-jain Jul 22, 2026
d428927
Merge branch 'release/26.08' into ooc-kmeans
tarang-jain Jul 23, 2026
761b843
merge origin ooc-kmeans
tarang-jain Jul 23, 2026
f384282
change handling for non-batched host inputs
tarang-jain Jul 23, 2026
85d2fd2
fix compilation error
tarang-jain Jul 23, 2026
f283725
Merge branch 'ooc-kmeans' of https://github.com/tarang-jain/cuml into…
tarang-jain Jul 23, 2026
8874547
fix style
tarang-jain Jul 24, 2026
160f64c
fix comment
tarang-jain Jul 24, 2026
583b58b
rm todo
tarang-jain Jul 24, 2026
d76f635
rm todo
tarang-jain Jul 24, 2026
c1162d4
isinstance
tarang-jain Jul 24, 2026
32ce96d
Merge branch 'ooc-kmeans' of https://github.com/tarang-jain/cuml into…
tarang-jain Jul 24, 2026
800b4e1
add tests python
tarang-jain Jul 24, 2026
496e4cf
fix len
tarang-jain Jul 24, 2026
8ca185c
fix docs
tarang-jain Jul 27, 2026
c1375d4
Merge branch 'ooc-kmeans' of https://github.com/tarang-jain/cuml into…
tarang-jain Jul 27, 2026
0345b53
fix comment and condition checks
tarang-jain Jul 27, 2026
5ee27ec
Merge branch 'release/26.08' into mnmg-kmeans
tarang-jain Jul 27, 2026
688b919
fix detect residency
tarang-jain Jul 27, 2026
e5169c7
Merge branch 'mnmg-kmeans' of https://github.com/tarang-jain/cuml int…
tarang-jain Jul 27, 2026
42b365b
address pr reviews
tarang-jain Jul 28, 2026
d3cf1a8
address pr reviews
tarang-jain Jul 28, 2026
c856979
re-add comment
tarang-jain Jul 28, 2026
b1d3b2e
Merge branch 'release/26.08' into mnmg-kmeans
tarang-jain Jul 28, 2026
cba68c1
validation of residency and dtype
tarang-jain Jul 28, 2026
583eb09
Merge branch 'mnmg-kmeans' of https://github.com/tarang-jain/cuml int…
tarang-jain Jul 28, 2026
a2b5b1b
cleanup
tarang-jain Jul 28, 2026
25b6b75
correct comment
tarang-jain Jul 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 53 additions & 2 deletions cpp/include/cuml/cluster/kmeans.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ namespace kmeans {
manages the CUDA resources.
* @param[in] params Parameters for KMeans model.
* @param[in] X Training instances to cluster, in row-major
* format. May live on the device or on the host.
* format. May or may not be device accessible.
* @param[in] n_samples Number of samples in the input X.
* @param[in] n_features Number of features or the dimensions of each
* sample.
Expand All @@ -33,7 +33,7 @@ namespace kmeans {
centroids as the initial cluster centers
* [out] Otherwise, generated centroids from the
kmeans algorithm is stored at the address pointed by 'centroids'. `centroids`
* must always live on the device.
* must always be device accessible.
* @param[out] inertia Sum of squared distances of samples to their
closest cluster center.
* @param[out] n_iter Number of iterations run.
Expand Down Expand Up @@ -78,6 +78,57 @@ void fit(const raft::handle_t& handle,
double& inertia,
int64_t& n_iter);

/**
* @brief Multi-GPU / out-of-core k-means fit over multiple local data partitions.
*
* Each rank (e.g. each Dask worker) supplies its local training data as an
* array of `n_parts` partitions.
* All partitions on a given rank must share the same residency. The
* distributed reduction across ranks is performed via the NCCL communicator
* that must be initialized on `handle`.
*
* @param[in] handle cuML handle with NCCL comms initialized.
* @param[in] params Parameters for the KMeans model. For
* host-resident partitions the
* host-to-device batch size is read from
* `params.device_buffer_samples`.
* @param[in] X_parts Array of `n_parts` pointers to the local
* row-major partitions (all host- or all
* device-resident). Partition `i` has shape
* [`n_samples_parts[i]`, `n_features`].
* @param[in] n_samples_parts Array of `n_parts` per-partition row counts.
* @param[in] n_parts Number of local partitions on this rank.
* @param[in] n_features Number of features (shared by all partitions).
* @param[in] sample_weight_parts Optional array of `n_parts` pointers to the
* per-partition weight vectors (matching the
* residency of `X_parts`), or `nullptr` for
* uniform weights.
* @param[inout] centroids Device matrix [n_clusters x n_features].
* @param[out] inertia Sum of squared distances to closest center.
* @param[out] n_iter Number of iterations run.
*/
void fit(const raft::handle_t& handle,
const KMeansParams& params,
const float* const* X_parts,
const int64_t* n_samples_parts,
int64_t n_parts,
int64_t n_features,
const float* const* sample_weight_parts,
float* centroids,
float& inertia,
int64_t& n_iter);

void fit(const raft::handle_t& handle,
const KMeansParams& params,
const double* const* X_parts,
const int64_t* n_samples_parts,
int64_t n_parts,
int64_t n_features,
const double* const* sample_weight_parts,
double* centroids,
double& inertia,
int64_t& n_iter);

/**
* @brief Predict the closest cluster each sample in X belongs to.
*
Expand Down
166 changes: 166 additions & 0 deletions cpp/src/kmeans/kmeans_fit.cu
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,89 @@
#include <kmeans/kmeans_params.hpp>

#include <optional>
#include <vector>

namespace ML {
namespace kmeans {

template <typename value_t>
void fit_impl_device_parts(const raft::handle_t& handle,
const KMeansParams& params,
const value_t* const* X_parts,
const int64_t* n_samples_parts,
int64_t n_parts,
int64_t n_features,
const value_t* const* sample_weight_parts,
value_t* centroids,
value_t& inertia,
int64_t& n_iter)
{
std::vector<raft::device_matrix_view<const value_t, int64_t>> X_views;
X_views.reserve(n_parts);
for (int64_t i = 0; i < n_parts; ++i) {
X_views.push_back(raft::make_device_matrix_view<const value_t, int64_t>(
X_parts[i], n_samples_parts[i], n_features));
}

std::optional<std::vector<raft::device_vector_view<const value_t, int64_t>>> sw = std::nullopt;
if (sample_weight_parts != nullptr) {
std::vector<raft::device_vector_view<const value_t, int64_t>> sw_views;
sw_views.reserve(n_parts);
for (int64_t i = 0; i < n_parts; ++i) {
sw_views.push_back(raft::make_device_vector_view<const value_t, int64_t>(
sample_weight_parts[i], n_samples_parts[i]));
}
sw = std::make_optional(std::move(sw_views));
}

auto centroids_view =
raft::make_device_matrix_view<value_t, int64_t>(centroids, params.n_clusters, n_features);
auto inertia_view = raft::make_host_scalar_view<value_t>(&inertia);
auto n_iter_view = raft::make_host_scalar_view<int64_t>(&n_iter);

cuvs::cluster::kmeans::fit(
handle, to_cuvs(params), X_views, sw, centroids_view, inertia_view, n_iter_view);
}

template <typename value_t>
void fit_impl_host_parts(const raft::handle_t& handle,
const KMeansParams& params,
const value_t* const* X_parts,
const int64_t* n_samples_parts,
int64_t n_parts,
int64_t n_features,
const value_t* const* sample_weight_parts,
value_t* centroids,
value_t& inertia,
int64_t& n_iter)
{
std::vector<raft::host_matrix_view<const value_t, int64_t>> X_views;
X_views.reserve(n_parts);
for (int64_t i = 0; i < n_parts; ++i) {
X_views.push_back(raft::make_host_matrix_view<const value_t, int64_t>(
X_parts[i], n_samples_parts[i], n_features));
}

std::optional<std::vector<raft::host_vector_view<const value_t, int64_t>>> sw = std::nullopt;
if (sample_weight_parts != nullptr) {
std::vector<raft::host_vector_view<const value_t, int64_t>> sw_views;
sw_views.reserve(n_parts);
for (int64_t i = 0; i < n_parts; ++i) {
sw_views.push_back(raft::make_host_vector_view<const value_t, int64_t>(sample_weight_parts[i],
n_samples_parts[i]));
}
sw = std::make_optional(std::move(sw_views));
}

auto centroids_view =
raft::make_device_matrix_view<value_t, int64_t>(centroids, params.n_clusters, n_features);
auto inertia_view = raft::make_host_scalar_view<value_t>(&inertia);
auto n_iter_view = raft::make_host_scalar_view<int64_t>(&n_iter);

cuvs::cluster::kmeans::fit(
handle, to_cuvs(params), X_views, sw, centroids_view, inertia_view, n_iter_view);
}

template <typename value_t, typename idx_t>
void fit_impl_host(const raft::handle_t& handle,
const KMeansParams& params,
Expand Down Expand Up @@ -151,5 +230,92 @@ void fit(const raft::handle_t& handle,
}
}

// Detect partition residency from the first non-empty partition: an empty
// partition may carry a null data pointer that `is_device_or_managed_type`
// cannot classify, so skip past empties. An all-empty rank has no local data;
// default to the host path.
template <typename value_t>
static bool parts_on_device(const value_t* const* X_parts,
const int64_t* n_samples_parts,
int64_t n_parts)
{
for (int64_t i = 0; i < n_parts; ++i) {
if (n_samples_parts[i] > 0) { return ML::is_device_or_managed_type(X_parts[i]); }
}
return false;
}

void fit(const raft::handle_t& handle,
Comment thread
csadorf marked this conversation as resolved.
const KMeansParams& params,
const float* const* X_parts,
const int64_t* n_samples_parts,
int64_t n_parts,
int64_t n_features,
const float* const* sample_weight_parts,
float* centroids,
float& inertia,
int64_t& n_iter)
{
if (parts_on_device(X_parts, n_samples_parts, n_parts)) {
fit_impl_device_parts(handle,
params,
X_parts,
n_samples_parts,
n_parts,
n_features,
sample_weight_parts,
centroids,
inertia,
n_iter);
} else {
fit_impl_host_parts(handle,
params,
X_parts,
n_samples_parts,
n_parts,
n_features,
sample_weight_parts,
centroids,
inertia,
n_iter);
}
}

void fit(const raft::handle_t& handle,
const KMeansParams& params,
const double* const* X_parts,
const int64_t* n_samples_parts,
int64_t n_parts,
int64_t n_features,
const double* const* sample_weight_parts,
double* centroids,
double& inertia,
int64_t& n_iter)
{
if (parts_on_device(X_parts, n_samples_parts, n_parts)) {
fit_impl_device_parts(handle,
params,
X_parts,
n_samples_parts,
n_parts,
n_features,
sample_weight_parts,
centroids,
inertia,
n_iter);
} else {
fit_impl_host_parts(handle,
params,
X_parts,
n_samples_parts,
n_parts,
n_features,
sample_weight_parts,
centroids,
inertia,
n_iter);
}
}

}; // end namespace kmeans
}; // end namespace ML
22 changes: 22 additions & 0 deletions python/cuml/cuml/cluster/cpp/kmeans.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,28 @@ cdef extern from "cuml/cluster/kmeans.hpp" namespace "ML::kmeans" nogil:
double &inertia,
int64_t &n_iter) except +

cdef void fit(handle_t& handle,
KMeansParams& params,
float **X_parts,
const int64_t *n_samples_parts,
int64_t n_parts,
int64_t n_features,
float **sample_weight_parts,
float *centroids,
float &inertia,
int64_t &n_iter) except +

cdef void fit(handle_t& handle,
KMeansParams& params,
double **X_parts,
const int64_t *n_samples_parts,
int64_t n_parts,
int64_t n_features,
double **sample_weight_parts,
double *centroids,
double &inertia,
int64_t &n_iter) except +

cdef void predict(handle_t& handle,
KMeansParams& params,
const float *centroids,
Expand Down
Loading
Loading