Summary
IndexBinaryIVF::reconstruct_n() writes reconstructed binary vectors using d as a byte stride. For binary indexes, d represents the number of bits, while reconstruction buffers are sized in bytes as ni * d / 8.
As a result, IndexBinaryIVF.reconstruct_n() returns incorrect compact output through the Python API and may write beyond the bounds of the caller-provided reconstruction buffer.
Bug type
Incorrect result
Environment
- FAISS version: 1.14.2
- Python version: 3.13.9
- OS: Windows 11
Expected behavior
IndexBinary::reconstruct_n specifies that the output buffer size should be ni * d / 8.
The Python API for binary indexes allocates reconstruction output as:
np.empty((ni, self.code_size), dtype=np.uint8)
Therefore, IndexBinaryIVF.reconstruct_n(i0, ni) should write each reconstructed binary vector contiguously using:
stride = code_size; // d / 8 bytes
For the reproducer below, index.reconstruct_n(0, 3) should return exactly:
[[ 0 1 2 3 4 5 6 7]
[ 8 9 10 11 12 13 14 15]
[16 17 18 19 20 21 22 23]]
Actual behavior
Only the first vector appears in the compact Python output. Subsequent vectors are not written at offsets 8 and 16, but instead appear at offsets 64 and 128, because d is used as a byte stride.
This leads to incorrect reconstruction results and may cause writes beyond the intended buffer region.
Minimal reproducer
import faiss
import numpy as np
print("faiss version:", getattr(faiss, "__version__", "unknown"))
d = 64
code_size = d // 8
xb = np.array([
[0, 1, 2, 3, 4, 5, 6, 7],
[8, 9, 10, 11, 12, 13, 14, 15],
[16, 17, 18, 19, 20, 21, 22, 23],
], dtype="uint8")
quantizer = faiss.IndexBinaryFlat(d)
quantizer.add(np.zeros((1, code_size), dtype="uint8"))
index = faiss.IndexBinaryIVF(quantizer, d, 1)
index.is_trained = True
index.add(xb)
# Public API: Python allocates shape (ni, code_size), as documented.
actual_public = index.reconstruct_n(0, 3)
print("Expected compact reconstruction:")
print(xb)
print("Actual public reconstruct_n:")
print(actual_public)
print("Public API equal:", np.array_equal(actual_public, xb))
# Stable diagnostic: allocate a large sentinel buffer and call the C binding.
# This shows where the reconstructed vectors are actually written.
buf = np.full(3 * d, 0xEE, dtype="uint8")
index.reconstruct_n_c(0, 3, faiss.swig_ptr(buf))
print("Compact view that public wrapper expects:")
print(buf[:3 * code_size].reshape(3, code_size))
print("Bytes written at wrong d-stride offsets:")
for i in range(3):
off = i * d
print(i, "offset", off, buf[off:off + code_size].tolist())
Reproducer output
Expected compact reconstruction:
[[ 0 1 2 3 4 5 6 7]
[ 8 9 10 11 12 13 14 15]
[16 17 18 19 20 21 22 23]]
Actual public reconstruct_n:
[[ 0 1 2 3 4 5 6 7]
[238 238 238 238 238 238 238 238]
[238 238 238 238 238 238 238 238]]
Public API equal: False
Compact view that public wrapper expects:
[[ 0 1 2 3 4 5 6 7]
[238 238 238 238 238 238 238 238]
[238 238 238 238 238 238 238 238]]
Bytes written at wrong d-stride offsets:
0 offset 0 [0, 1, 2, 3, 4, 5, 6, 7]
1 offset 64 [8, 9, 10, 11, 12, 13, 14, 15]
2 offset 128 [16, 17, 18, 19, 20, 21, 22, 23]
Why this is not user error
This issue is not caused by approximate-nearest-neighbor behavior, ties, metric selection, or misuse of the API.
The reproducer uses:
- a single IVF list
- deterministic sequential IDs
- exact stored binary codes
- no randomness
- no
remove_ids
- no direct-map dependency
- the documented
reconstruct_n API
The discrepancy arises from a mismatch between the documented buffer layout and the stride used during reconstruction.
No existing issue appears to report this specific problem.
Summary
IndexBinaryIVF::reconstruct_n()writes reconstructed binary vectors usingdas a byte stride. For binary indexes,drepresents the number of bits, while reconstruction buffers are sized in bytes asni * d / 8.As a result,
IndexBinaryIVF.reconstruct_n()returns incorrect compact output through the Python API and may write beyond the bounds of the caller-provided reconstruction buffer.Bug type
Incorrect result
Environment
Expected behavior
IndexBinary::reconstruct_nspecifies that the output buffer size should beni * d / 8.The Python API for binary indexes allocates reconstruction output as:
Therefore,
IndexBinaryIVF.reconstruct_n(i0, ni)should write each reconstructed binary vector contiguously using:stride = code_size; // d / 8 bytesFor the reproducer below,
index.reconstruct_n(0, 3)should return exactly:Actual behavior
Only the first vector appears in the compact Python output. Subsequent vectors are not written at offsets
8and16, but instead appear at offsets64and128, becausedis used as a byte stride.This leads to incorrect reconstruction results and may cause writes beyond the intended buffer region.
Minimal reproducer
Reproducer output
Why this is not user error
This issue is not caused by approximate-nearest-neighbor behavior, ties, metric selection, or misuse of the API.
The reproducer uses:
remove_idsreconstruct_nAPIThe discrepancy arises from a mismatch between the documented buffer layout and the stride used during reconstruction.
No existing issue appears to report this specific problem.