Skip to content

Commit 6ed6909

Browse files
committed
Merge branch 'main' of https://github.com/rapidsai/cuvs into cuvs-bench-fixup
Signed-off-by: James Bourbeau <jbourbeau@nvidia.com>
2 parents b7b4c1a + 1d128ca commit 6ed6909

37 files changed

Lines changed: 1347 additions & 97 deletions

‎c/include/cuvs/neighbors/cagra.h‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION.
2+
* SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

@@ -299,6 +299,27 @@ CUVS_EXPORT cuvsError_t cuvsCagraIndexParamsFromHnswParams(cuvsCagraIndexParams_
299299
enum cuvsCagraHnswHeuristicType heuristic,
300300
cuvsDistanceType metric);
301301

302+
/**
303+
* @brief Create CAGRA index parameters heuristically tuned for a dataset
304+
*
305+
* This factory function selects the graph build algorithm and its parameters based on the shape of
306+
* the dataset.
307+
*
308+
* @param[out] params The CAGRA index params to populate
309+
* @param[in] n_rows Number of rows in the dataset
310+
* @param[in] dim Number of dimensions in the dataset
311+
* @param[in] graph_degree Degree of the output graph
312+
* @param[in] metric Distance metric to use
313+
* @param[in] build_quality Higher values increase build quality (and cost) up to a point
314+
* @return cuvsError_t
315+
*/
316+
CUVS_EXPORT cuvsError_t cuvsCagraIndexParamsFromDataset(cuvsCagraIndexParams_t params,
317+
int64_t n_rows,
318+
int64_t dim,
319+
size_t graph_degree,
320+
cuvsDistanceType metric,
321+
size_t build_quality);
322+
302323
/**
303324
* @}
304325
*/

‎c/src/neighbors/cagra.cpp‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION.
2+
* SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

@@ -834,6 +834,22 @@ extern "C" cuvsError_t cuvsCagraIndexParamsFromHnswParams(cuvsCagraIndexParams_t
834834
});
835835
}
836836

837+
extern "C" cuvsError_t cuvsCagraIndexParamsFromDataset(cuvsCagraIndexParams_t params,
838+
int64_t n_rows,
839+
int64_t dim,
840+
size_t graph_degree,
841+
cuvsDistanceType metric,
842+
size_t build_quality)
843+
{
844+
return cuvs::core::translate_exceptions([=] {
845+
auto cpp_metric = static_cast<cuvs::distance::DistanceType>((int)metric);
846+
auto cpp_params = cuvs::neighbors::cagra::index_params::from_dataset(
847+
raft::matrix_extent<int64_t>(n_rows, dim), graph_degree, cpp_metric, build_quality);
848+
849+
_populate_cagra_index_params_from_cpp(params, cpp_params);
850+
});
851+
}
852+
837853
extern "C" cuvsError_t cuvsCagraExtendParamsCreate(cuvsCagraExtendParams_t* params)
838854
{
839855
return cuvs::core::translate_exceptions(

‎cpp/CMakeLists.txt‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,9 @@ if(NOT BUILD_CPU_ONLY)
365365
"$<$<COMPILE_LANGUAGE:CUDA>:${CUVS_CUDA_FLAGS}>"
366366
)
367367
target_compile_features(jit_lto_kernel_usage_requirements INTERFACE cuda_std_20)
368-
target_link_libraries(jit_lto_kernel_usage_requirements INTERFACE rmm::rmm raft::raft CCCL::CCCL)
368+
target_link_libraries(
369+
jit_lto_kernel_usage_requirements INTERFACE rmm::rmm raft::raft CCCL::CCCL cuco::cuco
370+
)
369371

370372
block(PROPAGATE jit_lto_files)
371373
set(jit_lto_files)
@@ -1326,6 +1328,7 @@ if(NOT BUILD_CPU_ONLY)
13261328
src/cluster/single_linkage_float.cu
13271329
src/cluster/spectral.cu
13281330
src/core/bitset.cu
1331+
src/core/bloom_filter.cu
13291332
src/core/omp_wrapper.cpp
13301333
src/util/file_io.cpp
13311334
src/util/host_memory.cpp
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
#pragma once
7+
8+
#include <cuvs/core/export.hpp>
9+
#include <raft/core/device_mdarray.hpp>
10+
#include <raft/core/resources.hpp>
11+
12+
#include <cstddef>
13+
#include <cstdint>
14+
#include <memory>
15+
16+
namespace CUVS_EXPORT cuvs {
17+
namespace core {
18+
19+
/**
20+
* @brief cuVS-owned Bloom filter wrapper with opaque implementation.
21+
*
22+
* This class intentionally hides cuCollections types from the cuVS public API.
23+
* The wrapper supports the expected bulk host APIs used by ANN workflows.
24+
*/
25+
class CUVS_EXPORT bloom_filter {
26+
private:
27+
struct impl;
28+
29+
public:
30+
using key_type = std::uint32_t;
31+
32+
/**
33+
* @brief Construct a Bloom filter with user-facing quality knobs.
34+
*
35+
* @p dataset_rows is the number of rows in the indexed dataset. The filter uses it with
36+
* @p filtering_rate to estimate the number of inserted valid ids and compute a target filter
37+
* size that satisfies the requested false-positive rate.
38+
*
39+
* The primary tuning knobs are:
40+
* - @p filtering_rate: expected fraction of dataset rows that will be inserted as valid ids.
41+
* - @p target_false_positive_rate: desired Bloom filter false-positive probability.
42+
*
43+
* Sizing math used internally:
44+
* - `expected_insertions = ceil(dataset_rows * filtering_rate)`
45+
* - The default policy uses 256-bit blocks split into eight 32-bit words and sets one bit in each
46+
* word per inserted key.
47+
* - For each candidate block count, the expected false-positive rate accounts for the binomial
48+
* distribution of inserted keys across blocks and the fixed eight-bit fingerprint.
49+
* - The smallest block count whose expected false-positive rate meets
50+
* @p target_false_positive_rate is selected.
51+
*
52+
* Practical knob behavior:
53+
* - Lower @p target_false_positive_rate -> larger filter, fewer false positives, typically higher
54+
* filtered-search recall.
55+
* - Higher @p filtering_rate -> larger filter for the same target false-positive rate.
56+
*/
57+
bloom_filter(raft::resources const& res,
58+
std::size_t dataset_rows,
59+
float filtering_rate = 1.0f,
60+
float target_false_positive_rate = 0.01f);
61+
~bloom_filter();
62+
63+
bloom_filter(bloom_filter const&) = delete;
64+
bloom_filter& operator=(bloom_filter const&) = delete;
65+
bloom_filter(bloom_filter&&) noexcept;
66+
bloom_filter& operator=(bloom_filter&&) noexcept;
67+
68+
void clear(raft::resources const& res);
69+
void clear_async(raft::resources const& res);
70+
71+
void add(raft::resources const& res, raft::device_vector_view<const key_type, int64_t> keys);
72+
void add_async(raft::resources const& res,
73+
raft::device_vector_view<const key_type, int64_t> keys);
74+
75+
void contains(raft::resources const& res,
76+
raft::device_vector_view<const key_type, int64_t> keys,
77+
raft::device_vector_view<std::uint8_t, int64_t> output) const;
78+
void contains_async(raft::resources const& res,
79+
raft::device_vector_view<const key_type, int64_t> keys,
80+
raft::device_vector_view<std::uint8_t, int64_t> output) const;
81+
82+
[[nodiscard]] std::size_t num_blocks() const noexcept;
83+
84+
/**
85+
* @brief Return the estimated fraction of dataset rows rejected by this filter.
86+
*
87+
* The estimate is derived at construction from the configured valid-row fraction and the
88+
* expected false-positive rate of the selected filter geometry. It performs no device work.
89+
*/
90+
[[nodiscard]] float estimate_filtering_rate() const noexcept;
91+
92+
private:
93+
friend impl const& get_bloom_filter_impl(bloom_filter const& filter) noexcept;
94+
95+
std::unique_ptr<impl> impl_;
96+
};
97+
98+
} // namespace core
99+
} // namespace CUVS_EXPORT cuvs

‎cpp/include/cuvs/detail/jit_lto/common_fragments.hpp‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION.
2+
* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

@@ -14,6 +14,7 @@ struct tag_i8 {};
1414
struct tag_u8 {};
1515
struct tag_filter_none {};
1616
struct tag_filter_bitset {};
17+
struct tag_filter_bloom_filter {};
1718
struct tag_filter_udf {};
1819

1920
struct tag_bitset_u32 {};

‎cpp/include/cuvs/neighbors/cagra.hpp‎

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,12 @@ namespace graph_build_params = cuvs::neighbors::graph_build_params;
112112
* @{
113113
*/
114114

115+
using graph_build_params_t = std::variant<std::monostate,
116+
graph_build_params::ivf_pq_params,
117+
graph_build_params::nn_descent_params,
118+
graph_build_params::ace_params,
119+
graph_build_params::iterative_search_params>;
120+
115121
/**
116122
* @brief A strategy for selecting the graph build parameters based on similar HNSW index
117123
* parameters.
@@ -181,12 +187,7 @@ struct index_params : cuvs::neighbors::index_params {
181187
* cagra::graph_build_params::iterative_search_params();
182188
* @endcode
183189
*/
184-
std::variant<std::monostate,
185-
graph_build_params::ivf_pq_params,
186-
graph_build_params::nn_descent_params,
187-
graph_build_params::ace_params,
188-
graph_build_params::iterative_search_params>
189-
graph_build_params;
190+
graph_build_params_t graph_build_params;
190191
/**
191192
* Whether to use MST optimization to guarantee graph connectivity.
192193
*/
@@ -223,6 +224,55 @@ struct index_params : cuvs::neighbors::index_params {
223224
*/
224225
bool attach_dataset_on_build = true;
225226

227+
/**
228+
* @brief Select the graph build algorithm and its parameters for a dataset.
229+
*
230+
* This is the main CAGRA build heuristic: it chooses between NN-descent and IVF-PQ based on the
231+
* dataset size and tunes their parameters based on the target intermediate graph degree and the
232+
* requested build quality. It returns the `graph_build_params` variant only; the caller is
233+
* responsible for setting `graph_degree` / `intermediate_graph_degree`.
234+
*
235+
* @param dataset The shape of the input dataset
236+
* @param intermediate_graph_degree The intermediate (kNN) graph degree the build should target.
237+
* Note: the intermediate graph degree must be not smaller than the output graph degree; a good
238+
* practice is to have it 1.5x to 2x of the desired graph_degree and a multiple of 32.
239+
* @param metric The distance metric to search
240+
* @param build_quality Higher values increase the build quality (and cost) up to a point.
241+
* Any value is valid, but values below 20 are the most practical (default = 7).
242+
*/
243+
static graph_build_params_t graph_build_heuristic(
244+
raft::matrix_extent<int64_t> dataset,
245+
size_t intermediate_graph_degree,
246+
cuvs::distance::DistanceType metric = cuvs::distance::DistanceType::L2Expanded,
247+
size_t build_quality = 7);
248+
249+
/**
250+
* @brief Create CAGRA index parameters heuristically tuned for a dataset.
251+
*
252+
* Returns default CAGRA `index_params` with `graph_build_params` selected by
253+
* `graph_build_heuristic` for the given dataset.
254+
*
255+
* @param dataset The shape of the input dataset
256+
* @param graph_degree Degree of the output graph.
257+
* @param metric The distance metric to search
258+
* @param build_quality Higher values increase the build quality (and cost) up to a point.
259+
* Any value is valid, but values below 20 are the most practical (default = 7).
260+
*
261+
* Usage example:
262+
* @code{.cpp}
263+
* using namespace cuvs::neighbors;
264+
* raft::resources res;
265+
* auto dataset = raft::make_device_matrix<float, int64_t>(res, N, D);
266+
* auto cagra_params = cagra::index_params::from_dataset(dataset.extents());
267+
* auto cagra_index = cagra::build(res, cagra_params, dataset);
268+
* @endcode
269+
*/
270+
static cagra::index_params from_dataset(
271+
raft::matrix_extent<int64_t> dataset,
272+
size_t graph_degree = 64,
273+
cuvs::distance::DistanceType metric = cuvs::distance::DistanceType::L2Expanded,
274+
size_t build_quality = 7);
275+
226276
/**
227277
* @brief Create a CAGRA index parameters compatible with HNSW index
228278
*

‎cpp/include/cuvs/neighbors/common.hpp‎

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION.
2+
* SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

@@ -33,6 +33,9 @@
3333
#endif
3434

3535
namespace CUVS_EXPORT cuvs {
36+
namespace core {
37+
class bloom_filter;
38+
}
3639
namespace neighbors {
3740
/**
3841
* @addtogroup cagra_cpp_index_params
@@ -497,7 +500,7 @@ namespace filtering {
497500
* @{
498501
*/
499502

500-
enum class FilterType { None, Bitmap, Bitset, UDF };
503+
enum class FilterType : int { None = 0, Bitmap = 1, Bitset = 2, Bloom = 3, UDF = 100 };
501504

502505
struct base_filter {
503506
~base_filter() = default;
@@ -617,6 +620,34 @@ struct bitset_filter : public base_filter {
617620
void to_csr(raft::resources const& handle, csr_matrix_t& csr);
618621
};
619622

623+
/**
624+
* @brief Filter CAGRA candidates with a global @c cuvs::core::bloom_filter over the index.
625+
*
626+
* Build the filter once on the host with bulk @c add() over the allowed dataset row ids and pass
627+
* the owning @c cuvs::core::bloom_filter to this wrapper. CAGRA internals build/cache the device
628+
* payload, similar to @ref bitset_filter, and the linked JIT-LTO fragment probes the same filter
629+
* for every query and candidate with probabilistic membership tests.
630+
*
631+
* Bloom filters have no false negatives: if a row was inserted, @c contains returns @c true. False
632+
* positives are possible, so highly selective predicates may still need a bitset or UDF for exact
633+
* filtering.
634+
*
635+
* This adapter is non-owning. The referenced @c cuvs::core::bloom_filter must outlive the adapter
636+
* and any searches that use it, and must not be moved or mutated concurrently with a search.
637+
*/
638+
struct bloom_filter : public base_filter {
639+
void* filter_data{nullptr};
640+
641+
bloom_filter() = default;
642+
643+
explicit bloom_filter(const cuvs::core::bloom_filter& bloom_filter)
644+
: filter_data(const_cast<cuvs::core::bloom_filter*>(&bloom_filter))
645+
{
646+
}
647+
648+
FilterType get_filter_type() const override { return FilterType::Bloom; }
649+
};
650+
620651
/**
621652
* @brief JIT-LTO user-defined filter predicate.
622653
*

0 commit comments

Comments
 (0)