Files
faiss/benchs/bench_for_interrupt.py
T
Tia Lim b37e929947 Reuse faiss.ResultHeap in benchs instead of duplicating it (#5363)
Summary:
Pull Request resolved: https://github.com/facebookresearch/faiss/pull/5363

`ResultHeap` is now part of the faiss Python package: it is defined in `faiss/python/extra_wrappers.py` and re-exported into the top-level namespace from `faiss/python/__init__.py`, so it is importable as `faiss.ResultHeap`. The `faiss.contrib` modules (`big_batch_search.py`, `exhaustive_search.py`, `client_server.py`) already consume the packaged class.

Two benchmark scripts still carried their own older, byte-for-byte-duplicated copy of the class:
- `benchs/bench_for_interrupt.py`
- `benchs/distributed_ondisk/search_server.py`

This removes both local copies and uses `faiss.ResultHeap` instead.

The packaged class exposes a slightly different (and richer) API than the old copies: it has `add_result(D, I)` rather than `add_batch_result(D, I, i0)`. The old `add_batch_result` simply applied an id offset (`I += i0`) before pushing the batch onto the heaps, so the call sites are translated as:
- `rh.add_batch_result(Di, Ii, i)` -> `Ii += i; rh.add_result(Di, Ii)`
- `rh.add_batch_result(Di, Ii, 0)` -> `rh.add_result(Di, Ii)` (offset was 0)

This is the code half of task T276876996. The other half is the broken wiki link on the "Brute force search without an index" page, which points at `benchs/bench_all_ivf/datasets.py#L75` on `master`. Now that `ResultHeap` lives in the package, the link is best repointed at the canonical implementation: `faiss/python/extra_wrappers.py`. That edit is on the GitHub wiki (a separate repo) and is not part of this diff.

Reviewed By: mnorris11

Differential Revision: D110118635

fbshipit-source-id: b9e0ba5ecbec3e2c22bdf6d26da931be976ed944
2026-07-17 10:58:18 -07:00

139 lines
3.2 KiB
Python

#! /usr/bin/env python3
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
from __future__ import print_function
import numpy as np
import faiss
import time
import os
import argparse
parser = argparse.ArgumentParser()
def aa(*args, **kwargs):
group.add_argument(*args, **kwargs)
group = parser.add_argument_group("dataset options")
aa("--dim", type=int, default=64)
aa("--nb", type=int, default=int(1e6))
aa("--subset_len", type=int, default=int(1e5))
aa("--key", default="IVF1000,Flat")
aa("--nprobe", type=int, default=640)
aa("--no_intcallback", default=False, action="store_true")
aa("--twostage", default=False, action="store_true")
aa("--nt", type=int, default=-1)
args = parser.parse_args()
print("args:", args)
d = args.dim # dimension
nb = args.nb # database size
nq = 1000 # nb of queries
nt = 100000
subset_len = args.subset_len
np.random.seed(1234) # make reproducible
xb = np.random.random((nb, d)).astype("float32")
xq = np.random.random((nq, d)).astype("float32")
xt = np.random.random((nt, d)).astype("float32")
k = 100
if args.no_intcallback:
faiss.InterruptCallback.clear_instance()
if args.nt != -1:
faiss.omp_set_num_threads(args.nt)
nprobe = args.nprobe
key = args.key
# key = 'IVF1000,Flat'
# key = 'IVF1000,PQ64'
# key = 'IVF100_HNSW32,PQ64'
# faiss.omp_set_num_threads(1)
pf = "dim%d_" % d
if d == 64:
pf = ""
basename = "/tmp/base%s%s.index" % (pf, key)
if os.path.exists(basename):
print("load", basename)
index_1 = faiss.read_index(basename)
else:
print("train + write", basename)
index_1 = faiss.index_factory(d, key)
index_1.train(xt)
faiss.write_index(index_1, basename)
print("add")
index_1.add(xb)
print("set nprobe=", nprobe)
faiss.ParameterSpace().set_index_parameter(index_1, "nprobe", nprobe)
stats = faiss.cvar.indexIVF_stats
stats.reset()
print(
"index size",
index_1.ntotal,
"imbalance",
index_1.invlists.imbalance_factor(),
)
start = time.time()
Dref, Iref = index_1.search(xq, k)
print(
"time of searching: %.3f s = %.3f + %.3f ms"
% (time.time() - start, stats.quantization_time, stats.search_time)
)
indexes = {}
if args.twostage:
for i in range(0, nb, subset_len):
index = faiss.read_index(basename)
faiss.ParameterSpace().set_index_parameter(index, "nprobe", nprobe)
print("add %d:%d" % (i, i + subset_len))
index.add(xb[i : i + subset_len])
indexes[i] = index
rh = faiss.ResultHeap(nq, k)
sum_time = tq = ts = 0
for i in range(0, nb, subset_len):
if not args.twostage:
index = faiss.read_index(basename)
faiss.ParameterSpace().set_index_parameter(index, "nprobe", nprobe)
print("add %d:%d" % (i, i + subset_len))
index.add(xb[i : i + subset_len])
else:
index = indexes[i]
stats.reset()
start = time.time()
Di, Ii = index.search(xq, k)
sum_time = sum_time + time.time() - start
tq += stats.quantization_time
ts += stats.search_time
Ii += i
rh.add_result(Di, Ii)
print(
"time of searching separately: %.3f s = %.3f + %.3f ms" % (sum_time, tq, ts)
)
rh.finalize()
print("diffs: %d / %d" % ((Iref != rh.I).sum(), Iref.size))