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: https://github.com/facebookresearch/faiss/pull/5441

Reviewed By: alibeklfc

Differential Revision: D114387063

Pulled By: mnorris11

fbshipit-source-id: b71ba6ced074c4998aee3183df236b692626cd34
This commit is contained in:
Akash Nayar
2026-08-03 10:53:18 -07:00
committed by meta-codesync[bot]
parent 3b50babc59
commit 76c67b7b73
8 changed files with 82 additions and 37 deletions
+1 -2
View File
@@ -69,13 +69,12 @@ def build_index(name):
nlevels = 16 if args.dataset == "gist1m" else 8
batch_size = 512
plt.figure(figsize=(8, 6), dpi=80)
names = [
"Flat",
f"PCA{d},FlatL2Panorama{nlevels}_{batch_size}",
f"PCA{d},FlatL2Panorama{nlevels}",
]
labels = []
+1 -1
View File
@@ -98,7 +98,7 @@ plt.figure(figsize=(8, 6), dpi=80)
eval_and_plot(f"IVF{nlist},Flat")
# IVFFlatPanorama (with PCA transform to concentrate energy in early dimensions)
eval_and_plot(f"PCA{d},IVF{nlist},FlatPanorama{nlevels}_{1024}")
eval_and_plot(f"PCA{d},IVF{nlist},FlatPanorama{nlevels}")
dataset_label = args.dataset.upper()
plt.title(f"IVF Flat Indexes on {dataset_label}")
+2 -2
View File
@@ -176,7 +176,7 @@ struct IndexFlatL2Panorama : IndexFlatPanorama {
explicit IndexFlatL2Panorama(
idx_t d_in,
size_t n_levels_in,
size_t batch_size_in = 512)
size_t batch_size_in = Panorama::kDefaultBatchSize)
: IndexFlatPanorama(d_in, METRIC_L2, n_levels_in, batch_size_in) {}
};
@@ -189,7 +189,7 @@ struct IndexFlatIPPanorama : IndexFlatPanorama {
explicit IndexFlatIPPanorama(
idx_t d_in,
size_t n_levels_in,
size_t batch_size_in = 512)
size_t batch_size_in = Panorama::kDefaultBatchSize)
: IndexFlatPanorama(
d_in,
METRIC_INNER_PRODUCT,
+2 -1
View File
@@ -235,7 +235,8 @@ inline auto with_bool(bool value, Lambda&& fn) {
* accelerating the refinement stage.
*/
struct Panorama {
static constexpr size_t kDefaultBatchSize = 128;
static constexpr size_t kDefaultBatchSize = 1024;
static constexpr size_t kLegacyBatchSize = 128;
size_t d = 0;
size_t code_size = 0;
+2 -2
View File
@@ -545,7 +545,7 @@ std::unique_ptr<InvertedLists> read_InvertedLists_up(
READ1(n_levels);
FAISS_THROW_IF_NOT_FMT(
n_levels > 0, "invalid ilpn n_levels %zd", n_levels);
constexpr size_t bs = Panorama::kDefaultBatchSize;
constexpr size_t bs = Panorama::kLegacyBatchSize;
auto ailp = std::make_unique<ArrayInvertedListsPanorama>(
nlist, code_size, n_levels, bs);
std::vector<size_t> sizes(nlist);
@@ -2136,7 +2136,7 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
read_ivf_header(ivfp.get(), f);
ivfp->code_size = ivfp->d * sizeof(float);
READ1(ivfp->n_levels);
ivfp->batch_size = Panorama::kDefaultBatchSize;
ivfp->batch_size = Panorama::kLegacyBatchSize;
read_InvertedLists(*ivfp, f, io_flags);
idx = std::move(ivfp);
} else if (h == fourcc("IwP2")) {
+11 -26
View File
@@ -275,20 +275,12 @@ void write_InvertedLists(const InvertedLists* ils, IOWriter* f) {
} else if (
const auto& ailp =
dynamic_cast<const ArrayInvertedListsPanorama*>(ils)) {
if (ailp->pano.batch_size == Panorama::kDefaultBatchSize) {
uint32_t h = fourcc("ilpn");
WRITE1(h);
WRITE1(ailp->nlist);
WRITE1(ailp->code_size);
WRITE1(ailp->n_levels);
} else {
uint32_t h = fourcc("ilp2");
WRITE1(h);
WRITE1(ailp->nlist);
WRITE1(ailp->code_size);
WRITE1(ailp->n_levels);
WRITE1(ailp->pano.batch_size);
}
uint32_t h = fourcc("ilp2");
WRITE1(h);
WRITE1(ailp->nlist);
WRITE1(ailp->code_size);
WRITE1(ailp->n_levels);
WRITE1(ailp->pano.batch_size);
uint32_t list_type = fourcc("full");
WRITE1(list_type);
std::vector<size_t> sizes;
@@ -736,18 +728,11 @@ void write_index(const Index* idx, IOWriter* f, int io_flags) {
} else if (
const IndexIVFFlatPanorama* ivfp =
dynamic_cast<const IndexIVFFlatPanorama*>(idx)) {
if (ivfp->batch_size == Panorama::kDefaultBatchSize) {
uint32_t h = fourcc("IwPn");
WRITE1(h);
write_ivf_header(ivfp, f);
WRITE1(ivfp->n_levels);
} else {
uint32_t h = fourcc("IwP2");
WRITE1(h);
write_ivf_header(ivfp, f);
WRITE1(ivfp->n_levels);
WRITE1(ivfp->batch_size);
}
uint32_t h = fourcc("IwP2");
WRITE1(h);
write_ivf_header(ivfp, f);
WRITE1(ivfp->n_levels);
WRITE1(ivfp->batch_size);
write_InvertedLists(ivfp->invlists, f);
} else if (
const IndexIVFFlat* ivfl_2 =
+1 -1
View File
@@ -385,7 +385,7 @@ IndexIVF* parse_IndexIVF(
}
if (match("FlatPanorama([0-9]+)?(_([0-9]+))?")) {
int nlevels = mres_to_int(sm[1], 8); // default to 8 levels
int bs = mres_to_int(sm[3], 128);
int bs = mres_to_int(sm[3], Panorama::kDefaultBatchSize);
return new IndexIVFFlatPanorama(
get_q(), d, nlist, nlevels, mt, own_il, bs);
}
+62 -2
View File
@@ -71,6 +71,7 @@ class TestIndexIVFFlatPanorama(unittest.TestCase):
nprobe=None,
make_direct_map=False,
metric=faiss.METRIC_L2,
batch_size=faiss.Panorama.kDefaultBatchSize,
):
"""Create and initialize IndexIVFFlatPanorama."""
quantizer = (
@@ -78,7 +79,9 @@ class TestIndexIVFFlatPanorama(unittest.TestCase):
if metric == faiss.METRIC_L2
else faiss.IndexFlatIP(d)
)
index = faiss.IndexIVFFlatPanorama(quantizer, d, nlist, nlevels, metric)
index = faiss.IndexIVFFlatPanorama(
quantizer, d, nlist, nlevels, metric, True, batch_size
)
index.train(xt)
if make_direct_map:
index.make_direct_map()
@@ -769,6 +772,56 @@ class TestIndexIVFFlatPanorama(unittest.TestCase):
np.testing.assert_array_equal(I_before, I_after)
np.testing.assert_array_equal(D_before, D_after)
def test_read_legacy_format(self):
"""Indexes serialized in the legacy "IwPn"/"ilpn" format (which does
not store batch_size and implies the legacy value of 128) must keep
deserializing correctly, whatever the current default batch_size is.
The current code always writes the explicit-batch_size format
("IwP2"/"ilp2"), so a legacy stream is reconstructed here by
transforming a serialized index: swap the fourccs and drop the two
8-byte batch_size fields.
"""
d, nlist, nlevels, nb, nq, k = 32, 4, 8, 2000, 10, 10
legacy_bs = 128
rng = np.random.RandomState(123)
xb = rng.rand(nb, d).astype("float32")
xq = rng.rand(nq, d).astype("float32")
quantizer = faiss.IndexFlatL2(d)
index = faiss.IndexIVFFlatPanorama(
quantizer, d, nlist, nlevels, faiss.METRIC_L2, True, legacy_bs
)
index.train(xb)
index.add(xb)
index.nprobe = nlist
D_ref, I_ref = index.search(xq, k)
buf = faiss.serialize_index(index).tobytes()
# layout: "IwP2" | ivf header | n_levels (8) | batch_size (8) |
# "ilp2" | nlist (8) | code_size (8) | n_levels (8) |
# batch_size (8) | inverted lists data
self.assertEqual(buf[:4], b"IwP2")
self.assertEqual(buf.count(b"ilp2"), 1)
p = buf.index(b"ilp2")
legacy = (
b"IwPn"
+ buf[4 : p - 8] # drop the IVF-level batch_size field
+ b"ilpn"
+ buf[p + 4 : p + 28] # nlist, code_size, n_levels
+ buf[p + 36 :] # drop the invlist-level batch_size field
)
index_legacy = faiss.deserialize_index(
np.frombuffer(legacy, dtype=np.uint8)
)
self.assertIsInstance(index_legacy, faiss.IndexIVFFlatPanorama)
self.assertEqual(index_legacy.batch_size, legacy_bs)
index_legacy.nprobe = nlist
D_legacy, I_legacy = index_legacy.search(xq, k)
np.testing.assert_array_equal(I_ref, I_legacy)
np.testing.assert_array_equal(D_ref, D_legacy)
def test_ratio_dims_scanned(self):
"""Test the correctness of the ratio of dimensions scanned"""
d, nb, nq, nlist, k = 128, 500000, 1, 1, 1
@@ -798,7 +851,14 @@ class TestIndexIVFFlatPanorama(unittest.TestCase):
with self.subTest(nlevels=nlevels):
faiss.cvar.indexPanorama_stats.reset()
index = self.create_panorama(
d, nlist, nlevels, xt, xb, nprobe=1, metric=metric
d,
nlist,
nlevels,
xt,
xb,
nprobe=1,
metric=metric,
batch_size=128,
)
D, I = index.search(xq, k)
self.assert_search_results_equal(D_base, I_base, D, I)