Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[REVIEW] Random Ball Cover Algorithm for 2D Haversine/Euclidean #213

Merged
merged 66 commits into from
Sep 23, 2021
Merged
Show file tree
Hide file tree
Changes from 60 commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
ca25914
Stubbing out rbc
cjnolet Apr 23, 2021
ef008ee
Merge branch 'branch-21.08' into poc-020-random_ball_cover
cjnolet Jun 16, 2021
ae16d08
Fixing style
cjnolet Jun 16, 2021
af9520f
It's coming along. Using warp-select for each closest R
cjnolet Jun 18, 2021
c4b27d6
checkjig in
cjnolet Jun 21, 2021
81c11df
Prototype of random ball cover so far.
cjnolet Jun 22, 2021
10d2acc
Working through discrepaqncies
cjnolet Jun 22, 2021
1cfe573
updates
cjnolet Jun 23, 2021
dc2c22c
Merge branch 'branch-21.08' into poc-020-random_ball_cover
cjnolet Jun 23, 2021
e7662f0
Updates
cjnolet Jun 23, 2021
b3738fb
Merge branch 'branch-21.08' into poc-020-random_ball_cover
cjnolet Jun 23, 2021
a79a268
Merge branch 'branch-21.08' into poc-020-random_ball_cover
cjnolet Jun 24, 2021
c324484
Const raft handle in sparse bfknn
cjnolet Jun 24, 2021
a9fbc87
Merge branch 'bug-sparse_bfknn_const_handle' into poc-020-random_ball…
cjnolet Jun 24, 2021
fc185e5
Additional consts needed
cjnolet Jun 24, 2021
87ac7f8
Merge branch 'branch-21.08' into bug-sparse_bfknn_const_handle
cjnolet Jun 24, 2021
729cb87
Merge branch 'bug-sparse_bfknn_const_handle' into poc-020-random_ball…
cjnolet Jun 24, 2021
14ef891
Updates. Runs in ~20s on 3M data items w/ an average number of distan…
cjnolet Jun 25, 2021
795daa2
changes
cjnolet Jun 26, 2021
2fb84a3
Looks like pruning is working. Need to separate the bounds for the la…
cjnolet Jul 7, 2021
c29d58d
Comment
cjnolet Jul 7, 2021
9759f59
Checking in
cjnolet Jul 20, 2021
38671ee
Merge branch 'branch-21.08' of github.com:rapidsai/raft into poc-020-…
cjnolet Jul 20, 2021
568f553
Checking in
cjnolet Jul 20, 2021
2dcbba7
Testing effects of removing post-processing
cjnolet Jul 21, 2021
3345dbd
Trying again
cjnolet Jul 21, 2021
60f9627
Checking in. parsing csv in gtest- verified filtering works. Still
cjnolet Jul 26, 2021
a0072e4
Fixed nondeterminism in k-select.
cjnolet Aug 3, 2021
eb96c0a
Getting exact match in 19s on 3M vectors for RBC
cjnolet Aug 4, 2021
872ca1b
Higher dimensions is working. Need to verify exact results.
cjnolet Aug 11, 2021
26fc9f4
Profiling. Need to find ways to limit register usage!
cjnolet Aug 11, 2021
1d0389f
Optimizing based on profiling results. Working to lower register count
cjnolet Aug 12, 2021
f90eae0
adding some light docs for now
cjnolet Aug 12, 2021
f4e5d3c
Yes! Back to exact results for 2d case (with generalized distance
cjnolet Aug 13, 2021
b898233
Pushing
cjnolet Aug 13, 2021
d68012b
Higher dimensions looks correct but bfknn in gtest is not computing the
cjnolet Aug 13, 2021
af7f457
The code works! Getting 5s for haversine knn (k=7) w/ 3Mx2, 3s for Eu…
cjnolet Aug 13, 2021
037aaa5
Fixing style
cjnolet Aug 13, 2021
5d25ad2
More cleanup and code commenting. Separating the rbc all-neighbors
cjnolet Aug 16, 2021
2953430
More cleanup
cjnolet Aug 16, 2021
02becc1
Found the source of remaining discrepancies.
cjnolet Aug 17, 2021
faf97ee
Broken something, trying to figure out what it is
cjnolet Aug 18, 2021
65df957
checking in
cjnolet Aug 18, 2021
fe28a15
Merge remote-tracking branch 'rapidsai/branch-21.10' into poc-020-ran…
cjnolet Aug 31, 2021
ff342a3
Merge remote-tracking branch 'rapidsai/branch-21.10' into poc-020-ran…
cjnolet Aug 31, 2021
66f17e0
Merging latest and building
cjnolet Aug 31, 2021
ab8a012
Supporting k up to 1024
cjnolet Aug 31, 2021
f63f911
Adding rbc functions for querying index using different set of inputs
cjnolet Sep 1, 2021
c1235fb
Fixing copyright years
cjnolet Sep 1, 2021
ed45d89
Renaming and cleanup
cjnolet Sep 1, 2021
ecb221f
Adding test file
cjnolet Sep 1, 2021
150dc6c
Updates
cjnolet Sep 1, 2021
481b21e
fixing ball cover common
cjnolet Sep 2, 2021
d7db5a0
Some small updates
cjnolet Sep 3, 2021
f5932b2
Review feedback
cjnolet Sep 17, 2021
fd54341
Fixing style
cjnolet Sep 17, 2021
3fdfec4
Adding more tests. Explicitly testing all supported metrics for current
cjnolet Sep 17, 2021
03e22bc
Fixing style of ball cover test file
cjnolet Sep 17, 2021
05dcb9f
Updating some docs
cjnolet Sep 20, 2021
f85d5bf
Fixing/adding some docs
cjnolet Sep 20, 2021
59c7488
Fixing small build issue
cjnolet Sep 20, 2021
c65a3c0
Updates based on review feedback
cjnolet Sep 20, 2021
5eda4ba
Fixing style ofr cpp tests
cjnolet Sep 20, 2021
bb07d44
Merge branch 'branch-21.10' into poc-020-random_ball_cover
cjnolet Sep 21, 2021
2850c79
Review feedback, fixing tests. Removing squared L2 distance (as it's not
cjnolet Sep 23, 2021
0de2e41
Adding cstdint to the top
cjnolet Sep 23, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions cpp/include/raft/cache/cache_util.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ namespace cache {
* @param [in] n the number of elements that need to be collected
* @param [out] out vectors collected from the cache, size [n_vec * n]
*/
template <typename math_t>
__global__ void get_vecs(const math_t *cache, int n_vec, const int *cache_idx,
int n, math_t *out) {
template <typename math_t, typename idx_t, typename int_t>
__global__ void get_vecs(const math_t *cache, int_t n_vec,
const idx_t *cache_idx, int_t n, math_t *out) {
int tid = threadIdx.x + blockIdx.x * blockDim.x;
int row = tid % n_vec; // row idx
if (tid < n_vec * n) {
Expand Down
10 changes: 5 additions & 5 deletions cpp/include/raft/sparse/linalg/degree.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ namespace linalg {
* @param nnz the size of the rows array
* @param results array to place results
*/
template <int TPB_X = 64>
__global__ void coo_degree_kernel(const int *rows, int nnz, int *results) {
template <int TPB_X = 64, typename T = int>
__global__ void coo_degree_kernel(const T *rows, int nnz, T *results) {
int row = (blockIdx.x * TPB_X) + threadIdx.x;
if (row < nnz) {
raft::myAtomicAdd(results + rows[row], 1);
atomicAdd(results + rows[row], (T)1);
}
}

Expand All @@ -59,8 +59,8 @@ __global__ void coo_degree_kernel(const int *rows, int nnz, int *results) {
* @param results: output result array
* @param stream: cuda stream to use
*/
template <int TPB_X = 64>
void coo_degree(const int *rows, int nnz, int *results, cudaStream_t stream) {
template <int TPB_X = 64, typename T = int>
void coo_degree(const T *rows, int nnz, T *results, cudaStream_t stream) {
dim3 grid_rc(raft::ceildiv(nnz, TPB_X), 1, 1);
dim3 blk_rc(TPB_X, 1, 1);

Expand Down
7 changes: 3 additions & 4 deletions cpp/include/raft/sparse/selection/knn.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@
#include <raft/sparse/coo.cuh>
#include <raft/sparse/csr.cuh>
#include <raft/sparse/distance/distance.cuh>
#include <raft/sparse/selection/selection.cuh>

#include <raft/spatial/knn/knn.hpp>

#include <raft/cudart_utils.h>
Expand Down Expand Up @@ -339,8 +337,9 @@ class sparse_knn_t {
if (metric == raft::distance::DistanceType::InnerProduct) ascending = false;

// kernel to slice first (min) k cols and copy into batched merge buffer
select_k(batch_dists, batch_indices, batch_rows, batch_cols, out_dists,
out_indices, ascending, n_neighbors, handle.get_stream());
raft::spatial::knn::select_k(batch_dists, batch_indices, batch_rows,
batch_cols, out_dists, out_indices, ascending,
n_neighbors, handle.get_stream());
}

void compute_distances(csr_batcher_t<value_idx, value_t> &idx_batcher,
Expand Down
3 changes: 2 additions & 1 deletion cpp/include/raft/sparse/selection/knn_graph.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,12 @@ void conv_indices(in_t *inds, out_t *out, size_t size, cudaStream_t stream) {
* @param[in] n number of observations (columns) in X
* @param[in] metric distance metric to use when constructing neighborhoods
* @param[out] out output edge list
* @param[out] out output edge list
* @param c
*/
template <typename value_idx = int, typename value_t = float>
void knn_graph(const handle_t &handle, const value_t *X, size_t m, size_t n,
distance::DistanceType metric,
raft::distance::DistanceType metric,
raft::sparse::COO<value_t, value_idx> &out, int c = 15) {
int k = build_k(m, c);

Expand Down
195 changes: 195 additions & 0 deletions cpp/include/raft/spatial/knn/ball_cover.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
/*
* Copyright (c) 2021, 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 <raft/linalg/distance_type.h>
#include <thrust/transform.h>
#include "ball_cover_common.h"
#include "detail/ball_cover.cuh"
#include "detail/ball_cover/common.cuh"

namespace raft {
namespace spatial {
namespace knn {

template <typename value_idx = int64_t, typename value_t,
cjnolet marked this conversation as resolved.
Show resolved Hide resolved
typename value_int = int>
inline void rbc_build_index(const raft::handle_t &handle,
cjnolet marked this conversation as resolved.
Show resolved Hide resolved
BallCoverIndex<value_idx, value_t> &index, int k) {
ASSERT(index.n == 2,
"Random ball cover currently only works in 2-dimensions");
if (index.metric == raft::distance::DistanceType::Haversine) {
detail::rbc_build_index(handle, index, k, detail::HaversineFunc());
} else if (index.metric == raft::distance::DistanceType::L2Expanded ||
index.metric == raft::distance::DistanceType::L2Unexpanded) {
detail::rbc_build_index(handle, index, k, detail::EuclideanFunc());
} else if (index.metric == raft::distance::DistanceType::L2SqrtExpanded ||
index.metric == raft::distance::DistanceType::L2SqrtUnexpanded) {
detail::rbc_build_index(handle, index, k, detail::EuclideanFunc());
}

index.set_index_trained();
}

* @tparam value_idx
* @tparam value_t
* @tparam value_int
* @tparam distance_func
* @param[in] handle raft handle for resource management
* @param[inout] index previously untrained index
* @param[in] k number neighbors to return
* @param[out] inds output indices
* @param[out] dists output distances
* @param[in] dfunc
* @param[in] perform_post_filtering turn off computing distances for
* additional landmarks outside of the closest k, if necessary.
* This can save a little computation time for approximate
* nearest neighbors and will generally return great recall.


/**
* 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 value_idx knn index type
* @tparam value_t knn distance type
* @tparam value_int type for integers, such as number of rows/cols
* @param handle raft handle for resource management
* @param index ball cover index which has not yet been built
* @param k number of nearest neighbors to find
* @param 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 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 <typename value_idx = int64_t, typename value_t,
typename value_int = int>
inline void rbc_all_knn_query(const raft::handle_t &handle,
BallCoverIndex<value_idx, value_t> &index, int k,
value_idx *inds, value_t *dists,
bool perform_post_filtering = true,
float weight = 1.0) {
ASSERT(index.n == 2,
"Random ball cover currently only works in 2-dimensions");
if (index.metric == raft::distance::DistanceType::Haversine) {
detail::rbc_all_knn_query(handle, index, k, inds, dists,
detail::HaversineFunc(),
perform_post_filtering, weight);
} else if (index.metric == raft::distance::DistanceType::L2Expanded ||
index.metric == raft::distance::DistanceType::L2Unexpanded) {
detail::rbc_all_knn_query(handle, index, k, inds, dists,
detail::EuclideanFunc(),
perform_post_filtering, weight);
} else if (index.metric == raft::distance::DistanceType::L2SqrtExpanded ||
index.metric ==
raft::distance::DistanceType::L2SqrtUnexpanded) {
detail::rbc_all_knn_query(handle, index, k, inds, dists,
detail::EuclideanFunc(),
perform_post_filtering, weight);

thrust::transform(handle.get_thrust_policy(), dists,
dists + (index.m * k), dists,
[] __device__(value_t in) { return sqrt(in); });
}

index.set_index_trained();
}

/**
* 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 value_idx index type
* @tparam value_t distances type
* @tparam value_int integer type for size info
* @param handle raft handle for resource management
* @param index ball cover index which has not yet been built
* @param k number of nearest neighbors to find
* @param query the
* @param 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 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 k
* @param inds
* @param dists
* @param n_samples
*/
template <typename value_idx = int64_t, typename value_t,
typename value_int = int>
inline void rbc_knn_query(const raft::handle_t &handle,
BallCoverIndex<value_idx, value_t> &index, int k,
const value_t *query, value_int n_query_pts,
value_idx *inds, value_t *dists,
bool perform_post_filtering = true,
float weight = 1.0) {
ASSERT(index.n == 2,
"Random ball cover currently only works in 2-dimensions");
if (index.metric == raft::distance::DistanceType::Haversine) {
detail::rbc_knn_query(handle, index, k, query, n_query_pts, inds, dists,
detail::HaversineFunc(), perform_post_filtering,
weight);
} else if (index.metric == raft::distance::DistanceType::L2Expanded ||
index.metric == raft::distance::DistanceType::L2Unexpanded) {
detail::rbc_knn_query(handle, index, k, query, n_query_pts, inds, dists,
detail::EuclideanFunc(), perform_post_filtering,
weight);
} else if (index.metric == raft::distance::DistanceType::L2SqrtExpanded ||
index.metric ==
raft::distance::DistanceType::L2SqrtUnexpanded) {
detail::rbc_knn_query(handle, index, k, query, n_query_pts, inds, dists,
detail::EuclideanFunc(), perform_post_filtering,
weight);

thrust::transform(handle.get_thrust_policy(), dists,
dists + (n_query_pts * k), dists,
[] __device__(value_t in) { return sqrt(in); });
}
}

// 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 BallCoverIndex and query against training data

} // namespace knn
} // namespace spatial
} // namespace raft
97 changes: 97 additions & 0 deletions cpp/include/raft/spatial/knn/ball_cover_common.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright (c) 2021, 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 <raft/linalg/distance_type.h>
#include <raft/handle.hpp>
#include <rmm/device_uvector.hpp>

namespace raft {
namespace spatial {
namespace knn {

/**
* 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 value_idx
* @tparam value_t
* @tparam value_int
*/
template <typename value_idx, typename value_t, typename value_int = int>
class BallCoverIndex {
public:
explicit BallCoverIndex(const raft::handle_t &handle_, const value_t *X_,
value_int m_, value_int n_,
raft::distance::DistanceType metric_)
: handle(handle_),
X(X_),
m(m_),
n(n_),
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(m_)),
R_indptr(sqrt(m_) + 1, handle.get_stream()),
R_1nn_cols(m_, handle.get_stream()),
R_1nn_dists(m_, handle.get_stream()),
R(sqrt(m_) * n_, handle.get_stream()),
R_radius(sqrt(m_), handle.get_stream()),
index_trained(false) {}

value_idx *get_R_indptr() { return R_indptr.data(); }
value_idx *get_R_1nn_cols() { return R_1nn_cols.data(); }
value_t *get_R_1nn_dists() { return R_1nn_dists.data(); }
value_t *get_R_radius() { return R_radius.data(); }
value_t *get_R() { return R.data(); }
const value_t *get_X() { return X; }

bool is_index_trained() const { return index_trained; };

// This should only be set by internal functions
void set_index_trained() { index_trained = true; }

const raft::handle_t &handle;

const value_int m;
const value_int n;
const int n_landmarks;
cjnolet marked this conversation as resolved.
Show resolved Hide resolved

const value_t *X;

raft::distance::DistanceType metric;

private:
// CSR storing the neighborhoods for each data point
rmm::device_uvector<value_idx> R_indptr;
rmm::device_uvector<value_idx> R_1nn_cols;
rmm::device_uvector<value_t> R_1nn_dists;

rmm::device_uvector<value_t> R_radius;

rmm::device_uvector<value_t> R;

protected:
bool index_trained;
};
} // namespace knn
} // namespace spatial
} // namespace raft
2 changes: 2 additions & 0 deletions cpp/include/raft/spatial/knn/detail/ann_quantized_faiss.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@
#pragma once

#include "../ann_common.h"
#include "knn_brute_force_faiss.cuh"

#include "common_faiss.h"
#include "processing.hpp"

#include <raft/cudart_utils.h>
#include <raft/cuda_utils.cuh>
#include "processing.hpp"

#include <label/classlabels.cuh>
#include <raft/distance/distance.cuh>
Expand Down
Loading