mirror of
https://github.com/facebookresearch/faiss.git
synced 2026-10-12 02:13:36 +00:00
Summary: Pull Request resolved: https://github.com/facebookresearch/faiss/pull/5360 `bench_transposed_encode` is an ad-hoc microbenchmark wired into CI as a plain `python_unittest`. It encodes 1,000,000 vectors with `ProductQuantizer.compute_codes`, single-threaded, repeated 5x, across all SIMD levels (`NONE`/`AVX2`/`AVX512`). Under `-c faiss.dynamic_dispatch=true` all three levels are available (the slow scalar `NONE` path is always available), so all three are timed — roughly tripling the work and pushing wall-clock right at the 600s test deadline (TestInfra showed `duration_min == duration_max == 600s`, avg 424s). It intermittently tripped the deadline, so TestWarden auto-disabled it (T277674127, dups T277525456 / T277525454). Fix: shrink the deterministic workload (`nb` 1,000,000 -> 50,000, `ntrain` 100,000 -> 20,000, `n_repeat` 5 -> 2) so the run finishes well under the deadline even when DD mode times all three SIMD levels, and add the `tpx_labels.long_running` label for extra timeout headroom. The cross-level `compute_codes` coverage is preserved. The "codes identical across levels" line stays a diagnostic print (not an assertion), because scalar vs FMA reduction-order differences legitimately change nearest-centroid tie-breaks, so exact code equality across SIMD levels is not guaranteed. ___ Differential Revision: D110092205 fbshipit-source-id: 2d4cabaf53e1dba0a1f74b7670f4cbbbebea1dbb
124 lines
3.7 KiB
Python
124 lines
3.7 KiB
Python
# 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 annotations
|
|
|
|
import sys
|
|
import time
|
|
import unittest
|
|
|
|
import faiss
|
|
import numpy as np
|
|
|
|
|
|
def _time_compute_codes(
|
|
pq: faiss.ProductQuantizer, xb: np.ndarray, n_repeat: int
|
|
) -> tuple[float, int]:
|
|
# warmup (also primes any lazy state)
|
|
codes = pq.compute_codes(xb)
|
|
checksum = int(codes.astype(np.int64).sum())
|
|
t0 = time.perf_counter()
|
|
for _ in range(n_repeat):
|
|
pq.compute_codes(xb)
|
|
dt = time.perf_counter() - t0
|
|
return dt / n_repeat, checksum
|
|
|
|
|
|
def run_case(
|
|
d: int,
|
|
m: int,
|
|
nbits: int,
|
|
nb: int,
|
|
ntrain: int,
|
|
n_repeat: int,
|
|
) -> None:
|
|
dsub = d // m
|
|
rng = np.random.RandomState(1234)
|
|
xt = rng.rand(ntrain, d).astype("float32")
|
|
xb = rng.rand(nb, d).astype("float32")
|
|
|
|
pq = faiss.ProductQuantizer(d, m, nbits)
|
|
pq.train(xt)
|
|
# populate transposed_centroids -> compute_1_code uses the
|
|
# fvec_L2sqr_ny_nearest_y_transposed kernel.
|
|
pq.sync_transposed_centroids()
|
|
|
|
threads = faiss.omp_get_max_threads()
|
|
print(
|
|
f"\n=== d={d} M={m} dsub={dsub} nbits={nbits} "
|
|
f"nb={nb:,} repeats={n_repeat} (threads={threads}) ==="
|
|
)
|
|
print(f"{'level':<10} {'ms/encode-call':>15} {'vs AVX512':>12} checksum")
|
|
|
|
levels = [
|
|
faiss.SIMDLevel_NONE,
|
|
faiss.SIMDLevel_AVX2,
|
|
faiss.SIMDLevel_AVX512,
|
|
]
|
|
names = {
|
|
faiss.SIMDLevel_NONE: "NONE",
|
|
faiss.SIMDLevel_AVX2: "AVX2",
|
|
faiss.SIMDLevel_AVX512: "AVX512",
|
|
}
|
|
|
|
results: dict[str, float] = {}
|
|
checks: dict[str, int] = {}
|
|
for lvl in levels:
|
|
if not faiss.SIMDConfig.is_simd_level_available(lvl):
|
|
print(f"{names[lvl]:<10} {'(not available)':>15}")
|
|
continue
|
|
faiss.SIMDConfig.set_level(lvl)
|
|
per_call, checksum = _time_compute_codes(pq, xb, n_repeat)
|
|
results[names[lvl]] = per_call
|
|
checks[names[lvl]] = checksum
|
|
|
|
avx512 = results.get("AVX512")
|
|
for name in ("NONE", "AVX2", "AVX512"):
|
|
if name not in results:
|
|
continue
|
|
ms = results[name] * 1e3
|
|
ratio = (results[name] / avx512) if avx512 else float("nan")
|
|
print(f"{name:<10} {ms:>15.3f} {ratio:>11.2f}x {checks[name]}")
|
|
|
|
# correctness: all levels must produce identical codes
|
|
distinct = set(checks.values())
|
|
status = "YES" if len(distinct) == 1 else f"NO {checks}"
|
|
print(f"codes identical across levels: {status}")
|
|
if "NONE" in results and avx512:
|
|
speedup = results["NONE"] / avx512
|
|
print(f"==> AVX512 fix speedup vs pre-fix NONE: {speedup:.2f}x")
|
|
|
|
|
|
def main() -> None:
|
|
print(
|
|
"faiss build SIMD level (auto-detected):",
|
|
faiss.SIMDConfig.get_level(),
|
|
)
|
|
print(
|
|
"AVX512 available:",
|
|
faiss.SIMDConfig.is_simd_level_available(faiss.SIMDLevel_AVX512),
|
|
)
|
|
# Single-threaded for a clean per-core kernel signal.
|
|
faiss.omp_set_num_threads(1)
|
|
|
|
# Sizes stay small so the run finishes well under the CI test deadline even
|
|
# in dynamic-dispatch mode, where the slow scalar NONE level is also timed
|
|
# on every case.
|
|
# dsub in {1,2,4,8} -> the fast _D specialization is taken.
|
|
run_case(d=128, m=32, nbits=8, nb=50_000, ntrain=20_000, n_repeat=2)
|
|
run_case(d=64, m=16, nbits=8, nb=50_000, ntrain=20_000, n_repeat=2)
|
|
# dsub=16 -> _D fast path NOT taken (control: no AVX512 vs NONE gap).
|
|
run_case(d=128, m=8, nbits=8, nb=50_000, ntrain=20_000, n_repeat=2)
|
|
sys.stdout.flush()
|
|
|
|
|
|
class BenchTransposedEncode(unittest.TestCase):
|
|
def test_bench(self) -> None:
|
|
main()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|