diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 76fd09b58..eabd262ee 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -258,6 +258,20 @@ add_library( src/distance/detail/fused_distance_nn.cu src/distance/distance.cu src/distance/pairwise_distance.cu + src/neighbors/ball_cover.cu + src/neighbors/ball_cover/detail/ball_cover/registers_eps_pass_euclidean.cu + src/neighbors/ball_cover/detail/ball_cover/registers_pass_one_2d_dist.cu + src/neighbors/ball_cover/detail/ball_cover/registers_pass_one_2d_euclidean.cu + src/neighbors/ball_cover/detail/ball_cover/registers_pass_one_2d_haversine.cu + src/neighbors/ball_cover/detail/ball_cover/registers_pass_one_3d_dist.cu + src/neighbors/ball_cover/detail/ball_cover/registers_pass_one_3d_euclidean.cu + src/neighbors/ball_cover/detail/ball_cover/registers_pass_one_3d_haversine.cu + src/neighbors/ball_cover/detail/ball_cover/registers_pass_two_2d_dist.cu + src/neighbors/ball_cover/detail/ball_cover/registers_pass_two_2d_euclidean.cu + src/neighbors/ball_cover/detail/ball_cover/registers_pass_two_2d_haversine.cu + src/neighbors/ball_cover/detail/ball_cover/registers_pass_two_3d_dist.cu + src/neighbors/ball_cover/detail/ball_cover/registers_pass_two_3d_euclidean.cu + src/neighbors/ball_cover/detail/ball_cover/registers_pass_two_3d_haversine.cu src/neighbors/brute_force.cu src/neighbors/cagra_build_float.cu src/neighbors/cagra_build_int8.cu @@ -429,8 +443,7 @@ add_library( target_compile_definitions(cuvs PRIVATE "CUVS_EXPLICIT_INSTANTIATE_ONLY") target_compile_options( - cuvs INTERFACE $<$:--expt-extended-lambda - --expt-relaxed-constexpr> + cuvs PUBLIC $<$:--expt-extended-lambda --expt-relaxed-constexpr> ) add_library(cuvs::cuvs ALIAS cuvs) diff --git a/cpp/include/cuvs/neighbors/ball_cover.hpp b/cpp/include/cuvs/neighbors/ball_cover.hpp new file mode 100644 index 000000000..97365eb78 --- /dev/null +++ b/cpp/include/cuvs/neighbors/ball_cover.hpp @@ -0,0 +1,361 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +#include + +namespace cuvs::neighbors::ball_cover { + +/** + * @ingroup random_ball_cover + * @{ + */ + +/** + * Stores raw index data points, sampled landmarks, the 1-nns of index points + * to their closest landmarks, and the ball radii of each landmark. This + * class is intended to be constructed once and reused across subsequent + * queries. + * @tparam int64_t + * @tparam float + * @tparam int + */ +template +struct index : cuvs::neighbors::index { + public: + explicit index(raft::resources const& handle_, + raft::device_matrix_view X_, + cuvs::distance::DistanceType metric_) + : handle(handle_), + X(X_), + m(X_.extent(0)), + n(X_.extent(1)), + metric(metric_), + /** + * the sqrt() here makes the sqrt(m)^2 a linear-time lower bound + * + * Total memory footprint of index: (2 * sqrt(m)) + (n * sqrt(m)) + (2 * m) + */ + n_landmarks(sqrt(X_.extent(0))), + R_indptr(raft::make_device_vector(handle, sqrt(X_.extent(0)) + 1)), + R_1nn_cols(raft::make_device_vector(handle, X_.extent(0))), + R_1nn_dists(raft::make_device_vector(handle, X_.extent(0))), + R_closest_landmark_dists(raft::make_device_vector(handle, X_.extent(0))), + R(raft::make_device_matrix(handle, sqrt(X_.extent(0)), X_.extent(1))), + X_reordered( + raft::make_device_matrix(handle, X_.extent(0), X_.extent(1))), + R_radius(raft::make_device_vector(handle, sqrt(X_.extent(0)))), + index_trained(false) + { + } + + auto get_R_indptr() const -> raft::device_vector_view + { + return R_indptr.view(); + } + auto get_R_1nn_cols() const -> raft::device_vector_view + { + return R_1nn_cols.view(); + } + auto get_R_1nn_dists() const -> raft::device_vector_view + { + return R_1nn_dists.view(); + } + auto get_R_radius() const -> raft::device_vector_view + { + return R_radius.view(); + } + auto get_R() const -> raft::device_matrix_view + { + return R.view(); + } + auto get_R_closest_landmark_dists() const -> raft::device_vector_view + { + return R_closest_landmark_dists.view(); + } + auto get_X_reordered() const + -> raft::device_matrix_view + { + return X_reordered.view(); + } + + raft::device_vector_view get_R_indptr() { return R_indptr.view(); } + raft::device_vector_view get_R_1nn_cols() { return R_1nn_cols.view(); } + raft::device_vector_view get_R_1nn_dists() { return R_1nn_dists.view(); } + raft::device_vector_view get_R_radius() { return R_radius.view(); } + raft::device_matrix_view get_R() { return R.view(); } + raft::device_vector_view get_R_closest_landmark_dists() + { + return R_closest_landmark_dists.view(); + } + raft::device_matrix_view get_X_reordered() + { + return X_reordered.view(); + } + raft::device_matrix_view get_X() const { return X; } + + cuvs::distance::DistanceType get_metric() const { return metric; } + + int get_n_landmarks() const { return n_landmarks; } + bool is_index_trained() const { return index_trained; }; + + // This should only be set by internal functions + void set_index_trained() { index_trained = true; } + + raft::resources const& handle; + + int_t m; + int_t n; + int_t n_landmarks; + + raft::device_matrix_view X; + + cuvs::distance::DistanceType metric; + + private: + // CSR storing the neighborhoods for each data point + raft::device_vector R_indptr; + raft::device_vector R_1nn_cols; + raft::device_vector R_1nn_dists; + raft::device_vector R_closest_landmark_dists; + + raft::device_vector R_radius; + + raft::device_matrix R; + raft::device_matrix X_reordered; + + protected: + bool index_trained; +}; + +/** @} */ + +/** + * @defgroup random_ball_cover Random Ball Cover algorithm + * @{ + */ + +/** + * Builds and populates a previously unbuilt cuvs::neighbors::ball_cover::index + * + * Usage example: + * @code{.cpp} + * + * #include + * #include + * #include + * using namespace cuvs::neighbors; + * + * raft::resources handle; + * ... + * auto metric = cuvs::distance::DistanceType::L2Expanded; + * ball_cover::index index(handle, X, metric); + * ball_cover::build_index(handle, index); + * @endcode + * + * @param[in] handle library resource management handle + * @param[inout] index an empty (and not previous built) instance of + * cuvs::neighbors::ball_cover::index + */ +void build(raft::resources const& handle, index& index); + +/** @} */ // end group random_ball_cover + +/** + * @ingroup random_ball_cover + * @{ + */ + +/** + * Performs a faster exact knn in metric spaces using the triangle + * inequality with a number of landmark points to reduce the + * number of distance computations from O(n^2) to O(sqrt(n)). This + * performs an all neighbors knn, which can reuse memory when + * the index and query are the same array. This function will + * build the index and assumes rbc_build_index() has not already + * been called. + * + * Usage example: + * @code{.cpp} + * + * #include + * #include + * #include + * using namespace cuvs::neighbors; + * + * raft::resources handle; + * ... + * auto metric = cuvs::distance::DistanceType::L2Expanded; + * + * // Construct a ball cover index + * ball_cover::index index(handle, X, metric); + * + * // Perform all neighbors knn query + * ball_cover::all_knn_query(handle, index, inds, dists, k); + * @endcode + * + * @param[in] handle raft handle for resource management + * @param[in] index ball cover index which has not yet been built + * @param[out] inds output knn indices + * @param[out] dists output knn distances + * @param[in] k number of nearest neighbors to find + * @param[in] perform_post_filtering if this is false, only the closest k landmarks + * are considered (which will return approximate + * results). + * @param[in] weight a weight for overlap between the closest landmark and + * the radius of other landmarks when pruning distances. + * Setting this value below 1 can effectively turn off + * computing distances against many other balls, enabling + * approximate nearest neighbors. Recall can be adjusted + * based on how many relevant balls are ignored. Note that + * many datasets can still have great recall even by only + * looking in the closest landmark. + */ +void all_knn_query(raft::resources const& handle, + index& index, + raft::device_matrix_view inds, + raft::device_matrix_view dists, + int k, + bool perform_post_filtering = true, + float weight = 1.0); + +/** @} */ + +/** + * @brief Computes epsilon neighborhood for the L2 distance metric using rbc + * + * @param[in] handle raft handle for resource management + * @param[in] index ball cover index which has been built + * @param[out] adj adjacency matrix [row-major] [on device] [dim = m x n] + * @param[out] vd vertex degree array [on device] [len = m + 1] + * `vd + m` stores the total number of edges in the adjacency + * matrix. Pass a nullptr if you don't need this info. + * @param[in] query first matrix [row-major] [on device] [dim = m x k] + * @param[in] eps defines epsilon neighborhood radius + */ +void eps_nn(raft::resources const& handle, + const index& index, + raft::device_matrix_view adj, + raft::device_vector_view vd, + raft::device_matrix_view query, + float eps); +/** + * @brief Computes epsilon neighborhood for the L2 distance metric using rbc + * + * @param[in] handle raft handle for resource management + * @param[in] index ball cover index which has been built + * @param[out] adj_ia adjacency matrix CSR row offsets + * @param[out] adj_ja adjacency matrix CSR column indices, needs to be nullptr + * in first pass with max_k nullopt + * @param[out] vd vertex degree array [on device] [len = m + 1] + * `vd + m` stores the total number of edges in the adjacency + * matrix. Pass a nullptr if you don't need this info. + * @param[in] query first matrix [row-major] [on device] [dim = m x k] + * @param[in] eps defines epsilon neighborhood radius + * @param[inout] max_k if nullopt (default), the user needs to make 2 subsequent calls: + * The first call computes row offsets in adj_ia, where adj_ia[m] + * contains the minimum required size for adj_ja. + * The second call fills in adj_ja based on adj_ia. + * If max_k != nullopt the algorithm only fills up neighbors up to a + * maximum number of max_k for each row in a single pass. Note + * that it is not guarantueed to return the nearest neighbors. + * Upon return max_k is overwritten with the actual max_k found during + * computation. + */ +void eps_nn(raft::resources const& handle, + const index& index, + raft::device_vector_view adj_ia, + raft::device_vector_view adj_ja, + raft::device_vector_view vd, + raft::device_matrix_view query, + float eps, + std::optional> max_k = std::nullopt); + +/** + * @ingroup random_ball_cover + * @{ + */ + +/** + * Performs a faster exact knn in metric spaces using the triangle + * inequality with a number of landmark points to reduce the + * number of distance computations from O(n^2) to O(sqrt(n)). This + * function does not build the index and assumes rbc_build_index() has + * already been called. Use this function when the index and + * query arrays are different, otherwise use rbc_all_knn_query(). + * + * Usage example: + * @code{.cpp} + * + * #include + * #include + * #include + * using namespace cuvs::neighbors; + * + * raft::resources handle; + * ... + * auto metric = cuvs::distance::DistanceType::L2Expanded; + * + * // Build a ball cover index + * ball_cover::index index(handle, X, metric); + * ball_cover::build_index(handle, index); + * + * // Perform all neighbors knn query + * ball_cover::knn_query(handle, index, inds, dists, k); + * @endcode + * @param[in] handle raft handle for resource management + * @param[in] index ball cover index which has not yet been built + * @param[in] query device matrix containing query data points + * @param[out] inds output knn indices + * @param[out] dists output knn distances + * @param[in] k number of nearest neighbors to find + * @param[in] perform_post_filtering if this is false, only the closest k landmarks + * are considered (which will return approximate + * results). + * @param[in] weight a weight for overlap between the closest landmark and + * the radius of other landmarks when pruning distances. + * Setting this value below 1 can effectively turn off + * computing distances against many other balls, enabling + * approximate nearest neighbors. Recall can be adjusted + * based on how many relevant balls are ignored. Note that + * many datasets can still have great recall even by only + * looking in the closest landmark. + */ +void knn_query(raft::resources const& handle, + const index& index, + raft::device_matrix_view query, + raft::device_matrix_view inds, + raft::device_matrix_view dists, + int k, + bool perform_post_filtering = true, + float weight = 1.0); + +/** @} */ + +} // namespace cuvs::neighbors::ball_cover diff --git a/cpp/include/cuvs/neighbors/ivf_pq.hpp b/cpp/include/cuvs/neighbors/ivf_pq.hpp index b2db96686..f992a66bb 100644 --- a/cpp/include/cuvs/neighbors/ivf_pq.hpp +++ b/cpp/include/cuvs/neighbors/ivf_pq.hpp @@ -125,7 +125,7 @@ struct index_params : cuvs::neighbors::index_params { * // create index_params for a [N. D] dataset and have InnerProduct as the distance metric * auto dataset = raft::make_device_matrix(res, N, D); * ivf_pq::index_params index_params = - * ivf_pq::index_params::from_dataset(dataset.extents(), raft::distance::InnerProduct); + * ivf_pq::index_params::from_dataset(dataset.extents(), cuvs::distance::InnerProduct); * // modify/update index_params as needed * index_params.add_data_on_build = true; * @endcode diff --git a/cpp/src/neighbors/ball_cover.cu b/cpp/src/neighbors/ball_cover.cu new file mode 100644 index 000000000..6726a9731 --- /dev/null +++ b/cpp/src/neighbors/ball_cover.cu @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2021-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ball_cover.cuh" +#include + +namespace cuvs::neighbors::ball_cover { + +void build(raft::resources const& handle, + cuvs::neighbors::ball_cover::index& index) +{ + detail::build_index(handle, index); +} + +void all_knn_query(raft::resources const& handle, + cuvs::neighbors::ball_cover::index& index, + raft::device_matrix_view inds, + raft::device_matrix_view dists, + int k, + bool perform_post_filtering, + float weight) +{ + detail::all_knn_query( + handle, index, inds, dists, k, perform_post_filtering, weight); +} + +void eps_nn(raft::resources const& handle, + const cuvs::neighbors::ball_cover::index& index, + raft::device_matrix_view adj, + raft::device_vector_view vd, + raft::device_matrix_view query, + float eps) +{ + detail::eps_nn(handle, index, adj, vd, query, eps); +} + +void eps_nn(raft::resources const& handle, + const cuvs::neighbors::ball_cover::index& index, + raft::device_vector_view adj_ia, + raft::device_vector_view adj_ja, + raft::device_vector_view vd, + raft::device_matrix_view query, + float eps, + std::optional> max_k) +{ + detail::eps_nn( + handle, index, adj_ia, adj_ja, vd, query, eps, max_k); +} + +void knn_query(raft::resources const& handle, + const cuvs::neighbors::ball_cover::index& index, + raft::device_matrix_view query, + raft::device_matrix_view inds, + raft::device_matrix_view dists, + int k, + bool perform_post_filtering, + float weight) +{ + detail::knn_query( + handle, index, query, inds, dists, k, perform_post_filtering, weight); +} + +} // namespace cuvs::neighbors::ball_cover \ No newline at end of file diff --git a/cpp/src/neighbors/ball_cover.cuh b/cpp/src/neighbors/ball_cover.cuh new file mode 100644 index 000000000..40a34bd71 --- /dev/null +++ b/cpp/src/neighbors/ball_cover.cuh @@ -0,0 +1,494 @@ +/* + * Copyright (c) 2021-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "ball_cover/ball_cover.cuh" +#include "ball_cover/common.cuh" +#include +#include + +#include + +#include + +namespace cuvs::neighbors::ball_cover::detail { + +/** + * @defgroup random_ball_cover Random Ball Cover algorithm + * @{ + */ + +/** + * Builds and populates a previously unbuilt cuvs::neighbors::ball_cover::index + * + * Usage example: + * @code{.cpp} + * + * #include + * #include + * #include + * using namespace raft::neighbors; + * + * raft::resources handle; + * ... + * auto metric = cuvs::distance::DistanceType::L2Expanded; + * cuvs::neighbors::ball_cover::index index(handle, X, metric); + * + * ball_cover::build_index(handle, index); + * @endcode + * + * @tparam idx_t knn index type + * @tparam value_t knn value type + * @tparam int_t integral type for knn params + * @tparam matrix_idx_t matrix indexing type + * @param[in] handle library resource management handle + * @param[inout] index an empty (and not previous built) instance of + * cuvs::neighbors::ball_cover::index + */ +template +void build_index(raft::resources const& handle, + cuvs::neighbors::ball_cover::index& index) +{ + if (index.metric == cuvs::distance::DistanceType::Haversine) { + cuvs::neighbors::ball_cover::detail::rbc_build_index( + handle, index, cuvs::neighbors::ball_cover::detail::HaversineFunc()); + } else if (index.metric == cuvs::distance::DistanceType::L2SqrtExpanded || + index.metric == cuvs::distance::DistanceType::L2SqrtUnexpanded) { + cuvs::neighbors::ball_cover::detail::rbc_build_index( + handle, index, cuvs::neighbors::ball_cover::detail::EuclideanFunc()); + } else { + RAFT_FAIL("Metric not support"); + } + + index.set_index_trained(); +} + +/** @} */ // end group random_ball_cover + +/** + * Performs a faster exact knn in metric spaces using the triangle + * inequality with a number of landmark points to reduce the + * number of distance computations from O(n^2) to O(sqrt(n)). This + * performs an all neighbors knn, which can reuse memory when + * the index and query are the same array. This function will + * build the index and assumes rbc_build_index() has not already + * been called. + * @tparam idx_t knn index type + * @tparam value_t knn distance type + * @tparam int_t type for integers, such as number of rows/cols + * @param[in] handle raft handle for resource management + * @param[inout] index ball cover index which has not yet been built + * @param[in] k number of nearest neighbors to find + * @param[in] perform_post_filtering if this is false, only the closest k landmarks + * are considered (which will return approximate + * results). + * @param[out] inds output knn indices + * @param[out] dists output knn distances + * @param[in] weight a weight for overlap between the closest landmark and + * the radius of other landmarks when pruning distances. + * Setting this value below 1 can effectively turn off + * computing distances against many other balls, enabling + * approximate nearest neighbors. Recall can be adjusted + * based on how many relevant balls are ignored. Note that + * many datasets can still have great recall even by only + * looking in the closest landmark. + */ +template +void all_knn_query(raft::resources const& handle, + cuvs::neighbors::ball_cover::index& index, + int_t k, + idx_t* inds, + value_t* dists, + bool perform_post_filtering = true, + float weight = 1.0) +{ + ASSERT(index.n <= 3, "only 2d and 3d vectors are supported in current implementation"); + if (index.metric == cuvs::distance::DistanceType::Haversine) { + cuvs::neighbors::ball_cover::detail::rbc_all_knn_query( + handle, + index, + k, + inds, + dists, + cuvs::neighbors::ball_cover::detail::HaversineFunc(), + perform_post_filtering, + weight); + } else if (index.metric == cuvs::distance::DistanceType::L2SqrtExpanded || + index.metric == cuvs::distance::DistanceType::L2SqrtUnexpanded) { + cuvs::neighbors::ball_cover::detail::rbc_all_knn_query( + handle, + index, + k, + inds, + dists, + cuvs::neighbors::ball_cover::detail::EuclideanFunc(), + perform_post_filtering, + weight); + } else { + RAFT_FAIL("Metric not supported"); + } + + index.set_index_trained(); +} + +/** + * @ingroup random_ball_cover + * @{ + */ + +/** + * Performs a faster exact knn in metric spaces using the triangle + * inequality with a number of landmark points to reduce the + * number of distance computations from O(n^2) to O(sqrt(n)). This + * performs an all neighbors knn, which can reuse memory when + * the index and query are the same array. This function will + * build the index and assumes rbc_build_index() has not already + * been called. + * + * Usage example: + * @code{.cpp} + * + * #include + * #include + * #include + * using namespace raft::neighbors; + * + * raft::resources handle; + * ... + * auto metric = cuvs::distance::DistanceType::L2Expanded; + * + * // Construct a ball cover index + * cuvs::neighbors::ball_cover::index index(handle, X, metric); + * + * // Perform all neighbors knn query + * ball_cover::all_knn_query(handle, index, inds, dists, k); + * @endcode + * + * @tparam idx_t knn index type + * @tparam value_t knn distance type + * @tparam int_t type for integers, such as number of rows/cols + * @tparam matrix_idx_t matrix indexing type + * + * @param[in] handle raft handle for resource management + * @param[in] index ball cover index which has not yet been built + * @param[out] inds output knn indices + * @param[out] dists output knn distances + * @param[in] k number of nearest neighbors to find + * @param[in] perform_post_filtering if this is false, only the closest k landmarks + * are considered (which will return approximate + * results). + * @param[in] weight a weight for overlap between the closest landmark and + * the radius of other landmarks when pruning distances. + * Setting this value below 1 can effectively turn off + * computing distances against many other balls, enabling + * approximate nearest neighbors. Recall can be adjusted + * based on how many relevant balls are ignored. Note that + * many datasets can still have great recall even by only + * looking in the closest landmark. + */ +template +void all_knn_query(raft::resources const& handle, + cuvs::neighbors::ball_cover::index& index, + raft::device_matrix_view inds, + raft::device_matrix_view dists, + int_t k, + bool perform_post_filtering = true, + float weight = 1.0) +{ + RAFT_EXPECTS(index.n <= 3, "only 2d and 3d vectors are supported in current implementation"); + RAFT_EXPECTS(k <= index.m, + "k must be less than or equal to the number of data points in the index"); + RAFT_EXPECTS(inds.extent(1) == dists.extent(1) && dists.extent(1) == static_cast(k), + "Number of columns in output indices and distances matrices must be equal to k"); + + RAFT_EXPECTS(inds.extent(0) == dists.extent(0) && dists.extent(0) == index.get_X().extent(0), + "Number of rows in output indices and distances matrices must equal number of rows " + "in index matrix."); + + all_knn_query( + handle, index, k, inds.data_handle(), dists.data_handle(), perform_post_filtering, weight); +} + +/** @} */ + +/** + * Performs a faster exact knn in metric spaces using the triangle + * inequality with a number of landmark points to reduce the + * number of distance computations from O(n^2) to O(sqrt(n)). This + * function does not build the index and assumes rbc_build_index() has + * already been called. Use this function when the index and + * query arrays are different, otherwise use rbc_all_knn_query(). + * @tparam idx_t index type + * @tparam value_t distances type + * @tparam int_t integer type for size info + * @param[in] handle raft handle for resource management + * @param[inout] index ball cover index which has not yet been built + * @param[in] k number of nearest neighbors to find + * @param[in] query the + * @param[in] perform_post_filtering if this is false, only the closest k landmarks + * are considered (which will return approximate + * results). + * @param[out] inds output knn indices + * @param[out] dists output knn distances + * @param[in] weight a weight for overlap between the closest landmark and + * the radius of other landmarks when pruning distances. + * Setting this value below 1 can effectively turn off + * computing distances against many other balls, enabling + * approximate nearest neighbors. Recall can be adjusted + * based on how many relevant balls are ignored. Note that + * many datasets can still have great recall even by only + * looking in the closest landmark. + * @param[in] n_query_pts number of query points + */ +template +void knn_query(raft::resources const& handle, + const cuvs::neighbors::ball_cover::index& index, + int_t k, + const value_t* query, + int_t n_query_pts, + idx_t* inds, + value_t* dists, + bool perform_post_filtering = true, + float weight = 1.0) +{ + ASSERT(index.n <= 3, "only 2d and 3d vectors are supported in current implementation"); + if (index.metric == cuvs::distance::DistanceType::Haversine) { + cuvs::neighbors::ball_cover::detail::rbc_knn_query( + handle, + index, + k, + query, + n_query_pts, + inds, + dists, + cuvs::neighbors::ball_cover::detail::HaversineFunc(), + perform_post_filtering, + weight); + } else if (index.metric == cuvs::distance::DistanceType::L2SqrtExpanded || + index.metric == cuvs::distance::DistanceType::L2SqrtUnexpanded) { + cuvs::neighbors::ball_cover::detail::rbc_knn_query( + handle, + index, + k, + query, + n_query_pts, + inds, + dists, + cuvs::neighbors::ball_cover::detail::EuclideanFunc(), + perform_post_filtering, + weight); + } else { + RAFT_FAIL("Metric not supported"); + } +} + +/** + * @brief Computes epsilon neighborhood for the L2 distance metric using rbc + * + * @tparam value_t IO and math type + * @tparam idx_t Index type + * + * @param[in] handle raft handle for resource management + * @param[in] index ball cover index which has been built + * @param[out] adj adjacency matrix [row-major] [on device] [dim = m x n] + * @param[out] vd vertex degree array [on device] [len = m + 1] + * `vd + m` stores the total number of edges in the adjacency + * matrix. Pass a nullptr if you don't need this info. + * @param[in] query first matrix [row-major] [on device] [dim = m x k] + * @param[in] eps defines epsilon neighborhood radius + */ +template +void eps_nn(raft::resources const& handle, + const cuvs::neighbors::ball_cover::index& index, + raft::device_matrix_view adj, + raft::device_vector_view vd, + raft::device_matrix_view query, + value_t eps) +{ + ASSERT(index.n == query.extent(1), "vector dimension needs to be the same for index and queries"); + ASSERT(index.metric == cuvs::distance::DistanceType::L2SqrtExpanded || + index.metric == cuvs::distance::DistanceType::L2SqrtUnexpanded, + "Metric not supported"); + ASSERT(index.is_index_trained(), "index must be previously trained"); + + // run query + cuvs::neighbors::ball_cover::detail::rbc_eps_nn_query( + handle, + index, + eps, + query.data_handle(), + query.extent(0), + adj.data_handle(), + vd.data_handle(), + cuvs::neighbors::ball_cover::detail::EuclideanSqFunc()); +} + +/** + * @brief Computes epsilon neighborhood for the L2 distance metric using rbc + * + * @tparam value_t IO and math type + * @tparam idx_t Index type + * + * @param[in] handle raft handle for resource management + * @param[in] index ball cover index which has been built + * @param[out] adj_ia adjacency matrix CSR row offsets + * @param[out] adj_ja adjacency matrix CSR column indices, needs to be nullptr + * in first pass with max_k nullopt + * @param[out] vd vertex degree array [on device] [len = m + 1] + * `vd + m` stores the total number of edges in the adjacency + * matrix. Pass a nullptr if you don't need this info. + * @param[in] query first matrix [row-major] [on device] [dim = m x k] + * @param[in] eps defines epsilon neighborhood radius + * @param[inout] max_k if nullopt (default), the user needs to make 2 subsequent calls: + * The first call computes row offsets in adj_ia, where adj_ia[m] + * contains the minimum required size for adj_ja. + * The second call fills in adj_ja based on adj_ia. + * If max_k != nullopt the algorithm only fills up neighbors up to a + * maximum number of max_k for each row in a single pass. Note + * that it is not guarantueed to return the nearest neighbors. + * Upon return max_k is overwritten with the actual max_k found during + * computation. + */ +template +void eps_nn(raft::resources const& handle, + const cuvs::neighbors::ball_cover::index& index, + raft::device_vector_view adj_ia, + raft::device_vector_view adj_ja, + raft::device_vector_view vd, + raft::device_matrix_view query, + value_t eps, + std::optional> max_k = std::nullopt) +{ + ASSERT(index.n == query.extent(1), "vector dimension needs to be the same for index and queries"); + ASSERT(index.metric == cuvs::distance::DistanceType::L2SqrtExpanded || + index.metric == cuvs::distance::DistanceType::L2SqrtUnexpanded, + "Metric not supported"); + ASSERT(index.is_index_trained(), "index must be previously trained"); + + int_t* max_k_ptr = nullptr; + if (max_k.has_value()) { max_k_ptr = max_k.value().data_handle(); } + + // run query + cuvs::neighbors::ball_cover::detail::rbc_eps_nn_query( + handle, + index, + eps, + max_k_ptr, + query.data_handle(), + query.extent(0), + adj_ia.data_handle(), + adj_ja.data_handle(), + vd.data_handle(), + cuvs::neighbors::ball_cover::detail::EuclideanSqFunc()); +} + +/** + * @ingroup random_ball_cover + * @{ + */ + +/** + * Performs a faster exact knn in metric spaces using the triangle + * inequality with a number of landmark points to reduce the + * number of distance computations from O(n^2) to O(sqrt(n)). This + * function does not build the index and assumes rbc_build_index() has + * already been called. Use this function when the index and + * query arrays are different, otherwise use rbc_all_knn_query(). + * + * Usage example: + * @code{.cpp} + * + * #include + * #include + * #include + * using namespace raft::neighbors; + * + * raft::resources handle; + * ... + * auto metric = cuvs::distance::DistanceType::L2Expanded; + * + * // Build a ball cover index + * cuvs::neighbors::ball_cover::index index(handle, X, metric); + * ball_cover::build_index(handle, index); + * + * // Perform all neighbors knn query + * ball_cover::knn_query(handle, index, inds, dists, k); + * @endcode + + * + * @tparam idx_t index type + * @tparam value_t distances type + * @tparam int_t integer type for size info + * @tparam matrix_idx_t + * @param[in] handle raft handle for resource management + * @param[in] index ball cover index which has not yet been built + * @param[in] query device matrix containing query data points + * @param[out] inds output knn indices + * @param[out] dists output knn distances + * @param[in] k number of nearest neighbors to find + * @param[in] perform_post_filtering if this is false, only the closest k landmarks + * are considered (which will return approximate + * results). + * @param[in] weight a weight for overlap between the closest landmark and + * the radius of other landmarks when pruning distances. + * Setting this value below 1 can effectively turn off + * computing distances against many other balls, enabling + * approximate nearest neighbors. Recall can be adjusted + * based on how many relevant balls are ignored. Note that + * many datasets can still have great recall even by only + * looking in the closest landmark. + */ +template +void knn_query(raft::resources const& handle, + const cuvs::neighbors::ball_cover::index& index, + raft::device_matrix_view query, + raft::device_matrix_view inds, + raft::device_matrix_view dists, + int_t k, + bool perform_post_filtering = true, + float weight = 1.0) +{ + RAFT_EXPECTS(k <= index.m, + "k must be less than or equal to the number of data points in the index"); + RAFT_EXPECTS(inds.extent(1) == dists.extent(1) && dists.extent(1) == static_cast(k), + "Number of columns in output indices and distances matrices must be equal to k"); + + RAFT_EXPECTS(inds.extent(0) == dists.extent(0) && dists.extent(0) == query.extent(0), + "Number of rows in output indices and distances matrices must equal number of rows " + "in search matrix."); + + RAFT_EXPECTS(query.extent(1) == index.get_X().extent(1), + "Number of columns in query and index matrices must match."); + + knn_query(handle, + index, + k, + query.data_handle(), + (int_t)query.extent(0), + inds.data_handle(), + dists.data_handle(), + perform_post_filtering, + weight); +} + +/** @} */ + +// TODO: implement functions for: +// 4. rbc_eps_neigh() - given a populated index, perform query against different query array +// 5. rbc_all_eps_neigh() - populate a cuvs::neighbors::ball_cover::index and query against +// training data + +} // namespace cuvs::neighbors::ball_cover::detail \ No newline at end of file diff --git a/cpp/src/neighbors/ball_cover/ball_cover.cuh b/cpp/src/neighbors/ball_cover/ball_cover.cuh new file mode 100644 index 000000000..d8a1410a6 --- /dev/null +++ b/cpp/src/neighbors/ball_cover/ball_cover.cuh @@ -0,0 +1,718 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "../detail/haversine_distance.cuh" +#include "common.cuh" +#include "registers.cuh" +#include "registers_types.cuh" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +namespace cuvs::neighbors::ball_cover::detail { + +/** + * Given a set of points in row-major order which are to be + * used as a set of index points, uniformly samples a subset + * of points to be used as landmarks. + * @tparam value_idx + * @tparam value_t + * @param handle + * @param index + */ +template +void sample_landmarks( + raft::resources const& handle, + cuvs::neighbors::ball_cover::index& index) +{ + rmm::device_uvector R_1nn_cols2(index.n_landmarks, + raft::resource::get_cuda_stream(handle)); + rmm::device_uvector R_1nn_ones(index.m, raft::resource::get_cuda_stream(handle)); + rmm::device_uvector R_indices(index.n_landmarks, + raft::resource::get_cuda_stream(handle)); + + thrust::sequence(raft::resource::get_thrust_policy(handle), + index.get_R_1nn_cols().data_handle(), + index.get_R_1nn_cols().data_handle() + index.m, + (value_idx)0); + + thrust::fill(raft::resource::get_thrust_policy(handle), + R_1nn_ones.data(), + R_1nn_ones.data() + R_1nn_ones.size(), + 1.0); + + thrust::fill(raft::resource::get_thrust_policy(handle), + R_indices.data(), + R_indices.data() + R_indices.size(), + 0.0); + + /** + * 1. Randomly sample sqrt(n) points from X + */ + raft::random::RngState rng_state(12345); + raft::random::sampleWithoutReplacement(handle, + rng_state, + R_indices.data(), + R_1nn_cols2.data(), + index.get_R_1nn_cols().data_handle(), + R_1nn_ones.data(), + (value_idx)index.n_landmarks, + (value_idx)index.m); + + auto x = index.get_X(); + auto r = index.get_R(); + + raft::matrix::copy_rows( + handle, + raft::make_device_matrix_view( + x.data_handle(), x.extent(0), x.extent(1)), + raft::make_device_matrix_view(r.data_handle(), r.extent(0), r.extent(1)), + raft::make_device_vector_view(R_1nn_cols2.data(), index.n_landmarks)); +} + +/** + * Constructs a 1-nn index mapping each landmark to their closest points. + * @tparam value_idx + * @tparam value_t + * @param handle + * @param R_knn_inds_ptr + * @param R_knn_dists_ptr + * @param k + * @param index + */ +template +void construct_landmark_1nn( + raft::resources const& handle, + const value_idx* R_knn_inds_ptr, + const value_t* R_knn_dists_ptr, + value_int k, + cuvs::neighbors::ball_cover::index& index) +{ + rmm::device_uvector R_1nn_inds(index.m, raft::resource::get_cuda_stream(handle)); + + thrust::fill(raft::resource::get_thrust_policy(handle), + R_1nn_inds.data(), + R_1nn_inds.data() + index.m, + std::numeric_limits::max()); + + value_idx* R_1nn_inds_ptr = R_1nn_inds.data(); + value_t* R_1nn_dists_ptr = index.get_R_1nn_dists().data_handle(); + + auto idxs = thrust::make_counting_iterator(0); + thrust::for_each( + raft::resource::get_thrust_policy(handle), idxs, idxs + index.m, [=] __device__(value_idx i) { + R_1nn_inds_ptr[i] = R_knn_inds_ptr[i * k]; + R_1nn_dists_ptr[i] = R_knn_dists_ptr[i * k]; + }); + + auto keys = thrust::make_zip_iterator( + thrust::make_tuple(R_1nn_inds.data(), index.get_R_1nn_dists().data_handle())); + + // group neighborhoods for each reference landmark and sort each group by distance + thrust::sort_by_key(raft::resource::get_thrust_policy(handle), + keys, + keys + index.m, + index.get_R_1nn_cols().data_handle(), + NNComp()); + + // convert to CSR for fast lookup + raft::sparse::convert::sorted_coo_to_csr(R_1nn_inds.data(), + index.m, + index.get_R_indptr().data_handle(), + index.n_landmarks + 1, + raft::resource::get_cuda_stream(handle)); + + // reorder X to allow aligned access + raft::matrix::copy_rows( + handle, index.get_X(), index.get_X_reordered(), index.get_R_1nn_cols()); +} + +/** + * Computes the k closest landmarks to a set of query points. + * @tparam value_idx + * @tparam value_t + * @tparam value_int + * @param handle + * @param index + * @param query_pts + * @param n_query_pts + * @param k + * @param R_knn_inds + * @param R_knn_dists + */ +template +void k_closest_landmarks( + raft::resources const& handle, + const cuvs::neighbors::ball_cover::index& index, + const value_t* query_pts, + value_int n_query_pts, + value_int k, + value_idx* R_knn_inds, + value_t* R_knn_dists) +{ + raft::device_matrix_view inputs = index.get_R(); + + auto bfknn = cuvs::neighbors::brute_force::build(handle, inputs, index.get_metric()); + cuvs::neighbors::brute_force::search( + handle, + bfknn, + raft::make_device_matrix_view(query_pts, n_query_pts, inputs.extent(1)), + raft::make_device_matrix_view(R_knn_inds, n_query_pts, k), + raft::make_device_matrix_view(R_knn_dists, n_query_pts, k), + std::nullopt); +} + +/** + * Uses the sorted data points in the 1-nn landmark index to compute + * an array of radii for each landmark. + * @tparam value_idx + * @tparam value_t + * @param handle + * @param index + */ +template +void compute_landmark_radii( + raft::resources const& handle, + cuvs::neighbors::ball_cover::index& index) +{ + auto entries = thrust::make_counting_iterator(0); + + const value_idx* R_indptr_ptr = index.get_R_indptr().data_handle(); + const value_t* R_1nn_dists_ptr = index.get_R_1nn_dists().data_handle(); + value_t* R_radius_ptr = index.get_R_radius().data_handle(); + thrust::for_each(raft::resource::get_thrust_policy(handle), + entries, + entries + index.n_landmarks, + [=] __device__(value_idx input) { + value_idx last_row_idx = R_indptr_ptr[input + 1] - 1; + R_radius_ptr[input] = R_1nn_dists_ptr[last_row_idx]; + }); +} + +/** + * 4. Perform k-select over original KNN, using L_r to filter distances + * + * a. Map 1 row to each warp/block + * b. Add closest k R points to heap + * c. Iterate through batches of R, having each thread in the warp load a set + * of distances y from R (only if d(q, r) < 3 * distance to closest r) and + * marking the distance to be computed between x, y only + * if knn[k].distance >= d(x_i, R_k) + d(R_k, y) + */ +template +void perform_rbc_query( + raft::resources const& handle, + const cuvs::neighbors::ball_cover::index& index, + const value_t* query, + value_int n_query_pts, + value_int k, + const value_idx* R_knn_inds, + const value_t* R_knn_dists, + dist_func dfunc, + value_idx* inds, + value_t* dists, + value_int* dists_counter, + value_int* post_dists_counter, + float weight = 1.0, + bool perform_post_filtering = true) +{ + // initialize output inds and dists + thrust::fill(raft::resource::get_thrust_policy(handle), + inds, + inds + (k * n_query_pts), + std::numeric_limits::max()); + thrust::fill(raft::resource::get_thrust_policy(handle), + dists, + dists + (k * n_query_pts), + std::numeric_limits::max()); + + if (index.n == 2) { + // Compute nearest k for each neighborhood in each closest R + rbc_low_dim_pass_one(handle, + index, + query, + n_query_pts, + k, + R_knn_inds, + R_knn_dists, + dfunc, + inds, + dists, + weight, + dists_counter); + + if (perform_post_filtering) { + rbc_low_dim_pass_two(handle, + index, + query, + n_query_pts, + k, + R_knn_inds, + R_knn_dists, + dfunc, + inds, + dists, + weight, + post_dists_counter); + } + + } else if (index.n == 3) { + // Compute nearest k for each neighborhood in each closest R + rbc_low_dim_pass_one(handle, + index, + query, + n_query_pts, + k, + R_knn_inds, + R_knn_dists, + dfunc, + inds, + dists, + weight, + dists_counter); + + if (perform_post_filtering) { + rbc_low_dim_pass_two(handle, + index, + query, + n_query_pts, + k, + R_knn_inds, + R_knn_dists, + dfunc, + inds, + dists, + weight, + post_dists_counter); + } + } +} + +/** + * Perform eps-select + * + */ +template +void perform_rbc_eps_nn_query( + raft::resources const& handle, + const cuvs::neighbors::ball_cover::index& index, + const value_t* query, + value_int n_query_pts, + value_t eps, + const value_t* landmarks, + dist_func dfunc, + bool* adj, + value_idx* vd) +{ + // initialize output + RAFT_CUDA_TRY(cudaMemsetAsync( + adj, 0, index.m * n_query_pts * sizeof(bool), raft::resource::get_cuda_stream(handle))); + + raft::resource::sync_stream(handle); + + rbc_eps_pass( + handle, index, query, n_query_pts, eps, landmarks, dfunc, adj, vd); + + raft::resource::sync_stream(handle); +} + +template +void perform_rbc_eps_nn_query( + raft::resources const& handle, + const cuvs::neighbors::ball_cover::index& index, + const value_t* query, + value_int n_query_pts, + value_t eps, + value_int* max_k, + const value_t* landmarks, + dist_func dfunc, + value_idx* adj_ia, + value_idx* adj_ja, + value_idx* vd) +{ + rbc_eps_pass( + handle, index, query, n_query_pts, eps, max_k, landmarks, dfunc, adj_ia, adj_ja, vd); + + raft::resource::sync_stream(handle); +} + +/** + * Similar to a ball tree, the random ball cover algorithm + * uses the triangle inequality to prune distance computations + * in any metric space with a guarantee of sqrt(n) * c^{3/2} + * where `c` is an expansion constant based on the distance + * metric. + * + * This function variant performs an all nearest neighbors + * query which is useful for algorithms that need to perform + * A * A.T. + */ +template +void rbc_build_index( + raft::resources const& handle, + cuvs::neighbors::ball_cover::index& index, + distance_func dfunc) +{ + ASSERT(!index.is_index_trained(), "index cannot be previously trained"); + + rmm::device_uvector R_knn_inds(index.m, raft::resource::get_cuda_stream(handle)); + + // Initialize the uvectors + thrust::fill(raft::resource::get_thrust_policy(handle), + R_knn_inds.begin(), + R_knn_inds.end(), + std::numeric_limits::max()); + thrust::fill(raft::resource::get_thrust_policy(handle), + index.get_R_closest_landmark_dists().data_handle(), + index.get_R_closest_landmark_dists().data_handle() + index.m, + std::numeric_limits::max()); + + /** + * 1. Randomly sample sqrt(n) points from X + */ + sample_landmarks(handle, index); + + /** + * 2. Perform knn = bfknn(X, R, k) + */ + value_int k = 1; + k_closest_landmarks(handle, + index, + index.get_X().data_handle(), + index.m, + k, + R_knn_inds.data(), + index.get_R_closest_landmark_dists().data_handle()); + + /** + * 3. Create L_r = knn[:,0].T (CSR) + * + * Slice closest neighboring R + * Secondary sort by (R_knn_inds, R_knn_dists) + */ + construct_landmark_1nn( + handle, R_knn_inds.data(), index.get_R_closest_landmark_dists().data_handle(), k, index); + + /** + * Compute radius of each R for filtering: p(q, r) <= p(q, q_r) + radius(r) + * (need to take the + */ + compute_landmark_radii(handle, index); +} + +/** + * Performs an all neighbors knn query (e.g. index == query) + */ +template +void rbc_all_knn_query( + raft::resources const& handle, + cuvs::neighbors::ball_cover::index& index, + value_int k, + value_idx* inds, + value_t* dists, + distance_func dfunc, + // approximate nn options + bool perform_post_filtering = true, + float weight = 1.0) +{ + ASSERT(index.n <= 3, "only 2d and 3d vectors are supported in current implementation"); + ASSERT(index.n_landmarks >= k, "number of landmark samples must be >= k"); + ASSERT(!index.is_index_trained(), "index cannot be previously trained"); + + rmm::device_uvector R_knn_inds(k * index.m, raft::resource::get_cuda_stream(handle)); + rmm::device_uvector R_knn_dists(k * index.m, raft::resource::get_cuda_stream(handle)); + + // Initialize the uvectors + thrust::fill(raft::resource::get_thrust_policy(handle), + R_knn_inds.begin(), + R_knn_inds.end(), + std::numeric_limits::max()); + thrust::fill(raft::resource::get_thrust_policy(handle), + R_knn_dists.begin(), + R_knn_dists.end(), + std::numeric_limits::max()); + + thrust::fill(raft::resource::get_thrust_policy(handle), + inds, + inds + (k * index.m), + std::numeric_limits::max()); + thrust::fill(raft::resource::get_thrust_policy(handle), + dists, + dists + (k * index.m), + std::numeric_limits::max()); + + // For debugging / verification. Remove before releasing + rmm::device_uvector dists_counter(index.m, raft::resource::get_cuda_stream(handle)); + rmm::device_uvector post_dists_counter(index.m, + raft::resource::get_cuda_stream(handle)); + + sample_landmarks(handle, index); + + k_closest_landmarks( + handle, index, index.get_X().data_handle(), index.m, k, R_knn_inds.data(), R_knn_dists.data()); + + construct_landmark_1nn(handle, R_knn_inds.data(), R_knn_dists.data(), k, index); + + compute_landmark_radii(handle, index); + + perform_rbc_query(handle, + index, + index.get_X().data_handle(), + index.m, + k, + R_knn_inds.data(), + R_knn_dists.data(), + dfunc, + inds, + dists, + dists_counter.data(), + post_dists_counter.data(), + weight, + perform_post_filtering); +} + +/** + * Performs a knn query against an index. This assumes the index has + * already been built. + */ +template +void rbc_knn_query( + raft::resources const& handle, + const cuvs::neighbors::ball_cover::index& index, + value_int k, + const value_t* query, + value_int n_query_pts, + value_idx* inds, + value_t* dists, + distance_func dfunc, + // approximate nn options + bool perform_post_filtering = true, + float weight = 1.0) +{ + ASSERT(index.n <= 3, "only 2d and 3d vectors are supported in current implementation"); + ASSERT(index.n_landmarks >= k, "number of landmark samples must be >= k"); + ASSERT(index.is_index_trained(), "index must be previously trained"); + + rmm::device_uvector R_knn_inds(k * n_query_pts, + raft::resource::get_cuda_stream(handle)); + rmm::device_uvector R_knn_dists(k * n_query_pts, + raft::resource::get_cuda_stream(handle)); + + // Initialize the uvectors + thrust::fill(raft::resource::get_thrust_policy(handle), + R_knn_inds.begin(), + R_knn_inds.end(), + std::numeric_limits::max()); + thrust::fill(raft::resource::get_thrust_policy(handle), + R_knn_dists.begin(), + R_knn_dists.end(), + std::numeric_limits::max()); + + thrust::fill(raft::resource::get_thrust_policy(handle), + inds, + inds + (k * n_query_pts), + std::numeric_limits::max()); + thrust::fill(raft::resource::get_thrust_policy(handle), + dists, + dists + (k * n_query_pts), + std::numeric_limits::max()); + + k_closest_landmarks(handle, index, query, n_query_pts, k, R_knn_inds.data(), R_knn_dists.data()); + + // For debugging / verification. Remove before releasing + rmm::device_uvector dists_counter(index.m, raft::resource::get_cuda_stream(handle)); + rmm::device_uvector post_dists_counter(index.m, + raft::resource::get_cuda_stream(handle)); + thrust::fill(raft::resource::get_thrust_policy(handle), + post_dists_counter.data(), + post_dists_counter.data() + post_dists_counter.size(), + 0); + thrust::fill(raft::resource::get_thrust_policy(handle), + dists_counter.data(), + dists_counter.data() + dists_counter.size(), + 0); + + perform_rbc_query(handle, + index, + query, + n_query_pts, + k, + R_knn_inds.data(), + R_knn_dists.data(), + dfunc, + inds, + dists, + dists_counter.data(), + post_dists_counter.data(), + weight, + perform_post_filtering); +} + +template +void compute_landmark_dists( + raft::resources const& handle, + const cuvs::neighbors::ball_cover::index& index, + const value_t* query_pts, + value_int n_query_pts, + value_t* R_dists) +{ + // compute distances for all queries against all landmarks + // index.get_R() -- landmark points in row order (index.n_landmarks x index.k) + // query_pts -- query points in row order (n_query_pts x index.k) + RAFT_EXPECTS(std::max(index.n_landmarks, n_query_pts) * index.n < + static_cast(std::numeric_limits::max()), + "Too large input for pairwise_distance with `int` index."); + RAFT_EXPECTS(n_query_pts * static_cast(index.n_landmarks) < + static_cast(std::numeric_limits::max()), + "Too large input for pairwise_distance with `int` index."); + cuvs::distance::pairwise_distance(handle, + query_pts, + index.get_R().data_handle(), + R_dists, + n_query_pts, + index.n_landmarks, + index.n, + index.get_metric()); +} + +/** + * Performs a knn query against an index. This assumes the index has + * already been built. + * Modified version that takes an eps as threshold and outputs to a dense adj matrix (row-major) + * we are assuming that there are sufficiently many landmarks + */ +template +void rbc_eps_nn_query( + raft::resources const& handle, + const cuvs::neighbors::ball_cover::index& index, + const value_t eps, + const value_t* query, + value_int n_query_pts, + bool* adj, + value_idx* vd, + distance_func dfunc) +{ + ASSERT(index.is_index_trained(), "index must be previously trained"); + + // query all points and write to adj + perform_rbc_eps_nn_query( + handle, index, query, n_query_pts, eps, index.get_R().data_handle(), dfunc, adj, vd); +} + +template +void rbc_eps_nn_query( + raft::resources const& handle, + const cuvs::neighbors::ball_cover::index& index, + const value_t eps, + value_int* max_k, + const value_t* query, + value_int n_query_pts, + value_idx* adj_ia, + value_idx* adj_ja, + value_idx* vd, + distance_func dfunc) +{ + ASSERT(index.is_index_trained(), "index must be previously trained"); + + // query all points and write to adj + perform_rbc_eps_nn_query(handle, + index, + query, + n_query_pts, + eps, + max_k, + index.get_R().data_handle(), + dfunc, + adj_ia, + adj_ja, + vd); +} + +}; // namespace cuvs::neighbors::ball_cover::detail diff --git a/cpp/src/neighbors/ball_cover/common.cuh b/cpp/src/neighbors/ball_cover/common.cuh new file mode 100644 index 000000000..d0008c2ad --- /dev/null +++ b/cpp/src/neighbors/ball_cover/common.cuh @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "../detail/haversine_distance.cuh" +#include "registers_types.cuh" + +#include +#include + +#include + +namespace cuvs::neighbors::ball_cover::detail { + +struct NNComp { + template + __host__ __device__ bool operator()(const one& t1, const two& t2) + { + // sort first by each sample's reference landmark, + if (thrust::get<0>(t1) < thrust::get<0>(t2)) return true; + if (thrust::get<0>(t1) > thrust::get<0>(t2)) return false; + + // then by closest neighbor, + return thrust::get<1>(t1) < thrust::get<1>(t2); + } +}; + +/** + * Zeros the bit at location h in a one-hot encoded 32-bit int array + */ +__device__ inline void _zero_bit(std::uint32_t* arr, std::uint32_t h) +{ + int bit = h % 32; + int idx = h / 32; + + std::uint32_t assumed; + std::uint32_t old = arr[idx]; + do { + assumed = old; + old = atomicCAS(arr + idx, assumed, assumed & ~(1 << bit)); + } while (assumed != old); +} + +/** + * Returns whether or not bit at location h is nonzero in a one-hot + * encoded 32-bit in array. + */ +__device__ inline bool _get_val(std::uint32_t* arr, std::uint32_t h) +{ + int bit = h % 32; + int idx = h / 32; + return (arr[idx] & (1 << bit)) > 0; +} + +}; // namespace cuvs::neighbors::ball_cover::detail diff --git a/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_00_generate.py b/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_00_generate.py new file mode 100644 index 000000000..254e0e250 --- /dev/null +++ b/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_00_generate.py @@ -0,0 +1,165 @@ +# Copyright (c) 2023-2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +header = """/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by registers_00_generate.py + * + * Make changes there and run in this directory: + * + * > python registers_00_generate.py + * + */ + +#include // int64_t +#include +#include "../../registers-inl.cuh" + +""" + + +macro_pass_one = """ +#define instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_one( \\ + Mvalue_idx, Mvalue_t, Mvalue_int, Mmatrix_idx, Mdims, Mdist_func) \\ + template void \\ + cuvs::neighbors::ball_cover::detail::rbc_low_dim_pass_one( \\ + raft::resources const& handle, \\ + const cuvs::neighbors::ball_cover::index& index, \\ + const Mvalue_t* query, \\ + const Mvalue_int n_query_rows, \\ + Mvalue_int k, \\ + const Mvalue_idx* R_knn_inds, \\ + const Mvalue_t* R_knn_dists, \\ + Mdist_func& dfunc, \\ + Mvalue_idx* inds, \\ + Mvalue_t* dists, \\ + float weight, \\ + Mvalue_int* dists_counter) + +""" + +macro_pass_two = """ +#define instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_two( \\ + Mvalue_idx, Mvalue_t, Mvalue_int, Mmatrix_idx, Mdims, Mdist_func) \\ + template void \\ + cuvs::neighbors::ball_cover::detail::rbc_low_dim_pass_two( \\ + raft::resources const& handle, \\ + const cuvs::neighbors::ball_cover::index& index, \\ + const Mvalue_t* query, \\ + const Mvalue_int n_query_rows, \\ + Mvalue_int k, \\ + const Mvalue_idx* R_knn_inds, \\ + const Mvalue_t* R_knn_dists, \\ + Mdist_func& dfunc, \\ + Mvalue_idx* inds, \\ + Mvalue_t* dists, \\ + float weight, \\ + Mvalue_int* dists_counter) + +""" + +macro_pass_eps = """ +#define instantiate_cuvs_neighbors_detail_rbc_eps_pass( \\ + Mvalue_idx, Mvalue_t, Mvalue_int, Mmatrix_idx, Mdist_func) \\ + template void \\ + cuvs::neighbors::ball_cover::detail::rbc_eps_pass( \\ + raft::resources const& handle, \\ + const cuvs::neighbors::ball_cover::index& index, \\ + const Mvalue_t* query, \\ + const Mvalue_int n_query_rows, \\ + Mvalue_t eps, \\ + const Mvalue_t* R_dists, \\ + Mdist_func& dfunc, \\ + bool* adj, \\ + Mvalue_idx* vd); \\ + \\ + template void \\ + cuvs::neighbors::ball_cover::detail::rbc_eps_pass( \\ + raft::resources const& handle, \\ + const cuvs::neighbors::ball_cover::index& index, \\ + const Mvalue_t* query, \\ + const Mvalue_int n_query_rows, \\ + Mvalue_t eps, \\ + Mvalue_int* max_k, \\ + const Mvalue_t* R_dists, \\ + Mdist_func& dfunc, \\ + Mvalue_idx* adj_ia, \\ + Mvalue_idx* adj_ja, \\ + Mvalue_idx* vd) + +""" + + +distances = dict( + haversine="cuvs::neighbors::ball_cover::detail::HaversineFunc", + euclidean="cuvs::neighbors::ball_cover::detail::EuclideanFunc", + dist="cuvs::neighbors::ball_cover::detail::DistFunc", +) + +euclideanSq="cuvs::neighbors::ball_cover::detail::EuclideanSqFunc", + +types = dict( + int64_float=("std::int64_t", "float"), + #int64_double=("std::int64_t", "double"), +) + +for k, v in distances.items(): + for dim in [2, 3]: + path = f"registers_pass_one_{dim}d_{k}.cu" + with open(path, "w") as f: + f.write(header) + f.write(macro_pass_one) + for type_path, (int_t, data_t) in types.items(): + f.write(f"instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_one(\n") + f.write(f" {int_t}, {data_t}, {int_t}, {int_t}, {dim}, {v});\n") + f.write("#undef instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_one\n") + print(f"src/neighbors/ball_cover/detail/ball_cover/{path}") + +for k, v in distances.items(): + for dim in [2, 3]: + path = f"registers_pass_two_{dim}d_{k}.cu" + with open(path, "w") as f: + f.write(header) + f.write(macro_pass_two) + for type_path, (int_t, data_t) in types.items(): + f.write(f"instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_two(\n") + f.write(f" {int_t}, {data_t}, {int_t}, {int_t}, {dim}, {v});\n") + f.write("#undef instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_two\n") + print(f"src/neighbors/ball_cover/detail/ball_cover/{path}") + +path="registers_eps_pass_euclidean.cu" +with open(path, "w") as f: + f.write(header) + f.write(macro_pass_eps) + for type_path, (int_t, data_t) in types.items(): + f.write(f"instantiate_cuvs_neighbors_detail_rbc_eps_pass(\n") + f.write(f" {int_t}, {data_t}, {int_t}, {int_t}, {euclideanSq});\n") + f.write("#undef instantiate_cuvs_neighbors_detail_rbc_eps_pass\n") + print(f"src/neighbors/ball_cover/detail/ball_cover/{path}") + diff --git a/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_eps_pass_euclidean.cu b/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_eps_pass_euclidean.cu new file mode 100644 index 000000000..4a0f9850c --- /dev/null +++ b/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_eps_pass_euclidean.cu @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by registers_00_generate.py + * + * Make changes there and run in this directory: + * + * > python registers_00_generate.py + * + */ + +#include "../../registers-inl.cuh" +#include // int64_t +#include + +#define instantiate_cuvs_neighbors_detail_rbc_eps_pass( \ + Mvalue_idx, Mvalue_t, Mvalue_int, Mmatrix_idx, Mdist_func) \ + template void cuvs::neighbors::ball_cover::detail:: \ + rbc_eps_pass( \ + raft::resources const& handle, \ + const cuvs::neighbors::ball_cover::index& \ + index, \ + const Mvalue_t* query, \ + const Mvalue_int n_query_rows, \ + Mvalue_t eps, \ + const Mvalue_t* R_dists, \ + Mdist_func& dfunc, \ + bool* adj, \ + Mvalue_idx* vd); \ + \ + template void cuvs::neighbors::ball_cover::detail:: \ + rbc_eps_pass( \ + raft::resources const& handle, \ + const cuvs::neighbors::ball_cover::index& \ + index, \ + const Mvalue_t* query, \ + const Mvalue_int n_query_rows, \ + Mvalue_t eps, \ + Mvalue_int* max_k, \ + const Mvalue_t* R_dists, \ + Mdist_func& dfunc, \ + Mvalue_idx* adj_ia, \ + Mvalue_idx* adj_ja, \ + Mvalue_idx* vd) + +instantiate_cuvs_neighbors_detail_rbc_eps_pass( + std::int64_t, + float, + std::int64_t, + std::int64_t, + cuvs::neighbors::ball_cover::detail::EuclideanSqFunc); +#undef instantiate_cuvs_neighbors_detail_rbc_eps_pass diff --git a/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_one_2d_dist.cu b/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_one_2d_dist.cu new file mode 100644 index 000000000..d36daf7c5 --- /dev/null +++ b/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_one_2d_dist.cu @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by registers_00_generate.py + * + * Make changes there and run in this directory: + * + * > python registers_00_generate.py + * + */ + +#include "../../registers-inl.cuh" +#include // int64_t +#include + +#define instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_one( \ + Mvalue_idx, Mvalue_t, Mvalue_int, Mmatrix_idx, Mdims, Mdist_func) \ + template void cuvs::neighbors::ball_cover::detail:: \ + rbc_low_dim_pass_one( \ + raft::resources const& handle, \ + const cuvs::neighbors::ball_cover::index& \ + index, \ + const Mvalue_t* query, \ + const Mvalue_int n_query_rows, \ + Mvalue_int k, \ + const Mvalue_idx* R_knn_inds, \ + const Mvalue_t* R_knn_dists, \ + Mdist_func& dfunc, \ + Mvalue_idx* inds, \ + Mvalue_t* dists, \ + float weight, \ + Mvalue_int* dists_counter) + +instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_one( + std::int64_t, + float, + std::int64_t, + std::int64_t, + 2, + cuvs::neighbors::ball_cover::detail::DistFunc); +#undef instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_one diff --git a/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_one_2d_euclidean.cu b/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_one_2d_euclidean.cu new file mode 100644 index 000000000..650d1e285 --- /dev/null +++ b/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_one_2d_euclidean.cu @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by registers_00_generate.py + * + * Make changes there and run in this directory: + * + * > python registers_00_generate.py + * + */ + +#include "../../registers-inl.cuh" +#include // int64_t +#include + +#define instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_one( \ + Mvalue_idx, Mvalue_t, Mvalue_int, Mmatrix_idx, Mdims, Mdist_func) \ + template void cuvs::neighbors::ball_cover::detail:: \ + rbc_low_dim_pass_one( \ + raft::resources const& handle, \ + const cuvs::neighbors::ball_cover::index& \ + index, \ + const Mvalue_t* query, \ + const Mvalue_int n_query_rows, \ + Mvalue_int k, \ + const Mvalue_idx* R_knn_inds, \ + const Mvalue_t* R_knn_dists, \ + Mdist_func& dfunc, \ + Mvalue_idx* inds, \ + Mvalue_t* dists, \ + float weight, \ + Mvalue_int* dists_counter) + +instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_one( + std::int64_t, + float, + std::int64_t, + std::int64_t, + 2, + cuvs::neighbors::ball_cover::detail::EuclideanFunc); +#undef instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_one diff --git a/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_one_2d_haversine.cu b/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_one_2d_haversine.cu new file mode 100644 index 000000000..1ed575055 --- /dev/null +++ b/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_one_2d_haversine.cu @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by registers_00_generate.py + * + * Make changes there and run in this directory: + * + * > python registers_00_generate.py + * + */ + +#include "../../registers-inl.cuh" +#include // int64_t +#include + +#define instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_one( \ + Mvalue_idx, Mvalue_t, Mvalue_int, Mmatrix_idx, Mdims, Mdist_func) \ + template void cuvs::neighbors::ball_cover::detail:: \ + rbc_low_dim_pass_one( \ + raft::resources const& handle, \ + const cuvs::neighbors::ball_cover::index& \ + index, \ + const Mvalue_t* query, \ + const Mvalue_int n_query_rows, \ + Mvalue_int k, \ + const Mvalue_idx* R_knn_inds, \ + const Mvalue_t* R_knn_dists, \ + Mdist_func& dfunc, \ + Mvalue_idx* inds, \ + Mvalue_t* dists, \ + float weight, \ + Mvalue_int* dists_counter) + +instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_one( + std::int64_t, + float, + std::int64_t, + std::int64_t, + 2, + cuvs::neighbors::ball_cover::detail::HaversineFunc); +#undef instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_one diff --git a/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_one_3d_dist.cu b/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_one_3d_dist.cu new file mode 100644 index 000000000..2600b8d0b --- /dev/null +++ b/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_one_3d_dist.cu @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by registers_00_generate.py + * + * Make changes there and run in this directory: + * + * > python registers_00_generate.py + * + */ + +#include "../../registers-inl.cuh" +#include // int64_t +#include + +#define instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_one( \ + Mvalue_idx, Mvalue_t, Mvalue_int, Mmatrix_idx, Mdims, Mdist_func) \ + template void cuvs::neighbors::ball_cover::detail:: \ + rbc_low_dim_pass_one( \ + raft::resources const& handle, \ + const cuvs::neighbors::ball_cover::index& \ + index, \ + const Mvalue_t* query, \ + const Mvalue_int n_query_rows, \ + Mvalue_int k, \ + const Mvalue_idx* R_knn_inds, \ + const Mvalue_t* R_knn_dists, \ + Mdist_func& dfunc, \ + Mvalue_idx* inds, \ + Mvalue_t* dists, \ + float weight, \ + Mvalue_int* dists_counter) + +instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_one( + std::int64_t, + float, + std::int64_t, + std::int64_t, + 3, + cuvs::neighbors::ball_cover::detail::DistFunc); +#undef instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_one diff --git a/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_one_3d_euclidean.cu b/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_one_3d_euclidean.cu new file mode 100644 index 000000000..a93acbce4 --- /dev/null +++ b/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_one_3d_euclidean.cu @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by registers_00_generate.py + * + * Make changes there and run in this directory: + * + * > python registers_00_generate.py + * + */ + +#include "../../registers-inl.cuh" +#include // int64_t +#include + +#define instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_one( \ + Mvalue_idx, Mvalue_t, Mvalue_int, Mmatrix_idx, Mdims, Mdist_func) \ + template void cuvs::neighbors::ball_cover::detail:: \ + rbc_low_dim_pass_one( \ + raft::resources const& handle, \ + const cuvs::neighbors::ball_cover::index& \ + index, \ + const Mvalue_t* query, \ + const Mvalue_int n_query_rows, \ + Mvalue_int k, \ + const Mvalue_idx* R_knn_inds, \ + const Mvalue_t* R_knn_dists, \ + Mdist_func& dfunc, \ + Mvalue_idx* inds, \ + Mvalue_t* dists, \ + float weight, \ + Mvalue_int* dists_counter) + +instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_one( + std::int64_t, + float, + std::int64_t, + std::int64_t, + 3, + cuvs::neighbors::ball_cover::detail::EuclideanFunc); +#undef instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_one diff --git a/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_one_3d_haversine.cu b/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_one_3d_haversine.cu new file mode 100644 index 000000000..fd3d01feb --- /dev/null +++ b/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_one_3d_haversine.cu @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by registers_00_generate.py + * + * Make changes there and run in this directory: + * + * > python registers_00_generate.py + * + */ + +#include "../../registers-inl.cuh" +#include // int64_t +#include + +#define instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_one( \ + Mvalue_idx, Mvalue_t, Mvalue_int, Mmatrix_idx, Mdims, Mdist_func) \ + template void cuvs::neighbors::ball_cover::detail:: \ + rbc_low_dim_pass_one( \ + raft::resources const& handle, \ + const cuvs::neighbors::ball_cover::index& \ + index, \ + const Mvalue_t* query, \ + const Mvalue_int n_query_rows, \ + Mvalue_int k, \ + const Mvalue_idx* R_knn_inds, \ + const Mvalue_t* R_knn_dists, \ + Mdist_func& dfunc, \ + Mvalue_idx* inds, \ + Mvalue_t* dists, \ + float weight, \ + Mvalue_int* dists_counter) + +instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_one( + std::int64_t, + float, + std::int64_t, + std::int64_t, + 3, + cuvs::neighbors::ball_cover::detail::HaversineFunc); +#undef instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_one diff --git a/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_two_2d_dist.cu b/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_two_2d_dist.cu new file mode 100644 index 000000000..c30a55991 --- /dev/null +++ b/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_two_2d_dist.cu @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by registers_00_generate.py + * + * Make changes there and run in this directory: + * + * > python registers_00_generate.py + * + */ + +#include "../../registers-inl.cuh" +#include // int64_t +#include + +#define instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_two( \ + Mvalue_idx, Mvalue_t, Mvalue_int, Mmatrix_idx, Mdims, Mdist_func) \ + template void cuvs::neighbors::ball_cover::detail:: \ + rbc_low_dim_pass_two( \ + raft::resources const& handle, \ + const cuvs::neighbors::ball_cover::index& \ + index, \ + const Mvalue_t* query, \ + const Mvalue_int n_query_rows, \ + Mvalue_int k, \ + const Mvalue_idx* R_knn_inds, \ + const Mvalue_t* R_knn_dists, \ + Mdist_func& dfunc, \ + Mvalue_idx* inds, \ + Mvalue_t* dists, \ + float weight, \ + Mvalue_int* dists_counter) + +instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_two( + std::int64_t, + float, + std::int64_t, + std::int64_t, + 2, + cuvs::neighbors::ball_cover::detail::DistFunc); +#undef instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_two diff --git a/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_two_2d_euclidean.cu b/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_two_2d_euclidean.cu new file mode 100644 index 000000000..49cc8404c --- /dev/null +++ b/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_two_2d_euclidean.cu @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by registers_00_generate.py + * + * Make changes there and run in this directory: + * + * > python registers_00_generate.py + * + */ + +#include "../../registers-inl.cuh" +#include // int64_t +#include + +#define instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_two( \ + Mvalue_idx, Mvalue_t, Mvalue_int, Mmatrix_idx, Mdims, Mdist_func) \ + template void cuvs::neighbors::ball_cover::detail:: \ + rbc_low_dim_pass_two( \ + raft::resources const& handle, \ + const cuvs::neighbors::ball_cover::index& \ + index, \ + const Mvalue_t* query, \ + const Mvalue_int n_query_rows, \ + Mvalue_int k, \ + const Mvalue_idx* R_knn_inds, \ + const Mvalue_t* R_knn_dists, \ + Mdist_func& dfunc, \ + Mvalue_idx* inds, \ + Mvalue_t* dists, \ + float weight, \ + Mvalue_int* dists_counter) + +instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_two( + std::int64_t, + float, + std::int64_t, + std::int64_t, + 2, + cuvs::neighbors::ball_cover::detail::EuclideanFunc); +#undef instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_two diff --git a/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_two_2d_haversine.cu b/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_two_2d_haversine.cu new file mode 100644 index 000000000..4cc9ec992 --- /dev/null +++ b/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_two_2d_haversine.cu @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by registers_00_generate.py + * + * Make changes there and run in this directory: + * + * > python registers_00_generate.py + * + */ + +#include "../../registers-inl.cuh" +#include // int64_t +#include + +#define instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_two( \ + Mvalue_idx, Mvalue_t, Mvalue_int, Mmatrix_idx, Mdims, Mdist_func) \ + template void cuvs::neighbors::ball_cover::detail:: \ + rbc_low_dim_pass_two( \ + raft::resources const& handle, \ + const cuvs::neighbors::ball_cover::index& \ + index, \ + const Mvalue_t* query, \ + const Mvalue_int n_query_rows, \ + Mvalue_int k, \ + const Mvalue_idx* R_knn_inds, \ + const Mvalue_t* R_knn_dists, \ + Mdist_func& dfunc, \ + Mvalue_idx* inds, \ + Mvalue_t* dists, \ + float weight, \ + Mvalue_int* dists_counter) + +instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_two( + std::int64_t, + float, + std::int64_t, + std::int64_t, + 2, + cuvs::neighbors::ball_cover::detail::HaversineFunc); +#undef instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_two diff --git a/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_two_3d_dist.cu b/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_two_3d_dist.cu new file mode 100644 index 000000000..abc51994d --- /dev/null +++ b/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_two_3d_dist.cu @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by registers_00_generate.py + * + * Make changes there and run in this directory: + * + * > python registers_00_generate.py + * + */ + +#include "../../registers-inl.cuh" +#include // int64_t +#include + +#define instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_two( \ + Mvalue_idx, Mvalue_t, Mvalue_int, Mmatrix_idx, Mdims, Mdist_func) \ + template void cuvs::neighbors::ball_cover::detail:: \ + rbc_low_dim_pass_two( \ + raft::resources const& handle, \ + const cuvs::neighbors::ball_cover::index& \ + index, \ + const Mvalue_t* query, \ + const Mvalue_int n_query_rows, \ + Mvalue_int k, \ + const Mvalue_idx* R_knn_inds, \ + const Mvalue_t* R_knn_dists, \ + Mdist_func& dfunc, \ + Mvalue_idx* inds, \ + Mvalue_t* dists, \ + float weight, \ + Mvalue_int* dists_counter) + +instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_two( + std::int64_t, + float, + std::int64_t, + std::int64_t, + 3, + cuvs::neighbors::ball_cover::detail::DistFunc); +#undef instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_two diff --git a/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_two_3d_euclidean.cu b/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_two_3d_euclidean.cu new file mode 100644 index 000000000..a24ce0dd6 --- /dev/null +++ b/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_two_3d_euclidean.cu @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by registers_00_generate.py + * + * Make changes there and run in this directory: + * + * > python registers_00_generate.py + * + */ + +#include "../../registers-inl.cuh" +#include // int64_t +#include + +#define instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_two( \ + Mvalue_idx, Mvalue_t, Mvalue_int, Mmatrix_idx, Mdims, Mdist_func) \ + template void cuvs::neighbors::ball_cover::detail:: \ + rbc_low_dim_pass_two( \ + raft::resources const& handle, \ + const cuvs::neighbors::ball_cover::index& \ + index, \ + const Mvalue_t* query, \ + const Mvalue_int n_query_rows, \ + Mvalue_int k, \ + const Mvalue_idx* R_knn_inds, \ + const Mvalue_t* R_knn_dists, \ + Mdist_func& dfunc, \ + Mvalue_idx* inds, \ + Mvalue_t* dists, \ + float weight, \ + Mvalue_int* dists_counter) + +instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_two( + std::int64_t, + float, + std::int64_t, + std::int64_t, + 3, + cuvs::neighbors::ball_cover::detail::EuclideanFunc); +#undef instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_two diff --git a/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_two_3d_haversine.cu b/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_two_3d_haversine.cu new file mode 100644 index 000000000..954753b63 --- /dev/null +++ b/cpp/src/neighbors/ball_cover/detail/ball_cover/registers_pass_two_3d_haversine.cu @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE: this file is generated by registers_00_generate.py + * + * Make changes there and run in this directory: + * + * > python registers_00_generate.py + * + */ + +#include "../../registers-inl.cuh" +#include // int64_t +#include + +#define instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_two( \ + Mvalue_idx, Mvalue_t, Mvalue_int, Mmatrix_idx, Mdims, Mdist_func) \ + template void cuvs::neighbors::ball_cover::detail:: \ + rbc_low_dim_pass_two( \ + raft::resources const& handle, \ + const cuvs::neighbors::ball_cover::index& \ + index, \ + const Mvalue_t* query, \ + const Mvalue_int n_query_rows, \ + Mvalue_int k, \ + const Mvalue_idx* R_knn_inds, \ + const Mvalue_t* R_knn_dists, \ + Mdist_func& dfunc, \ + Mvalue_idx* inds, \ + Mvalue_t* dists, \ + float weight, \ + Mvalue_int* dists_counter) + +instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_two( + std::int64_t, + float, + std::int64_t, + std::int64_t, + 3, + cuvs::neighbors::ball_cover::detail::HaversineFunc); +#undef instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_two diff --git a/cpp/src/neighbors/ball_cover/registers-ext.cuh b/cpp/src/neighbors/ball_cover/registers-ext.cuh new file mode 100644 index 000000000..7de9e11ce --- /dev/null +++ b/cpp/src/neighbors/ball_cover/registers-ext.cuh @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "registers_types.cuh" // DistFunc +#include // cuvs::neighbors::ball_cover::index + +#include //RAFT_EXPLICIT + +#include // uint32_t + +namespace cuvs::neighbors::ball_cover::detail { + +template +void rbc_low_dim_pass_one( + raft::resources const& handle, + const cuvs::neighbors::ball_cover::index& index, + const value_t* query, + const value_int n_query_rows, + value_int k, + const value_idx* R_knn_inds, + const value_t* R_knn_dists, + dist_func& dfunc, + value_idx* inds, + value_t* dists, + float weight, + value_int* dists_counter) RAFT_EXPLICIT; + +template +void rbc_low_dim_pass_two( + raft::resources const& handle, + const cuvs::neighbors::ball_cover::index& index, + const value_t* query, + const value_int n_query_rows, + value_int k, + const value_idx* R_knn_inds, + const value_t* R_knn_dists, + dist_func& dfunc, + value_idx* inds, + value_t* dists, + float weight, + value_int* post_dists_counter) RAFT_EXPLICIT; + +template +void rbc_eps_pass( + raft::resources const& handle, + const cuvs::neighbors::ball_cover::index& index, + const value_t* query, + const value_int n_query_rows, + value_t eps, + const value_t* R_dists, + dist_func& dfunc, + bool* adj, + value_idx* vd) RAFT_EXPLICIT; + +template +void rbc_eps_pass( + raft::resources const& handle, + const cuvs::neighbors::ball_cover::index& index, + const value_t* query, + const value_int n_query_rows, + value_t eps, + value_int* max_k, + const value_t* R_dists, + dist_func& dfunc, + value_idx* adj_ia, + value_idx* adj_ja, + value_idx* vd) RAFT_EXPLICIT; + +}; // namespace cuvs::neighbors::ball_cover::detail + +#define instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_one( \ + Mvalue_idx, Mvalue_t, Mvalue_int, Mmatrix_idx, Mdims, Mdist_func) \ + extern template void cuvs::neighbors::ball_cover::detail:: \ + rbc_low_dim_pass_one( \ + raft::resources const& handle, \ + const cuvs::neighbors::ball_cover::index& \ + index, \ + const Mvalue_t* query, \ + const Mvalue_int n_query_rows, \ + Mvalue_int k, \ + const Mvalue_idx* R_knn_inds, \ + const Mvalue_t* R_knn_dists, \ + Mdist_func& dfunc, \ + Mvalue_idx* inds, \ + Mvalue_t* dists, \ + float weight, \ + Mvalue_int* dists_counter) + +#define instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_two( \ + Mvalue_idx, Mvalue_t, Mvalue_int, Mmatrix_idx, Mdims, Mdist_func) \ + extern template void cuvs::neighbors::ball_cover::detail:: \ + rbc_low_dim_pass_two( \ + raft::resources const& handle, \ + const cuvs::neighbors::ball_cover::index& \ + index, \ + const Mvalue_t* query, \ + const Mvalue_int n_query_rows, \ + Mvalue_int k, \ + const Mvalue_idx* R_knn_inds, \ + const Mvalue_t* R_knn_dists, \ + Mdist_func& dfunc, \ + Mvalue_idx* inds, \ + Mvalue_t* dists, \ + float weight, \ + Mvalue_int* dists_counter) + +#define instantiate_cuvs_neighbors_detail_rbc_eps_pass( \ + Mvalue_idx, Mvalue_t, Mvalue_int, Mmatrix_idx, Mdist_func) \ + extern template void cuvs::neighbors::ball_cover::detail:: \ + rbc_eps_pass( \ + raft::resources const& handle, \ + const cuvs::neighbors::ball_cover::index& \ + index, \ + const Mvalue_t* query, \ + const Mvalue_int n_query_rows, \ + Mvalue_t eps, \ + const Mvalue_t* R_dists, \ + Mdist_func& dfunc, \ + bool* adj, \ + Mvalue_idx* vd); \ + \ + extern template void cuvs::neighbors::ball_cover::detail:: \ + rbc_eps_pass( \ + raft::resources const& handle, \ + const cuvs::neighbors::ball_cover::index& \ + index, \ + const Mvalue_t* query, \ + const Mvalue_int n_query_rows, \ + Mvalue_t eps, \ + Mvalue_int* max_k, \ + const Mvalue_t* R_dists, \ + Mdist_func& dfunc, \ + Mvalue_idx* adj_ia, \ + Mvalue_idx* adj_ja, \ + Mvalue_idx* vd); + +instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_one( + std::int64_t, + float, + std::int64_t, + std::int64_t, + 2, + cuvs::neighbors::ball_cover::detail::HaversineFunc); +instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_one( + std::int64_t, + float, + std::int64_t, + std::int64_t, + 3, + cuvs::neighbors::ball_cover::detail::HaversineFunc); +instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_one( + std::int64_t, + float, + std::int64_t, + std::int64_t, + 2, + cuvs::neighbors::ball_cover::detail::EuclideanFunc); +instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_one( + std::int64_t, + float, + std::int64_t, + std::int64_t, + 3, + cuvs::neighbors::ball_cover::detail::EuclideanFunc); +instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_one( + std::int64_t, + float, + std::int64_t, + std::int64_t, + 2, + cuvs::neighbors::ball_cover::detail::DistFunc); +instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_one( + std::int64_t, + float, + std::int64_t, + std::int64_t, + 3, + cuvs::neighbors::ball_cover::detail::DistFunc); + +instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_two( + std::int64_t, + float, + std::int64_t, + std::int64_t, + 2, + cuvs::neighbors::ball_cover::detail::HaversineFunc); +instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_two( + std::int64_t, + float, + std::int64_t, + std::int64_t, + 3, + cuvs::neighbors::ball_cover::detail::HaversineFunc); +instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_two( + std::int64_t, + float, + std::int64_t, + std::int64_t, + 2, + cuvs::neighbors::ball_cover::detail::EuclideanFunc); +instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_two( + std::int64_t, + float, + std::int64_t, + std::int64_t, + 3, + cuvs::neighbors::ball_cover::detail::EuclideanFunc); +instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_two( + std::int64_t, + float, + std::int64_t, + std::int64_t, + 2, + cuvs::neighbors::ball_cover::detail::DistFunc); +instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_two( + std::int64_t, + float, + std::int64_t, + std::int64_t, + 3, + cuvs::neighbors::ball_cover::detail::DistFunc); + +instantiate_cuvs_neighbors_detail_rbc_eps_pass( + std::int64_t, + float, + std::int64_t, + std::int64_t, + cuvs::neighbors::ball_cover::detail::EuclideanSqFunc); + +#undef instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_two +#undef instantiate_cuvs_neighbors_detail_rbc_low_dim_pass_one +#undef instantiate_cuvs_neighbors_detail_rbc_eps_pass diff --git a/cpp/src/neighbors/ball_cover/registers-inl.cuh b/cpp/src/neighbors/ball_cover/registers-inl.cuh new file mode 100644 index 000000000..07a723e85 --- /dev/null +++ b/cpp/src/neighbors/ball_cover/registers-inl.cuh @@ -0,0 +1,1630 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "../detail/haversine_distance.cuh" +#include "common.cuh" +#include "registers_types.cuh" // DistFunc +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +namespace cuvs::neighbors::ball_cover::detail { + +/** + * To find exact neighbors, we perform a post-processing stage + * that filters out those points which might have neighbors outside + * of their k closest landmarks. This is usually a very small portion + * of the total points. + * @tparam value_idx + * @tparam value_t + * @tparam value_int + * @tparam tpb + * @param X + * @param n_cols + * @param R_knn_inds + * @param R_knn_dists + * @param R_radius + * @param landmarks + * @param n_landmarks + * @param bitset_size + * @param k + * @param output + * @param weight + */ +template +RAFT_KERNEL perform_post_filter_registers(const value_t* X, + value_int n_cols, + const value_idx* R_knn_inds, + const value_t* R_knn_dists, + const value_t* R_radius, + const value_t* landmarks, + int n_landmarks, + value_int bitset_size, + value_int k, + distance_func dfunc, + std::uint32_t* output, + float weight = 1.0) +{ + // allocate array of size n_landmarks / 32 ints + extern __shared__ std::uint32_t shared_mem[]; + + // Start with all bits on + for (value_int i = threadIdx.x; i < bitset_size; i += tpb) { + shared_mem[i] = 0xffffffff; + } + + __syncthreads(); + + // TODO: Would it be faster to use L1 for this? + value_t local_x_ptr[col_q]; + for (value_int j = 0; j < n_cols; ++j) { + local_x_ptr[j] = X[n_cols * blockIdx.x + j]; + } + + value_t closest_R_dist = R_knn_dists[blockIdx.x * k + (k - 1)]; + + // zero out bits for closest k landmarks + for (value_int j = threadIdx.x; j < k; j += tpb) { + _zero_bit(shared_mem, (std::uint32_t)R_knn_inds[blockIdx.x * k + j]); + } + + __syncthreads(); + + // Discard any landmarks where p(q, r) > p(q, r_q) + radius(r) + // That is, the distance between the current point and the current + // landmark is > the distance between the current point and + // its closest landmark + the radius of the current landmark. + for (value_int l = threadIdx.x; l < n_landmarks; l += tpb) { + // compute p(q, r) + value_t dist = dfunc(local_x_ptr, landmarks + (n_cols * l), n_cols); + if (dist > weight * (closest_R_dist + R_radius[l]) || dist > 3 * closest_R_dist) { + _zero_bit(shared_mem, l); + } + } + + __syncthreads(); + + /** + * Output bitset + */ + for (value_int l = threadIdx.x; l < bitset_size; l += tpb) { + output[blockIdx.x * bitset_size + l] = shared_mem[l]; + } +} + +/** + * @tparam value_idx + * @tparam value_t + * @tparam value_int + * @tparam bitset_type + * @tparam warp_q number of registers to use per warp + * @tparam thread_q number of registers to use within each thread + * @tparam tpb number of threads per block + * @param X + * @param n_cols + * @param bitset + * @param bitset_size + * @param R_knn_dists + * @param R_indptr + * @param R_1nn_inds + * @param R_1nn_dists + * @param knn_inds + * @param knn_dists + * @param n_landmarks + * @param k + * @param dist_counter + */ +template +RAFT_KERNEL compute_final_dists_registers(const value_t* X_reordered, + const value_t* X, + const value_int n_cols, + bitset_type* bitset, + value_int bitset_size, + const value_t* R_closest_landmark_dists, + const value_idx* R_indptr, + const value_idx* R_1nn_inds, + const value_t* R_1nn_dists, + value_idx* knn_inds, + value_t* knn_dists, + value_int n_landmarks, + value_int k, + dist_func dfunc, + value_int* dist_counter) +{ + static constexpr int kNumWarps = tpb / raft::WarpSize; + + __shared__ value_t shared_memK[kNumWarps * warp_q]; + __shared__ raft::KeyValuePair shared_memV[kNumWarps * warp_q]; + + const value_t* x_ptr = X + (n_cols * blockIdx.x); + value_t local_x_ptr[col_q]; + for (value_int j = 0; j < n_cols; ++j) { + local_x_ptr[j] = x_ptr[j]; + } + + using namespace raft::neighbors::detail::faiss_select; + KeyValueBlockSelect, warp_q, thread_q, tpb> heap( + std::numeric_limits::max(), + std::numeric_limits::max(), + -1, + shared_memK, + shared_memV, + k); + + const value_int n_k = raft::Pow2::roundDown(k); + value_int i = threadIdx.x; + for (; i < n_k; i += tpb) { + value_idx ind = knn_inds[blockIdx.x * k + i]; + heap.add(knn_dists[blockIdx.x * k + i], R_closest_landmark_dists[ind], ind); + } + + if (i < k) { + value_idx ind = knn_inds[blockIdx.x * k + i]; + heap.addThreadQ(knn_dists[blockIdx.x * k + i], R_closest_landmark_dists[ind], ind); + } + + heap.checkThreadQ(); + + for (value_int cur_R_ind = 0; cur_R_ind < n_landmarks; ++cur_R_ind) { + // if cur R overlaps cur point's closest R, it could be a + // candidate + if (_get_val(bitset + (blockIdx.x * bitset_size), cur_R_ind)) { + value_idx R_start_offset = R_indptr[cur_R_ind]; + value_idx R_stop_offset = R_indptr[cur_R_ind + 1]; + value_idx R_size = R_stop_offset - R_start_offset; + + // Loop through R's neighborhood in parallel + + // Round R_size to the nearest warp threads so they can + // all be computing in parallel. + + const value_int limit = raft::Pow2::roundDown(R_size); + + i = threadIdx.x; + for (; i < limit; i += tpb) { + value_idx cur_candidate_ind = R_1nn_inds[R_start_offset + i]; + value_t cur_candidate_dist = R_1nn_dists[R_start_offset + i]; + + value_t z = heap.warpKTopRDist == 0.00 ? 0.0 + : (abs(heap.warpKTop - heap.warpKTopRDist) * + abs(heap.warpKTopRDist - cur_candidate_dist) - + heap.warpKTop * cur_candidate_dist) / + heap.warpKTopRDist; + z = isnan(z) || isinf(z) ? 0.0 : z; + + // If lower bound on distance could possibly be in + // the closest k neighbors, compute it and add to k-select + value_t dist = std::numeric_limits::max(); + if (z <= heap.warpKTop) { + const value_t* y_ptr = X_reordered + (n_cols * (R_start_offset + i)); + value_t local_y_ptr[col_q]; + for (value_int j = 0; j < n_cols; ++j) { + local_y_ptr[j] = y_ptr[j]; + } + + dist = dfunc(local_x_ptr, local_y_ptr, n_cols); + } + + heap.add(dist, cur_candidate_dist, cur_candidate_ind); + } + + // second round guarantees to be only a single warp. + if (i < R_size) { + value_idx cur_candidate_ind = R_1nn_inds[R_start_offset + i]; + value_t cur_candidate_dist = R_1nn_dists[R_start_offset + i]; + + value_t z = heap.warpKTopRDist == 0.00 ? 0.0 + : (abs(heap.warpKTop - heap.warpKTopRDist) * + abs(heap.warpKTopRDist - cur_candidate_dist) - + heap.warpKTop * cur_candidate_dist) / + heap.warpKTopRDist; + + z = isnan(z) || isinf(z) ? 0.0 : z; + + // If lower bound on distance could possibly be in + // the closest k neighbors, compute it and add to k-select + value_t dist = std::numeric_limits::max(); + if (z <= heap.warpKTop) { + const value_t* y_ptr = X_reordered + (n_cols * (R_start_offset + i)); + value_t local_y_ptr[col_q]; + for (value_int j = 0; j < n_cols; ++j) { + local_y_ptr[j] = y_ptr[j]; + } + dist = dfunc(local_x_ptr, local_y_ptr, n_cols); + } + heap.addThreadQ(dist, cur_candidate_dist, cur_candidate_ind); + } + heap.checkThreadQ(); + } + } + + heap.reduce(); + + for (value_int i = threadIdx.x; i < k; i += tpb) { + knn_dists[blockIdx.x * k + i] = shared_memK[i]; + knn_inds[blockIdx.x * k + i] = shared_memV[i].value; + } +} + +/** + * Random ball cover kernel for n_dims == 2 + * @tparam value_idx + * @tparam value_t + * @tparam warp_q + * @tparam thread_q + * @tparam tpb + * @tparam value_idx + * @tparam value_t + * @param R_knn_inds + * @param R_knn_dists + * @param m + * @param k + * @param R_indptr + * @param R_1nn_cols + * @param R_1nn_dists + */ +template +RAFT_KERNEL block_rbc_kernel_registers(const value_t* X_reordered, + const value_t* X, + value_int n_cols, // n_cols should be 2 or 3 dims + const value_idx* R_knn_inds, + const value_t* R_knn_dists, + value_int m, + value_int k, + const value_idx* R_indptr, + const value_idx* R_1nn_cols, + const value_t* R_1nn_dists, + value_idx* out_inds, + value_t* out_dists, + value_int* dist_counter, + const value_t* R_radius, + distance_func dfunc, + float weight = 1.0) +{ + static constexpr value_int kNumWarps = tpb / raft::WarpSize; + + __shared__ value_t shared_memK[kNumWarps * warp_q]; + __shared__ raft::KeyValuePair shared_memV[kNumWarps * warp_q]; + + // TODO: Separate kernels for different widths: + // 1. Very small (between 3 and 32) just use registers for columns of "blockIdx.x" + // 2. Can fit comfortably in shared memory (32 to a few thousand?) + // 3. Load each time individually. + const value_t* x_ptr = X + (n_cols * blockIdx.x); + + // Use registers only for 2d or 3d + value_t local_x_ptr[col_q]; + for (value_int i = 0; i < n_cols; ++i) { + local_x_ptr[i] = x_ptr[i]; + } + + // Each warp works on 1 R + using namespace raft::neighbors::detail::faiss_select; + KeyValueBlockSelect, warp_q, thread_q, tpb> heap( + std::numeric_limits::max(), + std::numeric_limits::max(), + -1, + shared_memK, + shared_memV, + k); + + value_t min_R_dist = R_knn_dists[blockIdx.x * k + (k - 1)]; + value_int n_dists_computed = 0; + + /** + * First add distances for k closest neighbors of R + * to the heap + */ + // Start iterating through elements of each set from closest R elements, + // determining if the distance could even potentially be in the heap. + for (value_int cur_k = 0; cur_k < k; ++cur_k) { + // index and distance to current blockIdx.x's closest landmark + value_t cur_R_dist = R_knn_dists[blockIdx.x * k + cur_k]; + value_idx cur_R_ind = R_knn_inds[blockIdx.x * k + cur_k]; + + // Equation (2) in Cayton's paper- prune out R's which are > 3 * p(q, r_q) + if (cur_R_dist > weight * (min_R_dist + R_radius[cur_R_ind])) continue; + if (cur_R_dist > 3 * min_R_dist) return; + + // The whole warp should iterate through the elements in the current R + value_idx R_start_offset = R_indptr[cur_R_ind]; + value_idx R_stop_offset = R_indptr[cur_R_ind + 1]; + + value_idx R_size = R_stop_offset - R_start_offset; + + value_int limit = raft::Pow2::roundDown(R_size); + value_int i = threadIdx.x; + for (; i < limit; i += tpb) { + // Index and distance of current candidate's nearest landmark + value_idx cur_candidate_ind = R_1nn_cols[R_start_offset + i]; + value_t cur_candidate_dist = R_1nn_dists[R_start_offset + i]; + + // Take 2 landmarks l_1 and l_2 where l_1 is the furthest point in the heap + // and l_2 is the current landmark R. s is the current data point and + // t is the new candidate data point. We know that: + // d(s, t) cannot possibly be any smaller than | d(s, l_1) - d(l_1, l_2) | * | d(l_1, l_2) - + // d(l_2, t) | - d(s, l_1) * d(l_2, t) + + // Therefore, if d(s, t) >= d(s, l_1) from the computation above, we know that the distance to + // the candidate point cannot possibly be in the nearest neighbors. However, if d(s, t) < d(s, + // l_1) then we should compute the distance because it's possible it could be smaller. + // + value_t z = heap.warpKTopRDist == 0.00 ? 0.0 + : (abs(heap.warpKTop - heap.warpKTopRDist) * + abs(heap.warpKTopRDist - cur_candidate_dist) - + heap.warpKTop * cur_candidate_dist) / + heap.warpKTopRDist; + + z = isnan(z) || isinf(z) ? 0.0 : z; + value_t dist = std::numeric_limits::max(); + + if (z <= heap.warpKTop) { + const value_t* y_ptr = X_reordered + (n_cols * (R_start_offset + i)); + value_t local_y_ptr[col_q]; + for (value_int j = 0; j < n_cols; ++j) { + local_y_ptr[j] = y_ptr[j]; + } + dist = dfunc(local_x_ptr, local_y_ptr, n_cols); + ++n_dists_computed; + } + + heap.add(dist, cur_candidate_dist, cur_candidate_ind); + } + + if (i < R_size) { + value_idx cur_candidate_ind = R_1nn_cols[R_start_offset + i]; + value_t cur_candidate_dist = R_1nn_dists[R_start_offset + i]; + value_t z = heap.warpKTopRDist == 0.0 ? 0.0 + : (abs(heap.warpKTop - heap.warpKTopRDist) * + abs(heap.warpKTopRDist - cur_candidate_dist) - + heap.warpKTop * cur_candidate_dist) / + heap.warpKTopRDist; + + z = isnan(z) || isinf(z) ? 0.0 : z; + value_t dist = std::numeric_limits::max(); + + if (z <= heap.warpKTop) { + const value_t* y_ptr = X_reordered + (n_cols * (R_start_offset + i)); + value_t local_y_ptr[col_q]; + for (value_int j = 0; j < n_cols; ++j) { + local_y_ptr[j] = y_ptr[j]; + } + dist = dfunc(local_x_ptr, local_y_ptr, n_cols); + ++n_dists_computed; + } + + heap.addThreadQ(dist, cur_candidate_dist, cur_candidate_ind); + } + + heap.checkThreadQ(); + } + + heap.reduce(); + + for (int i = threadIdx.x; i < k; i += tpb) { + out_dists[blockIdx.x * k + i] = shared_memK[i]; + out_inds[blockIdx.x * k + i] = shared_memV[i].value; + } +} + +template +__device__ value_t squared(const value_t& a) +{ + return a * a; +} + +template +RAFT_KERNEL block_rbc_kernel_eps_dense(const value_t* X_reordered, + const value_t* X, + const value_int n_queries, + const value_int n_cols, + const value_t* R, + const value_int m, + const value_t eps, + const value_int n_landmarks, + const value_idx* R_indptr, + const value_idx* R_1nn_cols, + const value_t* R_1nn_dists, + const value_t* R_radius, + distance_func dfunc, + bool* adj, + value_idx* vd) +{ + constexpr int num_warps = tpb / raft::WarpSize; + + // process 1 query per warp + const uint32_t lid = raft::laneId(); + + // this should help the compiler to prevent branches + const int query_id = raft::shfl(blockIdx.x * num_warps + (threadIdx.x / raft::WarpSize), 0); + + // this is an early out for a full warp + if (query_id >= n_queries) return; + + value_idx column_count = 0; + + const value_t* x_ptr = X + (n_cols * query_id); + adj += query_id * m; + + // we omit the sqrt() in the inner distance compute + const value_t eps2 = eps * eps; + +#pragma nounroll + for (uint32_t cur_k0 = 0; cur_k0 < n_landmarks; cur_k0 += raft::WarpSize) { + // Pre-compute landmark_dist & triangularization checks for 32 iterations + const uint32_t lane_k = cur_k0 + lid; + const value_t lane_R_dist_sq = lane_k < n_landmarks ? dfunc(x_ptr, R + lane_k * n_cols, n_cols) + : std::numeric_limits::max(); + const int lane_check = lane_k < n_landmarks + ? static_cast(lane_R_dist_sq <= squared(eps + R_radius[lane_k])) + : 0; + + int lane_mask = raft::ballot(lane_check); + if (lane_mask == 0) continue; + + // reverse to use __clz instead of __ffs + lane_mask = __brev(lane_mask); + do { + // look for next k_offset + const uint32_t k_offset = __clz(lane_mask); + + const uint32_t cur_k = cur_k0 + k_offset; + + // The whole warp should iterate through the elements in the current R + const value_idx R_start_offset = R_indptr[cur_k]; + + // update lane_mask for next iteration - erase bits up to k_offset + lane_mask &= (0x7fffffff >> k_offset); + + const uint32_t R_size = R_indptr[cur_k + 1] - R_start_offset; + + // we have precomputed the query<->landmark distance + const value_t cur_R_dist = raft::sqrt(raft::shfl(lane_R_dist_sq, k_offset)); + + const uint32_t limit = raft::Pow2::roundDown(R_size); + uint32_t i = limit + lid; + + // R_1nn_dists are sorted ascendingly for each landmark + // Iterating backwards, after pruning the first point w.r.t. triangle + // inequality all subsequent points can be pruned as well + const value_t* y_ptr = X_reordered + (n_cols * (R_start_offset + i)); + { + const value_t min_warp_dist = + limit < R_size ? R_1nn_dists[R_start_offset + limit] : cur_R_dist; + const value_t dist = + (i < R_size) ? dfunc(x_ptr, y_ptr, n_cols) : std::numeric_limits::max(); + const bool in_range = (dist <= eps2); + if (in_range) { + auto index = R_1nn_cols[R_start_offset + i]; + column_count++; + adj[index] = true; + } + // abort in case subsequent points cannot possibly be in reach + i *= (cur_R_dist - min_warp_dist <= eps); + } + + uint32_t i0 = raft::shfl(i, 0); + + while (i0 >= raft::WarpSize) { + y_ptr -= raft::WarpSize * n_cols; + i0 -= raft::WarpSize; + const value_t min_warp_dist = R_1nn_dists[R_start_offset + i0]; + const value_t dist = dfunc(x_ptr, y_ptr, n_cols); + const bool in_range = (dist <= eps2); + if (in_range) { + auto index = R_1nn_cols[R_start_offset + i0 + lid]; + column_count++; + adj[index] = true; + } + // abort in case subsequent points cannot possibly be in reach + i0 *= (cur_R_dist - min_warp_dist <= eps); + } + } while (lane_mask); + } + + if (vd != nullptr) { + value_idx row_sum = raft::warpReduce(column_count); + if (lid == 0) vd[query_id] = row_sum; + } +} + +template +RAFT_KERNEL block_rbc_kernel_eps_csr_pass(const value_t* X_reordered, + const value_t* X, + const value_int n_queries, + const value_int n_cols, + const value_t* R, + const value_int m, + const value_t eps, + const value_int n_landmarks, + const value_idx* R_indptr, + const value_idx* R_1nn_cols, + const value_t* R_1nn_dists, + const value_t* R_radius, + distance_func dfunc, + value_idx* adj_ia, + value_idx* adj_ja) +{ + constexpr int num_warps = tpb / raft::WarpSize; + + // process 1 query per warp + const uint32_t lid = raft::laneId(); + const uint32_t lid_mask = (1 << lid) - 1; + + // this should help the compiler to prevent branches + const int query_id = raft::shfl(blockIdx.x * num_warps + (threadIdx.x / raft::WarpSize), 0); + + // this is an early out for a full warp + if (query_id >= n_queries) return; + + uint32_t column_index_offset = 0; + + if constexpr (write_pass) { + value_idx offset = adj_ia[query_id]; + // we have no neighbors to fill for this query + if (offset == adj_ia[query_id + 1]) return; + adj_ja += offset; + } + + const value_t* x_ptr = X + (n_cols * query_id); + + // we omit the sqrt() in the inner distance compute + const value_t eps2 = eps * eps; + +#pragma nounroll + for (uint32_t cur_k0 = 0; cur_k0 < n_landmarks; cur_k0 += raft::WarpSize) { + // Pre-compute landmark_dist & triangularization checks for 32 iterations + const uint32_t lane_k = cur_k0 + lid; + const value_t lane_R_dist_sq = lane_k < n_landmarks ? dfunc(x_ptr, R + lane_k * n_cols, n_cols) + : std::numeric_limits::max(); + const int lane_check = lane_k < n_landmarks + ? static_cast(lane_R_dist_sq <= squared(eps + R_radius[lane_k])) + : 0; + + int lane_mask = raft::ballot(lane_check); + if (lane_mask == 0) continue; + + // reverse to use __clz instead of __ffs + lane_mask = __brev(lane_mask); + do { + // look for next k_offset + const uint32_t k_offset = __clz(lane_mask); + + const uint32_t cur_k = cur_k0 + k_offset; + + // The whole warp should iterate through the elements in the current R + const value_idx R_start_offset = R_indptr[cur_k]; + + // update lane_mask for next iteration - erase bits up to k_offset + lane_mask &= (0x7fffffff >> k_offset); + + const uint32_t R_size = R_indptr[cur_k + 1] - R_start_offset; + + // we have precomputed the query<->landmark distance + const value_t cur_R_dist = raft::sqrt(raft::shfl(lane_R_dist_sq, k_offset)); + + const uint32_t limit = raft::Pow2::roundDown(R_size); + uint32_t i = limit + lid; + + // R_1nn_dists are sorted ascendingly for each landmark + // Iterating backwards, after pruning the first point w.r.t. triangle + // inequality all subsequent points can be pruned as well + const value_t* y_ptr = X_reordered + (n_cols * (R_start_offset + i)); + { + const value_t min_warp_dist = + limit < R_size ? R_1nn_dists[R_start_offset + limit] : cur_R_dist; + const value_t dist = + (i < R_size) ? dfunc(x_ptr, y_ptr, n_cols) : std::numeric_limits::max(); + const bool in_range = (dist <= eps2); + if constexpr (write_pass) { + const int mask = raft::ballot(in_range); + if (in_range) { + const uint32_t index = R_1nn_cols[R_start_offset + i]; + const uint32_t row_pos = __popc(mask & lid_mask); + adj_ja[row_pos] = index; + } + adj_ja += __popc(mask); + } else { + column_index_offset += (in_range); + } + // abort in case subsequent points cannot possibly be in reach + i *= (cur_R_dist - min_warp_dist <= eps); + } + + uint32_t i0 = raft::shfl(i, 0); + + while (i0 >= raft::WarpSize) { + y_ptr -= raft::WarpSize * n_cols; + i0 -= raft::WarpSize; + const value_t min_warp_dist = R_1nn_dists[R_start_offset + i0]; + const value_t dist = dfunc(x_ptr, y_ptr, n_cols); + const bool in_range = (dist <= eps2); + if constexpr (write_pass) { + const int mask = raft::ballot(in_range); + if (in_range) { + const uint32_t index = R_1nn_cols[R_start_offset + i0 + lid]; + const uint32_t row_pos = __popc(mask & lid_mask); + adj_ja[row_pos] = index; + } + adj_ja += __popc(mask); + } else { + column_index_offset += (in_range); + } + // abort in case subsequent points cannot possibly be in reach + i0 *= (cur_R_dist - min_warp_dist <= eps); + } + } while (lane_mask); + } + + if constexpr (!write_pass) { + value_idx row_sum = raft::warpReduce(column_index_offset); + if (lid == 0) adj_ia[query_id] = row_sum; + } +} + +template +RAFT_KERNEL __launch_bounds__(tpb) + block_rbc_kernel_eps_csr_pass_xd(const value_t* __restrict__ X_reordered, + const value_t* __restrict__ X, + const value_int n_queries, + const value_int n_cols, + const value_t* __restrict__ R, + const value_int m, + const value_t eps, + const value_int n_landmarks, + const value_idx* __restrict__ R_indptr, + const value_idx* __restrict__ R_1nn_cols, + const value_t* __restrict__ R_1nn_dists, + const value_t* __restrict__ R_radius, + distance_func dfunc, + value_idx* __restrict__ adj_ia, + value_idx* adj_ja) +{ + constexpr int num_warps = tpb / raft::WarpSize; + + // process 1 query per warp + const uint32_t lid = raft::laneId(); + const uint32_t lid_mask = (1 << lid) - 1; + + // this should help the compiler to prevent branches + const int query_id = raft::shfl(blockIdx.x * num_warps + (threadIdx.x / raft::WarpSize), 0); + + // this is an early out for a full warp + if (query_id >= n_queries) return; + + uint32_t column_index_offset = 0; + + if constexpr (write_pass) { + value_idx offset = adj_ia[query_id]; + // we have no neighbors to fill for this query + if (offset == adj_ia[query_id + 1]) return; + adj_ja += offset; + } + + const value_t* x_ptr = X + (dim * query_id); + value_t local_x_ptr[dim]; +#pragma unroll + for (uint32_t i = 0; i < dim; ++i) { + local_x_ptr[i] = x_ptr[i]; + } + + // we omit the sqrt() in the inner distance compute + const value_t eps2 = eps * eps; + +#pragma nounroll + for (uint32_t cur_k0 = 0; cur_k0 < n_landmarks; cur_k0 += raft::WarpSize) { + // Pre-compute landmark_dist & triangularization checks for 32 iterations + const uint32_t lane_k = cur_k0 + lid; + const value_t lane_R_dist_sq = lane_k < n_landmarks ? dfunc(local_x_ptr, R + lane_k * dim, dim) + : std::numeric_limits::max(); + const int lane_check = lane_k < n_landmarks + ? static_cast(lane_R_dist_sq <= squared(eps + R_radius[lane_k])) + : 0; + + int lane_mask = raft::ballot(lane_check); + if (lane_mask == 0) continue; + + // reverse to use __clz instead of __ffs + lane_mask = __brev(lane_mask); + do { + // look for next k_offset + const uint32_t k_offset = __clz(lane_mask); + + const uint32_t cur_k = cur_k0 + k_offset; + + // The whole warp should iterate through the elements in the current R + const value_idx R_start_offset = R_indptr[cur_k]; + + // update lane_mask for next iteration - erase bits up to k_offset + lane_mask &= (0x7fffffff >> k_offset); + + const uint32_t R_size = R_indptr[cur_k + 1] - R_start_offset; + + // we have precomputed the query<->landmark distance + const value_t cur_R_dist = raft::sqrt(raft::shfl(lane_R_dist_sq, k_offset)); + + const uint32_t limit = raft::Pow2::roundDown(R_size); + uint32_t i = limit + lid; + + // R_1nn_dists are sorted ascendingly for each landmark + // Iterating backwards, after pruning the first point w.r.t. triangle + // inequality all subsequent points can be pruned as well + const value_t* y_ptr = X_reordered + (dim * (R_start_offset + i)); + { + const value_t min_warp_dist = + limit < R_size ? R_1nn_dists[R_start_offset + limit] : cur_R_dist; + const value_t dist = + (i < R_size) ? dfunc(local_x_ptr, y_ptr, dim) : std::numeric_limits::max(); + const bool in_range = (dist <= eps2); + if constexpr (write_pass) { + const int mask = raft::ballot(in_range); + if (in_range) { + const uint32_t index = R_1nn_cols[R_start_offset + i]; + const uint32_t row_pos = __popc(mask & lid_mask); + adj_ja[row_pos] = index; + } + adj_ja += __popc(mask); + } else { + column_index_offset += (in_range); + } + // abort in case subsequent points cannot possibly be in reach + i *= (cur_R_dist - min_warp_dist <= eps); + } + + uint32_t i0 = raft::shfl(i, 0); + + while (i0 >= raft::WarpSize) { + y_ptr -= raft::WarpSize * dim; + i0 -= raft::WarpSize; + const value_t min_warp_dist = R_1nn_dists[R_start_offset + i0]; + const value_t dist = dfunc(local_x_ptr, y_ptr, dim); + const bool in_range = (dist <= eps2); + if constexpr (write_pass) { + const int mask = raft::ballot(in_range); + if (in_range) { + const uint32_t index = R_1nn_cols[R_start_offset + i0 + lid]; + const uint32_t row_pos = __popc(mask & lid_mask); + adj_ja[row_pos] = index; + } + adj_ja += __popc(mask); + } else { + column_index_offset += (in_range); + } + // abort in case subsequent points cannot possibly be in reach + i0 *= (cur_R_dist - min_warp_dist <= eps); + } + } while (lane_mask); + } + + if constexpr (!write_pass) { + value_idx row_sum = raft::warpReduce(column_index_offset); + if (lid == 0) adj_ia[query_id] = row_sum; + } +} + +template +RAFT_KERNEL block_rbc_kernel_eps_max_k(const value_t* X_reordered, + const value_t* X, + const value_int n_queries, + const value_int n_cols, + const value_t* R, + const value_int m, + const value_t eps, + const value_int n_landmarks, + const value_idx* R_indptr, + const value_idx* R_1nn_cols, + const value_t* R_1nn_dists, + const value_t* R_radius, + distance_func dfunc, + value_idx* vd, + const value_int max_k, + value_idx* tmp) +{ + constexpr int num_warps = tpb / raft::WarpSize; + + // process 1 query per warp + const uint32_t lid = raft::laneId(); + const uint32_t lid_mask = (1 << lid) - 1; + + // this should help the compiler to prevent branches + const int query_id = raft::shfl(blockIdx.x * num_warps + (threadIdx.x / raft::WarpSize), 0); + + // this is an early out for a full warp + if (query_id >= n_queries) return; + + value_idx column_count = 0; + + const value_t* x_ptr = X + (n_cols * query_id); + tmp += query_id * max_k; + + // we omit the sqrt() in the inner distance compute + const value_t eps2 = eps * eps; + +#pragma nounroll + for (uint32_t cur_k0 = 0; cur_k0 < n_landmarks; cur_k0 += raft::WarpSize) { + // Pre-compute landmark_dist & triangularization checks for 32 iterations + const uint32_t lane_k = cur_k0 + lid; + const value_t lane_R_dist_sq = lane_k < n_landmarks ? dfunc(x_ptr, R + lane_k * n_cols, n_cols) + : std::numeric_limits::max(); + const int lane_check = lane_k < n_landmarks + ? static_cast(lane_R_dist_sq <= squared(eps + R_radius[lane_k])) + : 0; + + int lane_mask = raft::ballot(lane_check); + if (lane_mask == 0) continue; + + // reverse to use __clz instead of __ffs + lane_mask = __brev(lane_mask); + do { + // look for next k_offset + const uint32_t k_offset = __clz(lane_mask); + + const uint32_t cur_k = cur_k0 + k_offset; + + // The whole warp should iterate through the elements in the current R + const value_idx R_start_offset = R_indptr[cur_k]; + + // update lane_mask for next iteration - erase bits up to k_offset + lane_mask &= (0x7fffffff >> k_offset); + + const uint32_t R_size = R_indptr[cur_k + 1] - R_start_offset; + + // we have precomputed the query<->landmark distance + const value_t cur_R_dist = raft::sqrt(raft::shfl(lane_R_dist_sq, k_offset)); + + const uint32_t limit = raft::Pow2::roundDown(R_size); + uint32_t i = limit + lid; + + // R_1nn_dists are sorted ascendingly for each landmark + // Iterating backwards, after pruning the first point w.r.t. triangle + // inequality all subsequent points can be pruned as well + const value_t* y_ptr = X_reordered + (n_cols * (R_start_offset + i)); + { + const value_t min_warp_dist = + limit < R_size ? R_1nn_dists[R_start_offset + limit] : cur_R_dist; + const value_t dist = + (i < R_size) ? dfunc(x_ptr, y_ptr, n_cols) : std::numeric_limits::max(); + const bool in_range = (dist <= eps2); + const int mask = raft::ballot(in_range); + if (in_range) { + auto row_pos = column_count + __popc(mask & lid_mask); + // we still continue to look for more hits to return valid vd + if (row_pos < max_k) { + auto index = R_1nn_cols[R_start_offset + i]; + tmp[row_pos] = index; + } + } + column_count += __popc(mask); + // abort in case subsequent points cannot possibly be in reach + i *= (cur_R_dist - min_warp_dist <= eps); + } + + uint32_t i0 = raft::shfl(i, 0); + + while (i0 >= raft::WarpSize) { + y_ptr -= raft::WarpSize * n_cols; + i0 -= raft::WarpSize; + const value_t min_warp_dist = R_1nn_dists[R_start_offset + i0]; + const value_t dist = dfunc(x_ptr, y_ptr, n_cols); + const bool in_range = (dist <= eps2); + const int mask = raft::ballot(in_range); + if (in_range) { + auto row_pos = column_count + __popc(mask & lid_mask); + // we still continue to look for more hits to return valid vd + if (row_pos < max_k) { + auto index = R_1nn_cols[R_start_offset + i0 + lid]; + tmp[row_pos] = index; + } + } + column_count += __popc(mask); + // abort in case subsequent points cannot possibly be in reach + i0 *= (cur_R_dist - min_warp_dist <= eps); + } + } while (lane_mask); + } + + if (lid == 0) vd[query_id] = column_count; +} + +template +RAFT_KERNEL block_rbc_kernel_eps_max_k_copy(const value_int max_k, + const value_idx* adj_ia, + const value_idx* tmp, + value_idx* adj_ja) +{ + value_int offset = blockIdx.x * max_k; + + value_int row_idx = blockIdx.x; + value_idx col_start_idx = adj_ia[row_idx]; + value_idx num_cols = adj_ia[row_idx + 1] - col_start_idx; + + value_int limit = raft::Pow2::roundDown(num_cols); + value_int i = threadIdx.x; + for (; i < limit; i += tpb) { + adj_ja[col_start_idx + i] = tmp[offset + i]; + } + if (i < num_cols) { adj_ja[col_start_idx + i] = tmp[offset + i]; } +} + +template +void rbc_low_dim_pass_one( + raft::resources const& handle, + const cuvs::neighbors::ball_cover::index& index, + const value_t* query, + const value_int n_query_rows, + value_int k, + const value_idx* R_knn_inds, + const value_t* R_knn_dists, + dist_func& dfunc, + value_idx* inds, + value_t* dists, + float weight, + value_int* dists_counter) +{ + if (k <= 32) + block_rbc_kernel_registers + <<>>( + index.get_X_reordered().data_handle(), + query, + index.n, + R_knn_inds, + R_knn_dists, + index.m, + k, + index.get_R_indptr().data_handle(), + index.get_R_1nn_cols().data_handle(), + index.get_R_1nn_dists().data_handle(), + inds, + dists, + dists_counter, + index.get_R_radius().data_handle(), + dfunc, + weight); + + else if (k <= 64) + block_rbc_kernel_registers + <<>>( + index.get_X_reordered().data_handle(), + query, + index.n, + R_knn_inds, + R_knn_dists, + index.m, + k, + index.get_R_indptr().data_handle(), + index.get_R_1nn_cols().data_handle(), + index.get_R_1nn_dists().data_handle(), + inds, + dists, + dists_counter, + index.get_R_radius().data_handle(), + dfunc, + weight); + else if (k <= 128) + block_rbc_kernel_registers + <<>>( + index.get_X_reordered().data_handle(), + query, + index.n, + R_knn_inds, + R_knn_dists, + index.m, + k, + index.get_R_indptr().data_handle(), + index.get_R_1nn_cols().data_handle(), + index.get_R_1nn_dists().data_handle(), + inds, + dists, + dists_counter, + index.get_R_radius().data_handle(), + dfunc, + weight); + + else if (k <= 256) + block_rbc_kernel_registers + <<>>( + index.get_X_reordered().data_handle(), + query, + index.n, + R_knn_inds, + R_knn_dists, + index.m, + k, + index.get_R_indptr().data_handle(), + index.get_R_1nn_cols().data_handle(), + index.get_R_1nn_dists().data_handle(), + inds, + dists, + dists_counter, + index.get_R_radius().data_handle(), + dfunc, + weight); + + else if (k <= 512) + block_rbc_kernel_registers + <<>>( + index.get_X_reordered().data_handle(), + query, + index.n, + R_knn_inds, + R_knn_dists, + index.m, + k, + index.get_R_indptr().data_handle(), + index.get_R_1nn_cols().data_handle(), + index.get_R_1nn_dists().data_handle(), + inds, + dists, + dists_counter, + index.get_R_radius().data_handle(), + dfunc, + weight); + + else if (k <= 1024) + block_rbc_kernel_registers + <<>>( + index.get_X_reordered().data_handle(), + query, + index.n, + R_knn_inds, + R_knn_dists, + index.m, + k, + index.get_R_indptr().data_handle(), + index.get_R_1nn_cols().data_handle(), + index.get_R_1nn_dists().data_handle(), + inds, + dists, + dists_counter, + index.get_R_radius().data_handle(), + dfunc, + weight); +} + +template +void rbc_low_dim_pass_two( + raft::resources const& handle, + const cuvs::neighbors::ball_cover::index& index, + const value_t* query, + const value_int n_query_rows, + value_int k, + const value_idx* R_knn_inds, + const value_t* R_knn_dists, + dist_func& dfunc, + value_idx* inds, + value_t* dists, + float weight, + value_int* post_dists_counter) +{ + const value_int bitset_size = ceil(index.n_landmarks / 32.0); + + rmm::device_uvector bitset(bitset_size * n_query_rows, + raft::resource::get_cuda_stream(handle)); + thrust::fill( + raft::resource::get_thrust_policy(handle), bitset.data(), bitset.data() + bitset.size(), 0); + + perform_post_filter_registers + <<>>(query, + index.n, + R_knn_inds, + R_knn_dists, + index.get_R_radius().data_handle(), + index.get_R().data_handle(), + index.n_landmarks, + bitset_size, + k, + dfunc, + bitset.data(), + weight); + + if (k <= 32) + compute_final_dists_registers + <<>>( + index.get_X_reordered().data_handle(), + query, + index.n, + bitset.data(), + bitset_size, + index.get_R_closest_landmark_dists().data_handle(), + index.get_R_indptr().data_handle(), + index.get_R_1nn_cols().data_handle(), + index.get_R_1nn_dists().data_handle(), + inds, + dists, + index.n_landmarks, + k, + dfunc, + post_dists_counter); + else if (k <= 64) + compute_final_dists_registers + <<>>( + index.get_X_reordered().data_handle(), + query, + index.n, + bitset.data(), + bitset_size, + index.get_R_closest_landmark_dists().data_handle(), + index.get_R_indptr().data_handle(), + index.get_R_1nn_cols().data_handle(), + index.get_R_1nn_dists().data_handle(), + inds, + dists, + index.n_landmarks, + k, + dfunc, + post_dists_counter); + else if (k <= 128) + compute_final_dists_registers + <<>>( + index.get_X_reordered().data_handle(), + query, + index.n, + bitset.data(), + bitset_size, + index.get_R_closest_landmark_dists().data_handle(), + index.get_R_indptr().data_handle(), + index.get_R_1nn_cols().data_handle(), + index.get_R_1nn_dists().data_handle(), + inds, + dists, + index.n_landmarks, + k, + dfunc, + post_dists_counter); + else if (k <= 256) + compute_final_dists_registers + <<>>( + index.get_X_reordered().data_handle(), + query, + index.n, + bitset.data(), + bitset_size, + index.get_R_closest_landmark_dists().data_handle(), + index.get_R_indptr().data_handle(), + index.get_R_1nn_cols().data_handle(), + index.get_R_1nn_dists().data_handle(), + inds, + dists, + index.n_landmarks, + k, + dfunc, + post_dists_counter); + else if (k <= 512) + compute_final_dists_registers + <<>>( + index.get_X_reordered().data_handle(), + query, + index.n, + bitset.data(), + bitset_size, + index.get_R_closest_landmark_dists().data_handle(), + index.get_R_indptr().data_handle(), + index.get_R_1nn_cols().data_handle(), + index.get_R_1nn_dists().data_handle(), + inds, + dists, + index.n_landmarks, + k, + dfunc, + post_dists_counter); + else if (k <= 1024) + compute_final_dists_registers + <<>>( + index.get_X_reordered().data_handle(), + query, + index.n, + bitset.data(), + bitset_size, + index.get_R_closest_landmark_dists().data_handle(), + index.get_R_indptr().data_handle(), + index.get_R_1nn_cols().data_handle(), + index.get_R_1nn_dists().data_handle(), + inds, + dists, + index.n_landmarks, + k, + dfunc, + post_dists_counter); +} + +template +void rbc_eps_pass( + raft::resources const& handle, + const cuvs::neighbors::ball_cover::index& index, + const value_t* query, + const value_int n_query_rows, + value_t eps, + const value_t* R, + dist_func& dfunc, + bool* adj, + value_idx* vd) +{ + block_rbc_kernel_eps_dense + <<>>( + index.get_X_reordered().data_handle(), + query, + n_query_rows, + index.n, + R, + index.m, + eps, + index.n_landmarks, + index.get_R_indptr().data_handle(), + index.get_R_1nn_cols().data_handle(), + index.get_R_1nn_dists().data_handle(), + index.get_R_radius().data_handle(), + dfunc, + adj, + vd); + + if (vd != nullptr) { + value_idx sum = + thrust::reduce(raft::resource::get_thrust_policy(handle), vd, vd + n_query_rows); + // copy sum to last element + RAFT_CUDA_TRY(cudaMemcpyAsync(vd + n_query_rows, + &sum, + sizeof(value_idx), + cudaMemcpyHostToDevice, + raft::resource::get_cuda_stream(handle))); + } + + raft::resource::sync_stream(handle); +} + +template +void rbc_eps_pass( + raft::resources const& handle, + const cuvs::neighbors::ball_cover::index& index, + const value_t* query, + const value_int n_query_rows, + value_t eps, + value_int* max_k, + const value_t* R, + dist_func& dfunc, + value_idx* adj_ia, + value_idx* adj_ja, + value_idx* vd) +{ + // if max_k == nullptr we are either pass 1 or pass 2 + if (max_k == nullptr) { + if (adj_ja == nullptr) { + // pass 1 -> only compute adj_ia / vd + value_idx* vd_ptr = (vd != nullptr) ? vd : adj_ia; + if (index.n == 2) { + block_rbc_kernel_eps_csr_pass_xd + <<(n_query_rows, 2), + 64, + 0, + raft::resource::get_cuda_stream(handle)>>>(index.get_X_reordered().data_handle(), + query, + n_query_rows, + index.n, + R, + index.m, + eps, + index.n_landmarks, + index.get_R_indptr().data_handle(), + index.get_R_1nn_cols().data_handle(), + index.get_R_1nn_dists().data_handle(), + index.get_R_radius().data_handle(), + dfunc, + vd_ptr, + nullptr); + } else if (index.n == 3) { + block_rbc_kernel_eps_csr_pass_xd + <<(n_query_rows, 2), + 64, + 0, + raft::resource::get_cuda_stream(handle)>>>(index.get_X_reordered().data_handle(), + query, + n_query_rows, + index.n, + R, + index.m, + eps, + index.n_landmarks, + index.get_R_indptr().data_handle(), + index.get_R_1nn_cols().data_handle(), + index.get_R_1nn_dists().data_handle(), + index.get_R_radius().data_handle(), + dfunc, + vd_ptr, + nullptr); + } else { + block_rbc_kernel_eps_csr_pass + <<(n_query_rows, 2), + 64, + 0, + raft::resource::get_cuda_stream(handle)>>>(index.get_X_reordered().data_handle(), + query, + n_query_rows, + index.n, + R, + index.m, + eps, + index.n_landmarks, + index.get_R_indptr().data_handle(), + index.get_R_1nn_cols().data_handle(), + index.get_R_1nn_dists().data_handle(), + index.get_R_radius().data_handle(), + dfunc, + vd_ptr, + nullptr); + } + + thrust::exclusive_scan(raft::resource::get_thrust_policy(handle), + vd_ptr, + vd_ptr + n_query_rows + 1, + adj_ia, + (value_idx)0); + + } else { + // pass 2 -> fill in adj_ja + if (index.n == 2) { + block_rbc_kernel_eps_csr_pass_xd + <<(n_query_rows, 2), + 64, + 0, + raft::resource::get_cuda_stream(handle)>>>(index.get_X_reordered().data_handle(), + query, + n_query_rows, + index.n, + R, + index.m, + eps, + index.n_landmarks, + index.get_R_indptr().data_handle(), + index.get_R_1nn_cols().data_handle(), + index.get_R_1nn_dists().data_handle(), + index.get_R_radius().data_handle(), + dfunc, + adj_ia, + adj_ja); + } else if (index.n == 3) { + block_rbc_kernel_eps_csr_pass_xd + <<(n_query_rows, 2), + 64, + 0, + raft::resource::get_cuda_stream(handle)>>>(index.get_X_reordered().data_handle(), + query, + n_query_rows, + index.n, + R, + index.m, + eps, + index.n_landmarks, + index.get_R_indptr().data_handle(), + index.get_R_1nn_cols().data_handle(), + index.get_R_1nn_dists().data_handle(), + index.get_R_radius().data_handle(), + dfunc, + adj_ia, + adj_ja); + } else { + block_rbc_kernel_eps_csr_pass + <<(n_query_rows, 2), + 64, + 0, + raft::resource::get_cuda_stream(handle)>>>(index.get_X_reordered().data_handle(), + query, + n_query_rows, + index.n, + R, + index.m, + eps, + index.n_landmarks, + index.get_R_indptr().data_handle(), + index.get_R_1nn_cols().data_handle(), + index.get_R_1nn_dists().data_handle(), + index.get_R_radius().data_handle(), + dfunc, + adj_ia, + adj_ja); + } + } + } else { + value_int max_k_in = *max_k; + value_idx* vd_ptr = (vd != nullptr) ? vd : adj_ia; + + rmm::device_uvector tmp(n_query_rows * max_k_in, + raft::resource::get_cuda_stream(handle)); + + block_rbc_kernel_eps_max_k + <<(n_query_rows, 2), + 64, + 0, + raft::resource::get_cuda_stream(handle)>>>(index.get_X_reordered().data_handle(), + query, + n_query_rows, + index.n, + R, + index.m, + eps, + index.n_landmarks, + index.get_R_indptr().data_handle(), + index.get_R_1nn_cols().data_handle(), + index.get_R_1nn_dists().data_handle(), + index.get_R_radius().data_handle(), + dfunc, + vd_ptr, + max_k_in, + tmp.data()); + + value_int actual_max = thrust::reduce(raft::resource::get_thrust_policy(handle), + vd_ptr, + vd_ptr + n_query_rows, + (value_idx)0, + thrust::maximum()); + + if (actual_max > max_k_in) { + // ceil vd to max_k + thrust::transform(raft::resource::get_thrust_policy(handle), + vd_ptr, + vd_ptr + n_query_rows, + vd_ptr, + [max_k_in] __device__(value_idx vd_count) { + return vd_count > max_k_in ? max_k_in : vd_count; + }); + } + + thrust::exclusive_scan(raft::resource::get_thrust_policy(handle), + vd_ptr, + vd_ptr + n_query_rows + 1, + adj_ia, + (value_idx)0); + + block_rbc_kernel_eps_max_k_copy + <<>>( + max_k_in, adj_ia, tmp.data(), adj_ja); + + // return 'new' max-k + *max_k = actual_max; + } + + if (vd != nullptr && (max_k != nullptr || adj_ja == nullptr)) { + // copy sum to last element + RAFT_CUDA_TRY(cudaMemcpyAsync(vd + n_query_rows, + adj_ia + n_query_rows, + sizeof(value_idx), + cudaMemcpyDeviceToDevice, + raft::resource::get_cuda_stream(handle))); + } + + raft::resource::sync_stream(handle); +} + +}; // namespace cuvs::neighbors::ball_cover::detail diff --git a/cpp/src/neighbors/ball_cover/registers.cuh b/cpp/src/neighbors/ball_cover/registers.cuh new file mode 100644 index 000000000..1dc4a0bc9 --- /dev/null +++ b/cpp/src/neighbors/ball_cover/registers.cuh @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#ifndef CUVS_EXPLICIT_INSTANTIATE_ONLY +#include "registers-inl.cuh" +#endif + +#include "registers-ext.cuh" diff --git a/cpp/src/neighbors/ball_cover/registers_types.cuh b/cpp/src/neighbors/ball_cover/registers_types.cuh new file mode 100644 index 000000000..3777932a7 --- /dev/null +++ b/cpp/src/neighbors/ball_cover/registers_types.cuh @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "../detail/haversine_distance.cuh" // compute_haversine + +#include // uint32_t + +namespace cuvs::neighbors::ball_cover::detail { + +template +struct DistFunc { + virtual __device__ __host__ __forceinline__ value_t operator()(const value_t* a, + const value_t* b, + const value_int n_dims) + { + return -1; + }; +}; + +template +struct HaversineFunc : public DistFunc { + __device__ __host__ __forceinline__ value_t operator()(const value_t* a, + const value_t* b, + const value_int n_dims) override + { + return cuvs::neighbors::detail::compute_haversine(a[0], b[0], a[1], b[1]); + } +}; + +template +struct EuclideanFunc : public DistFunc { + __device__ __host__ __forceinline__ value_t operator()(const value_t* a, + const value_t* b, + const value_int n_dims) override + { + value_t sum_sq = 0; + for (value_int i = 0; i < n_dims; ++i) { + value_t diff = a[i] - b[i]; + sum_sq += diff * diff; + } + + return raft::sqrt(sum_sq); + } +}; + +template +struct EuclideanSqFunc : public DistFunc { + __device__ __host__ __forceinline__ value_t operator()(const value_t* a, + const value_t* b, + const value_int n_dims) override + { + value_t sum_sq = 0; + for (value_int i = 0; i < n_dims; ++i) { + value_t diff = a[i] - b[i]; + sum_sq += diff * diff; + } + return sum_sq; + } +}; + +}; // namespace cuvs::neighbors::ball_cover::detail diff --git a/cpp/test/CMakeLists.txt b/cpp/test/CMakeLists.txt index a30e2dec7..780bdd7f8 100644 --- a/cpp/test/CMakeLists.txt +++ b/cpp/test/CMakeLists.txt @@ -94,13 +94,13 @@ endfunction() if(BUILD_TESTS) ConfigureTest( - NAME NEIGHBORS_TEST PATH neighbors/brute_force.cu - neighbors/brute_force_prefiltered.cu neighbors/refine.cu GPUS 1 PERCENT 100 + NAME NEIGHBORS_TEST PATH neighbors/brute_force.cu neighbors/brute_force_prefiltered.cu + neighbors/refine.cu GPUS 1 PERCENT 100 ) ConfigureTest( - NAME CLUSTER_TEST PATH cluster/kmeans.cu cluster/kmeans_balanced.cu - cluster/kmeans_find_k.cu cluster/linkage.cu GPUS 1 PERCENT 100 + NAME CLUSTER_TEST PATH cluster/kmeans.cu cluster/kmeans_balanced.cu cluster/kmeans_find_k.cu + cluster/linkage.cu GPUS 1 PERCENT 100 ) ConfigureTest( @@ -130,15 +130,8 @@ if(BUILD_TESTS) ) ConfigureTest( - NAME - NEIGHBORS_ANN_BRUTE_FORCE_TEST - PATH - neighbors/ann_brute_force/test_float.cu - neighbors/ann_brute_force/test_half.cu - GPUS - 1 - PERCENT - 100 + NAME NEIGHBORS_ANN_BRUTE_FORCE_TEST PATH neighbors/ann_brute_force/test_float.cu + neighbors/ann_brute_force/test_half.cu GPUS 1 PERCENT 100 ) ConfigureTest( @@ -167,6 +160,8 @@ if(BUILD_TESTS) 100 ) + ConfigureTest(NAME NEIGHBORS_BALL_COVER_TEST PATH neighbors/ball_cover.cu GPUS 1 PERCENT 100) + if(BUILD_CAGRA_HNSWLIB) ConfigureTest(NAME NEIGHBORS_HNSW_TEST PATH neighbors/hnsw.cu GPUS 1 PERCENT 100) endif() @@ -201,24 +196,20 @@ endif() if(BUILD_C_TESTS) ConfigureTest(NAME INTEROP_TEST PATH core/interop.cu C_LIB) ConfigureTest( - NAME DISTANCE_C_TEST PATH distance/run_pairwise_distance_c.c - distance/pairwise_distance_c.cu C_LIB - ) - - ConfigureTest( - NAME BRUTEFORCE_C_TEST PATH neighbors/run_brute_force_c.c neighbors/brute_force_c.cu + NAME DISTANCE_C_TEST PATH distance/run_pairwise_distance_c.c distance/pairwise_distance_c.cu C_LIB ) ConfigureTest( - NAME IVF_FLAT_C_TEST PATH neighbors/run_ivf_flat_c.c neighbors/ann_ivf_flat_c.cu - C_LIB + NAME BRUTEFORCE_C_TEST PATH neighbors/run_brute_force_c.c neighbors/brute_force_c.cu C_LIB ) ConfigureTest( - NAME IVF_PQ_C_TEST PATH neighbors/run_ivf_pq_c.c neighbors/ann_ivf_pq_c.cu C_LIB + NAME IVF_FLAT_C_TEST PATH neighbors/run_ivf_flat_c.c neighbors/ann_ivf_flat_c.cu C_LIB ) + ConfigureTest(NAME IVF_PQ_C_TEST PATH neighbors/run_ivf_pq_c.c neighbors/ann_ivf_pq_c.cu C_LIB) + ConfigureTest(NAME CAGRA_C_TEST PATH neighbors/ann_cagra_c.cu C_LIB) endif() diff --git a/cpp/test/neighbors/ball_cover.cu b/cpp/test/neighbors/ball_cover.cu new file mode 100644 index 000000000..9a2f76059 --- /dev/null +++ b/cpp/test/neighbors/ball_cover.cu @@ -0,0 +1,393 @@ +/* + * Copyright (c) 2021-2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../test_utils.cuh" +#include "spatial_data.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include + +namespace cuvs::neighbors::ball_cover { +using namespace std; + +template +RAFT_KERNEL count_discrepancies_kernel(value_idx* actual_idx, + value_idx* expected_idx, + value_t* actual, + value_t* expected, + uint32_t m, + uint32_t n, + uint32_t* out, + float thres = 1e-3) +{ + uint32_t row = blockDim.x * blockIdx.x + threadIdx.x; + + int n_diffs = 0; + if (row < m) { + for (uint32_t i = 0; i < n; i++) { + value_t d = actual[row * n + i] - expected[row * n + i]; + bool matches = (fabsf(d) <= thres) || (actual_idx[row * n + i] == expected_idx[row * n + i] && + actual_idx[row * n + i] == row); + + if (!matches) { + printf( + "row=%ud, n=%ud, actual_dist=%f, actual_ind=%ld, expected_dist=%f, expected_ind=%ld\n", + row, + i, + actual[row * n + i], + actual_idx[row * n + i], + expected[row * n + i], + expected_idx[row * n + i]); + } + n_diffs += !matches; + out[row] = n_diffs; + } + } +} + +struct is_nonzero { + __host__ __device__ bool operator()(uint32_t& i) { return i > 0; } +}; + +template +uint32_t count_discrepancies(value_idx* actual_idx, + value_idx* expected_idx, + value_t* actual, + value_t* expected, + uint32_t m, + uint32_t n, + uint32_t* out, + cudaStream_t stream) +{ + uint32_t tpb = 256; + count_discrepancies_kernel<<>>( + actual_idx, expected_idx, actual, expected, m, n, out); + + auto exec_policy = rmm::exec_policy(stream); + + uint32_t result = thrust::count_if(exec_policy, out, out + m, is_nonzero()); + return result; +} + +template +void compute_bfknn(const raft::resources& handle, + const value_t* X1, + const value_t* X2, + uint32_t n_rows, + uint32_t n_query_rows, + uint32_t d, + uint32_t k, + const cuvs::distance::DistanceType metric, + value_t* dists, + int64_t* inds) +{ + raft::device_matrix_view input_vec = + raft::make_device_matrix_view(X1, n_rows, d); + + auto bfindex = cuvs::neighbors::brute_force::build(handle, input_vec, metric); + cuvs::neighbors::brute_force::search(handle, + bfindex, + raft::make_device_matrix_view(X2, n_query_rows, d), + raft::make_device_matrix_view(inds, n_query_rows, k), + raft::make_device_matrix_view(dists, n_query_rows, k), + std::nullopt); +} + +struct ToRadians { + __device__ __host__ float operator()(float a) { return a * (CUDART_PI_F / 180.0); } +}; + +template +struct BallCoverInputs { + value_int k; + value_int n_rows; + value_int n_cols; + float weight; + value_int n_query; + cuvs::distance::DistanceType metric; +}; + +template +class BallCoverKNNQueryTest : public ::testing::TestWithParam> { + protected: + void basicTest() + { + params = ::testing::TestWithParam>::GetParam(); + raft::resources handle; + + uint32_t k = params.k; + uint32_t n_centers = 25; + float weight = params.weight; + auto metric = params.metric; + + rmm::device_uvector X(params.n_rows * params.n_cols, + raft::resource::get_cuda_stream(handle)); + rmm::device_uvector Y(params.n_rows, raft::resource::get_cuda_stream(handle)); + + // Make sure the train and query sets are completely disjoint + rmm::device_uvector X2(params.n_query * params.n_cols, + raft::resource::get_cuda_stream(handle)); + rmm::device_uvector Y2(params.n_query, raft::resource::get_cuda_stream(handle)); + + raft::random::make_blobs(X.data(), + Y.data(), + params.n_rows, + params.n_cols, + n_centers, + raft::resource::get_cuda_stream(handle)); + + raft::random::make_blobs(X2.data(), + Y2.data(), + params.n_query, + params.n_cols, + n_centers, + raft::resource::get_cuda_stream(handle)); + + rmm::device_uvector d_ref_I(params.n_query * k, + raft::resource::get_cuda_stream(handle)); + rmm::device_uvector d_ref_D(params.n_query * k, + raft::resource::get_cuda_stream(handle)); + + if (metric == cuvs::distance::DistanceType::Haversine) { + thrust::transform(raft::resource::get_thrust_policy(handle), + X.data(), + X.data() + X.size(), + X.data(), + ToRadians()); + thrust::transform(raft::resource::get_thrust_policy(handle), + X2.data(), + X2.data() + X2.size(), + X2.data(), + ToRadians()); + } + + compute_bfknn(handle, + X.data(), + X2.data(), + params.n_rows, + params.n_query, + params.n_cols, + k, + metric, + d_ref_D.data(), + d_ref_I.data()); + + raft::resource::sync_stream(handle); + + // Allocate predicted arrays + rmm::device_uvector d_pred_I(params.n_query * k, + raft::resource::get_cuda_stream(handle)); + rmm::device_uvector d_pred_D(params.n_query * k, + raft::resource::get_cuda_stream(handle)); + + auto X_view = + raft::make_device_matrix_view(X.data(), params.n_rows, params.n_cols); + auto X2_view = raft::make_device_matrix_view( + (const value_t*)X2.data(), params.n_query, params.n_cols); + + auto d_pred_I_view = + raft::make_device_matrix_view(d_pred_I.data(), params.n_query, k); + auto d_pred_D_view = + raft::make_device_matrix_view(d_pred_D.data(), params.n_query, k); + + cuvs::neighbors::ball_cover::index index( + handle, X_view, metric); + cuvs::neighbors::ball_cover::build(handle, index); + cuvs::neighbors::ball_cover::knn_query( + handle, index, X2_view, d_pred_I_view, d_pred_D_view, k, true); + + raft::resource::sync_stream(handle); + // What we really want are for the distances to match exactly. The + // indices may or may not match exactly, depending upon the ordering which + // can be nondeterministic. + + rmm::device_uvector discrepancies(params.n_query, + raft::resource::get_cuda_stream(handle)); + thrust::fill(raft::resource::get_thrust_policy(handle), + discrepancies.data(), + discrepancies.data() + discrepancies.size(), + 0); + // + int res = count_discrepancies(d_ref_I.data(), + d_pred_I.data(), + d_ref_D.data(), + d_pred_D.data(), + params.n_query, + k, + discrepancies.data(), + raft::resource::get_cuda_stream(handle)); + + ASSERT_TRUE(res == 0); + } + + void SetUp() override {} + + void TearDown() override {} + + protected: + uint32_t d = 2; + BallCoverInputs params; +}; + +template +class BallCoverAllKNNTest : public ::testing::TestWithParam> { + protected: + void basicTest() + { + params = ::testing::TestWithParam>::GetParam(); + raft::resources handle; + + uint32_t k = params.k; + uint32_t n_centers = 25; + float weight = params.weight; + auto metric = params.metric; + + rmm::device_uvector X(params.n_rows * params.n_cols, + raft::resource::get_cuda_stream(handle)); + rmm::device_uvector Y(params.n_rows, raft::resource::get_cuda_stream(handle)); + + raft::random::make_blobs(X.data(), + Y.data(), + params.n_rows, + params.n_cols, + n_centers, + raft::resource::get_cuda_stream(handle)); + + rmm::device_uvector d_ref_I(params.n_rows * k, + raft::resource::get_cuda_stream(handle)); + rmm::device_uvector d_ref_D(params.n_rows * k, + raft::resource::get_cuda_stream(handle)); + + auto X_view = raft::make_device_matrix_view( + (const value_t*)X.data(), params.n_rows, params.n_cols); + + if (metric == cuvs::distance::DistanceType::Haversine) { + thrust::transform(raft::resource::get_thrust_policy(handle), + X.data(), + X.data() + X.size(), + X.data(), + ToRadians()); + } + + compute_bfknn(handle, + X.data(), + X.data(), + params.n_rows, + params.n_rows, + params.n_cols, + k, + metric, + d_ref_D.data(), + d_ref_I.data()); + + raft::resource::sync_stream(handle); + + // Allocate predicted arrays + rmm::device_uvector d_pred_I(params.n_rows * k, + raft::resource::get_cuda_stream(handle)); + rmm::device_uvector d_pred_D(params.n_rows * k, + raft::resource::get_cuda_stream(handle)); + + auto d_pred_I_view = + raft::make_device_matrix_view(d_pred_I.data(), params.n_rows, k); + auto d_pred_D_view = + raft::make_device_matrix_view(d_pred_D.data(), params.n_rows, k); + + cuvs::neighbors::ball_cover::index index(handle, X_view, metric); + + cuvs::neighbors::ball_cover::all_knn_query( + handle, index, d_pred_I_view, d_pred_D_view, k, true); + + raft::resource::sync_stream(handle); + // What we really want are for the distances to match exactly. The + // indices may or may not match exactly, depending upon the ordering which + // can be nondeterministic. + + rmm::device_uvector discrepancies(params.n_rows, + raft::resource::get_cuda_stream(handle)); + thrust::fill(raft::resource::get_thrust_policy(handle), + discrepancies.data(), + discrepancies.data() + discrepancies.size(), + 0); + // + uint32_t res = count_discrepancies(d_ref_I.data(), + d_pred_I.data(), + d_ref_D.data(), + d_pred_D.data(), + params.n_rows, + k, + discrepancies.data(), + raft::resource::get_cuda_stream(handle)); + + // TODO: There seem to be discrepancies here only when + // the entire test suite is executed. + // Ref: https://github.com/rapidsai/raft/issues/ + // 1-5 mismatches in 8000 samples is 0.0125% - 0.0625% + ASSERT_TRUE(res <= 5); + } + + void SetUp() override {} + + void TearDown() override {} + + protected: + BallCoverInputs params; +}; + +typedef BallCoverAllKNNTest BallCoverAllKNNTestF; +typedef BallCoverKNNQueryTest BallCoverKNNQueryTestF; + +const std::vector> ballcover_inputs = { + {11, 5000, 2, 1.0, 10000, cuvs::distance::DistanceType::Haversine}, + {25, 10000, 2, 1.0, 5000, cuvs::distance::DistanceType::Haversine}, + {2, 10000, 2, 1.0, 5000, cuvs::distance::DistanceType::L2SqrtUnexpanded}, + {2, 5000, 2, 1.0, 10000, cuvs::distance::DistanceType::Haversine}, + {11, 10000, 2, 1.0, 5000, cuvs::distance::DistanceType::L2SqrtUnexpanded}, + {25, 5000, 2, 1.0, 10000, cuvs::distance::DistanceType::L2SqrtUnexpanded}, + {5, 8000, 3, 1.0, 10000, cuvs::distance::DistanceType::L2SqrtUnexpanded}, + {11, 6000, 3, 1.0, 10000, cuvs::distance::DistanceType::L2SqrtUnexpanded}, + {25, 10000, 3, 1.0, 5000, cuvs::distance::DistanceType::L2SqrtUnexpanded}}; + +INSTANTIATE_TEST_CASE_P(BallCoverAllKNNTest, + BallCoverAllKNNTestF, + ::testing::ValuesIn(ballcover_inputs)); +INSTANTIATE_TEST_CASE_P(BallCoverKNNQueryTest, + BallCoverKNNQueryTestF, + ::testing::ValuesIn(ballcover_inputs)); + +TEST_P(BallCoverAllKNNTestF, Fit) { basicTest(); } +TEST_P(BallCoverKNNQueryTestF, Fit) { basicTest(); } + +} // namespace cuvs::neighbors::ball_cover diff --git a/cpp/test/neighbors/spatial_data.h b/cpp/test/neighbors/spatial_data.h new file mode 100644 index 000000000..3936d6320 --- /dev/null +++ b/cpp/test/neighbors/spatial_data.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +namespace cuvs { +namespace spatial { + +// Latitude and longitude coordinates of 51 US states / territories +std::vector spatial_data = { + 63.588753, -154.493062, 32.318231, -86.902298, 35.20105, -91.831833, 34.048928, -111.093731, + 36.778261, -119.417932, 39.550051, -105.782067, 41.603221, -73.087749, 38.905985, -77.033418, + 38.910832, -75.52767, 27.664827, -81.515754, 32.157435, -82.907123, 19.898682, -155.665857, + 41.878003, -93.097702, 44.068202, -114.742041, 40.633125, -89.398528, 40.551217, -85.602364, + 39.011902, -98.484246, 37.839333, -84.270018, 31.244823, -92.145024, 42.407211, -71.382437, + 39.045755, -76.641271, 45.253783, -69.445469, 44.314844, -85.602364, 46.729553, -94.6859, + 37.964253, -91.831833, 32.354668, -89.398528, 46.879682, -110.362566, 35.759573, -79.0193, + 47.551493, -101.002012, 41.492537, -99.901813, 43.193852, -71.572395, 40.058324, -74.405661, + 34.97273, -105.032363, 38.80261, -116.419389, 43.299428, -74.217933, 40.417287, -82.907123, + 35.007752, -97.092877, 43.804133, -120.554201, 41.203322, -77.194525, 18.220833, -66.590149, + 41.580095, -71.477429, 33.836081, -81.163725, 43.969515, -99.901813, 35.517491, -86.580447, + 31.968599, -99.901813, 39.32098, -111.093731, 37.431573, -78.656894, 44.558803, -72.577841, + 47.751074, -120.740139, 43.78444, -88.787868, 38.597626, -80.454903, 43.075968, -107.290284}; +}; // namespace spatial +}; // namespace cuvs \ No newline at end of file diff --git a/notebooks/VectorSearch_QuestionRetrieval.ipynb b/notebooks/VectorSearch_QuestionRetrieval.ipynb index 4023a1821..21d59975b 100644 --- a/notebooks/VectorSearch_QuestionRetrieval.ipynb +++ b/notebooks/VectorSearch_QuestionRetrieval.ipynb @@ -344,7 +344,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.13" + "version": "3.11.9" } }, "nbformat": 4, diff --git a/notebooks/ivf_flat_example.ipynb b/notebooks/ivf_flat_example.ipynb index 2d9c5fb58..e39c0ebee 100644 --- a/notebooks/ivf_flat_example.ipynb +++ b/notebooks/ivf_flat_example.ipynb @@ -21,7 +21,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "fe73ada7-7b7f-4005-9440-85428194311b", "metadata": {}, "outputs": [], @@ -46,7 +46,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "5350e4d9-0993-406a-80af-29538b5677c2", "metadata": {}, "outputs": [], @@ -71,10 +71,41 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "a5daa4b4-96de-4e74-bfd6-505b13595f62", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Wed Jul 10 17:19:06 2024 \n", + "+-----------------------------------------------------------------------------+\n", + "| NVIDIA-SMI 520.61.05 Driver Version: 520.61.05 CUDA Version: 11.8 |\n", + "|-------------------------------+----------------------+----------------------+\n", + "| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |\n", + "| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |\n", + "| | | MIG M. |\n", + "|===============================+======================+======================|\n", + "| 0 NVIDIA RTX A6000 Off | 00000000:B3:00.0 On | Off |\n", + "| 35% 60C P2 88W / 300W | 3226MiB / 49140MiB | 11% Default |\n", + "| | | N/A |\n", + "+-------------------------------+----------------------+----------------------+\n", + " \n", + "+-----------------------------------------------------------------------------+\n", + "| Processes: |\n", + "| GPU GI CI PID Type Process name GPU Memory |\n", + "| ID ID Usage |\n", + "|=============================================================================|\n", + "| 0 N/A N/A 1346 G /usr/lib/xorg/Xorg 687MiB |\n", + "| 0 N/A N/A 1901 G /usr/bin/gnome-shell 60MiB |\n", + "| 0 N/A N/A 263673 C ...vs_062724_2408/bin/python 2078MiB |\n", + "| 0 N/A N/A 3393713 G ...372896767459192031,262144 253MiB |\n", + "| 0 N/A N/A 3456207 G ...--variations-seed-version 49MiB |\n", + "+-----------------------------------------------------------------------------+\n" + ] + } + ], "source": [ "# Report the GPU in use\n", "!nvidia-smi" @@ -94,10 +125,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "5f529ad6-b0bd-495c-bf7c-43f10fb6aa14", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The index and data will be saved in /tmp/cuvs_example\n" + ] + } + ], "source": [ "WORK_FOLDER = os.path.join(tempfile.gettempdir(), \"cuvs_example\")\n", "f = load_dataset(\"http://ann-benchmarks.com/sift-128-euclidean.hdf5\", work_folder=WORK_FOLDER)" @@ -105,10 +144,19 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "3d68a7db-bcf4-449c-96c3-1e8ab146c84d", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loaded dataset of size (1000000, 128), 0.5 GiB; metric: 'euclidean'.\n", + "Number of test queries: 10000\n" + ] + } + ], "source": [ "metric = f.attrs['distance']\n", "\n", @@ -134,10 +182,19 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "737f8841-93f9-4c8e-b2e1-787d4474ef94", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 123 ms, sys: 27.7 ms, total: 150 ms\n", + "Wall time: 149 ms\n" + ] + } + ], "source": [ "%%time\n", "build_params = ivf_flat.IndexParams(\n", @@ -161,10 +218,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "1aec7024-6e5d-4d2c-82e6-7b5734aec958", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Index(type=IvfFlat)\n" + ] + } + ], "source": [ "print(index)" ] @@ -187,7 +252,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "id": "46e0421b-9335-47a2-8451-a91f56c2f086", "metadata": {}, "outputs": [], @@ -205,10 +270,19 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "595454e1-7240-4b43-9a73-963d5670b00c", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 710 ms, sys: 293 ms, total: 1 s\n", + "Wall time: 996 ms\n" + ] + } + ], "source": [ "%%time\n", "n_queries=10000\n", @@ -233,10 +307,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "id": "8cd9cd20-ca00-4a35-a0a0-86636521b31a", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "0.97398" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "calc_recall(neighbors, gt_neighbors)" ] @@ -252,7 +337,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "id": "bf94e45c-e7fb-4aa3-a611-ddaee7ac41ae", "metadata": {}, "outputs": [], @@ -263,7 +348,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "id": "1622d9be-be41-4d25-be99-d348c5e54957", "metadata": {}, "outputs": [], @@ -284,10 +369,57 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "id": "ace0c31f-af75-4352-a438-123a9a03612c", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Benchmarking search with n_probes = 10\n", + "recall 0.86668\n", + "Average search time: 0.075 +/- 0.00267 s\n", + "Queries per second (QPS): 133984\n", + "\n", + "Benchmarking search with n_probes = 20\n", + "recall 0.94766\n", + "Average search time: 0.144 +/- 0.00121 s\n", + "Queries per second (QPS): 69339\n", + "\n", + "Benchmarking search with n_probes = 30\n", + "recall 0.97398\n", + "Average search time: 0.215 +/- 0.000938 s\n", + "Queries per second (QPS): 46452\n", + "\n", + "Benchmarking search with n_probes = 50\n", + "recall 0.99117\n", + "Average search time: 0.356 +/- 0.00109 s\n", + "Queries per second (QPS): 28067\n", + "\n", + "Benchmarking search with n_probes = 100\n", + "recall 0.99831\n", + "Average search time: 0.719 +/- 0.0074 s\n", + "Queries per second (QPS): 13901\n", + "\n", + "Benchmarking search with n_probes = 200\n", + "recall 0.99932\n", + "Average search time: 1.438 +/- 0.00288 s\n", + "Queries per second (QPS): 6953\n", + "\n", + "Benchmarking search with n_probes = 500\n", + "recall 0.99936\n", + "Average search time: 3.302 +/- 0.0646 s\n", + "Queries per second (QPS): 3028\n", + "\n", + "Benchmarking search with n_probes = 1024\n", + "recall 0.99933\n", + "Average search time: 2.272 +/- 0.0397 s\n", + "Queries per second (QPS): 4402\n" + ] + } + ], "source": [ "n_probes = np.asarray([10, 20, 30, 50, 100, 200, 500, 1024]);\n", "qps = np.zeros(n_probes.shape);\n", @@ -327,10 +459,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "id": "e1ac370f-91c8-4054-95c7-a749df5f16d2", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/png": "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jBAfH59G1ejGxn769Gn517/+JT/++KNkZGTIqFGjxM/PTy5cuGBqs3//flGpVJKcnCw5OTmSnJxc6yPHmvr5Yo24lyxZIm5ubrJlyxazz5WioiKHf8/nz58vO3fulHPnzklWVpa8+OKLolarJSMjw2LvubVir2Dtfb2+mlk19orH67322muSnZ0ta9asqfZ4PVvs6yJ8vF6zrFy5UsLCwsTNzU169OhhejyTvQGo8afycyWNRqMkJSVJUFCQaLVa+c1vfiPHjx83W8+vv/4qU6ZMET8/P/Hw8JARI0bIpUuXbJzNA1W/yDpDDl9++aVER0eLVquViIgI+eijj8zmO3oOhYWFMn36dGnbtq24u7tL+/btZe7cuaLX6x02h++++67G/X/ChAkWjbegoEDGjh0rPj4+4uPjI2PHjpWbN29aJSdnxPpoW6yPrI8NxRrpWOqqlRMmTJBBgwaZtV++fLlERkaKh4eHBAcHy9ixY82e1y5S3kkCILt27apxm6NGjZLg4GDRaDQSEhIizz33nJw8edKqsWdnZ0tMTIx4eHiIr6+vPPvsszU+anHz5s3y2GOPiUajkYiICNm6dWujtmuPuMPCwmr8P5WUlGRq46jv+YwZM6Rt27bi5uYmDz/8sMTHx0t6enqjtmuv2EVss6/XVzNr+n+6e/du6d69u7i5uUm7du1k9erV1dZri31dIVLlWh8iIiIiIiIiclq8R5+IiIiIiIjIhbCjT0RERERERORC2NEnIiIiIiIiciHs6BMRERERERG5EHb0iYiIiIiIiFwIO/pERERERERELoQdfSIiIiIiIiIXwo4+ERERERERkQthR5/ICi5evAiFQoGjR4/aOxQiIofC+khE1HDz589HTEyM6fULL7yAkSNH2i0ech7s6BMRERERERG5EHb0iRrJYDDYOwQiIofE+khELUlJSYm9QyCqFTv65PQGDx6MadOmYfbs2fDz80NQUBDmz5/foGUVCgVWr16NYcOGwcPDA+Hh4di8ebNpfsUlpp9//jkGDx4Md3d3bNiwAUajEQsXLkRoaCi0Wi1iYmKwc+fOaus/deoU+vfvD3d3d0RFRWH37t1m87Ozs/HUU0/B29sbgYGBGD9+PK5fv26av2XLFnTp0gUeHh7w9/fHE088gbt37zbpfSKilof1kYjIcgYPHowpU6Zg5syZCAgIwJNPPllvrTIajViyZAk6duwIrVaLtm3b4q233jLNT0xMxKOPPgpPT0+0b98e8+bN40FTsgh29MklrF+/Hl5eXsjIyMDSpUuxcOFCpKWlNWjZefPm4Xe/+x2OHTuGcePGYcyYMcjJyTFrk5iYiGnTpiEnJwcJCQn44IMP8O677+Kdd97BDz/8gISEBDzzzDM4c+aM2XKzZs3C66+/jqysLPTv3x/PPPMMCgoKAAC5ubkYNGgQYmJicPjwYezcuRPXrl3D888/b5o/ZswYTJw4ETk5Odi9ezeee+45iIgF3jEiailYH4mILGf9+vVQq9XYv38/kpOT66xVAPDGG29gyZIlmDdvHrKzs/Hpp58iMDDQNN/Hxwfr1q1DdnY2PvjgA6SkpOD999+3R2rkaoTIyQ0aNEgGDBhgNq1Xr16SmJhY77IAZNKkSWbT+vTpI6+88oqIiFy4cEEAyLJly8zahISEyFtvvVVtm5MnTzZbLjk52TTfYDBIaGioLFmyRERE5s2bJ/Hx8WbruHz5sgCQ06dPS2ZmpgCQixcv1psHEVFNWB+JiCxn0KBBEhMTY3pdX60qLCwUrVYrKSkpDd7G0qVLpWfPnqbXSUlJ0q1bN9PrCRMmyLPPPtvkHKjlUNvp+AKRRXXt2tXsdXBwMPLz8xu0bL9+/aq9rjoadGxsrOn3wsJCXL16FXFxcWZt4uLicOzYsVrXrVarERsbazoblpmZie+++w7e3t7VYjp37hzi4+MxZMgQdOnSBQkJCYiPj8fvf/97tGrVqkF5EREBrI9ERJZUuebVV6tu3boFvV6PIUOG1Lq+LVu2YNmyZTh79izu3LmD0tJS+Pr6WiV2alnY0SeXoNFozF4rFAoYjcYmr0+hUJi99vLyqreNiFSbVte6jUYjnn76aSxZsqRam+DgYKhUKqSlpSE9PR27du3CihUrMHfuXGRkZCA8PLwx6RBRC8b6SERkOZVrXn216vz583Wu6+DBgxg9ejQWLFiAhIQE6HQ6pKam4t1337V43NTy8B59avEOHjxY7XVERESt7X19fRESEoJ9+/aZTU9PT0fnzp1rXXdpaSkyMzNN6+7RowdOnjyJdu3aoWPHjmY/FR8iCoUCcXFxWLBgAbKysuDm5oZt27Y1K18iooZifSQiql19tapTp07w8PDAt99+W+Py+/fvR1hYGObOnYvY2Fh06tQJP/30k42zIFfFM/rU4m3evBmxsbEYMGAANm7ciEOHDmHNmjV1LjNr1iwkJSWhQ4cOiImJwdq1a3H06FFs3LjRrN3KlSvRqVMndO7cGe+//z5u3ryJiRMnAgBeffVVpKSkYMyYMZg1axYCAgJw9uxZpKamIiUlBYcPH8a3336L+Ph4tG7dGhkZGfjll1+qfVkmIrIW1kciotrVV6vc3d2RmJiI2bNnw83NDXFxcfjll19w8uRJvPTSS+jYsSMuXbqE1NRU9OrVC//97395wJIshh19avEWLFiA1NRUTJ48GUFBQdi4cSMiIyPrXGbatGkoLCzE66+/jvz8fERGRmL79u3o1KmTWbvk5GQsWbIEWVlZ6NChA/7zn/8gICAAABASEoL9+/cjMTERCQkJ0Ov1CAsLw9ChQ6FUKuHr64vvv/8ey5YtQ2FhIcLCwvDuu+9i2LBhVnsviIgqY30kIqpdfbUKKH96iVqtxptvvomrV68iODgYkyZNAgA8++yzeO211zBlyhTo9XoMHz4c8+bNa/BjUInqohDhs2io5VIoFNi2bRtGjhxp71CIiBwK6yMREZHz4j36RERERERERC6EHX1yWRs3boS3t3eNP1FRUfYOj4jIblgfiYiIXBsv3SeXVVRUhGvXrtU4T6PRICwszMYRERE5BtZHIiIi18aOPhEREREREZEL4aX7RERERERERC6EHX0iIiIiIiIiF8KOPhEREREREZELYUefiIiIiIiIyIWwo09ERERERETkQtjRJyIiIiIiInIh7OgTERERERERuRB29ImIiIiIiIhcyP8HCwB1PLI8upYAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "fig = plt.figure(figsize=(12,3))\n", "ax = fig.add_subplot(131)\n", @@ -368,10 +511,19 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "id": "addbfff3-7773-4290-9608-5489edf4886d", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 464 ms, sys: 4.68 ms, total: 469 ms\n", + "Wall time: 463 ms\n" + ] + } + ], "source": [ "%%time\n", "build_params = ivf_flat.IndexParams(\n", @@ -395,10 +547,19 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "id": "8a0149ad-de38-4195-97a5-ce5d5d877036", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 512 ms, sys: 240 ms, total: 752 ms\n", + "Wall time: 745 ms\n" + ] + } + ], "source": [ "%%time\n", "n_queries=10000\n", @@ -414,10 +575,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "id": "eedc3ec4-06af-42c5-8cdf-490a5c2bc49a", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "0.98719" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "calc_recall(neighbors, gt_neighbors)" ] @@ -433,10 +605,19 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "id": "5a54d190-64d4-4cd4-a497-365cbffda871", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 67.7 ms, sys: 3.97 ms, total: 71.7 ms\n", + "Wall time: 71 ms\n" + ] + } + ], "source": [ "%%time\n", "build_params = ivf_flat.IndexParams( \n", @@ -458,10 +639,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 19, "id": "4cc992e8-a5e5-4508-b790-0e934160b660", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "0.98814" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "search_params = ivf_flat.SearchParams(n_probes=10)\n", "\n", @@ -487,10 +679,29 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 20, "id": "7ebcf970-94ed-4825-9885-277bd984b90c", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Index before adding vectors Index(type=IvfFlat)\n" + ] + }, + { + "ename": "AttributeError", + "evalue": "module 'cuvs.neighbors.ivf_flat' has no attribute 'extend'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[20], line 17\u001b[0m\n\u001b[1;32m 13\u001b[0m index \u001b[38;5;241m=\u001b[39m ivf_flat\u001b[38;5;241m.\u001b[39mbuild(build_params, train_set)\n\u001b[1;32m 15\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mIndex before adding vectors\u001b[39m\u001b[38;5;124m\"\u001b[39m, index)\n\u001b[0;32m---> 17\u001b[0m \u001b[43mivf_flat\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mextend\u001b[49m(index, dataset, cp\u001b[38;5;241m.\u001b[39marange(dataset\u001b[38;5;241m.\u001b[39mshape[\u001b[38;5;241m0\u001b[39m], dtype\u001b[38;5;241m=\u001b[39mcp\u001b[38;5;241m.\u001b[39mint64))\n\u001b[1;32m 19\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mIndex after adding vectors\u001b[39m\u001b[38;5;124m\"\u001b[39m, index)\n", + "\u001b[0;31mAttributeError\u001b[0m: module 'cuvs.neighbors.ivf_flat' has no attribute 'extend'" + ] + } + ], "source": [ "# subsample the dataset\n", "n_train = 10000\n", @@ -520,6 +731,30 @@ "metadata": {}, "outputs": [], "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "23010fbc-8f5a-4403-a112-33f190a85498", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "774848e8-fa45-4223-bd2a-e8585650531e", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6309b8a7-f4eb-4976-a824-cd4499a0000d", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { @@ -538,7 +773,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.13" + "version": "3.11.9" } }, "nbformat": 4, diff --git a/notebooks/tutorial_ivf_pq.ipynb b/notebooks/tutorial_ivf_pq.ipynb index cc0fe4142..fb6296228 100644 --- a/notebooks/tutorial_ivf_pq.ipynb +++ b/notebooks/tutorial_ivf_pq.ipynb @@ -14,16 +14,37 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: adjustText in /home/cjnolet/software/miniconda3/envs/cuvs_062724_2408/lib/python3.11/site-packages (1.2.0)\n", + "Requirement already satisfied: h5py in /home/cjnolet/software/miniconda3/envs/cuvs_062724_2408/lib/python3.11/site-packages (3.11.0)\n", + "Requirement already satisfied: matplotlib in /home/cjnolet/software/miniconda3/envs/cuvs_062724_2408/lib/python3.11/site-packages (3.8.4)\n", + "Requirement already satisfied: numpy in /home/cjnolet/software/miniconda3/envs/cuvs_062724_2408/lib/python3.11/site-packages (from adjustText) (1.26.4)\n", + "Requirement already satisfied: scipy in /home/cjnolet/software/miniconda3/envs/cuvs_062724_2408/lib/python3.11/site-packages (from adjustText) (1.14.0)\n", + "Requirement already satisfied: contourpy>=1.0.1 in /home/cjnolet/software/miniconda3/envs/cuvs_062724_2408/lib/python3.11/site-packages (from matplotlib) (1.2.1)\n", + "Requirement already satisfied: cycler>=0.10 in /home/cjnolet/software/miniconda3/envs/cuvs_062724_2408/lib/python3.11/site-packages (from matplotlib) (0.12.1)\n", + "Requirement already satisfied: fonttools>=4.22.0 in /home/cjnolet/software/miniconda3/envs/cuvs_062724_2408/lib/python3.11/site-packages (from matplotlib) (4.53.1)\n", + "Requirement already satisfied: kiwisolver>=1.3.1 in /home/cjnolet/software/miniconda3/envs/cuvs_062724_2408/lib/python3.11/site-packages (from matplotlib) (1.4.5)\n", + "Requirement already satisfied: packaging>=20.0 in /home/cjnolet/software/miniconda3/envs/cuvs_062724_2408/lib/python3.11/site-packages (from matplotlib) (24.1)\n", + "Requirement already satisfied: pillow>=8 in /home/cjnolet/software/miniconda3/envs/cuvs_062724_2408/lib/python3.11/site-packages (from matplotlib) (10.4.0)\n", + "Requirement already satisfied: pyparsing>=2.3.1 in /home/cjnolet/software/miniconda3/envs/cuvs_062724_2408/lib/python3.11/site-packages (from matplotlib) (3.1.2)\n", + "Requirement already satisfied: python-dateutil>=2.7 in /home/cjnolet/software/miniconda3/envs/cuvs_062724_2408/lib/python3.11/site-packages (from matplotlib) (2.9.0)\n", + "Requirement already satisfied: six>=1.5 in /home/cjnolet/software/miniconda3/envs/cuvs_062724_2408/lib/python3.11/site-packages (from python-dateutil>=2.7->matplotlib) (1.16.0)\n" + ] + } + ], "source": [ "!pip install adjustText h5py matplotlib" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -47,7 +68,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -62,9 +83,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The index and data will be saved in /tmp/cuvs_ivf_pq_tutorial\n" + ] + } + ], "source": [ "# We'll need to load store some data in this tutorial\n", "WORK_FOLDER = os.path.join(tempfile.gettempdir(), 'cuvs_ivf_pq_tutorial')\n", @@ -76,9 +105,40 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Wed Jul 10 18:28:55 2024 \n", + "+-----------------------------------------------------------------------------+\n", + "| NVIDIA-SMI 520.61.05 Driver Version: 520.61.05 CUDA Version: 11.8 |\n", + "|-------------------------------+----------------------+----------------------+\n", + "| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |\n", + "| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |\n", + "| | | MIG M. |\n", + "|===============================+======================+======================|\n", + "| 0 NVIDIA RTX A6000 Off | 00000000:B3:00.0 On | Off |\n", + "| 30% 44C P8 40W / 300W | 12334MiB / 49140MiB | 21% Default |\n", + "| | | N/A |\n", + "+-------------------------------+----------------------+----------------------+\n", + " \n", + "+-----------------------------------------------------------------------------+\n", + "| Processes: |\n", + "| GPU GI CI PID Type Process name GPU Memory |\n", + "| ID ID Usage |\n", + "|=============================================================================|\n", + "| 0 N/A N/A 1346 G /usr/lib/xorg/Xorg 574MiB |\n", + "| 0 N/A N/A 1901 G /usr/bin/gnome-shell 70MiB |\n", + "| 0 N/A N/A 263673 C ...vs_062724_2408/bin/python 11250MiB |\n", + "| 0 N/A N/A 3393713 G ...372896767459192031,262144 219MiB |\n", + "| 0 N/A N/A 3456207 G ...--variations-seed-version 54MiB |\n", + "+-----------------------------------------------------------------------------+\n" + ] + } + ], "source": [ "# Report the GPU in use to put the measurements into perspective\n", "!nvidia-smi" @@ -95,7 +155,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -119,11 +179,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The index and data will be saved in /tmp/raft_example\n" + ] + } + ], "source": [ "DATASET_URL = \"http://ann-benchmarks.com/sift-128-euclidean.hdf5\"\n", + "DATASET_NAME = \"SIFT-128\"\n", "f = load_dataset(DATASET_URL)" ] }, @@ -136,9 +205,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loaded dataset of size (1000000, 128); metric: 'euclidean'.\n", + "Number of test queries: 10000\n" + ] + } + ], "source": [ "metric = f.attrs['distance']\n", "\n", @@ -165,7 +243,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -176,9 +254,30 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "{'add_data_on_build': True,\n", + " 'codebook_kind': 0,\n", + " 'conservative_memory_allocation': False,\n", + " 'force_random_rotation': False,\n", + " 'kmeans_n_iters': 20,\n", + " 'kmeans_trainset_fraction': 0.5,\n", + " 'metric': 'euclidean',\n", + " 'metric_arg': 2.0,\n", + " 'n_lists': 1024,\n", + " 'pq_bits': 8,\n", + " 'pq_dim': 64}" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# First, we need to initialize the build/indexing parameters.\n", "# One of the more important parameters is the product quantisation (PQ) dim.\n", @@ -197,16 +296,36 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "using ivf_pq::index_params nrows 1000000, dim 128, n_lits 1024, pq_dim 64\n", + "CPU times: user 4.06 s, sys: 299 ms, total: 4.36 s\n", + "Wall time: 4.28 s\n" + ] + }, + { + "data": { + "text/plain": [ + "Index(type=IvfPq)" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "%%time\n", "## Build the index\n", "# This function takes a row-major either numpy or cupy (GPU) array.\n", "# Generally, it's a bit faster with GPU inputs, but the CPU version may come in handy\n", "# if the whole dataset cannot fit into GPU memory.\n", - "index = ivf_pq.build(index_params, dataset, handle=resources)\n", + "index = ivf_pq.build(index_params, dataset, resources=resources)\n", "# This function is asynchronous so we need to explicitly synchronize the GPU before we can measure the execution time\n", "resources.sync()\n", "index" @@ -222,9 +341,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 75.7 ms, sys: 84.3 ms, total: 160 ms\n", + "Wall time: 158 ms\n" + ] + }, + { + "data": { + "text/plain": [ + "Index(type=IvfPq)" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "%%time\n", "index_filepath = os.path.join(WORK_FOLDER, \"ivf_pq.bin\")\n", @@ -246,9 +384,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "{'internal_distance_dtype': 0, 'lut_dtype': 0, 'n_probes': 20}" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "k = 10\n", "search_params = ivf_pq.SearchParams()\n", @@ -257,12 +406,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 26.3 ms, sys: 16.4 ms, total: 42.8 ms\n", + "Wall time: 42.4 ms\n" + ] + } + ], "source": [ "%%time\n", - "distances, neighbors = ivf_pq.search(search_params, index, queries, k, handle=resources)\n", + "distances, neighbors = ivf_pq.search(search_params, index, queries, k, resources=resources)\n", "# Sync the GPU to make sure we've got the timing right\n", "resources.sync()" ] @@ -277,9 +435,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Got recall = 0.85222 with the default parameters (k = 10).\n" + ] + } + ], "source": [ "recall_first_try = calc_recall(neighbors, gt_neighbors)\n", "print(f\"Got recall = {recall_first_try} with the default parameters (k = {k}).\")" @@ -297,22 +463,39 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 92 ms, sys: 16 ms, total: 108 ms\n", + "Wall time: 107 ms\n" + ] + } + ], "source": [ "%%time\n", "\n", - "candidates = ivf_pq.search(search_params, index, queries, k * 2, handle=resources)[1]\n", - "distances, neighbors = refine(dataset, queries, candidates, k, handle=resources)\n", + "candidates = ivf_pq.search(search_params, index, queries, k * 2, resources=resources)[1]\n", + "distances, neighbors = refine(dataset, queries, candidates, k, resources=resources)\n", "resources.sync()" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Got recall = 0.94949 with 2x refinement (k = 10).\n" + ] + } + ], "source": [ "recall_refine2x = calc_recall(neighbors, gt_neighbors)\n", "print(f\"Got recall = {recall_refine2x} with 2x refinement (k = {k}).\")" @@ -341,15 +524,42 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "32.8 ms ± 277 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n", + "34.5 ms ± 416 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n", + "36.6 ms ± 464 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n", + "38.1 ms ± 408 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n", + "39 ms ± 96.7 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n", + "36.9 ms ± 73.1 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n", + "42.2 ms ± 264 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n", + "53.1 ms ± 710 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n", + "37.6 ms ± 582 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n", + "37.6 ms ± 450 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA04AAAGwCAYAAACepzXBAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAACB4UlEQVR4nOzdeVxVdf7H8ddluyzCFURABFxSUQOtXFFzSUXLJWtmrCwmp8axyaXCNmuaqWbMptQWnWmqaXJSy+Y3ZrlFoKZmgguJiguaG26IC4Kisp7fH8SdCJVLAofl/Xw8eOi953vP+dwP517u536/5/u1GIZhICIiIiIiIlflZHYAIiIiIiIitZ0KJxERERERkQqocBIREREREamACicREREREZEKqHASERERERGpgAonERERERGRCqhwEhERERERqYCL2QE0NMXFxRw/fhxvb28sFovZ4YiIiIiINGiGYXD+/HmCg4Nxcrp6v5IKpxp2/PhxQkNDzQ5DRERERER+5MiRI4SEhFx1uwqnGubt7Q2U/GJ8fHxMiaGgoID4+Hiio6NxdXU1JYa6QrmqHOXLccqV45QrxylXjlOuHKdcVY7y5bjakqucnBxCQ0Ptn9OvRoVTDSsdnufj42Nq4eTp6YmPj49e0BVQripH+XKccuU45cpxypXjlCvHKVeVo3w5rrblqqLLaDQ5hIiIiIiISAVUOImIiIiIiFRAhZOIiIiIiEgFVDiJiIiIiIhUQIWTiIiIiIhIBVQ4iYiIiIiIVECFk4iIiIiISAVUOImIiIiIiFRAhZOIiIiIiEgFVDiJiIiIiIhUQIWTiIiIiIhIBVQ4iYiIiIiIVECFk4iIiIiISAVUOImIiIiIiFTAxewApGZdzC+k4x/jARf6DyrE5upqdkgiIiIiIrWeepxEREREREQqoMJJRERERESkAiqcREREREREKqDCSUREREREpAIqnERERERERCqgwqkBe3v1frIvFZgdhoiIiIhIrafCqYEpLjbs///g28P0e/1r3lu3n8sFRSZGJSIiIiJSu6lwamCcnCz2/7f29+LcxQJeWbGH22as4T9bjlD0o8JKRERERERKqHBqwP47vjuv/bITwTZ3jmdf5un/bmfom+uI35mBYaiAEhEREREppcKpAXN2sjC6ayirn+zP83d0oLGnK/syL/C7ecn88h+JbDp41uwQ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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "bench_k = np.exp2(np.arange(10)).astype(np.int32)\n", "bench_avg = np.zeros_like(bench_k, dtype=np.float32)\n", "bench_std = np.zeros_like(bench_k, dtype=np.float32)\n", "for i, k in enumerate(bench_k):\n", - " r = %timeit -o ivf_pq.search(search_params, index, queries, k, handle=resources); resources.sync()\n", + " r = %timeit -o ivf_pq.search(search_params, index, queries, k, resources=resources); resources.sync()\n", " bench_avg[i] = (queries.shape[0] * r.loops / np.array(r.all_runs)).mean()\n", " bench_std[i] = (queries.shape[0] * r.loops / np.array(r.all_runs)).std()\n", "\n", @@ -377,9 +587,27 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 19, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3.86 ms ± 96.5 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n", + "6.83 ms ± 150 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n", + "12.8 ms ± 239 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n", + "23.7 ms ± 473 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n", + "43.5 ms ± 756 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n", + "81.6 ms ± 156 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n", + "158 ms ± 500 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n", + "305 ms ± 2.29 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n", + "591 ms ± 4.66 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n", + "1.12 s ± 2.16 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n", + "2.23 s ± 12.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], "source": [ "bench_probes = np.exp2(np.arange(11)).astype(np.int32)\n", "bench_qps = np.zeros_like(bench_probes, dtype=np.float32)\n", @@ -387,9 +615,9 @@ "k = 100\n", "for i, n_probes in enumerate(bench_probes):\n", " sp = ivf_pq.SearchParams(n_probes=n_probes)\n", - " r = %timeit -o ivf_pq.search(sp, index, queries, k, handle=resources); resources.sync()\n", + " r = %timeit -o ivf_pq.search(sp, index, queries, k, resources=resources); resources.sync()\n", " bench_qps[i] = (queries.shape[0] * r.loops / np.array(r.all_runs)).mean()\n", - " bench_recall[i] = calc_recall(ivf_pq.search(sp, index, queries, k, handle=resources)[1], gt_neighbors)\n", + " bench_recall[i] = calc_recall(ivf_pq.search(sp, index, queries, k, resources=resources)[1], gt_neighbors)\n", " " ] }, @@ -407,9 +635,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 20, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABR8AAAFzCAYAAAC3uH7uAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAACe/UlEQVR4nOzdd3xUZdrG8d/MpJFOEpJQEgidJNSELigCEWxgAxs2UJCoi6wFXtd1UXfZtbC4GhBExS4WLKuoREVaKCHSQycQCCkkkE6SSTLvH5GsCCikncnk+n5ePuucmXPmOiEvz5l77vM8JpvNZkNERERERERERESkjpmNDiAiIiIiIiIiIiKOScVHERERERERERERqRcqPoqIiIiIiIiIiEi9UPFRRERERERERERE6oWKjyIiIiIiIiIiIlIvVHwUERERERERERGReqHio4iIiIiIiIiIiNQLFR9FRERERERERESkXjgZHaChVVZWcuzYMby8vDCZTEbHERGR32Gz2SgoKKBVq1aYzfq+7Fw0romINB4a1/6YxjURkcbhYsa0Jld8PHbsGCEhIUbHEBGRi3DkyBHatGljdAy7pHFNRKTx0bh2fhrXREQalwsZ05pc8dHLywuo+uF4e3vX6BhWq5Xly5cTExODs7NzXcZThkaYwV5yKIMy2FuGusiRn59PSEhI9b/dcjaNa8qgDI6dQxkcK4PGtT9WF+Pa+djD71F9cMTz0jk1Ho54XjqnC3MxY1qTKz6ebt339vau1Yc0d3d3vL29Db34UQb7yGAvOZRBGewtQ13m0G1X56dxTRmUwbFzKINjZtC4dn51Ma6djz38HtUHRzwvnVPj4YjnpXO6OBcypmmiEREREREREREREakXKj6KiIiIiIiIiIhIvVDxUUREREREREREROqFio8iIiIiIiIiIiJSL1R8FBERaYTi4uIIDw+nb9++RkcRERGpNY1rIiKOS8VHERGRRig2Npbk5GQSExONjiIiIlJrGtdERByXio8iIiIiIiIiIiJSL1R8FBERERERERERkXqh4qOIiIiIiIiIiIjUCyejA4iI1FRlpY1iawVFpeUUlpZTVFpOXlEpu06acN97HIvFQmUl2ACbzUalDaDqf202sFX/t63qeDYbNhvV207vV72Nqv/+33O/3qfqGDYbWCvK2Z1m4ujqFCwWiyE/m4qKCvYY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2rRJ8bgyfPhwkZmZqVgWEhIifH19FZfXr18vhgwZIkxNTYW5ubkYNmyY2Lx5s7h//34bJG+ZOnU9qj2efBRCvZoSEhJE//79hYmJiXjiiSeEj4+Pyi+26JO681RUVCTGjBkjZDKZsLOzE/Pnz2/zF2Gbom5dv//+u5DJZEr7aVtSt6b169eLwYMHC5lMJmxsbMSbb74prl69qufULVOnpsLCQuHm5iZkMpkwNzcXEydOFD/99JPOskmEaAfvESUiIiIiIiIiIqJOh9/5SERERERERERERDrBk49ERERERERERESkEzz5SERERERERERERDrBk49ERERERERERESkEzz5SERERERERERERDrBk49ERERERERERESkEzz5SERERERERERERDrBk49Ej4FLly5BIpEgLy+vraMQERG1GvsaERF1ZnFxcXBzc1Ncnj59OiZNmtRmeYhaiycfiYiIiIiIiIiISCd48pGog6urq2vrCERERFrDvkZERO3Z3bt32zoCUYfDk49EWjZ69GhERERg4cKF6NGjB6ytrREXF6fSthKJBFu2bMG4ceMgk8ng4OCA3bt3K5Y/+JjZV199hdGjR8PExARffPEF6uvrsWzZMtjZ2cHY2Bhubm44ePBgo/3/9NNP8PLygomJCYYMGYKMjIwGywsLC/Hiiy+ia9eu6NWrF4KDg/Hrr78qlu/ZswcuLi6QyWTo2bMnxowZgzt37mh0OxERUcfAvkZERI+z0aNHIzw8HPPnz4elpSVeeOEFpf2lvr4eCQkJcHR0hLGxMfr06YPly5crlkdFRWHAgAEwNTVFv379EBMTwxffqFPjyUciHfjss89gZmaG7OxsrFy5EsuWLUNaWppK28bExOBvf/sbzp07h6lTp2LKlCkoKipqsE5UVBQiIiJQVFSEgIAArFu3DqtXr8aqVatw/vx5BAQEYMKECSgtLW2wXWRkJBYsWIDc3Fx4eXlhwoQJuHHjBgCgsrISvr6+cHNzw5kzZ3Dw4EFcv34dr7/+umL5lClTMGPGDBQVFSEjIwOBgYEQQmjhFiMiovaMfY2IiB5nn332GaRSKY4fP474+PgW+wsALFq0CAkJCYiJiUFhYSG+/PJL9OrVS7G8W7du+PTTT1FYWIh169Zh27ZtWLNmTVuURqQfgoi0ytfXV/j4+DS4buTIkSIqKkrptgBEaGhog+s8PDzEO++8I4QQory8XAAQa9eubbCOra2tWL58eaMx33333QbbxcfHK5bX1dUJOzs7kZCQIIQQIiYmRvj7+zfYx5UrVwQAUVxcLHJycgQAcenSJaV1EBFR58G+RkREjzNfX1/h5uamuKysv8jlcmFsbCy2bdum8hgrV64UI0aMUFyOjY0Vrq6uisshISFi4sSJGtdA1NakbXTOk6hTGzp0aIPLNjY2qKqqUmlbT0/PRpcf/TVPd3d3xb/lcjkqKirg7e3dYB1vb2+cO3eu2X1LpVK4u7sr3n2Sk5OD9PR0dO3atVGmCxcuwN/fH35+fnBxcUFAQAD8/f3x6quv4oknnlCpLiIi6rjY14iI6HH2cJ9S1l9+//131NbWws/Pr9n97dmzB2vXrkVZWRlu376Ne/fuwdzcXCfZidoDnnwk0gFDQ8MGlyUSCerr6zXen0QiaXDZzMxM6TpCiEbXtbTv+vp6vPzyy0hISGi0jo2NDQwMDJCWloYTJ07g0KFD2LBhAxYvXozs7Gw4ODioUw4REXUw7GtERPQ4e7hPKesvFy9ebHFfJ0+exOTJk7F06VIEBATAwsICycnJWL16tdZzE7UX/M5Honbm5MmTjS47Ozs3u765uTlsbW2RlZXV4PoTJ05g0KBBze773r17yMnJUex7+PDhKCgoQN++feHo6Njg70GzlUgk8Pb2xtKlS5GbmwsjIyOkpqa2ql4iIurc2NeIiKgzUdZfnJycIJPJcPjw4Sa3P378OOzt7bF48WK4u7vDyckJP//8s56rINIvvvORqJ3ZvXs33N3d4ePjg6SkJJw6dQrbt29vcZvIyEjExsaif//+cHNzw44dO5CXl4ekpKQG623atAlOTk4YNGgQ1qxZg5s3b2LGjBkAgLCwMGzbtg1TpkxBZGQkLC0tUVZWhuTkZGzbtg1nzpzB4cOH4e/vDysrK2RnZ+OXX35p9ESQiIjoYexrRETUmSjrLyYmJoiKisLChQthZGQEb29v/PLLLygoKMDMmTPh6OiIy5cvIzk5GSNHjsT+/fv5whd1ejz5SNTOLF26FMnJyXj33XdhbW2NpKQkDB48uMVtIiIiIJfLsWDBAlRVVWHw4MHYt28fnJycGqwXHx+PhIQE5Obmon///vj3v/8NS0tLAICtrS2OHz+OqKgoBAQEoLa2Fvb29hg7diy6dOkCc3NzHD16FGvXroVcLoe9vT1Wr16NcePG6ey2ICKijo99jYiIOhNl/QUAYmJiIJVK8cEHH6CiogI2NjYIDQ0FAEycOBHz5s1DeHg4amtrMX78eMTExCAuLq4NqyLSLYkQQrR1CCL6k0QiQWpqKiZNmtTWUYiIiFqNfY2IiIiI+J2PREREREREREREpBM8+UikJ0lJSejatWuTf0OGDGnreERERGphXyMiIiIiVfBj10R6Ul1djevXrze5zNDQEPb29npOREREpDn2NSIiIiJSBU8+EhERERERERERkU7wY9dERERERERERESkEzz5SERERERERERERDrBk49ERERERERERESkEzz5SERERERERERERDrBk49ERERERERERESkEzz5SERERERERERERDrBk49ERERERERERESkEzz5SERERERERERERDrxf2Shktx2LDM0AAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "fig, ax = plt.subplots(1, 3, figsize=plt.figaspect(1/4))\n", "\n", @@ -475,9 +714,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 21, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "467 ms ± 1.14 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n", + "362 ms ± 1.91 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n", + "297 ms ± 2.25 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n", + "344 ms ± 1.71 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n", + "288 ms ± 1.12 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], "source": [ "bench_qps_s1 = np.zeros((5,), dtype=np.float32)\n", "bench_recall_s1 = np.zeros((5,), dtype=np.float32)\n", @@ -492,20 +743,31 @@ "bench_names = ['32/32', '32/16', '32/8', '16/16', '16/8']\n", "\n", "for i, sp in enumerate(search_ps):\n", - " r = %timeit -o ivf_pq.search(sp, index, queries, k, handle=resources); resources.sync()\n", + " r = %timeit -o ivf_pq.search(sp, index, queries, k, resources=resources); resources.sync()\n", " bench_qps_s1[i] = (queries.shape[0] * r.loops / np.array(r.all_runs)).mean()\n", - " bench_recall_s1[i] = calc_recall(ivf_pq.search(sp, index, queries, k, handle=resources)[1], gt_neighbors)" + " bench_recall_s1[i] = calc_recall(ivf_pq.search(sp, index, queries, k, resources=resources)[1], gt_neighbors)" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 22, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA0UAAAHgCAYAAABqycbBAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAACftklEQVR4nOzdd1gU1/4/8PcCS4eVIk0Qa7AAarCBCqKCBbCkoOEGJRJLRI01iVFjuZbYEr0mlhtrxPZN7BoJ2FADWFBMEEs0YomgRpqiwALn94c/5rouICgK7r5fz8OTzJnPnDkzZ1b2w5w5IxNCCBAREREREWkpnepuABERERERUXViUkRERERERFqNSREREREREWk1JkVERERERKTVmBQREREREZFWY1JERERERERajUkRERERERFpNSZFRERERESk1ZgUERERERGRVmNSRBpp3bp1kMlkZf4cOXJEis3IyMCAAQNgY2MDmUyGvn37AgBSU1MREBAAS0tLyGQyjBkzpsrbuWzZMqxbt67K662I13F81SU1NRUymQwLFy6s7qa8sarz2qwpbt68iZEjR6Jhw4YwNDSEhYUFunTpgq1bt6rFllxzJT86OjqwsrJCr169EB8frxJ7//59TJo0Cc2aNYOJiQkUCgWaNGmC0NBQ/P7772p1FxUVwcbGBt9+++0rO9aXERYWhnr16qmU1atXD2FhYc/dds6cOdi5c+craVdZjhw5ovZ74HWqqt85WVlZsLa2xpYtW1TKf/31V/j7+8PBwQEGBgZwcHBA586d8fXXX6vE1atXD4GBgSplZf3OtLa2fu7v1ZKfZ6+Fp+3duxcDBw6Em5sb5HI5ZDJZqXGJiYmIiIiAm5sbzMzMYGtri27duuHQoUOlxm/btg0dOnSApaUlatWqhbZt22LDhg0qMZmZmahVq9Zrv97ozaFX3Q0gepXWrl2LJk2aqJU3a9ZM+v9///vf2LFjB9asWYOGDRvC0tISADB27FicOHECa9asgZ2dHezt7au8fcuWLYO1tXWFvjxUtddxfPTmqs5rsyb47bffEBgYCFNTU0ycOBHu7u7Izs7G//3f/2HAgAH45ZdfpC+JTxs1ahRCQkJQVFSE8+fPY8aMGfD19UV8fDxatWqFhw8fon379nj48CEmTpyIFi1a4PHjx7h8+TK2b9+OpKQkuLu7q9R59OhR3Lt3D++8887rPAWvxZw5c/Dee+9JiYE2qKrfOTNmzICDgwP69+8vla1YsQKffPIJ3n33XXz33XewtLTEzZs3ERcXh59//hlffPHFc9v33nvvYfz48SplcrkcdevWVUvwPT091eINDAzKrHvHjh1ISEhAq1atYGBggMTExFLjNm/ejJMnT2Lw4MFo0aIFcnNzsWLFCnTt2hXr16/HwIEDpdg1a9YgPDwc7777LqZMmQKZTCbF/PPPPxg7diwAwMLCAmPHjsXEiRPRq1cv6OvrP/dckJYRRBpo7dq1AoA4derUc2O7desmmjZtqlbeqFEj0bNnz1fRPEnz5s2Fj4/PK91HWV7H8VW13NzcCsVdu3ZNABALFix4xS16ORU9nurwKq7N4uJi8ejRoyqt81XIzMwUNjY2wtnZWaSnp6ut//rrrwUA8c0330hlZV1zBw8eFADExx9/LIQQYs2aNQKAOHToUKn7LioqUisbMWKEaN26dbltrs7zOmjQIOHs7KxS5uzsLAYNGvTcbU1MTCoUJ8STYywuLq58A59x+PBhAUAcPnz4pet6EVXxO+f+/fvCyMhIrFixQqW8bt26wtvbu9Rtnr22nJ2dRUBAgEoZABEREVGhNrxI/NNtiIiIEGV9Db1z545aWWFhoXB3dxcNGzZUKe/QoYNwdnZWqbu4uFg0adJEuLu7q8Smp6cLPT09sXHjxgq3mbQHh8+R1ioZ7nLgwAFcuHBBZWidTCbDlStXsH//fqk8NTUVAJCTk4MJEyagfv360NfXR506dTBmzBjk5uaq1F9cXIylS5eiZcuWMDIyQq1atdC+fXvs3r0bwJOhC+fPn0dsbKzasIPi4mLMmjULLi4u0rbu7u5YsmTJc4/rxo0b+PDDD2FjYwMDAwM0bdoUixYtQnFxMQA89/hK89NPP6Fdu3ZQKBQwNjZGgwYNMHjwYJWYip6X77//Ht7e3rCxsYGJiQnc3Nwwf/58KJVKlbjOnTvD1dUVR48ehZeXF4yNjaV9ZmVlYfz48WjQoAEMDAxgY2ODXr164eLFi2pt/+abb1C/fn2YmprC09MTCQkJzz2HJXcAYmJi8NFHH8HS0hImJiYICgrCX3/9pRIbExODPn36wNHREYaGhmjUqBGGDRuGf/75RyVu+vTpkMlkOHPmDN577z1YWFigYcOGAIDTp09jwIABqFevHoyMjFCvXj188MEHuH79eqntOnToEIYMGQIrKyuYm5tj4MCByM3NRXp6OoKDg1GrVi3Y29tjwoQJaue1oKAAs2bNQpMmTWBgYIDatWvjo48+wr1796SY8q5NoOJ9LZPJMHLkSKxYsQJNmzaFgYEB1q9fDwBYvnw5WrRoAVNTU5iZmaFJkyb48ssvn9s3GRkZGDFiBOrUqQN9fX00aNAAkydPRn5+fqn73rBhA5o2bQpjY2O0aNECe/fufe4+Vq1ahbt37+Lrr7+Gra2t2vrPPvsMTZo0wdy5c1FYWFhuXe3btwcAqS/v378PAGXeBdDRUf21LITAjh078O6770plJcOetm/fjlatWsHQ0BAzZswAAKSnp2PYsGFwdHSEvr4+6tevjxkzZqi1Mz8/HzNnzkTTpk1haGgIKysr+Pr6Ii4uToqp6Gf1RclkMuTm5mL9+vXSdda5c2cA/7vWo6OjMXjwYNSuXRvGxsbIz8/HlStX8NFHH6Fx48YwNjZGnTp1EBQUhD/++ENtHxcvXkSPHj1gbGwMa2trDB8+HA8ePCi1PQcOHEDXrl1hbm4OY2NjdOjQAQcPHqzw8Tzv2nzR3zmlWbduHQoLC1XuEgFPrq+KXlvVoaJtsLGxUSvT1dWFh4cHbt68qVIul8thamqqUrdMJoO5uTkMDQ1VYm1tbeHn54cVK1a8QOtJ03H4HGm0oqIitS8DMpkMurq6sLe3R3x8PEaMGIHs7Gxs3LgRwJOhdfHx8ejXrx8aNmwoPZdib2+PR48ewcfHB7du3cKXX34Jd3d3nD9/Hl999RX++OMPHDhwQBpOExYWhsjISISHh2PmzJnQ19fHmTNnpF90O3bswHvvvQeFQoFly5YB+N+wg/nz52P69OmYMmUKvL29oVQqcfHiRWRlZZV7vPfu3YOXlxcKCgrw73//G/Xq1cPevXsxYcIEXL16FcuWLcPbb79d5vGVJj4+Hv3790f//v0xffp0GBoa4vr16ypjuytzXq5evYqQkBDpC/W5c+cwe/ZsXLx4EWvWrFHZd1paGj788EN89tlnmDNnDnR0dPDgwQN07NgRqamp+Pzzz9GuXTs8fPgQR48eRVpamspwye+//x5NmjTB4sWLAQBTp05Fr169cO3aNSgUinLPJQCEh4fDz88PmzZtws2bNzFlyhR07twZv//+O2rVqiUdj6enJz7++GMoFAqkpqbim2++QceOHfHHH39ALper1PnOO+9gwIABGD58uJREpKamwsXFBQMGDIClpSXS0tKwfPlytGnTBikpKbC2tlap4+OPP8Y777yDLVu24OzZs/jyyy9RWFiIS5cu4Z133sHQoUNx4MABzJs3Dw4ODhg3bhyAJ8l2nz59cOzYMXz22Wfw8vLC9evXMW3aNHTu3BmnT5+GkZFRuddmZfoaAHbu3Iljx47hq6++gp2dHWxsbLBlyxaMGDECo0aNwsKFC6Gjo4MrV64gJSWl3P7Iy8uDr68vrl69ihkzZsDd3R3Hjh3D3LlzkZSUhH379qnE79u3D6dOncLMmTNhamqK+fPno1+/frh06RIaNGhQ5n5iYmKgq6uLoKCgUtfLZDL07t0b8+fPx9mzZ9GmTZsy67py5QoAoHbt2gCeDDcCgIEDB+LLL79Ep06dYGVlVeb2cXFxSEtLU0mKAODMmTO4cOECpkyZgvr168PExATp6elo27YtdHR08NVXX6Fhw4aIj4/HrFmzkJqairVr1wIACgsL0bNnTxw7dgxjxoxBly5dUFhYiISEBNy4cQNeXl4AKvdZfRHx8fHo0qULfH19MXXqVACAubm5SszgwYMREBCADRs2IDc3F3K5HLdv34aVlRW+/vpr1K5dGxkZGVi/fj3atWuHs2fPwsXFBQBw584d+Pj4QC6XY9myZbC1tcXGjRsxcuRItbZERkZi4MCB6NOnD9avXw+5XI6VK1eie/fu+PXXX9G1a9dyj6Ui1+aL/M4py759+9CqVSvp36ESnp6e2LZtG6ZPn45+/frB1dUVurq65XfEM4QQar83dXV1y3z+53UpLCzEsWPH0Lx5c5XyUaNG4f3338fs2bMxdOhQyGQyrFu3DomJidi8ebNaPZ07d8akSZOQlZWldv5Iy1X3rSqiV6Fk+FxpP7q6uiqxPj4+onnz5mp1lDa0YO7cuUJHR0dtWN7PP/8sAIhffvlFCCHE0aNHBQAxefLkcttZ1hClwMBA0bJly4ocqoovvvhCABAnTpxQKf/kk0+ETCYTly5dkspKO77SLFy4UAAQWVlZZcZU9Lw8q6ioSCiVSvHjjz8KXV1dkZGRIa3z8fERAMTBgwdVtpk5c6YAIGJiYspsT8lQJjc3N1FYWCiVnzx5UgAQmzdvLveYS66ffv36qZT/9ttvAoCYNWtWqdsVFxcLpVIprl+/LgCIXbt2SeumTZsmAIivvvqq3H0L8WSYyMOHD4WJiYlYsmSJWrtGjRqlEt+3b1+14VxCCNGyZUvx9ttvS8ubN28WAMS2bdtU4k6dOiUAiGXLlkllZV2blelrAEKhUKj0qxBCjBw5UtSqVes5Z0HdihUrBADxf//3fyrl8+bNEwBEdHS0yr5tbW1FTk6OVJaeni50dHTE3Llzy91PkyZNhJ2dXbkxy5cvFwDETz/9JIT43zU3b948oVQqRV5enkhMTBRt2rQRAMS+ffukbWfOnCn09fWlf5Pq168vhg8fLs6dO6e2nzFjxgg3NzeVMmdnZ6Grq6vyeRZCiGHDhglTU1Nx/fp1lfKSz/D58+eFEEL8+OOPAoD44Ycfyj3Gp5X3WX0Vw+dKrvWBAwc+t47CwkJRUFAgGjduLMaOHSuVf/7550Imk4mkpCSVeD8/P5Xhc7m5ucLS0lIEBQWpxBUVFYkWLVqItm3bPrcNlbk2K/M7pyzGxsZi+PDhauVXrlwRrq6u0rVlZGQkunbtKr777jtRUFDw3P2V9XuzrGsFlRw+97Tyhs+VZvLkyQKA2Llzp9q6nTt3CoVCoXLckZGRpdYTExMjAIj9+/e/ULtJc1X/vVSiV+jHH3/EqVOnVH5OnDjxwvXt3bsXrq6uaNmyJQoLC6Wf7t27q8xmtH//fgBARETEC+2nbdu2OHfuHEaMGIFff/0VOTk5Fdru0KFDaNasGdq2batSHhYWBiFEmTP3lKfkr+DBwcH4v//7P/z9999qMRU9LwBw9uxZ9O7dG1ZWVtDV1YVcLsfAgQNRVFSEy5cvq9RbMtvX0/bv34+33noL3bp1e27bAwICVP5KWvIA+7PD0sryr3/9S2XZy8sLzs7OOHz4sFR29+5dDB8+HE5OTtDT04NcLoezszMA4MKFC2p1PvsXfwB4+PAhPv/8czRq1Ah6enrQ09ODqakpcnNzS63j2RmjmjZtKh3vs+VPH+vevXtRq1YtBAUFqfRTy5YtYWdnV6HZuCrT1wDQpUsXWFhYqJS1bdsWWVlZ+OCDD7Br1y61oYZlOXToEExMTPDee++plJdMBvHsUCdfX1+YmZlJy7a2trCxsalw/5dHCAEAan89//zzzyGXy2FoaAgPDw/cuHEDK1euRK9evaSYqVOn4saNG1izZg2GDRsGU1NTrFixAh4eHmp/2d6+fXup14y7uzveeustlbK9e/fC19cXDg4OKn3Ts2dPAEBsbCyAJ58hQ0NDtSGwz6rMZ/VVKe3YCwsLMWfOHDRr1gz6+vrQ09ODvr4+/vzzT5XPy+HDh9G8eXO0aNFCZfuQkBCV5bi4OGRkZGDQoEEq5624uBg9evTAqVOnpLu6T68vLCyUroPKXpsvIysrC48ePSp1iFnDhg1x7tw5xMbGYsaMGejWrRtOnTqFkSNHwtPTE3l5ec+tPzg4WO33ZmUmwigZofH0eXxZq1atwuzZszF+/Hj06dNHZV1UVBQ+/PBDvPPOO9i/fz9iYmLw8ccfIywsTLo7+rSS81ba7zLSbhw+RxqtadOmaN26dZXVd+fOHVy5ckVtSFSJki939+7dg66uLuzs7F5oP5MmTYKJiQkiIyOxYsUK6OrqwtvbG/PmzSv3eO7fv1/qdKgODg7S+sry9vbGzp078Z///AcDBw5Efn4+mjdvjsmTJ+ODDz4AUPHzcuPGDXTq1AkuLi5YsmQJ6tWrB0NDQ5w8eRIRERF4/PixynalDR+5d+8e6tatW6G2PzssqWQI2LP7KUtp/WdnZyedx+LiYvj7++P27duYOnUq3NzcYGJiguLiYrRv377U/ZR2TCEhITh48CCmTp2KNm3awNzcHDKZDL169Sq1jpLZqkqUzKJUWvnTX4Lu3LmDrKysMmddqkhyUtG+LlHa8YaGhqKwsBA//PAD3n33XRQXF6NNmzaYNWsW/Pz8ytz3/fv3YWdnp5aI2NjYQE9PT+36Lm1YmoGBwXP7v27duvjzzz+Rm5sLExOTUmNKhsE6OTmplH/66af48MMPoaOjg1q1aqF+/fqlDjuytbXFRx99hI8++gjAkxnmevbsiU8//VT6XJ08eRI3btwoNTEo7bzeuXMHe/bsqdC/Tw4ODuU+31HZz+qrUtpxjhs3Dt9//z0+//xz+Pj4wMLCAjo6Ovj4449V2nX//n3Ur19fbftnP9d37twBALWE5mkZGRm4d++eWn2HDx9G586dK31tvoySY3z2eZkSOjo68Pb2hre3NwAgNzcX4eHh2Lp1K9asWYMRI0aUW3/t2rVf6vdm165dpQQcAAYNGvRS0/uvXbsWw4YNw9ChQ7FgwQKVdUIIDB48GN7e3ipDOrt164bs7GyMGjUKwcHBKp/jkvP2uq5henMwKSKqBGtraxgZGZU5nr7k2Y/atWujqKgI6enpLzTVtZ6eHsaNG4dx48YhKysLBw4cwJdffonu3bvj5s2bMDY2LnU7KysrpKWlqZXfvn1bpX2V1adPH/Tp0wf5+flISEjA3LlzERISgnr16sHT07PC52Xnzp3Izc3F9u3bpbspAJCUlFTqdqV9maxduzZu3br1QsdRWenp6aWWNWrUCACQnJyMc+fOYd26dRg0aJAUU/IcSWmePabs7Gzs3bsX06ZNU5kuNz8/HxkZGS97CCqsra1hZWWFqKioUtc/fVelvDoq0tclynoOoSQhyM3NxdGjRzFt2jQEBgbi8uXLKtfG06ysrHDixAkIIVTqvXv3LgoLC1/4+n6Wv78/oqOjsWfPHgwYMEBtvRACu3fvhpWVldpdCEdHxxf6Qunt7Q1/f3/s3LkTd+/ehY2NDbZt24a33noLrq6uavGlnVdra2u4u7tj9uzZpe6j5I8jtWvXxvHjx1FcXFxmYlTZz+qrUtpxljz/M2fOHJXyf/75R+UZESsrqzI/w08ruW6WLl0qTYzxrJIJN06dOqVSXvL80uu6Nkv2BaDC/z6YmJhg0qRJ2Lp1K5KTk6usHWVZuXKlymQWL3Psa9euxccff4xBgwZhxYoVatfDnTt3kJaWhmHDhqlt26ZNG/z4449ITU1VeQ6p5LxVZZ+QZuDwOaJKCAwMxNWrV2FlZYXWrVur/ZTcpSkZrrJ8+fJy66vIX61r1aqF9957DxEREcjIyCh3RqKuXbsiJSUFZ86cUSn/8ccfIZPJ4Ovr+/yDfE57fXx8MG/ePABPhtcAFT8vJb/Qnn6PhRACP/zwQ4Xb0LNnT1y+fPmFhgJWVsmD0CXi4uJw/fp1aYas0o4HePKloKJkMhmEEGp1rFq1CkVFRS/Q6rIFBgbi/v37KCoqKrWfSr7gAWVfmxXt64oyMTFBz549MXnyZBQUFOD8+fNlxnbt2hUPHz5Ue/nijz/+KK2vCuHh4bC1tcWkSZNw9+5dtfXz58/HxYsXMXz48HLfyVKaO3fulDqcqKioCH/++SeMjY2lL/bbtm0r9S5RWQIDA5GcnIyGDRuW2jclSVHPnj2Rl5dX7l/vq+KzWhEV+TewtLY9e9737dunNhzK19cX58+fx7lz51TKN23apLLcoUMH1KpVCykpKaWet9atW0NfXx/6+vpq5SV/SHhd1yYAaWa7q1evqq0r7Y9iwP+G8pZcA6+Si4vLS/2bUGLdunX4+OOP8eGHH2LVqlWlJsgWFhYwNDQsdVbR+Ph46OjoqP1hsmQG0affV0gE8E4Rabjk5ORSp8xt2LChNBtUZYwZMwbbtm2Dt7c3xo4dC3d3dxQXF+PGjRuIjo7G+PHj0a5dO3Tq1AmhoaGYNWsW7ty5g8DAQBgYGODs2bMwNjbGqFGjAABubm7YsmULtm7digYNGsDQ0BBubm4ICgqCq6srWrdujdq1a+P69etYvHgxnJ2d0bhx4zLbN3bsWPz4448ICAjAzJkz4ezsjH379mHZsmX45JNP1J5BqIivvvoKt27dQteuXeHo6IisrCwsWbIEcrkcPj4+lTovfn5+0NfXxwcffIDPPvsMeXl5WL58OTIzMyvVB1u3bkWfPn3wxRdfoG3btnj8+DFiY2MRGBj40onf006fPo2PP/4Y77//Pm7evInJkyejTp060vCTJk2aoGHDhvjiiy8ghIClpSX27NmDmJiYCu/D3Nwc3t7eWLBgAaytrVGvXj3ExsZi9erVVT4z0oABA7Bx40b06tULn376Kdq2bQu5XI5bt27h8OHD6NOnD/r16weg7Guzon1dniFDhsDIyAgdOnSAvb090tPTMXfuXCgUinJnchs4cCC+//57DBo0CKmpqXBzc8Px48cxZ84c9OrVq0LPmVVErVq1sG3bNgQGBsLDw0N6yWpOTg62bt2KjRs3ws/PD9OnT6903Rs2bMDKlSsREhKCNm3aQKFQ4NatW1i1apU0i5++vj6SkpJw9erVSiVFM2fORExMDLy8vDB69Gi4uLggLy8Pqamp+OWXX7BixQo4Ojrigw8+wNq1azF8+HBcunQJvr6+KC4uxokTJ9C0aVMMGDCgSj6rFeHm5oYjR45gz549sLe3h5mZmUpyXprAwECsW7cOTZo0gbu7OxITE7FgwQI4OjqqxI0ZMwZr1qxBQEAAZs2aJc0+9+zU/aampli6dCkGDRqEjIwMvPfee7CxscG9e/dw7tw53Lt377l/4Hpd12aJzp07S8+uPq158+bo2rUrevbsiYYNGyIvLw8nTpzAokWLYGtri/Dw8CptR2Vdv35duttWktT9/PPPAJ5MNV9yl/Wnn35CeHg4WrZsiWHDhuHkyZMq9ZS8/NXAwAAjRozAN998g4EDB6J///7Q1dXFzp07sWnTJoSHh6sNK05ISICVlRXc3Nxe9eHSm6aaJnggeqXKm30Oz8ykU9mZgB4+fCimTJkiXFxchL6+vlAoFMLNzU2MHTtW5UWPRUVF4ttvvxWurq5SnKenp9izZ48Uk5qaKvz9/YWZmZkAIM3gtGjRIuHl5SWsra2Fvr6+qFu3rggPDxepqanPPfbr16+LkJAQYWVlJeRyuXBxcRELFiyo0Iv7SrN3717Rs2dPUadOHaGvry9sbGxEr169xLFjx17ovOzZs0e0aNFCGBoaijp16oiJEyeK/fv3q71Msax+EeLJyzU//fRTUbduXSGXy4WNjY0ICAgQFy9eFEKU//JWAGLatGnlHnPJ9RMdHS1CQ0NFrVq1hJGRkejVq5f4888/VWJTUlKEn5+fMDMzExYWFuL9998XN27cUNtPyexz9+7dU9vfrVu3xLvvvissLCyEmZmZ6NGjh0hOTlabwauslxKXVfegQYOEiYmJSplSqRQLFy6U+sDU1FQ0adJEDBs2TOXYyro2hah4X6OMmanWr18vfH19ha2trdDX1xcODg4iODhY/P777+qd8Yz79++L4cOHC3t7e6GnpyecnZ3FpEmTRF5enkpcWfuu6KxoQjz5LI0YMULUr19fyOVy6d+PmTNnqsxqKETFXxickpIixo8fL1q3bi1q164t9PT0hIWFhfDx8REbNmyQ4qZMmaI2o9vTx1DWZ/fevXti9OjRUpstLS2Fh4eHmDx5snj48KEU9/jxY/HVV1+Jxo0bC319fWFlZSW6dOki4uLipJiKflZfZva5pKQk0aFDB2FsbCwASDMelvcC7szMTBEeHi5sbGyEsbGx6Nixozh27Jjw8fFRmzGx5PNpaGgoLC0tRXh4uNi1a1epL2+NjY0VAQEBwtLSUsjlclGnTh0REBAgzTD4PBW9Nqti9rmSlwKfPHlSpXzlypXinXfeEQ0aNBDGxsZCX19fNGzYUAwfPlzcvHnzufsr63NTlsrGl/e7+enrZdCgQeX+Dr927ZoUW1RUJH744QfRunVrUatWLWFubi5atWpV6ox7xcXFwtnZWW0GTyIhhJAJ8f+nTiEiIgBPhm189NFHOHXqVJVO1EFvtj/++AOdOnVCy5YtsX//fhgZGb2yfTVr1gw9e/bEokWLXtk+6M3m7u6ODh06PPcuFv3PwYMH4e/vj/Pnz6u8044I4DNFREREFeLm5oZdu3YhISEB77zzDgoKCl7ZvlJSUpgQUbnmz5+PdevWvbaJZzTBrFmzMHjwYCZEVCo+U0RERFRBPj4+FXrXC9Gr1qNHDyxYsADXrl1Te56K1GVmZsLHx+e5U5KT9uLwOSIiIiIi0mocPkdERERERFqNSREREREREWk1JkVEVC2mT58OmUyGf/75p7qbgilTpiAwMBB16tSBTCZDWFhYmbF//fUX3nnnHdSqVQumpqbw8/NTe1muNli3bh1kMhlOnz5d3U2pUomJiYiIiICbmxvMzMxga2uLbt26lfqy4JJr+NkfQ0PDUuv+559/8Omnn6JevXowMDCAra0tevbsiYyMjFd9WK9cyfXw9MulO3fuLL3o+E1z/Phx9OrVCxYWFjAyMkLjxo3x73//u8x4IQS8vb0hk8kwcuTI19hSIqoqnGiBiLTet99+C3d3d/Tu3Rtr1qwpM+7evXvo1KkTLCwssGbNGhgaGmLu3Lno3LkzTp069dyXTlLNt3nzZpw8eRKDBw9GixYtkJubixUrVqBr165Yv349Bg4cqLZNVFQUFAqFtKyjo/73xtu3b6NTp07Q09PD1KlT0bhxY/zzzz84fPjwK53FrjotW7asupvwQjZt2oTQ0FAEBwfjxx9/hKmpKa5evYrbt2+Xuc3333+PK1euvMZWElFVY1JERFrvwYMH0hfZDRs2lBm3YMEC3Lt3D3FxcXB2dgYAdOzYEQ0bNsRXX32FrVu3vpb2vgpCCOTl5b3Sd++8CT777DMsXLhQpaxXr154++23MXPmzFKTIg8PD1hbW5db74gRI5Cfn4/Tp0/DwsJCKn/nnXeqpuE1ULNmzaq7CZX2999/Y+jQoRg2bJhKUufr61vmNqmpqZg0aRJ+/PFHje5PIk3H4XNEVGNcvHgRDRo0QLt27XD37t3Xtt/S/rJfmh07dqBLly5SQgQA5ubmeOedd7Bnzx4UFhZWet8lQ7DOnz+PDz74AAqFAra2thg8eDCys7MrVVdYWBhMTU1x/vx5dO3aFSYmJqhduzZGjhyJR48eqcSWDPNZsWIFmjZtCgMDA6xfvx7Ak6FDXbt2hZmZGYyNjeHl5YV9+/aVus/MzEx89NFHsLS0hImJCYKCgvDXX3+pxR04cABdu3aFubk5jI2N0aFDBxw8eFAl5t69exg6dCicnJxgYGCA2rVro0OHDjhw4EClzsPLsLGxUSvT1dWFh4cHbt68+UJ1pqamYvfu3RgyZIhKQvSy6tWrh8DAQOzYsQPu7u4wNDREgwYN8J///Ect9uLFi+jRoweMjY1hbW2N4cOHY8+ePZDJZDhy5Eil9puQkIAOHTrA0NAQDg4OmDRpEpRKpVrcs8PnUlNTIZPJsGDBAsybNw/16tWDkZEROnfujMuXL0OpVOKLL76Ag4MDFAoF+vXr91r/HQCAVatWITc3F59//nmFtxk6dCj8/PzQr1+/V9gyInrVmBQRUY0QGxsLLy8vuLu74/Dhw6V+OS0hhEBhYWGFfqrK48ePcfXqVbi7u6utc3d3x+PHj0tNBirq3XffxVtvvYVt27bhiy++wKZNmzB27NhK16NUKtGrVy907doVO3fuxMiRI7Fy5Ur0799fLXbnzp1Yvnw5vvrqK/z666/o1KkTYmNj0aVLF2RnZ2P16tXYvHkzzMzMEBQUVOqdsPDwcOjo6GDTpk1YvHgxTp48ic6dOyMrK0uKiYyMhL+/P8zNzbF+/Xr83//9HywtLdG9e3eVxCg0NBQ7d+7EV199hejoaKxatQrdunXD/fv3yz3mV309FBYW4tixY2jevHmp693c3KCrqwtbW1sMHDgQN27cUFl/7NgxCCHg4OCADz74AKampjA0NETnzp0RHx//Qm0qkZSUhDFjxmDs2LHYsWMHvLy88Omnn6rc7bpz5w58fHyQnJyMZcuWYcOGDXj48OELPfuSkpKCrl27IisrC+vWrcOKFStw9uxZzJo1q8J1fP/99/jtt9/w/fffY9WqVbh48SKCgoIQHh6Oe/fuYc2aNZg/fz4OHDiAjz/++Ln1VWX/Hz16FJaWlrh48SJatmwJPT092NjYYPjw4cjJyVGLX7VqFU6ePInvvvuuwsdPRDWUICKqBtOmTRMAxL1798SGDRuEvr6+GD16tCgqKnrutocPHxYAKvRz7dq1SrXLxMREDBo0SK3877//FgDE3Llz1dZt2rRJABBxcXGV2pcQ/zsP8+fPVykfMWKEMDQ0FMXFxRWua9CgQQKAWLJkiUr57NmzBQBx/PhxqQyAUCgUIiMjQyW2ffv2wsbGRjx48EAqKywsFK6ursLR0VFqz9q1awUA0a9fP5Xtf/vtNwFAzJo1SwghRG5urrC0tBRBQUEqcUVFRaJFixaibdu2UpmpqakYM2ZMhY+3xKu8HoQQYvLkyQKA2Llzp0r5jz/+KGbPni1++eUXcejQIfH1118LS0tLYWtrK27duiXFzZ07VwAQ5ubmok+fPiIqKkps27ZNuLu7C0NDQ3Hu3LlKt0kIIZydnYVMJhNJSUkq5X5+fsLc3Fzk5uYKIYT4/PPPy4wDIA4fPlzhffbv318YGRmJ9PR0qaywsFA0adJE7fz6+PgIHx8fafnatWsCgGjRooXK53zx4sUCgOjdu7fKvsaMGSMAiOzs7HLbVHItVuTneVxcXIShoaEwMzMTc+bMEYcPHxbz588XRkZGokOHDiqfx1u3bgmFQiFWrlwplQEQERERz90PEdU8fKaIiKrV7NmzsXTpUixYsKDCd0Y8PDxw6tSpCsU6ODi8TPPUyGSyF1r3PL1791ZZdnd3R15eHu7evQtbW9tK1fWvf/1LZTkkJASTJ0/G4cOH0aFDB6m8S5cuKsO5cnNzceLECXzyyScwNTWVynV1dREaGorPP/8cly5dQpMmTcrcl5eXF5ydnXH48GFMnjwZcXFxyMjIwKBBg9T+Ut+jRw/Mnz8fubm5MDExQdu2bbFu3TpYWVmhW7du8PDwgFwuf+7xvsrrYdWqVZg9ezbGjx+PPn36qKwLDQ1VWfb19YWvry88PT0xf/58LFmyBABQXFwMAHB0dMS2bdugq6sLAPD09ESjRo0wf/58REZGVqpdJZo3b44WLVqolIWEhCAmJgZnzpxBx44dcfjw4XLjKuPw4cPo2rWryjWpq6uL/v37Y8aMGRWqo1evXipDVps2bQoACAgIUIkrKb9x4wZcXV3LrC8oKKjC/f88xcXFyMvLw7Rp0/DFF18AeDIMUF9fH2PGjMHBgwfRrVs3AMDw4cPRokULDBkypEr2TUTVi0kREVWryMhI1KlTBwMGDKjwNqampmjZsmWFYvX0quafOQsLC8hkslKHcpVMqWxpafnC9VtZWaksGxgYAHgybK8y9PT01Oqys7MDALW229vbqyxnZmZCCKFWDvwvmXi2jpK6ny0ribtz5w4A4L333iuzzRkZGTAxMcHWrVsxa9YsrFq1ClOnToWpqSn69euH+fPnl7qfEq/qeli7di2GDRuGoUOHYsGCBRXapm3btnjrrbeQkJAglZX0R7du3aSECHhy/lu0aPFSU7qXdf6B//XV/fv3Ub9+/Qpt+zz3798vd58V8eznRF9fv9zyvLy859b39Ox/L8PKygp//vknunfvrlLes2dPjBkzBmfOnEG3bt3w888/IyoqCsePH1d79q+goABZWVkwMTGpUFJPRDUDnykiomoVFRUFuVyOTp064fr16xXaJjY2FnK5vEI/T7835WUYGRmhUaNG+OOPP9TW/fHHHzAyMkKDBg2qZF8vo7CwUC1xSU9PB6CeeD17Z8vCwgI6OjpIS0tTq7dkOuJnZ1krqfvZspJ9lcQvXboUp06dKvWn5K6DtbU1Fi9ejNTUVFy/fh1z587F9u3by31vFPBqroe1a9fi448/xqBBg7BixYpK3QUUQqjcCSntObSyYiurrPMP/K+/raysyo2rjKqsq6qsX7++wv3/PGX1lRACwP8mZUlOTkZhYSHat28PCwsL6QcAfvjhB1hYWJQ5OQkR1Uy8U0RE1crZ2RnHjh1Dt27d0KlTJxw8eBCNGzcud5vqGj7Xr18/LF68GDdv3oSTkxOAJ9N5b9++Hb17966yu1Iva+PGjRg9erS0vGnTJgB47os0TUxM0K5dO2zfvh0LFy6UpucuLi5GZGQkHB0d8dZbb6nt691335WW4+LicP36dekB+Q4dOqBWrVpISUmp1IP9devWxciRI3Hw4EH89ttv5cZW9fWwbt06fPzxx/jwww+xatWqSiVECQkJ+PPPP1XOf7t27eDo6Ijo6GgUFRVJd4tu376Nc+fOISQkpML1P+v8+fM4d+6cytC4TZs2wczMDG+//TaAJ8P65s+fX2pcZfn6+mL37t24c+eOlMwWFRVV63T0VTl87t1338V///tf7N+/H61atZLKf/nlFwBA+/btATyZ6bG0z5Ovry/69u2LTz/9tNwhf0RU89SM3+BEpNXs7e0RGxuL7t27w9vbGzExMeV+oTAzM0Pr1q2rbP+xsbG4d+8egCdf8K5fv46ff/4ZAODj44PatWsDACZMmIANGzYgICAAM2fOhIGBAb7++mvk5eVh+vTpKnWGhYVh/fr1uHbtGurVq1dlbX0efX19LFq0CA8fPkSbNm0QFxeHWbNmoWfPnujYseNzt587dy78/Pzg6+uLCRMmQF9fH8uWLUNycjI2b96sliCcPn0aH3/8Md5//33cvHkTkydPRp06dTBixAgAT4a2LV26FIMGDUJGRgbee+892NjY4N69ezh37hzu3buH5cuXIzs7G76+vggJCUGTJk1gZmaGU6dOISoq6rnvfqnK6+Gnn35CeHg4WrZsiWHDhuHkyZMq61u1aiUNbWzRogU+/PBDNG3aFIaGhjh58iQWLFgAOzs7fPbZZ9I2Ojo6+PbbbxEcHIw+ffrgk08+QW5uLv79739DX18fkyZNUtmHTCaDj49PhabKdnBwQO/evTF9+nTY29sjMjISMTExmDdvHoyNjQEAY8aMwZo1axAQEIBZs2bB1tYWGzduxMWLFyt9fqZMmYLdu3ejS5cu+Oqrr2BsbIzvv/8eubm5la6rqlhZWandBX1R/v7+CAoKwsyZM1FcXIz27dvj9OnTmDFjBgIDA6XPUL169cr8XNepU+e5f4Agohqoeud5ICJt9fTscyWysrJEhw4dhKWlpTh16tRra4uPj0+Zs1U9OzPXlStXRN++fYW5ubkwNjYWXbt2FYmJiWp1vvvuu8LIyEhkZmaWu+/SzoMQ/5tRqzKzpQ0aNEiYmJiI33//XXTu3FkYGRkJS0tL8cknn4iHDx+qxKKcWbKOHTsmunTpIkxMTISRkZFo37692LNnT6nti46OFqGhoaJWrVrCyMhI9OrVS/z5559qdcbGxoqAgABhaWkp5HK5qFOnjggICBA//fSTEEKIvLw8MXz4cOHu7i7Mzc2FkZGRcHFxEdOmTZNmUXsdSmbwK+vn6f4YMGCAaNSokTAxMRFyuVw4OzuL4cOHi9u3b5da986dO0WbNm2EoaGhUCgUonfv3uL8+fMqMQ8ePBAAxIABA57bVmdnZxEQECB+/vln0bx5c6Gvry/q1asnvvnmG7XYlJQU4efnJwwNDYWlpaUIDw8Xu3btqvTsc0I8mWGwffv2wsDAQNjZ2YmJEyeK//73vxWefW7BggUq9ZXMHlhyLZQoucZe578FQgjx6NEj8fnnnwsnJyehp6cn6tatKyZNmiTy8vKeu215nysiqtlkQvz/gbJERFRl7OzsEBoaWuEH9KtCWFgYfv75Zzx8+PC17ZOq1i+//ILAwECcO3cObm5u5cbWq1cPrq6u2Lt37wvt68iRI/D19cXhw4d5Z4OItB4nWiAiqmLnz5/Ho0eP8Pnnn1d3U+gNc/jwYQwYMOC5CREREVUtPlNERFTFmjdvjpycnCqrr7i4WHrXTVlqyiQP9HJe553FpwkhUFRUVG6Mrq7uS72Li4ioJuPwOSKiGq5k0oby8J9yehnr1q3DRx99VG4Mh9kRkSZjUkREVMOlpqbin3/+KTemKmfjI+1z//59XLt2rdwYFxcXmJmZvaYWERG9XkyKiIiIiIhIq3GiBSIiIiIi0mpMioiIiIiISKsxKSIiIiIiIq3GpIiIiIiIiLQakyIiIiIiItJqTIqIiIiIiEirMSkiIiIiIiKtxqSIiIiIiIi0GpMiIiIiIiLSakyKiIiIiIhIq+lVdwM0SXFxMW7fvg0zMzPIZLLqbg4RERERkVYTQuDBgwdwcHCAjk7Z94OYFFWh27dvw8nJqbqbQURERERET7l58yYcHR3LXM+kqAqZmZkBeHLSzc3Nq7k1/6NUKhEdHQ1/f3/I5fLqbg69BPalZmA/ag72pWZgP2oO9qVmqMp+zMnJgZOTk/Q9vSxMiqpQyZA5c3PzGpcUGRsbw9zcnP9AvOHYl5qB/ag52Jeagf2oOdiXmuFV9OPzHm3hRAtERERERKTVmBQREREREZFWY1JERERERERajUkRERERERFpNSZFRERERESk1ZgUERERERGRVmNSREREREREWo1JERERERERaTUmRUREREREpNX0qrsBVHWKigVOXsvA3Qd5sDEzRNv6ltDVKf/tvURERERE2o5JkYaISk7DjD0pSMvOk8rsFYaYFtQMXV2sq7FlREREREQ1G4fPaYCo5DR8EnlGJSECgPTsPHwSeQa/nr9TTS0jIiIiIqr5mBS94YqKBWbsSYEoZV1J2ez9F1FcWgARERERETEpetOdvJahdofoaQJAWnY+rubw2SIiIiIiotIwKXrD3X1QdkL0tBzlK24IEREREdEbiknRG87GzLBCcebyV9wQIiIiIqI3FJOiN1zb+pawVxiirMFxMgD2CgM0NOdDRUREREREpWFS9Jrk5eUhLCwMbm5u0NPTQ9++fUuNy8/Px+TJk+Hs7AwDAwM0bNgQa9asUYubPn06BgwYAF0dGUZ5WuPe3kW4+d2HuPHNu0hb9ylyLx6XEqXJPZuArysiIiIiIiod31P0mhQVFcHIyAijR4/Gtm3byowLDg7GnTt3sHr1ajRq1Ah3795FYWGhWtzu3bsxceJEAMDa2RNQR5YJ/YGzkFFsiNyUI/hn93zYOzlj/rA+6OpijV+uv7JDIyIiIiJ6ozEpek1MTEywfPlyAMBvv/2GrKwstZioqCjExsbir7/+gqWlJQCgXr16anE3b95EcnIyevbsCQCIj4/H8uXLEfKvD3HyWgbuPuiBgb77MMpdDz1c7aFUcpYFIiIiIqKycPhcDbJ79260bt0a8+fPR506dfDWW29hwoQJePz4sVqct7c3atWqBQDo2LEjtm7diuysTLSrb4HHF4+hSFmALl18q+EoiIiIiIjeLLxTVIP89ddfOH78OAwNDbFjxw78888/GDFiBDIyMlSeK9q1axf69OkjLW/duhX9+/eHlZUV9PT0YGxsjB07dqBhw4bVcRhERERERG8U3imqQYqLiyGTybBx40a0bdsWvXr1wjfffIN169ZJd4tycnIQGxuL3r17S9tNmTIFmZmZOHDgAE6fPo1x48bh/fffxx9//FFdh0JERERE9MbgnaIaxN7eHnXq1IFCoZDKmjZtCiEEbt26hcaNG2P//v1o2rQpnJ2dAQBXr17Fd999h+TkZDRv3hwA0KJFCxw7dgzff/89VqxYUS3HQkRERET0puCdohqkQ4cOuH37Nh4+fCiVXb58GTo6OnB0dATwZOjc03eJHj16BADQ0VHtSl1dXRQXF7+GVhMRERERvdmYFL1GKSkpSEpKQkZGBrKzs5GUlISkpCRpfUhICKysrPDRRx8hJSUFR48excSJEzF48GAYGRmhsLAQ+/fvV3meqEmTJmjUqBGGDRuGkydP4urVq1i0aBFiYmLKfBcSERERERH9D4fPvUa9evXC9ev/e2FQq1atAABCCACAqakpYmJiMGrUKLRu3RpWVlYIDg7GrFmzAACxsbEwNTWFh4eHVIdcLscvv/yCL774AkFBQXj48CEaNWqE9evXo1evXq/x6IiIiIiI3kxMil6j1NTU58Y0adIEMTExpa7btWsXgoKC1MobN25c7gthiYiIiIiobEyK3iCurq7w9PSs7mYQEREREWmUan2maPny5XB3d4e5uTnMzc3h6emJ/fv3lxo7bNgwyGQyLF68WKU8Pz8fo0aNgrW1NUxMTNC7d2/cunVLJSYzMxOhoaFQKBRQKBQIDQ1FVlaWSsyNGzcQFBQEExMTWFtbY/To0SgoKKjKw31pQ4cOhZubW3U3g4iIiIhIo1RrUuTo6Iivv/4ap0+fxunTp9GlSxf06dMH58+fV4nbuXMnTpw4AQcHB7U6xowZgx07dmDLli04fvw4Hj58iMDAQBQVFUkxISEhSEpKQlRUFKKiopCUlITQ0FBpfVFREQICApCbm4vjx49jy5Yt2LZtG8aPH//qDp6IiIiIiGqEah0+9+zzMbNnz8by5cuRkJAgvXPn77//xsiRI/Hrr78iICBAJT47OxurV6/Ghg0b0K1bNwBAZGQknJyccODAAXTv3h0XLlxAVFQUEhIS0K5dOwDADz/8AE9PT1y6dAkuLi6Ijo5GSkoKbt68KSVeixYtQlhYGGbPng1zc/NS25+fn4/8/HxpOScnBwCgVCqhVCqr4AxVjZK21KQ20YthX2oG9qPmYF9qBvaj5mBfaoaq7MeK1lFjnikqKirCTz/9hNzcXOm5meLiYoSGhmLixIlSkvS0xMREKJVK+Pv7S2UODg5wdXVFXFwcunfvjvj4eCgUCikhAoD27dtDoVAgLi4OLi4uiI+Ph6urq8qdqO7duyM/Px+JiYnw9fUttc1z587FjBkz1Mqjo6NhbGz8wufiVSlrAgd687AvNQP7UXOwLzUD+1FzsC81Q1X0Y8k7PZ+n2pOiP/74A56ensjLy4OpqSl27NiBZs2aAQDmzZsHPT09jB49utRt09PToa+vDwsLC5VyW1tbpKenSzE2NjZq29rY2KjE2Nraqqy3sLCAvr6+FFOaSZMmYdy4cdJyTk4OnJyc4O/vX+bdpeqgVCoRExMDPz8/yOXy6m4OvQT2pWZgP2oO9qVmYD9qDvalZqjKfiwZyfU81Z4Uubi4ICkpCVlZWdi2bRsGDRqE2NhYPH78GEuWLMGZM2cgk8kqVacQQmWb0rZ/kZhnGRgYwMDAQK1cLpfXyA9iTW0XVR77UjOwHzUH+1IzsB81B/tSM1RFP1Z0+2qdaAEA9PX10ahRI7Ru3Rpz585FixYtsGTJEhw7dgx3795F3bp1oaenBz09PVy/fh3jx49HvXr1AAB2dnYoKChAZmamSp13796V7vzY2dnhzp07avu9d++eSsyzd4QyMzOhVCrV7iAREREREZFmqfak6FlCCOTn5yM0NBS///47kpKSpB8HBwdMnDgRv/76KwDAw8MDcrlcZbxhWloakpOT4eXlBQDw9PREdnY2Tp48KcWcOHEC2dnZKjHJyclIS0uTYqKjo2FgYAAPD4/XcdhERERERK9dUbFA/NX72JX0N+Kv3kdRsajuJlWLah0+9+WXX6Jnz55wcnLCgwcPsGXLFhw5cgRRUVGwsrKClZWVSrxcLoednR1cXFwAAAqFAuHh4Rg/fjysrKxgaWmJCRMmwM3NTZqNrmnTpujRoweGDBmClStXAnjyvp/AwECpHn9/fzRr1gyhoaFYsGABMjIyMGHCBAwZMqRGPRtERERERFRVopLTMGNPCtKy86Qye4UhpgU1Qw9X+2ps2etXrXeK7ty5g9DQULi4uKBr1644ceIEoqKi4OfnV+E6vv32W/Tt2xfBwcHo0KEDjI2NsWfPHujq6koxGzduhJubG/z9/eHv7w93d3ds2LBBWq+rq4t9+/bB0NAQHTp0QHBwMPr27YuFCxdW6fESEREREdUEUclp+CTyjEpCBADp2Xn4JPIMopLTythSM1XrnaLVq1dXKj41NVWtzNDQEEuXLsXSpUvL3M7S0hKRkZHl1l23bl3s3bu3Uu0hIiIiInrTFBULzNiTgtIGygkAMgAz9qTAr5kddHUqN+HZm6rGPVNERERERESvzslrGWp3iJ4mAKRl5+HktYzX16hqxqSIiIiIiEiL3H1QdkL0InGagEkREREREZEWsTEzrNI4TcCkiIiIiIhIi7Stbwl7hSHKelpIhiez0LWtb/k6m1WtmBQREREREWkRXR0ZpgU1AwC1xKhkeVpQM62ZZAFgUkREREREVCNdunQJvr6+sLW1haGhIRo0aIApU6ZAqVRKMdu3b4efnx9q164Nc3NzeHp64tdffy21vunTp2PAgAEAgJbWMjQ4vxZ/fx+KG9+8i7R1nyL34nHYKQyx/MO3+Z4iIiIiIiKqfnK5HAMHDkR0dDQuXbqExYsX44cffsC0adOkmKNHj8LPzw+//PILEhMT4evri6CgIJw9e1atvt27d6NPnz4AgNDQUDy4cwOxMfvxf7/+hvfefQcZe+Zjqb+F1iVEQDW/p4iIiIiIiErXoEEDNGjQQFp2dnbGkSNHcOzYMals8eLFKtvMmTMHu3btwp49e9CqVSup/ObNm0hOTkbPnj0BAPHx8Vi+fDk827cDALzn64GoLatxLuksWnu8/QqPqmbinSIiIiIiojfAlStXEBUVBR8fnzJjiouL8eDBA1haqk6SsHv3bnh7e6NWrVoAgI4dO2Lr1q3IyMhAcXExtmzZgvz8fHTu3PkVHkHNxaSIiIiIiKgG8/LygqGhIRo3boxOnTph5syZZcYuWrQIubm5CA4OVinftWuXNHQOALZu3YrCwkJYWVnBwMAAw4YNw44dO9CwYcNXdhw1GZMiIiIiIqIabOvWrThz5gw2bdqEffv2YeHChaXGbd68GdOnT8fWrVthY2Mjlefk5CA2Nha9e/eWyqZMmYLMzEwcOHAAp0+fxrhx4/D+++/jjz/+eOXHUxPxmSIiIiIiohrMyckJANCsWTMUFRVh6NChGD9+PHR1daWYrVu3Ijw8HD/99BO6deumsv3+/fvRtGlTODs7AwCuXr2K7777DsnJyWjevDkAoEWLFjh27Bi+//57rFix4jUdWc3BO0VERERERG8IIQSUSiWEEFLZ5s2bERYWhk2bNiEgIEBtm127dqncJXr06BEAQEdHNRXQ1dVFcXHxK2p5zcY7RURERERENdDGjRshl8vh5uYGAwMDJCYmYtKkSejfvz/09J58jd+8eTMGDhyIJUuWoH379khPTwcAGBkZQaFQoLCwEPv378eBAwekeps0aYJGjRph2LBhWLhwIaysrLBz507ExMRg79691XKs1Y1JERERERFRDaSnp4d58+bh8uXLEELA2dkZERERGDt2rBSzcuVKFBYWIiIiAhEREVL5oEGDsG7dOsTGxsLU1BQeHh7SOrlcjl9++QVffPEFgoKC8PDhQzRq1Ajr169Hr169Xusx1hRMioiIiIiIaqD+/fujf//+5cYcOXKk3PW7du1CUFCQWnnjxo2xbdu2l2meRmFSRERERESkoVxdXeHp6VndzajxmBQREREREWmooUOHVncT3gicfY6IiIiIiLQakyIiIiIiItJqTIqIiIiIiEirMSkiIiIiIiKtxqSIiIiIiIi0GpMiIiIiIiLSakyKiIiIiIhIqzEpIiIiIiIircakiIiIiIiItBqTIiIiIiIi0mpMioiIiIiISKsxKSIiIiIiIq3GpIiIiIiIiLQakyIiIiIiItJqTIqIiIiIiEirMSkiIiIiIiKtxqSIiIiIiIi0GpMiIiIiIiLSakyKiIiIiIhIqzEpIiIiIiIircakiIiIiIiItFq1JkXLly+Hu7s7zM3NYW5uDk9PT+zfvx8AoFQq8fnnn8PNzQ0mJiZwcHDAwIEDcfv2bZU68vPzMWrUKFhbW8PExAS9e/fGrVu3VGIyMzMRGhoKhUIBhUKB0NBQZGVlqcTcuHEDQUFBMDExgbW1NUaPHo2CgoJXevxERERERFT9qjUpcnR0xNdff43Tp0/j9OnT6NKlC/r06YPz58/j0aNHOHPmDKZOnYozZ85g+/btuHz5Mnr37q1Sx5gxY7Bjxw5s2bIFx48fx8OHDxEYGIiioiIpJiQkBElJSYiKikJUVBSSkpIQGhoqrS8qKkJAQAByc3Nx/PhxbNmyBdu2bcP48eNf27kgIiIiIqLqoVedOw8KClJZnj17NpYvX46EhASEh4cjJiZGZf3SpUvRtm1b3LhxA3Xr1kV2djZWr16NDRs2oFu3bgCAyMhIODk54cCBA+jevTsuXLiAqKgoJCQkoF27dgCAH374AZ6enrh06RJcXFwQHR2NlJQU3Lx5Ew4ODgCARYsWISwsDLNnz4a5uflrOBtERERERFQdqjUpelpRURF++ukn5ObmwtPTs9SY7OxsyGQy1KpVCwCQmJgIpVIJf39/KcbBwQGurq6Ii4tD9+7dER8fD4VCISVEANC+fXsoFArExcXBxcUF8fHxcHV1lRIiAOjevTvy8/ORmJgIX1/fUtuTn5+P/Px8aTknJwfAk6F/SqXyhc9FVStpS01qE70Y9qVmYD9qDvalZmA/ag72pWaoyn6saB3VnhT98ccf8PT0RF5eHkxNTbFjxw40a9ZMLS4vLw9ffPEFQkJCpDs36enp0NfXh4WFhUqsra0t0tPTpRgbGxu1+mxsbFRibG1tVdZbWFhAX19fiinN3LlzMWPGDLXy6OhoGBsbP+fIX79n77zRm4t9qRnYj5qDfakZ2I+ag32pGaqiHx89elShuGpPilxcXJCUlISsrCxs27YNgwYNQmxsrEpipFQqMWDAABQXF2PZsmXPrVMIAZlMJi0//f8vE/OsSZMmYdy4cdJyTk4OnJyc4O/vX6OG3CmVSsTExMDPzw9yuby6m0MvgX2pGdiPmoN9qRnYj5qDfakZqrIfS0ZyPU+1J0X6+vpo1KgRAKB169Y4deoUlixZgpUrVwJ4clKCg4Nx7do1HDp0SCXZsLOzQ0FBATIzM1XuFt29exdeXl5SzJ07d9T2e+/ePenukJ2dHU6cOKGyPjMzE0qlUu0O0tMMDAxgYGCgVi6Xy2vkB7Gmtosqj32pGdiPmoN9qRnYj5qDfakZqqIfK7p9jXtPkRBCek6nJCH6888/ceDAAVhZWanEenh4QC6Xq9xaS0tLQ3JyspQUeXp6Ijs7GydPnpRiTpw4gezsbJWY5ORkpKWlSTHR0dEwMDCAh4fHKztWIiIiIiKqftV6p+jLL79Ez5494eTkhAcPHmDLli04cuQIoqKiUFhYiPfeew9nzpzB3r17UVRUJD3fY2lpCX19fSgUCoSHh2P8+PGwsrKCpaUlJkyYADc3N2k2uqZNm6JHjx4YMmSIdPdp6NChCAwMhIuLCwDA398fzZo1Q2hoKBYsWICMjAxMmDABQ4YMqVHD4IiIiIiIqOpVa1J0584dhIaGIi0tDQqFAu7u7oiKioKfnx9SU1Oxe/duAEDLli1Vtjt8+DA6d+4MAPj222+hp6eH4OBgPH78GF27dsW6deugq6srxW/cuBGjR4+WZqnr3bs3vvvuO2m9rq4u9u3bhxEjRqBDhw4wMjJCSEgIFi5c+GpPABERERERVbtqTYpWr15d5rp69epBCPHcOgwNDbF06VIsXbq0zBhLS0tERkaWW0/dunWxd+/e5+6PiIiIiIg0S417poiIiIiIiOh1YlJERERERERajUkRERERERFpNSZFRERERESk1ZgUERERERGRVmNSREREREREWo1JERERERERaTUmRUREREREpNWYFBERERERkVZjUkRERERERFqNSREREREREWk1JkVERERERKTVmBQREREREZFWY1JERERERERajUkRERERERFpNSZFRERERESk1ZgUERERERGRVmNSREREREREWo1JERERERERaTUmRUREREREpNWYFBERERERkVZjUkRERERERFqNSREREREREWk1JkVERERERKTVmBQREREREZFWY1JERERERERajUkRERERERFpNSZFRERERESk1ZgUERERERGRVmNSREREREREWo1JERERERERaTUmRUREREREpNWYFBERERERkVZjUkRERERERFqNSREREREREWk1JkVERERERKTVmBQREREREZFWY1JERERERERajUkRERERERFpNSZFRERERESk1ao1KVq+fDnc3d1hbm4Oc3NzeHp6Yv/+/dJ6IQSmT58OBwcHGBkZoXPnzjh//rxKHfn5+Rg1ahSsra1hYmKC3r1749atWyoxmZmZCA0NhUKhgEKhQGhoKLKyslRibty4gaCgIJiYmMDa2hqjR49GQUHBKzt2IiIiIiKqGao1KXJ0dMTXX3+N06dP4/Tp0+jSpQv69OkjJT7z58/H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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "fig, ax = plt.subplots(1, 1, figsize=plt.figaspect(1/2))\n", "fig.suptitle(\n", - " f'Effects of search parameters on QPS/recall trade-off ({DATASET_FILENAME})\\n' + \\\n", + " f'Effects of search parameters on QPS/recall trade-off ({DATASET_NAME})\\n' + \\\n", " f'k = {k}, n_probes = {n_probes}, pq_dim = {pq_dim}')\n", "ax.plot(bench_recall_s1, bench_qps_s1, 'o')\n", "ax.set_xlabel('recall')\n", @@ -547,14 +809,36 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 23, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "463 ms ± 2.33 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n", + "360 ms ± 2.12 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n", + "297 ms ± 2.74 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n", + "342 ms ± 1.37 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n", + "287 ms ± 1.79 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n", + "490 ms ± 3.19 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n", + "407 ms ± 3.57 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n", + "378 ms ± 1.97 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n", + "395 ms ± 1.73 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n", + "342 ms ± 2.51 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n", + "541 ms ± 1.61 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n", + "437 ms ± 1.09 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n", + "366 ms ± 1.56 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n", + "414 ms ± 1.27 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n", + "375 ms ± 1.89 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], "source": [ "def search_refine(ps, ratio):\n", " k_search = k * ratio\n", - " candidates = ivf_pq.search(ps, index, queries, k_search, handle=resources)[1]\n", - " return candidates if ratio == 1 else refine(dataset, queries, candidates, k, handle=resources)[1]\n", + " candidates = ivf_pq.search(ps, index, queries, k_search, resources=resources)[1]\n", + " return candidates if ratio == 1 else refine(dataset, queries, candidates, k, resources=resources)[1]\n", "\n", "ratios = [1, 2, 4]\n", "bench_qps_sr = np.zeros((len(ratios), len(search_ps)), dtype=np.float32)\n", @@ -569,13 +853,24 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 24, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/png": "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/h3YOBx47UegQaDuLqe2684SlVq1AIrurwCKnKvAit6aBRtClgBGzy5V4UwREREREREBJ9ZqluV2agzITYD0RGDnDKDxq4D837Th79+BDcOAoNma2Z471zTlCjPNLFBxkWam6J1NUrUWBZkwOrAQaNADsHAA7qQDsQs1+9QP0ATlpGsSIqWb5jqivBv322Wte5+6ysakiIiIiIiINKfHHVioWXVOCKCWO9D6faDtCM12dTEQuwBQFwHbJ2p+SjQdCLyyFLgYC5hYAa7NpU1qIwVkl+KBI98Dd28DVo6alekGx9y/FuncLs3iCrfOA1810m7X9Oyn2m2ASREREREREQGA72uan9IkbQaiPtKc4lbCxhUImgP49L1fdmo70CBIa9d7ClsU94uEUcl9ikrT/C3Nj4HwmiIiIiIiIipb0mZg7TvaCRGgOeVt7Tua7SUcGwGtBj/b9lUCJkVERERERFQ6dbFmhgiilI3/lkVN0cQBQMt3NdckPWeYFBERERERUekuxunOEGkRQM4VTdxzjEkRERERERGVLvda5cZVUUyKiIiIiIiodFYVXA67onFVFJMiIiIiIiIqnWd7zSpzkJURIANsXtDEPceYFBERERERUemM5JpltwHoJkb/Pg6arYl7jjEpIiIiIiKisvn0BUJ/AWxctMttXDXlD96n6Dll0KRo1qxZaNWqFaytreHo6IiQkBCkpKRoxQghMH36dLi6usLc3Bxdu3bFP//8oxVTUFCAUaNGwcHBAZaWlujbty8uX76sFZOVlYWwsDAolUoolUqEhYXh9u3bWjFpaWno06cPLC0t4eDggNGjR6OwsPCp9J2IiIiI6Lnh0xcYexIYtBV4bbnm37F/V4uECDBwUrR3716MGDEC8fHxiImJQVFREQICApCXlyfFfPnll/jqq6/wzTff4MiRI3B2dkbPnj1x584dKWbs2LHYsGEDIiMjERsbi9zcXAQHB6O4uFiKGThwIBITExEVFYWoqCgkJiYiLCxM2l5cXIzevXsjLy8PsbGxiIyMxLp16zBhwoRn82IQEREREVVlRnKgTifA73XNv8/5KXMPMjbkk0dFRWk9joiIgKOjIxISEtC5c2cIIbBw4UJ8/PHHePXVVwEAP//8M5ycnLBmzRoMGzYM2dnZWL58OVauXIkePXoAAFatWgV3d3fs3LkTgYGBSE5ORlRU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HDx4UGv/uLg4dO7cGebm5nB3d8fo0aORl5dX5utX4vvvv4e7uzssLCzQv39/3L59W9r24MxVeHg49u7di0WLFkntu3DhArKysvDWW2+hdu3aMDc3R/369REREQFA99vj8PBwad8Hf0r6WVhYiMmTJ+OFF16ApaUl2rRpo/UalCYtLQ39+vWDlZUVbGxsEBoaimvXrgHQzN74+fkBAOrWrSu1uSIenP0r6cf69etLfQ/27NmDd999F9nZ2VKfpk+fXqE+lcwwbd26FT4+PjA1NcXFixfh5eWFL774Au+99x6sra3h4eGBH374QauNH330ERo0aAALCwvUrVsX06ZNe+wbNlfk9+nh2Z8lS5agfv36MDMzg5OTE15//XUAZY+V4uJiDB48GHXq1IG5uTm8vb2xaNEirecoGXPz5s2Di4sL7O3tMWLECK1+FRQUYPLkyXB3d4epqSnq16+P5cuXS9uTkpLQq1cvWFlZwcnJCWFhYbhx40aFXoe8vDy88847sLKygouLC+bPn6/zGly8eBHjxo2T+paXlwcbGxutYwug+f23tLTEnTt3pDEUGRmJ9u3bw8zMDI0bN9YZ3xVp+4EDB2BkZIQ2bdrotL+0GcuNGzdCJpNJ22fMmIG//vpLav+Ds8FlOXPmDDp37gwzMzP4+PggJiZGa3udOnUAAM2bN4dMJkPXrl2xb98+KBQKZGRkaMVOmDABnTt31mpvZX6WEBFVaYIqTXZ2tgAgsrOzS91eWFgo1qxZI6ZPny6uXr1aasygQYNEv379ynyOBQsWiF27donz58+LP//8U3h7e4sPP/xQ2h4RESEUCoVo2bKliIuLE0ePHhWtW7cW7du3l2KioqKEjY2NWLFihTh37pyIjo4WXl5eYvr06VIMALFhwwYhhBBz584V7u7uYt++feLChQti//79Ys2aNWW2cc6cOUKpVIrff/9dJCUlicGDBwtra2utfv3nP/8RDRs2FFFRUeLcuXMiIiJCmJqaij179gghhNi9e7cAINq0aSP27Nkj/vnnH9GpUyetfqxfv14oFArx7bffipSUFDF//nwhl8vFrl27hBBCZGZmCgAiIiJCpKeni8zMzAr3/0F37twRoaGhIigoSKSnp4v09HRRUFAgUlNTBQDRsGFDsXXrVpGSkiJef/114enpKVQqlRBCiBMnTggrKyuxYMECcfr0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6tWDQqFAfHy89GVCVlYWTp8+rXUMKev34O2338bkyZPx9ddf459//sGgQYN0Ykp7jUeOHFnhtm/atAnvv/9+mX2oXbs27ty5ozWr//Ay2foeZ0p+zx4+VjxcJ4BS633//ffxxhtvwM3NDS+++CI6dOigtf1pfJYQUfW38+JOzD48G9fyr0llNjIbmF8yR1DdIAO2rHxMiqqg7OxsnQ9LOzs7vPjiiygqKsLixYvRp08fHDhwAN99953O/gqFAqNGjcLXX38NhUKBkSNHom3bttIH2yeffILg4GC4u7ujf//+MDIywokTJ/D333/j888/16lvwYIFcHFxQbNmzWBkZITffvsNzs7OZX7rPGbMGMyePRv169dHo0aN8NVXX2mtnGZtbY2JEydi3LhxUKvV6NixI3JychAXFwcrK6tS/2ApzaRJkxAaGooWLVqge/fu2LJlC9avX4+dO3dKMV5eXvjzzz/RoUMHmJqawtbWVu/+l9SzY8cOpKSkwN7evsJLYn/00Udo27YtRowYgSFDhsDS0hLJycmIiYnB4sWLy9zPzMwMgwYNwrx585CTk4PRo0cjNDS0zFPnvLy8cOjQIVy4cAFWVlaws7PD9OnT4e/vj8aNG6OgoABbt24tMyEZNmwYLl26hD///BPXr1+Xyu3s7NCgQQO89dZbeOeddzB//nw0b94cN27cwK5du+Dn54devXrp1NejRw80adIEb731FhYuXIiioiIMHz4cXbp00Tod52FTp07FlStX8Msvv5QZoy8vLy/k5ubizz//RNOmTWFhYfFYfaqoevXqIS0tDZGRkWjVqhW2bduGDRs2PHZ9j/p9etjWrVtx/vx5dO7cGba2tti+fTvUarV0qllpY6VevXr45ZdfsGPHDtSpUwcrV67EkSNHpJXLKsLLywuDBg3Ce++9h6+//hpNmzbFxYsXkZmZidDQUIwYMQLLli3Dm2++iUmTJsHBwQFnz55FZGQkli1bpjNj9yArKysMHjwYkyZNgr29PZycnPDxxx/rLI/u5eWFffv24Y033oCpqSkcHBwAALa2tnj11VcxadIkBAQEaJ1uVuLbb7+VXuMFCxYgKysL7733HgA8su03b97EkSNHyr0nWps2bWBhYYH//Oc/GDVqFA4fPqyzupyXlxdSU1ORmJgINzc3WFtb68yeP6hHjx7w9vaWxnFOTo5W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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "fig, ax = plt.subplots(1, 1, figsize=plt.figaspect(1/2))\n", "fig.suptitle(\n", - " f'Effects of search parameters on QPS/recall trade-off ({DATASET_FILENAME})\\n' + \\\n", + " f'Effects of search parameters on QPS/recall trade-off ({DATASET_NAME})\\n' + \\\n", " f'k = {k}, n_probes = {n_probes}, pq_dim = {pq_dim}')\n", "labels = []\n", "for j, ratio in enumerate(ratios):\n", @@ -619,7 +914,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 25, "metadata": {}, "outputs": [], "source": [ @@ -629,8 +924,8 @@ " n_probes=n_probes,\n", " internal_distance_dtype=internal_distance_dtype,\n", " lut_dtype=lut_dtype)\n", - " candidates = ivf_pq.search(ps, index, queries, k_search, handle=resources)[1]\n", - " return candidates if ratio == 1 else refine(dataset, queries, candidates, k, handle=resources)[1]\n", + " candidates = ivf_pq.search(ps, index, queries, k_search, resources=resources)[1]\n", + " return candidates if ratio == 1 else refine(dataset, queries, candidates, k, resources=resources)[1]\n", "\n", "search_configs = [\n", " lambda n_probes: search_refine(np.float16, np.float16, 1, n_probes),\n", @@ -688,9 +983,52 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 26, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "using ivf_pq::index_params nrows 1000000, dim 128, n_lits 100, pq_dim 64\n", + "5.41 ms ± 25 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n", + "5.41 ms ± 31.8 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n", + "5.41 ms ± 18.1 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n", + "9.76 ms ± 85.6 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n", + "37.8 ms ± 219 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n", + "70.5 ms ± 78 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n", + "using ivf_pq::index_params nrows 1000000, dim 128, n_lits 500, pq_dim 64\n", + "2.37 ms ± 12.3 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n", + "4.08 ms ± 19.5 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n", + "8.81 ms ± 18.8 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n", + "16.3 ms ± 38.6 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n", + "73.3 ms ± 176 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n", + "142 ms ± 362 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n", + "using ivf_pq::index_params nrows 1000000, dim 128, n_lits 1000, pq_dim 64\n", + "3.49 ms ± 20.3 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n", + "7.36 ms ± 7.32 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n", + "13.6 ms ± 29.1 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n", + "26.3 ms ± 1.21 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)\n", + "120 ms ± 150 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n", + "233 ms ± 1.24 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + }, + { + "ename": "CuvsException", + "evalue": "std::bad_alloc: out_of_memory: RMM failure at:/home/cjnolet/software/miniconda3/envs/cuvs_062724_2408/include/rmm/mr/device/pool_memory_resource.hpp:255: Maximum pool size exceeded", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mCuvsException\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[26], line 12\u001b[0m\n\u001b[1;32m 10\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m i, n_lists \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28menumerate\u001b[39m(n_list_variants):\n\u001b[1;32m 11\u001b[0m index_params \u001b[38;5;241m=\u001b[39m ivf_pq\u001b[38;5;241m.\u001b[39mIndexParams(n_lists\u001b[38;5;241m=\u001b[39mn_lists, metric\u001b[38;5;241m=\u001b[39mmetric, pq_dim\u001b[38;5;241m=\u001b[39mpq_dim)\n\u001b[0;32m---> 12\u001b[0m index \u001b[38;5;241m=\u001b[39m \u001b[43mivf_pq\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mbuild\u001b[49m\u001b[43m(\u001b[49m\u001b[43mindex_params\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdataset\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mresources\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mresources\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 13\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m j, pl_ratio \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28menumerate\u001b[39m(pl_ratio_variants):\n\u001b[1;32m 14\u001b[0m n_probes \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mmax\u001b[39m(\u001b[38;5;241m1\u001b[39m, n_lists \u001b[38;5;241m/\u001b[39m\u001b[38;5;241m/\u001b[39m pl_ratio)\n", + "File \u001b[0;32mresources.pyx:110\u001b[0m, in \u001b[0;36mcuvs.common.resources.auto_sync_resources.wrapper\u001b[0;34m()\u001b[0m\n", + "File \u001b[0;32mivf_pq.pyx:269\u001b[0m, in \u001b[0;36mcuvs.neighbors.ivf_pq.ivf_pq.build\u001b[0;34m()\u001b[0m\n", + "File \u001b[0;32mivf_pq.pyx:270\u001b[0m, in \u001b[0;36mcuvs.neighbors.ivf_pq.ivf_pq.build\u001b[0;34m()\u001b[0m\n", + "File \u001b[0;32mexceptions.pyx:37\u001b[0m, in \u001b[0;36mcuvs.common.exceptions.check_cuvs\u001b[0;34m()\u001b[0m\n", + "\u001b[0;31mCuvsException\u001b[0m: std::bad_alloc: out_of_memory: RMM failure at:/home/cjnolet/software/miniconda3/envs/cuvs_062724_2408/include/rmm/mr/device/pool_memory_resource.hpp:255: Maximum pool size exceeded" + ] + } + ], "source": [ "n_list_variants = [100, 500, 1000, 2000, 5000]\n", "pl_ratio_variants = [500, 200, 100, 50, 10, 5]\n", @@ -703,12 +1041,13 @@ "\n", "for i, n_lists in enumerate(n_list_variants):\n", " index_params = ivf_pq.IndexParams(n_lists=n_lists, metric=metric, pq_dim=pq_dim)\n", - " index = ivf_pq.build(index_params, dataset, handle=resources)\n", + " index = ivf_pq.build(index_params, dataset, resources=resources)\n", " for j, pl_ratio in enumerate(pl_ratio_variants):\n", " n_probes = max(1, n_lists // pl_ratio)\n", " r = %timeit -o search_fun(n_probes); resources.sync()\n", " bench_qps_nl[i, j] = (queries.shape[0] * r.loops / np.array(r.all_runs)).mean()\n", - " bench_recall_nl[i, j] = calc_recall(search_fun(n_probes), gt_neighbors)" + " bench_recall_nl[i, j] = calc_recall(search_fun(n_probes), gt_neighbors)\n", + " del index" ] }, { @@ -719,7 +1058,7 @@ "source": [ "fig, ax = plt.subplots(1, 1, figsize=plt.figaspect(1/2))\n", "fig.suptitle(\n", - " f'Effects of n_list on QPS/recall trade-off ({DATASET_FILENAME})\\n' + \\\n", + " f'Effects of n_list on QPS/recall trade-off ({DATASET_NAME})\\n' + \\\n", " f'k = {k}, pq_dim = {pq_dim}, search = {search_label}')\n", "labels = []\n", "for i, n_lists in enumerate(n_list_variants):\n", @@ -875,7 +1214,7 @@ "bench_recall_ip = np.zeros_like(bench_qps_ip, dtype=np.float32)\n", "\n", "for i, index_params in enumerate(build_configs.values()):\n", - " index = ivf_pq.build(index_params, dataset, handle=resources)\n", + " index = ivf_pq.build(index_params, dataset, resources=resources)\n", " for l, search_fun in enumerate(search_configs):\n", " for j, n_probes in enumerate(n_probes_variants):\n", " r = %timeit -o search_fun(n_probes); resources.sync()\n", @@ -891,7 +1230,7 @@ "source": [ "fig, ax = plt.subplots(len(search_config_names), 1, figsize=(16, len(search_config_names)*8))\n", "fig.suptitle(\n", - " f'Effects of index parameters on QPS/recall trade-off ({DATASET_FILENAME})\\n' + \\\n", + " f'Effects of index parameters on QPS/recall trade-off ({DATASET_NAME})\\n' + \\\n", " f'k = {k}, n_lists = {n_lists}')\n", "\n", "for j, search_label in enumerate(search_config_names):\n", @@ -932,7 +1271,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.13" + "version": "3.11.9" }, "vscode": { "interpreter": {