Skip to content

Iterative CAGRA-Q - #1810

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

irina-resh-nvda wants to merge 78 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.

This PR also adds support for C and Python APIs for creating a PQ dataset and building a cagra graph on it. We also add support to update_dataset from dense->PQ or PQ->dense. Serializing the index is also supported.

Example Runnable workflow

import cupy as cp

from cuvs.common import make_device_pq_dataset
from cuvs.neighbors import cagra, hnsw
from cuvs.preprocessing.quantize import pq

dataset = cp.random.random((5000, 64), dtype=cp.float32)
pq_dataset = make_device_pq_dataset(
    pq.PQDatasetParams(pq_dim=32), dataset
)

index = cagra.build(
    cagra.IndexParams(build_algo="iterative_cagra_search"), pq_dataset
)
cagra.save("index.bin", index, include_dataset=False)

loaded_index = cagra.Index()
cagra.load(loaded_index, "index.bin")
cagra.update_dataset(loaded_index, dataset)

queries = dataset[:100]
cagra.search(cagra.SearchParams(), loaded_index, queries, 10)

hnsw_index = hnsw.from_cagra(hnsw.IndexParams(), loaded_index)
hnsw.search(hnsw.SearchParams(), hnsw_index, cp.asnumpy(queries), 10)

@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
Comment thread cpp/cmake/patches/faiss_override.json Outdated
Comment thread c/tests/CMakeLists.txt Outdated
@aamijar

aamijar commented Sep 10, 2026

Copy link
Copy Markdown
Member

/ok to test 100db4f

@irina-resh-nvda

Copy link
Copy Markdown
Contributor Author

It looks good to me

@aamijar

aamijar commented Sep 11, 2026

Copy link
Copy Markdown
Member

/ok to test 19f7397


auto cagra_graph =
raft::make_host_matrix<IdxT, int64_t>(dev_graph.extent(0), dev_graph.extent(1));
raft::copy(cagra_graph.data_handle(),

@aamijar aamijar Sep 14, 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.

Wait - this should be removed. We are doing a D2H of the entire graph. And then if you look at the caller we are doing H2D back again. update_graph member function does a copy to device.

auto cagra_graph = detail::iterative_build_graph<T, IdxT>(res, effective_params, dataset);
index_type idx(res, effective_params.metric);
idx.update_graph(res, raft::make_const_mdspan(cagra_graph.view()));

void update_graph(
raft::resources const& res,
raft::host_matrix_view<const graph_index_type, int64_t, raft::row_major> knn_graph)
{
RAFT_LOG_DEBUG("Copying CAGRA knn graph from host to device");

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.

Addressed in ffd1a1d

@aamijar

aamijar commented Sep 14, 2026

Copy link
Copy Markdown
Member

/ok to test ffd1a1d

Comment thread cpp/include/cuvs/neighbors/cagra.hpp
@aamijar

aamijar commented Sep 14, 2026

Copy link
Copy Markdown
Member

/ok to test abd796c

@aamijar

aamijar commented Sep 14, 2026

Copy link
Copy Markdown
Member

/ok to test e218f9e

@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 everyone for pushing it through the finish line, the PR looks good to me!

Comment on lines +2371 to +2379
reconstruct_vpq_queries<T, half, int64_t>(
res,
*vpq_queries,
static_cast<uint64_t>(offset),
static_cast<uint32_t>(batch_size),
raft::make_device_matrix_view<T, int64_t>(
reconstructed_batch_queries->data_handle(), batch_size, query_ld));
auto batch_query_view = raft::make_device_matrix_view<const T, int64_t>(
reconstructed_batch_queries->data_handle(), batch_size, query_ld);

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.

Nitpick, but we can construct the view first and use in two places

Suggested change
reconstruct_vpq_queries<T, half, int64_t>(
res,
*vpq_queries,
static_cast<uint64_t>(offset),
static_cast<uint32_t>(batch_size),
raft::make_device_matrix_view<T, int64_t>(
reconstructed_batch_queries->data_handle(), batch_size, query_ld));
auto batch_query_view = raft::make_device_matrix_view<const T, int64_t>(
reconstructed_batch_queries->data_handle(), batch_size, query_ld);
auto batch_query_view = raft::make_device_matrix_view<const T, int64_t>(
reconstructed_batch_queries->data_handle(), batch_size, query_ld);
reconstruct_vpq_queries<T, half, int64_t>(
res,
*vpq_queries,
static_cast<uint64_t>(offset),
static_cast<uint32_t>(batch_size),
batch_query_view);

Comment thread cpp/src/neighbors/detail/cagra/cagra_build.cuh Outdated
@tfeher
tfeher dismissed achirkin’s stale review September 15, 2026 06:54

Most of Artem's comments were addressed, I have created issue #2610 to track the open question from Artem's review.

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.

9 participants