Files
faiss/benchs/bench_distance_computer.cpp
T
Tom Jackson 432e8b4248 Extend DistanceComputer batching to PQ, SQ, and PreTransform (#5309)
Summary:
Pull Request resolved: https://github.com/facebookresearch/faiss/pull/5309

Implement `DistanceComputer` batching interfaces in SQ and PQ:
* Add `FlatCodesDistanceComputer::distance_to_code_batch_4` virtual , override in ScalarQuantizer distance computer to call `query_to_codes_batch_4`.
* Implement `PQDistanceComputer::distances_batch_4` using `PQCodeDist::distance_four_codes` for 4-way PQ lookup table evaluation.
* Propagate batching through `IndexPreTransform` wrapper (`PreTransformDistanceComputer`).

This allows HNSW graph traversal to issue batched distance calls instead of scalar `operator()` per neighbor, improving cache locality and enabling SIMD-friendly PQ four-code path, leading to more efficient search.

This is tested with a randomly-connected HNSW graph (only testing throughput, not recall) using `HNSW::fill_with_random_links()`, which has been tweaked to remove debug logging and properly initialized `entry_point` and `max_level`.

Reviewed By: mnorris11

Differential Revision: D108383062

fbshipit-source-id: 8153c43ba27e88fdf4114324cd00355c600d492b
2026-06-16 17:40:46 -07:00

119 lines
3.5 KiB
C++

/*
* 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.
*/
#include <memory>
#include <vector>
#include <benchmark/benchmark.h>
#include <faiss/IndexHNSW.h>
#include <faiss/index_factory.h>
#include <faiss/utils/random.h>
namespace faiss {
const int d = 256;
const size_t nb = 1000000;
const size_t nq = 100000;
std::vector<float> random_data(size_t n, int seed) {
std::vector<float> data(n);
float_rand(data.data(), n, seed);
return data;
}
const auto xb = random_data(nb * d, 1234);
const auto xq = random_data(nq * d, 5678);
std::unique_ptr<Index> make_index(const std::string& factory) {
std::unique_ptr<Index> index{faiss::index_factory(d, factory.c_str())};
index->train(100000, xb.data()); // train on subset for speed.
index->add(nb, xb.data());
return index;
}
std::unique_ptr<IndexHNSW> make_hollow_hnsw() {
auto index = std::make_unique<IndexHNSW>();
index->d = d;
index->own_fields = false;
auto& hnsw = index->hnsw;
hnsw.efSearch = 32;
hnsw.fill_with_random_links(nb);
index->ntotal = nb;
return index;
}
using benchmark::Counter;
void bench_index(benchmark::State& state, const Index& index) {
std::vector<float> distances(nq * 10);
std::vector<faiss::idx_t> labels(nq * 10);
for (auto _ : state) {
index.search(nq, xq.data(), 10, distances.data(), labels.data());
benchmark::DoNotOptimize(distances[1]);
}
state.counters["nq"] = Counter(nq, Counter::kAvgThreadsRate);
}
void bench_hnsw(benchmark::State& state, Index& storage) {
static auto index = make_hollow_hnsw();
index->storage = &storage;
hnsw_stats.reset();
bench_index(state, *index);
state.counters["ndis"] = Counter(hnsw_stats.ndis, Counter::kAvgThreadsRate);
}
void bench_ivf(benchmark::State& state, Index& index) {
indexIVF_stats.reset();
bench_index(state, index);
state.counters["ndis"] =
Counter(indexIVF_stats.ndis, Counter::kAvgThreadsRate);
}
// Define and register a benchmark for a quantizer. The function name and the
// index_factory string are both derived from it. Each index is built lazily in
// a function-local static, so filtering to a single benchmark only builds the
// index it actually uses.
#define BENCH_IVF(Q) \
void BM_IVF_##Q(benchmark::State& state) { \
static auto index = make_index("IVF100," #Q); \
bench_ivf(state, *index); \
} \
BENCHMARK(BM_IVF_##Q) \
->MeasureProcessCPUTime() \
->Unit(benchmark::kMillisecond) \
->Name("IVF1K," #Q)
#define BENCH_HNSW(Q) \
void BM_HNSW_##Q(benchmark::State& state) { \
static auto storage = make_index(#Q); \
bench_hnsw(state, *storage); \
} \
BENCHMARK(BM_HNSW_##Q) \
->MeasureProcessCPUTime() \
->Unit(benchmark::kMillisecond) \
->Name("HNSW32," #Q)
BENCH_HNSW(Flat);
BENCH_HNSW(SQ8);
BENCH_HNSW(SQ6);
BENCH_HNSW(SQ4);
BENCH_HNSW(PQ64x8np);
BENCH_HNSW(PQ64x4np);
BENCH_HNSW(RaBitQ1);
BENCH_HNSW(RaBitQ2);
BENCH_IVF(SQ4);
BENCH_IVF(SQ8);
BENCH_IVF(PQ64x8np);
#undef BENCH_IVF
#undef BENCH_HNSW
} // namespace faiss
BENCHMARK_MAIN();