Skip to content

Fix IndexIVFPQ losing polysemous_ht/do_polysemous_training on I/O round-trip - #5487

Open
Juanpacol wants to merge 1 commit into
facebookresearch:mainfrom
Juanpacol:fix/polysemous-io
Open

Fix IndexIVFPQ losing polysemous_ht/do_polysemous_training on I/O round-trip#5487
Juanpacol wants to merge 1 commit into
facebookresearch:mainfrom
Juanpacol:fix/polysemous-io

Conversation

@Juanpacol

Copy link
Copy Markdown

Summary

IndexIVFPQ never serialized its polysemous_ht and do_polysemous_training fields. The write_index() function wrote only the fields common to the IVFPQ family (by_residual, code_size, the ProductQuantizer, and the inverted lists) under the "IwPQ"/"IwQR" fourcc, so read_index() had no data to restore these two members. They silently reverted to their constructor defaults (do_polysemous_training = false, polysemous_ht = 0) on every load.

A caller who trained polysemous codes and tuned a Hamming threshold via ParameterSpace (nprobe=10,ht=32) would get back an index that searches as if polysemous filtering were never configured, with no error or warning to indicate the parameters were dropped.

Reproduction (Issue #2120)

import faiss
n, d, nlist = 10000, 64, 100
X = faiss.randn((n, d))
index = faiss.index_factory(d, f"IVF{nlist},PQ8")
index.train(X)
index.add(X)

params = faiss.ParameterSpace()
params.set_index_parameters(index, "nprobe=10,ht=32")
faiss.write_index(index, "testing.index")

index2 = faiss.read_index("testing.index")
assert index.polysemous_ht == index2.polysemous_ht  # FAILED: 32 != 0

Solution

Introduced new fourcc tags:

  • "IwPh" for IndexIVFPQ (replacing "IwPQ")
  • "IwQh" for IndexIVFPQR (replacing "IwQR")

These new tags additionally write do_polysemous_training and polysemous_ht right after the ProductQuantizer, before the inverted lists.

The read_ivfpq() function recognizes the new tags and reads the two extra fields only when present. Existing "IwPQ"/"IwQR"/"IvPQ"/"IvQR" files continue to load exactly as before (fields default to false/0, same as pre-fix behavior), preserving full backward and forward binary compatibility with indexes already persisted to disk.

Testing & Verification

  • C++ suite: 284/285 tests pass (1 unrelated SIMD-hardware skip)
  • Python suite (tests/test_io.py, tests/test_index.py): 79 passed
  • New regression test: TestIORoundTrip.test_index_ivfpq_polysemous_ht() validates that polysemous parameters survive a serialize/deserialize cycle
  • Backward compatibility: Legacy-format regression test TestIVFPQRead.test_reader confirms old on-disk indexes deserialize correctly
  • Build: Verified against libfaiss built from source (CMake + SWIG, CPU-only, Accelerate BLAS/LAPACK)

Closes #2120

…nd-trip

IndexIVFPQ never serialized its polysemous_ht and do_polysemous_training
fields. write_index() wrote only the fields common to the IVFPQ family
(by_residual, code_size, the ProductQuantizer, and the inverted lists)
under the "IwPQ"/"IwQR" fourcc, so read_index() had no data to restore
these two members and they silently reverted to their constructor
defaults (do_polysemous_training = false, polysemous_ht = 0) on every
load. A caller who trained polysemous codes and tuned a Hamming
threshold via ParameterSpace ("nprobe=10,ht=32") would get back an
index that searches as if polysemous filtering were never configured,
with no error or warning to indicate the parameters were dropped.

Reproduced the exact scenario from GH issue facebookresearch#2120: train an
"IVF100,PQ8" index, set nprobe/ht through ParameterSpace, write it to
disk, and read it back — index2.polysemous_ht came back as 0 instead
of 32.

Fix: introduce new fourcc tags "IwPh" (IndexIVFPQ) and "IwQh"
(IndexIVFPQR) that additionally write do_polysemous_training and
polysemous_ht right after the ProductQuantizer, before the inverted
lists. read_ivfpq() recognizes the new tags and reads the two extra
fields only when present, so existing "IwPQ"/"IwQR"/"IvPQ"/"IvQR"
files keep loading exactly as before (fields default to false/0, same
as pre-fix behavior) — this preserves backward and forward binary
compatibility with indexes already persisted to disk.

Added TestIORoundTrip.test_index_ivfpq_polysemous_ht in
tests/test_io.py, which builds an IndexIVFPQ, sets both fields, and
asserts they, plus search results, survive a serialize/deserialize
cycle.

Verified locally end to end:
- Built libfaiss and the SWIG Python extension from source (CMake +
  SWIG, CPU-only, Accelerate BLAS/LAPACK).
- Confirmed the bug reproduces against the pre-fix code (polysemous_ht
  32 -> 0 after read_index) and disappears with the fix applied
  (32 -> 32).
- Full C++ suite: 284/285 tests pass (1 unrelated SIMD-hardware skip).
- Python suite (tests/test_io.py, tests/test_index.py): 79 passed,
  including the legacy-format regression test TestIVFPQRead.test_reader,
  confirming old on-disk indexes still deserialize correctly.
@meta-cla

meta-cla Bot commented Jul 30, 2026

Copy link
Copy Markdown

Hi @Juanpacol!

Thank you for your pull request and welcome to our community.

Action Required

In order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you.

Process

In order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA.

Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with CLA signed. The tagging process may take up to 1 hour after signing. Please give it that time before contacting us about it.

If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks!

@meta-cla meta-cla Bot added the CLA Signed label Jul 30, 2026
@meta-cla

meta-cla Bot commented Jul 30, 2026

Copy link
Copy Markdown

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

polysemous_ht resets to 0 when loading from disk

2 participants