Skip to content

Roaring Bitmap Filter in CAGRA - #2446

Merged
rapids-bot[bot] merged 21 commits into
NVIDIA:release/26.10from
divyegala:roaring-filter
Sep 21, 2026
Merged

rapids-bot[bot] merged 21 commits into
NVIDIA:release/26.10from
divyegala:roaring-filter

Conversation

@divyegala

@divyegala divyegala commented Aug 15, 2026 •

Copy link
Copy Markdown
Contributor

This PR uses cuco's Roaring Bitmap filter implementation to work with CAGRA search. Currently, it only supports single allowlist construction.

Benchmarks on GIST 1M, measuring single-batch search latency + filter construction time:

Allowed Cardinality Bitset total (ms) CSR UDF total (ms) Roaring total (ms)
0.1% 1,000 5.106 5.275 6.042
1% 10,000 1.209 1.553 1.545
10% 100,000 0.814 4.015 1.297
50% 500,000 1.199 17.801 2.146
90% 900,000 1.512 32.017 2.384
100% 1,000,000 1.530 35.768 2.552

Filter sizes:

Allowed Cardinality Bitset CSR UDF Roaring
0.1% 1,000 122.07 KiB 3.92 KiB 2.22 KiB
1% 10,000 122.07 KiB 39.08 KiB 19.80 KiB
10% 100,000 122.07 KiB 390.64 KiB 123.58 KiB
50% 500,000 122.07 KiB 1,953.14 KiB 128.27 KiB
90% 900,000 122.07 KiB 3,515.64 KiB 128.27 KiB
100% 1,000,000 122.07 KiB 3,906.27 KiB 128.27 KiB

@copy-pr-bot

copy-pr-bot Bot commented Aug 15, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@copy-pr-bot

copy-pr-bot Bot commented Sep 2, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@divyegala
divyegala marked this pull request as ready for review September 2, 2026 19:49
@divyegala
divyegala requested review from a team as code owners September 2, 2026 19:49
@divyegala divyegala added feature request New feature or request non-breaking Introduces a non-breaking change labels Sep 2, 2026
@divyegala

Copy link
Copy Markdown
Contributor Author

/ok to test 1529d8f

@dantegd dantegd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we explicitly limit Roaring filters to direct cagra::search for now? I don’t think the query mapping works cleanly with dynamic batching because CAGRA may receive either max_batch_size or the actual batch size. Tiered search also passes the same filter to CAGRA and brute-force partitions with different row domains, and brute force doesn’t support Roaring. Since cagra::merge already rejects this filter type, would it make sense to reject or document these paths too, unless they’re intended to be supported? What do you think?

Comment thread cpp/cmake/patches/cuco_override.json Outdated
tenant_views.push_back(allowlist.view());
}
cuvs::neighbors::filtering::roaring_filter roaring_filter(res, tenant_views);
auto roaring_result = search(roaring_filter, 2.0f / 3.0f);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we clarify the stream semantics here? Construction may return while serialization and the device-reference copy are still queued. Using the result on the same stream is safe, but another stream needs an explicit dependency, like the event handoff in the test. The current comment explains how long the input must remain valid, but not when the returned view is safe to use.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We commit to stream ordered synchronization guarantees on raft::resources. Is that not already true here? If the host returns execution, it is safe to use.

Comment thread cpp/tests/neighbors/roaring_allowlist.cu
auto& sample_filter =
dynamic_cast<const cuvs::neighbors::filtering::roaring_filter&>(sample_filter_ref);
RAFT_EXPECTS(sample_filter.valid(), "roaring_filter must be initialized before search.");
RAFT_EXPECTS(sample_filter.num_queries() == static_cast<std::size_t>(queries.extent(0)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we expect Roaring filters to work through dynamic batching as well? Dynamic batching may call CAGRA with either max_batch_size or the actual batch size, so this equality can fail depending on the dispatch mode. If that path isn’t supported yet, should we document it or reject the filter earlier?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's reject it

{
auto minimum_cardinality = dataset_rows;
for (auto const& allowlist : allowlists) {
minimum_cardinality = std::min(minimum_cardinality, allowlist.cardinality());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is using the minimum cardinality intentional here? One very small or empty allowlist makes the batch-wide filtering rate approach 0.999, which can substantially increase the MULTI_CTA work for every query in the batch. Would it be worth adding coverage for mixed-cardinality batches or documenting this tradeoff?

@divyegala divyegala Sep 10, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is documented, I ran into this problem myself. The right way to do this for users it to set search_params::filtering_rate so that it is a CAGRA search specific hint, and users can tradeoff between latency and recall.

@divyegala
divyegala changed the base branch from main to release/26.10 September 10, 2026 17:50
Comment thread cpp/cmake/patches/cuco_override.json Outdated
namespace core {

/**
* @brief Non-owning device view of one immutable Roaring allowlist.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a core vocab type. Do we want to consider putting this in raft at some point?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea of putting it in RAFT as we are introducing several new filters as core vocabulary types that I consider are reusable.

Comment thread cpp/include/cuvs/core/roaring_allowlist.hpp
Comment thread cpp/include/cuvs/core/roaring_allowlist.hpp
Comment thread cpp/include/cuvs/neighbors/common.hpp Outdated
* @see cuvs::core::roaring_allowlist
* @see https://github.com/RoaringBitmap/RoaringFormatSpec
*/
struct roaring_filter : public base_filter {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any benefits to calling this a roaring_bitmap_filter just for clarity? I'm kind of neutral to both- just not sure if one always implies the other.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, let's do roaring_bitmap_filter 👍

@divyegala
divyegala requested a review from a team as a code owner September 16, 2026 19:21
@divyegala

Copy link
Copy Markdown
Contributor Author

/ok to test 35f50f5

@copy-pr-bot

copy-pr-bot Bot commented Sep 17, 2026

Copy link
Copy Markdown

/ok to test 35f50f5

@divyegala, there was an error processing your request: E2

See the following link for more information: https://docs.gha-runners.nvidia.com/cpr/e/2/

@divyegala

Copy link
Copy Markdown
Contributor Author

/ok to test 3025b9a

@dantegd dantegd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some very minor things, otherwise it looks great!

Comment thread cpp/src/core/roaring_allowlist.cu Outdated
Comment thread cpp/src/neighbors/detail/cagra/search_multi_cta_inst.cu.in Outdated
auto empty_allowlist = cuvs::core::roaring_allowlist::from_ids(
res, n_rows, raft::make_host_vector_view<const std::uint32_t, std::int64_t>(nullptr, 0));
roaring_bitmap_filter.set_allowlist(res, 1, empty_allowlist.view());
auto empty_result = search(roaring_bitmap_filter, 0.999f);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we let this search use the default filtering rate? The filter’s automatic estimate should already be 0.999f after installing the empty allowlist, so this would exercise the new params.filtering_rate < 0 CAGRA dispatch path without changing the test’s intended behavior. The owner test covers the estimator itself, but not this wiring. What do you think?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea!

Comment thread cpp/include/cuvs/neighbors/common.hpp
@divyegala

Copy link
Copy Markdown
Contributor Author

/ok to test

@copy-pr-bot

copy-pr-bot Bot commented Sep 17, 2026

Copy link
Copy Markdown

/ok to test

@divyegala, there was an error processing your request: E1

See the following link for more information: https://docs.gha-runners.nvidia.com/cpr/e/1/

@divyegala

Copy link
Copy Markdown
Contributor Author

/ok to test 82d706c

@dantegd

dantegd commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

/merge

@rapids-bot
rapids-bot Bot merged commit 73fa5f7 into NVIDIA:release/26.10 Sep 21, 2026
285 of 290 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature request New feature or request non-breaking Introduces a non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants