IndexScalarQuantizer.merge_from() 接受不兼容的训练范围,并会默默地以错误的方式解码合并的向量
作者: leemeii创建于 2026年9月7日更新于 2026年9月7日
环境:
- FAISS 版本: 1.15.0
- Python 包: faiss-cpu==1.15.0
描述:
IndexScalarQuantizer.merge_from() 接受两个 Scalar Quantizer 索引,它们的训练量化范围不兼容。目标索引在 [0, 1] 中训练,而源索引在 [100, 101] 中训练。每个索引都存储其中点。调用:
destination.merge_from(source, 0)成功并清空源索引。但是,移动的源向量随后使用目标索引的量化状态进行解码。 源向量重构为:
merge 前: [100.5, 100.5]
merge 后: [0.5, 0.5]使用在相同范围上训练的索引的控制案例正确地保留了重构。 这不是普通的量化误差:
- 源索引在 merge 前正确且一致地重构。
- 原始编码字节保持不变。
- 重构仅在代码被移动到具有不兼容训练状态的索引后才发生变化。
- 源索引的空性和目标索引的元素数量不变性仍然正确。
最小重现:
import faiss
import numpy as np
d = 2
destination = faiss.IndexScalarQuantizer(
d, faiss.ScalarQuantizer.QT_8bit
)
source = faiss.IndexScalarQuantizer(
d, faiss.ScalarQuantizer.QT_8bit
)
destination.train(
np.array([[0.0, 0.0], [1.0, 1.0]], dtype="float32")
)
source.train(
np.array([[100.0, 100.0], [101.0, 101.0]], dtype="float32")
)
destination.add(
np.array([[0.5, 0.5]], dtype="float32")
)
source.add(
np.array([[100.5, 100.5]], dtype="float32")
)
before = source.reconstruct(0)
destination.merge_from(source, 0)
after = destination.reconstruct(1)
print("before:", before)
print("after:", after)
print("source.ntotal:", source.ntotal)
print("destination.ntotal:", destination.ntotal)观察到的输出:
before: [100.5, 100.5]
after: [0.5, 0.5]
source.ntotal: 0
destination.ntotal: 2预期行为:
merge_from() 应该通过抛出一个明确的异常来拒绝具有不兼容训练量化状态的索引,而不是默默地将它们合并。
兼容性至少应该包括:
- 维度;
- 度量;
- 量化器类型;
- 代码大小;
- Scalar Quantizer 配置;
- 训练的缩放、范围、偏移和其他解码器状态。 或者,IndexScalarQuantizer 应该提供对 check_compatible_for_merge() 的更严格覆盖。
根本原因:
IndexFlatCodes 继承的兼容性检查验证结构属性,例如编码类型、维度和代码大小。 IndexScalarQuantizer 不对训练量化状态进行更严格的比较。Scalar Quantizer 代码仅在训练期间学习的缩放/偏移状态下才有意义。将源代码字节添加到目标索引会导致…
内容来源: facebookresearch/faiss