IndexIVFIndependentQuantizer leaves its public ntotal stale after add
Environment
- Faiss:
faiss-cpu==1.15.0 - Python: 3.x (CPython)
- OS: Windows
- Build: CPU
- Interface: in-process Python API
Summary
IndexIVFIndependentQuantizer.add() successfully adds vectors to its inner IVF index, but does not update the wrapper's public ntotal field.
As a result, the wrapper reports zero indexed vectors even though the inner IVF contains all added vectors. Search still works, but public metadata and lifecycle accounting are incorrect.
Minimal reproduction
import faiss
import numpy as np
d = 4
inner = faiss.IndexIVFFlat(
faiss.IndexFlatL2(d),
d,
2,
)
index = faiss.IndexIVFIndependentQuantizer(
faiss.IndexFlatL2(d),
inner,
)
x = np.array(
[
[0.0, 0.0, 0.0, 0.0],
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
],
dtype="float32",
)
index.train(x)
index.add(x)
index.index_ivf.nprobe = 2
D, I = index.search(x[:1], 2)
print("outer ntotal:", index.ntotal)
print("inner ntotal:", inner.ntotal)
print("search:", D[0], I[0])
assert index.ntotal == inner.ntotalExpected behavior
The wrapper's public ntotal should track the number of vectors stored by the inner IVF:
outer ntotal: 4
inner ntotal: 4The assertion should pass. Search should continue to return valid results, for example:
D[0] = [0.0, 1.0]
I[0] = [0, 1]The exact ID among the tied distance-1 results may vary.
Actual behavior
outer ntotal: 0
inner ntotal: 4
search: [0.0, 1.0] [0, 1]
AssertionErrorThe add operation succeeds and the search result is usable, but the public wrapper metadata is stale.
Root cause
The constructor initializes the wrapper metadata once:
ntotal = index_ivf->ntotal;However, IndexIVFIndependentQuantizer::add() only delegates storage to the inner IVF:
index_ivf->add_core(n, tv.x, nullptr, I.data());It does not update the wrapper's inherited public ntotal field.
reset() explicitly resets the wrapper metadata:
void IndexIVFIndependentQuantizer::reset() {
index_ivf->reset();
ntotal = 0;
}This creates an inconsistent lifecycle: reset keeps the wrapper and inner index synchronized, while add leaves them inconsistent.
Relevant source:
[IndexIVFIndependentQuantizer.cpp](https://github.com/facebookresearch/faiss/blob/v1.15.0/faiss/IndexIVFIndependentQuantizer.cpp)[IndexIVFIndependentQuantizer.h](https://github.com/facebookresearch/faiss/blob/v1.15.0/faiss/IndexIVFIndependentQuantizer.h)[Index.h](https://github.com/facebookresearch/faiss/blob/v1.15.0/faiss/Index.h)
Impact
Applications that rely on the public ntotal field may make incorrect:
- cardinality checks;
- capacity decisions;
- pagination calculations;
- empty-index checks;
- lifecycle and monitoring decisions.
Because add() and search() complete successfully, the inconsistency is silent.
Suggested fix
Synchronize the wrapper after adding vectors, for example:
index_ivf->add_core(n, tv.x, nullptr, I.data());
ntotal = index_ivf->ntotal;A regression test should verify ntotal after:
- one
add()call; - multiple
add()calls; reset()followed by anotheradd();- comparison between the wrapper and its inner IVF.
Duplicate search
I searched Faiss issues and pull requests for IndexIVFIndependentQuantizer, ntotal, add_core, and related combinations. I did not find an existing report describing this stale wrapper metadata.
Source: facebookresearch/faiss