Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 10 additions & 2 deletions faiss/impl/index_read.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1550,10 +1550,13 @@ static std::unique_ptr<IndexIVFPQ> read_ivfpq(
uint32_t h,
int io_flags) {
bool legacy = h == fourcc("IvQR") || h == fourcc("IvPQ");
// "IwPh"/"IwQh" are the only formats that store the polysemous
// training parameters (see GH issue #2120).
bool has_polysemous = h == fourcc("IwPh") || h == fourcc("IwQh");

IndexIVFPQR* ivfpqr = nullptr;
std::unique_ptr<IndexIVFPQ> ivpq;
if (h == fourcc("IvQR") || h == fourcc("IwQR")) {
if (h == fourcc("IvQR") || h == fourcc("IwQR") || h == fourcc("IwQh")) {
ivpq = std::make_unique<IndexIVFPQR>();
ivfpqr = static_cast<IndexIVFPQR*>(ivpq.get());
} else {
Expand All @@ -1566,6 +1569,11 @@ static std::unique_ptr<IndexIVFPQ> read_ivfpq(
READ1(ivpq->code_size);
read_ProductQuantizer(&ivpq->pq, f);

if (has_polysemous) {
READ1_BOOL(ivpq->do_polysemous_training);
READ1(ivpq->polysemous_ht);
}

if (legacy) {
ArrayInvertedLists* ail = set_array_invlist(ivpq.get(), ids);
for (size_t i = 0; i < ail->nlist; i++)
Expand Down Expand Up @@ -2240,7 +2248,7 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
idx = std::move(ivsp);
} else if (
h == fourcc("IvPQ") || h == fourcc("IvQR") || h == fourcc("IwPQ") ||
h == fourcc("IwQR")) {
h == fourcc("IwQR") || h == fourcc("IwPh") || h == fourcc("IwQh")) {
idx = read_ivfpq(f, h, io_flags);
} else if (h == fourcc("IwIQ")) {
auto indep = std::make_unique<IndexIVFIndependentQuantizer>();
Expand Down
7 changes: 6 additions & 1 deletion faiss/impl/index_write.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -814,12 +814,17 @@ void write_index(const Index* idx, IOWriter* f, int io_flags) {
} else if (const IndexIVFPQ* ivpq = dynamic_cast<const IndexIVFPQ*>(idx)) {
const IndexIVFPQR* ivfpqr = dynamic_cast<const IndexIVFPQR*>(idx);

uint32_t h = fourcc(ivfpqr ? "IwQR" : "IwPQ");
// "IwPh"/"IwQh" additionally store the polysemous training
// parameters, which the older "IwPQ"/"IwQR" formats omitted
// (see GH issue #2120).
uint32_t h = fourcc(ivfpqr ? "IwQh" : "IwPh");
WRITE1(h);
write_ivf_header(ivpq, f);
WRITE1(ivpq->by_residual);
WRITE1(ivpq->code_size);
write_ProductQuantizer(&ivpq->pq, f);
WRITE1(ivpq->do_polysemous_training);
WRITE1(ivpq->polysemous_ht);
write_InvertedLists(ivpq->invlists, f);
if (ivfpqr) {
write_ProductQuantizer(&ivfpqr->refine_pq, f);
Expand Down
20 changes: 20 additions & 0 deletions tests/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,26 @@ def test_index_pq(self):
np.testing.assert_array_equal(Iref, I2)
np.testing.assert_array_equal(Dref, D2)

def test_index_ivfpq_polysemous_ht(self):
"""IndexIVFPQ must preserve do_polysemous_training/polysemous_ht
across a write/read round-trip (see GH issue #2120)."""
xt, xb, xq = get_dataset_2(d, nt, nb, nq)
index = faiss.index_factory(d, "IVF32,PQ4np")
index.train(xt)
index.add(xb)
index.do_polysemous_training = True
index.polysemous_ht = 42
index.nprobe = 4

index2 = faiss.deserialize_index(faiss.serialize_index(index))
self.assertEqual(index2.do_polysemous_training, index.do_polysemous_training)
self.assertEqual(index2.polysemous_ht, index.polysemous_ht)

Dref, Iref = index.search(xq, 5)
D2, I2 = index2.search(xq, 5)
np.testing.assert_array_equal(Iref, I2)
np.testing.assert_array_equal(Dref, D2)

def test_index_sq8(self):
"""IndexScalarQuantizer with SQ8."""
xt, xb, xq = get_dataset_2(d, nt, nb, nq)
Expand Down
Loading