Fix distance conversion in IndexBinaryFromFloat for non-L2 metrics (#5549)

Summary:
Pull Request resolved: https://github.com/facebookresearch/faiss/pull/5549

`IndexBinaryFromFloat::search()` converted the wrapped float index's distances back to
Hamming distances with a hardcoded `round(df[i] / 4.0)`, which is only correct for
`METRIC_L2`. For any other metric the returned distances were silently wrong.

`binary_to_real()` maps bits to `{-1, +1}`, so the relation between the float distance and
the Hamming distance depends on the metric:

- `METRIC_L2` (squared): `float_dist = 4 * hamming`
- `METRIC_INNER_PRODUCT`: `float_dist = d - 2 * hamming`
- `METRIC_L1`: `float_dist = 2 * hamming`

Branch on `index->metric_type` and apply the matching inverse. `METRIC_L2` remains the
default/fallback, so existing behavior is unchanged. Each mapping is monotonic in the
Hamming distance, so the result order the wrapped index produces is already correct and
needs no reordering.

Reported in https://github.com/facebookresearch/faiss/issues/5546.

___

Differential Revision: D118160087

fbshipit-source-id: f2ce2b97ebfd47461346508f9878b1af7e321b12
This commit is contained in:
Trang Nguyen
2026-09-01 10:47:31 -07:00
committed by meta-codesync[bot]
parent af9eeffcf7
commit 718002f7cf
2 changed files with 61 additions and 2 deletions
+23 -2
View File
@@ -9,9 +9,11 @@
#include <faiss/IndexBinaryFromFloat.h>
#include <faiss/MetricType.h>
#include <faiss/impl/FaissAssert.h>
#include <faiss/utils/utils.h>
#include <algorithm>
#include <cmath>
#include <memory>
namespace faiss {
@@ -67,8 +69,27 @@ void IndexBinaryFromFloat::search(
binary_to_real(bn * d, x + b * code_size, xf.get());
index->search(bn, xf.get(), k, df.get(), labels + b * k);
for (int i = 0; i < bn * k; ++i) {
distances[b * k + i] = int32_t(std::round(df[i] / 4.0));
// binary_to_real maps bits to {-1, +1}, so the float distance relates
// to the Hamming distance differently for each metric. In all cases the
// mapping is monotonic, so the result order needs no adjustment.
switch (index->metric_type) {
case METRIC_INNER_PRODUCT:
for (int i = 0; i < bn * k; ++i) {
distances[b * k + i] =
int32_t(std::round((d - df[i]) / 2.0));
}
break;
case METRIC_L1:
for (int i = 0; i < bn * k; ++i) {
distances[b * k + i] = int32_t(std::round(df[i] / 2.0));
}
break;
case METRIC_L2:
default:
for (int i = 0; i < bn * k; ++i) {
distances[b * k + i] = int32_t(std::round(df[i] / 4.0));
}
break;
}
}
}
+38
View File
@@ -49,6 +49,44 @@ class TestIndexBinaryFromFloat(unittest.TestCase):
np.testing.assert_allclose((D_ref / 4.0).astype("int32"), D)
def test_index_from_float_inner_product(self):
d = 256
nt = 0
nb = 1500
nq = 500
(xt, xb, xq) = make_binary_dataset(d, nb, nt, nq)
index_ref = faiss.IndexBinaryFlat(d)
index_ref.add(xb)
index = faiss.IndexFlat(d, faiss.METRIC_INNER_PRODUCT)
index_bin = faiss.IndexBinaryFromFloat(index)
index_bin.add(xb)
D_ref, I_ref = index_ref.search(xq, 10)
D, I = index_bin.search(xq, 10)
np.testing.assert_array_equal(D_ref, D)
def test_index_from_float_l1(self):
d = 256
nt = 0
nb = 1500
nq = 500
(xt, xb, xq) = make_binary_dataset(d, nb, nt, nq)
index_ref = faiss.IndexBinaryFlat(d)
index_ref.add(xb)
index = faiss.IndexFlat(d, faiss.METRIC_L1)
index_bin = faiss.IndexBinaryFromFloat(index)
index_bin.add(xb)
D_ref, I_ref = index_ref.search(xq, 10)
D, I = index_bin.search(xq, 10)
np.testing.assert_array_equal(D_ref, D)
def test_wrapped_quantizer(self):
d = 256
nt = 150