diff --git a/cpp/src/stats/detail/batched/silhouette_score.cuh b/cpp/src/stats/detail/batched/silhouette_score.cuh index bb8a75dbf9..f41b843d5c 100644 --- a/cpp/src/stats/detail/batched/silhouette_score.cuh +++ b/cpp/src/stats/detail/batched/silhouette_score.cuh @@ -247,12 +247,15 @@ value_t silhouette_score( raft::resource::sync_stream_pool(handle); - // calculating row-wise minimum in b + // Keep the row-wise reduction output separate from b. The input is an + // n_rows x n_labels matrix, so writing an n_rows vector at b_ptr aliases + // matrix elements that may still be read by the reduction. + rmm::device_uvector b_min(n_rows, stream); raft::linalg::reduce( handle, raft::make_device_matrix_view( b_ptr, n_rows, n_labels), - raft::make_device_vector_view(b_ptr, n_rows), + raft::make_device_vector_view(b_min.data(), n_rows), std::numeric_limits::max(), false, raft::identity_op(), @@ -265,7 +268,7 @@ value_t silhouette_score( cuvs::stats::detail::SilOp(), raft::make_const_mdspan(raft::make_device_vector_view(a_ptr, n_rows)), raft::make_const_mdspan( - raft::make_device_vector_view(b_ptr, n_rows))); + raft::make_device_vector_view(b_min.data(), n_rows))); auto sum = raft::make_device_vector(handle, 1); raft::linalg::reduce( diff --git a/cpp/tests/stats/silhouette_score.cu b/cpp/tests/stats/silhouette_score.cu index 2f0d35450c..15a4c61e81 100644 --- a/cpp/tests/stats/silhouette_score.cu +++ b/cpp/tests/stats/silhouette_score.cu @@ -16,10 +16,12 @@ #include #include +#include #include #include #include #include +#include namespace cuvs { namespace stats { @@ -261,5 +263,71 @@ TEST(silhouetteScore, BatchedStreamPoolOrdering) } } +TEST(silhouetteScore, BatchedMatchesNonBatchedAcrossMetricsAndChunkSizes) +{ + constexpr int64_t n_rows = 1000; + constexpr int64_t n_cols = 2; + constexpr int n_labels = 2; + constexpr float tolerance = 1e-4f; + constexpr std::array chunks{n_rows, n_rows / 3, n_rows / 5}; + constexpr std::array metrics{cuvs::distance::DistanceType::CosineExpanded, + cuvs::distance::DistanceType::L2SqrtUnexpanded, + cuvs::distance::DistanceType::L2Expanded, + cuvs::distance::DistanceType::L1}; + + std::mt19937 rng(170); + std::uniform_real_distribution centers(-1.0f, 1.0f); + std::normal_distribution noise(0.0f, 1.5f); + std::array, n_labels> center{}; + for (auto& c : center) { + for (auto& x : c) { + x = centers(rng); + } + } + std::vector order(n_rows); + for (int64_t i = 0; i < n_rows; ++i) { + order[i] = i; + } + std::shuffle(order.begin(), order.end(), rng); + std::vector X(n_rows * n_cols); + std::vector labels(n_rows); + for (int64_t row = 0; row < n_rows; ++row) { + auto label = static_cast(order[row] / (n_rows / n_labels)); + labels[row] = label; + for (int64_t col = 0; col < n_cols; ++col) { + X[row * n_cols + col] = center[label][col] + noise(rng); + } + } + + raft::resources default_handle; + raft::resources pool_handle; + raft::resource::set_cuda_stream_pool(pool_handle, std::make_shared(4)); + auto stream = raft::resource::get_cuda_stream(default_handle); + + rmm::device_uvector d_X(X.size(), stream); + rmm::device_uvector d_labels(labels.size(), stream); + raft::update_device(d_X.data(), X.data(), X.size(), stream); + raft::update_device(d_labels.data(), labels.data(), labels.size(), stream); + raft::resource::sync_stream(default_handle); + + auto X_view = raft::make_device_matrix_view(d_X.data(), n_rows, n_cols); + auto labels_view = raft::make_device_vector_view(d_labels.data(), n_rows); + + for (auto metric : metrics) { + auto expected = cuvs::stats::silhouette_score( + default_handle, X_view, labels_view, std::nullopt, n_labels, metric); + for (auto const& handle : + {std::pair{"default", &default_handle}, std::pair{"pool", &pool_handle}}) { + for (auto chunk : chunks) { + SCOPED_TRACE(::testing::Message() << "handle=" << handle.first << " metric=" + << static_cast(metric) << " chunk=" << chunk); + auto actual = cuvs::stats::silhouette_score_batched( + *handle.second, X_view, labels_view, std::nullopt, n_labels, chunk, metric); + ASSERT_NEAR(actual, expected, tolerance); + } + } + } +} + } // end namespace stats } // end namespace cuvs