diff --git a/c/src/cluster/kmeans.cpp b/c/src/cluster/kmeans.cpp index 9d371c182b..89eac23e7a 100644 --- a/c/src/cluster/kmeans.cpp +++ b/c/src/cluster/kmeans.cpp @@ -11,6 +11,11 @@ #include #include +#include +#include +#include +#include + #include "../core/exceptions.hpp" #include "../core/interop.hpp" @@ -210,10 +215,14 @@ void _cluster_cost(cuvsResources_t res, if (cuvs::core::is_dlpack_device_compatible(X)) { using mdspan_type = raft::device_matrix_view; + auto d_cost = raft::make_device_scalar(*res_ptr, T{0}); cuvs::cluster::kmeans::cluster_cost(*res_ptr, cuvs::core::from_dlpack(X_tensor), cuvs::core::from_dlpack(centroids_tensor), - raft::make_host_scalar_view(&cost_temp)); + d_cost.view()); + raft::copy( + *res_ptr, raft::make_host_scalar_view(&cost_temp), raft::make_const_mdspan(d_cost.view())); + raft::resource::sync_stream(*res_ptr); } else { RAFT_FAIL("X dataset must be accessible on device memory"); } diff --git a/cpp/include/cuvs/cluster/kmeans.hpp b/cpp/include/cuvs/cluster/kmeans.hpp index 79f3c020b9..ea9a9de90a 100644 --- a/cpp/include/cuvs/cluster/kmeans.hpp +++ b/cpp/include/cuvs/cluster/kmeans.hpp @@ -1584,14 +1584,16 @@ void transform(raft::resources const& handle, * @param[out] cost Resulting cluster cost * @param[in] sample_weight Optional per-sample weights. * [len = n_samples] - * + * @param[in] X_norm Optional precomputed squared L2 row norms of X (||x||^2) [n_samples]. + * When provided, the internal norm computation is skipped. */ void cluster_cost( const raft::resources& handle, raft::device_matrix_view X, raft::device_matrix_view centroids, - raft::host_scalar_view cost, - std::optional> sample_weight = std::nullopt); + raft::device_scalar_view cost, + std::optional> sample_weight = std::nullopt, + std::optional> X_norm = std::nullopt); /** * @brief Compute cluster cost @@ -1606,13 +1608,17 @@ void cluster_cost( * @param[out] cost Resulting cluster cost * @param[in] sample_weight Optional per-sample weights. * [len = n_samples] + * @param[in] X_norm Optional precomputed squared L2 row norms of X (||x||^2, + * i.e. sum of squares without the sqrt) [n_samples]. When + * provided, the internal norm computation is skipped. */ void cluster_cost( const raft::resources& handle, raft::device_matrix_view X, raft::device_matrix_view centroids, - raft::host_scalar_view cost, - std::optional> sample_weight = std::nullopt); + raft::device_scalar_view cost, + std::optional> sample_weight = std::nullopt, + std::optional> X_norm = std::nullopt); /** * @brief Compute (optionally weighted) cluster cost @@ -1627,13 +1633,17 @@ void cluster_cost( * @param[out] cost Resulting cluster cost * @param[in] sample_weight Optional per-sample weights. * [len = n_samples] + * @param[in] X_norm Optional precomputed squared L2 row norms of X (||x||^2, + * i.e. sum of squares without the sqrt) [n_samples]. When + * provided, the internal norm computation is skipped. */ void cluster_cost( const raft::resources& handle, raft::device_matrix_view X, raft::device_matrix_view centroids, - raft::host_scalar_view cost, - std::optional> sample_weight = std::nullopt); + raft::device_scalar_view cost, + std::optional> sample_weight = std::nullopt, + std::optional> X_norm = std::nullopt); /** * @brief Compute (optionally weighted) cluster cost @@ -1648,13 +1658,98 @@ void cluster_cost( * @param[out] cost Resulting cluster cost * @param[in] sample_weight Optional per-sample weights. * [len = n_samples] + * @param[in] X_norm Optional precomputed squared L2 row norms of X (||x||^2, + * i.e. sum of squares without the sqrt) [n_samples]. When + * provided, the internal norm computation is skipped. */ void cluster_cost( + const raft::resources& handle, + raft::device_matrix_view X, + raft::device_matrix_view centroids, + raft::device_scalar_view cost, + std::optional> sample_weight = std::nullopt, + std::optional> X_norm = std::nullopt); + +/** + * @brief [deprecated] Compute (optionally weighted) cluster cost, writing the result to a host + * scalar. + * + * @param[in] handle The raft handle + * @param[in] X Training instances [n_samples x n_features], row-major + * @param[in] centroids Cluster centroids [n_clusters x n_features], row-major + * @param[out] cost Resulting cluster cost + * @param[in] sample_weight Optional per-sample weights [n_samples] + */ +[[deprecated( + "Pass a raft::device_scalar_view; this host-scalar overload forces a D->H copy and " + "stream sync")]] void +cluster_cost( + const raft::resources& handle, + raft::device_matrix_view X, + raft::device_matrix_view centroids, + raft::host_scalar_view cost, + std::optional> sample_weight = std::nullopt); + +/** + * @brief [deprecated] Compute (optionally weighted) cluster cost, writing the result to a host + * scalar. + * + * @param[in] handle The raft handle + * @param[in] X Training instances [n_samples x n_features], row-major + * @param[in] centroids Cluster centroids [n_clusters x n_features], row-major + * @param[out] cost Resulting cluster cost + * @param[in] sample_weight Optional per-sample weights [n_samples] + */ +[[deprecated( + "Pass a raft::device_scalar_view; this host-scalar overload forces a D->H copy and " + "stream sync")]] void +cluster_cost( + const raft::resources& handle, + raft::device_matrix_view X, + raft::device_matrix_view centroids, + raft::host_scalar_view cost, + std::optional> sample_weight = std::nullopt); + +/** + * @brief [deprecated] Compute (optionally weighted) cluster cost, writing the result to a host + * scalar. + * + * @param[in] handle The raft handle + * @param[in] X Training instances [n_samples x n_features], row-major + * @param[in] centroids Cluster centroids [n_clusters x n_features], row-major + * @param[out] cost Resulting cluster cost + * @param[in] sample_weight Optional per-sample weights [n_samples] + */ +[[deprecated( + "Pass a raft::device_scalar_view; this host-scalar overload forces a D->H copy and " + "stream sync")]] void +cluster_cost( + const raft::resources& handle, + raft::device_matrix_view X, + raft::device_matrix_view centroids, + raft::host_scalar_view cost, + std::optional> sample_weight = std::nullopt); + +/** + * @brief [deprecated] Compute (optionally weighted) cluster cost, writing the result to a host + * scalar. + * + * @param[in] handle The raft handle + * @param[in] X Training instances [n_samples x n_features], row-major + * @param[in] centroids Cluster centroids [n_clusters x n_features], row-major + * @param[out] cost Resulting cluster cost + * @param[in] sample_weight Optional per-sample weights [n_samples] + */ +[[deprecated( + "Pass a raft::device_scalar_view; this host-scalar overload forces a D->H copy and " + "stream sync")]] void +cluster_cost( const raft::resources& handle, raft::device_matrix_view X, raft::device_matrix_view centroids, raft::host_scalar_view cost, std::optional> sample_weight = std::nullopt); + /** * @} */ diff --git a/cpp/src/cluster/detail/kmeans.cuh b/cpp/src/cluster/detail/kmeans.cuh index e3ffb4a439..a57455e1a7 100644 --- a/cpp/src/cluster/detail/kmeans.cuh +++ b/cpp/src/cluster/detail/kmeans.cuh @@ -933,7 +933,11 @@ void kmeans_fit( auto centroids_const = raft::make_device_matrix_view( cur_centroids_ptr, n_clusters, n_features); - iter_inertia = DataT{0}; + auto d_iter_inertia = raft::make_device_scalar(handle, DataT{0}); + auto d_batch_cost = raft::make_device_scalar(handle, DataT{0}); + DataT* p_acc = d_iter_inertia.data_handle(); + DataT* p_batch = d_batch_cost.data_handle(); + data_batches.reset(); using wt_iter_t = cuvs::spatial::knn::detail::utils::batch_load_iterator_dyn; std::optional wt_it; @@ -958,15 +962,33 @@ void kmeans_fit( cur_batch_weights(static_cast(data_batch.offset()), wt_data, cur_batch_size); } - DataT batch_cost = DataT{0}; - cuvs::cluster::kmeans::cluster_cost(handle, - batch_data_view, - centroids_const, - raft::make_host_scalar_view(&batch_cost), - batch_sw); + std::optional> batch_xnorm = std::nullopt; + if (need_compute_norms) { + if constexpr (data_on_device) { + batch_xnorm = raft::make_device_vector_view( + L2NormBatch.data_handle() + data_batch.offset(), cur_batch_size); + } else if (norms_cached) { + raft::copy(L2NormBatch.data_handle(), + h_norm_cache.data_handle() + data_batch.offset(), + cur_batch_size, + stream); + batch_xnorm = raft::make_device_vector_view( + L2NormBatch.data_handle(), cur_batch_size); + } + } + + cuvs::cluster::kmeans::cluster_cost( + handle, batch_data_view, centroids_const, d_batch_cost.view(), batch_sw, batch_xnorm); - iter_inertia += batch_cost; + raft::linalg::map_offset(handle, + raft::make_device_vector_view(p_acc, 1), + [p_acc, p_batch] __device__(int) { return *p_acc + *p_batch; }); } + + raft::copy(handle, + raft::make_host_scalar_view(&iter_inertia), + raft::make_const_mdspan(d_iter_inertia.view())); + raft::resource::sync_stream(handle); } if (iter_inertia < inertia[0]) { diff --git a/cpp/src/cluster/detail/kmeans_balanced.cuh b/cpp/src/cluster/detail/kmeans_balanced.cuh index ac0430b430..02e4680268 100644 --- a/cpp/src/cluster/detail/kmeans_balanced.cuh +++ b/cpp/src/cluster/detail/kmeans_balanced.cuh @@ -1287,8 +1287,21 @@ void build_hierarchical(const raft::resources& handle, reinterpret_cast(dataset), n_rows, dim); auto centroids_view = raft::make_device_matrix_view(cluster_centers, n_clusters, dim); + auto d_inertia = raft::make_device_scalar(handle, MathT{0}); + // Reuse precomputed ||x||^2 for L2 metrics; CosineExpanded stores sqrt norms + // which are incompatible with cluster_cost + std::optional> X_norm = std::nullopt; + if (dataset_norm != nullptr && + (params.metric == cuvs::distance::DistanceType::L2Expanded || + params.metric == cuvs::distance::DistanceType::L2SqrtExpanded)) { + X_norm = raft::make_device_vector_view(dataset_norm, n_rows); + } cuvs::cluster::kmeans::cluster_cost( - handle, X_view, centroids_view, raft::make_host_scalar_view(inertia)); + handle, X_view, centroids_view, d_inertia.view(), std::nullopt, X_norm); + raft::copy(handle, + raft::make_host_scalar_view(inertia), + raft::make_const_mdspan(d_inertia.view())); + raft::resource::sync_stream(handle, stream); } else { RAFT_LOG_WARN("Inertia is not computed for non float/double types"); } diff --git a/cpp/src/cluster/kmeans.cuh b/cpp/src/cluster/kmeans.cuh index f6e2c7d819..cf287b62b1 100644 --- a/cpp/src/cluster/kmeans.cuh +++ b/cpp/src/cluster/kmeans.cuh @@ -331,6 +331,8 @@ void min_cluster_distance(raft::resources const& handle, * @param[in] centroids Cluster centroids [n_clusters x n_features] * @param[out] cost Sum of squared distances to nearest centroid (device) * @param[in] sample_weight Optional per-sample weights [n_samples] + * @param[in] X_norm Optional precomputed squared L2 row norms of X (||x||^2) [n_samples]. + * When provided, the internal norm computation is skipped. */ template void cluster_cost( @@ -338,7 +340,8 @@ void cluster_cost( raft::device_matrix_view X, raft::device_matrix_view centroids, raft::device_scalar_view cost, - std::optional> sample_weight = std::nullopt) + std::optional> sample_weight = std::nullopt, + std::optional> X_norm = std::nullopt) { auto stream = raft::resource::get_cuda_stream(handle); auto n_clusters = centroids.extent(0); @@ -347,8 +350,18 @@ void cluster_cost( rmm::device_uvector workspace(n_samples * sizeof(IndexT), stream); - auto x_norms = raft::make_device_vector(handle, n_samples); - raft::linalg::norm(handle, X, x_norms.view()); + std::optional> x_norms_buf; + DataT* x_norms_ptr; + if (X_norm.has_value()) { + RAFT_EXPECTS(X_norm->extent(0) == n_samples, "X_norm size !=n_samples"); + x_norms_ptr = const_cast(X_norm->data_handle()); + } else { + x_norms_buf.emplace(raft::make_device_vector(handle, n_samples)); + raft::linalg::norm( + handle, X, x_norms_buf->view()); + x_norms_ptr = x_norms_buf->data_handle(); + } + auto x_norms_view = raft::make_device_vector_view(x_norms_ptr, n_samples); auto min_cluster_distance = raft::make_device_vector(handle, n_samples); rmm::device_uvector l2_norm_or_distance_buffer(0, stream); @@ -361,7 +374,7 @@ void cluster_cost( raft::make_device_matrix_view( const_cast(centroids.data_handle()), n_clusters, n_features), min_cluster_distance.view(), - x_norms.view(), + x_norms_view, l2_norm_or_distance_buffer, metric, n_samples, @@ -380,31 +393,16 @@ void cluster_cost( handle, min_cluster_distance.view(), workspace, cost, raft::add_op{}); } -/** - * @brief Compute (optionally weighted) cluster cost (inertia) — host-scalar output. - * - * Convenience wrapper that copies the result to host and synchronizes. - * - * @tparam DataT float or double - * @tparam IndexT Index type - * - * @param[in] handle The raft handle - * @param[in] X Input data [n_samples x n_features] - * @param[in] centroids Cluster centroids [n_clusters x n_features] - * @param[out] cost Sum of squared distances to nearest centroid (host) - * @param[in] sample_weight Optional per-sample weights [n_samples] - */ template -void cluster_cost( - raft::resources const& handle, - raft::device_matrix_view X, - raft::device_matrix_view centroids, - raft::host_scalar_view cost, - std::optional> sample_weight = std::nullopt) +void cluster_cost_host(const raft::resources& handle, + raft::device_matrix_view X, + raft::device_matrix_view centroids, + raft::host_scalar_view cost, + std::optional> sample_weight) { - auto device_cost = raft::make_device_scalar(handle, DataT(0)); - cuvs::cluster::kmeans::cluster_cost(handle, X, centroids, device_cost.view(), sample_weight); - raft::copy(handle, cost, raft::make_const_mdspan(device_cost.view())); + auto d_cost = raft::make_device_scalar(handle, DataT{0}); + cluster_cost(handle, X, centroids, d_cost.view(), sample_weight, std::nullopt); + raft::copy(handle, cost, raft::make_const_mdspan(d_cost.view())); raft::resource::sync_stream(handle); } diff --git a/cpp/src/cluster/kmeans_cluster_cost.cu b/cpp/src/cluster/kmeans_cluster_cost.cu index 0cdc182fb9..05f5315d25 100644 --- a/cpp/src/cluster/kmeans_cluster_cost.cu +++ b/cpp/src/cluster/kmeans_cluster_cost.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION. + * SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ @@ -8,13 +8,58 @@ #include namespace cuvs::cluster::kmeans { +void cluster_cost(const raft::resources& handle, + raft::device_matrix_view X, + raft::device_matrix_view centroids, + raft::device_scalar_view cost, + std::optional> sample_weight, + std::optional> X_norm) +{ + cuvs::cluster::kmeans::cluster_cost( + handle, X, centroids, cost, sample_weight, X_norm); +} + +void cluster_cost(const raft::resources& handle, + raft::device_matrix_view X, + raft::device_matrix_view centroids, + raft::device_scalar_view cost, + std::optional> sample_weight, + std::optional> X_norm) +{ + cuvs::cluster::kmeans::cluster_cost( + handle, X, centroids, cost, sample_weight, X_norm); +} + +void cluster_cost(const raft::resources& handle, + raft::device_matrix_view X, + raft::device_matrix_view centroids, + raft::device_scalar_view cost, + std::optional> sample_weight, + std::optional> X_norm) +{ + cuvs::cluster::kmeans::cluster_cost( + handle, X, centroids, cost, sample_weight, X_norm); +} + +void cluster_cost(const raft::resources& handle, + raft::device_matrix_view X, + raft::device_matrix_view centroids, + raft::device_scalar_view cost, + std::optional> sample_weight, + std::optional> X_norm) +{ + cuvs::cluster::kmeans::cluster_cost( + handle, X, centroids, cost, sample_weight, X_norm); +} + +// Deprecated host-scalar overloads. void cluster_cost(const raft::resources& handle, raft::device_matrix_view X, raft::device_matrix_view centroids, raft::host_scalar_view cost, std::optional> sample_weight) { - cuvs::cluster::kmeans::cluster_cost(handle, X, centroids, cost, sample_weight); + cuvs::cluster::kmeans::cluster_cost_host(handle, X, centroids, cost, sample_weight); } void cluster_cost(const raft::resources& handle, @@ -23,7 +68,7 @@ void cluster_cost(const raft::resources& handle, raft::host_scalar_view cost, std::optional> sample_weight) { - cuvs::cluster::kmeans::cluster_cost(handle, X, centroids, cost, sample_weight); + cuvs::cluster::kmeans::cluster_cost_host(handle, X, centroids, cost, sample_weight); } void cluster_cost(const raft::resources& handle, @@ -32,7 +77,8 @@ void cluster_cost(const raft::resources& handle, raft::host_scalar_view cost, std::optional> sample_weight) { - cuvs::cluster::kmeans::cluster_cost(handle, X, centroids, cost, sample_weight); + cuvs::cluster::kmeans::cluster_cost_host( + handle, X, centroids, cost, sample_weight); } void cluster_cost(const raft::resources& handle, @@ -41,6 +87,7 @@ void cluster_cost(const raft::resources& handle, raft::host_scalar_view cost, std::optional> sample_weight) { - cuvs::cluster::kmeans::cluster_cost(handle, X, centroids, cost, sample_weight); + cuvs::cluster::kmeans::cluster_cost_host( + handle, X, centroids, cost, sample_weight); } } // namespace cuvs::cluster::kmeans diff --git a/cpp/tests/cluster/kmeans.cu b/cpp/tests/cluster/kmeans.cu index 804922a93d..7021fb1f2a 100644 --- a/cpp/tests/cluster/kmeans.cu +++ b/cpp/tests/cluster/kmeans.cu @@ -8,10 +8,12 @@ #include #include +#include #include #include #include #include +#include #include #include #include @@ -20,6 +22,7 @@ #include +#include #include #include @@ -706,4 +709,59 @@ INSTANTIATE_TEST_CASE_P(KmeansFitBatchedTests, KmeansFitBatchedTestD, ::testing::ValuesIn(batched_inputsd2)); +TEST(KmeansClusterCostXNorm, Equivalence) +{ + using T = float; + using IndexT = int; + + raft::resources handle; + auto stream = raft::resource::get_cuda_stream(handle); + + constexpr IndexT n_samples = 500; + constexpr IndexT n_features = 32; + constexpr IndexT n_clusters = 5; + + auto bi = make_kmeans_blob_inputs(handle, n_samples, n_features, n_clusters, false); + auto X_view = raft::make_const_mdspan(bi.d_X.view()); + + auto centroids = raft::make_device_matrix(handle, n_clusters, n_features); + raft::copy(centroids.data_handle(), bi.d_X.data_handle(), n_clusters * n_features, stream); + auto centroids_view = raft::make_const_mdspan(centroids.view()); + + auto cost_internal = raft::make_device_scalar(handle, T{0}); + cuvs::cluster::kmeans::cluster_cost(handle, X_view, centroids_view, cost_internal.view()); + + auto X_norm = raft::make_device_vector(handle, n_samples); + raft::linalg::norm(handle, X_view, X_norm.view()); + + auto cost_supplied = raft::make_device_scalar(handle, T{0}); + cuvs::cluster::kmeans::cluster_cost(handle, + X_view, + centroids_view, + cost_supplied.view(), + std::nullopt, + std::make_optional(raft::make_const_mdspan(X_norm.view()))); + + T h_cost_internal = T{0}; + T h_cost_supplied = T{0}; + raft::copy(&h_cost_internal, cost_internal.data_handle(), 1, stream); + raft::copy(&h_cost_supplied, cost_supplied.data_handle(), 1, stream); + raft::resource::sync_stream(handle, stream); + + ASSERT_TRUE(std::isfinite(h_cost_internal)); + ASSERT_GT(h_cost_internal, T{0}); + ASSERT_NEAR(h_cost_supplied, h_cost_internal, std::abs(h_cost_internal) * T(1e-5)); + + auto bad_X_norm = raft::make_device_vector(handle, n_samples - 1); + auto cost_bad = raft::make_device_scalar(handle, T{0}); + EXPECT_THROW(cuvs::cluster::kmeans::cluster_cost( + handle, + X_view, + centroids_view, + cost_bad.view(), + std::nullopt, + std::make_optional(raft::make_const_mdspan(bad_X_norm.view()))), + raft::logic_error); +} + } // namespace cuvs diff --git a/fern/pages/cpp_api/cpp-api-cluster-kmeans.md b/fern/pages/cpp_api/cpp-api-cluster-kmeans.md index 67e63f5c21..7fee700728 100644 --- a/fern/pages/cpp_api/cpp-api-cluster-kmeans.md +++ b/fern/pages/cpp_api/cpp-api-cluster-kmeans.md @@ -967,8 +967,9 @@ void cluster_cost( const raft::resources& handle, raft::device_matrix_view X, raft::device_matrix_view centroids, -raft::host_scalar_view cost, -std::optional> sample_weight = std::nullopt); +raft::device_scalar_view cost, +std::optional> sample_weight = std::nullopt, +std::optional> X_norm = std::nullopt); ``` **Parameters** @@ -978,8 +979,9 @@ std::optional> sample_weight = std::n | `handle` | in | `const raft::resources&` | The raft handle | | `X` | in | `raft::device_matrix_view` | Training instances to cluster. The data must be in row-major format. [dim = n_samples x n_features] | | `centroids` | in | `raft::device_matrix_view` | Cluster centroids. The data must be in row-major format. [dim = n_clusters x n_features] | -| `cost` | out | `raft::host_scalar_view` | Resulting cluster cost | +| `cost` | out | `raft::device_scalar_view` | Resulting cluster cost | | `sample_weight` | in | `std::optional>` | Optional per-sample weights. [len = n_samples]
Default: `std::nullopt`. | +| `X_norm` | in | `std::optional>` | Optional precomputed squared L2 row norms of X (\|\|x\|\|^2) [n_samples]. When provided, the internal norm computation is skipped.
Default: `std::nullopt`. | **Returns** @@ -994,8 +996,9 @@ void cluster_cost( const raft::resources& handle, raft::device_matrix_view X, raft::device_matrix_view centroids, -raft::host_scalar_view cost, -std::optional> sample_weight = std::nullopt); +raft::device_scalar_view cost, +std::optional> sample_weight = std::nullopt, +std::optional> X_norm = std::nullopt); ``` **Parameters** @@ -1005,8 +1008,9 @@ std::optional> sample_weight = std:: | `handle` | in | `const raft::resources&` | The raft handle | | `X` | in | `raft::device_matrix_view` | Training instances to cluster. The data must be in row-major format. [dim = n_samples x n_features] | | `centroids` | in | `raft::device_matrix_view` | Cluster centroids. The data must be in row-major format. [dim = n_clusters x n_features] | -| `cost` | out | `raft::host_scalar_view` | Resulting cluster cost | +| `cost` | out | `raft::device_scalar_view` | Resulting cluster cost | | `sample_weight` | in | `std::optional>` | Optional per-sample weights. [len = n_samples]
Default: `std::nullopt`. | +| `X_norm` | in | `std::optional>` | Optional precomputed squared L2 row norms of X (\|\|x\|\|^2, i.e. sum of squares without the sqrt) [n_samples]. When provided, the internal norm computation is skipped.
Default: `std::nullopt`. | **Returns** @@ -1021,8 +1025,9 @@ void cluster_cost( const raft::resources& handle, raft::device_matrix_view X, raft::device_matrix_view centroids, -raft::host_scalar_view cost, -std::optional> sample_weight = std::nullopt); +raft::device_scalar_view cost, +std::optional> sample_weight = std::nullopt, +std::optional> X_norm = std::nullopt); ``` **Parameters** @@ -1032,8 +1037,9 @@ std::optional> sample_weight = st | `handle` | in | `const raft::resources&` | The raft handle | | `X` | in | `raft::device_matrix_view` | Training instances to cluster. The data must be in row-major format. [dim = n_samples x n_features] | | `centroids` | in | `raft::device_matrix_view` | Cluster centroids. The data must be in row-major format. [dim = n_clusters x n_features] | -| `cost` | out | `raft::host_scalar_view` | Resulting cluster cost | +| `cost` | out | `raft::device_scalar_view` | Resulting cluster cost | | `sample_weight` | in | `std::optional>` | Optional per-sample weights. [len = n_samples]
Default: `std::nullopt`. | +| `X_norm` | in | `std::optional>` | Optional precomputed squared L2 row norms of X (\|\|x\|\|^2, i.e. sum of squares without the sqrt) [n_samples]. When provided, the internal norm computation is skipped.
Default: `std::nullopt`. | **Returns** @@ -1048,8 +1054,9 @@ void cluster_cost( const raft::resources& handle, raft::device_matrix_view X, raft::device_matrix_view centroids, -raft::host_scalar_view cost, -std::optional> sample_weight = std::nullopt); +raft::device_scalar_view cost, +std::optional> sample_weight = std::nullopt, +std::optional> X_norm = std::nullopt); ``` **Parameters** @@ -1059,8 +1066,9 @@ std::optional> sample_weight = s | `handle` | in | `const raft::resources&` | The raft handle | | `X` | in | `raft::device_matrix_view` | Training instances to cluster. The data must be in row-major format. [dim = n_samples x n_features] | | `centroids` | in | `raft::device_matrix_view` | Cluster centroids. The data must be in row-major format. [dim = n_clusters x n_features] | -| `cost` | out | `raft::host_scalar_view` | Resulting cluster cost | +| `cost` | out | `raft::device_scalar_view` | Resulting cluster cost | | `sample_weight` | in | `std::optional>` | Optional per-sample weights. [len = n_samples]
Default: `std::nullopt`. | +| `X_norm` | in | `std::optional>` | Optional precomputed squared L2 row norms of X (\|\|x\|\|^2, i.e. sum of squares without the sqrt) [n_samples]. When provided, the internal norm computation is skipped.
Default: `std::nullopt`. | **Returns**