faiss 0.6.2 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/lib/faiss/version.rb +1 -1
  4. data/vendor/faiss/faiss/AutoTune.cpp +3 -1
  5. data/vendor/faiss/faiss/Clustering.cpp +9 -1
  6. data/vendor/faiss/faiss/Clustering.h +8 -0
  7. data/vendor/faiss/faiss/IVFlib.cpp +14 -3
  8. data/vendor/faiss/faiss/Index.h +2 -2
  9. data/vendor/faiss/faiss/IndexAdditiveQuantizer.cpp +9 -10
  10. data/vendor/faiss/faiss/IndexAdditiveQuantizerFastScan.cpp +2 -3
  11. data/vendor/faiss/faiss/IndexBinaryFromFloat.cpp +24 -4
  12. data/vendor/faiss/faiss/IndexBinaryHNSW.cpp +16 -145
  13. data/vendor/faiss/faiss/IndexBinaryHNSW.h +0 -6
  14. data/vendor/faiss/faiss/IndexBinaryHash.cpp +5 -9
  15. data/vendor/faiss/faiss/IndexBinaryIVF.cpp +8 -18
  16. data/vendor/faiss/faiss/IndexBinaryIVF.h +8 -1
  17. data/vendor/faiss/faiss/IndexEDEN.cpp +273 -0
  18. data/vendor/faiss/faiss/IndexEDEN.h +57 -0
  19. data/vendor/faiss/faiss/IndexFastScan.cpp +15 -4
  20. data/vendor/faiss/faiss/IndexFlat.cpp +21 -54
  21. data/vendor/faiss/faiss/IndexFlat.h +2 -2
  22. data/vendor/faiss/faiss/IndexHNSW.cpp +311 -102
  23. data/vendor/faiss/faiss/IndexHNSW.h +31 -7
  24. data/vendor/faiss/faiss/IndexIDMap.cpp +26 -8
  25. data/vendor/faiss/faiss/IndexIDMap.h +2 -0
  26. data/vendor/faiss/faiss/IndexIVF.cpp +36 -10
  27. data/vendor/faiss/faiss/IndexIVFAdditiveQuantizer.cpp +1 -1
  28. data/vendor/faiss/faiss/IndexIVFAdditiveQuantizerFastScan.cpp +3 -4
  29. data/vendor/faiss/faiss/IndexIVFEDEN.cpp +302 -0
  30. data/vendor/faiss/faiss/IndexIVFEDEN.h +70 -0
  31. data/vendor/faiss/faiss/IndexIVFFastScan.cpp +5 -6
  32. data/vendor/faiss/faiss/IndexIVFFlat.cpp +3 -4
  33. data/vendor/faiss/faiss/IndexIVFIndependentQuantizer.cpp +1 -1
  34. data/vendor/faiss/faiss/IndexIVFPQ.cpp +49 -23
  35. data/vendor/faiss/faiss/IndexIVFPQ.h +11 -0
  36. data/vendor/faiss/faiss/IndexIVFPQFastScan.cpp +0 -1
  37. data/vendor/faiss/faiss/IndexIVFRaBitQ.cpp +19 -49
  38. data/vendor/faiss/faiss/IndexIVFRaBitQFastScan.cpp +180 -76
  39. data/vendor/faiss/faiss/IndexIVFRaBitQFastScan.h +5 -4
  40. data/vendor/faiss/faiss/IndexIVFSpectralHash.cpp +8 -6
  41. data/vendor/faiss/faiss/IndexLSH.cpp +2 -3
  42. data/vendor/faiss/faiss/IndexLattice.cpp +5 -0
  43. data/vendor/faiss/faiss/IndexNNDescent.cpp +9 -2
  44. data/vendor/faiss/faiss/IndexNSG.cpp +7 -2
  45. data/vendor/faiss/faiss/IndexPQ.cpp +6 -8
  46. data/vendor/faiss/faiss/IndexPreTransform.cpp +15 -0
  47. data/vendor/faiss/faiss/IndexRaBitQ.cpp +2 -2
  48. data/vendor/faiss/faiss/IndexRaBitQFastScan.cpp +1 -2
  49. data/vendor/faiss/faiss/IndexRaBitQFastScan.h +5 -1
  50. data/vendor/faiss/faiss/IndexRefine.cpp +30 -1
  51. data/vendor/faiss/faiss/IndexReplicas.cpp +1 -2
  52. data/vendor/faiss/faiss/IndexShards.cpp +5 -5
  53. data/vendor/faiss/faiss/IndexShardsIVF.cpp +6 -5
  54. data/vendor/faiss/faiss/MetaIndexes.cpp +2 -4
  55. data/vendor/faiss/faiss/SuperKMeans.cpp +286 -247
  56. data/vendor/faiss/faiss/SuperKMeans.h +33 -2
  57. data/vendor/faiss/faiss/VectorTransform.cpp +71 -2
  58. data/vendor/faiss/faiss/VectorTransform.h +3 -0
  59. data/vendor/faiss/faiss/clone_index.cpp +8 -0
  60. data/vendor/faiss/faiss/factory_tools.cpp +47 -4
  61. data/vendor/faiss/faiss/gpu/GpuCloner.cpp +11 -11
  62. data/vendor/faiss/faiss/gpu/GpuClonerOptions.h +1 -5
  63. data/vendor/faiss/faiss/gpu/GpuDistance.h +2 -5
  64. data/vendor/faiss/faiss/gpu/GpuIndex.h +38 -16
  65. data/vendor/faiss/faiss/gpu/GpuIndexCagra.h +71 -1
  66. data/vendor/faiss/faiss/gpu/GpuIndexIVF.h +17 -0
  67. data/vendor/faiss/faiss/gpu/GpuIndexIVFScalarQuantizer.h +16 -0
  68. data/vendor/faiss/faiss/gpu/perf/PerfClustering.cpp +1 -1
  69. data/vendor/faiss/faiss/gpu/perf/PerfIVFPQAdd.cpp +2 -2
  70. data/vendor/faiss/faiss/gpu/test/TestGpuIndexIVFScalarQuantizer.cpp +180 -0
  71. data/vendor/faiss/faiss/gpu_metal/MetalIndexIVFFlat.h +1 -5
  72. data/vendor/faiss/faiss/gpu_metal/MetalIndexIVFPQ.h +88 -0
  73. data/vendor/faiss/faiss/gpu_metal/impl/MetalIVFPQ.h +134 -0
  74. data/vendor/faiss/faiss/impl/AdditiveQuantizer.cpp +1 -1
  75. data/vendor/faiss/faiss/impl/ClusteringInitialization.cpp +7 -4
  76. data/vendor/faiss/faiss/impl/DistanceComputer.h +34 -0
  77. data/vendor/faiss/faiss/impl/EDENQuantizer.h +119 -0
  78. data/vendor/faiss/faiss/impl/HNSW.cpp +528 -267
  79. data/vendor/faiss/faiss/impl/HNSW.h +46 -7
  80. data/vendor/faiss/faiss/impl/IDSelector.h +44 -0
  81. data/vendor/faiss/faiss/impl/LocalSearchQuantizer.cpp +2 -2
  82. data/vendor/faiss/faiss/impl/NNDescent.cpp +10 -3
  83. data/vendor/faiss/faiss/impl/NSG.cpp +3 -1
  84. data/vendor/faiss/faiss/impl/Panorama.h +20 -9
  85. data/vendor/faiss/faiss/impl/PolysemousTraining.cpp +152 -84
  86. data/vendor/faiss/faiss/impl/ProductQuantizer.cpp +38 -26
  87. data/vendor/faiss/faiss/impl/RaBitQUtils.cpp +45 -37
  88. data/vendor/faiss/faiss/impl/RaBitQUtils.h +35 -0
  89. data/vendor/faiss/faiss/impl/RaBitQuantizer.cpp +239 -72
  90. data/vendor/faiss/faiss/impl/RaBitQuantizer.h +66 -4
  91. data/vendor/faiss/faiss/impl/RaBitQuantizerMultiBit.cpp +4 -13
  92. data/vendor/faiss/faiss/impl/ResultHandler.h +34 -34
  93. data/vendor/faiss/faiss/impl/ScalarQuantizer.cpp +287 -84
  94. data/vendor/faiss/faiss/impl/ScalarQuantizer.h +26 -10
  95. data/vendor/faiss/faiss/impl/ThreadedIndex-inl.h +2 -2
  96. data/vendor/faiss/faiss/impl/VisitedTable.cpp +22 -2
  97. data/vendor/faiss/faiss/impl/VisitedTable.h +20 -0
  98. data/vendor/faiss/faiss/impl/binary_hamming/IndexBinaryIVF_impl.h +90 -14
  99. data/vendor/faiss/faiss/impl/binary_hamming/avx2.cpp +4 -4
  100. data/vendor/faiss/faiss/impl/expanded_scanners.h +5 -1
  101. data/vendor/faiss/faiss/impl/fast_scan/decompose_qbs.h +1 -0
  102. data/vendor/faiss/faiss/impl/fast_scan/dispatching.h +35 -2
  103. data/vendor/faiss/faiss/impl/hnsw/LockVector.cpp +1 -1
  104. data/vendor/faiss/faiss/impl/index_read.cpp +491 -50
  105. data/vendor/faiss/faiss/impl/index_write.cpp +86 -30
  106. data/vendor/faiss/faiss/impl/lattice_Zn.cpp +8 -9
  107. data/vendor/faiss/faiss/impl/platform_macros.h +3 -1
  108. data/vendor/faiss/faiss/impl/polysemous_training/avx512.cpp +284 -0
  109. data/vendor/faiss/faiss/impl/polysemous_training/dispatch.h +115 -0
  110. data/vendor/faiss/faiss/impl/pq_code_distance/IVFPQScanner_impl.h +73 -39
  111. data/vendor/faiss/faiss/impl/pq_code_distance/IVFPQ_QueryTables.cpp +0 -1
  112. data/vendor/faiss/faiss/impl/pq_code_distance/PQDistanceComputer_impl.h +26 -15
  113. data/vendor/faiss/faiss/impl/pq_code_distance/avx2.cpp +4 -4
  114. data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-generic.cpp +4 -4
  115. data/vendor/faiss/faiss/impl/result_handler/ResultHandler.cpp +195 -0
  116. data/vendor/faiss/faiss/impl/result_handler/avx2.cpp +133 -0
  117. data/vendor/faiss/faiss/impl/result_handler/avx512.cpp +281 -0
  118. data/vendor/faiss/faiss/impl/scalar_quantizer/EDENQuantizer-avx2.cpp +72 -0
  119. data/vendor/faiss/faiss/impl/scalar_quantizer/EDENQuantizer-avx512.cpp +228 -0
  120. data/vendor/faiss/faiss/impl/scalar_quantizer/EDENQuantizer.cpp +887 -0
  121. data/vendor/faiss/faiss/impl/scalar_quantizer/distance_computers.h +2 -2
  122. data/vendor/faiss/faiss/impl/scalar_quantizer/quantizers.h +9 -8
  123. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx2.cpp +90 -24
  124. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512-impl.h +30 -30
  125. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512-spr.cpp +4 -5
  126. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512.cpp +101 -34
  127. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-dispatch.h +169 -13
  128. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-neon.cpp +125 -26
  129. data/vendor/faiss/faiss/impl/simd_dispatch.h +70 -31
  130. data/vendor/faiss/faiss/index_factory.cpp +40 -7
  131. data/vendor/faiss/faiss/invlists/DirectMap.cpp +1 -1
  132. data/vendor/faiss/faiss/invlists/InvertedLists.cpp +9 -6
  133. data/vendor/faiss/faiss/invlists/OnDiskInvertedLists.cpp +29 -8
  134. data/vendor/faiss/faiss/python/python_callbacks.cpp +3 -1
  135. data/vendor/faiss/faiss/svs/IndexSVSFaissUtils.h +60 -0
  136. data/vendor/faiss/faiss/svs/IndexSVSFlat.cpp +26 -1
  137. data/vendor/faiss/faiss/svs/IndexSVSFlat.h +13 -0
  138. data/vendor/faiss/faiss/svs/IndexSVSIVF.cpp +1 -1
  139. data/vendor/faiss/faiss/svs/IndexSVSIVFLeanVec.cpp +1 -1
  140. data/vendor/faiss/faiss/svs/IndexSVSVamana.cpp +47 -5
  141. data/vendor/faiss/faiss/svs/IndexSVSVamana.h +23 -3
  142. data/vendor/faiss/faiss/svs/IndexSVSVamanaLVQ.cpp +4 -2
  143. data/vendor/faiss/faiss/svs/IndexSVSVamanaLVQ.h +2 -1
  144. data/vendor/faiss/faiss/svs/IndexSVSVamanaLeanVec.cpp +10 -4
  145. data/vendor/faiss/faiss/svs/IndexSVSVamanaLeanVec.h +2 -1
  146. data/vendor/faiss/faiss/utils/approx_topk_hamming/approx_topk_hamming.h +1 -1
  147. data/vendor/faiss/faiss/utils/distances.cpp +30 -11
  148. data/vendor/faiss/faiss/utils/distances_dispatch.h +30 -24
  149. data/vendor/faiss/faiss/utils/distances_fused/distances_fused.cpp +1 -1
  150. data/vendor/faiss/faiss/utils/distances_simd.cpp +4 -3
  151. data/vendor/faiss/faiss/utils/extra_distances.cpp +4 -14
  152. data/vendor/faiss/faiss/utils/extra_distances.h +1 -2
  153. data/vendor/faiss/faiss/utils/hamming.cpp +16 -10
  154. data/vendor/faiss/faiss/utils/hamming.h +10 -1
  155. data/vendor/faiss/faiss/utils/hamming_distance/common.h +14 -3
  156. data/vendor/faiss/faiss/utils/hamming_distance/hamming_avx512_vpopcnt.cpp +24 -0
  157. data/vendor/faiss/faiss/utils/hamming_distance/hamming_computer-avx512.h +1 -1
  158. data/vendor/faiss/faiss/utils/hamming_distance/{hamming_computer-avx512_spr.h → hamming_computer-avx512_vpopcnt.h} +85 -24
  159. data/vendor/faiss/faiss/utils/hamming_distance/hamming_impl.h +141 -0
  160. data/vendor/faiss/faiss/utils/quantize_lut.cpp +29 -8
  161. data/vendor/faiss/faiss/utils/rabitq_simd.h +202 -0
  162. data/vendor/faiss/faiss/utils/simd_impl/distances_arm_sve.cpp +194 -30
  163. data/vendor/faiss/faiss/utils/simd_impl/distances_avx2.cpp +0 -1
  164. data/vendor/faiss/faiss/utils/simd_impl/distances_avx512.cpp +263 -15
  165. data/vendor/faiss/faiss/utils/simd_impl/distances_rvv.cpp +198 -18
  166. data/vendor/faiss/faiss/utils/simd_impl/rabitq_avx2.cpp +245 -0
  167. data/vendor/faiss/faiss/utils/simd_impl/rabitq_avx512.cpp +330 -40
  168. data/vendor/faiss/faiss/utils/simd_impl/{rabitq_avx512_spr.cpp → rabitq_avx512_vpopcnt.cpp} +112 -23
  169. data/vendor/faiss/faiss/utils/simd_impl/rabitq_neon.cpp +11 -0
  170. data/vendor/faiss/faiss/utils/simd_impl/rabitq_rvv.cpp +143 -6
  171. data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_dispatch.h +2 -7
  172. data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_kernels.h +6 -1
  173. data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_kernels_sve.cpp +34 -0
  174. data/vendor/faiss/faiss/utils/simd_levels.cpp +196 -47
  175. data/vendor/faiss/faiss/utils/simd_levels.h +33 -8
  176. data/vendor/faiss/faiss/utils/utils.cpp +9 -27
  177. metadata +21 -5
  178. data/vendor/faiss/faiss/utils/hamming_distance/hamming_avx512_spr.cpp +0 -15
@@ -30,11 +30,13 @@
30
30
  #include <faiss/Index2Layer.h>
31
31
  #include <faiss/IndexAdditiveQuantizer.h>
32
32
  #include <faiss/IndexAdditiveQuantizerFastScan.h>
33
+ #include <faiss/IndexEDEN.h>
33
34
  #include <faiss/IndexFlat.h>
34
35
  #include <faiss/IndexHNSW.h>
35
36
  #include <faiss/IndexIVF.h>
36
37
  #include <faiss/IndexIVFAdditiveQuantizer.h>
37
38
  #include <faiss/IndexIVFAdditiveQuantizerFastScan.h>
39
+ #include <faiss/IndexIVFEDEN.h>
38
40
  #include <faiss/IndexIVFFlat.h>
39
41
  #include <faiss/IndexIVFFlatPanorama.h>
40
42
  #include <faiss/IndexIVFIndependentQuantizer.h>
@@ -68,6 +70,7 @@
68
70
  #include <faiss/IndexScalarQuantizer.h>
69
71
  #include <faiss/MetaIndexes.h>
70
72
  #include <faiss/VectorTransform.h>
73
+ #include <faiss/impl/EDENQuantizer.h>
71
74
 
72
75
  #include <faiss/IndexBinaryFlat.h>
73
76
  #include <faiss/IndexBinaryFromFloat.h>
@@ -337,7 +340,9 @@ std::unique_ptr<VectorTransform> read_VectorTransform_up(IOReader* f) {
337
340
  READVECTOR(lt->b);
338
341
  FAISS_THROW_IF_NOT(
339
342
  lt->A.size() >= size_t(lt->d_in) * size_t(lt->d_out));
340
- FAISS_THROW_IF_NOT(!lt->have_bias || lt->b.size() >= size_t(lt->d_out));
343
+ FAISS_THROW_IF_MSG(
344
+ lt->have_bias && lt->b.size() < size_t(lt->d_out),
345
+ "bias vector smaller than d_out");
341
346
  lt->set_is_orthonormal();
342
347
  vt = std::move(lt);
343
348
  } else if (h == fourcc("RmDT")) {
@@ -540,7 +545,7 @@ std::unique_ptr<InvertedLists> read_InvertedLists_up(
540
545
  READ1(n_levels);
541
546
  FAISS_THROW_IF_NOT_FMT(
542
547
  n_levels > 0, "invalid ilpn n_levels %zd", n_levels);
543
- constexpr size_t bs = Panorama::kDefaultBatchSize;
548
+ constexpr size_t bs = Panorama::kLegacyBatchSize;
544
549
  auto ailp = std::make_unique<ArrayInvertedListsPanorama>(
545
550
  nlist, code_size, n_levels, bs);
546
551
  std::vector<size_t> sizes(nlist);
@@ -732,7 +737,9 @@ void read_ProductQuantizer(ProductQuantizer* pq, IOReader* f) {
732
737
  FAISS_THROW_IF_NOT_FMT(
733
738
  pq->M > 0, "invalid ProductQuantizer M=%zd (must be > 0)", pq->M);
734
739
  FAISS_THROW_IF_NOT_FMT(
735
- pq->nbits <= 24, "invalid ProductQuantizer nbits=%zd", pq->nbits);
740
+ pq->nbits >= 1 && pq->nbits <= 24,
741
+ "invalid ProductQuantizer nbits=%zd (must be in [1, 24])",
742
+ pq->nbits);
736
743
  {
737
744
  size_t ksub = size_t{1} << pq->nbits;
738
745
  size_t n = mul_no_overflow(pq->d, ksub, "PQ centroids");
@@ -814,6 +821,10 @@ static void read_AdditiveQuantizer(AdditiveQuantizer& aq, IOReader* f) {
814
821
  }
815
822
 
816
823
  aq.set_derived_values();
824
+ FAISS_THROW_IF_NOT_FMT(
825
+ aq.code_size > 0,
826
+ "invalid AdditiveQuantizer: nbits sum to 0 bits, code_size %zd",
827
+ aq.code_size);
817
828
 
818
829
  // Sanity-check codebooks size without knowing the effective dimension.
819
830
  // codebooks stores effective_d * total_codebook_size floats, so its
@@ -862,6 +873,11 @@ static void validate_fastscan_fields(
862
873
  index_type,
863
874
  M,
864
875
  ksub);
876
+ FAISS_THROW_IF_NOT_FMT(
877
+ ksub == 16,
878
+ "%s: invalid ksub=%zd (fast-scan requires nbits=4 / ksub=16)",
879
+ index_type,
880
+ ksub);
865
881
  FAISS_THROW_IF_NOT_FMT(
866
882
  bbs > 0 && bbs % 32 == 0,
867
883
  "%s: invalid bbs=%d (must be > 0 and a multiple of 32)",
@@ -1086,6 +1102,30 @@ void read_ScalarQuantizer(
1086
1102
  case ScalarQuantizer::QT_8bit_tqmse:
1087
1103
  expected = 256 + 255;
1088
1104
  break;
1105
+ case ScalarQuantizer::QT_1bit_eden:
1106
+ expected = 2 + 1; // 2^bits centroids + (2^bits - 1) boundaries
1107
+ break;
1108
+ case ScalarQuantizer::QT_2bit_eden:
1109
+ expected = 4 + 3;
1110
+ break;
1111
+ case ScalarQuantizer::QT_3bit_eden:
1112
+ expected = 8 + 7;
1113
+ break;
1114
+ case ScalarQuantizer::QT_4bit_eden:
1115
+ expected = 16 + 15;
1116
+ break;
1117
+ case ScalarQuantizer::QT_5bit_eden:
1118
+ expected = 32 + 31;
1119
+ break;
1120
+ case ScalarQuantizer::QT_6bit_eden:
1121
+ expected = 64 + 63;
1122
+ break;
1123
+ case ScalarQuantizer::QT_7bit_eden:
1124
+ expected = 128 + 127;
1125
+ break;
1126
+ case ScalarQuantizer::QT_8bit_eden:
1127
+ expected = 256 + 255;
1128
+ break;
1089
1129
  case ScalarQuantizer::QT_2bit_tq:
1090
1130
  case ScalarQuantizer::QT_3bit_tq:
1091
1131
  case ScalarQuantizer::QT_4bit_tq:
@@ -1124,8 +1164,7 @@ void read_ScalarQuantizer(
1124
1164
  }
1125
1165
  }
1126
1166
 
1127
- // TurboQ full types: extract seed and qjl_type from trained,
1128
- // regenerate projection matrix.
1167
+ // TurboQ full types: extract seed and qjl_type from trained.
1129
1168
  if (ScalarQuantizer::TurboQuantRefine::is_turboq_full(ivsc->qtype) &&
1130
1169
  ivsc->trained.size() >= 3) {
1131
1170
  size_t n = ivsc->trained.size();
@@ -1134,7 +1173,6 @@ void read_ScalarQuantizer(
1134
1173
  ivsc->turboq_refine.seed =
1135
1174
  ScalarQuantizer::TurboQuantRefine::unpack_seed(
1136
1175
  ivsc->trained[n - 3], ivsc->trained[n - 2]);
1137
- ivsc->turboq_refine.init_projection(ivsc->d);
1138
1176
  }
1139
1177
  }
1140
1178
 
@@ -1339,13 +1377,15 @@ static void read_NNDescent(NNDescent& nnd, IOReader* f) {
1339
1377
  READVECTOR(nnd.final_graph);
1340
1378
  // Validate neighbor IDs in the graph
1341
1379
  if (nnd.has_built && nnd.K > 0 && nnd.ntotal > 0) {
1380
+ const size_t expected_final_graph_size = mul_no_overflow(
1381
+ (size_t)nnd.ntotal, (size_t)nnd.K, "NNDescent final_graph");
1342
1382
  FAISS_THROW_IF_NOT_FMT(
1343
- nnd.final_graph.size() == (size_t)nnd.ntotal * (size_t)nnd.K,
1383
+ nnd.final_graph.size() == expected_final_graph_size,
1344
1384
  "NNDescent final_graph size %zu != ntotal * K (%d * %d = %zu)",
1345
1385
  nnd.final_graph.size(),
1346
1386
  nnd.ntotal,
1347
1387
  nnd.K,
1348
- (size_t)nnd.ntotal * (size_t)nnd.K);
1388
+ expected_final_graph_size);
1349
1389
  for (size_t i = 0; i < nnd.final_graph.size(); i++) {
1350
1390
  int id = nnd.final_graph[i];
1351
1391
  FAISS_THROW_IF_NOT_FMT(
@@ -1402,6 +1442,64 @@ static void read_RaBitQuantizer(
1402
1442
  expected_d);
1403
1443
  }
1404
1444
 
1445
+ static void read_EDENScalarQuantizer(
1446
+ ScalarQuantizer& sq,
1447
+ EDENScaleType& scale_type,
1448
+ IOReader* f,
1449
+ int expected_d,
1450
+ MetricType expected_metric_type,
1451
+ bool read_scale_type) {
1452
+ size_t d;
1453
+ size_t stored_code_size;
1454
+ size_t nb_bits;
1455
+
1456
+ READ1(d);
1457
+ READ1(stored_code_size);
1458
+ int metric_type_int;
1459
+ READ1(metric_type_int);
1460
+ const MetricType metric_type = metric_type_from_int(metric_type_int);
1461
+ READ1(nb_bits);
1462
+ if (read_scale_type) {
1463
+ int scale_type_int;
1464
+ READ1(scale_type_int);
1465
+ if (scale_type_int == 0) {
1466
+ scale_type = EDENScaleType_UNBIASED;
1467
+ } else {
1468
+ scale_type = static_cast<EDENScaleType>(scale_type_int);
1469
+ }
1470
+ } else {
1471
+ scale_type = EDENScaleType_UNBIASED;
1472
+ }
1473
+
1474
+ FAISS_THROW_IF_NOT_FMT(
1475
+ d == static_cast<size_t>(expected_d),
1476
+ "EDEN ScalarQuantizer dimension mismatch: sq.d=%zu vs index d=%d",
1477
+ d,
1478
+ expected_d);
1479
+ FAISS_THROW_IF_NOT_FMT(
1480
+ metric_type == expected_metric_type,
1481
+ "EDEN ScalarQuantizer metric mismatch: stored=%d vs index=%d",
1482
+ metric_type_int,
1483
+ static_cast<int>(expected_metric_type));
1484
+ FAISS_THROW_IF_NOT_FMT(
1485
+ nb_bits >= 1 && nb_bits <= 8,
1486
+ "invalid EDEN nb_bits=%zu (must be in [1, 8])",
1487
+ nb_bits);
1488
+ FAISS_THROW_IF_NOT_FMT(
1489
+ scale_type == EDENScaleType_UNBIASED ||
1490
+ scale_type == EDENScaleType_BIASED,
1491
+ "invalid EDEN scale_type=%d",
1492
+ static_cast<int>(scale_type));
1493
+ sq = ScalarQuantizer(d, eden_utils::quantizer_type_for_bits(nb_bits));
1494
+ sq.train(0, nullptr);
1495
+ const size_t expected_code_size = eden_utils::code_size(d, nb_bits);
1496
+ FAISS_THROW_IF_NOT_FMT(
1497
+ stored_code_size == expected_code_size,
1498
+ "EDEN ScalarQuantizer code_size mismatch: stored=%zu vs expected=%zu",
1499
+ stored_code_size,
1500
+ expected_code_size);
1501
+ }
1502
+
1405
1503
  void read_direct_map(DirectMap* dm, IOReader* f) {
1406
1504
  char maintain_direct_map;
1407
1505
  READ1(maintain_direct_map);
@@ -1453,6 +1551,45 @@ ArrayInvertedLists* set_array_invlist(
1453
1551
  return result;
1454
1552
  }
1455
1553
 
1554
+ static void validate_ivfpq_precomputed_table_size(
1555
+ const Index* quantizer,
1556
+ const ProductQuantizer& pq) {
1557
+ // The precomputed table is not stored; precompute_table() rebuilds it on
1558
+ // load at a size derived from attacker-controlled header fields. Bound
1559
+ // every table initialize_IVFPQ_precomputed_table() may allocate.
1560
+ const size_t m_ksub =
1561
+ mul_no_overflow(pq.M, pq.ksub, "IVFPQ precomputed_table");
1562
+ // type 1: nlist (== quantizer->ntotal) * pq.M * pq.ksub.
1563
+ size_t precompute_elems = mul_no_overflow(
1564
+ static_cast<size_t>(quantizer->ntotal),
1565
+ m_ksub,
1566
+ "IVFPQ precomputed_table");
1567
+ // type 2 (MultiIndexQuantizer coarse quantizer): cpq.ksub * pq.M * pq.ksub,
1568
+ // plus a temporary quantizer->d * cpq.ksub centroid table. Both derive from
1569
+ // the coarse PQ's ksub, which is independent of quantizer->ntotal, so the
1570
+ // type-1 bound above does not cover them.
1571
+ if (const auto* miq = dynamic_cast<const MultiIndexQuantizer*>(quantizer)) {
1572
+ const size_t cpq_ksub = miq->pq.ksub;
1573
+ const size_t type2_table =
1574
+ mul_no_overflow(cpq_ksub, m_ksub, "IVFPQ precomputed_table");
1575
+ const size_t type2_centroids = mul_no_overflow(
1576
+ static_cast<size_t>(quantizer->d),
1577
+ cpq_ksub,
1578
+ "IVFPQ precomputed_table");
1579
+ if (type2_table > precompute_elems) {
1580
+ precompute_elems = type2_table;
1581
+ }
1582
+ if (type2_centroids > precompute_elems) {
1583
+ precompute_elems = type2_centroids;
1584
+ }
1585
+ }
1586
+ FAISS_THROW_IF_NOT_MSG(
1587
+ precompute_elems <
1588
+ get_deserialization_vector_byte_limit() / sizeof(float),
1589
+ "IVFPQ precomputed_table allocation would exceed deserialization "
1590
+ "byte limit");
1591
+ }
1592
+
1456
1593
  static std::unique_ptr<IndexIVFPQ> read_ivfpq(
1457
1594
  IOReader* f,
1458
1595
  uint32_t h,
@@ -1470,6 +1607,8 @@ static std::unique_ptr<IndexIVFPQ> read_ivfpq(
1470
1607
 
1471
1608
  std::vector<std::vector<idx_t>> ids;
1472
1609
  read_ivf_header(ivpq.get(), f, legacy ? &ids : nullptr);
1610
+ FAISS_THROW_IF_NOT_MSG(
1611
+ ivpq->quantizer != nullptr, "IVFPQ coarse quantizer is null");
1473
1612
  READ1_BOOL(ivpq->by_residual);
1474
1613
  READ1(ivpq->code_size);
1475
1614
  read_ProductQuantizer(&ivpq->pq, f);
@@ -1488,6 +1627,8 @@ static std::unique_ptr<IndexIVFPQ> read_ivfpq(
1488
1627
  ivpq->use_precomputed_table = 0;
1489
1628
  if (ivpq->by_residual) {
1490
1629
  if ((io_flags & IO_FLAG_SKIP_PRECOMPUTE_TABLE) == 0) {
1630
+ validate_ivfpq_precomputed_table_size(
1631
+ ivpq->quantizer, ivpq->pq);
1491
1632
  ivpq->precompute_table();
1492
1633
  }
1493
1634
  }
@@ -1516,7 +1657,29 @@ static std::unique_ptr<IndexIVFPQ> read_ivfpq(
1516
1657
 
1517
1658
  int read_old_fmt_hack = 0;
1518
1659
 
1660
+ namespace {
1661
+
1662
+ constexpr int kMaxIndexNestingDepth = 50;
1663
+ thread_local int index_read_nesting_depth = 0;
1664
+
1665
+ struct IndexNestingGuard {
1666
+ IndexNestingGuard() {
1667
+ FAISS_THROW_IF_NOT_FMT(
1668
+ index_read_nesting_depth < kMaxIndexNestingDepth,
1669
+ "faiss index nesting depth exceeds limit of %d; "
1670
+ "input may be corrupt or malicious",
1671
+ kMaxIndexNestingDepth);
1672
+ ++index_read_nesting_depth;
1673
+ }
1674
+ ~IndexNestingGuard() {
1675
+ --index_read_nesting_depth;
1676
+ }
1677
+ };
1678
+
1679
+ } // namespace
1680
+
1519
1681
  std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1682
+ IndexNestingGuard nesting_guard;
1520
1683
  std::unique_ptr<Index> idx;
1521
1684
  uint32_t h;
1522
1685
  READ1(h);
@@ -1527,9 +1690,15 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1527
1690
  int d;
1528
1691
  size_t n_levels, batch_size;
1529
1692
  READ1(d);
1693
+ // This branch does not go through read_index_header, so the range
1694
+ // check that guards `d` for every other index type has to be
1695
+ // repeated here.
1696
+ FAISS_CHECK_RANGE(d, 0, (1 << 20) + 1);
1530
1697
  READ1(n_levels);
1531
1698
  FAISS_THROW_IF_NOT_FMT(n_levels > 0, "invalid n_levels %zd", n_levels);
1532
1699
  READ1(batch_size);
1700
+ FAISS_THROW_IF_NOT_FMT(
1701
+ batch_size > 0, "invalid IxFP batch_size %zd", batch_size);
1533
1702
  std::unique_ptr<IndexFlatPanorama> idxp;
1534
1703
  if (h == fourcc("IxFP")) {
1535
1704
  idxp = std::make_unique<IndexFlatL2Panorama>(
@@ -1539,9 +1708,36 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1539
1708
  d, n_levels, batch_size);
1540
1709
  }
1541
1710
  READ1(idxp->ntotal);
1711
+ FAISS_THROW_IF_NOT_FMT(
1712
+ idxp->ntotal >= 0,
1713
+ "invalid ntotal %" PRId64 " read from IxFP index",
1714
+ (int64_t)idxp->ntotal);
1542
1715
  READ1_BOOL(idxp->is_trained);
1543
1716
  READVECTOR(idxp->codes);
1544
1717
  READVECTOR(idxp->cum_sums);
1718
+ const size_t ntotal = (size_t)idxp->ntotal;
1719
+ const size_t num_batches =
1720
+ ntotal / batch_size + (ntotal % batch_size != 0);
1721
+ const size_t num_slots = mul_no_overflow(
1722
+ num_batches,
1723
+ batch_size,
1724
+ "IndexFlatPanorama num_batches*batch_size");
1725
+ const size_t expected_codes_size = mul_no_overflow(
1726
+ num_slots, idxp->code_size, "IndexFlatPanorama codes");
1727
+ FAISS_THROW_IF_NOT_FMT(
1728
+ idxp->codes.size() == expected_codes_size,
1729
+ "IndexFlatPanorama codes size mismatch: got %zu, expected %zu",
1730
+ idxp->codes.size(),
1731
+ expected_codes_size);
1732
+ const size_t expected_cum_sums_size = mul_no_overflow(
1733
+ num_slots,
1734
+ idxp->pano.n_levels + 1,
1735
+ "IndexFlatPanorama cum_sums");
1736
+ FAISS_THROW_IF_NOT_FMT(
1737
+ idxp->cum_sums.size() == expected_cum_sums_size,
1738
+ "IndexFlatPanorama cum_sums size mismatch: got %zu, expected %zu",
1739
+ idxp->cum_sums.size(),
1740
+ expected_cum_sums_size);
1545
1741
  idxp->verbose = false;
1546
1742
  idx = std::move(idxp);
1547
1743
  } else if (
@@ -1558,7 +1754,11 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1558
1754
  idxf->code_size = idxf->d * sizeof(float);
1559
1755
  read_xb_vector(idxf->codes, f);
1560
1756
  FAISS_THROW_IF_NOT(
1561
- idxf->codes.size() == idxf->ntotal * idxf->code_size);
1757
+ idxf->codes.size() ==
1758
+ mul_no_overflow(
1759
+ (size_t)idxf->ntotal,
1760
+ idxf->code_size,
1761
+ "IndexFlat codes"));
1562
1762
  idx = std::move(idxf);
1563
1763
  } else if (h == fourcc("IxHE") || h == fourcc("IxHe")) {
1564
1764
  auto idxl = std::make_unique<IndexLSH>();
@@ -1597,7 +1797,11 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1597
1797
  FAISS_THROW_IF_NOT(
1598
1798
  idxl->rrot.d_in == idxl->d && idxl->rrot.d_out == idxl->nbits);
1599
1799
  FAISS_THROW_IF_NOT(
1600
- idxl->codes.size() == idxl->ntotal * idxl->code_size);
1800
+ idxl->codes.size() ==
1801
+ mul_no_overflow(
1802
+ (size_t)idxl->ntotal,
1803
+ idxl->code_size,
1804
+ "IndexLSH codes"));
1601
1805
  idx = std::move(idxl);
1602
1806
  } else if (
1603
1807
  h == fourcc("IxPQ") || h == fourcc("IxPo") || h == fourcc("IxPq")) {
@@ -1607,8 +1811,16 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1607
1811
  read_ProductQuantizer(&idxp->pq, f);
1608
1812
  idxp->code_size = idxp->pq.code_size;
1609
1813
  read_vector(idxp->codes, f);
1814
+ FAISS_THROW_IF_NOT_MSG(
1815
+ idxp->code_size > 0 || idxp->ntotal == 0,
1816
+ "IndexPQ with ntotal > 0 must have code_size > 0 "
1817
+ "(corrupt ProductQuantizer nbits?)");
1610
1818
  FAISS_THROW_IF_NOT(
1611
- idxp->codes.size() == idxp->ntotal * idxp->code_size);
1819
+ idxp->codes.size() ==
1820
+ mul_no_overflow(
1821
+ (size_t)idxp->ntotal,
1822
+ idxp->code_size,
1823
+ "IndexPQ codes"));
1612
1824
  if (h == fourcc("IxPo") || h == fourcc("IxPq")) {
1613
1825
  READ1(idxp->search_type);
1614
1826
  READ1_BOOL(idxp->encode_signs);
@@ -1636,7 +1848,11 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1636
1848
  idxr->code_size, idxr->rq.code_size, "IndexResidualQuantizer");
1637
1849
  read_vector(idxr->codes, f);
1638
1850
  FAISS_THROW_IF_NOT(
1639
- idxr->codes.size() == idxr->ntotal * idxr->code_size);
1851
+ idxr->codes.size() ==
1852
+ mul_no_overflow(
1853
+ (size_t)idxr->ntotal,
1854
+ idxr->code_size,
1855
+ "IndexResidualQuantizer codes"));
1640
1856
  idx = std::move(idxr);
1641
1857
  } else if (h == fourcc("IxLS")) {
1642
1858
  auto idxr = std::make_unique<IndexLocalSearchQuantizer>();
@@ -1651,7 +1867,11 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1651
1867
  "IndexLocalSearchQuantizer");
1652
1868
  read_vector(idxr->codes, f);
1653
1869
  FAISS_THROW_IF_NOT(
1654
- idxr->codes.size() == idxr->ntotal * idxr->code_size);
1870
+ idxr->codes.size() ==
1871
+ mul_no_overflow(
1872
+ (size_t)idxr->ntotal,
1873
+ idxr->code_size,
1874
+ "IndexLocalSearchQuantizer codes"));
1655
1875
  idx = std::move(idxr);
1656
1876
  } else if (h == fourcc("IxPR")) {
1657
1877
  auto idxpr = std::make_unique<IndexProductResidualQuantizer>();
@@ -1666,7 +1886,11 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1666
1886
  "IndexProductResidualQuantizer");
1667
1887
  read_vector(idxpr->codes, f);
1668
1888
  FAISS_THROW_IF_NOT(
1669
- idxpr->codes.size() == idxpr->ntotal * idxpr->code_size);
1889
+ idxpr->codes.size() ==
1890
+ mul_no_overflow(
1891
+ (size_t)idxpr->ntotal,
1892
+ idxpr->code_size,
1893
+ "IndexProductResidualQuantizer codes"));
1670
1894
  idx = std::move(idxpr);
1671
1895
  } else if (h == fourcc("IxPL")) {
1672
1896
  auto idxpl = std::make_unique<IndexProductLocalSearchQuantizer>();
@@ -1681,7 +1905,11 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1681
1905
  "IndexProductLocalSearchQuantizer");
1682
1906
  read_vector(idxpl->codes, f);
1683
1907
  FAISS_THROW_IF_NOT(
1684
- idxpl->codes.size() == idxpl->ntotal * idxpl->code_size);
1908
+ idxpl->codes.size() ==
1909
+ mul_no_overflow(
1910
+ (size_t)idxpl->ntotal,
1911
+ idxpl->code_size,
1912
+ "IndexProductLocalSearchQuantizer codes"));
1685
1913
  idx = std::move(idxpl);
1686
1914
  } else if (h == fourcc("ImRQ")) {
1687
1915
  auto idxr = std::make_unique<ResidualCoarseQuantizer>();
@@ -1725,6 +1953,16 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1725
1953
  idxr->ntotal,
1726
1954
  idxr->rq.M);
1727
1955
  }
1956
+ FAISS_THROW_IF_NOT_MSG(
1957
+ idxr->rq.tot_bits <= 63,
1958
+ "ResidualCoarseQuantizer tot_bits too large (max 63)");
1959
+ FAISS_THROW_IF_NOT_FMT(
1960
+ static_cast<size_t>(idxr->ntotal) ==
1961
+ (((size_t)1) << idxr->rq.tot_bits),
1962
+ "ResidualCoarseQuantizer ntotal %" PRId64
1963
+ " inconsistent with 2^tot_bits (tot_bits=%zu)",
1964
+ idxr->ntotal,
1965
+ idxr->rq.tot_bits);
1728
1966
  idxr->set_beam_factor(idxr->beam_factor);
1729
1967
  idx = std::move(idxr);
1730
1968
  } else if (
@@ -1859,9 +2097,28 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1859
2097
  ivfl->code_size = ivfl->d * sizeof(float);
1860
2098
  ArrayInvertedLists* ail = set_array_invlist(ivfl.get(), ids);
1861
2099
 
2100
+ // Legacy IVF serialized ids and codes as separate vectors.
2101
+ // Check not required in default IVF, due to single sizes vector.
2102
+ auto validate_legacy_codes_size = [&](size_t i) {
2103
+ const size_t expected_codes_bytes = mul_no_overflow(
2104
+ ail->ids[i].size(),
2105
+ ivfl->code_size,
2106
+ "legacy IVFFlat inverted list codes");
2107
+ FAISS_THROW_IF_NOT_FMT(
2108
+ ail->codes[i].size() == expected_codes_bytes,
2109
+ "Legacy IVFFlat inverted list %zu: codes size %zu bytes "
2110
+ "does not match ids size %zu * code_size %zu = %zu bytes",
2111
+ i,
2112
+ ail->codes[i].size(),
2113
+ ail->ids[i].size(),
2114
+ (size_t)ivfl->code_size,
2115
+ expected_codes_bytes);
2116
+ };
2117
+
1862
2118
  if (h == fourcc("IvFL")) {
1863
2119
  for (size_t i = 0; i < ivfl->nlist; i++) {
1864
2120
  READVECTOR(ail->codes[i]);
2121
+ validate_legacy_codes_size(i);
1865
2122
  }
1866
2123
  } else { // old format
1867
2124
  for (size_t i = 0; i < ivfl->nlist; i++) {
@@ -1869,6 +2126,7 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1869
2126
  READVECTOR(vec);
1870
2127
  ail->codes[i].resize(vec.size() * sizeof(float));
1871
2128
  memcpy(ail->codes[i].data(), vec.data(), ail->codes[i].size());
2129
+ validate_legacy_codes_size(i);
1872
2130
  }
1873
2131
  }
1874
2132
  idx = std::move(ivfl);
@@ -1896,7 +2154,7 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1896
2154
  read_ivf_header(ivfp.get(), f);
1897
2155
  ivfp->code_size = ivfp->d * sizeof(float);
1898
2156
  READ1(ivfp->n_levels);
1899
- ivfp->batch_size = Panorama::kDefaultBatchSize;
2157
+ ivfp->batch_size = Panorama::kLegacyBatchSize;
1900
2158
  read_InvertedLists(*ivfp, f, io_flags);
1901
2159
  idx = std::move(ivfp);
1902
2160
  } else if (h == fourcc("IwP2")) {
@@ -1905,6 +2163,10 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1905
2163
  ivfp->code_size = ivfp->d * sizeof(float);
1906
2164
  READ1(ivfp->n_levels);
1907
2165
  READ1(ivfp->batch_size);
2166
+ FAISS_THROW_IF_NOT_FMT(
2167
+ ivfp->batch_size > 0,
2168
+ "invalid IwP2 batch_size %zd",
2169
+ ivfp->batch_size);
1908
2170
  read_InvertedLists(*ivfp, f, io_flags);
1909
2171
  idx = std::move(ivfp);
1910
2172
  } else if (h == fourcc("IwFl")) {
@@ -1919,6 +2181,12 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1919
2181
  read_ScalarQuantizer(&idxs->sq, f, *idxs);
1920
2182
  read_vector(idxs->codes, f);
1921
2183
  idxs->code_size = idxs->sq.code_size;
2184
+ FAISS_THROW_IF_NOT(
2185
+ idxs->codes.size() ==
2186
+ mul_no_overflow(
2187
+ (size_t)idxs->ntotal,
2188
+ idxs->code_size,
2189
+ "IndexScalarQuantizer codes"));
1922
2190
  idx = std::move(idxs);
1923
2191
  } else if (h == fourcc("IxLa")) {
1924
2192
  int d, nsq, scale_nbit, r2;
@@ -1983,6 +2251,16 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1983
2251
  r2);
1984
2252
  }
1985
2253
  read_index_header(*idxl, f);
2254
+ FAISS_THROW_IF_NOT_FMT(
2255
+ idxl->ntotal == 0,
2256
+ "IndexLattice deserialization carries no code storage; "
2257
+ "ntotal=%zd != 0 is corrupt",
2258
+ (size_t)idxl->ntotal);
2259
+ FAISS_THROW_IF_NOT_FMT(
2260
+ idxl->d == d,
2261
+ "IndexLattice header d=%d inconsistent with encoded d=%d",
2262
+ idxl->d,
2263
+ d);
1986
2264
  READVECTOR(idxl->trained);
1987
2265
  idx = std::move(idxl);
1988
2266
  } else if (h == fourcc("IvSQ")) { // legacy
@@ -2120,8 +2398,18 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
2120
2398
  // Validate transform chain dimension consistency:
2121
2399
  // chain[0].d_in must equal the outer index d, consecutive
2122
2400
  // transforms must have matching d_out/d_in, and the last
2123
- // transform's d_out must equal the sub-index d.
2124
- if (nt > 0) {
2401
+ // transform's d_out must equal the sub-index d. With an empty
2402
+ // chain the sub-index d must equal the outer d directly, because
2403
+ // reconstruct() then hands the caller's buffer -- sized from the
2404
+ // outer d -- straight to the sub-index.
2405
+ if (nt == 0 && ixpt->index) {
2406
+ FAISS_THROW_IF_NOT_FMT(
2407
+ ixpt->index->d == ixpt->d,
2408
+ "IndexPreTransform empty chain: sub-index d=%d "
2409
+ "!= index d=%d",
2410
+ ixpt->index->d,
2411
+ ixpt->d);
2412
+ } else if (nt > 0) {
2125
2413
  FAISS_THROW_IF_NOT_FMT(
2126
2414
  ixpt->chain[0]->d_in == ixpt->d,
2127
2415
  "IndexPreTransform chain[0] d_in=%d != index d=%d",
@@ -2158,6 +2446,8 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
2158
2446
  read_index_header(*idxrf, f);
2159
2447
  auto base = read_index_up(f, io_flags);
2160
2448
  auto refine = read_index_up(f, io_flags);
2449
+ FAISS_THROW_IF_NOT_MSG(base, "IndexRefine base index is null");
2450
+ FAISS_THROW_IF_NOT_MSG(refine, "IndexRefine refine index is null");
2161
2451
  READ1(idxrf->k_factor);
2162
2452
  // Same rationale as IndexIVFPQR k_factor above.
2163
2453
  FAISS_THROW_IF_NOT_FMT(
@@ -2218,6 +2508,10 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
2218
2508
  READ1(idxp->code_size_1);
2219
2509
  READ1(idxp->code_size_2);
2220
2510
  READ1(idxp->code_size);
2511
+ validate_code_size_match(
2512
+ idxp->code_size_1,
2513
+ idxp->q1.coarse_code_size(),
2514
+ "Index2Layer code_size_1");
2221
2515
  validate_code_size_match(
2222
2516
  idxp->code_size_2,
2223
2517
  idxp->pq.code_size,
@@ -2227,32 +2521,36 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
2227
2521
  idxp->code_size_1 + idxp->code_size_2,
2228
2522
  "Index2Layer");
2229
2523
  read_vector(idxp->codes, f);
2524
+ FAISS_THROW_IF_NOT(
2525
+ idxp->codes.size() ==
2526
+ mul_no_overflow(
2527
+ (size_t)idxp->ntotal,
2528
+ idxp->code_size,
2529
+ "Index2Layer codes"));
2230
2530
  idx = std::move(idxp);
2231
2531
  } else if (
2232
2532
  h == fourcc("IHNf") || h == fourcc("IHNp") || h == fourcc("IHNs") ||
2233
2533
  h == fourcc("IHN2") || h == fourcc("IHNc") || h == fourcc("IHc2") ||
2234
- h == fourcc("IHfP")) {
2534
+ h == fourcc("IHfP") || h == fourcc("IHNr") || h == fourcc("IH00")) {
2235
2535
  std::unique_ptr<IndexHNSW> idxhnsw;
2236
- if (h == fourcc("IHNf")) {
2536
+ if (h == fourcc("IH00")) {
2537
+ idxhnsw = std::make_unique<IndexHNSW>();
2538
+ } else if (h == fourcc("IHNf")) {
2237
2539
  idxhnsw = std::make_unique<IndexHNSWFlat>();
2238
- }
2239
- if (h == fourcc("IHfP")) {
2540
+ } else if (h == fourcc("IHfP")) {
2240
2541
  idxhnsw = std::make_unique<IndexHNSWFlatPanorama>();
2241
- }
2242
- if (h == fourcc("IHNp")) {
2542
+ } else if (h == fourcc("IHNp")) {
2243
2543
  idxhnsw = std::make_unique<IndexHNSWPQ>();
2244
- }
2245
- if (h == fourcc("IHNs")) {
2544
+ } else if (h == fourcc("IHNs")) {
2246
2545
  idxhnsw = std::make_unique<IndexHNSWSQ>();
2247
- }
2248
- if (h == fourcc("IHN2")) {
2546
+ } else if (h == fourcc("IHN2")) {
2249
2547
  idxhnsw = std::make_unique<IndexHNSW2Level>();
2250
- }
2251
- if (h == fourcc("IHNc")) {
2548
+ } else if (h == fourcc("IHNc")) {
2252
2549
  idxhnsw = std::make_unique<IndexHNSWCagra>();
2253
- }
2254
- if (h == fourcc("IHc2")) {
2550
+ } else if (h == fourcc("IHc2")) {
2255
2551
  idxhnsw = std::make_unique<IndexHNSWCagra>();
2552
+ } else if (h == fourcc("IHNr")) {
2553
+ idxhnsw = std::make_unique<IndexHNSWRaBitQ>();
2256
2554
  }
2257
2555
  read_index_header(*idxhnsw, f);
2258
2556
  if (h == fourcc("IHfP")) {
@@ -2263,12 +2561,22 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
2263
2561
  "dynamic_cast to IndexHNSWFlatPanorama failed");
2264
2562
  size_t nlevels;
2265
2563
  READ1(nlevels);
2564
+ FAISS_THROW_IF_NOT_FMT(
2565
+ nlevels > 0, "invalid IHfP n_levels %zd", nlevels);
2266
2566
  const_cast<size_t&>(idx_panorama->num_panorama_levels) = nlevels;
2267
2567
  const_cast<Panorama&>(idx_panorama->pano) =
2268
2568
  Panorama(idx_panorama->d * sizeof(float), nlevels, 1);
2269
2569
  READVECTOR(idx_panorama->cum_sums);
2270
- }
2271
- if (h == fourcc("IHNc") || h == fourcc("IHc2")) {
2570
+ // Both the search path and get_cum_sum() index cum_sums at
2571
+ // stride pano.n_levels + 1, which may be lower than the
2572
+ // serialized nlevels if set_derived_values() truncated it.
2573
+ FAISS_THROW_IF_NOT(
2574
+ idx_panorama->cum_sums.size() ==
2575
+ mul_no_overflow(
2576
+ (size_t)idx_panorama->ntotal,
2577
+ idx_panorama->pano.n_levels + 1,
2578
+ "IndexHNSWFlatPanorama cum_sums"));
2579
+ } else if (h == fourcc("IHNc") || h == fourcc("IHc2")) {
2272
2580
  READ1_BOOL(idxhnsw->keep_max_size_level0);
2273
2581
  auto idx_hnsw_cagra = dynamic_cast<IndexHNSWCagra*>(idxhnsw.get());
2274
2582
  FAISS_THROW_IF_NOT_MSG(
@@ -2288,7 +2596,8 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
2288
2596
  "HNSW levels size %zu != index ntotal %" PRId64,
2289
2597
  idxhnsw->hnsw.levels.size(),
2290
2598
  idxhnsw->ntotal);
2291
- idxhnsw->hnsw.is_panorama = (h == fourcc("IHfP"));
2599
+ idxhnsw->hnsw.search_method =
2600
+ h == fourcc("IHfP") ? HNSW::SM_PANORAMA : HNSW::SM_DEFAULT;
2292
2601
  // `HNSW::is_similarity` is intentionally not serialized, so we
2293
2602
  // re-derive it here from the persisted metric type. Without this,
2294
2603
  // a saved IP/similarity index would come back configured as a
@@ -2310,6 +2619,57 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
2310
2619
  idxhnsw->storage->d,
2311
2620
  idxhnsw->d);
2312
2621
  }
2622
+ if (h == fourcc("IHNr")) {
2623
+ auto* idx_rabitq = dynamic_cast<IndexHNSWRaBitQ*>(idxhnsw.get());
2624
+ FAISS_THROW_IF_NOT_MSG(
2625
+ idx_rabitq, "IHNr must deserialize to an IndexHNSWRaBitQ");
2626
+ FAISS_THROW_IF_NOT_MSG(
2627
+ idxhnsw->metric_type == METRIC_L2,
2628
+ "IndexHNSWRaBitQ supports only the L2 metric");
2629
+ bool staged;
2630
+ READ1_BOOL(staged);
2631
+ idxhnsw->hnsw.search_method =
2632
+ staged ? HNSW::SM_RABITQ : HNSW::SM_DEFAULT;
2633
+ if (idxhnsw->storage) {
2634
+ auto* rq = dynamic_cast<IndexRaBitQ*>(idxhnsw->storage);
2635
+ FAISS_THROW_IF_NOT_MSG(
2636
+ rq, "IndexHNSWRaBitQ storage must be an IndexRaBitQ");
2637
+ FAISS_THROW_IF_NOT_MSG(
2638
+ rq->metric_type == idxhnsw->metric_type &&
2639
+ rq->rabitq.metric_type == idxhnsw->metric_type,
2640
+ "IndexHNSWRaBitQ storage metric mismatch");
2641
+ FAISS_THROW_IF_NOT_MSG(
2642
+ rq->is_trained == idxhnsw->is_trained,
2643
+ "IndexHNSWRaBitQ storage training state mismatch");
2644
+ FAISS_THROW_IF_NOT_FMT(
2645
+ rq->rabitq.nb_bits >= 1 && rq->rabitq.nb_bits <= 9,
2646
+ "invalid RaBitQ nb_bits=%zu",
2647
+ rq->rabitq.nb_bits);
2648
+ const size_t expected_code_size =
2649
+ rq->rabitq.compute_code_size(rq->d, rq->rabitq.nb_bits);
2650
+ validate_code_size_match(
2651
+ rq->rabitq.code_size,
2652
+ expected_code_size,
2653
+ "IndexHNSWRaBitQ quantizer");
2654
+ validate_code_size_match(
2655
+ rq->code_size,
2656
+ expected_code_size,
2657
+ "IndexHNSWRaBitQ storage");
2658
+ FAISS_THROW_IF_NOT(
2659
+ rq->codes.size() ==
2660
+ mul_no_overflow(
2661
+ static_cast<size_t>(rq->ntotal),
2662
+ rq->code_size,
2663
+ "IndexHNSWRaBitQ codes"));
2664
+ FAISS_THROW_IF_NOT_MSG(
2665
+ !rq->is_trained ||
2666
+ rq->center.size() == static_cast<size_t>(rq->d),
2667
+ "IndexHNSWRaBitQ center size mismatch");
2668
+ FAISS_THROW_IF_NOT_MSG(
2669
+ staged == (rq->rabitq.nb_bits >= 2),
2670
+ "IndexHNSWRaBitQ staged-search metadata mismatch");
2671
+ }
2672
+ }
2313
2673
  if (h == fourcc("IHN2")) {
2314
2674
  FAISS_THROW_IF_NOT_MSG(
2315
2675
  idxhnsw->storage,
@@ -2430,6 +2790,9 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
2430
2790
  } else if (h == fourcc("IwPf")) {
2431
2791
  auto ivpq = std::make_unique<IndexIVFPQFastScan>();
2432
2792
  read_ivf_header(ivpq.get(), f);
2793
+ FAISS_THROW_IF_NOT_MSG(
2794
+ ivpq->quantizer != nullptr,
2795
+ "IVFPQFastScan coarse quantizer is null");
2433
2796
  READ1_BOOL(ivpq->by_residual);
2434
2797
  READ1(ivpq->code_size);
2435
2798
  READ1(ivpq->bbs);
@@ -2438,6 +2801,7 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
2438
2801
  READ1(ivpq->qbs2);
2439
2802
  read_ProductQuantizer(&ivpq->pq, f);
2440
2803
  read_InvertedLists(*ivpq, f, io_flags);
2804
+ validate_ivfpq_precomputed_table_size(ivpq->quantizer, ivpq->pq);
2441
2805
  ivpq->precompute_table();
2442
2806
 
2443
2807
  const auto& pq = ivpq->pq;
@@ -2467,6 +2831,45 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
2467
2831
  imm->own_fields = true;
2468
2832
 
2469
2833
  idx = std::move(imm);
2834
+ } else if (h == fourcc("IxEd") || h == fourcc("IxEe")) {
2835
+ auto idxe = std::make_unique<IndexEDEN>();
2836
+ read_index_header(*idxe, f);
2837
+ read_EDENScalarQuantizer(
2838
+ idxe->sq,
2839
+ idxe->scale_type,
2840
+ f,
2841
+ idxe->d,
2842
+ idxe->metric_type,
2843
+ h == fourcc("IxEe"));
2844
+ READVECTOR(idxe->codes);
2845
+ READVECTOR(idxe->center);
2846
+
2847
+ idxe->code_size = eden_utils::code_size(idxe->d, idxe->sq.bits);
2848
+ FAISS_THROW_IF_NOT(
2849
+ idxe->codes.size() == idxe->ntotal * idxe->code_size);
2850
+ idx = std::move(idxe);
2851
+ } else if (h == fourcc("IwEd") || h == fourcc("IwEe")) {
2852
+ auto iveden = std::make_unique<IndexIVFEDEN>();
2853
+ read_ivf_header(iveden.get(), f);
2854
+ read_EDENScalarQuantizer(
2855
+ iveden->sq,
2856
+ iveden->scale_type,
2857
+ f,
2858
+ iveden->d,
2859
+ iveden->metric_type,
2860
+ h == fourcc("IwEe"));
2861
+ size_t stored_ivf_code_size;
2862
+ READ1(stored_ivf_code_size);
2863
+ READ1(iveden->by_residual);
2864
+
2865
+ iveden->code_size = eden_utils::code_size(iveden->d, iveden->sq.bits);
2866
+ FAISS_THROW_IF_NOT_FMT(
2867
+ stored_ivf_code_size == iveden->code_size,
2868
+ "IndexIVFEDEN code_size mismatch: stored=%zu vs expected=%zu",
2869
+ stored_ivf_code_size,
2870
+ iveden->code_size);
2871
+ read_InvertedLists(*iveden, f, io_flags);
2872
+ idx = std::move(iveden);
2470
2873
  } else if (h == fourcc("Irfn") || h == fourcc("Irfs")) {
2471
2874
  // Irfn = new format (aux data embedded in SIMD blocks)
2472
2875
  // Irfs = legacy format (flat_storage separate, needs migration)
@@ -2534,7 +2937,7 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
2534
2937
  auto idxq = std::make_unique<IndexRaBitQ>();
2535
2938
  read_index_header(*idxq, f);
2536
2939
  read_RaBitQuantizer(idxq->rabitq, f, idxq->d, false);
2537
- READVECTOR(idxq->codes);
2940
+ read_vector(idxq->codes, f);
2538
2941
  READVECTOR(idxq->center);
2539
2942
  READ1(idxq->qb);
2540
2943
  // qb=0: Not quantized - direct distance computation on given float32s.
@@ -2553,7 +2956,7 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
2553
2956
  read_index_header(*idxq, f);
2554
2957
  read_RaBitQuantizer(
2555
2958
  idxq->rabitq, f, idxq->d, true); // Reads nb_bits from file
2556
- READVECTOR(idxq->codes);
2959
+ read_vector(idxq->codes, f);
2557
2960
  READVECTOR(idxq->center);
2558
2961
  READ1(idxq->qb);
2559
2962
  // qb=0: Not quantized - direct distance computation on given float32s.
@@ -2645,10 +3048,22 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
2645
3048
  bool initialized;
2646
3049
  READ1_BOOL(initialized);
2647
3050
  if (initialized) {
2648
- faiss::svs_io::ReaderStreambuf rbuf(
2649
- f, get_deserialization_vector_byte_limit());
2650
- std::istream is(&rbuf);
2651
- svs->deserialize_impl(is);
3051
+ if ((io_flags & IO_FLAG_MMAP_IFC) == IO_FLAG_MMAP_IFC &&
3052
+ svs->is_static) {
3053
+ // Use memory-mapped I/O for static indices
3054
+ auto* mf = dynamic_cast<MappedFileIOReader*>(f);
3055
+ FAISS_THROW_IF_NOT_MSG(
3056
+ mf,
3057
+ "IO_FLAG_MMAP_IFC flag set but IOReader is not "
3058
+ "MappedFileIOReader");
3059
+ svs->map_to(mf);
3060
+ } else {
3061
+ // Use standard deserialization
3062
+ faiss::svs_io::ReaderStreambuf rbuf(
3063
+ f, get_deserialization_vector_byte_limit());
3064
+ std::istream is(&rbuf);
3065
+ svs->deserialize_impl(is);
3066
+ }
2652
3067
  }
2653
3068
  if (h == fourcc("ISVL")) {
2654
3069
  bool trained;
@@ -2666,6 +3081,13 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
2666
3081
  }
2667
3082
  if (h == fourcc("ISV2")) {
2668
3083
  READVECTOR(svs->stored_vectors);
3084
+ FAISS_THROW_IF_NOT_MSG(
3085
+ svs->stored_vectors.size() ==
3086
+ mul_no_overflow(
3087
+ (size_t)svs->ntotal,
3088
+ (size_t)svs->d,
3089
+ "IndexSVSVamana stored_vectors"),
3090
+ "ISV2: stored_vectors size inconsistent with ntotal * d");
2669
3091
  } else {
2670
3092
  svs->stored_vectors_valid = false;
2671
3093
  }
@@ -2677,10 +3099,21 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
2677
3099
  bool initialized;
2678
3100
  READ1_BOOL(initialized);
2679
3101
  if (initialized) {
2680
- faiss::svs_io::ReaderStreambuf rbuf(
2681
- f, get_deserialization_vector_byte_limit());
2682
- std::istream is(&rbuf);
2683
- svs->deserialize_impl(is);
3102
+ if ((io_flags & IO_FLAG_MMAP_IFC) == IO_FLAG_MMAP_IFC) {
3103
+ // Use memory-mapped I/O
3104
+ auto* mf = dynamic_cast<MappedFileIOReader*>(f);
3105
+ FAISS_THROW_IF_NOT_MSG(
3106
+ mf,
3107
+ "IO_FLAG_MMAP_IFC flag set but IOReader is not "
3108
+ "MappedFileIOReader");
3109
+ svs->map_to(mf);
3110
+ } else {
3111
+ // Use standard deserialization
3112
+ faiss::svs_io::ReaderStreambuf rbuf(
3113
+ f, get_deserialization_vector_byte_limit());
3114
+ std::istream is(&rbuf);
3115
+ svs->deserialize_impl(is);
3116
+ }
2684
3117
  }
2685
3118
  idx = std::move(svs);
2686
3119
  } else if (
@@ -2718,7 +3151,8 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
2718
3151
  bool initialized;
2719
3152
  READ1_BOOL(initialized);
2720
3153
  if (initialized) {
2721
- faiss::svs_io::ReaderStreambuf rbuf(f);
3154
+ faiss::svs_io::ReaderStreambuf rbuf(
3155
+ f, get_deserialization_vector_byte_limit());
2722
3156
  std::istream is(&rbuf);
2723
3157
  svs_ivf->deserialize_impl(is);
2724
3158
  }
@@ -2726,7 +3160,8 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
2726
3160
  bool trained;
2727
3161
  READ1_BOOL(trained);
2728
3162
  if (trained) {
2729
- faiss::svs_io::ReaderStreambuf rbuf(f);
3163
+ faiss::svs_io::ReaderStreambuf rbuf(
3164
+ f, get_deserialization_vector_byte_limit());
2730
3165
  std::istream is(&rbuf);
2731
3166
  auto* leanvec =
2732
3167
  dynamic_cast<IndexSVSIVFLeanVec*>(svs_ivf.get());
@@ -3024,6 +3459,7 @@ static void read_binary_multi_hash_map(
3024
3459
  }
3025
3460
 
3026
3461
  std::unique_ptr<IndexBinary> read_index_binary_up(IOReader* f, int io_flags) {
3462
+ IndexNestingGuard nesting_guard;
3027
3463
  std::unique_ptr<IndexBinary> idx;
3028
3464
  uint32_t h;
3029
3465
  READ1(h);
@@ -3031,7 +3467,12 @@ std::unique_ptr<IndexBinary> read_index_binary_up(IOReader* f, int io_flags) {
3031
3467
  auto idxf = std::make_unique<IndexBinaryFlat>();
3032
3468
  read_index_binary_header(*idxf, f);
3033
3469
  read_vector(idxf->xb, f);
3034
- FAISS_THROW_IF_NOT(idxf->xb.size() == idxf->ntotal * idxf->code_size);
3470
+ FAISS_THROW_IF_NOT(
3471
+ idxf->xb.size() ==
3472
+ mul_no_overflow(
3473
+ (size_t)idxf->ntotal,
3474
+ idxf->code_size,
3475
+ "IndexBinaryFlat xb"));
3035
3476
  idx = std::move(idxf);
3036
3477
  } else if (h == fourcc("IBwF")) {
3037
3478
  auto ivf = std::make_unique<IndexBinaryIVF>();
@@ -3050,7 +3491,7 @@ std::unique_ptr<IndexBinary> read_index_binary_up(IOReader* f, int io_flags) {
3050
3491
  auto idxhnsw = std::make_unique<IndexBinaryHNSW>();
3051
3492
  read_index_binary_header(*idxhnsw, f);
3052
3493
  read_HNSW(idxhnsw->hnsw, f);
3053
- idxhnsw->hnsw.is_panorama = false;
3494
+ idxhnsw->hnsw.search_method = HNSW::SM_DEFAULT;
3054
3495
  FAISS_THROW_IF_NOT_FMT(
3055
3496
  idxhnsw->hnsw.levels.size() == (size_t)idxhnsw->ntotal,
3056
3497
  "IndexBinaryHNSW HNSW levels size %zu != ntotal %" PRId64,
@@ -3074,7 +3515,7 @@ std::unique_ptr<IndexBinary> read_index_binary_up(IOReader* f, int io_flags) {
3074
3515
  READ1_BOOL(idxhnsw->base_level_only);
3075
3516
  READ1(idxhnsw->num_base_level_search_entrypoints);
3076
3517
  read_HNSW(idxhnsw->hnsw, f);
3077
- idxhnsw->hnsw.is_panorama = false;
3518
+ idxhnsw->hnsw.search_method = HNSW::SM_DEFAULT;
3078
3519
  FAISS_THROW_IF_NOT_FMT(
3079
3520
  idxhnsw->hnsw.levels.size() == (size_t)idxhnsw->ntotal,
3080
3521
  "IndexBinaryHNSWCagra HNSW levels size %zu != ntotal %" PRId64,