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/5444 We fix every remaining flake8 E501 (line > 80 chars) violation in fbcode/faiss/ (excluding gpu/), closing out a lint-cleanup initiative that a June 29, 2026 diff stack (9a05463576 -> d304e29b9a -> 7d140d86ca) explicitly promised but left 41% incomplete. The middle diff in that stack aligned .flake8's max-line-length to black's 80-char width and stated the remaining 277 violations after the config change were "genuine lint (E501 long strings/URLs, F401/F841/B011, etc.), addressed in later commits in this stack." The follow-up commit fixed only the F401/F403/F841/B011 categories (45 issues) and never touched E501, leaving 192 violations across 53 files live in trunk. A direct re-scan today found 201 violations across 54 files (two files gained lines since the original count). We fixed 183 violations across 52 files. Two of the originally-flagged files, benchs/bench_rabitq.py and demos/index_pq_flat_separate_codes_from_codebook.py, already carry a file-level `# flake8: noqa` directive (the second is a Bento/Grimaldi notebook export that also fails standalone py_compile due to embedded shell magic) -- flake8 ignores both files entirely, so no changes were needed there and they are excluded from this diff. Fix approach, by category: - The large majority of violations are prose: docstrings and comments rewrapped at word boundaries, preserving each file's existing indentation and docstring conventions. No wording was changed. - Code lines (function calls, f-strings, assert messages, long argument lists) were reflowed in black-compatible style: parenthesization, comma breaks, or implicit adjacent string-literal concatenation. Every touched file is confirmed unchanged by `black --line-length 80 --diff` after the edit. - A small number of genuinely unwrappable long URLs (no reasonable break point) were suppressed with `# noqa: E501` rather than force-wrapped, matching the precedent set by the original black-format diff stack. - demos/offline_ivf/offline_ivf.py: a repeated, non-trivial ratio expression used in both an assert condition and its (lazily-evaluated) failure message was hoisted into a single local variable, eliminating the duplicate computation. The expression is pure and deterministic (built from already-computed arrays), so this is behaviorally identical to the original -- not a functional change. - `arc lint -a` additionally auto-fixed a real bug it detected while processing these files: `raise NotImplemented` -> `raise NotImplementedError` in contrib/evaluation.py (the sentinel object NotImplemented is not an exception type; raising it produces a TypeError masking the intended NotImplementedError). It also added a missing trailing newline in benchs/fb/bench_index_binary_from_float.py and, in the same file, rewrote the `#!/usr/bin/env python2` shebang to `#!/usr/bin/env fbpython` -- that rewrite was reverted by hand, since the file still does `import cPickle` (a Python 2-only module), so the auto-fix would have made the shebang describe an interpreter the script cannot actually run under. Fixing the underlying py2-to-py3 migration is out of scope for this line-length-only sweep. - Rebasing onto master (this diff's base commit had drifted) surfaced two concurrent upstream fixes to `check_ref_knn_with_draws(...)` calls in tests/test_ivf_flat_panorama.py and tests/test_refine_panorama.py (an unrelated in-flight correctness fix passing `D_panorama`/`atol` instead of a stale `D_regular` arg with no `atol`). The rebase took upstream's corrected arguments and re-applied only this diff's line-wrap on top. We verified zero functional change with an AST-level check: every string and f-string literal in every touched file was extracted before and after the edit and compared with whitespace normalized, confirming no wording, value, or expression was altered beyond the intended `# noqa: E501` suffixes and the one documented ratio-hoisting simplification above. Reviewed By: mnorris11 Differential Revision: D112525458 fbshipit-source-id: e4b85d73dafcaac779f3522a383a66e490fa942a
139 lines
3.6 KiB
Python
139 lines
3.6 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.
|
|
|
|
import multiprocessing as mp
|
|
import time
|
|
|
|
import faiss
|
|
import matplotlib.pyplot as plt
|
|
|
|
try:
|
|
from faiss.contrib.datasets_fb import DatasetGIST1M
|
|
except ImportError:
|
|
from faiss.contrib.datasets import DatasetGIST1M
|
|
|
|
|
|
ds = DatasetGIST1M()
|
|
|
|
nq = 100
|
|
xq = ds.get_queries()[:nq]
|
|
xb = ds.get_database()
|
|
gt = ds.get_groundtruth()[:nq]
|
|
|
|
xt = ds.get_train()
|
|
|
|
nb, d = xb.shape
|
|
nt, d = xt.shape
|
|
|
|
k = 10
|
|
gt = gt[:, :k]
|
|
|
|
|
|
def eval_once(index, queries, params=None):
|
|
t0 = time.time()
|
|
D, I = index.search(queries, k=k, params=params)
|
|
t = time.time() - t0
|
|
speed_ms = t * 1000 / nq
|
|
qps = 1000.0 / speed_ms
|
|
corrects = (gt == I).sum()
|
|
recall = corrects / (nq * k)
|
|
return recall, qps
|
|
|
|
|
|
def build_refine_indexes(factory_string, n_levels):
|
|
base_index = faiss.index_factory(d, factory_string)
|
|
faiss.omp_set_num_threads(mp.cpu_count())
|
|
|
|
base_index.train(xt)
|
|
base_index.add(xb)
|
|
|
|
refine_pano = faiss.index_factory(d, f"PCA{d},FlatL2Panorama{n_levels}_1")
|
|
refine_pano.train(xt)
|
|
refine_pano.add(xb)
|
|
|
|
faiss.omp_set_num_threads(1)
|
|
|
|
# Refine wrappers
|
|
idx_flat = faiss.IndexRefineFlat(base_index, faiss.swig_ptr(xb))
|
|
idx_pano = faiss.IndexRefinePanorama(base_index, refine_pano)
|
|
|
|
return base_index, idx_flat, idx_pano
|
|
|
|
|
|
factory = "IVF256,PQ60x4fs"
|
|
nlevels = 8
|
|
|
|
base_index, idx_flat, idx_pano = build_refine_indexes(factory, nlevels)
|
|
|
|
# Parameter sweeps
|
|
nprobe_list = [4, 16, 64, 256]
|
|
kfactor_list = [1, 8, 64, 256, 1024]
|
|
|
|
print(f"Benchmark on GIST1M with base '{factory}', k={k}, nq={nq}")
|
|
print(
|
|
"nprobe k_factor recall_flat qps_flat recall_pano qps_pano "
|
|
"dims_scanned(%) speedup(x)"
|
|
)
|
|
|
|
faiss.omp_set_num_threads(1)
|
|
|
|
# Visualization at a fixed nprobe
|
|
plt.figure(figsize=(8, 5), dpi=300)
|
|
qps_f_list, qps_p_list = [], []
|
|
fixed_nprobe = 16
|
|
|
|
for nprobe in nprobe_list:
|
|
base_index.nprobe = nprobe
|
|
|
|
for kf in kfactor_list:
|
|
params = faiss.IndexRefineSearchParameters(k_factor=kf)
|
|
|
|
# Flat refinement
|
|
recall_f, qps_f = eval_once(idx_flat, xq, params=params)
|
|
|
|
# Panorama refinement
|
|
faiss.cvar.indexPanorama_stats.reset()
|
|
recall_p, qps_p = eval_once(idx_pano, xq, params=params)
|
|
dims_pct = faiss.cvar.indexPanorama_stats.ratio_dims_scanned * 100.0
|
|
|
|
speedup = qps_p / qps_f
|
|
|
|
print(
|
|
f"{nprobe:6d} {kf:7.1f} "
|
|
f"{recall_f:11.6f} {qps_f:9.2f} "
|
|
f"{recall_p:11.6f} {qps_p:9.2f} "
|
|
f"{dims_pct:15.2f} {speedup:9.2f}x"
|
|
)
|
|
|
|
if nprobe == fixed_nprobe:
|
|
qps_f_list.append(qps_f)
|
|
qps_p_list.append(qps_p)
|
|
|
|
# Draw speedup and recall
|
|
plt.plot([kf, kf], [qps_f, qps_p], "k--", linewidth=1, alpha=0.7)
|
|
mid_y = (qps_f * qps_p) ** 0.5
|
|
plt.text(
|
|
kf + 10,
|
|
mid_y,
|
|
f"{speedup:.2f}x\nr={recall_p:.2f}",
|
|
ha="left",
|
|
va="center",
|
|
fontsize=8,
|
|
)
|
|
|
|
plt.plot(kfactor_list, qps_f_list, label="RefineFlat")
|
|
plt.plot(kfactor_list, qps_p_list, label=f"RefineFlatPanorama({nlevels})")
|
|
plt.yscale("log")
|
|
plt.ylim(bottom=100)
|
|
plt.xlim(right=kfactor_list[-1] * 1.075)
|
|
plt.xlabel("k_factor (k_base = k * k_factor)")
|
|
plt.ylabel("QPS")
|
|
plt.title(
|
|
f"GIST1M, base={factory}, nprobe={fixed_nprobe}, nlevels={nlevels}, k={k}"
|
|
)
|
|
plt.legend()
|
|
plt.tight_layout()
|
|
plt.savefig("bench_refine_panorama.png", bbox_inches="tight")
|