Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 4 additions & 1 deletion cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,9 @@ if(NOT BUILD_CPU_ONLY)
"$<$<COMPILE_LANGUAGE:CUDA>:${CUVS_CUDA_FLAGS}>"
)
target_compile_features(jit_lto_kernel_usage_requirements INTERFACE cuda_std_20)
target_link_libraries(jit_lto_kernel_usage_requirements INTERFACE rmm::rmm raft::raft CCCL::CCCL)
target_link_libraries(
jit_lto_kernel_usage_requirements INTERFACE rmm::rmm raft::raft CCCL::CCCL cuco::cuco
)

block(PROPAGATE jit_lto_files)
set(jit_lto_files)
Expand Down Expand Up @@ -1326,6 +1328,7 @@ if(NOT BUILD_CPU_ONLY)
src/cluster/single_linkage_float.cu
src/cluster/spectral.cu
src/core/bitset.cu
src/core/bloom_filter.cu
src/core/omp_wrapper.cpp
src/util/file_io.cpp
src/util/host_memory.cpp
Expand Down
99 changes: 99 additions & 0 deletions cpp/include/cuvs/core/bloom_filter.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

#pragma once

#include <cuvs/core/export.hpp>
#include <raft/core/device_mdarray.hpp>
#include <raft/core/resources.hpp>

#include <cstddef>
#include <cstdint>
#include <memory>

namespace CUVS_EXPORT cuvs {
namespace core {

/**
* @brief cuVS-owned Bloom filter wrapper with opaque implementation.
*
* This class intentionally hides cuCollections types from the cuVS public API.
* The wrapper supports the expected bulk host APIs used by ANN workflows.
*/
class CUVS_EXPORT bloom_filter {
private:
struct impl;

public:
using key_type = std::uint32_t;

/**
* @brief Construct a Bloom filter with user-facing quality knobs.
*
* @p dataset_rows is the number of rows in the indexed dataset. The filter uses it with
* @p filtering_rate to estimate the number of inserted valid ids and compute a target filter
* size that satisfies the requested false-positive rate.
*
* The primary tuning knobs are:
* - @p filtering_rate: expected fraction of dataset rows that will be inserted as valid ids.
* - @p target_false_positive_rate: desired Bloom filter false-positive probability.
*
* Sizing math used internally:
* - `expected_insertions = ceil(dataset_rows * filtering_rate)`
* - The default policy uses 256-bit blocks split into eight 32-bit words and sets one bit in each
* word per inserted key.
* - For each candidate block count, the expected false-positive rate accounts for the binomial
* distribution of inserted keys across blocks and the fixed eight-bit fingerprint.
* - The smallest block count whose expected false-positive rate meets
* @p target_false_positive_rate is selected.
*
* Practical knob behavior:
* - Lower @p target_false_positive_rate -> larger filter, fewer false positives, typically higher
* filtered-search recall.
* - Higher @p filtering_rate -> larger filter for the same target false-positive rate.
*/
bloom_filter(raft::resources const& res,
std::size_t dataset_rows,
float filtering_rate = 1.0f,
float target_false_positive_rate = 0.01f);
~bloom_filter();

bloom_filter(bloom_filter const&) = delete;
bloom_filter& operator=(bloom_filter const&) = delete;
bloom_filter(bloom_filter&&) noexcept;
bloom_filter& operator=(bloom_filter&&) noexcept;

void clear(raft::resources const& res);
void clear_async(raft::resources const& res);

void add(raft::resources const& res, raft::device_vector_view<const key_type, int64_t> keys);
void add_async(raft::resources const& res,
raft::device_vector_view<const key_type, int64_t> keys);

void contains(raft::resources const& res,
raft::device_vector_view<const key_type, int64_t> keys,
raft::device_vector_view<std::uint8_t, int64_t> output) const;
void contains_async(raft::resources const& res,
raft::device_vector_view<const key_type, int64_t> keys,
raft::device_vector_view<std::uint8_t, int64_t> output) const;

[[nodiscard]] std::size_t num_blocks() const noexcept;

/**
* @brief Return the estimated fraction of dataset rows rejected by this filter.
*
* The estimate is derived at construction from the configured valid-row fraction and the
* expected false-positive rate of the selected filter geometry. It performs no device work.
*/
[[nodiscard]] float estimate_filtering_rate() const noexcept;

private:
friend impl const& get_bloom_filter_impl(bloom_filter const& filter) noexcept;

std::unique_ptr<impl> impl_;
};

} // namespace core
} // namespace CUVS_EXPORT cuvs
3 changes: 2 additions & 1 deletion cpp/include/cuvs/detail/jit_lto/common_fragments.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION.
* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

Expand All @@ -14,6 +14,7 @@ struct tag_i8 {};
struct tag_u8 {};
struct tag_filter_none {};
struct tag_filter_bitset {};
struct tag_filter_bloom_filter {};
struct tag_filter_udf {};

struct tag_bitset_u32 {};
Expand Down
35 changes: 33 additions & 2 deletions cpp/include/cuvs/neighbors/common.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION.
* SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

Expand Down Expand Up @@ -33,6 +33,9 @@
#endif

namespace CUVS_EXPORT cuvs {
namespace core {
class bloom_filter;
}
namespace neighbors {
/**
* @addtogroup cagra_cpp_index_params
Expand Down Expand Up @@ -497,7 +500,7 @@ namespace filtering {
* @{
*/

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

struct base_filter {
~base_filter() = default;
Expand Down Expand Up @@ -617,6 +620,34 @@ struct bitset_filter : public base_filter {
void to_csr(raft::resources const& handle, csr_matrix_t& csr);
};

/**
* @brief Filter CAGRA candidates with a global @c cuvs::core::bloom_filter over the index.
*
* Build the filter once on the host with bulk @c add() over the allowed dataset row ids and pass
* the owning @c cuvs::core::bloom_filter to this wrapper. CAGRA internals build/cache the device
* payload, similar to @ref bitset_filter, and the linked JIT-LTO fragment probes the same filter
* for every query and candidate with probabilistic membership tests.
*
* Bloom filters have no false negatives: if a row was inserted, @c contains returns @c true. False
* positives are possible, so highly selective predicates may still need a bitset or UDF for exact
* filtering.
*
* This adapter is non-owning. The referenced @c cuvs::core::bloom_filter must outlive the adapter
* and any searches that use it, and must not be moved or mutated concurrently with a search.
*/
struct bloom_filter : public base_filter {
void* filter_data{nullptr};

bloom_filter() = default;

explicit bloom_filter(const cuvs::core::bloom_filter& bloom_filter)
: filter_data(const_cast<cuvs::core::bloom_filter*>(&bloom_filter))
{
}

FilterType get_filter_type() const override { return FilterType::Bloom; }
};

/**
* @brief JIT-LTO user-defined filter predicate.
*
Expand Down
Loading
Loading