diff --git a/faiss/utils/distances.cpp b/faiss/utils/distances.cpp index e86cb2df3..60fc29d0c 100644 --- a/faiss/utils/distances.cpp +++ b/faiss/utils/distances.cpp @@ -563,7 +563,11 @@ struct Run_search_inner_product { size_t d, size_t nx, size_t ny) { - if (res.sel || + // ny == 0 goes to the sequential path: it guards only on nx, so its + // per-query begin()/end() still runs and each handler writes its own + // neutral distance and -1 label. The BLAS path instead returns early + // on ny == 0, before the handler is initialized. + if (res.sel || ny == 0 || nx * d < static_cast(distance_compute_blas_threshold)) { exhaustive_inner_product_seq(x, y, d, nx, ny, res); } else { @@ -582,7 +586,8 @@ struct Run_search_L2sqr { size_t nx, size_t ny, const float* y_norm2) { - if (res.sel || + // See the note on ny == 0 in Run_search_inner_product. + if (res.sel || ny == 0 || nx * d < static_cast(distance_compute_blas_threshold)) { exhaustive_L2sqr_seq(x, y, d, nx, ny, res); } else { diff --git a/tests/test_index.py b/tests/test_index.py index d4105d6f1..52de4f73a 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -137,6 +137,32 @@ class TestIndexFlat(unittest.TestCase): lims, np.zeros(1, dtype=np.int64) ) + def test_empty_index_with_blas(self): + # The threshold is what makes this a regression test: it puts the + # search in a configuration that would otherwise select the BLAS path, + # which returns early on an empty database without ever initializing + # the result handler. k covers the Top1, heap, and reservoir handlers. + saved_threshold = faiss.cvar.distance_compute_blas_threshold + faiss.cvar.distance_compute_blas_threshold = 1 + try: + xq = np.arange(5, dtype="float32").reshape(-1, 1) + for metric_type in (faiss.METRIC_L2, faiss.METRIC_INNER_PRODUCT): + expected_distance = ( + np.finfo("float32").max + if metric_type == faiss.METRIC_L2 + else np.finfo("float32").min + ) + index = faiss.IndexFlat(1, metric_type) + for k in (1, 10, 150): + with self.subTest(metric_type=metric_type, k=k): + D = np.full((len(xq), k), 42, dtype="float32") + I = np.full((len(xq), k), 99, dtype="int64") + index.search(xq, k, D=D, I=I) + np.testing.assert_array_equal(D, expected_distance) + np.testing.assert_array_equal(I, -1) + finally: + faiss.cvar.distance_compute_blas_threshold = saved_threshold + @for_all_simd_levels class TestDbParallelSearch(unittest.TestCase):