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
214 lines
5.8 KiB
Python
214 lines
5.8 KiB
Python
# @lint-ignore-every LICENSELINT
|
|
# 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.
|
|
#
|
|
# Copyright (c) 2024-2025, NVIDIA CORPORATION.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
#
|
|
|
|
import numpy as np
|
|
import faiss
|
|
import time
|
|
import argparse
|
|
import rmm
|
|
|
|
try:
|
|
from faiss.contrib.datasets_fb import DatasetSIFT1M
|
|
except ImportError:
|
|
from faiss.contrib.datasets import DatasetSIFT1M
|
|
|
|
|
|
# ds = DatasetDeep1B(10**6)
|
|
# ds = DatasetBigANN(nb_M=1)
|
|
ds = DatasetSIFT1M()
|
|
|
|
xq = ds.get_queries()
|
|
xb = ds.get_database()
|
|
gt = ds.get_groundtruth()
|
|
|
|
xt = ds.get_train()
|
|
|
|
nb, d = xb.shape
|
|
nq, d = xq.shape
|
|
nt, d = xt.shape
|
|
|
|
M = d / 2
|
|
|
|
######################################################
|
|
# Command-line parsing
|
|
######################################################
|
|
|
|
parser = argparse.ArgumentParser()
|
|
|
|
|
|
def aa(*args, **kwargs):
|
|
group.add_argument(*args, **kwargs)
|
|
|
|
|
|
group = parser.add_argument_group("benchmarking options")
|
|
|
|
aa(
|
|
"--bm_train",
|
|
default=True,
|
|
help="whether to benchmark train operation on GPU index",
|
|
)
|
|
aa(
|
|
"--bm_add",
|
|
default=True,
|
|
help="whether to benchmark add operation on GPU index",
|
|
)
|
|
aa(
|
|
"--bm_search",
|
|
default=True,
|
|
help="whether to benchmark search operation on GPU index",
|
|
)
|
|
|
|
|
|
group = parser.add_argument_group("IVF options")
|
|
aa("--nlist", default=1024, type=np.int64, help="number of IVF centroids")
|
|
aa(
|
|
"--bits_per_code",
|
|
default=8,
|
|
type=np.int64,
|
|
help="bits per code. Note that < 8 is only supported when cuVS is enabled",
|
|
)
|
|
|
|
|
|
group = parser.add_argument_group("searching")
|
|
|
|
aa("--k", default=10, type=int, help="nb of nearest neighbors")
|
|
aa("--nprobe", default=10, help="nb of IVF lists to probe")
|
|
|
|
args = parser.parse_args()
|
|
|
|
print("args:", args)
|
|
|
|
gt = gt[:, : args.k]
|
|
nlist = args.nlist
|
|
bits_per_code = args.bits_per_code
|
|
|
|
rs = np.random.RandomState(123)
|
|
|
|
res = faiss.StandardGpuResources()
|
|
|
|
# Use an RMM pool memory resource for device allocations
|
|
mr = rmm.mr.PoolMemoryResource(rmm.mr.CudaMemoryResource())
|
|
rmm.mr.set_current_device_resource(mr)
|
|
|
|
|
|
def eval_recall(neighbors, t):
|
|
speed = t * 1000 / nq
|
|
qps = 1000 / speed
|
|
|
|
corrects = (gt == neighbors).sum()
|
|
recall = corrects / (nq * args.k)
|
|
|
|
return recall, qps
|
|
|
|
|
|
def bench_train_milliseconds(trainVecs, use_cuvs):
|
|
config = faiss.GpuIndexIVFPQConfig()
|
|
config.use_cuvs = use_cuvs
|
|
index = faiss.GpuIndexIVFPQ(res, d, 1024, 32, 8, faiss.METRIC_L2, config)
|
|
t0 = time.time()
|
|
index.train(trainVecs)
|
|
return 1000 * (time.time() - t0)
|
|
|
|
|
|
# warmup
|
|
xw = rs.rand(nt, d)
|
|
bench_train_milliseconds(xw, True)
|
|
|
|
|
|
if args.bm_train:
|
|
print("=" * 40)
|
|
print("GPU Train Benchmarks")
|
|
print("=" * 40)
|
|
|
|
cuvs_gpu_train_time = bench_train_milliseconds(xt, True)
|
|
classical_gpu_train_time = bench_train_milliseconds(xt, False)
|
|
print(
|
|
"TRAIN, dim: %d, nlist %d, numTrain: %d, "
|
|
"classical GPU train time: %.3f milliseconds, "
|
|
"cuVS enabled GPU train time: %.3f milliseconds"
|
|
% (d, nlist, nt, classical_gpu_train_time, cuvs_gpu_train_time)
|
|
)
|
|
|
|
|
|
def bench_add_milliseconds(addVecs, index_cpu, use_cuvs):
|
|
# construct a GPU index using the same trained coarse quantizer
|
|
config = faiss.GpuClonerOptions()
|
|
config.use_cuvs = use_cuvs
|
|
index_gpu = faiss.index_cpu_to_gpu(res, 0, index_cpu, config)
|
|
assert index_gpu.is_trained
|
|
t0 = time.time()
|
|
index_gpu.add(addVecs)
|
|
return 1000 * (time.time() - t0)
|
|
|
|
|
|
if args.bm_add:
|
|
print("=" * 40)
|
|
print("GPU Add Benchmarks")
|
|
print("=" * 40)
|
|
quantizer = faiss.IndexFlatL2(d)
|
|
index_cpu = faiss.IndexIVFPQ(quantizer, d, 1024, 32, 8, faiss.METRIC_L2)
|
|
index_cpu.train(xt)
|
|
cuvs_gpu_add_time = bench_add_milliseconds(xb, index_cpu, True)
|
|
classical_gpu_add_time = bench_add_milliseconds(xb, index_cpu, False)
|
|
print(
|
|
"ADD, dim: %d, nlist %d, numAdd: %d, "
|
|
"classical GPU add time: %.3f milliseconds, "
|
|
"cuVS enabled GPU add time: %.3f milliseconds"
|
|
% (d, nlist, nb, classical_gpu_add_time, cuvs_gpu_add_time)
|
|
)
|
|
|
|
|
|
def bench_search_milliseconds(index, queryVecs, nprobe, k, use_cuvs):
|
|
co = faiss.GpuClonerOptions()
|
|
co.use_cuvs = use_cuvs
|
|
index_gpu = faiss.index_cpu_to_gpu(res, 0, index, co)
|
|
index_gpu.nprobe = nprobe
|
|
t0 = time.time()
|
|
_, I = index_gpu.search(queryVecs, k)
|
|
return I, 1000 * (time.time() - t0)
|
|
|
|
|
|
# Search benchmarks: both indexes have identical IVF centroids and lists.
|
|
if args.bm_search:
|
|
print("=" * 40)
|
|
print("GPU Search Benchmarks")
|
|
print("=" * 40)
|
|
index_cpu = faiss.IndexIVFPQ(quantizer, d, 1024, 32, 8, faiss.METRIC_L2)
|
|
index_cpu.train(xt)
|
|
index_cpu.add(xb)
|
|
|
|
cuvs_indices, cuvs_gpu_search_time = bench_search_milliseconds(
|
|
index_cpu, xq, args.nprobe, args.k, True
|
|
)
|
|
classical_gpu_indices, classical_gpu_search_time = (
|
|
bench_search_milliseconds(index_cpu, xq, args.nprobe, args.k, False)
|
|
)
|
|
cuvs_recall, cuvs_qps = eval_recall(cuvs_indices, cuvs_gpu_search_time)
|
|
classical_recall, classical_qps = eval_recall(
|
|
classical_gpu_indices, classical_gpu_search_time
|
|
)
|
|
print(
|
|
"SEARCH, dim: %d, nlist: %d, numVecs: %d, numQuery: %d, "
|
|
"nprobe: %d, k: %d, "
|
|
"classical GPU qps: %.3f, cuVS enabled GPU qps: %.3f"
|
|
% (d, nlist, nb, nq, args.nprobe, args.k, classical_qps, cuvs_qps)
|
|
)
|