faiss 0.6.0 → 0.6.1

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 (361) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/ext/faiss/extconf.rb +2 -1
  4. data/ext/faiss/{index_rb.cpp → index.cpp} +1 -1
  5. data/ext/faiss/index_binary.cpp +1 -1
  6. data/ext/faiss/kmeans.cpp +1 -1
  7. data/ext/faiss/pca_matrix.cpp +1 -1
  8. data/ext/faiss/product_quantizer.cpp +1 -1
  9. data/ext/faiss/{utils_rb.cpp → utils.cpp} +1 -1
  10. data/lib/faiss/version.rb +1 -1
  11. data/vendor/faiss/faiss/AutoTune.cpp +93 -80
  12. data/vendor/faiss/faiss/Clustering.cpp +39 -240
  13. data/vendor/faiss/faiss/Clustering.h +6 -0
  14. data/vendor/faiss/faiss/IVFlib.cpp +41 -21
  15. data/vendor/faiss/faiss/Index.cpp +6 -5
  16. data/vendor/faiss/faiss/Index.h +5 -5
  17. data/vendor/faiss/faiss/Index2Layer.cpp +37 -53
  18. data/vendor/faiss/faiss/IndexAdditiveQuantizer.cpp +49 -37
  19. data/vendor/faiss/faiss/IndexAdditiveQuantizerFastScan.cpp +36 -34
  20. data/vendor/faiss/faiss/IndexAdditiveQuantizerFastScan.h +4 -1
  21. data/vendor/faiss/faiss/IndexBinary.cpp +5 -3
  22. data/vendor/faiss/faiss/IndexBinary.h +4 -4
  23. data/vendor/faiss/faiss/IndexBinaryFlat.cpp +1 -1
  24. data/vendor/faiss/faiss/IndexBinaryFlat.h +1 -1
  25. data/vendor/faiss/faiss/IndexBinaryFromFloat.cpp +4 -4
  26. data/vendor/faiss/faiss/IndexBinaryHNSW.cpp +84 -92
  27. data/vendor/faiss/faiss/IndexBinaryHNSW.h +9 -3
  28. data/vendor/faiss/faiss/IndexBinaryHash.cpp +45 -236
  29. data/vendor/faiss/faiss/IndexBinaryHash.h +6 -6
  30. data/vendor/faiss/faiss/IndexBinaryIVF.cpp +87 -415
  31. data/vendor/faiss/faiss/IndexFastScan.cpp +72 -109
  32. data/vendor/faiss/faiss/IndexFastScan.h +25 -23
  33. data/vendor/faiss/faiss/IndexFlat.cpp +27 -20
  34. data/vendor/faiss/faiss/IndexFlat.h +21 -18
  35. data/vendor/faiss/faiss/IndexFlatCodes.cpp +42 -19
  36. data/vendor/faiss/faiss/IndexHNSW.cpp +283 -145
  37. data/vendor/faiss/faiss/IndexHNSW.h +16 -2
  38. data/vendor/faiss/faiss/IndexIDMap.cpp +25 -21
  39. data/vendor/faiss/faiss/IndexIDMap.h +9 -7
  40. data/vendor/faiss/faiss/IndexIVF.cpp +465 -362
  41. data/vendor/faiss/faiss/IndexIVF.h +33 -12
  42. data/vendor/faiss/faiss/IndexIVFAdditiveQuantizer.cpp +77 -74
  43. data/vendor/faiss/faiss/IndexIVFAdditiveQuantizerFastScan.cpp +96 -93
  44. data/vendor/faiss/faiss/IndexIVFAdditiveQuantizerFastScan.h +4 -1
  45. data/vendor/faiss/faiss/IndexIVFFastScan.cpp +357 -238
  46. data/vendor/faiss/faiss/IndexIVFFastScan.h +42 -41
  47. data/vendor/faiss/faiss/IndexIVFFlat.cpp +36 -68
  48. data/vendor/faiss/faiss/IndexIVFFlat.h +32 -0
  49. data/vendor/faiss/faiss/IndexIVFFlatPanorama.cpp +53 -30
  50. data/vendor/faiss/faiss/IndexIVFFlatPanorama.h +3 -1
  51. data/vendor/faiss/faiss/IndexIVFIndependentQuantizer.cpp +18 -15
  52. data/vendor/faiss/faiss/IndexIVFPQ.cpp +71 -843
  53. data/vendor/faiss/faiss/IndexIVFPQFastScan.cpp +151 -121
  54. data/vendor/faiss/faiss/IndexIVFPQFastScan.h +3 -0
  55. data/vendor/faiss/faiss/IndexIVFPQR.cpp +21 -17
  56. data/vendor/faiss/faiss/IndexIVFRaBitQ.cpp +26 -39
  57. data/vendor/faiss/faiss/IndexIVFRaBitQ.h +2 -1
  58. data/vendor/faiss/faiss/IndexIVFRaBitQFastScan.cpp +475 -476
  59. data/vendor/faiss/faiss/IndexIVFRaBitQFastScan.h +248 -93
  60. data/vendor/faiss/faiss/IndexIVFSpectralHash.cpp +41 -127
  61. data/vendor/faiss/faiss/IndexIVFSpectralHash.h +1 -1
  62. data/vendor/faiss/faiss/IndexLSH.cpp +36 -19
  63. data/vendor/faiss/faiss/IndexLattice.cpp +13 -13
  64. data/vendor/faiss/faiss/IndexNNDescent.cpp +36 -21
  65. data/vendor/faiss/faiss/IndexNNDescent.h +2 -2
  66. data/vendor/faiss/faiss/IndexNSG.cpp +39 -23
  67. data/vendor/faiss/faiss/IndexNeuralNetCodec.cpp +31 -11
  68. data/vendor/faiss/faiss/IndexPQ.cpp +128 -221
  69. data/vendor/faiss/faiss/IndexPQ.h +3 -2
  70. data/vendor/faiss/faiss/IndexPQFastScan.cpp +20 -14
  71. data/vendor/faiss/faiss/IndexPQFastScan.h +3 -0
  72. data/vendor/faiss/faiss/IndexPreTransform.cpp +25 -18
  73. data/vendor/faiss/faiss/IndexPreTransform.h +1 -1
  74. data/vendor/faiss/faiss/IndexRaBitQ.cpp +11 -36
  75. data/vendor/faiss/faiss/IndexRaBitQ.h +2 -1
  76. data/vendor/faiss/faiss/IndexRaBitQFastScan.cpp +41 -277
  77. data/vendor/faiss/faiss/IndexRaBitQFastScan.h +183 -27
  78. data/vendor/faiss/faiss/IndexRefine.cpp +30 -25
  79. data/vendor/faiss/faiss/IndexRefine.h +4 -4
  80. data/vendor/faiss/faiss/IndexReplicas.cpp +6 -6
  81. data/vendor/faiss/faiss/IndexRowwiseMinMax.cpp +15 -14
  82. data/vendor/faiss/faiss/IndexRowwiseMinMax.h +1 -1
  83. data/vendor/faiss/faiss/IndexScalarQuantizer.cpp +82 -14
  84. data/vendor/faiss/faiss/IndexShards.cpp +10 -9
  85. data/vendor/faiss/faiss/IndexShardsIVF.cpp +21 -15
  86. data/vendor/faiss/faiss/MatrixStats.cpp +5 -4
  87. data/vendor/faiss/faiss/MetaIndexes.cpp +19 -17
  88. data/vendor/faiss/faiss/MetaIndexes.h +1 -1
  89. data/vendor/faiss/faiss/MetricType.h +14 -7
  90. data/vendor/faiss/faiss/SuperKMeans.cpp +656 -0
  91. data/vendor/faiss/faiss/SuperKMeans.h +97 -0
  92. data/vendor/faiss/faiss/VectorTransform.cpp +237 -149
  93. data/vendor/faiss/faiss/VectorTransform.h +16 -16
  94. data/vendor/faiss/faiss/build.cpp +23 -0
  95. data/vendor/faiss/faiss/build.h +15 -0
  96. data/vendor/faiss/faiss/clone_index.cpp +48 -47
  97. data/vendor/faiss/faiss/cppcontrib/sa_decode/Level2-avx2-inl.h +47 -47
  98. data/vendor/faiss/faiss/cppcontrib/sa_decode/Level2-inl.h +11 -0
  99. data/vendor/faiss/faiss/cppcontrib/sa_decode/PQ-avx2-inl.h +38 -38
  100. data/vendor/faiss/faiss/cppcontrib/sa_decode/PQ-inl.h +11 -0
  101. data/vendor/faiss/faiss/factory_tools.cpp +5 -0
  102. data/vendor/faiss/faiss/gpu/GpuIndexCagra.h +6 -5
  103. data/vendor/faiss/faiss/gpu/GpuResources.h +1 -1
  104. data/vendor/faiss/faiss/gpu/StandardGpuResources.cpp +9 -9
  105. data/vendor/faiss/faiss/gpu/StandardGpuResources.h +4 -3
  106. data/vendor/faiss/faiss/gpu/test/TestGpuIndexFlat.cpp +46 -0
  107. data/vendor/faiss/faiss/gpu/test/TestGpuIndexIVFFlat.cpp +56 -0
  108. data/vendor/faiss/faiss/gpu/test/TestGpuIndexIVFPQ.cpp +78 -1
  109. data/vendor/faiss/faiss/gpu/test/TestUtils.cpp +72 -0
  110. data/vendor/faiss/faiss/gpu/test/TestUtils.h +23 -0
  111. data/vendor/faiss/faiss/gpu/utils/CuvsFilterConvert.h +1 -1
  112. data/vendor/faiss/faiss/gpu/utils/CuvsUtils.h +21 -10
  113. data/vendor/faiss/faiss/gpu_metal/GpuIndexFlat.h +22 -0
  114. data/vendor/faiss/faiss/gpu_metal/MetalCloner.h +35 -0
  115. data/vendor/faiss/faiss/gpu_metal/MetalFlatKernels.h +40 -0
  116. data/vendor/faiss/faiss/gpu_metal/MetalIndex.h +51 -0
  117. data/vendor/faiss/faiss/gpu_metal/MetalIndexFlat.h +65 -0
  118. data/vendor/faiss/faiss/gpu_metal/MetalKernels.h +66 -0
  119. data/vendor/faiss/faiss/gpu_metal/MetalResources.h +79 -0
  120. data/vendor/faiss/faiss/gpu_metal/StandardMetalResources.h +35 -0
  121. data/vendor/faiss/faiss/impl/AdSampling.cpp +103 -0
  122. data/vendor/faiss/faiss/impl/AdSampling.h +35 -0
  123. data/vendor/faiss/faiss/impl/AdditiveQuantizer.cpp +29 -25
  124. data/vendor/faiss/faiss/impl/AdditiveQuantizer.h +1 -0
  125. data/vendor/faiss/faiss/impl/AuxIndexStructures.cpp +10 -9
  126. data/vendor/faiss/faiss/impl/AuxIndexStructures.h +3 -0
  127. data/vendor/faiss/faiss/impl/ClusteringHelpers.cpp +244 -0
  128. data/vendor/faiss/faiss/impl/ClusteringHelpers.h +94 -0
  129. data/vendor/faiss/faiss/impl/ClusteringInitialization.cpp +16 -16
  130. data/vendor/faiss/faiss/impl/CodePacker.cpp +3 -3
  131. data/vendor/faiss/faiss/impl/CodePackerRaBitQ.cpp +1 -1
  132. data/vendor/faiss/faiss/impl/DistanceComputer.h +8 -8
  133. data/vendor/faiss/faiss/impl/FaissAssert.h +6 -3
  134. data/vendor/faiss/faiss/impl/FaissException.h +50 -3
  135. data/vendor/faiss/faiss/impl/HNSW.cpp +92 -317
  136. data/vendor/faiss/faiss/impl/HNSW.h +13 -34
  137. data/vendor/faiss/faiss/impl/IDSelector.cpp +15 -11
  138. data/vendor/faiss/faiss/impl/IDSelector.h +8 -8
  139. data/vendor/faiss/faiss/impl/InvertedListScannerStats.h +26 -0
  140. data/vendor/faiss/faiss/impl/LocalSearchQuantizer.cpp +82 -77
  141. data/vendor/faiss/faiss/impl/NNDescent.cpp +62 -25
  142. data/vendor/faiss/faiss/impl/NNDescent.h +6 -2
  143. data/vendor/faiss/faiss/impl/NSG.cpp +38 -21
  144. data/vendor/faiss/faiss/impl/NSG.h +4 -4
  145. data/vendor/faiss/faiss/impl/Panorama.cpp +23 -6
  146. data/vendor/faiss/faiss/impl/Panorama.h +258 -87
  147. data/vendor/faiss/faiss/impl/PdxLayout.cpp +93 -0
  148. data/vendor/faiss/faiss/impl/PdxLayout.h +41 -0
  149. data/vendor/faiss/faiss/impl/PolysemousTraining.cpp +46 -32
  150. data/vendor/faiss/faiss/impl/PolysemousTraining.h +3 -3
  151. data/vendor/faiss/faiss/impl/ProductAdditiveQuantizer.cpp +35 -35
  152. data/vendor/faiss/faiss/impl/ProductQuantizer-inl.h +21 -16
  153. data/vendor/faiss/faiss/impl/ProductQuantizer.cpp +30 -23
  154. data/vendor/faiss/faiss/impl/Quantizer.h +2 -2
  155. data/vendor/faiss/faiss/impl/RaBitQUtils.cpp +55 -49
  156. data/vendor/faiss/faiss/impl/RaBitQUtils.h +65 -0
  157. data/vendor/faiss/faiss/impl/RaBitQuantizer.cpp +296 -283
  158. data/vendor/faiss/faiss/impl/ResidualQuantizer.cpp +26 -23
  159. data/vendor/faiss/faiss/impl/ResidualQuantizer.h +1 -1
  160. data/vendor/faiss/faiss/impl/ResultHandler.h +99 -75
  161. data/vendor/faiss/faiss/impl/ScalarQuantizer.cpp +52 -4
  162. data/vendor/faiss/faiss/impl/ScalarQuantizer.h +27 -1
  163. data/vendor/faiss/faiss/impl/ThreadedIndex-inl.h +14 -11
  164. data/vendor/faiss/faiss/impl/VisitedTable.h +7 -0
  165. data/vendor/faiss/faiss/impl/approx_topk/approx_topk.h +276 -0
  166. data/vendor/faiss/faiss/impl/approx_topk/avx2.cpp +68 -0
  167. data/vendor/faiss/faiss/{utils → impl}/approx_topk/generic.h +15 -8
  168. data/vendor/faiss/faiss/impl/approx_topk/neon.cpp +68 -0
  169. data/vendor/faiss/faiss/impl/approx_topk/rq_beam_search_tab-inl.h +169 -0
  170. data/vendor/faiss/faiss/impl/approx_topk/rq_beam_search_tab.h +117 -0
  171. data/vendor/faiss/faiss/impl/approx_topk/simdlib256-inl.h +146 -0
  172. data/vendor/faiss/faiss/impl/binary_hamming/IndexBinaryHNSW_impl.h +73 -0
  173. data/vendor/faiss/faiss/impl/binary_hamming/IndexBinaryHash_impl.h +270 -0
  174. data/vendor/faiss/faiss/impl/binary_hamming/IndexBinaryIVF_impl.h +460 -0
  175. data/vendor/faiss/faiss/impl/binary_hamming/IndexIVFSpectralHash_impl.h +159 -0
  176. data/vendor/faiss/faiss/impl/binary_hamming/IndexPQ_impl.h +92 -0
  177. data/vendor/faiss/faiss/impl/binary_hamming/avx2.cpp +26 -0
  178. data/vendor/faiss/faiss/impl/binary_hamming/avx512.cpp +26 -0
  179. data/vendor/faiss/faiss/impl/binary_hamming/dispatch.h +143 -0
  180. data/vendor/faiss/faiss/impl/binary_hamming/neon.cpp +26 -0
  181. data/vendor/faiss/faiss/impl/binary_hamming/rvv.cpp +26 -0
  182. data/vendor/faiss/faiss/impl/expanded_scanners.h +8 -3
  183. data/vendor/faiss/faiss/impl/{FastScanDistancePostProcessing.h → fast_scan/FastScanDistancePostProcessing.h} +13 -6
  184. data/vendor/faiss/faiss/impl/{LookupTableScaler.h → fast_scan/LookupTableScaler.h} +16 -5
  185. data/vendor/faiss/faiss/impl/fast_scan/accumulate_loops.h +237 -0
  186. data/vendor/faiss/faiss/impl/fast_scan/accumulate_loops_512.h +185 -0
  187. data/vendor/faiss/faiss/impl/fast_scan/decompose_qbs.h +229 -0
  188. data/vendor/faiss/faiss/impl/fast_scan/dispatching.h +268 -0
  189. data/vendor/faiss/faiss/impl/{pq4_fast_scan.cpp → fast_scan/fast_scan.cpp} +169 -2
  190. data/vendor/faiss/faiss/impl/fast_scan/fast_scan.h +341 -0
  191. data/vendor/faiss/faiss/impl/fast_scan/impl-avx2.cpp +36 -0
  192. data/vendor/faiss/faiss/impl/fast_scan/impl-avx512.cpp +40 -0
  193. data/vendor/faiss/faiss/impl/fast_scan/impl-neon.cpp +120 -0
  194. data/vendor/faiss/faiss/impl/fast_scan/impl-riscv.cpp +104 -0
  195. data/vendor/faiss/faiss/impl/fast_scan/kernels_simd256.h +213 -0
  196. data/vendor/faiss/faiss/impl/{pq4_fast_scan_search_qbs.cpp → fast_scan/kernels_simd512.h} +26 -356
  197. data/vendor/faiss/faiss/impl/fast_scan/rabitq_dispatching.h +90 -0
  198. data/vendor/faiss/faiss/impl/fast_scan/rabitq_result_handler.h +108 -0
  199. data/vendor/faiss/faiss/impl/{simd_result_handlers.h → fast_scan/simd_result_handlers.h} +282 -134
  200. data/vendor/faiss/faiss/impl/hnsw/LockVector.cpp +54 -0
  201. data/vendor/faiss/faiss/impl/hnsw/LockVector.h +64 -0
  202. data/vendor/faiss/faiss/impl/hnsw/MinimaxHeap.cpp +91 -0
  203. data/vendor/faiss/faiss/impl/hnsw/MinimaxHeap.h +64 -0
  204. data/vendor/faiss/faiss/impl/hnsw/avx2.cpp +104 -0
  205. data/vendor/faiss/faiss/impl/hnsw/avx512.cpp +111 -0
  206. data/vendor/faiss/faiss/impl/index_read.cpp +1132 -45
  207. data/vendor/faiss/faiss/impl/index_read_utils.h +1 -1
  208. data/vendor/faiss/faiss/impl/index_write.cpp +95 -13
  209. data/vendor/faiss/faiss/impl/io.cpp +6 -6
  210. data/vendor/faiss/faiss/impl/io_macros.h +33 -16
  211. data/vendor/faiss/faiss/impl/kmeans1d.cpp +10 -10
  212. data/vendor/faiss/faiss/impl/lattice_Zn.cpp +37 -23
  213. data/vendor/faiss/faiss/impl/lattice_Zn.h +6 -6
  214. data/vendor/faiss/faiss/impl/mapped_io.cpp +6 -6
  215. data/vendor/faiss/faiss/impl/platform_macros.h +11 -4
  216. data/vendor/faiss/faiss/impl/pq_code_distance/IVFPQScanner_impl.h +549 -0
  217. data/vendor/faiss/faiss/impl/pq_code_distance/IVFPQ_QueryTables.cpp +245 -0
  218. data/vendor/faiss/faiss/impl/pq_code_distance/IVFPQ_QueryTables.h +105 -0
  219. data/vendor/faiss/faiss/impl/pq_code_distance/PQDistanceComputer_impl.h +106 -0
  220. data/vendor/faiss/faiss/impl/pq_code_distance/avx2.cpp +21 -0
  221. data/vendor/faiss/faiss/impl/pq_code_distance/avx512.cpp +21 -0
  222. data/vendor/faiss/faiss/impl/pq_code_distance/neon.cpp +21 -0
  223. data/vendor/faiss/faiss/impl/pq_code_distance/{pq_code_distance-avx2.cpp → pq_code_distance-avx2.h} +9 -13
  224. data/vendor/faiss/faiss/impl/pq_code_distance/{pq_code_distance-avx512.cpp → pq_code_distance-avx512.h} +9 -57
  225. data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-generic.cpp +29 -111
  226. data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-generic.h +96 -0
  227. data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-inl.h +238 -5
  228. data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-sve.cpp +5 -7
  229. data/vendor/faiss/faiss/impl/pq_code_distance/rvv.cpp +68 -0
  230. data/vendor/faiss/faiss/impl/residual_quantizer_encode_steps.cpp +311 -477
  231. data/vendor/faiss/faiss/impl/residual_quantizer_encode_steps.h +1 -1
  232. data/vendor/faiss/faiss/impl/scalar_quantizer/codecs.h +1 -1
  233. data/vendor/faiss/faiss/impl/scalar_quantizer/distance_computers.h +3 -2
  234. data/vendor/faiss/faiss/impl/scalar_quantizer/quantizers.h +102 -11
  235. data/vendor/faiss/faiss/impl/scalar_quantizer/scanners.h +27 -1
  236. data/vendor/faiss/faiss/impl/scalar_quantizer/similarities.h +3 -3
  237. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx2.cpp +148 -0
  238. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512.cpp +167 -0
  239. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-dispatch.h +59 -0
  240. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-neon.cpp +163 -0
  241. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-rvv.cpp +311 -0
  242. data/vendor/faiss/faiss/impl/scalar_quantizer/training.cpp +192 -8
  243. data/vendor/faiss/faiss/impl/scalar_quantizer/training.h +12 -0
  244. data/vendor/faiss/faiss/impl/simd_dispatch.h +100 -66
  245. data/vendor/faiss/faiss/impl/simdlib/simdlib.h +57 -0
  246. data/vendor/faiss/faiss/{utils → impl/simdlib}/simdlib_avx2.h +264 -172
  247. data/vendor/faiss/faiss/impl/simdlib/simdlib_avx512.h +414 -0
  248. data/vendor/faiss/faiss/impl/simdlib/simdlib_dispatch.h +44 -0
  249. data/vendor/faiss/faiss/{utils → impl/simdlib}/simdlib_emulated.h +231 -166
  250. data/vendor/faiss/faiss/{utils → impl/simdlib}/simdlib_neon.h +270 -218
  251. data/vendor/faiss/faiss/{utils → impl/simdlib}/simdlib_ppc64.h +201 -160
  252. data/vendor/faiss/faiss/impl/svs_io.cpp +12 -3
  253. data/vendor/faiss/faiss/impl/svs_io.h +8 -2
  254. data/vendor/faiss/faiss/index_factory.cpp +86 -18
  255. data/vendor/faiss/faiss/index_io.h +24 -0
  256. data/vendor/faiss/faiss/invlists/BlockInvertedLists.cpp +66 -16
  257. data/vendor/faiss/faiss/invlists/DirectMap.cpp +24 -14
  258. data/vendor/faiss/faiss/invlists/DirectMap.h +4 -3
  259. data/vendor/faiss/faiss/invlists/InvertedLists.cpp +157 -73
  260. data/vendor/faiss/faiss/invlists/InvertedLists.h +86 -23
  261. data/vendor/faiss/faiss/invlists/InvertedListsIOHook.cpp +4 -4
  262. data/vendor/faiss/faiss/invlists/OnDiskInvertedLists.cpp +13 -13
  263. data/vendor/faiss/faiss/invlists/OnDiskInvertedLists.h +1 -1
  264. data/vendor/faiss/faiss/svs/IndexSVSFaissUtils.h +1 -1
  265. data/vendor/faiss/faiss/svs/IndexSVSFlat.cpp +2 -2
  266. data/vendor/faiss/faiss/svs/IndexSVSIVF.cpp +350 -0
  267. data/vendor/faiss/faiss/svs/IndexSVSIVF.h +128 -0
  268. data/vendor/faiss/faiss/svs/IndexSVSIVFLVQ.cpp +40 -0
  269. data/vendor/faiss/faiss/svs/IndexSVSIVFLVQ.h +43 -0
  270. data/vendor/faiss/faiss/svs/IndexSVSIVFLeanVec.cpp +225 -0
  271. data/vendor/faiss/faiss/svs/IndexSVSIVFLeanVec.h +71 -0
  272. data/vendor/faiss/faiss/svs/IndexSVSVamana.cpp +25 -1
  273. data/vendor/faiss/faiss/svs/IndexSVSVamana.h +18 -2
  274. data/vendor/faiss/faiss/svs/IndexSVSVamanaLVQ.h +1 -1
  275. data/vendor/faiss/faiss/svs/IndexSVSVamanaLeanVec.cpp +12 -3
  276. data/vendor/faiss/faiss/svs/IndexSVSVamanaLeanVec.h +7 -2
  277. data/vendor/faiss/faiss/utils/Heap.cpp +10 -10
  278. data/vendor/faiss/faiss/utils/NeuralNet.cpp +47 -36
  279. data/vendor/faiss/faiss/utils/NeuralNet.h +1 -1
  280. data/vendor/faiss/faiss/utils/approx_topk_hamming/approx_topk_hamming.h +10 -4
  281. data/vendor/faiss/faiss/utils/distances.cpp +390 -560
  282. data/vendor/faiss/faiss/utils/distances.h +20 -1
  283. data/vendor/faiss/faiss/utils/distances_dispatch.h +117 -37
  284. data/vendor/faiss/faiss/utils/distances_fused/avx512.cpp +8 -7
  285. data/vendor/faiss/faiss/utils/distances_fused/distances_fused.cpp +33 -14
  286. data/vendor/faiss/faiss/utils/distances_fused/distances_fused.h +12 -1
  287. data/vendor/faiss/faiss/utils/distances_fused/simdlib_based.cpp +16 -293
  288. data/vendor/faiss/faiss/utils/distances_fused/simdlib_based_neon.cpp +57 -0
  289. data/vendor/faiss/faiss/utils/distances_fused/simdlib_kernel-inl.h +290 -0
  290. data/vendor/faiss/faiss/utils/distances_simd.cpp +5 -177
  291. data/vendor/faiss/faiss/utils/extra_distances.cpp +9 -8
  292. data/vendor/faiss/faiss/utils/extra_distances.h +32 -6
  293. data/vendor/faiss/faiss/utils/hamming-inl.h +13 -11
  294. data/vendor/faiss/faiss/utils/hamming.cpp +66 -517
  295. data/vendor/faiss/faiss/utils/hamming.h +92 -2
  296. data/vendor/faiss/faiss/utils/hamming_distance/common.h +287 -10
  297. data/vendor/faiss/faiss/utils/hamming_distance/hamming_avx2.cpp +15 -0
  298. data/vendor/faiss/faiss/utils/hamming_distance/hamming_avx512.cpp +15 -0
  299. data/vendor/faiss/faiss/utils/hamming_distance/hamming_computer-avx2.h +142 -0
  300. data/vendor/faiss/faiss/utils/hamming_distance/hamming_computer-avx512.h +234 -0
  301. data/vendor/faiss/faiss/utils/hamming_distance/hamming_computer-generic.h +368 -0
  302. data/vendor/faiss/faiss/utils/hamming_distance/hamming_computer-neon.h +322 -0
  303. data/vendor/faiss/faiss/utils/hamming_distance/hamming_computer-rvv.h +39 -0
  304. data/vendor/faiss/faiss/utils/hamming_distance/hamming_computer.h +146 -0
  305. data/vendor/faiss/faiss/utils/hamming_distance/hamming_impl.h +481 -0
  306. data/vendor/faiss/faiss/utils/hamming_distance/hamming_neon.cpp +15 -0
  307. data/vendor/faiss/faiss/utils/hamming_distance/hamming_rvv.cpp +15 -0
  308. data/vendor/faiss/faiss/utils/partitioning.cpp +66 -987
  309. data/vendor/faiss/faiss/utils/partitioning.h +31 -0
  310. data/vendor/faiss/faiss/utils/popcount.h +29 -0
  311. data/vendor/faiss/faiss/utils/pq_code_distance.h +2 -2
  312. data/vendor/faiss/faiss/utils/prefetch.h +2 -2
  313. data/vendor/faiss/faiss/utils/quantize_lut.cpp +30 -30
  314. data/vendor/faiss/faiss/utils/quantize_lut.h +1 -1
  315. data/vendor/faiss/faiss/utils/rabitq_simd.h +57 -536
  316. data/vendor/faiss/faiss/utils/random.cpp +6 -6
  317. data/vendor/faiss/faiss/utils/simd_impl/IVFFlatScanner-inl.h +51 -0
  318. data/vendor/faiss/faiss/utils/simd_impl/distances_aarch64.cpp +5 -1
  319. data/vendor/faiss/faiss/utils/simd_impl/distances_arm_sve.cpp +213 -4
  320. data/vendor/faiss/faiss/utils/simd_impl/distances_autovec-inl.h +163 -10
  321. data/vendor/faiss/faiss/utils/simd_impl/distances_avx2.cpp +250 -4
  322. data/vendor/faiss/faiss/utils/simd_impl/distances_avx512.cpp +7 -4
  323. data/vendor/faiss/faiss/utils/simd_impl/distances_rvv.cpp +189 -0
  324. data/vendor/faiss/faiss/utils/simd_impl/distances_simdlib256.h +195 -0
  325. data/vendor/faiss/faiss/utils/simd_impl/distances_sse-inl.h +2 -1
  326. data/vendor/faiss/faiss/utils/{distances_fused/simdlib_based.h → simd_impl/exhaustive_L2sqr_blas_cmax.h} +5 -10
  327. data/vendor/faiss/faiss/utils/simd_impl/hamming_impl.h +481 -0
  328. data/vendor/faiss/faiss/utils/simd_impl/partitioning_avx2.cpp +14 -0
  329. data/vendor/faiss/faiss/utils/simd_impl/partitioning_neon.cpp +14 -0
  330. data/vendor/faiss/faiss/utils/simd_impl/partitioning_simdlib256.h +1085 -0
  331. data/vendor/faiss/faiss/utils/simd_impl/rabitq_avx2.cpp +355 -0
  332. data/vendor/faiss/faiss/utils/simd_impl/rabitq_avx512.cpp +477 -0
  333. data/vendor/faiss/faiss/utils/simd_impl/rabitq_neon.cpp +55 -0
  334. data/vendor/faiss/faiss/utils/simd_impl/rabitq_rvv.cpp +55 -0
  335. data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_dispatch.h +32 -0
  336. data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_kernels.h +43 -0
  337. data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_kernels_avx2.cpp +57 -0
  338. data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_kernels_avx512.cpp +45 -0
  339. data/vendor/faiss/faiss/utils/simd_levels.cpp +17 -5
  340. data/vendor/faiss/faiss/utils/simd_levels.h +93 -1
  341. data/vendor/faiss/faiss/utils/sorting.cpp +48 -36
  342. data/vendor/faiss/faiss/utils/utils.cpp +5 -5
  343. data/vendor/faiss/faiss/utils/utils.h +3 -3
  344. metadata +119 -34
  345. data/vendor/faiss/faiss/impl/RaBitQStats.cpp +0 -29
  346. data/vendor/faiss/faiss/impl/RaBitQStats.h +0 -56
  347. data/vendor/faiss/faiss/impl/pq4_fast_scan.h +0 -224
  348. data/vendor/faiss/faiss/impl/pq4_fast_scan_search_1.cpp +0 -230
  349. data/vendor/faiss/faiss/utils/approx_topk/approx_topk.h +0 -84
  350. data/vendor/faiss/faiss/utils/approx_topk/avx2-inl.h +0 -196
  351. data/vendor/faiss/faiss/utils/approx_topk/mode.h +0 -34
  352. data/vendor/faiss/faiss/utils/distances_fused/avx512.h +0 -36
  353. data/vendor/faiss/faiss/utils/extra_distances-inl.h +0 -235
  354. data/vendor/faiss/faiss/utils/hamming_distance/avx2-inl.h +0 -462
  355. data/vendor/faiss/faiss/utils/hamming_distance/avx512-inl.h +0 -490
  356. data/vendor/faiss/faiss/utils/hamming_distance/generic-inl.h +0 -449
  357. data/vendor/faiss/faiss/utils/hamming_distance/hamdis-inl.h +0 -87
  358. data/vendor/faiss/faiss/utils/hamming_distance/neon-inl.h +0 -524
  359. data/vendor/faiss/faiss/utils/simdlib.h +0 -42
  360. data/vendor/faiss/faiss/utils/simdlib_avx512.h +0 -365
  361. /data/ext/faiss/{utils_rb.h → utils.h} +0 -0
@@ -10,6 +10,8 @@
10
10
 
11
11
  #include <faiss/impl/io_macros.h>
12
12
 
13
+ #include <cinttypes>
14
+ #include <cmath>
13
15
  #include <cstdio>
14
16
  #include <cstdlib>
15
17
  #include <cstring>
@@ -56,6 +58,9 @@
56
58
  #ifdef FAISS_ENABLE_SVS
57
59
  #include <faiss/impl/svs_io.h>
58
60
  #include <faiss/svs/IndexSVSFlat.h>
61
+ #include <faiss/svs/IndexSVSIVF.h>
62
+ #include <faiss/svs/IndexSVSIVFLVQ.h>
63
+ #include <faiss/svs/IndexSVSIVFLeanVec.h>
59
64
  #include <faiss/svs/IndexSVSVamana.h>
60
65
  #include <faiss/svs/IndexSVSVamanaLVQ.h>
61
66
  #include <faiss/svs/IndexSVSVamanaLeanVec.h>
@@ -78,6 +83,59 @@
78
83
 
79
84
  namespace faiss {
80
85
 
86
+ namespace {
87
+ size_t deserialization_loop_limit_ = 0;
88
+ size_t deserialization_vector_byte_limit_ = uint64_t{1} << 40; // 1 TB
89
+
90
+ #ifdef FAISS_ENABLE_SVS
91
+ // Read and validate an SVSStorageKind from the stream. Centralizes the
92
+ // [0, SVS_count) range check so every SVS read site rejects out-of-range
93
+ // values uniformly at the deserialization boundary, instead of letting
94
+ // to_svs_storage_kind() surface the failure later from inside an SVS
95
+ // runtime load.
96
+ SVSStorageKind read_svs_storage_kind(IOReader* f) {
97
+ int sk;
98
+ READ1(sk);
99
+ FAISS_THROW_IF_NOT_FMT(
100
+ sk >= 0 && sk < static_cast<int>(SVS_count),
101
+ "invalid SVS storage_kind=%d (must be in [0, %d))",
102
+ sk,
103
+ static_cast<int>(SVS_count));
104
+ return static_cast<SVSStorageKind>(sk);
105
+ }
106
+ #endif // FAISS_ENABLE_SVS
107
+ } // namespace
108
+
109
+ size_t get_deserialization_loop_limit() {
110
+ return deserialization_loop_limit_;
111
+ }
112
+
113
+ void set_deserialization_loop_limit(size_t value) {
114
+ deserialization_loop_limit_ = value;
115
+ }
116
+
117
+ size_t get_deserialization_vector_byte_limit() {
118
+ return deserialization_vector_byte_limit_;
119
+ }
120
+
121
+ void set_deserialization_vector_byte_limit(size_t value) {
122
+ deserialization_vector_byte_limit_ = value;
123
+ }
124
+
125
+ #define FAISS_CHECK_DESERIALIZATION_LOOP_LIMIT(val, field_name) \
126
+ do { \
127
+ auto limit_ = get_deserialization_loop_limit(); \
128
+ if (limit_ > 0) { \
129
+ FAISS_THROW_IF_NOT_FMT( \
130
+ static_cast<size_t>(val) <= limit_, \
131
+ "%s=%zd exceeds deserialization_loop_limit" \
132
+ " of %zd", \
133
+ field_name, \
134
+ static_cast<size_t>(val), \
135
+ limit_); \
136
+ } \
137
+ } while (0)
138
+
81
139
  /*************************************************************
82
140
  * Mmap-ing and viewing facilities
83
141
  **************************************************************/
@@ -268,8 +326,9 @@ std::unique_ptr<VectorTransform> read_VectorTransform_up(IOReader* f) {
268
326
  READ1(lt->have_bias);
269
327
  READVECTOR(lt->A);
270
328
  READVECTOR(lt->b);
271
- FAISS_THROW_IF_NOT(lt->A.size() >= lt->d_in * lt->d_out);
272
- FAISS_THROW_IF_NOT(!lt->have_bias || lt->b.size() >= lt->d_out);
329
+ FAISS_THROW_IF_NOT(
330
+ lt->A.size() >= size_t(lt->d_in) * size_t(lt->d_out));
331
+ FAISS_THROW_IF_NOT(!lt->have_bias || lt->b.size() >= size_t(lt->d_out));
273
332
  lt->set_is_orthonormal();
274
333
  vt = std::move(lt);
275
334
  } else if (h == fourcc("RmDT")) {
@@ -318,10 +377,103 @@ std::unique_ptr<VectorTransform> read_VectorTransform_up(IOReader* f) {
318
377
  READ1(vt->d_in);
319
378
  READ1(vt->d_out);
320
379
  READ1(vt->is_trained);
380
+ FAISS_THROW_IF_NOT_FMT(
381
+ vt->d_in >= 0,
382
+ "invalid VectorTransform d_in=%d (must be >= 0)",
383
+ vt->d_in);
384
+ FAISS_THROW_IF_NOT_FMT(
385
+ vt->d_out >= 0,
386
+ "invalid VectorTransform d_out=%d (must be >= 0)",
387
+ vt->d_out);
388
+ {
389
+ size_t dim_product = mul_no_overflow(
390
+ vt->d_in, vt->d_out, "VectorTransform d_in * d_out");
391
+ FAISS_THROW_IF_NOT_MSG(
392
+ dim_product <=
393
+ get_deserialization_vector_byte_limit() / sizeof(float),
394
+ "VectorTransform d_in * d_out would exceed "
395
+ "deserialization vector byte limit");
396
+ }
321
397
  if (h == fourcc("HRot")) {
398
+ FAISS_THROW_IF_NOT_FMT(
399
+ vt->d_out > 0 && (vt->d_out & (vt->d_out - 1)) == 0,
400
+ "invalid HadamardRotation d_out=%d (must be a power of 2 > 0)",
401
+ vt->d_out);
402
+ FAISS_THROW_IF_NOT_FMT(
403
+ vt->d_out >= vt->d_in,
404
+ "invalid HadamardRotation d_out=%d < d_in=%d",
405
+ vt->d_out,
406
+ vt->d_in);
407
+ FAISS_THROW_IF_NOT_FMT(
408
+ static_cast<size_t>(vt->d_out) <=
409
+ get_deserialization_vector_byte_limit() /
410
+ (3 * sizeof(float)),
411
+ "HadamardRotation d_out=%d would exceed deserialization byte limit",
412
+ vt->d_out);
322
413
  auto* hr = dynamic_cast<HadamardRotation*>(vt.get());
414
+ FAISS_THROW_IF_NOT_MSG(hr, "dynamic_cast to HadamardRotation failed");
415
+ FAISS_THROW_IF_NOT_FMT(
416
+ vt->d_in > 0,
417
+ "invalid HadamardRotation d_in=%d (must be > 0)",
418
+ vt->d_in);
419
+ size_t p = 1;
420
+ while (p < static_cast<size_t>(vt->d_in)) {
421
+ p <<= 1;
422
+ }
423
+ FAISS_THROW_IF_NOT_FMT(
424
+ static_cast<size_t>(vt->d_out) == p,
425
+ "invalid HadamardRotation d_out %d for d_in %d"
426
+ " (d_out must be the smallest power of 2 >= d_in)",
427
+ vt->d_out,
428
+ vt->d_in);
429
+ size_t byte_limit = get_deserialization_vector_byte_limit();
430
+ FAISS_THROW_IF_NOT_MSG(
431
+ p <= byte_limit / (3 * sizeof(float)),
432
+ "HadamardRotation d_out exceeds deserialization byte limit");
323
433
  hr->init(hr->seed);
324
434
  }
435
+ if (h == fourcc("RmDT")) {
436
+ auto* rdt = dynamic_cast<RemapDimensionsTransform*>(vt.get());
437
+ FAISS_THROW_IF_NOT_MSG(
438
+ rdt, "dynamic_cast to RemapDimensionsTransform failed");
439
+ FAISS_THROW_IF_NOT_FMT(
440
+ static_cast<int>(rdt->map.size()) >= rdt->d_out,
441
+ "RemapDimensionsTransform map size %d < d_out %d",
442
+ (int)rdt->map.size(),
443
+ rdt->d_out);
444
+ }
445
+ if (h == fourcc("VNrm")) {
446
+ FAISS_THROW_IF_NOT_FMT(
447
+ vt->d_in == vt->d_out,
448
+ "NormalizationTransform requires d_in == d_out, "
449
+ "got d_in=%d d_out=%d",
450
+ vt->d_in,
451
+ vt->d_out);
452
+ }
453
+ if (h == fourcc("VCnt")) {
454
+ auto* ct = dynamic_cast<CenteringTransform*>(vt.get());
455
+ FAISS_THROW_IF_NOT_MSG(ct, "dynamic_cast to CenteringTransform failed");
456
+ FAISS_THROW_IF_NOT_FMT(
457
+ static_cast<int>(ct->mean.size()) >= ct->d_in,
458
+ "CenteringTransform mean size %d < d_in %d",
459
+ (int)ct->mean.size(),
460
+ ct->d_in);
461
+ FAISS_THROW_IF_NOT_FMT(
462
+ vt->d_in == vt->d_out,
463
+ "CenteringTransform requires d_in == d_out, "
464
+ "got d_in=%d d_out=%d",
465
+ vt->d_in,
466
+ vt->d_out);
467
+ }
468
+ if (h == fourcc("Viqt")) {
469
+ auto* itqt = dynamic_cast<ITQTransform*>(vt.get());
470
+ FAISS_THROW_IF_NOT_MSG(itqt, "dynamic_cast to ITQTransform failed");
471
+ FAISS_THROW_IF_NOT_FMT(
472
+ static_cast<int>(itqt->mean.size()) >= itqt->d_in,
473
+ "ITQTransform mean size %d < d_in %d",
474
+ (int)itqt->mean.size(),
475
+ itqt->d_in);
476
+ }
325
477
  return vt;
326
478
  }
327
479
 
@@ -357,33 +509,121 @@ static void read_ArrayInvertedLists_sizes(
357
509
  }
358
510
  }
359
511
 
512
+ bool index_read_warn_on_null_invlists = true;
513
+
360
514
  std::unique_ptr<InvertedLists> read_InvertedLists_up(
361
515
  IOReader* f,
362
516
  int io_flags) {
363
517
  uint32_t h;
364
518
  READ1(h);
365
519
  if (h == fourcc("il00")) {
366
- fprintf(stderr,
367
- "read_InvertedLists:"
368
- " WARN! inverted lists not stored with IVF object\n");
520
+ if (index_read_warn_on_null_invlists) {
521
+ fprintf(stderr,
522
+ "read_InvertedLists:"
523
+ " WARN! inverted lists not stored with IVF object\n");
524
+ }
369
525
  return nullptr;
370
526
  } else if (h == fourcc("ilpn") && !(io_flags & IO_FLAG_SKIP_IVF_DATA)) {
371
527
  size_t nlist, code_size, n_levels;
372
528
  READ1(nlist);
529
+ FAISS_CHECK_DESERIALIZATION_LOOP_LIMIT(nlist, "ilpn nlist");
530
+ READ1(code_size);
531
+ READ1(n_levels);
532
+ FAISS_THROW_IF_NOT_FMT(
533
+ n_levels > 0, "invalid ilpn n_levels %zd", n_levels);
534
+ constexpr size_t bs = Panorama::kDefaultBatchSize;
535
+ auto ailp = std::make_unique<ArrayInvertedListsPanorama>(
536
+ nlist, code_size, n_levels, bs);
537
+ std::vector<size_t> sizes(nlist);
538
+ read_ArrayInvertedLists_sizes(f, sizes);
539
+ // Do resize + read in a single pass per list. See the matching
540
+ // comment in the `ilar` branch below for rationale.
541
+ size_t byte_limit = get_deserialization_vector_byte_limit();
542
+ for (size_t i = 0; i < nlist; i++) {
543
+ size_t n = sizes[i];
544
+ FAISS_THROW_IF_NOT_FMT(
545
+ n <= byte_limit / sizeof(idx_t),
546
+ "inverted list %zu ids size %zu exceeds "
547
+ "deserialization byte limit",
548
+ i,
549
+ n);
550
+ ailp->ids[i].resize(n);
551
+ size_t num_elems = ((n + bs - 1) / bs) * bs;
552
+ size_t codes_bytes = mul_no_overflow(
553
+ num_elems, code_size, "inverted list codes");
554
+ FAISS_THROW_IF_NOT_FMT(
555
+ codes_bytes <= byte_limit,
556
+ "inverted list %zu codes size %zu exceeds "
557
+ "deserialization byte limit",
558
+ i,
559
+ codes_bytes);
560
+ ailp->codes[i].resize(codes_bytes);
561
+ size_t cum_sums_count = mul_no_overflow(
562
+ num_elems,
563
+ add_no_overflow(
564
+ n_levels, 1, "inverted list cum_sums n_levels"),
565
+ "inverted list cum_sums");
566
+ FAISS_THROW_IF_NOT_FMT(
567
+ cum_sums_count <= byte_limit / sizeof(ailp->cum_sums[0][0]),
568
+ "inverted list %zu cum_sums size %zu exceeds "
569
+ "deserialization byte limit",
570
+ i,
571
+ cum_sums_count);
572
+ ailp->cum_sums[i].resize(cum_sums_count);
573
+ if (n > 0) {
574
+ read_vector_with_known_size(
575
+ ailp->codes[i], f, ailp->codes[i].size());
576
+ read_vector_with_known_size(ailp->ids[i], f, n);
577
+ read_vector_with_known_size(
578
+ ailp->cum_sums[i], f, ailp->cum_sums[i].size());
579
+ }
580
+ }
581
+ return ailp;
582
+ } else if (h == fourcc("ilp2") && !(io_flags & IO_FLAG_SKIP_IVF_DATA)) {
583
+ size_t nlist, code_size, n_levels, bs;
584
+ READ1(nlist);
585
+ FAISS_CHECK_DESERIALIZATION_LOOP_LIMIT(nlist, "ilp2 nlist");
373
586
  READ1(code_size);
374
587
  READ1(n_levels);
588
+ READ1(bs);
589
+ FAISS_THROW_IF_NOT_FMT(
590
+ n_levels > 0, "invalid ilp2 n_levels %zd", n_levels);
591
+ FAISS_THROW_IF_NOT_FMT(bs > 0, "invalid ilp2 batch_size %zd", bs);
375
592
  auto ailp = std::make_unique<ArrayInvertedListsPanorama>(
376
- nlist, code_size, n_levels);
593
+ nlist, code_size, n_levels, bs);
377
594
  std::vector<size_t> sizes(nlist);
378
595
  read_ArrayInvertedLists_sizes(f, sizes);
596
+ size_t byte_limit = get_deserialization_vector_byte_limit();
379
597
  for (size_t i = 0; i < nlist; i++) {
598
+ FAISS_THROW_IF_NOT_FMT(
599
+ sizes[i] <= byte_limit / sizeof(idx_t),
600
+ "inverted list %zu ids size %zu exceeds "
601
+ "deserialization byte limit",
602
+ i,
603
+ sizes[i]);
380
604
  ailp->ids[i].resize(sizes[i]);
381
- size_t num_elems =
382
- ((sizes[i] + ArrayInvertedListsPanorama::kBatchSize - 1) /
383
- ArrayInvertedListsPanorama::kBatchSize) *
384
- ArrayInvertedListsPanorama::kBatchSize;
385
- ailp->codes[i].resize(num_elems * code_size);
386
- ailp->cum_sums[i].resize(num_elems * (n_levels + 1));
605
+ size_t num_elems = ((sizes[i] + bs - 1) / bs) * bs;
606
+ size_t codes_bytes = mul_no_overflow(
607
+ num_elems, code_size, "inverted list codes");
608
+ FAISS_THROW_IF_NOT_FMT(
609
+ codes_bytes <= byte_limit,
610
+ "inverted list %zu codes size %zu exceeds "
611
+ "deserialization byte limit",
612
+ i,
613
+ codes_bytes);
614
+ ailp->codes[i].resize(codes_bytes);
615
+ size_t cum_sums_count = mul_no_overflow(
616
+ num_elems,
617
+ add_no_overflow(
618
+ n_levels, 1, "inverted list cum_sums n_levels"),
619
+ "inverted list cum_sums");
620
+ FAISS_THROW_IF_NOT_FMT(
621
+ cum_sums_count <= byte_limit / sizeof(ailp->cum_sums[0][0]),
622
+ "inverted list %zu cum_sums size %zu exceeds "
623
+ "deserialization byte limit",
624
+ i,
625
+ cum_sums_count);
626
+ ailp->cum_sums[i].resize(cum_sums_count);
387
627
  }
388
628
  for (size_t i = 0; i < nlist; i++) {
389
629
  size_t n = sizes[i];
@@ -399,18 +639,36 @@ std::unique_ptr<InvertedLists> read_InvertedLists_up(
399
639
  } else if (h == fourcc("ilar") && !(io_flags & IO_FLAG_SKIP_IVF_DATA)) {
400
640
  auto ails = std::make_unique<ArrayInvertedLists>(0, 0);
401
641
  READ1(ails->nlist);
642
+ FAISS_CHECK_DESERIALIZATION_LOOP_LIMIT(ails->nlist, "ilar nlist");
402
643
  READ1(ails->code_size);
403
644
  ails->ids.resize(ails->nlist);
404
645
  ails->codes.resize(ails->nlist);
405
646
  std::vector<size_t> sizes(ails->nlist);
406
647
  read_ArrayInvertedLists_sizes(f, sizes);
407
- for (size_t i = 0; i < ails->nlist; i++) {
408
- ails->ids[i].resize(sizes[i]);
409
- ails->codes[i].resize(mul_no_overflow(
410
- sizes[i], ails->code_size, "inverted list codes"));
411
- }
412
- for (size_t i = 0; i < ails->nlist; i++) {
413
- size_t n = ails->ids[i].size();
648
+ // Resize + read in a single pass per list so that each list's
649
+ // heap allocation is released by the mmap view-substitution
650
+ // before the next list is allocated. This bounds peak heap to
651
+ // one list's worth of memory, which matters for large IVF
652
+ // indexes (hundreds of GB) under IO_FLAG_MMAP_IFC.
653
+ size_t ilar_byte_limit = get_deserialization_vector_byte_limit();
654
+ for (size_t i = 0; i < sizes.size(); i++) {
655
+ size_t n = sizes[i];
656
+ FAISS_THROW_IF_NOT_FMT(
657
+ n <= ilar_byte_limit / sizeof(idx_t),
658
+ "inverted list %zu ids size %zu exceeds "
659
+ "deserialization byte limit",
660
+ i,
661
+ n);
662
+ ails->ids[i].resize(n);
663
+ size_t codes_bytes =
664
+ mul_no_overflow(n, ails->code_size, "inverted list codes");
665
+ FAISS_THROW_IF_NOT_FMT(
666
+ codes_bytes <= ilar_byte_limit,
667
+ "inverted list %zu codes size %zu exceeds "
668
+ "deserialization byte limit",
669
+ i,
670
+ codes_bytes);
671
+ ails->codes[i].resize(codes_bytes);
414
672
  if (n > 0) {
415
673
  read_vector_with_known_size(
416
674
  ails->codes[i],
@@ -429,6 +687,7 @@ std::unique_ptr<InvertedLists> read_InvertedLists_up(
429
687
  int h2 = (io_flags & 0xffff0000) | (fourcc("il__") & 0x0000ffff);
430
688
  size_t nlist, code_size;
431
689
  READ1(nlist);
690
+ FAISS_CHECK_DESERIALIZATION_LOOP_LIMIT(nlist, "ilar skip nlist");
432
691
  READ1(code_size);
433
692
  std::vector<size_t> sizes(nlist);
434
693
  read_ArrayInvertedLists_sizes(f, sizes);
@@ -463,13 +722,40 @@ void read_ProductQuantizer(ProductQuantizer* pq, IOReader* f) {
463
722
  READ1(pq->nbits);
464
723
  FAISS_THROW_IF_NOT_FMT(
465
724
  pq->M > 0, "invalid ProductQuantizer M=%zd (must be > 0)", pq->M);
725
+ FAISS_THROW_IF_NOT_FMT(
726
+ pq->nbits <= 24, "invalid ProductQuantizer nbits=%zd", pq->nbits);
727
+ {
728
+ size_t ksub = size_t{1} << pq->nbits;
729
+ size_t n = mul_no_overflow(pq->d, ksub, "PQ centroids");
730
+ FAISS_THROW_IF_NOT_MSG(
731
+ n < get_deserialization_vector_byte_limit() / sizeof(float),
732
+ "PQ centroids allocation would exceed deserialization byte limit");
733
+ // Per-subquantizer tables (e.g. IVFPQ residual norms, search-time
734
+ // distance tables) are sized M * ksub.
735
+ size_t m_ksub = mul_no_overflow(pq->M, ksub, "PQ M*ksub");
736
+ FAISS_THROW_IF_NOT_MSG(
737
+ m_ksub <
738
+ get_deserialization_vector_byte_limit() / sizeof(float),
739
+ "PQ M*ksub allocation would exceed deserialization byte limit");
740
+ }
466
741
  pq->set_derived_values();
467
742
  READVECTOR(pq->centroids);
743
+ FAISS_THROW_IF_NOT_FMT(
744
+ pq->centroids.size() == pq->d * pq->ksub,
745
+ "ProductQuantizer centroids size %zu != d * ksub (%zu * %zu = %zu)",
746
+ pq->centroids.size(),
747
+ pq->d,
748
+ pq->ksub,
749
+ pq->d * pq->ksub);
468
750
  }
469
751
 
470
752
  static void read_ResidualQuantizer_old(ResidualQuantizer& rq, IOReader* f) {
471
753
  READ1(rq.d);
754
+ FAISS_THROW_IF_NOT_FMT(
755
+ rq.d > 0, "invalid AdditiveQuantizer d %zd, must be > 0", rq.d);
472
756
  READ1(rq.M);
757
+ FAISS_THROW_IF_NOT_FMT(
758
+ rq.M > 0, "invalid AdditiveQuantizer M %zd, must be > 0", rq.M);
473
759
  READVECTOR(rq.nbits);
474
760
  FAISS_THROW_IF_NOT_FMT(
475
761
  rq.nbits.size() == rq.M,
@@ -488,7 +774,11 @@ static void read_ResidualQuantizer_old(ResidualQuantizer& rq, IOReader* f) {
488
774
 
489
775
  static void read_AdditiveQuantizer(AdditiveQuantizer& aq, IOReader* f) {
490
776
  READ1(aq.d);
777
+ FAISS_THROW_IF_NOT_FMT(
778
+ aq.d > 0, "invalid AdditiveQuantizer d %zd, must be > 0", aq.d);
491
779
  READ1(aq.M);
780
+ FAISS_THROW_IF_NOT_FMT(
781
+ aq.M > 0, "invalid AdditiveQuantizer M %zd, must be > 0", aq.M);
492
782
  READVECTOR(aq.nbits);
493
783
  READ1(aq.is_trained);
494
784
  READVECTOR(aq.codebooks);
@@ -515,6 +805,97 @@ static void read_AdditiveQuantizer(AdditiveQuantizer& aq, IOReader* f) {
515
805
  }
516
806
 
517
807
  aq.set_derived_values();
808
+
809
+ // Sanity-check codebooks size without knowing the effective dimension.
810
+ // codebooks stores effective_d * total_codebook_size floats, so its
811
+ // size must be a positive multiple of total_codebook_size.
812
+ if (aq.total_codebook_size > 0) {
813
+ FAISS_THROW_IF_NOT_FMT(
814
+ aq.codebooks.size() >= aq.total_codebook_size &&
815
+ aq.codebooks.size() % aq.total_codebook_size == 0,
816
+ "AdditiveQuantizer codebooks size %zd is not a positive "
817
+ "multiple of total_codebook_size %zd",
818
+ aq.codebooks.size(),
819
+ aq.total_codebook_size);
820
+ }
821
+ }
822
+
823
+ // Validate that the codebooks vector is large enough for the given
824
+ // effective dimension. For a standalone AdditiveQuantizer the effective
825
+ // dimension equals aq.d. For a ProductAdditiveQuantizer the codebooks
826
+ // are sized for d_sub = d / nsplits, so callers pass that instead.
827
+ static void validate_codebooks_size(
828
+ const AdditiveQuantizer& aq,
829
+ size_t effective_d) {
830
+ size_t required = mul_no_overflow(
831
+ effective_d, aq.total_codebook_size, "codebooks validation");
832
+ FAISS_THROW_IF_NOT_FMT(
833
+ aq.codebooks.size() >= required,
834
+ "AdditiveQuantizer codebooks size %zd too small for "
835
+ "d=%zd * total_codebook_size=%zd",
836
+ aq.codebooks.size(),
837
+ effective_d,
838
+ aq.total_codebook_size);
839
+ }
840
+
841
+ // Validate FastScan fields shared by all FastScan index types.
842
+ // M, ksub, bbs must be positive; bbs must be 32-aligned; M2 must be
843
+ // roundup(M, 2); and ksub * M / ksub * M2 must not overflow.
844
+ static void validate_fastscan_fields(
845
+ size_t M,
846
+ size_t M2,
847
+ size_t ksub,
848
+ int bbs,
849
+ const char* index_type) {
850
+ FAISS_THROW_IF_NOT_FMT(
851
+ M > 0 && ksub > 0,
852
+ "%s: invalid quantizer state (M=%zd, ksub=%zd, must be > 0)",
853
+ index_type,
854
+ M,
855
+ ksub);
856
+ FAISS_THROW_IF_NOT_FMT(
857
+ bbs > 0 && bbs % 32 == 0,
858
+ "%s: invalid bbs=%d (must be > 0 and a multiple of 32)",
859
+ index_type,
860
+ bbs);
861
+ size_t expected_M2 = (M + 1) & ~static_cast<size_t>(1); // roundup(M, 2)
862
+ FAISS_THROW_IF_NOT_FMT(
863
+ M2 == expected_M2,
864
+ "%s: invalid M2=%zd (expected roundup(M=%zd, 2) = %zd)",
865
+ index_type,
866
+ M2,
867
+ M,
868
+ expected_M2);
869
+ mul_no_overflow(ksub, M, index_type);
870
+ mul_no_overflow(ksub, M2, index_type);
871
+ }
872
+
873
+ // Validate that the AdditiveQuantizer dimension matches the index header
874
+ // dimension. compute_LUT() treats codebooks as a (d, total_codebook_size)
875
+ // matrix and query vectors are sized for idx_d, so a mismatch leads to
876
+ // out-of-bounds reads.
877
+ static void validate_aq_dimension_match(
878
+ const AdditiveQuantizer& aq,
879
+ int idx_d,
880
+ const char* index_type) {
881
+ FAISS_THROW_IF_NOT_FMT(
882
+ aq.d == static_cast<size_t>(idx_d),
883
+ "%s: AdditiveQuantizer d=%zd does not match index d=%d",
884
+ index_type,
885
+ aq.d,
886
+ idx_d);
887
+ }
888
+
889
+ static void validate_code_size_match(
890
+ size_t stored,
891
+ size_t expected,
892
+ const char* index_type) {
893
+ FAISS_THROW_IF_NOT_FMT(
894
+ stored == expected,
895
+ "%s code_size mismatch: stored %zd vs derived %zd",
896
+ index_type,
897
+ stored,
898
+ expected);
518
899
  }
519
900
 
520
901
  static void read_ResidualQuantizer(
@@ -522,8 +903,29 @@ static void read_ResidualQuantizer(
522
903
  IOReader* f,
523
904
  int io_flags) {
524
905
  read_AdditiveQuantizer(rq, f);
906
+ validate_codebooks_size(rq, rq.d);
525
907
  READ1(rq.train_type);
526
908
  READ1(rq.max_beam_size);
909
+ FAISS_THROW_IF_NOT_FMT(
910
+ rq.max_beam_size > 0,
911
+ "invalid max_beam_size %d, must be > 0",
912
+ rq.max_beam_size);
913
+ {
914
+ // Validate that the key allocation driven by max_beam_size
915
+ // (beam_size * M * sizeof(int32_t)) fits within the byte limit.
916
+ size_t beam_alloc = mul_no_overflow(
917
+ static_cast<size_t>(rq.max_beam_size),
918
+ rq.M,
919
+ "max_beam_size * M");
920
+ beam_alloc = mul_no_overflow(
921
+ beam_alloc, sizeof(int32_t), "max_beam_size * M * elem");
922
+ FAISS_THROW_IF_NOT_FMT(
923
+ beam_alloc < get_deserialization_vector_byte_limit(),
924
+ "max_beam_size %d * M %zd would exceed "
925
+ "deserialization vector byte limit",
926
+ rq.max_beam_size,
927
+ rq.M);
928
+ }
527
929
  if ((rq.train_type & ResidualQuantizer::Skip_codebook_tables) ||
528
930
  (io_flags & IO_FLAG_SKIP_PRECOMPUTE_TABLE)) {
529
931
  // don't precompute the tables
@@ -534,6 +936,7 @@ static void read_ResidualQuantizer(
534
936
 
535
937
  static void read_LocalSearchQuantizer(LocalSearchQuantizer& lsq, IOReader* f) {
536
938
  read_AdditiveQuantizer(lsq, f);
939
+ validate_codebooks_size(lsq, lsq.d);
537
940
  READ1(lsq.K);
538
941
  READ1(lsq.train_iters);
539
942
  READ1(lsq.encode_ils_iters);
@@ -552,6 +955,17 @@ static void read_ProductAdditiveQuantizer(
552
955
  IOReader* f) {
553
956
  read_AdditiveQuantizer(paq, f);
554
957
  READ1(paq.nsplits);
958
+ FAISS_THROW_IF_NOT_FMT(
959
+ paq.nsplits > 0,
960
+ "invalid ProductAdditiveQuantizer nsplits %zd (must be > 0)",
961
+ paq.nsplits);
962
+ FAISS_CHECK_DESERIALIZATION_LOOP_LIMIT(paq.nsplits, "nsplits");
963
+ FAISS_THROW_IF_NOT_FMT(
964
+ paq.d % paq.nsplits == 0,
965
+ "ProductAdditiveQuantizer d=%zd not divisible by nsplits=%zd",
966
+ paq.d,
967
+ paq.nsplits);
968
+ validate_codebooks_size(paq, paq.d / paq.nsplits);
555
969
  }
556
970
 
557
971
  static void read_ProductResidualQuantizer(
@@ -560,9 +974,19 @@ static void read_ProductResidualQuantizer(
560
974
  int io_flags) {
561
975
  read_ProductAdditiveQuantizer(prq, f);
562
976
 
977
+ size_t d_sub = prq.d / prq.nsplits;
563
978
  for (size_t i = 0; i < prq.nsplits; i++) {
564
979
  auto rq = std::make_unique<ResidualQuantizer>();
565
980
  read_ResidualQuantizer(*rq, f, io_flags);
981
+ FAISS_THROW_IF_NOT_FMT(
982
+ rq->d == d_sub,
983
+ "ProductResidualQuantizer sub-quantizer %zd has d=%zd, "
984
+ "expected d_sub=%zd (d=%zd / nsplits=%zd)",
985
+ i,
986
+ rq->d,
987
+ d_sub,
988
+ prq.d,
989
+ prq.nsplits);
566
990
  prq.quantizers.push_back(rq.release());
567
991
  }
568
992
  }
@@ -572,20 +996,112 @@ static void read_ProductLocalSearchQuantizer(
572
996
  IOReader* f) {
573
997
  read_ProductAdditiveQuantizer(plsq, f);
574
998
 
999
+ size_t d_sub = plsq.d / plsq.nsplits;
575
1000
  for (size_t i = 0; i < plsq.nsplits; i++) {
576
1001
  auto lsq = std::make_unique<LocalSearchQuantizer>();
577
1002
  read_LocalSearchQuantizer(*lsq, f);
1003
+ FAISS_THROW_IF_NOT_FMT(
1004
+ lsq->d == d_sub,
1005
+ "ProductLocalSearchQuantizer sub-quantizer %zd has d=%zd, "
1006
+ "expected d_sub=%zd (d=%zd / nsplits=%zd)",
1007
+ i,
1008
+ lsq->d,
1009
+ d_sub,
1010
+ plsq.d,
1011
+ plsq.nsplits);
578
1012
  plsq.quantizers.push_back(lsq.release());
579
1013
  }
580
1014
  }
581
1015
 
582
- void read_ScalarQuantizer(ScalarQuantizer* ivsc, IOReader* f) {
583
- READ1(ivsc->qtype);
1016
+ void read_ScalarQuantizer(
1017
+ ScalarQuantizer* ivsc,
1018
+ IOReader* f,
1019
+ const Index& idx) {
1020
+ int qtype_int;
1021
+ READ1(qtype_int);
1022
+ FAISS_THROW_IF_NOT_FMT(
1023
+ qtype_int >= ScalarQuantizer::QT_8bit &&
1024
+ qtype_int < ScalarQuantizer::QT_count,
1025
+ "invalid ScalarQuantizer qtype %d",
1026
+ qtype_int);
1027
+ ivsc->qtype = static_cast<ScalarQuantizer::QuantizerType>(qtype_int);
584
1028
  READ1(ivsc->rangestat);
585
1029
  READ1(ivsc->rangestat_arg);
586
1030
  READ1(ivsc->d);
587
1031
  READ1(ivsc->code_size);
1032
+ FAISS_THROW_IF_NOT_FMT(
1033
+ static_cast<size_t>(idx.d) == ivsc->d,
1034
+ "ScalarQuantizer d %zu != index header d %d",
1035
+ ivsc->d,
1036
+ idx.d);
588
1037
  READVECTOR(ivsc->trained);
1038
+ // Validate trained vector size matches the quantizer type and dimension.
1039
+ // UNIFORM/NON_UNIFORM qtypes require training data; other qtypes
1040
+ // (fp16, bf16, 8bit_direct*) need none.
1041
+ // An untrained index (is_trained == false) legitimately has
1042
+ // trained.size() == 0, so we allow that case.
1043
+ {
1044
+ size_t expected = 0;
1045
+ switch (ivsc->qtype) {
1046
+ case ScalarQuantizer::QT_4bit_uniform:
1047
+ case ScalarQuantizer::QT_8bit_uniform:
1048
+ expected = 2;
1049
+ break;
1050
+ case ScalarQuantizer::QT_4bit:
1051
+ case ScalarQuantizer::QT_8bit:
1052
+ case ScalarQuantizer::QT_6bit:
1053
+ expected = 2 * ivsc->d;
1054
+ break;
1055
+ case ScalarQuantizer::QT_fp16:
1056
+ case ScalarQuantizer::QT_bf16:
1057
+ case ScalarQuantizer::QT_8bit_direct:
1058
+ case ScalarQuantizer::QT_8bit_direct_signed:
1059
+ case ScalarQuantizer::QT_0bit:
1060
+ case ScalarQuantizer::QT_count:
1061
+ expected = 0;
1062
+ break;
1063
+ case ScalarQuantizer::QT_1bit_tqmse:
1064
+ expected = 2 + 1; // 2^bits centroids + (2^bits - 1) boundaries
1065
+ break;
1066
+ case ScalarQuantizer::QT_2bit_tqmse:
1067
+ expected = 4 + 3;
1068
+ break;
1069
+ case ScalarQuantizer::QT_3bit_tqmse:
1070
+ expected = 8 + 7;
1071
+ break;
1072
+ case ScalarQuantizer::QT_4bit_tqmse:
1073
+ expected = 16 + 15;
1074
+ break;
1075
+ case ScalarQuantizer::QT_8bit_tqmse:
1076
+ expected = 256 + 255;
1077
+ break;
1078
+ }
1079
+ if (ivsc->trained.empty() && expected > 0) {
1080
+ // Empty trained is only valid for untrained indices.
1081
+ FAISS_THROW_IF_NOT_FMT(
1082
+ !idx.is_trained,
1083
+ "ScalarQuantizer trained size 0 != expected %zu "
1084
+ "for qtype %d, d %zu (index is marked as trained)",
1085
+ expected,
1086
+ (int)ivsc->qtype,
1087
+ ivsc->d);
1088
+ } else {
1089
+ FAISS_THROW_IF_NOT_FMT(
1090
+ ivsc->trained.size() == expected,
1091
+ "ScalarQuantizer trained size %zu != expected %zu "
1092
+ "for qtype %d, d %zu",
1093
+ ivsc->trained.size(),
1094
+ expected,
1095
+ (int)ivsc->qtype,
1096
+ ivsc->d);
1097
+ if (expected > 0) {
1098
+ FAISS_THROW_IF_NOT_MSG(
1099
+ idx.is_trained,
1100
+ "ScalarQuantizer has training data but "
1101
+ "index header is_trained is false");
1102
+ }
1103
+ }
1104
+ }
589
1105
  ivsc->set_derived_sizes();
590
1106
  }
591
1107
 
@@ -723,6 +1239,9 @@ static void read_HNSW(HNSW& hnsw, IOReader* f) {
723
1239
  static void read_NSG(NSG& nsg, IOReader* f) {
724
1240
  READ1(nsg.ntotal);
725
1241
  READ1(nsg.R);
1242
+ FAISS_CHECK_DESERIALIZATION_LOOP_LIMIT(nsg.ntotal, "nsg.ntotal");
1243
+ FAISS_CHECK_DESERIALIZATION_LOOP_LIMIT(nsg.R, "nsg.R");
1244
+ FAISS_THROW_IF_NOT_FMT(nsg.R > 0, "invalid NSG R %d (must be > 0)", nsg.R);
726
1245
  READ1(nsg.L);
727
1246
  READ1(nsg.C);
728
1247
  READ1(nsg.search_L);
@@ -785,6 +1304,26 @@ static void read_NNDescent(NNDescent& nnd, IOReader* f) {
785
1304
  nnd.ntotal >= 0, "invalid NNDescent ntotal %d", nnd.ntotal);
786
1305
 
787
1306
  READVECTOR(nnd.final_graph);
1307
+ // Validate neighbor IDs in the graph
1308
+ if (nnd.has_built && nnd.K > 0 && nnd.ntotal > 0) {
1309
+ FAISS_THROW_IF_NOT_FMT(
1310
+ nnd.final_graph.size() == (size_t)nnd.ntotal * (size_t)nnd.K,
1311
+ "NNDescent final_graph size %zu != ntotal * K (%d * %d = %zu)",
1312
+ nnd.final_graph.size(),
1313
+ nnd.ntotal,
1314
+ nnd.K,
1315
+ (size_t)nnd.ntotal * (size_t)nnd.K);
1316
+ for (size_t i = 0; i < nnd.final_graph.size(); i++) {
1317
+ int id = nnd.final_graph[i];
1318
+ FAISS_THROW_IF_NOT_FMT(
1319
+ id >= -1 && id < nnd.ntotal,
1320
+ "NNDescent final_graph[%zu] = %d out of range "
1321
+ "[-1, %d)",
1322
+ i,
1323
+ id,
1324
+ nnd.ntotal);
1325
+ }
1326
+ }
788
1327
  }
789
1328
 
790
1329
  std::unique_ptr<ProductQuantizer> read_ProductQuantizer_up(const char* fname) {
@@ -809,6 +1348,7 @@ ProductQuantizer* read_ProductQuantizer(IOReader* reader) {
809
1348
  static void read_RaBitQuantizer(
810
1349
  RaBitQuantizer& rabitq,
811
1350
  IOReader* f,
1351
+ int expected_d,
812
1352
  bool multi_bit = true) {
813
1353
  READ1(rabitq.d);
814
1354
  READ1(rabitq.code_size);
@@ -821,6 +1361,12 @@ static void read_RaBitQuantizer(
821
1361
  } else {
822
1362
  rabitq.nb_bits = 1;
823
1363
  }
1364
+
1365
+ FAISS_THROW_IF_NOT_FMT(
1366
+ rabitq.d == static_cast<size_t>(expected_d),
1367
+ "RaBitQuantizer dimension mismatch: rabitq.d=%zu vs index d=%d",
1368
+ rabitq.d,
1369
+ expected_d);
824
1370
  }
825
1371
 
826
1372
  void read_direct_map(DirectMap* dm, IOReader* f) {
@@ -845,6 +1391,7 @@ void read_ivf_header(
845
1391
  std::vector<std::vector<idx_t>>* ids) {
846
1392
  read_index_header(*ivf, f);
847
1393
  READ1(ivf->nlist);
1394
+ FAISS_CHECK_DESERIALIZATION_LOOP_LIMIT(ivf->nlist, "nlist");
848
1395
  READ1(ivf->nprobe);
849
1396
  ivf->quantizer = read_index(f);
850
1397
  ivf->own_fields = true;
@@ -915,6 +1462,20 @@ static std::unique_ptr<IndexIVFPQ> read_ivfpq(
915
1462
  read_ProductQuantizer(&ivfpqr->refine_pq, f);
916
1463
  READVECTOR(ivfpqr->refine_codes);
917
1464
  READ1(ivfpqr->k_factor);
1465
+ // k_factor multiplies k to size search-time allocations
1466
+ // (n * k * k_factor labels + distances). Defaults are 1
1467
+ // (IndexRefine) and 4 (IndexIVFPQR); AutoTune explores
1468
+ // powers-of-two up to 64. Cap at 1000 to leave ample
1469
+ // headroom beyond any known usage while still blocking
1470
+ // OOM from crafted files (same cap as beam_factor in
1471
+ // ResidualCoarseQuantizer).
1472
+ FAISS_THROW_IF_NOT_FMT(
1473
+ std::isfinite(ivfpqr->k_factor) &&
1474
+ ivfpqr->k_factor >= 1.0f &&
1475
+ ivfpqr->k_factor <= 1000.0f,
1476
+ "k_factor %.6g out of valid range [1, 1000]"
1477
+ " for IndexIVFPQR",
1478
+ ivfpqr->k_factor);
918
1479
  }
919
1480
  }
920
1481
  return ivpq;
@@ -975,6 +1536,10 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
975
1536
  READVECTOR(idxl->thresholds);
976
1537
  int code_size_i;
977
1538
  READ1(code_size_i);
1539
+ FAISS_THROW_IF_NOT_FMT(
1540
+ code_size_i >= 0,
1541
+ "IndexLSH invalid code_size %d (must be >= 0)",
1542
+ code_size_i);
978
1543
  idxl->code_size = code_size_i;
979
1544
  if (h == fourcc("IxHE")) {
980
1545
  FAISS_THROW_IF_NOT_FMT(
@@ -985,6 +1550,8 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
985
1550
  // leak
986
1551
  idxl->code_size *= 8;
987
1552
  }
1553
+ validate_code_size_match(
1554
+ idxl->code_size, (idxl->nbits + 7) / 8, "IndexLSH");
988
1555
  {
989
1556
  // Read, dereference, discard.
990
1557
  auto sub_vt = read_VectorTransform_up(f);
@@ -1007,6 +1574,8 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1007
1574
  read_ProductQuantizer(&idxp->pq, f);
1008
1575
  idxp->code_size = idxp->pq.code_size;
1009
1576
  read_vector(idxp->codes, f);
1577
+ FAISS_THROW_IF_NOT(
1578
+ idxp->codes.size() == idxp->ntotal * idxp->code_size);
1010
1579
  if (h == fourcc("IxPo") || h == fourcc("IxPq")) {
1011
1580
  READ1(idxp->search_type);
1012
1581
  READ1(idxp->encode_signs);
@@ -1027,40 +1596,102 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1027
1596
  } else {
1028
1597
  read_ResidualQuantizer(idxr->rq, f, io_flags);
1029
1598
  }
1599
+ validate_aq_dimension_match(
1600
+ idxr->rq, idxr->d, "IndexResidualQuantizer");
1030
1601
  READ1(idxr->code_size);
1602
+ validate_code_size_match(
1603
+ idxr->code_size, idxr->rq.code_size, "IndexResidualQuantizer");
1031
1604
  read_vector(idxr->codes, f);
1605
+ FAISS_THROW_IF_NOT(
1606
+ idxr->codes.size() == idxr->ntotal * idxr->code_size);
1032
1607
  idx = std::move(idxr);
1033
1608
  } else if (h == fourcc("IxLS")) {
1034
1609
  auto idxr = std::make_unique<IndexLocalSearchQuantizer>();
1035
1610
  read_index_header(*idxr, f);
1036
1611
  read_LocalSearchQuantizer(idxr->lsq, f);
1612
+ validate_aq_dimension_match(
1613
+ idxr->lsq, idxr->d, "IndexLocalSearchQuantizer");
1037
1614
  READ1(idxr->code_size);
1615
+ validate_code_size_match(
1616
+ idxr->code_size,
1617
+ idxr->lsq.code_size,
1618
+ "IndexLocalSearchQuantizer");
1038
1619
  read_vector(idxr->codes, f);
1620
+ FAISS_THROW_IF_NOT(
1621
+ idxr->codes.size() == idxr->ntotal * idxr->code_size);
1039
1622
  idx = std::move(idxr);
1040
1623
  } else if (h == fourcc("IxPR")) {
1041
1624
  auto idxpr = std::make_unique<IndexProductResidualQuantizer>();
1042
1625
  read_index_header(*idxpr, f);
1043
1626
  read_ProductResidualQuantizer(idxpr->prq, f, io_flags);
1627
+ validate_aq_dimension_match(
1628
+ idxpr->prq, idxpr->d, "IndexProductResidualQuantizer");
1044
1629
  READ1(idxpr->code_size);
1630
+ validate_code_size_match(
1631
+ idxpr->code_size,
1632
+ idxpr->prq.code_size,
1633
+ "IndexProductResidualQuantizer");
1045
1634
  read_vector(idxpr->codes, f);
1635
+ FAISS_THROW_IF_NOT(
1636
+ idxpr->codes.size() == idxpr->ntotal * idxpr->code_size);
1046
1637
  idx = std::move(idxpr);
1047
1638
  } else if (h == fourcc("IxPL")) {
1048
1639
  auto idxpl = std::make_unique<IndexProductLocalSearchQuantizer>();
1049
1640
  read_index_header(*idxpl, f);
1050
1641
  read_ProductLocalSearchQuantizer(idxpl->plsq, f);
1642
+ validate_aq_dimension_match(
1643
+ idxpl->plsq, idxpl->d, "IndexProductLocalSearchQuantizer");
1051
1644
  READ1(idxpl->code_size);
1645
+ validate_code_size_match(
1646
+ idxpl->code_size,
1647
+ idxpl->plsq.code_size,
1648
+ "IndexProductLocalSearchQuantizer");
1052
1649
  read_vector(idxpl->codes, f);
1650
+ FAISS_THROW_IF_NOT(
1651
+ idxpl->codes.size() == idxpl->ntotal * idxpl->code_size);
1053
1652
  idx = std::move(idxpl);
1054
1653
  } else if (h == fourcc("ImRQ")) {
1055
1654
  auto idxr = std::make_unique<ResidualCoarseQuantizer>();
1056
1655
  read_index_header(*idxr, f);
1057
1656
  read_ResidualQuantizer(idxr->rq, f, io_flags);
1657
+ validate_aq_dimension_match(
1658
+ idxr->rq, idxr->d, "ResidualCoarseQuantizer");
1058
1659
  READ1(idxr->beam_factor);
1059
1660
  if (io_flags & IO_FLAG_SKIP_PRECOMPUTE_TABLE) {
1060
1661
  // then we force the beam factor to -1
1061
1662
  // which skips the table precomputation.
1062
1663
  idxr->beam_factor = -1;
1063
1664
  }
1665
+ FAISS_THROW_IF_NOT_MSG(
1666
+ static_cast<size_t>(idxr->ntotal) <
1667
+ get_deserialization_vector_byte_limit() / sizeof(float),
1668
+ "ResidualCoarseQuantizer centroid norms allocation would "
1669
+ "exceed deserialization byte limit");
1670
+ // Validate beam_factor to prevent overflow in search() where
1671
+ // beam_size = int(k * beam_factor) and allocations scale with it.
1672
+ if (idxr->beam_factor > 0) {
1673
+ FAISS_THROW_IF_NOT_FMT(
1674
+ idxr->beam_factor <= 1000.0f,
1675
+ "beam_factor %.6g is too large (max 1000)",
1676
+ idxr->beam_factor);
1677
+ }
1678
+ // Validate ntotal against byte limit: search() allocates
1679
+ // O(ntotal * M) when beam_size is capped to ntotal.
1680
+ {
1681
+ size_t ntotal_alloc = mul_no_overflow(
1682
+ static_cast<size_t>(idxr->ntotal),
1683
+ idxr->rq.M,
1684
+ "ntotal * M");
1685
+ ntotal_alloc = mul_no_overflow(
1686
+ ntotal_alloc, sizeof(int32_t), "ntotal * M * elem");
1687
+ FAISS_THROW_IF_NOT_FMT(
1688
+ ntotal_alloc < get_deserialization_vector_byte_limit(),
1689
+ "ResidualCoarseQuantizer ntotal %" PRId64
1690
+ " * M %zd would exceed "
1691
+ "deserialization vector byte limit",
1692
+ idxr->ntotal,
1693
+ idxr->rq.M);
1694
+ }
1064
1695
  idxr->set_beam_factor(idxr->beam_factor);
1065
1696
  idx = std::move(idxr);
1066
1697
  } else if (
@@ -1095,10 +1726,13 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1095
1726
  read_ProductResidualQuantizer(
1096
1727
  *(ProductResidualQuantizer*)idxaqfs->aq, f, io_flags);
1097
1728
  }
1729
+ validate_aq_dimension_match(
1730
+ *idxaqfs->aq, idxaqfs->d, "IndexAdditiveQuantizerFastScan");
1098
1731
 
1099
1732
  READ1(idxaqfs->implem);
1100
1733
  READ1(idxaqfs->bbs);
1101
1734
  READ1(idxaqfs->qbs);
1735
+ FAISS_THROW_IF_NOT_MSG(idxaqfs->qbs >= 0, "qbs must be non-negative");
1102
1736
 
1103
1737
  READ1(idxaqfs->M);
1104
1738
  READ1(idxaqfs->nbits);
@@ -1112,6 +1746,14 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1112
1746
  READ1(idxaqfs->max_train_points);
1113
1747
 
1114
1748
  READVECTOR(idxaqfs->codes);
1749
+
1750
+ validate_fastscan_fields(
1751
+ idxaqfs->M,
1752
+ idxaqfs->M2,
1753
+ idxaqfs->ksub,
1754
+ idxaqfs->bbs,
1755
+ "IndexAdditiveQuantizerFastScan");
1756
+
1115
1757
  idx = std::move(idxaqfs);
1116
1758
  } else if (
1117
1759
  h == fourcc("IVLf") || h == fourcc("IVRf") || h == fourcc("NPLf") ||
@@ -1146,11 +1788,14 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1146
1788
  read_ProductResidualQuantizer(
1147
1789
  *(ProductResidualQuantizer*)ivaqfs->aq, f, io_flags);
1148
1790
  }
1791
+ validate_aq_dimension_match(
1792
+ *ivaqfs->aq, ivaqfs->d, "IndexIVFAdditiveQuantizerFastScan");
1149
1793
 
1150
1794
  READ1(ivaqfs->by_residual);
1151
1795
  READ1(ivaqfs->implem);
1152
1796
  READ1(ivaqfs->bbs);
1153
1797
  READ1(ivaqfs->qbs);
1798
+ FAISS_THROW_IF_NOT_MSG(ivaqfs->qbs >= 0, "qbs must be non-negative");
1154
1799
 
1155
1800
  READ1(ivaqfs->M);
1156
1801
  READ1(ivaqfs->nbits);
@@ -1165,6 +1810,14 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1165
1810
 
1166
1811
  read_InvertedLists(*ivaqfs, f, io_flags);
1167
1812
  ivaqfs->init_code_packer();
1813
+
1814
+ validate_fastscan_fields(
1815
+ ivaqfs->M,
1816
+ ivaqfs->M2,
1817
+ ivaqfs->ksub,
1818
+ ivaqfs->bbs,
1819
+ "IndexIVFAdditiveQuantizerFastScan");
1820
+
1168
1821
  idx = std::move(ivaqfs);
1169
1822
  } else if (h == fourcc("IvFl") || h == fourcc("IvFL")) { // legacy
1170
1823
  auto ivfl = std::make_unique<IndexIVFFlat>();
@@ -1198,7 +1851,7 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1198
1851
  "invalid IVFFlatDedup instances table size: %zd "
1199
1852
  "(must be even)",
1200
1853
  tab.size());
1201
- for (long i = 0; i < tab.size(); i += 2) {
1854
+ for (size_t i = 0; i < tab.size(); i += 2) {
1202
1855
  std::pair<idx_t, idx_t> pair(tab[i], tab[i + 1]);
1203
1856
  ivfl->instances.insert(pair);
1204
1857
  }
@@ -1210,6 +1863,15 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1210
1863
  read_ivf_header(ivfp.get(), f);
1211
1864
  ivfp->code_size = ivfp->d * sizeof(float);
1212
1865
  READ1(ivfp->n_levels);
1866
+ ivfp->batch_size = Panorama::kDefaultBatchSize;
1867
+ read_InvertedLists(*ivfp, f, io_flags);
1868
+ idx = std::move(ivfp);
1869
+ } else if (h == fourcc("IwP2")) {
1870
+ auto ivfp = std::make_unique<IndexIVFFlatPanorama>();
1871
+ read_ivf_header(ivfp.get(), f);
1872
+ ivfp->code_size = ivfp->d * sizeof(float);
1873
+ READ1(ivfp->n_levels);
1874
+ READ1(ivfp->batch_size);
1213
1875
  read_InvertedLists(*ivfp, f, io_flags);
1214
1876
  idx = std::move(ivfp);
1215
1877
  } else if (h == fourcc("IwFl")) {
@@ -1221,7 +1883,7 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1221
1883
  } else if (h == fourcc("IxSQ")) {
1222
1884
  auto idxs = std::make_unique<IndexScalarQuantizer>();
1223
1885
  read_index_header(*idxs, f);
1224
- read_ScalarQuantizer(&idxs->sq, f);
1886
+ read_ScalarQuantizer(&idxs->sq, f, *idxs);
1225
1887
  read_vector(idxs->codes, f);
1226
1888
  idxs->code_size = idxs->sq.code_size;
1227
1889
  idx = std::move(idxs);
@@ -1248,6 +1910,27 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1248
1910
  nsq,
1249
1911
  dsq);
1250
1912
  auto idxl = std::make_unique<IndexLattice>(d, nsq, scale_nbit, r2);
1913
+ // IndexLattice is a lossy compressor: code_size should be
1914
+ // smaller than the uncompressed vector (d floats). A corrupt
1915
+ // scale_nbit can overflow the total_nbit computation, producing
1916
+ // a code_size that wraps to a huge value.
1917
+ {
1918
+ size_t max_code_size = mul_no_overflow(
1919
+ static_cast<size_t>(d),
1920
+ sizeof(float),
1921
+ "IndexLattice uncompressed vector size");
1922
+ FAISS_THROW_IF_NOT_FMT(
1923
+ idxl->code_size <= max_code_size,
1924
+ "IndexLattice code_size %zd exceeds uncompressed "
1925
+ "vector size %zd (likely corrupt scale_nbit=%d, "
1926
+ "d=%d, nsq=%d, r2=%d)",
1927
+ idxl->code_size,
1928
+ max_code_size,
1929
+ scale_nbit,
1930
+ d,
1931
+ nsq,
1932
+ r2);
1933
+ }
1251
1934
  read_index_header(*idxl, f);
1252
1935
  READVECTOR(idxl->trained);
1253
1936
  idx = std::move(idxl);
@@ -1255,17 +1938,21 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1255
1938
  auto ivsc = std::make_unique<IndexIVFScalarQuantizer>();
1256
1939
  std::vector<std::vector<idx_t>> ids;
1257
1940
  read_ivf_header(ivsc.get(), f, &ids);
1258
- read_ScalarQuantizer(&ivsc->sq, f);
1941
+ read_ScalarQuantizer(&ivsc->sq, f, *ivsc);
1259
1942
  READ1(ivsc->code_size);
1943
+ validate_code_size_match(
1944
+ ivsc->code_size, ivsc->sq.code_size, "IndexIVFScalarQuantizer");
1260
1945
  ArrayInvertedLists* ail = set_array_invlist(ivsc.get(), ids);
1261
- for (int i = 0; i < ivsc->nlist; i++)
1946
+ for (size_t i = 0; i < ivsc->nlist; i++)
1262
1947
  READVECTOR(ail->codes[i]);
1263
1948
  idx = std::move(ivsc);
1264
1949
  } else if (h == fourcc("IwSQ") || h == fourcc("IwSq")) {
1265
1950
  auto ivsc = std::make_unique<IndexIVFScalarQuantizer>();
1266
1951
  read_ivf_header(ivsc.get(), f);
1267
- read_ScalarQuantizer(&ivsc->sq, f);
1952
+ read_ScalarQuantizer(&ivsc->sq, f, *ivsc);
1268
1953
  READ1(ivsc->code_size);
1954
+ validate_code_size_match(
1955
+ ivsc->code_size, ivsc->sq.code_size, "IndexIVFScalarQuantizer");
1269
1956
  if (h == fourcc("IwSQ")) {
1270
1957
  ivsc->by_residual = true;
1271
1958
  } else {
@@ -1302,6 +1989,12 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1302
1989
  read_ProductResidualQuantizer(
1303
1990
  *(ProductResidualQuantizer*)iva->aq, f, io_flags);
1304
1991
  }
1992
+ validate_aq_dimension_match(
1993
+ *iva->aq, iva->d, "IndexIVFAdditiveQuantizer");
1994
+ validate_code_size_match(
1995
+ iva->code_size,
1996
+ iva->aq->code_size,
1997
+ "IndexIVFAdditiveQuantizer");
1305
1998
  READ1(iva->by_residual);
1306
1999
  READ1(iva->use_precomputed_table);
1307
2000
  read_InvertedLists(*iva, f, io_flags);
@@ -1337,6 +2030,18 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1337
2030
  indep->index_ivf = dynamic_cast<IndexIVF*>(ivf_idx.get());
1338
2031
  FAISS_THROW_IF_NOT(indep->index_ivf);
1339
2032
  ivf_idx.release();
2033
+ if (indep->vt) {
2034
+ FAISS_THROW_IF_NOT_FMT(
2035
+ indep->vt->d_in == indep->d,
2036
+ "IndexIVFIndependentQuantizer: vt->d_in (%d) != index d (%d)",
2037
+ indep->vt->d_in,
2038
+ indep->d);
2039
+ FAISS_THROW_IF_NOT_FMT(
2040
+ indep->vt->d_out == indep->index_ivf->d,
2041
+ "IndexIVFIndependentQuantizer: vt->d_out (%d) != index_ivf->d (%d)",
2042
+ indep->vt->d_out,
2043
+ indep->index_ivf->d);
2044
+ }
1340
2045
  if (auto index_ivfpq = dynamic_cast<IndexIVFPQ*>(indep->index_ivf)) {
1341
2046
  READ1(index_ivfpq->use_precomputed_table);
1342
2047
  }
@@ -1351,10 +2056,46 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1351
2056
  } else {
1352
2057
  READ1(nt);
1353
2058
  }
2059
+ FAISS_THROW_IF_NOT_FMT(
2060
+ nt >= 0,
2061
+ "invalid VectorTransform chain length %d (must be >= 0)",
2062
+ nt);
2063
+ FAISS_CHECK_DESERIALIZATION_LOOP_LIMIT(
2064
+ nt, "VectorTransform chain length");
1354
2065
  for (int i = 0; i < nt; i++) {
1355
2066
  ixpt->chain.push_back(read_VectorTransform(f));
1356
2067
  }
1357
2068
  ixpt->index = read_index(f, io_flags);
2069
+ // Validate transform chain dimension consistency:
2070
+ // chain[0].d_in must equal the outer index d, consecutive
2071
+ // transforms must have matching d_out/d_in, and the last
2072
+ // transform's d_out must equal the sub-index d.
2073
+ if (nt > 0) {
2074
+ FAISS_THROW_IF_NOT_FMT(
2075
+ ixpt->chain[0]->d_in == ixpt->d,
2076
+ "IndexPreTransform chain[0] d_in=%d != index d=%d",
2077
+ ixpt->chain[0]->d_in,
2078
+ ixpt->d);
2079
+ for (int i = 1; i < nt; i++) {
2080
+ FAISS_THROW_IF_NOT_FMT(
2081
+ ixpt->chain[i]->d_in == ixpt->chain[i - 1]->d_out,
2082
+ "IndexPreTransform chain[%d] d_in=%d != "
2083
+ "chain[%d] d_out=%d",
2084
+ i,
2085
+ ixpt->chain[i]->d_in,
2086
+ i - 1,
2087
+ ixpt->chain[i - 1]->d_out);
2088
+ }
2089
+ if (ixpt->index) {
2090
+ FAISS_THROW_IF_NOT_FMT(
2091
+ ixpt->chain[nt - 1]->d_out == ixpt->index->d,
2092
+ "IndexPreTransform chain[%d] d_out=%d "
2093
+ "!= sub-index d=%d",
2094
+ nt - 1,
2095
+ ixpt->chain[nt - 1]->d_out,
2096
+ ixpt->index->d);
2097
+ }
2098
+ }
1358
2099
  idx = std::move(ixpt);
1359
2100
  } else if (h == fourcc("Imiq")) {
1360
2101
  auto imiq = std::make_unique<MultiIndexQuantizer>();
@@ -1367,6 +2108,12 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1367
2108
  auto base = read_index_up(f, io_flags);
1368
2109
  auto refine = read_index_up(f, io_flags);
1369
2110
  READ1(idxrf->k_factor);
2111
+ // Same rationale as IndexIVFPQR k_factor above.
2112
+ FAISS_THROW_IF_NOT_FMT(
2113
+ std::isfinite(idxrf->k_factor) && idxrf->k_factor >= 1.0f &&
2114
+ idxrf->k_factor <= 1000.0f,
2115
+ "k_factor %.6g out of valid range [1, 1000] for IndexRefine",
2116
+ idxrf->k_factor);
1370
2117
  if (h == fourcc("IxRP")) {
1371
2118
  // then make a RefineFlatPanorama with it
1372
2119
  auto idxrf_new = std::make_unique<IndexRefinePanorama>();
@@ -1392,6 +2139,18 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1392
2139
  idxmap->index = read_index(f, io_flags);
1393
2140
  idxmap->own_fields = true;
1394
2141
  READVECTOR(idxmap->id_map);
2142
+ FAISS_THROW_IF_NOT_FMT(
2143
+ idxmap->id_map.size() == idxmap->ntotal,
2144
+ "IndexIDMap id_map size (%" PRId64
2145
+ ") does not match ntotal (%" PRId64 ")",
2146
+ int64_t(idxmap->id_map.size()),
2147
+ idxmap->ntotal);
2148
+ FAISS_THROW_IF_NOT_FMT(
2149
+ idxmap->index->ntotal == idxmap->ntotal,
2150
+ "IndexIDMap inner index ntotal (%" PRId64
2151
+ ") does not match IndexIDMap ntotal (%" PRId64 ")",
2152
+ idxmap->index->ntotal,
2153
+ idxmap->ntotal);
1395
2154
  if (is_map2) {
1396
2155
  static_cast<IndexIDMap2*>(idxmap.get())->construct_rev_map();
1397
2156
  }
@@ -1400,12 +2159,21 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1400
2159
  auto idxp = std::make_unique<Index2Layer>();
1401
2160
  read_index_header(*idxp, f);
1402
2161
  idxp->q1.quantizer = read_index(f, io_flags);
2162
+ idxp->q1.own_fields = true;
1403
2163
  READ1(idxp->q1.nlist);
1404
2164
  READ1(idxp->q1.quantizer_trains_alone);
1405
2165
  read_ProductQuantizer(&idxp->pq, f);
1406
2166
  READ1(idxp->code_size_1);
1407
2167
  READ1(idxp->code_size_2);
1408
2168
  READ1(idxp->code_size);
2169
+ validate_code_size_match(
2170
+ idxp->code_size_2,
2171
+ idxp->pq.code_size,
2172
+ "Index2Layer code_size_2");
2173
+ validate_code_size_match(
2174
+ idxp->code_size,
2175
+ idxp->code_size_1 + idxp->code_size_2,
2176
+ "Index2Layer");
1409
2177
  read_vector(idxp->codes, f);
1410
2178
  idx = std::move(idxp);
1411
2179
  } else if (
@@ -1438,6 +2206,9 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1438
2206
  if (h == fourcc("IHfP")) {
1439
2207
  auto idx_panorama =
1440
2208
  dynamic_cast<IndexHNSWFlatPanorama*>(idxhnsw.get());
2209
+ FAISS_THROW_IF_NOT_MSG(
2210
+ idx_panorama,
2211
+ "dynamic_cast to IndexHNSWFlatPanorama failed");
1441
2212
  size_t nlevels;
1442
2213
  READ1(nlevels);
1443
2214
  const_cast<size_t&>(idx_panorama->num_panorama_levels) = nlevels;
@@ -1448,6 +2219,8 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1448
2219
  if (h == fourcc("IHNc") || h == fourcc("IHc2")) {
1449
2220
  READ1(idxhnsw->keep_max_size_level0);
1450
2221
  auto idx_hnsw_cagra = dynamic_cast<IndexHNSWCagra*>(idxhnsw.get());
2222
+ FAISS_THROW_IF_NOT_MSG(
2223
+ idx_hnsw_cagra, "dynamic_cast to IndexHNSWCagra failed");
1451
2224
  READ1(idx_hnsw_cagra->base_level_only);
1452
2225
  READ1(idx_hnsw_cagra->num_base_level_search_entrypoints);
1453
2226
  if (h == fourcc("IHc2")) {
@@ -1457,11 +2230,43 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1457
2230
  }
1458
2231
  }
1459
2232
  read_HNSW(idxhnsw->hnsw, f);
2233
+ // Cross-check HNSW graph size against index header ntotal
2234
+ FAISS_THROW_IF_NOT_FMT(
2235
+ idxhnsw->hnsw.levels.size() == (size_t)idxhnsw->ntotal,
2236
+ "HNSW levels size %zu != index ntotal %" PRId64,
2237
+ idxhnsw->hnsw.levels.size(),
2238
+ idxhnsw->ntotal);
1460
2239
  idxhnsw->hnsw.is_panorama = (h == fourcc("IHfP"));
1461
2240
  idxhnsw->storage = read_index(f, io_flags);
1462
2241
  idxhnsw->own_fields = idxhnsw->storage != nullptr;
2242
+ // Cross-check storage ntotal and d against index
2243
+ if (idxhnsw->storage) {
2244
+ FAISS_THROW_IF_NOT_FMT(
2245
+ idxhnsw->storage->ntotal == idxhnsw->ntotal,
2246
+ "HNSW storage ntotal %" PRId64 " != index ntotal %" PRId64,
2247
+ idxhnsw->storage->ntotal,
2248
+ idxhnsw->ntotal);
2249
+ FAISS_THROW_IF_NOT_FMT(
2250
+ idxhnsw->storage->d == idxhnsw->d,
2251
+ "HNSW storage d %d != index d %d",
2252
+ idxhnsw->storage->d,
2253
+ idxhnsw->d);
2254
+ }
2255
+ if (h == fourcc("IHN2")) {
2256
+ FAISS_THROW_IF_NOT_MSG(
2257
+ idxhnsw->storage,
2258
+ "IndexHNSW2Level requires non-null storage");
2259
+ FAISS_THROW_IF_NOT_MSG(
2260
+ dynamic_cast<Index2Layer*>(idxhnsw->storage) ||
2261
+ dynamic_cast<IndexIVFPQ*>(idxhnsw->storage),
2262
+ "IndexHNSW2Level storage must be Index2Layer or IndexIVFPQ");
2263
+ }
1463
2264
  if (h == fourcc("IHNp") && !(io_flags & IO_FLAG_PQ_SKIP_SDC_TABLE)) {
1464
- dynamic_cast<IndexPQ*>(idxhnsw->storage)->pq.compute_sdc_table();
2265
+ auto* storage_pq = dynamic_cast<IndexPQ*>(idxhnsw->storage);
2266
+ FAISS_THROW_IF_NOT_MSG(
2267
+ storage_pq,
2268
+ "dynamic_cast to IndexPQ failed for HNSW storage");
2269
+ storage_pq->pq.compute_sdc_table();
1465
2270
  }
1466
2271
  idx = std::move(idxhnsw);
1467
2272
  } else if (
@@ -1484,15 +2289,58 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1484
2289
  READ1(idxnsg->nndescent_L);
1485
2290
  READ1(idxnsg->nndescent_iter);
1486
2291
  read_NSG(idxnsg->nsg, f);
2292
+ // Cross-check NSG graph ntotal against index header ntotal
2293
+ if (idxnsg->nsg.is_built) {
2294
+ FAISS_THROW_IF_NOT_FMT(
2295
+ idxnsg->nsg.ntotal == idxnsg->ntotal,
2296
+ "NSG ntotal %d != index ntotal %" PRId64,
2297
+ idxnsg->nsg.ntotal,
2298
+ idxnsg->ntotal);
2299
+ }
1487
2300
  idxnsg->storage = read_index(f, io_flags);
1488
2301
  idxnsg->own_fields = true;
2302
+ // Cross-check storage ntotal and d against index
2303
+ if (idxnsg->storage) {
2304
+ FAISS_THROW_IF_NOT_FMT(
2305
+ idxnsg->storage->ntotal == idxnsg->ntotal,
2306
+ "NSG storage ntotal %" PRId64 " != index ntotal %" PRId64,
2307
+ idxnsg->storage->ntotal,
2308
+ idxnsg->ntotal);
2309
+ FAISS_THROW_IF_NOT_FMT(
2310
+ idxnsg->storage->d == idxnsg->d,
2311
+ "NSG storage d %d != index d %d",
2312
+ idxnsg->storage->d,
2313
+ idxnsg->d);
2314
+ }
1489
2315
  idx = std::move(idxnsg);
1490
2316
  } else if (h == fourcc("INNf")) {
1491
2317
  auto idxnnd = std::make_unique<IndexNNDescentFlat>();
1492
2318
  read_index_header(*idxnnd, f);
1493
2319
  read_NNDescent(idxnnd->nndescent, f);
2320
+ // Cross-check NNDescent ntotal against index header ntotal
2321
+ if (idxnnd->nndescent.has_built) {
2322
+ FAISS_THROW_IF_NOT_FMT(
2323
+ idxnnd->nndescent.ntotal == idxnnd->ntotal,
2324
+ "NNDescent ntotal %d != index ntotal %" PRId64,
2325
+ idxnnd->nndescent.ntotal,
2326
+ idxnnd->ntotal);
2327
+ }
1494
2328
  idxnnd->storage = read_index(f, io_flags);
1495
2329
  idxnnd->own_fields = true;
2330
+ // Cross-check storage ntotal and d against index
2331
+ if (idxnnd->storage) {
2332
+ FAISS_THROW_IF_NOT_FMT(
2333
+ idxnnd->storage->ntotal == idxnnd->ntotal,
2334
+ "NNDescent storage ntotal %" PRId64
2335
+ " != index ntotal %" PRId64,
2336
+ idxnnd->storage->ntotal,
2337
+ idxnnd->ntotal);
2338
+ FAISS_THROW_IF_NOT_FMT(
2339
+ idxnnd->storage->d == idxnnd->d,
2340
+ "NNDescent storage d %d != index d %d",
2341
+ idxnnd->storage->d,
2342
+ idxnnd->d);
2343
+ }
1496
2344
  idx = std::move(idxnnd);
1497
2345
  } else if (h == fourcc("IPfs")) {
1498
2346
  auto idxpqfs = std::make_unique<IndexPQFastScan>();
@@ -1501,6 +2349,7 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1501
2349
  READ1(idxpqfs->implem);
1502
2350
  READ1(idxpqfs->bbs);
1503
2351
  READ1(idxpqfs->qbs);
2352
+ FAISS_THROW_IF_NOT_MSG(idxpqfs->qbs >= 0, "qbs must be non-negative");
1504
2353
  READ1(idxpqfs->ntotal2);
1505
2354
  READ1(idxpqfs->M2);
1506
2355
  READVECTOR(idxpqfs->codes);
@@ -1511,6 +2360,13 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1511
2360
  idxpqfs->ksub = (1 << pq.nbits);
1512
2361
  idxpqfs->code_size = pq.code_size;
1513
2362
 
2363
+ validate_fastscan_fields(
2364
+ idxpqfs->M,
2365
+ idxpqfs->M2,
2366
+ idxpqfs->ksub,
2367
+ idxpqfs->bbs,
2368
+ "IndexPQFastScan");
2369
+
1514
2370
  idx = std::move(idxpqfs);
1515
2371
 
1516
2372
  } else if (h == fourcc("IwPf")) {
@@ -1533,6 +2389,9 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1533
2389
  ivpq->code_size = pq.code_size;
1534
2390
  ivpq->init_code_packer();
1535
2391
 
2392
+ validate_fastscan_fields(
2393
+ ivpq->M, ivpq->M2, ivpq->ksub, ivpq->bbs, "IndexIVFPQFastScan");
2394
+
1536
2395
  idx = std::move(ivpq);
1537
2396
  } else if (h == fourcc("IRMf")) {
1538
2397
  auto imm = std::make_unique<IndexRowwiseMinMax>();
@@ -1557,9 +2416,13 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1557
2416
 
1558
2417
  auto idxqfs = std::make_unique<IndexRaBitQFastScan>();
1559
2418
  read_index_header(*idxqfs, f);
1560
- read_RaBitQuantizer(idxqfs->rabitq, f, true);
2419
+ read_RaBitQuantizer(idxqfs->rabitq, f, idxqfs->d, true);
1561
2420
  READVECTOR(idxqfs->center);
1562
2421
  READ1(idxqfs->qb);
2422
+ FAISS_THROW_IF_NOT_FMT(
2423
+ idxqfs->qb > 0 && idxqfs->qb <= 8,
2424
+ "invalid RaBitQ qb=%d (must be in [1, 8])",
2425
+ idxqfs->qb);
1563
2426
 
1564
2427
  std::vector<uint8_t> legacy_flat_storage;
1565
2428
  if (is_legacy) {
@@ -1577,6 +2440,13 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1577
2440
  idxqfs->nbits = nbits_fastscan;
1578
2441
  idxqfs->ksub = (1 << nbits_fastscan);
1579
2442
 
2443
+ validate_fastscan_fields(
2444
+ idxqfs->M,
2445
+ idxqfs->M2,
2446
+ idxqfs->ksub,
2447
+ idxqfs->bbs,
2448
+ "IndexRaBitQFastScan");
2449
+
1580
2450
  READVECTOR(idxqfs->codes);
1581
2451
 
1582
2452
  if (is_legacy) {
@@ -1602,12 +2472,19 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1602
2472
 
1603
2473
  idx = std::move(idxqfs);
1604
2474
  } else if (h == fourcc("Ixrq")) {
2475
+ // Ixrq = original single-bit format
1605
2476
  auto idxq = std::make_unique<IndexRaBitQ>();
1606
2477
  read_index_header(*idxq, f);
1607
- read_RaBitQuantizer(idxq->rabitq, f, false);
2478
+ read_RaBitQuantizer(idxq->rabitq, f, idxq->d, false);
1608
2479
  READVECTOR(idxq->codes);
1609
2480
  READVECTOR(idxq->center);
1610
2481
  READ1(idxq->qb);
2482
+ // qb=0: Not quantized - direct distance computation on given float32s.
2483
+ // qb>0 && qb<=8: Scalar-quantized with qb bits of precision.
2484
+ FAISS_THROW_IF_NOT_FMT(
2485
+ idxq->qb <= 8,
2486
+ "invalid RaBitQ qb=%d (must be in [0, 8])",
2487
+ idxq->qb);
1611
2488
 
1612
2489
  // rabitq.nb_bits is already set to 1 by read_RaBitQuantizer
1613
2490
  idxq->code_size = idxq->rabitq.code_size;
@@ -1616,20 +2493,33 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1616
2493
  // Ixrr = multi-bit format (new)
1617
2494
  auto idxq = std::make_unique<IndexRaBitQ>();
1618
2495
  read_index_header(*idxq, f);
1619
- read_RaBitQuantizer(idxq->rabitq, f, true); // Reads nb_bits from file
2496
+ read_RaBitQuantizer(
2497
+ idxq->rabitq, f, idxq->d, true); // Reads nb_bits from file
1620
2498
  READVECTOR(idxq->codes);
1621
2499
  READVECTOR(idxq->center);
1622
2500
  READ1(idxq->qb);
2501
+ // qb=0: Not quantized - direct distance computation on given float32s.
2502
+ // qb>0 && qb<=8: Scalar-quantized with qb bits of precision.
2503
+ FAISS_THROW_IF_NOT_FMT(
2504
+ idxq->qb <= 8,
2505
+ "invalid RaBitQ qb=%d (must be in [0, 8])",
2506
+ idxq->qb);
1623
2507
 
1624
2508
  idxq->code_size = idxq->rabitq.code_size;
1625
2509
  idx = std::move(idxq);
1626
2510
  } else if (h == fourcc("Iwrq")) {
1627
2511
  auto ivrq = std::make_unique<IndexIVFRaBitQ>();
1628
2512
  read_ivf_header(ivrq.get(), f);
1629
- read_RaBitQuantizer(ivrq->rabitq, f, false);
2513
+ read_RaBitQuantizer(ivrq->rabitq, f, ivrq->d, false);
1630
2514
  READ1(ivrq->code_size);
1631
2515
  READ1(ivrq->by_residual);
1632
2516
  READ1(ivrq->qb);
2517
+ // qb=0: Not quantized - direct distance computation on given float32s.
2518
+ // qb>0 && qb<=8: Scalar-quantized with qb bits of precision.
2519
+ FAISS_THROW_IF_NOT_FMT(
2520
+ ivrq->qb <= 8,
2521
+ "invalid RaBitQ qb=%d (must be in [0, 8])",
2522
+ ivrq->qb);
1633
2523
 
1634
2524
  // rabitq.nb_bits is already set to 1 by read_RaBitQuantizer
1635
2525
  // Update rabitq to match nb_bits
@@ -1642,10 +2532,17 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1642
2532
  // Iwrr = multi-bit format (new)
1643
2533
  auto ivrq = std::make_unique<IndexIVFRaBitQ>();
1644
2534
  read_ivf_header(ivrq.get(), f);
1645
- read_RaBitQuantizer(ivrq->rabitq, f, true); // Reads nb_bits from file
2535
+ read_RaBitQuantizer(
2536
+ ivrq->rabitq, f, ivrq->d, true); // Reads nb_bits from file
1646
2537
  READ1(ivrq->code_size);
1647
2538
  READ1(ivrq->by_residual);
1648
2539
  READ1(ivrq->qb);
2540
+ // qb=0: Not quantized - direct distance computation on given float32s.
2541
+ // qb>0 && qb<=8: Scalar-quantized with qb bits of precision.
2542
+ FAISS_THROW_IF_NOT_FMT(
2543
+ ivrq->qb <= 8,
2544
+ "invalid RaBitQ qb=%d (must be in [0, 8])",
2545
+ ivrq->qb);
1649
2546
 
1650
2547
  // Update rabitq to match nb_bits
1651
2548
  ivrq->rabitq.code_size =
@@ -1656,13 +2553,14 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1656
2553
  }
1657
2554
  #ifdef FAISS_ENABLE_SVS
1658
2555
  else if (
1659
- h == fourcc("ILVQ") || h == fourcc("ISVL") || h == fourcc("ISVD")) {
2556
+ h == fourcc("ILVQ") || h == fourcc("ISVL") || h == fourcc("ISVD") ||
2557
+ h == fourcc("ISV2")) {
1660
2558
  std::unique_ptr<IndexSVSVamana> svs;
1661
2559
  if (h == fourcc("ILVQ")) {
1662
2560
  svs = std::make_unique<IndexSVSVamanaLVQ>();
1663
2561
  } else if (h == fourcc("ISVL")) {
1664
2562
  svs = std::make_unique<IndexSVSVamanaLeanVec>();
1665
- } else if (h == fourcc("ISVD")) {
2563
+ } else if (h == fourcc("ISVD") || h == fourcc("ISV2")) {
1666
2564
  svs = std::make_unique<IndexSVSVamana>();
1667
2565
  }
1668
2566
 
@@ -1675,15 +2573,21 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1675
2573
  READ1(svs->max_candidate_pool_size);
1676
2574
  READ1(svs->prune_to);
1677
2575
  READ1(svs->use_full_search_history);
1678
- READ1(svs->storage_kind);
2576
+
2577
+ svs->storage_kind = read_svs_storage_kind(f);
2578
+
1679
2579
  if (h == fourcc("ISVL")) {
1680
- READ1(dynamic_cast<IndexSVSVamanaLeanVec*>(svs.get())->leanvec_d);
2580
+ auto* leanvec = dynamic_cast<IndexSVSVamanaLeanVec*>(svs.get());
2581
+ FAISS_THROW_IF_NOT_MSG(
2582
+ leanvec, "dynamic_cast to IndexSVSVamanaLeanVec failed");
2583
+ READ1(leanvec->leanvec_d);
1681
2584
  }
1682
2585
 
1683
2586
  bool initialized;
1684
2587
  READ1(initialized);
1685
2588
  if (initialized) {
1686
- faiss::svs_io::ReaderStreambuf rbuf(f);
2589
+ faiss::svs_io::ReaderStreambuf rbuf(
2590
+ f, get_deserialization_vector_byte_limit());
1687
2591
  std::istream is(&rbuf);
1688
2592
  svs->deserialize_impl(is);
1689
2593
  }
@@ -1691,12 +2595,21 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1691
2595
  bool trained;
1692
2596
  READ1(trained);
1693
2597
  if (trained) {
1694
- faiss::svs_io::ReaderStreambuf rbuf(f);
2598
+ faiss::svs_io::ReaderStreambuf rbuf(
2599
+ f, get_deserialization_vector_byte_limit());
1695
2600
  std::istream is(&rbuf);
1696
- dynamic_cast<IndexSVSVamanaLeanVec*>(svs.get())
1697
- ->deserialize_training_data(is);
2601
+ auto* leanvec = dynamic_cast<IndexSVSVamanaLeanVec*>(svs.get());
2602
+ FAISS_THROW_IF_NOT_MSG(
2603
+ leanvec,
2604
+ "dynamic_cast to IndexSVSVamanaLeanVec failed");
2605
+ leanvec->deserialize_training_data(is);
1698
2606
  }
1699
2607
  }
2608
+ if (h == fourcc("ISV2")) {
2609
+ READVECTOR(svs->stored_vectors);
2610
+ } else {
2611
+ svs->stored_vectors_valid = false;
2612
+ }
1700
2613
  idx = std::move(svs);
1701
2614
  } else if (h == fourcc("ISVF")) {
1702
2615
  auto svs = std::make_unique<IndexSVSFlat>();
@@ -1705,11 +2618,65 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1705
2618
  bool initialized;
1706
2619
  READ1(initialized);
1707
2620
  if (initialized) {
1708
- faiss::svs_io::ReaderStreambuf rbuf(f);
2621
+ faiss::svs_io::ReaderStreambuf rbuf(
2622
+ f, get_deserialization_vector_byte_limit());
1709
2623
  std::istream is(&rbuf);
1710
2624
  svs->deserialize_impl(is);
1711
2625
  }
1712
2626
  idx = std::move(svs);
2627
+ } else if (
2628
+ h == fourcc("ISIQ") || h == fourcc("ISIL") || h == fourcc("ISID")) {
2629
+ std::unique_ptr<IndexSVSIVF> svs_ivf;
2630
+ if (h == fourcc("ISIQ")) {
2631
+ svs_ivf = std::make_unique<IndexSVSIVFLVQ>();
2632
+ } else if (h == fourcc("ISIL")) {
2633
+ svs_ivf = std::make_unique<IndexSVSIVFLeanVec>();
2634
+ } else if (h == fourcc("ISID")) {
2635
+ svs_ivf = std::make_unique<IndexSVSIVF>();
2636
+ }
2637
+
2638
+ read_index_header(*svs_ivf, f);
2639
+ READ1(svs_ivf->num_centroids);
2640
+ READ1(svs_ivf->minibatch_size);
2641
+ READ1(svs_ivf->num_iterations);
2642
+ READ1(svs_ivf->is_hierarchical);
2643
+ READ1(svs_ivf->training_fraction);
2644
+ READ1(svs_ivf->hierarchical_level1_clusters);
2645
+ READ1(svs_ivf->seed);
2646
+ READ1(svs_ivf->n_probes);
2647
+ READ1(svs_ivf->k_reorder);
2648
+ READ1(svs_ivf->num_threads);
2649
+ READ1(svs_ivf->intra_query_threads);
2650
+ svs_ivf->storage_kind = read_svs_storage_kind(f);
2651
+ READ1(svs_ivf->is_static);
2652
+ if (h == fourcc("ISIL")) {
2653
+ auto* leanvec = dynamic_cast<IndexSVSIVFLeanVec*>(svs_ivf.get());
2654
+ FAISS_THROW_IF_NOT_MSG(
2655
+ leanvec, "dynamic_cast to IndexSVSIVFLeanVec failed");
2656
+ READ1(leanvec->leanvec_d);
2657
+ }
2658
+
2659
+ bool initialized;
2660
+ READ1(initialized);
2661
+ if (initialized) {
2662
+ faiss::svs_io::ReaderStreambuf rbuf(f);
2663
+ std::istream is(&rbuf);
2664
+ svs_ivf->deserialize_impl(is);
2665
+ }
2666
+ if (h == fourcc("ISIL")) {
2667
+ bool trained;
2668
+ READ1(trained);
2669
+ if (trained) {
2670
+ faiss::svs_io::ReaderStreambuf rbuf(f);
2671
+ std::istream is(&rbuf);
2672
+ auto* leanvec =
2673
+ dynamic_cast<IndexSVSIVFLeanVec*>(svs_ivf.get());
2674
+ FAISS_THROW_IF_NOT_MSG(
2675
+ leanvec, "dynamic_cast to IndexSVSIVFLeanVec failed");
2676
+ leanvec->deserialize_training_data(is);
2677
+ }
2678
+ }
2679
+ idx = std::move(svs_ivf);
1713
2680
  }
1714
2681
  #endif // FAISS_ENABLE_SVS
1715
2682
  else if (h == fourcc("Iwrn") || h == fourcc("Iwrf")) {
@@ -1719,7 +2686,7 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1719
2686
 
1720
2687
  auto ivrqfs = std::make_unique<IndexIVFRaBitQFastScan>();
1721
2688
  read_ivf_header(ivrqfs.get(), f);
1722
- read_RaBitQuantizer(ivrqfs->rabitq, f);
2689
+ read_RaBitQuantizer(ivrqfs->rabitq, f, ivrqfs->d);
1723
2690
  READ1(ivrqfs->by_residual);
1724
2691
  READ1(ivrqfs->code_size);
1725
2692
  READ1(ivrqfs->bbs);
@@ -1727,6 +2694,10 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1727
2694
  READ1(ivrqfs->M2);
1728
2695
  READ1(ivrqfs->implem);
1729
2696
  READ1(ivrqfs->qb);
2697
+ FAISS_THROW_IF_NOT_FMT(
2698
+ ivrqfs->qb > 0 && ivrqfs->qb <= 8,
2699
+ "invalid RaBitQ qb=%d (must be in [1, 8])",
2700
+ ivrqfs->qb);
1730
2701
  READ1(ivrqfs->centered);
1731
2702
 
1732
2703
  std::vector<uint8_t> legacy_flat_storage;
@@ -1741,6 +2712,13 @@ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1741
2712
  ivrqfs->nbits = nbits_fastscan;
1742
2713
  ivrqfs->ksub = (1 << nbits_fastscan);
1743
2714
 
2715
+ validate_fastscan_fields(
2716
+ ivrqfs->M,
2717
+ ivrqfs->M2,
2718
+ ivrqfs->ksub,
2719
+ ivrqfs->bbs,
2720
+ "IndexIVFRaBitQFastScan");
2721
+
1744
2722
  read_InvertedLists(*ivrqfs, f, io_flags);
1745
2723
  ivrqfs->init_code_packer();
1746
2724
 
@@ -1852,7 +2830,14 @@ static void read_index_binary_header(IndexBinary& idx, IOReader* f) {
1852
2830
  READ1(metric_type_int);
1853
2831
  idx.metric_type = metric_type_from_int(metric_type_int);
1854
2832
  FAISS_THROW_IF_NOT_FMT(
1855
- idx.d >= 0, "invalid binary index dimension %d", idx.d);
2833
+ idx.d > 0 && idx.d % 8 == 0,
2834
+ "invalid binary index dimension %d (must be > 0 and a multiple of 8)",
2835
+ idx.d);
2836
+ FAISS_THROW_IF_NOT_FMT(
2837
+ idx.code_size == idx.d / 8,
2838
+ "binary index code_size=%d does not match d/8=%d",
2839
+ (int)idx.code_size,
2840
+ idx.d / 8);
1856
2841
  FAISS_THROW_IF_NOT_FMT(
1857
2842
  idx.ntotal >= 0,
1858
2843
  "invalid binary index ntotal %" PRId64,
@@ -1866,6 +2851,7 @@ static void read_binary_ivf_header(
1866
2851
  std::vector<std::vector<idx_t>>* ids = nullptr) {
1867
2852
  read_index_binary_header(ivf, f);
1868
2853
  READ1(ivf.nlist);
2854
+ FAISS_CHECK_DESERIALIZATION_LOOP_LIMIT(ivf.nlist, "nlist");
1869
2855
  READ1(ivf.nprobe);
1870
2856
  ivf.quantizer = read_index_binary(f);
1871
2857
  ivf.own_fields = true;
@@ -1880,11 +2866,25 @@ static void read_binary_ivf_header(
1880
2866
  static void read_binary_hash_invlists(
1881
2867
  IndexBinaryHash::InvertedListMap& invlists,
1882
2868
  int b,
2869
+ size_t code_size,
1883
2870
  IOReader* f) {
1884
2871
  size_t sz;
1885
2872
  READ1(sz);
2873
+ FAISS_CHECK_DESERIALIZATION_LOOP_LIMIT(sz, "binary hash invlists sz");
1886
2874
  int il_nbit = 0;
1887
2875
  READ1(il_nbit);
2876
+ FAISS_THROW_IF_NOT_FMT(
2877
+ il_nbit >= 0,
2878
+ "invalid binary hash invlists il_nbit=%d (must be >= 0)",
2879
+ il_nbit);
2880
+ if (sz > 0) {
2881
+ FAISS_THROW_IF_NOT_FMT(
2882
+ il_nbit > 0,
2883
+ "invalid binary hash invlists il_nbit=%d for sz=%zd "
2884
+ "(must be > 0 when entries exist)",
2885
+ il_nbit,
2886
+ sz);
2887
+ }
1888
2888
  // buffer for bitstrings
1889
2889
  size_t bits_per_entry = (size_t)b + (size_t)il_nbit;
1890
2890
  size_t total_bits =
@@ -1909,6 +2909,13 @@ static void read_binary_hash_invlists(
1909
2909
  READVECTOR(il.ids);
1910
2910
  FAISS_THROW_IF_NOT(il.ids.size() == ilsz);
1911
2911
  READVECTOR(il.vecs);
2912
+ FAISS_THROW_IF_NOT_FMT(
2913
+ il.vecs.size() == il.ids.size() * code_size,
2914
+ "binary hash invlists: vecs size %zu != ids size %zu * "
2915
+ "code_size %zu",
2916
+ il.vecs.size(),
2917
+ il.ids.size(),
2918
+ code_size);
1912
2919
  }
1913
2920
  }
1914
2921
 
@@ -1921,6 +2928,7 @@ static void read_binary_multi_hash_map(
1921
2928
  size_t sz;
1922
2929
  READ1(id_bits);
1923
2930
  READ1(sz);
2931
+ FAISS_CHECK_DESERIALIZATION_LOOP_LIMIT(sz, "multi hash map sz");
1924
2932
  std::vector<uint8_t> buf;
1925
2933
  READVECTOR(buf);
1926
2934
  size_t nbit = add_no_overflow(
@@ -1930,12 +2938,28 @@ static void read_binary_multi_hash_map(
1930
2938
  FAISS_THROW_IF_NOT(buf.size() == (nbit + 7) / 8);
1931
2939
  BitstringReader rd(buf.data(), buf.size());
1932
2940
  map.reserve(sz);
2941
+ size_t total_ids = 0;
1933
2942
  for (size_t i = 0; i < sz; i++) {
1934
2943
  uint64_t hash = rd.read(b);
1935
2944
  uint64_t ilsz = rd.read(id_bits);
2945
+ FAISS_THROW_IF_NOT_FMT(
2946
+ ilsz <= ntotal - total_ids,
2947
+ "multi hash map: ilsz=%zu at entry %zu would exceed "
2948
+ "ntotal=%zu (already read %zu ids)",
2949
+ (size_t)ilsz,
2950
+ i,
2951
+ ntotal,
2952
+ total_ids);
2953
+ total_ids += ilsz;
1936
2954
  auto& il = map[hash];
1937
2955
  for (size_t j = 0; j < ilsz; j++) {
1938
- il.push_back(rd.read(id_bits));
2956
+ uint64_t id = rd.read(id_bits);
2957
+ FAISS_THROW_IF_NOT_FMT(
2958
+ id < ntotal,
2959
+ "multi hash map: id=%zu >= ntotal=%zu",
2960
+ (size_t)id,
2961
+ ntotal);
2962
+ il.push_back(id);
1939
2963
  }
1940
2964
  }
1941
2965
  }
@@ -1966,8 +2990,21 @@ std::unique_ptr<IndexBinary> read_index_binary_up(IOReader* f, int io_flags) {
1966
2990
  read_index_binary_header(*idxhnsw, f);
1967
2991
  read_HNSW(idxhnsw->hnsw, f);
1968
2992
  idxhnsw->hnsw.is_panorama = false;
2993
+ FAISS_THROW_IF_NOT_FMT(
2994
+ idxhnsw->hnsw.levels.size() == (size_t)idxhnsw->ntotal,
2995
+ "IndexBinaryHNSW HNSW levels size %zu != ntotal %" PRId64,
2996
+ idxhnsw->hnsw.levels.size(),
2997
+ idxhnsw->ntotal);
1969
2998
  idxhnsw->storage = read_index_binary(f, io_flags);
1970
2999
  idxhnsw->own_fields = true;
3000
+ FAISS_THROW_IF_NOT_MSG(
3001
+ idxhnsw->storage &&
3002
+ dynamic_cast<IndexBinaryFlat*>(idxhnsw->storage) !=
3003
+ nullptr,
3004
+ "IndexBinaryHNSW requires IndexBinaryFlat storage");
3005
+ FAISS_THROW_IF_NOT_MSG(
3006
+ idxhnsw->storage->ntotal == idxhnsw->ntotal,
3007
+ "IndexBinaryHNSW storage ntotal mismatch");
1971
3008
  idx = std::move(idxhnsw);
1972
3009
  } else if (h == fourcc("IBHc")) {
1973
3010
  auto idxhnsw = std::make_unique<IndexBinaryHNSWCagra>();
@@ -1977,8 +3014,21 @@ std::unique_ptr<IndexBinary> read_index_binary_up(IOReader* f, int io_flags) {
1977
3014
  READ1(idxhnsw->num_base_level_search_entrypoints);
1978
3015
  read_HNSW(idxhnsw->hnsw, f);
1979
3016
  idxhnsw->hnsw.is_panorama = false;
3017
+ FAISS_THROW_IF_NOT_FMT(
3018
+ idxhnsw->hnsw.levels.size() == (size_t)idxhnsw->ntotal,
3019
+ "IndexBinaryHNSWCagra HNSW levels size %zu != ntotal %" PRId64,
3020
+ idxhnsw->hnsw.levels.size(),
3021
+ idxhnsw->ntotal);
1980
3022
  idxhnsw->storage = read_index_binary(f, io_flags);
1981
3023
  idxhnsw->own_fields = true;
3024
+ FAISS_THROW_IF_NOT_MSG(
3025
+ idxhnsw->storage &&
3026
+ dynamic_cast<IndexBinaryFlat*>(idxhnsw->storage) !=
3027
+ nullptr,
3028
+ "IndexBinaryHNSWCagra requires IndexBinaryFlat storage");
3029
+ FAISS_THROW_IF_NOT_MSG(
3030
+ idxhnsw->storage->ntotal == idxhnsw->ntotal,
3031
+ "IndexBinaryHNSWCagra storage ntotal mismatch");
1982
3032
  idx = std::move(idxhnsw);
1983
3033
  } else if (h == fourcc("IBMp") || h == fourcc("IBM2")) {
1984
3034
  bool is_map2 = h == fourcc("IBM2");
@@ -1989,6 +3039,18 @@ std::unique_ptr<IndexBinary> read_index_binary_up(IOReader* f, int io_flags) {
1989
3039
  idxmap->index = read_index_binary(f, io_flags);
1990
3040
  idxmap->own_fields = true;
1991
3041
  READVECTOR(idxmap->id_map);
3042
+ FAISS_THROW_IF_NOT_FMT(
3043
+ idxmap->id_map.size() == idxmap->ntotal,
3044
+ "IndexBinaryIDMap id_map size (%" PRId64
3045
+ ") does not match ntotal (%" PRId64 ")",
3046
+ int64_t(idxmap->id_map.size()),
3047
+ idxmap->ntotal);
3048
+ FAISS_THROW_IF_NOT_FMT(
3049
+ idxmap->index->ntotal == idxmap->ntotal,
3050
+ "IndexBinaryIDMap inner index ntotal (%" PRId64
3051
+ ") does not match IndexBinaryIDMap ntotal (%" PRId64 ")",
3052
+ idxmap->index->ntotal,
3053
+ idxmap->ntotal);
1992
3054
  if (is_map2) {
1993
3055
  static_cast<IndexBinaryIDMap2*>(idxmap.get())->construct_rev_map();
1994
3056
  }
@@ -1997,8 +3059,17 @@ std::unique_ptr<IndexBinary> read_index_binary_up(IOReader* f, int io_flags) {
1997
3059
  auto idxh = std::make_unique<IndexBinaryHash>();
1998
3060
  read_index_binary_header(*idxh, f);
1999
3061
  READ1(idxh->b);
3062
+ FAISS_THROW_IF_NOT_FMT(
3063
+ idxh->b > 0,
3064
+ "invalid IndexBinaryHash b=%d (must be > 0)",
3065
+ idxh->b);
3066
+ FAISS_THROW_IF_NOT_FMT(
3067
+ static_cast<size_t>(idxh->b) <= idxh->code_size * 8,
3068
+ "IndexBinaryHash b=%d exceeds code_size=%d bits",
3069
+ idxh->b,
3070
+ idxh->code_size);
2000
3071
  READ1(idxh->nflip);
2001
- read_binary_hash_invlists(idxh->invlists, idxh->b, f);
3072
+ read_binary_hash_invlists(idxh->invlists, idxh->b, idxh->code_size, f);
2002
3073
  idx = std::move(idxh);
2003
3074
  } else if (h == fourcc("IBHm")) {
2004
3075
  auto idxmh = std::make_unique<IndexBinaryMultiHash>();
@@ -2010,7 +3081,23 @@ std::unique_ptr<IndexBinary> read_index_binary_up(IOReader* f, int io_flags) {
2010
3081
  storage_idx.release();
2011
3082
  idxmh->own_fields = true;
2012
3083
  READ1(idxmh->b);
3084
+ FAISS_THROW_IF_NOT_FMT(
3085
+ idxmh->b > 0,
3086
+ "invalid IndexBinaryMultiHash b=%d (must be > 0)",
3087
+ idxmh->b);
2013
3088
  READ1(idxmh->nhash);
3089
+ FAISS_THROW_IF_NOT_FMT(
3090
+ idxmh->nhash > 0,
3091
+ "invalid IndexBinaryMultiHash nhash %d (must be > 0)",
3092
+ idxmh->nhash);
3093
+ FAISS_CHECK_DESERIALIZATION_LOOP_LIMIT(idxmh->nhash, "nhash");
3094
+ FAISS_THROW_IF_NOT_FMT(
3095
+ mul_no_overflow(idxmh->nhash, idxmh->b, "nhash * b") <=
3096
+ mul_no_overflow(idxmh->code_size, 8, "code_size * 8"),
3097
+ "IndexBinaryMultiHash nhash=%d * b=%d exceeds code_size=%d bits",
3098
+ idxmh->nhash,
3099
+ idxmh->b,
3100
+ idxmh->code_size);
2014
3101
  READ1(idxmh->nflip);
2015
3102
  idxmh->maps.resize(idxmh->nhash);
2016
3103
  for (int i = 0; i < idxmh->nhash; i++) {