Skip to content

Commit 76c67b7

Browse files
aknayarmeta-codesync[bot]
authored andcommitted
Update default Panorama batch size to 1024 (#5441)
Summary: ## Changes This PR unifies the default Panorama batch size to 1024 going forward. This was shown to offer better performance than the current values ([paper](https://arxiv.org/abs/2510.00566), figure 11). ## Performance ### IVFFlatPanorama SIFT1M | nprobe | qps_before | qps_after | speedup | |---|---|---|---| | 1 | 5980.4 | 6343.7 | 1.06x | | 2 | 3537.7 | 3933.2 | 1.11x | | 4 | 2105.4 | 2419.9 | 1.15x | | 8 | 1245.2 | 1466.4 | 1.18x | | 16 | 729.2 | 887.4 | 1.22x | | 32 | 429.6 | 538.0 | 1.25x | | 64 | 249.0 | 319.3 | 1.28x | GIST1M | nprobe | qps_before | qps_after | speedup | |---|---|---|---| | 1 | 1826.0 | 1494.7 | 0.82x | | 2 | 1091.0 | 1001.7 | 0.92x | | 4 | 642.2 | 639.0 | 0.99x | | 8 | 368.7 | 392.7 | 1.07x | | 16 | 212.2 | 233.9 | 1.10x | | 32 | 122.2 | 138.6 | 1.13x | | 64 | 71.3 | 82.6 | 1.16x | ## FlatL2Panorama SIFT1M | qps_before | qps_after | speedup | |---|---|---| | 124.8 | 133.4 | 1.07x | GIST1M | qps_before | qps_after | speedup | |---|---|---| | 44.1 | 46.2 | 1.05x | Pull Request resolved: #5441 Reviewed By: alibeklfc Differential Revision: D114387063 Pulled By: mnorris11 fbshipit-source-id: b71ba6ced074c4998aee3183df236b692626cd34
1 parent 3b50bab commit 76c67b7

8 files changed

Lines changed: 82 additions & 37 deletions

File tree

benchs/bench_flat_l2_panorama.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,12 @@ def build_index(name):
6969

7070

7171
nlevels = 16 if args.dataset == "gist1m" else 8
72-
batch_size = 512
7372

7473
plt.figure(figsize=(8, 6), dpi=80)
7574

7675
names = [
7776
"Flat",
78-
f"PCA{d},FlatL2Panorama{nlevels}_{batch_size}",
77+
f"PCA{d},FlatL2Panorama{nlevels}",
7978
]
8079

8180
labels = []

benchs/bench_ivf_flat_panorama.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ def eval_and_plot(name, plot=True):
9898
eval_and_plot(f"IVF{nlist},Flat")
9999

100100
# IVFFlatPanorama (with PCA transform to concentrate energy in early dimensions)
101-
eval_and_plot(f"PCA{d},IVF{nlist},FlatPanorama{nlevels}_{1024}")
101+
eval_and_plot(f"PCA{d},IVF{nlist},FlatPanorama{nlevels}")
102102

103103
dataset_label = args.dataset.upper()
104104
plt.title(f"IVF Flat Indexes on {dataset_label}")

faiss/IndexFlat.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ struct IndexFlatL2Panorama : IndexFlatPanorama {
176176
explicit IndexFlatL2Panorama(
177177
idx_t d_in,
178178
size_t n_levels_in,
179-
size_t batch_size_in = 512)
179+
size_t batch_size_in = Panorama::kDefaultBatchSize)
180180
: IndexFlatPanorama(d_in, METRIC_L2, n_levels_in, batch_size_in) {}
181181
};
182182

@@ -189,7 +189,7 @@ struct IndexFlatIPPanorama : IndexFlatPanorama {
189189
explicit IndexFlatIPPanorama(
190190
idx_t d_in,
191191
size_t n_levels_in,
192-
size_t batch_size_in = 512)
192+
size_t batch_size_in = Panorama::kDefaultBatchSize)
193193
: IndexFlatPanorama(
194194
d_in,
195195
METRIC_INNER_PRODUCT,

faiss/impl/Panorama.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,8 @@ inline auto with_bool(bool value, Lambda&& fn) {
235235
* accelerating the refinement stage.
236236
*/
237237
struct Panorama {
238-
static constexpr size_t kDefaultBatchSize = 128;
238+
static constexpr size_t kDefaultBatchSize = 1024;
239+
static constexpr size_t kLegacyBatchSize = 128;
239240

240241
size_t d = 0;
241242
size_t code_size = 0;

faiss/impl/index_read.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,7 @@ std::unique_ptr<InvertedLists> read_InvertedLists_up(
545545
READ1(n_levels);
546546
FAISS_THROW_IF_NOT_FMT(
547547
n_levels > 0, "invalid ilpn n_levels %zd", n_levels);
548-
constexpr size_t bs = Panorama::kDefaultBatchSize;
548+
constexpr size_t bs = Panorama::kLegacyBatchSize;
549549
auto ailp = std::make_unique<ArrayInvertedListsPanorama>(
550550
nlist, code_size, n_levels, bs);
551551
std::vector<size_t> sizes(nlist);
@@ -2136,7 +2136,7 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
21362136
read_ivf_header(ivfp.get(), f);
21372137
ivfp->code_size = ivfp->d * sizeof(float);
21382138
READ1(ivfp->n_levels);
2139-
ivfp->batch_size = Panorama::kDefaultBatchSize;
2139+
ivfp->batch_size = Panorama::kLegacyBatchSize;
21402140
read_InvertedLists(*ivfp, f, io_flags);
21412141
idx = std::move(ivfp);
21422142
} else if (h == fourcc("IwP2")) {

faiss/impl/index_write.cpp

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -275,20 +275,12 @@ void write_InvertedLists(const InvertedLists* ils, IOWriter* f) {
275275
} else if (
276276
const auto& ailp =
277277
dynamic_cast<const ArrayInvertedListsPanorama*>(ils)) {
278-
if (ailp->pano.batch_size == Panorama::kDefaultBatchSize) {
279-
uint32_t h = fourcc("ilpn");
280-
WRITE1(h);
281-
WRITE1(ailp->nlist);
282-
WRITE1(ailp->code_size);
283-
WRITE1(ailp->n_levels);
284-
} else {
285-
uint32_t h = fourcc("ilp2");
286-
WRITE1(h);
287-
WRITE1(ailp->nlist);
288-
WRITE1(ailp->code_size);
289-
WRITE1(ailp->n_levels);
290-
WRITE1(ailp->pano.batch_size);
291-
}
278+
uint32_t h = fourcc("ilp2");
279+
WRITE1(h);
280+
WRITE1(ailp->nlist);
281+
WRITE1(ailp->code_size);
282+
WRITE1(ailp->n_levels);
283+
WRITE1(ailp->pano.batch_size);
292284
uint32_t list_type = fourcc("full");
293285
WRITE1(list_type);
294286
std::vector<size_t> sizes;
@@ -736,18 +728,11 @@ void write_index(const Index* idx, IOWriter* f, int io_flags) {
736728
} else if (
737729
const IndexIVFFlatPanorama* ivfp =
738730
dynamic_cast<const IndexIVFFlatPanorama*>(idx)) {
739-
if (ivfp->batch_size == Panorama::kDefaultBatchSize) {
740-
uint32_t h = fourcc("IwPn");
741-
WRITE1(h);
742-
write_ivf_header(ivfp, f);
743-
WRITE1(ivfp->n_levels);
744-
} else {
745-
uint32_t h = fourcc("IwP2");
746-
WRITE1(h);
747-
write_ivf_header(ivfp, f);
748-
WRITE1(ivfp->n_levels);
749-
WRITE1(ivfp->batch_size);
750-
}
731+
uint32_t h = fourcc("IwP2");
732+
WRITE1(h);
733+
write_ivf_header(ivfp, f);
734+
WRITE1(ivfp->n_levels);
735+
WRITE1(ivfp->batch_size);
751736
write_InvertedLists(ivfp->invlists, f);
752737
} else if (
753738
const IndexIVFFlat* ivfl_2 =

faiss/index_factory.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ IndexIVF* parse_IndexIVF(
385385
}
386386
if (match("FlatPanorama([0-9]+)?(_([0-9]+))?")) {
387387
int nlevels = mres_to_int(sm[1], 8); // default to 8 levels
388-
int bs = mres_to_int(sm[3], 128);
388+
int bs = mres_to_int(sm[3], Panorama::kDefaultBatchSize);
389389
return new IndexIVFFlatPanorama(
390390
get_q(), d, nlist, nlevels, mt, own_il, bs);
391391
}

tests/test_ivf_flat_panorama.py

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,17 @@ def create_panorama(
7171
nprobe=None,
7272
make_direct_map=False,
7373
metric=faiss.METRIC_L2,
74+
batch_size=faiss.Panorama.kDefaultBatchSize,
7475
):
7576
"""Create and initialize IndexIVFFlatPanorama."""
7677
quantizer = (
7778
faiss.IndexFlatL2(d)
7879
if metric == faiss.METRIC_L2
7980
else faiss.IndexFlatIP(d)
8081
)
81-
index = faiss.IndexIVFFlatPanorama(quantizer, d, nlist, nlevels, metric)
82+
index = faiss.IndexIVFFlatPanorama(
83+
quantizer, d, nlist, nlevels, metric, True, batch_size
84+
)
8285
index.train(xt)
8386
if make_direct_map:
8487
index.make_direct_map()
@@ -769,6 +772,56 @@ def test_serialization(self):
769772
np.testing.assert_array_equal(I_before, I_after)
770773
np.testing.assert_array_equal(D_before, D_after)
771774

775+
def test_read_legacy_format(self):
776+
"""Indexes serialized in the legacy "IwPn"/"ilpn" format (which does
777+
not store batch_size and implies the legacy value of 128) must keep
778+
deserializing correctly, whatever the current default batch_size is.
779+
780+
The current code always writes the explicit-batch_size format
781+
("IwP2"/"ilp2"), so a legacy stream is reconstructed here by
782+
transforming a serialized index: swap the fourccs and drop the two
783+
8-byte batch_size fields.
784+
"""
785+
d, nlist, nlevels, nb, nq, k = 32, 4, 8, 2000, 10, 10
786+
legacy_bs = 128
787+
rng = np.random.RandomState(123)
788+
xb = rng.rand(nb, d).astype("float32")
789+
xq = rng.rand(nq, d).astype("float32")
790+
791+
quantizer = faiss.IndexFlatL2(d)
792+
index = faiss.IndexIVFFlatPanorama(
793+
quantizer, d, nlist, nlevels, faiss.METRIC_L2, True, legacy_bs
794+
)
795+
index.train(xb)
796+
index.add(xb)
797+
index.nprobe = nlist
798+
D_ref, I_ref = index.search(xq, k)
799+
800+
buf = faiss.serialize_index(index).tobytes()
801+
# layout: "IwP2" | ivf header | n_levels (8) | batch_size (8) |
802+
# "ilp2" | nlist (8) | code_size (8) | n_levels (8) |
803+
# batch_size (8) | inverted lists data
804+
self.assertEqual(buf[:4], b"IwP2")
805+
self.assertEqual(buf.count(b"ilp2"), 1)
806+
p = buf.index(b"ilp2")
807+
legacy = (
808+
b"IwPn"
809+
+ buf[4 : p - 8] # drop the IVF-level batch_size field
810+
+ b"ilpn"
811+
+ buf[p + 4 : p + 28] # nlist, code_size, n_levels
812+
+ buf[p + 36 :] # drop the invlist-level batch_size field
813+
)
814+
815+
index_legacy = faiss.deserialize_index(
816+
np.frombuffer(legacy, dtype=np.uint8)
817+
)
818+
self.assertIsInstance(index_legacy, faiss.IndexIVFFlatPanorama)
819+
self.assertEqual(index_legacy.batch_size, legacy_bs)
820+
index_legacy.nprobe = nlist
821+
D_legacy, I_legacy = index_legacy.search(xq, k)
822+
np.testing.assert_array_equal(I_ref, I_legacy)
823+
np.testing.assert_array_equal(D_ref, D_legacy)
824+
772825
def test_ratio_dims_scanned(self):
773826
"""Test the correctness of the ratio of dimensions scanned"""
774827
d, nb, nq, nlist, k = 128, 500000, 1, 1, 1
@@ -798,7 +851,14 @@ def test_ratio_dims_scanned(self):
798851
with self.subTest(nlevels=nlevels):
799852
faiss.cvar.indexPanorama_stats.reset()
800853
index = self.create_panorama(
801-
d, nlist, nlevels, xt, xb, nprobe=1, metric=metric
854+
d,
855+
nlist,
856+
nlevels,
857+
xt,
858+
xb,
859+
nprobe=1,
860+
metric=metric,
861+
batch_size=128,
802862
)
803863
D, I = index.search(xq, k)
804864
self.assert_search_results_equal(D_base, I_base, D, I)

0 commit comments

Comments
 (0)