Skip to content

Iterative CAGRA-Q - #1810

Open
irina-resh-nvda wants to merge 53 commits into
NVIDIA:release/26.10from
irina-resh-nvda:iterative_cagra_q
Open

Iterative CAGRA-Q#1810
irina-resh-nvda wants to merge 53 commits into
NVIDIA:release/26.10from
irina-resh-nvda:iterative_cagra_q

Conversation

@irina-resh-nvda

@irina-resh-nvda irina-resh-nvda commented Feb 16, 2026

Copy link
Copy Markdown
Contributor

Build CAGRA on PQ datasets with Iterative CAGRA-Q

Iterative cagra graph construction using CAGRA-Q search.

This PR improves the iterative CAGRA build method by enabling PQ compression: the dataset is compressed before the iterative search starts, and CAGRA-Q is used to iteratively update the KNN graph.

This is the first time we are introducing building CAGRA on (PQ) quantized datasets directly.

@copy-pr-bot

copy-pr-bot Bot commented Feb 16, 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.

… search

- Configurable growth-phase in-build search params (itopk_size, search_width,
  max_iterations) and internal/smem dtype; itopk auto-forced on the final
  full-size iteration.
- Decouple compression params used during iterative construction from the
  target index compression.
- Add shuffle_dataset option; fix out-of-bounds access from the in-place raft
  gather by switching to an out-of-place gather.
…around)

The shuffle_dataset path used an out-of-place gather into a temporary buffer to
work around an illegal memory access in raft's in-place gather overload when
n_rows * row_len exceeded 2^31 (32-bit index overflow).

That bug is now fixed upstream in raft (NVIDIA/raft#3059, closes #3055), which
the cuvs raft pin now includes. Revert to the in-place gather to drop the extra
full-size temporary allocation and copy.
@irina-resh-nvda
irina-resh-nvda marked this pull request as ready for review July 15, 2026 08:48
@irina-resh-nvda
irina-resh-nvda requested review from a team as code owners July 15, 2026 08:48
@aamijar aamijar changed the title Iterative cagra q Iterative graph build using CAGRA-Q search Jul 22, 2026
@aamijar aamijar changed the title Iterative graph build using CAGRA-Q search Iterative CAGRA-Q Jul 22, 2026

@achirkin achirkin 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.

Please undo all changes to bench/ann/src/common. Let's keep CAGRA-Q compressed dataset support as before - by letting the compressed dataset be a part of index file / folder.
We should leave a proper refactoring of the benchmark harness for discussion and implementation in follow up work (if and how we want to pass compressed datasets as first-class input to the benchmarks).

@irina-resh-nvda

irina-resh-nvda commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

Please undo all changes to bench/ann/src/common. Let's keep CAGRA-Q compressed dataset support as before - by letting the compressed dataset be a part of index file / folder. We should leave a proper refactoring of the benchmark harness for discussion and implementation in follow up work (if and how we want to pass compressed datasets as first-class input to the benchmarks).

I created a standalone PR for the vpq dataset serialisation. We can look through it and merge faster that this PR, and start working on the cuvsbench refactoring sooner.

I need the refactoring to rerun the benchmarks (the older performance numbers are obsolete now). I won't be comfortable merging this PR into main before the new benchmarks land

@aamijar aamijar self-assigned this Aug 20, 2026

@tarang-jain tarang-jain 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.

Thanks for the great work!

auto batch_queries =
vpq_queries != nullptr || dev_query_view.extent(1) != query_dim
? raft::make_device_matrix<T, int64_t>(res, static_cast<int64_t>(max_chunk_size), query_dim)
: raft::make_device_matrix<T, int64_t>(res, 0, 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.

Looks like CAGRA search will pad these queries to the aligned dim if they are not aligned right? So why not make the batch itself padded here -- will avoid an extra D2D copy during cagra search (it might need the reconstruct kernel to jump the padded dim).

@tarang-jain

Copy link
Copy Markdown
Contributor

What do you think about putting the ANN bench changes in a separate PR (even though you want to benchmark this e2e)? That can be a follow up to this PR.

@tfeher tfeher 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.

Thanks @irina-resh-nvda and @aamijar for this PR! My main questions below are about naming of params / APIs related to PQ compressed input. Otherwise, the PR looks great overall.

* @brief Compress a dense dataset into a device VPQ dataset.
*/
CUVS_EXPORT cuvsError_t cuvsDatasetMakeVpq(cuvsResources_t res,
cuvsCagraCompressionParams_t params,

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 function compresses the dataset into VPQ format. The user can decide later to use that for CAGRA, or for other algorithms. It would make sense to rename the params struct to something like cuvsPqParams.

Comment thread c/include/cuvs/core/dataset.h Outdated
Comment on lines +441 to +448
if (build_search_conf.contains("persistent")) {
arg.persistent = build_search_conf.at("persistent");
}
if (build_search_conf.contains("persistent_lifetime")) {
arg.persistent_lifetime = build_search_conf.at("persistent_lifetime");
}
if (build_search_conf.contains("persistent_device_usage")) {
arg.persistent_device_usage = build_search_conf.at("persistent_device_usage");

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.

I assume build_search is set of parameters used only during build. Is persistent mode used during build? I don't expect that, since with large batch search operations we have large throughput without the persistent kernel. My concern in exposing this as tunable build parameter is that it might motivate users to add these options to their build parameter scan, which is not productive.

If the goal is completeness in parameter handling, then shouldn't we use the same function that parses search params?

if (out_dataset == nullptr) {
cuvs::neighbors::detail::skip_dense_dataset<T, int64_t>(res, is);
// Dropping the rows leaves a searchable index for a dense view, whose dataset can be
// reattached from the caller's own copy, but not for a VPQ one: the compressed rows exist

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.

Why do we handle the pq compressed dataset differently? Can't the user reattach a pq compressed dataset to an index after the graph was loaded from file?

final_graph_size = static_cast<uint64_t>(dataset.n_rows());
vpq_dataset = &dataset.dset();
} else {
auto search_dataset = ensure_device_padded_for_iterative_search<T>(res, dataset, padded_own);

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.

I believe that using the new DataSet API, the guideline is that we do not make such copies for the user, but expect them to provide already padded data.

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.

so we basically error out if its not padded, right?

logical_dim);
auto idx = cuvs::neighbors::cagra::device_padded_index<T, IdxT>(res, params.metric);
idx.update_device_dataset_same_layout(res, sub_padded);
if (use_device_graph) {

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.

Expect of the first iteration, we always use dev_graph, right? Could you simply copy the initial cagra_graph to the device so that we can simplify the code here?

Comment thread cpp/src/neighbors/detail/cagra/cagra_build.cuh Outdated
@tarang-jain
tarang-jain changed the base branch from main to release/26.10 September 8, 2026 21:42
Keep VPQ serialize/deserialize and iterative-build search_main usage, and adopt KvikIO dataset I/O plus cuda_rt_essentials from the release branch.
index_type idx(res, effective_params.metric);
idx.update_graph(res, raft::make_const_mdspan(cagra_graph.view()));
if (effective_params.attach_dataset_on_build) {
idx.update_device_dataset_same_layout(res, dataset);

@aamijar aamijar Sep 9, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I thought we got rid of this function update_device_dataset_same_layout, probably a merge conflict artifact

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

Status: In Progress

Development

Successfully merging this pull request may close these issues.

8 participants