@@ -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