Skip to content
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
b54188f
first dirty commit
tarang-jain Dec 1, 2025
0568ae2
Merge branch 'main' into ivfpq-flat-codes
tarang-jain Dec 2, 2025
c9cb3eb
Merge branch 'main' into ivfpq-flat-codes
tarang-jain Dec 4, 2025
fc0ff9a
Merge branch 'main' into ivfpq-flat-codes
tarang-jain Dec 19, 2025
2fef6e7
Merge branch 'main' into ivfpq-flat-codes
tarang-jain Jan 9, 2026
6332964
add kernel
tarang-jain Jan 9, 2026
ee28540
fix build and serialize
tarang-jain Jan 9, 2026
dc99bfb
fix gtests
tarang-jain Jan 9, 2026
fdff302
Merge branch 'main' into ivfpq-flat-codes
tarang-jain Jan 12, 2026
75e9ea1
fix function names
tarang-jain Jan 12, 2026
28957ca
corrections to list spec
tarang-jain Jan 12, 2026
d504d3f
attempt for lists() to variably dispatch to interleaved / flat types …
tarang-jain Jan 12, 2026
d3bab42
add base struct to common
tarang-jain Jan 13, 2026
b36a47a
Merge branch 'main' into ivfpq-flat-codes
tarang-jain Jan 13, 2026
d787a07
fix compilation
tarang-jain Jan 13, 2026
0038daa
Merge branch 'ivfpq-flat-codes' of https://github.com/tarang-jain/cuv…
tarang-jain Jan 13, 2026
3644d9e
Merge branch 'main' into ivfpq-flat-codes
tarang-jain Jan 13, 2026
c0a111d
fix compilation
tarang-jain Jan 13, 2026
63a2969
should work
tarang-jain Jan 13, 2026
2309b7a
fix gtest filter
tarang-jain Jan 13, 2026
3dec2dd
fix faiss patch
tarang-jain Jan 13, 2026
d41aa96
Merge branch 'main' into ivfpq-flat-codes
tarang-jain Jan 13, 2026
5e9a32c
fix faiss patch
tarang-jain Jan 14, 2026
f4a0f7f
Merge branch 'ivfpq-flat-codes' of https://github.com/tarang-jain/cuv…
tarang-jain Jan 14, 2026
8a0ac38
fix copyright
tarang-jain Jan 14, 2026
52f3534
Merge branch 'main' into ivfpq-flat-codes
tarang-jain Jan 14, 2026
42b930f
style
tarang-jain Jan 14, 2026
f6cf637
Merge branch 'main' into ivfpq-flat-codes
tarang-jain Jan 14, 2026
645cac5
add helper signatures to header
tarang-jain Jan 15, 2026
d9c9b62
Merge branch 'ivfpq-flat-codes' of https://github.com/tarang-jain/cuv…
tarang-jain Jan 15, 2026
02f4c76
Merge branch 'main' into ivfpq-flat-codes
tarang-jain Jan 15, 2026
bcbbca9
fix failing tests
tarang-jain Jan 15, 2026
550fbdf
Merge branch 'ivfpq-flat-codes' of https://github.com/tarang-jain/cuv…
tarang-jain Jan 15, 2026
a5e482b
fix docs
tarang-jain Jan 15, 2026
855e460
c + python
tarang-jain Jan 15, 2026
a549e44
fix docs
tarang-jain Jan 15, 2026
252cfbd
Merge branch 'main' into ivfpq-flat-codes
tarang-jain Jan 15, 2026
b5cfc7e
Merge branch 'release/26.02' of https://github.com/rapidsai/cuvs into…
tarang-jain Jan 16, 2026
c96b7f7
Merge branch 'release/26.02' of https://github.com/rapidsai/cuvs into…
tarang-jain Jan 22, 2026
03fd421
fix prefix
tarang-jain Jan 22, 2026
4551976
fix
tarang-jain Jan 22, 2026
b0ff258
fix prefix compilation errors
tarang-jain Jan 22, 2026
36339c8
add issue to docs
tarang-jain Jan 22, 2026
c0b6423
fix rust side
tarang-jain Jan 23, 2026
4f761fb
Merge branch 'release/26.02' of https://github.com/rapidsai/cuvs into…
tarang-jain Jan 23, 2026
400d234
copyright
tarang-jain Jan 23, 2026
cb3fec1
fix go bindings
tarang-jain Jan 23, 2026
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
17 changes: 17 additions & 0 deletions c/include/cuvs/neighbors/ivf_pq.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ enum codebook_gen { // NOLINT
PER_CLUSTER = 1, // NOLINT
};

/**
* @brief A type for specifying the memory layout of IVF-PQ list data
*
*/
enum list_layout { // NOLINT
FLAT = 0, // NOLINT
Comment thread
tarang-jain marked this conversation as resolved.
Outdated
INTERLEAVED = 1, // NOLINT
};

/**
* @brief Supplemental parameters to build IVF-PQ Index
*
Expand Down Expand Up @@ -114,6 +123,14 @@ struct cuvsIvfPqIndexParams {
* points to train each codebook.
*/
uint32_t max_train_points_per_pq_code;
/**
* Memory layout of the IVF-PQ list data.
*
* - FLAT: Codes are stored contiguously, one vector's codes after another.
* - INTERLEAVED: Codes are interleaved for optimized search performance.
* This is the default and recommended for search workloads.
*/
enum list_layout codes_layout;
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.

Just making a note here that this is going to break the C ABI. No action needed, but we will need to track these things in the future. I'll share the POR with you (and the team) for more info.

};

typedef struct cuvsIvfPqIndexParams* cuvsIvfPqIndexParams_t;
Expand Down
16 changes: 13 additions & 3 deletions c/src/neighbors/ivf_pq.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ void convert_c_index_params(cuvsIvfPqIndexParams params, cuvs::neighbors::ivf_pq
out->force_random_rotation = params.force_random_rotation;
out->conservative_memory_allocation = params.conservative_memory_allocation;
out->max_train_points_per_pq_code = params.max_train_points_per_pq_code;
out->codes_layout = static_cast<cuvs::neighbors::ivf_pq::list_layout>((int)params.codes_layout);
}
void convert_c_search_params(cuvsIvfPqSearchParams params,
cuvs::neighbors::ivf_pq::search_params* out)
Expand Down Expand Up @@ -218,8 +219,16 @@ void _get_list_indices(cuvsIvfPqIndex index,
uint32_t label,
DLManagedTensor* out_labels)
{
auto index_ptr = reinterpret_cast<cuvs::neighbors::ivf_pq::index<IdxT>*>(index.addr);
cuvs::core::to_dlpack(index_ptr->lists()[label]->indices.view(), out_labels);
auto index_ptr = reinterpret_cast<cuvs::neighbors::ivf_pq::index<IdxT>*>(index.addr);
if (index_ptr->codes_layout() == cuvs::neighbors::ivf_pq::list_layout::FLAT) {
auto& list =
static_cast<cuvs::neighbors::ivf_pq::list_data_flat<IdxT>&>(*index_ptr->lists()[label]);
cuvs::core::to_dlpack(list.indices.view(), out_labels);
} else {
auto& list = static_cast<cuvs::neighbors::ivf_pq::list_data_interleaved<IdxT>&>(
*index_ptr->lists()[label]);
cuvs::core::to_dlpack(list.indices.view(), out_labels);
}
}
} // namespace

Expand Down Expand Up @@ -328,7 +337,8 @@ extern "C" cuvsError_t cuvsIvfPqIndexParamsCreate(cuvsIvfPqIndexParams_t* params
.codebook_kind = codebook_gen::PER_SUBSPACE,
.force_random_rotation = false,
.conservative_memory_allocation = false,
.max_train_points_per_pq_code = 256};
.max_train_points_per_pq_code = 256,
.codes_layout = list_layout::INTERLEAVED};
});
}

Expand Down
32 changes: 30 additions & 2 deletions cpp/cmake/patches/faiss-1.13-cuvs-26.02.diff
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
diff --git a/faiss/gpu/impl/CuvsIVFPQ.cu b/faiss/gpu/impl/CuvsIVFPQ.cu
index 1e2fef225..35b388147 100644
index 1e2fef225..2ee40da46 100644
--- a/faiss/gpu/impl/CuvsIVFPQ.cu
+++ b/faiss/gpu/impl/CuvsIVFPQ.cu
@@ -129,8 +129,14 @@ void CuvsIVFPQ::updateQuantizer(Index* quantizer) {
Expand Down Expand Up @@ -122,7 +122,35 @@ index 1e2fef225..35b388147 100644
}

setPQCentroids_();
@@ -520,7 +583,7 @@ void CuvsIVFPQ::setPQCentroids_() {
@@ -404,10 +467,11 @@ void CuvsIVFPQ::copyInvertedListsFrom(const InvertedLists* ivf) {
auto& cuvs_index_lists = cuvs_index->lists();

// conservative memory alloc for cloning cpu inverted lists
- cuvs::neighbors::ivf_pq::list_spec<uint32_t, idx_t> ivf_list_spec{
- static_cast<uint32_t>(bitsPerSubQuantizer_),
- static_cast<uint32_t>(numSubQuantizers_),
- true};
+ cuvs::neighbors::ivf_pq::list_spec_interleaved<uint32_t, idx_t>
+ ivf_list_spec{
+ static_cast<uint32_t>(bitsPerSubQuantizer_),
+ static_cast<uint32_t>(numSubQuantizers_),
+ true};

for (size_t i = 0; i < nlist; ++i) {
size_t listSize = ivf->list_size(i);
@@ -426,9 +490,9 @@ void CuvsIVFPQ::copyInvertedListsFrom(const InvertedLists* ivf) {
// This cuVS list must currently be empty
FAISS_ASSERT(getListLength(i) == 0);

- cuvs::neighbors::ivf::resize_list(
+ cuvs::neighbors::ivf_pq::helpers::resize_list(
raft_handle,
- cuvs_index_lists[i],
+ cuvs_index_lists[i],
ivf_list_spec,
static_cast<uint32_t>(listSize),
static_cast<uint32_t>(0));
@@ -520,7 +587,7 @@ void CuvsIVFPQ::setPQCentroids_() {
auto stream = resources_->getDefaultStreamCurrentDevice();

raft::copy(
Expand Down
69 changes: 67 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-2025, NVIDIA CORPORATION.
* SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION.
* SPDX-License-Identifier: Apache-2.0
*/

Expand Down Expand Up @@ -683,11 +683,50 @@ template <typename IdxT>
constexpr static IdxT kInvalidRecord =
(std::is_signed_v<IdxT> ? IdxT{0} : std::numeric_limits<IdxT>::max()) - 1;

/**
* Abstract base class for IVF list data.
* This allows polymorphic access to list data regardless of the underlying layout.
*
* @tparam ValueT The data element type (e.g., uint8_t for PQ codes, float for raw vectors)
* @tparam IdxT The index type for source indices
* @tparam SizeT The size type
*/
template <typename ValueT, typename IdxT, typename SizeT = uint32_t>
struct list_base {
using value_type = ValueT;
using index_type = IdxT;
using size_type = SizeT;

virtual ~list_base() = default;

/** Get the raw data pointer. */
virtual value_type* data_ptr() noexcept = 0;
virtual const value_type* data_ptr() const noexcept = 0;

/** Get the indices pointer. */
virtual index_type* indices_ptr() noexcept = 0;
virtual const index_type* indices_ptr() const noexcept = 0;

/** Get the current size (number of records). */
virtual size_type get_size() const noexcept = 0;

/** Set the current size (number of records). */
virtual void set_size(size_type new_size) noexcept = 0;
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 suppose as long as users are never direclty mutating this object then we shouldn't have to be too vigilent. Would be nice if we could make this internal, though, so that only cuVS funcitons are capable of changing it. Just concerned for users shooting themselves in the foot.

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.

Tracking here #1726


/** Get the total size of the data array in bytes. */
virtual size_t data_byte_size() const noexcept = 0;

/** Get the capacity (number of indices that can be stored). */
virtual size_type indices_capacity() const noexcept = 0;
};

/** The data for a single IVF list. */
template <template <typename, typename...> typename SpecT,
typename SizeT,
typename... SpecExtraArgs>
struct list {
struct list : public list_base<typename SpecT<SizeT, SpecExtraArgs...>::value_type,
typename SpecT<SizeT, SpecExtraArgs...>::index_type,
SizeT> {
using size_type = SizeT;
using spec_type = SpecT<size_type, SpecExtraArgs...>;
using value_type = typename spec_type::value_type;
Expand All @@ -703,6 +742,18 @@ struct list {

/** Allocate a new list capable of holding at least `n_rows` data records and indices. */
list(raft::resources const& res, const spec_type& spec, size_type n_rows);

value_type* data_ptr() noexcept override { return data.data_handle(); }
const value_type* data_ptr() const noexcept override { return data.data_handle(); }

index_type* indices_ptr() noexcept override { return indices.data_handle(); }
const index_type* indices_ptr() const noexcept override { return indices.data_handle(); }

size_type get_size() const noexcept override { return size.load(); }
void set_size(size_type new_size) noexcept override { size.store(new_size); }

size_t data_byte_size() const noexcept override { return data.size() * sizeof(value_type); }
size_type indices_capacity() const noexcept override { return indices.extent(0); }
};

template <typename ListT, class T = void>
Expand All @@ -727,6 +778,10 @@ using enable_if_valid_list_t = typename enable_if_valid_list<ListT, T>::type;
/**
* Resize a list by the given id, so that it can contain the given number of records;
* copy the data if necessary.
*
* @note This is an internal function that requires the concrete list type.
* For IVF-PQ indexes, prefer using the helper functions in
* `cuvs::neighbors::ivf_pq::helpers::resize_list` which handle type casting internally.
*/
template <typename ListT>
void resize_list(raft::resources const& res,
Expand All @@ -735,13 +790,23 @@ void resize_list(raft::resources const& res,
typename ListT::size_type new_used_size,
typename ListT::size_type old_used_size);

/**
* Serialize a list to an output stream.
*
* @note This function requires the concrete list type (not the base class) because:
* 1. It needs access to the spec_type to determine the data layout for serialization
* 2. The serialized format depends on the spec's make_list_extents() method
* When calling from code that only has a base class pointer, use std::static_pointer_cast
* to obtain the typed pointer first.
*/
template <typename ListT>
enable_if_valid_list_t<ListT> serialize_list(
const raft::resources& handle,
std::ostream& os,
const ListT& ld,
const typename ListT::spec_type& store_spec,
std::optional<typename ListT::size_type> size_override = std::nullopt);

template <typename ListT>
enable_if_valid_list_t<ListT> serialize_list(
const raft::resources& handle,
Expand Down
30 changes: 15 additions & 15 deletions cpp/include/cuvs/neighbors/ivf_flat.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION.
* SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION.
* SPDX-License-Identifier: Apache-2.0
*/

Expand Down Expand Up @@ -2933,17 +2933,17 @@ void reset_index(const raft::resources& res, index<uint8_t, int64_t>* index);
* ivf_flat::index<uint8_t, int64_t> index(res, index_params, D);
* ivf_flat::helpers::reset_index(res, &index);
* // resize the first IVF list to hold 5 records
* auto spec = list_spec<uint32_t, uint8_t, int64_t>{
* index->dim(), index->conservative_memory_allocation()};
* auto spec = list_spec<uint32_t, float, int64_t>{
* index.dim(), index.conservative_memory_allocation()};
* uint32_t new_size = 5;
* ivf::resize_list(res, list, spec, new_size, 0);
* ivf::resize_list(res, index.lists()[0], spec, new_size, 0);
* raft::update_device(index.list_sizes(), &new_size, 1, stream);
* // recompute the internal state of the index
* ivf_flat::helpers::recompute_internal_state(res, index);
* @endcode
*
* @param[in] res raft resource
* @param[inout] index pointer to IVF-PQ index
* @param[inout] index pointer to IVF-Flat index
*/
void recompute_internal_state(const raft::resources& res, index<float, int64_t>* index);

Expand All @@ -2961,17 +2961,17 @@ void recompute_internal_state(const raft::resources& res, index<float, int64_t>*
* ivf_flat::index<uint8_t, int64_t> index(res, index_params, D);
* ivf_flat::helpers::reset_index(res, &index);
* // resize the first IVF list to hold 5 records
* auto spec = list_spec<uint32_t, uint8_t, int64_t>{
* index->dim(), index->conservative_memory_allocation()};
* auto spec = list_spec<uint32_t, half, int64_t>{
* index.dim(), index.conservative_memory_allocation()};
* uint32_t new_size = 5;
* ivf::resize_list(res, list, spec, new_size, 0);
* ivf::resize_list(res, index.lists()[0], spec, new_size, 0);
* raft::update_device(index.list_sizes(), &new_size, 1, stream);
* // recompute the internal state of the index
* ivf_flat::helpers::recompute_internal_state(res, index);
* @endcode
*
* @param[in] res raft resource
* @param[inout] index pointer to IVF-PQ index
* @param[inout] index pointer to IVF-Flat index
*/
void recompute_internal_state(const raft::resources& res, index<half, int64_t>* index);

Expand All @@ -2989,17 +2989,17 @@ void recompute_internal_state(const raft::resources& res, index<half, int64_t>*
* ivf_flat::index<uint8_t, int64_t> index(res, index_params, D);
* ivf_flat::helpers::reset_index(res, &index);
* // resize the first IVF list to hold 5 records
* auto spec = list_spec<uint32_t, uint8_t, int64_t>{
* index->dim(), index->conservative_memory_allocation()};
* auto spec = list_spec<uint32_t, int8_t, int64_t>{
* index.dim(), index.conservative_memory_allocation()};
* uint32_t new_size = 5;
* ivf::resize_list(res, list, spec, new_size, 0);
* ivf::resize_list(res, index.lists()[0], spec, new_size, 0);
* raft::update_device(index.list_sizes(), &new_size, 1, stream);
* // recompute the internal state of the index
* ivf_flat::helpers::recompute_internal_state(res, index);
* @endcode
*
* @param[in] res raft resource
* @param[inout] index pointer to IVF-PQ index
* @param[inout] index pointer to IVF-Flat index
*/
void recompute_internal_state(const raft::resources& res, index<int8_t, int64_t>* index);

Expand All @@ -3018,9 +3018,9 @@ void recompute_internal_state(const raft::resources& res, index<int8_t, int64_t>
* ivf_flat::helpers::reset_index(res, &index);
* // resize the first IVF list to hold 5 records
* auto spec = list_spec<uint32_t, uint8_t, int64_t>{
* index->dim(), index->conservative_memory_allocation()};
* index.dim(), index.conservative_memory_allocation()};
* uint32_t new_size = 5;
* ivf::resize_list(res, list, spec, new_size, 0);
* ivf::resize_list(res, index.lists()[0], spec, new_size, 0);
* raft::update_device(index.list_sizes(), &new_size, 1, stream);
* // recompute the internal state of the index
* ivf_flat::helpers::recompute_internal_state(res, index);
Expand Down
Loading