Files
faiss/benchs/bench_refine_panorama.py
T
Alibek Zhakubayev a238933d17 Finish E501 line-length lint sweep across faiss (183 violations, 52 files) (#5444)
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
2026-07-17 10:20:42 -07:00

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")