faiss 0.5.3 → 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 (379) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/ext/faiss/ext.cpp +1 -1
  4. data/ext/faiss/extconf.rb +4 -4
  5. data/ext/faiss/index.cpp +63 -45
  6. data/ext/faiss/index_binary.cpp +37 -27
  7. data/ext/faiss/kmeans.cpp +9 -8
  8. data/ext/faiss/pca_matrix.cpp +9 -7
  9. data/ext/faiss/product_quantizer.cpp +13 -11
  10. data/ext/faiss/utils.cpp +4 -2
  11. data/ext/faiss/utils.h +4 -0
  12. data/lib/faiss/version.rb +1 -1
  13. data/lib/faiss.rb +1 -1
  14. data/vendor/faiss/faiss/AutoTune.cpp +214 -82
  15. data/vendor/faiss/faiss/AutoTune.h +14 -1
  16. data/vendor/faiss/faiss/Clustering.cpp +97 -249
  17. data/vendor/faiss/faiss/Clustering.h +18 -0
  18. data/vendor/faiss/faiss/IVFlib.cpp +67 -44
  19. data/vendor/faiss/faiss/Index.cpp +25 -12
  20. data/vendor/faiss/faiss/Index.h +26 -4
  21. data/vendor/faiss/faiss/Index2Layer.cpp +37 -53
  22. data/vendor/faiss/faiss/IndexAdditiveQuantizer.cpp +68 -61
  23. data/vendor/faiss/faiss/IndexAdditiveQuantizerFastScan.cpp +36 -34
  24. data/vendor/faiss/faiss/IndexAdditiveQuantizerFastScan.h +4 -1
  25. data/vendor/faiss/faiss/IndexBinary.cpp +6 -3
  26. data/vendor/faiss/faiss/IndexBinary.h +4 -4
  27. data/vendor/faiss/faiss/IndexBinaryFlat.cpp +1 -1
  28. data/vendor/faiss/faiss/IndexBinaryFlat.h +1 -1
  29. data/vendor/faiss/faiss/IndexBinaryFromFloat.cpp +4 -4
  30. data/vendor/faiss/faiss/IndexBinaryHNSW.cpp +92 -95
  31. data/vendor/faiss/faiss/IndexBinaryHNSW.h +9 -3
  32. data/vendor/faiss/faiss/IndexBinaryHash.cpp +45 -236
  33. data/vendor/faiss/faiss/IndexBinaryHash.h +6 -6
  34. data/vendor/faiss/faiss/IndexBinaryIVF.cpp +120 -414
  35. data/vendor/faiss/faiss/IndexFastScan.cpp +105 -129
  36. data/vendor/faiss/faiss/IndexFastScan.h +35 -24
  37. data/vendor/faiss/faiss/IndexFlat.cpp +216 -152
  38. data/vendor/faiss/faiss/IndexFlat.h +32 -14
  39. data/vendor/faiss/faiss/IndexFlatCodes.cpp +88 -41
  40. data/vendor/faiss/faiss/IndexFlatCodes.h +7 -1
  41. data/vendor/faiss/faiss/IndexHNSW.cpp +299 -187
  42. data/vendor/faiss/faiss/IndexHNSW.h +30 -14
  43. data/vendor/faiss/faiss/IndexIDMap.cpp +26 -22
  44. data/vendor/faiss/faiss/IndexIDMap.h +9 -7
  45. data/vendor/faiss/faiss/IndexIVF.cpp +535 -405
  46. data/vendor/faiss/faiss/IndexIVF.h +47 -16
  47. data/vendor/faiss/faiss/IndexIVFAdditiveQuantizer.cpp +77 -74
  48. data/vendor/faiss/faiss/IndexIVFAdditiveQuantizerFastScan.cpp +105 -99
  49. data/vendor/faiss/faiss/IndexIVFAdditiveQuantizerFastScan.h +6 -3
  50. data/vendor/faiss/faiss/IndexIVFFastScan.cpp +379 -249
  51. data/vendor/faiss/faiss/IndexIVFFastScan.h +65 -60
  52. data/vendor/faiss/faiss/IndexIVFFlat.cpp +41 -124
  53. data/vendor/faiss/faiss/IndexIVFFlat.h +32 -0
  54. data/vendor/faiss/faiss/IndexIVFFlatPanorama.cpp +89 -138
  55. data/vendor/faiss/faiss/IndexIVFFlatPanorama.h +3 -1
  56. data/vendor/faiss/faiss/IndexIVFIndependentQuantizer.cpp +18 -15
  57. data/vendor/faiss/faiss/IndexIVFPQ.cpp +77 -907
  58. data/vendor/faiss/faiss/IndexIVFPQFastScan.cpp +184 -122
  59. data/vendor/faiss/faiss/IndexIVFPQFastScan.h +3 -0
  60. data/vendor/faiss/faiss/IndexIVFPQR.cpp +23 -18
  61. data/vendor/faiss/faiss/IndexIVFRaBitQ.cpp +59 -60
  62. data/vendor/faiss/faiss/IndexIVFRaBitQ.h +4 -3
  63. data/vendor/faiss/faiss/IndexIVFRaBitQFastScan.cpp +564 -416
  64. data/vendor/faiss/faiss/IndexIVFRaBitQFastScan.h +269 -111
  65. data/vendor/faiss/faiss/IndexIVFSpectralHash.cpp +41 -127
  66. data/vendor/faiss/faiss/IndexIVFSpectralHash.h +1 -1
  67. data/vendor/faiss/faiss/IndexLSH.cpp +44 -25
  68. data/vendor/faiss/faiss/IndexLattice.cpp +41 -36
  69. data/vendor/faiss/faiss/IndexNNDescent.cpp +37 -21
  70. data/vendor/faiss/faiss/IndexNNDescent.h +2 -2
  71. data/vendor/faiss/faiss/IndexNSG.cpp +40 -23
  72. data/vendor/faiss/faiss/IndexNSG.h +0 -2
  73. data/vendor/faiss/faiss/IndexNeuralNetCodec.cpp +32 -12
  74. data/vendor/faiss/faiss/IndexPQ.cpp +129 -213
  75. data/vendor/faiss/faiss/IndexPQ.h +3 -2
  76. data/vendor/faiss/faiss/IndexPQFastScan.cpp +20 -14
  77. data/vendor/faiss/faiss/IndexPQFastScan.h +3 -0
  78. data/vendor/faiss/faiss/IndexPreTransform.cpp +25 -18
  79. data/vendor/faiss/faiss/IndexPreTransform.h +1 -1
  80. data/vendor/faiss/faiss/IndexRaBitQ.cpp +31 -43
  81. data/vendor/faiss/faiss/IndexRaBitQ.h +4 -3
  82. data/vendor/faiss/faiss/IndexRaBitQFastScan.cpp +135 -317
  83. data/vendor/faiss/faiss/IndexRaBitQFastScan.h +192 -34
  84. data/vendor/faiss/faiss/IndexRefine.cpp +30 -55
  85. data/vendor/faiss/faiss/IndexRefine.h +4 -4
  86. data/vendor/faiss/faiss/IndexReplicas.cpp +6 -6
  87. data/vendor/faiss/faiss/IndexRowwiseMinMax.cpp +15 -14
  88. data/vendor/faiss/faiss/IndexRowwiseMinMax.h +1 -1
  89. data/vendor/faiss/faiss/IndexScalarQuantizer.cpp +82 -14
  90. data/vendor/faiss/faiss/IndexShards.cpp +13 -13
  91. data/vendor/faiss/faiss/IndexShardsIVF.cpp +21 -15
  92. data/vendor/faiss/faiss/MatrixStats.cpp +5 -4
  93. data/vendor/faiss/faiss/MetaIndexes.cpp +19 -17
  94. data/vendor/faiss/faiss/MetaIndexes.h +1 -1
  95. data/vendor/faiss/faiss/MetricType.h +29 -6
  96. data/vendor/faiss/faiss/SuperKMeans.cpp +656 -0
  97. data/vendor/faiss/faiss/SuperKMeans.h +97 -0
  98. data/vendor/faiss/faiss/VectorTransform.cpp +349 -141
  99. data/vendor/faiss/faiss/VectorTransform.h +39 -16
  100. data/vendor/faiss/faiss/build.cpp +23 -0
  101. data/vendor/faiss/faiss/build.h +15 -0
  102. data/vendor/faiss/faiss/clone_index.cpp +55 -51
  103. data/vendor/faiss/faiss/cppcontrib/sa_decode/Level2-avx2-inl.h +47 -47
  104. data/vendor/faiss/faiss/cppcontrib/sa_decode/Level2-inl.h +11 -0
  105. data/vendor/faiss/faiss/cppcontrib/sa_decode/PQ-avx2-inl.h +38 -38
  106. data/vendor/faiss/faiss/cppcontrib/sa_decode/PQ-inl.h +11 -0
  107. data/vendor/faiss/faiss/{cppcontrib/factory_tools.cpp → factory_tools.cpp} +6 -1
  108. data/vendor/faiss/faiss/gpu/GpuCloner.cpp +1 -1
  109. data/vendor/faiss/faiss/gpu/GpuIndexCagra.h +6 -5
  110. data/vendor/faiss/faiss/gpu/GpuResources.h +1 -1
  111. data/vendor/faiss/faiss/gpu/StandardGpuResources.cpp +9 -9
  112. data/vendor/faiss/faiss/gpu/StandardGpuResources.h +4 -3
  113. data/vendor/faiss/faiss/gpu/test/TestGpuIndexFlat.cpp +46 -0
  114. data/vendor/faiss/faiss/gpu/test/TestGpuIndexIVFFlat.cpp +56 -0
  115. data/vendor/faiss/faiss/gpu/test/TestGpuIndexIVFPQ.cpp +78 -1
  116. data/vendor/faiss/faiss/gpu/test/TestUtils.cpp +72 -0
  117. data/vendor/faiss/faiss/gpu/test/TestUtils.h +23 -0
  118. data/vendor/faiss/faiss/gpu/utils/CuvsFilterConvert.h +1 -1
  119. data/vendor/faiss/faiss/gpu/utils/CuvsUtils.h +21 -10
  120. data/vendor/faiss/faiss/gpu_metal/GpuIndexFlat.h +22 -0
  121. data/vendor/faiss/faiss/gpu_metal/MetalCloner.h +35 -0
  122. data/vendor/faiss/faiss/gpu_metal/MetalFlatKernels.h +40 -0
  123. data/vendor/faiss/faiss/gpu_metal/MetalIndex.h +51 -0
  124. data/vendor/faiss/faiss/gpu_metal/MetalIndexFlat.h +65 -0
  125. data/vendor/faiss/faiss/gpu_metal/MetalKernels.h +66 -0
  126. data/vendor/faiss/faiss/gpu_metal/MetalResources.h +79 -0
  127. data/vendor/faiss/faiss/gpu_metal/StandardMetalResources.h +35 -0
  128. data/vendor/faiss/faiss/impl/AdSampling.cpp +103 -0
  129. data/vendor/faiss/faiss/impl/AdSampling.h +35 -0
  130. data/vendor/faiss/faiss/impl/AdditiveQuantizer.cpp +64 -34
  131. data/vendor/faiss/faiss/impl/AdditiveQuantizer.h +1 -0
  132. data/vendor/faiss/faiss/impl/AuxIndexStructures.cpp +10 -9
  133. data/vendor/faiss/faiss/impl/AuxIndexStructures.h +3 -28
  134. data/vendor/faiss/faiss/impl/ClusteringHelpers.cpp +244 -0
  135. data/vendor/faiss/faiss/impl/ClusteringHelpers.h +94 -0
  136. data/vendor/faiss/faiss/impl/ClusteringInitialization.cpp +367 -0
  137. data/vendor/faiss/faiss/impl/ClusteringInitialization.h +107 -0
  138. data/vendor/faiss/faiss/impl/CodePacker.cpp +7 -3
  139. data/vendor/faiss/faiss/impl/CodePacker.h +11 -3
  140. data/vendor/faiss/faiss/impl/CodePackerRaBitQ.cpp +83 -0
  141. data/vendor/faiss/faiss/impl/CodePackerRaBitQ.h +47 -0
  142. data/vendor/faiss/faiss/impl/DistanceComputer.h +8 -8
  143. data/vendor/faiss/faiss/impl/FaissAssert.h +64 -3
  144. data/vendor/faiss/faiss/impl/FaissException.h +50 -3
  145. data/vendor/faiss/faiss/impl/HNSW.cpp +117 -351
  146. data/vendor/faiss/faiss/impl/HNSW.h +21 -40
  147. data/vendor/faiss/faiss/impl/IDSelector.cpp +15 -11
  148. data/vendor/faiss/faiss/impl/IDSelector.h +8 -8
  149. data/vendor/faiss/faiss/impl/InvertedListScannerStats.h +26 -0
  150. data/vendor/faiss/faiss/impl/LocalSearchQuantizer.cpp +114 -102
  151. data/vendor/faiss/faiss/impl/NNDescent.cpp +63 -26
  152. data/vendor/faiss/faiss/impl/NNDescent.h +6 -2
  153. data/vendor/faiss/faiss/impl/NSG.cpp +44 -26
  154. data/vendor/faiss/faiss/impl/NSG.h +20 -10
  155. data/vendor/faiss/faiss/impl/Panorama.cpp +76 -52
  156. data/vendor/faiss/faiss/impl/Panorama.h +265 -78
  157. data/vendor/faiss/faiss/impl/PdxLayout.cpp +93 -0
  158. data/vendor/faiss/faiss/impl/PdxLayout.h +41 -0
  159. data/vendor/faiss/faiss/impl/PolysemousTraining.cpp +62 -37
  160. data/vendor/faiss/faiss/impl/PolysemousTraining.h +3 -3
  161. data/vendor/faiss/faiss/impl/ProductAdditiveQuantizer.cpp +35 -35
  162. data/vendor/faiss/faiss/impl/ProductQuantizer-inl.h +21 -16
  163. data/vendor/faiss/faiss/impl/ProductQuantizer.cpp +99 -80
  164. data/vendor/faiss/faiss/impl/Quantizer.h +2 -2
  165. data/vendor/faiss/faiss/impl/RaBitQUtils.cpp +135 -37
  166. data/vendor/faiss/faiss/impl/RaBitQUtils.h +148 -21
  167. data/vendor/faiss/faiss/impl/RaBitQuantizer.cpp +298 -301
  168. data/vendor/faiss/faiss/impl/RaBitQuantizer.h +3 -10
  169. data/vendor/faiss/faiss/impl/RaBitQuantizerMultiBit.cpp +15 -41
  170. data/vendor/faiss/faiss/impl/RaBitQuantizerMultiBit.h +0 -4
  171. data/vendor/faiss/faiss/impl/ResidualQuantizer.cpp +40 -32
  172. data/vendor/faiss/faiss/impl/ResidualQuantizer.h +1 -1
  173. data/vendor/faiss/faiss/impl/ResultHandler.h +218 -113
  174. data/vendor/faiss/faiss/impl/ScalarQuantizer.cpp +119 -2362
  175. data/vendor/faiss/faiss/impl/ScalarQuantizer.h +27 -3
  176. data/vendor/faiss/faiss/impl/ThreadedIndex-inl.h +14 -11
  177. data/vendor/faiss/faiss/impl/VisitedTable.cpp +42 -0
  178. data/vendor/faiss/faiss/impl/VisitedTable.h +76 -0
  179. data/vendor/faiss/faiss/impl/approx_topk/approx_topk.h +276 -0
  180. data/vendor/faiss/faiss/impl/approx_topk/avx2.cpp +68 -0
  181. data/vendor/faiss/faiss/{utils → impl}/approx_topk/generic.h +15 -8
  182. data/vendor/faiss/faiss/impl/approx_topk/neon.cpp +68 -0
  183. data/vendor/faiss/faiss/impl/approx_topk/rq_beam_search_tab-inl.h +169 -0
  184. data/vendor/faiss/faiss/impl/approx_topk/rq_beam_search_tab.h +117 -0
  185. data/vendor/faiss/faiss/impl/approx_topk/simdlib256-inl.h +146 -0
  186. data/vendor/faiss/faiss/impl/binary_hamming/IndexBinaryHNSW_impl.h +73 -0
  187. data/vendor/faiss/faiss/impl/binary_hamming/IndexBinaryHash_impl.h +270 -0
  188. data/vendor/faiss/faiss/impl/binary_hamming/IndexBinaryIVF_impl.h +460 -0
  189. data/vendor/faiss/faiss/impl/binary_hamming/IndexIVFSpectralHash_impl.h +159 -0
  190. data/vendor/faiss/faiss/impl/binary_hamming/IndexPQ_impl.h +92 -0
  191. data/vendor/faiss/faiss/impl/binary_hamming/avx2.cpp +26 -0
  192. data/vendor/faiss/faiss/impl/binary_hamming/avx512.cpp +26 -0
  193. data/vendor/faiss/faiss/impl/binary_hamming/dispatch.h +143 -0
  194. data/vendor/faiss/faiss/impl/binary_hamming/neon.cpp +26 -0
  195. data/vendor/faiss/faiss/impl/binary_hamming/rvv.cpp +26 -0
  196. data/vendor/faiss/faiss/impl/expanded_scanners.h +163 -0
  197. data/vendor/faiss/faiss/impl/{FastScanDistancePostProcessing.h → fast_scan/FastScanDistancePostProcessing.h} +13 -6
  198. data/vendor/faiss/faiss/impl/{LookupTableScaler.h → fast_scan/LookupTableScaler.h} +16 -5
  199. data/vendor/faiss/faiss/impl/fast_scan/accumulate_loops.h +237 -0
  200. data/vendor/faiss/faiss/impl/fast_scan/accumulate_loops_512.h +185 -0
  201. data/vendor/faiss/faiss/impl/fast_scan/decompose_qbs.h +229 -0
  202. data/vendor/faiss/faiss/impl/fast_scan/dispatching.h +268 -0
  203. data/vendor/faiss/faiss/impl/{pq4_fast_scan.cpp → fast_scan/fast_scan.cpp} +176 -4
  204. data/vendor/faiss/faiss/impl/fast_scan/fast_scan.h +341 -0
  205. data/vendor/faiss/faiss/impl/fast_scan/impl-avx2.cpp +36 -0
  206. data/vendor/faiss/faiss/impl/fast_scan/impl-avx512.cpp +40 -0
  207. data/vendor/faiss/faiss/impl/fast_scan/impl-neon.cpp +120 -0
  208. data/vendor/faiss/faiss/impl/fast_scan/impl-riscv.cpp +104 -0
  209. data/vendor/faiss/faiss/impl/fast_scan/kernels_simd256.h +213 -0
  210. data/vendor/faiss/faiss/impl/{pq4_fast_scan_search_qbs.cpp → fast_scan/kernels_simd512.h} +26 -348
  211. data/vendor/faiss/faiss/impl/fast_scan/rabitq_dispatching.h +90 -0
  212. data/vendor/faiss/faiss/impl/fast_scan/rabitq_result_handler.h +108 -0
  213. data/vendor/faiss/faiss/impl/{simd_result_handlers.h → fast_scan/simd_result_handlers.h} +290 -142
  214. data/vendor/faiss/faiss/impl/hnsw/LockVector.cpp +54 -0
  215. data/vendor/faiss/faiss/impl/hnsw/LockVector.h +64 -0
  216. data/vendor/faiss/faiss/impl/hnsw/MinimaxHeap.cpp +91 -0
  217. data/vendor/faiss/faiss/impl/hnsw/MinimaxHeap.h +64 -0
  218. data/vendor/faiss/faiss/impl/hnsw/avx2.cpp +104 -0
  219. data/vendor/faiss/faiss/impl/hnsw/avx512.cpp +111 -0
  220. data/vendor/faiss/faiss/impl/index_read.cpp +1950 -505
  221. data/vendor/faiss/faiss/impl/index_read_utils.h +1 -2
  222. data/vendor/faiss/faiss/impl/index_write.cpp +112 -21
  223. data/vendor/faiss/faiss/impl/io.cpp +6 -6
  224. data/vendor/faiss/faiss/impl/io_macros.h +33 -16
  225. data/vendor/faiss/faiss/impl/kmeans1d.cpp +10 -10
  226. data/vendor/faiss/faiss/impl/lattice_Zn.cpp +81 -40
  227. data/vendor/faiss/faiss/impl/lattice_Zn.h +6 -6
  228. data/vendor/faiss/faiss/impl/mapped_io.cpp +15 -8
  229. data/vendor/faiss/faiss/impl/platform_macros.h +11 -4
  230. data/vendor/faiss/faiss/impl/pq_code_distance/IVFPQScanner_impl.h +549 -0
  231. data/vendor/faiss/faiss/impl/pq_code_distance/IVFPQ_QueryTables.cpp +245 -0
  232. data/vendor/faiss/faiss/impl/pq_code_distance/IVFPQ_QueryTables.h +105 -0
  233. data/vendor/faiss/faiss/impl/pq_code_distance/PQDistanceComputer_impl.h +106 -0
  234. data/vendor/faiss/faiss/impl/pq_code_distance/avx2.cpp +21 -0
  235. data/vendor/faiss/faiss/impl/pq_code_distance/avx512.cpp +21 -0
  236. data/vendor/faiss/faiss/impl/pq_code_distance/neon.cpp +21 -0
  237. data/vendor/faiss/faiss/impl/{code_distance/code_distance-avx2.h → pq_code_distance/pq_code_distance-avx2.h} +43 -220
  238. data/vendor/faiss/faiss/impl/{code_distance/code_distance-avx512.h → pq_code_distance/pq_code_distance-avx512.h} +25 -112
  239. data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-generic.cpp +59 -0
  240. data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-generic.h +96 -0
  241. data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-inl.h +256 -0
  242. data/vendor/faiss/faiss/impl/{code_distance/code_distance-sve.h → pq_code_distance/pq_code_distance-sve.cpp} +57 -146
  243. data/vendor/faiss/faiss/impl/pq_code_distance/rvv.cpp +68 -0
  244. data/vendor/faiss/faiss/impl/residual_quantizer_encode_steps.cpp +320 -483
  245. data/vendor/faiss/faiss/impl/residual_quantizer_encode_steps.h +1 -1
  246. data/vendor/faiss/faiss/impl/scalar_quantizer/codecs.h +121 -0
  247. data/vendor/faiss/faiss/impl/scalar_quantizer/distance_computers.h +137 -0
  248. data/vendor/faiss/faiss/impl/scalar_quantizer/quantizers.h +371 -0
  249. data/vendor/faiss/faiss/impl/scalar_quantizer/scanners.h +190 -0
  250. data/vendor/faiss/faiss/impl/scalar_quantizer/similarities.h +94 -0
  251. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx2.cpp +603 -0
  252. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512.cpp +597 -0
  253. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-dispatch.h +388 -0
  254. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-neon.cpp +630 -0
  255. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-rvv.cpp +311 -0
  256. data/vendor/faiss/faiss/impl/scalar_quantizer/training.cpp +387 -0
  257. data/vendor/faiss/faiss/impl/scalar_quantizer/training.h +54 -0
  258. data/vendor/faiss/faiss/impl/simd_dispatch.h +173 -0
  259. data/vendor/faiss/faiss/impl/simdlib/simdlib.h +57 -0
  260. data/vendor/faiss/faiss/{utils → impl/simdlib}/simdlib_avx2.h +274 -171
  261. data/vendor/faiss/faiss/impl/simdlib/simdlib_avx512.h +414 -0
  262. data/vendor/faiss/faiss/impl/simdlib/simdlib_dispatch.h +44 -0
  263. data/vendor/faiss/faiss/{utils → impl/simdlib}/simdlib_emulated.h +231 -166
  264. data/vendor/faiss/faiss/{utils → impl/simdlib}/simdlib_neon.h +275 -217
  265. data/vendor/faiss/faiss/{utils → impl/simdlib}/simdlib_ppc64.h +201 -160
  266. data/vendor/faiss/faiss/impl/svs_io.cpp +12 -3
  267. data/vendor/faiss/faiss/impl/svs_io.h +8 -2
  268. data/vendor/faiss/faiss/index_factory.cpp +115 -28
  269. data/vendor/faiss/faiss/index_io.h +53 -3
  270. data/vendor/faiss/faiss/invlists/BlockInvertedLists.cpp +73 -20
  271. data/vendor/faiss/faiss/invlists/DirectMap.cpp +24 -14
  272. data/vendor/faiss/faiss/invlists/DirectMap.h +4 -3
  273. data/vendor/faiss/faiss/invlists/InvertedLists.cpp +157 -73
  274. data/vendor/faiss/faiss/invlists/InvertedLists.h +86 -23
  275. data/vendor/faiss/faiss/invlists/InvertedListsIOHook.cpp +4 -4
  276. data/vendor/faiss/faiss/invlists/OnDiskInvertedLists.cpp +14 -14
  277. data/vendor/faiss/faiss/invlists/OnDiskInvertedLists.h +1 -1
  278. data/vendor/faiss/faiss/svs/IndexSVSFaissUtils.h +9 -19
  279. data/vendor/faiss/faiss/svs/IndexSVSFlat.cpp +2 -2
  280. data/vendor/faiss/faiss/svs/IndexSVSFlat.h +2 -0
  281. data/vendor/faiss/faiss/svs/IndexSVSIVF.cpp +350 -0
  282. data/vendor/faiss/faiss/svs/IndexSVSIVF.h +128 -0
  283. data/vendor/faiss/faiss/svs/IndexSVSIVFLVQ.cpp +40 -0
  284. data/vendor/faiss/faiss/svs/IndexSVSIVFLVQ.h +43 -0
  285. data/vendor/faiss/faiss/svs/IndexSVSIVFLeanVec.cpp +225 -0
  286. data/vendor/faiss/faiss/svs/IndexSVSIVFLeanVec.h +71 -0
  287. data/vendor/faiss/faiss/svs/IndexSVSVamana.cpp +25 -1
  288. data/vendor/faiss/faiss/svs/IndexSVSVamana.h +19 -2
  289. data/vendor/faiss/faiss/svs/IndexSVSVamanaLVQ.h +1 -1
  290. data/vendor/faiss/faiss/svs/IndexSVSVamanaLeanVec.cpp +19 -2
  291. data/vendor/faiss/faiss/svs/IndexSVSVamanaLeanVec.h +14 -0
  292. data/vendor/faiss/faiss/utils/Heap.cpp +56 -10
  293. data/vendor/faiss/faiss/utils/Heap.h +21 -0
  294. data/vendor/faiss/faiss/utils/NeuralNet.cpp +54 -40
  295. data/vendor/faiss/faiss/utils/NeuralNet.h +1 -1
  296. data/vendor/faiss/faiss/utils/approx_topk_hamming/approx_topk_hamming.h +10 -4
  297. data/vendor/faiss/faiss/utils/distances.cpp +507 -559
  298. data/vendor/faiss/faiss/utils/distances.h +118 -1
  299. data/vendor/faiss/faiss/utils/distances_dispatch.h +250 -0
  300. data/vendor/faiss/faiss/utils/distances_fused/avx512.cpp +8 -7
  301. data/vendor/faiss/faiss/utils/distances_fused/distances_fused.cpp +33 -14
  302. data/vendor/faiss/faiss/utils/distances_fused/distances_fused.h +12 -1
  303. data/vendor/faiss/faiss/utils/distances_fused/simdlib_based.cpp +16 -293
  304. data/vendor/faiss/faiss/utils/distances_fused/simdlib_based_neon.cpp +57 -0
  305. data/vendor/faiss/faiss/utils/distances_fused/simdlib_kernel-inl.h +290 -0
  306. data/vendor/faiss/faiss/utils/distances_simd.cpp +72 -3681
  307. data/vendor/faiss/faiss/utils/extra_distances.cpp +60 -102
  308. data/vendor/faiss/faiss/utils/extra_distances.h +79 -7
  309. data/vendor/faiss/faiss/utils/hamming-inl.h +13 -11
  310. data/vendor/faiss/faiss/utils/hamming.cpp +66 -517
  311. data/vendor/faiss/faiss/utils/hamming.h +92 -2
  312. data/vendor/faiss/faiss/utils/hamming_distance/common.h +287 -10
  313. data/vendor/faiss/faiss/utils/hamming_distance/hamming_avx2.cpp +15 -0
  314. data/vendor/faiss/faiss/utils/hamming_distance/hamming_avx512.cpp +15 -0
  315. data/vendor/faiss/faiss/utils/hamming_distance/hamming_computer-avx2.h +142 -0
  316. data/vendor/faiss/faiss/utils/hamming_distance/hamming_computer-avx512.h +234 -0
  317. data/vendor/faiss/faiss/utils/hamming_distance/hamming_computer-generic.h +368 -0
  318. data/vendor/faiss/faiss/utils/hamming_distance/hamming_computer-neon.h +322 -0
  319. data/vendor/faiss/faiss/utils/hamming_distance/hamming_computer-rvv.h +39 -0
  320. data/vendor/faiss/faiss/utils/hamming_distance/hamming_computer.h +146 -0
  321. data/vendor/faiss/faiss/utils/hamming_distance/hamming_impl.h +481 -0
  322. data/vendor/faiss/faiss/utils/hamming_distance/hamming_neon.cpp +15 -0
  323. data/vendor/faiss/faiss/utils/hamming_distance/hamming_rvv.cpp +15 -0
  324. data/vendor/faiss/faiss/utils/partitioning.cpp +66 -987
  325. data/vendor/faiss/faiss/utils/partitioning.h +31 -0
  326. data/vendor/faiss/faiss/utils/popcount.h +29 -0
  327. data/vendor/faiss/faiss/utils/pq_code_distance.h +251 -0
  328. data/vendor/faiss/faiss/utils/prefetch.h +2 -2
  329. data/vendor/faiss/faiss/utils/quantize_lut.cpp +30 -30
  330. data/vendor/faiss/faiss/utils/quantize_lut.h +1 -1
  331. data/vendor/faiss/faiss/utils/rabitq_simd.h +124 -343
  332. data/vendor/faiss/faiss/utils/random.cpp +6 -6
  333. data/vendor/faiss/faiss/utils/simd_impl/IVFFlatScanner-inl.h +51 -0
  334. data/vendor/faiss/faiss/utils/simd_impl/distances_aarch64.cpp +154 -0
  335. data/vendor/faiss/faiss/utils/simd_impl/distances_arm_sve.cpp +777 -0
  336. data/vendor/faiss/faiss/utils/simd_impl/distances_autovec-inl.h +306 -0
  337. data/vendor/faiss/faiss/utils/simd_impl/distances_avx2.cpp +1431 -0
  338. data/vendor/faiss/faiss/utils/simd_impl/distances_avx512.cpp +1095 -0
  339. data/vendor/faiss/faiss/utils/simd_impl/distances_rvv.cpp +189 -0
  340. data/vendor/faiss/faiss/utils/simd_impl/distances_simdlib256.h +195 -0
  341. data/vendor/faiss/faiss/utils/simd_impl/distances_sse-inl.h +392 -0
  342. data/vendor/faiss/faiss/utils/{distances_fused/simdlib_based.h → simd_impl/exhaustive_L2sqr_blas_cmax.h} +5 -10
  343. data/vendor/faiss/faiss/utils/simd_impl/hamming_impl.h +481 -0
  344. data/vendor/faiss/faiss/utils/simd_impl/partitioning_avx2.cpp +14 -0
  345. data/vendor/faiss/faiss/utils/simd_impl/partitioning_neon.cpp +14 -0
  346. data/vendor/faiss/faiss/utils/simd_impl/partitioning_simdlib256.h +1085 -0
  347. data/vendor/faiss/faiss/utils/simd_impl/rabitq_avx2.cpp +355 -0
  348. data/vendor/faiss/faiss/utils/simd_impl/rabitq_avx512.cpp +477 -0
  349. data/vendor/faiss/faiss/utils/simd_impl/rabitq_neon.cpp +55 -0
  350. data/vendor/faiss/faiss/utils/simd_impl/rabitq_rvv.cpp +55 -0
  351. data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_dispatch.h +32 -0
  352. data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_kernels.h +43 -0
  353. data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_kernels_avx2.cpp +57 -0
  354. data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_kernels_avx512.cpp +45 -0
  355. data/vendor/faiss/faiss/utils/simd_levels.cpp +334 -0
  356. data/vendor/faiss/faiss/utils/simd_levels.h +183 -0
  357. data/vendor/faiss/faiss/utils/sorting.cpp +48 -36
  358. data/vendor/faiss/faiss/utils/utils.cpp +21 -14
  359. data/vendor/faiss/faiss/utils/utils.h +3 -3
  360. metadata +156 -42
  361. data/vendor/faiss/faiss/impl/RaBitQStats.cpp +0 -29
  362. data/vendor/faiss/faiss/impl/RaBitQStats.h +0 -56
  363. data/vendor/faiss/faiss/impl/code_distance/code_distance-generic.h +0 -81
  364. data/vendor/faiss/faiss/impl/code_distance/code_distance.h +0 -186
  365. data/vendor/faiss/faiss/impl/pq4_fast_scan.h +0 -216
  366. data/vendor/faiss/faiss/impl/pq4_fast_scan_search_1.cpp +0 -224
  367. data/vendor/faiss/faiss/utils/approx_topk/approx_topk.h +0 -84
  368. data/vendor/faiss/faiss/utils/approx_topk/avx2-inl.h +0 -196
  369. data/vendor/faiss/faiss/utils/approx_topk/mode.h +0 -34
  370. data/vendor/faiss/faiss/utils/distances_fused/avx512.h +0 -36
  371. data/vendor/faiss/faiss/utils/extra_distances-inl.h +0 -228
  372. data/vendor/faiss/faiss/utils/hamming_distance/avx2-inl.h +0 -462
  373. data/vendor/faiss/faiss/utils/hamming_distance/avx512-inl.h +0 -490
  374. data/vendor/faiss/faiss/utils/hamming_distance/generic-inl.h +0 -450
  375. data/vendor/faiss/faiss/utils/hamming_distance/hamdis-inl.h +0 -87
  376. data/vendor/faiss/faiss/utils/hamming_distance/neon-inl.h +0 -524
  377. data/vendor/faiss/faiss/utils/simdlib.h +0 -42
  378. data/vendor/faiss/faiss/utils/simdlib_avx512.h +0 -296
  379. /data/vendor/faiss/faiss/{cppcontrib/factory_tools.h → factory_tools.h} +0 -0
@@ -10,16 +10,23 @@
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>
17
+ #include <cstring>
18
+ #include <memory>
15
19
  #include <optional>
16
20
 
17
21
  #include <faiss/impl/FaissAssert.h>
22
+ #include <faiss/impl/RaBitQUtils.h>
18
23
  #include <faiss/impl/io.h>
19
24
  #include <faiss/utils/hamming.h>
20
25
 
21
26
  #include <faiss/invlists/InvertedListsIOHook.h>
22
27
 
28
+ #include <faiss/invlists/BlockInvertedLists.h>
29
+
23
30
  #include <faiss/Index2Layer.h>
24
31
  #include <faiss/IndexAdditiveQuantizer.h>
25
32
  #include <faiss/IndexAdditiveQuantizerFastScan.h>
@@ -51,6 +58,9 @@
51
58
  #ifdef FAISS_ENABLE_SVS
52
59
  #include <faiss/impl/svs_io.h>
53
60
  #include <faiss/svs/IndexSVSFlat.h>
61
+ #include <faiss/svs/IndexSVSIVF.h>
62
+ #include <faiss/svs/IndexSVSIVFLVQ.h>
63
+ #include <faiss/svs/IndexSVSIVFLeanVec.h>
54
64
  #include <faiss/svs/IndexSVSVamana.h>
55
65
  #include <faiss/svs/IndexSVSVamanaLVQ.h>
56
66
  #include <faiss/svs/IndexSVSVamanaLeanVec.h>
@@ -73,6 +83,59 @@
73
83
 
74
84
  namespace faiss {
75
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
+
76
139
  /*************************************************************
77
140
  * Mmap-ing and viewing facilities
78
141
  **************************************************************/
@@ -205,34 +268,41 @@ void read_xb_vector(VectorT& target, IOReader* f) {
205
268
  * Read
206
269
  **************************************************************/
207
270
 
208
- void read_index_header(Index* idx, IOReader* f) {
209
- READ1(idx->d);
210
- READ1(idx->ntotal);
271
+ static void read_index_header(Index& idx, IOReader* f) {
272
+ READ1(idx.d);
273
+ READ1(idx.ntotal);
274
+ FAISS_CHECK_RANGE(idx.d, 0, (1 << 20) + 1);
275
+ FAISS_THROW_IF_NOT_FMT(
276
+ idx.ntotal >= 0,
277
+ "invalid ntotal %" PRId64 " read from index",
278
+ (int64_t)idx.ntotal);
211
279
  idx_t dummy;
212
280
  READ1(dummy);
213
281
  READ1(dummy);
214
- READ1(idx->is_trained);
215
- READ1(idx->metric_type);
216
- if (idx->metric_type > 1) {
217
- READ1(idx->metric_arg);
282
+ READ1(idx.is_trained);
283
+ int metric_type_int;
284
+ READ1(metric_type_int);
285
+ idx.metric_type = metric_type_from_int(metric_type_int);
286
+ if (idx.metric_type > 1) {
287
+ READ1(idx.metric_arg);
218
288
  }
219
- idx->verbose = false;
289
+ idx.verbose = false;
220
290
  }
221
291
 
222
- VectorTransform* read_VectorTransform(IOReader* f) {
292
+ std::unique_ptr<VectorTransform> read_VectorTransform_up(IOReader* f) {
223
293
  uint32_t h;
224
294
  READ1(h);
225
- VectorTransform* vt = nullptr;
295
+ std::unique_ptr<VectorTransform> vt;
226
296
 
227
297
  if (h == fourcc("rrot") || h == fourcc("PCAm") || h == fourcc("LTra") ||
228
298
  h == fourcc("PcAm") || h == fourcc("Viqm") || h == fourcc("Pcam")) {
229
- LinearTransform* lt = nullptr;
299
+ std::unique_ptr<LinearTransform> lt;
230
300
  if (h == fourcc("rrot")) {
231
- lt = new RandomRotationMatrix();
301
+ lt = std::make_unique<RandomRotationMatrix>();
232
302
  } else if (
233
303
  h == fourcc("PCAm") || h == fourcc("PcAm") ||
234
304
  h == fourcc("Pcam")) {
235
- PCAMatrix* pca = new PCAMatrix();
305
+ auto pca = std::make_unique<PCAMatrix>();
236
306
  READ1(pca->eigen_power);
237
307
  if (h == fourcc("Pcam")) {
238
308
  READ1(pca->epsilon);
@@ -244,53 +314,59 @@ VectorTransform* read_VectorTransform(IOReader* f) {
244
314
  READVECTOR(pca->mean);
245
315
  READVECTOR(pca->eigenvalues);
246
316
  READVECTOR(pca->PCAMat);
247
- lt = pca;
317
+ lt = std::move(pca);
248
318
  } else if (h == fourcc("Viqm")) {
249
- ITQMatrix* itqm = new ITQMatrix();
319
+ auto itqm = std::make_unique<ITQMatrix>();
250
320
  READ1(itqm->max_iter);
251
321
  READ1(itqm->seed);
252
- lt = itqm;
322
+ lt = std::move(itqm);
253
323
  } else if (h == fourcc("LTra")) {
254
- lt = new LinearTransform();
324
+ lt = std::make_unique<LinearTransform>();
255
325
  }
256
326
  READ1(lt->have_bias);
257
327
  READVECTOR(lt->A);
258
328
  READVECTOR(lt->b);
259
- FAISS_THROW_IF_NOT(lt->A.size() >= lt->d_in * lt->d_out);
260
- 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));
261
332
  lt->set_is_orthonormal();
262
- vt = lt;
333
+ vt = std::move(lt);
263
334
  } else if (h == fourcc("RmDT")) {
264
- RemapDimensionsTransform* rdt = new RemapDimensionsTransform();
335
+ auto rdt = std::make_unique<RemapDimensionsTransform>();
265
336
  READVECTOR(rdt->map);
266
- vt = rdt;
337
+ vt = std::move(rdt);
267
338
  } else if (h == fourcc("VNrm")) {
268
- NormalizationTransform* nt = new NormalizationTransform();
339
+ auto nt = std::make_unique<NormalizationTransform>();
269
340
  READ1(nt->norm);
270
- vt = nt;
341
+ vt = std::move(nt);
271
342
  } else if (h == fourcc("VCnt")) {
272
- CenteringTransform* ct = new CenteringTransform();
343
+ auto ct = std::make_unique<CenteringTransform>();
273
344
  READVECTOR(ct->mean);
274
- vt = ct;
345
+ vt = std::move(ct);
275
346
  } else if (h == fourcc("Viqt")) {
276
- ITQTransform* itqt = new ITQTransform();
347
+ auto itqt = std::make_unique<ITQTransform>();
277
348
 
278
349
  READVECTOR(itqt->mean);
279
350
  READ1(itqt->do_pca);
280
351
  {
281
- ITQMatrix* itqm = dynamic_cast<ITQMatrix*>(read_VectorTransform(f));
352
+ // Read, dereference, discard.
353
+ auto sub_vt = read_VectorTransform_up(f);
354
+ ITQMatrix* itqm = dynamic_cast<ITQMatrix*>(sub_vt.get());
282
355
  FAISS_THROW_IF_NOT(itqm);
283
356
  itqt->itq = *itqm;
284
- delete itqm;
285
357
  }
286
358
  {
287
- LinearTransform* pi =
288
- dynamic_cast<LinearTransform*>(read_VectorTransform(f));
359
+ // Read, dereference, discard.
360
+ auto sub_vt = read_VectorTransform_up(f);
361
+ LinearTransform* pi = dynamic_cast<LinearTransform*>(sub_vt.get());
289
362
  FAISS_THROW_IF_NOT(pi);
290
363
  itqt->pca_then_itq = *pi;
291
- delete pi;
292
364
  }
293
- vt = itqt;
365
+ vt = std::move(itqt);
366
+ } else if (h == fourcc("HRot")) {
367
+ auto hr = std::make_unique<HadamardRotation>();
368
+ READ1(hr->seed);
369
+ vt = std::move(hr);
294
370
  } else {
295
371
  FAISS_THROW_FMT(
296
372
  "fourcc %ud (\"%s\") not recognized in %s",
@@ -301,9 +377,110 @@ VectorTransform* read_VectorTransform(IOReader* f) {
301
377
  READ1(vt->d_in);
302
378
  READ1(vt->d_out);
303
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
+ }
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);
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");
433
+ hr->init(hr->seed);
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
+ }
304
477
  return vt;
305
478
  }
306
479
 
480
+ VectorTransform* read_VectorTransform(IOReader* f) {
481
+ return read_VectorTransform_up(f).release();
482
+ }
483
+
307
484
  static void read_ArrayInvertedLists_sizes(
308
485
  IOReader* f,
309
486
  std::vector<size_t>& sizes) {
@@ -316,6 +493,10 @@ static void read_ArrayInvertedLists_sizes(
316
493
  } else if (list_type == fourcc("sprs")) {
317
494
  std::vector<size_t> idsizes;
318
495
  READVECTOR(idsizes);
496
+ FAISS_THROW_IF_NOT_FMT(
497
+ idsizes.size() % 2 == 0,
498
+ "invalid sparse inverted list size: %zd (must be even)",
499
+ idsizes.size());
319
500
  for (size_t j = 0; j < idsizes.size(); j += 2) {
320
501
  FAISS_THROW_IF_NOT(idsizes[j] < sizes.size());
321
502
  sizes[idsizes[j]] = idsizes[j + 1];
@@ -328,30 +509,121 @@ static void read_ArrayInvertedLists_sizes(
328
509
  }
329
510
  }
330
511
 
331
- InvertedLists* read_InvertedLists(IOReader* f, int io_flags) {
512
+ bool index_read_warn_on_null_invlists = true;
513
+
514
+ std::unique_ptr<InvertedLists> read_InvertedLists_up(
515
+ IOReader* f,
516
+ int io_flags) {
332
517
  uint32_t h;
333
518
  READ1(h);
334
519
  if (h == fourcc("il00")) {
335
- fprintf(stderr,
336
- "read_InvertedLists:"
337
- " 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
+ }
338
525
  return nullptr;
339
526
  } else if (h == fourcc("ilpn") && !(io_flags & IO_FLAG_SKIP_IVF_DATA)) {
340
527
  size_t nlist, code_size, n_levels;
341
528
  READ1(nlist);
529
+ FAISS_CHECK_DESERIALIZATION_LOOP_LIMIT(nlist, "ilpn nlist");
342
530
  READ1(code_size);
343
531
  READ1(n_levels);
344
- auto ailp = new ArrayInvertedListsPanorama(nlist, code_size, 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);
345
537
  std::vector<size_t> sizes(nlist);
346
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();
347
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");
586
+ READ1(code_size);
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);
592
+ auto ailp = std::make_unique<ArrayInvertedListsPanorama>(
593
+ nlist, code_size, n_levels, bs);
594
+ std::vector<size_t> sizes(nlist);
595
+ read_ArrayInvertedLists_sizes(f, sizes);
596
+ size_t byte_limit = get_deserialization_vector_byte_limit();
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]);
348
604
  ailp->ids[i].resize(sizes[i]);
349
- size_t num_elems =
350
- ((sizes[i] + ArrayInvertedListsPanorama::kBatchSize - 1) /
351
- ArrayInvertedListsPanorama::kBatchSize) *
352
- ArrayInvertedListsPanorama::kBatchSize;
353
- ailp->codes[i].resize(num_elems * code_size);
354
- 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);
355
627
  }
356
628
  for (size_t i = 0; i < nlist; i++) {
357
629
  size_t n = sizes[i];
@@ -365,22 +637,44 @@ InvertedLists* read_InvertedLists(IOReader* f, int io_flags) {
365
637
  }
366
638
  return ailp;
367
639
  } else if (h == fourcc("ilar") && !(io_flags & IO_FLAG_SKIP_IVF_DATA)) {
368
- auto ails = new ArrayInvertedLists(0, 0);
640
+ auto ails = std::make_unique<ArrayInvertedLists>(0, 0);
369
641
  READ1(ails->nlist);
642
+ FAISS_CHECK_DESERIALIZATION_LOOP_LIMIT(ails->nlist, "ilar nlist");
370
643
  READ1(ails->code_size);
371
644
  ails->ids.resize(ails->nlist);
372
645
  ails->codes.resize(ails->nlist);
373
646
  std::vector<size_t> sizes(ails->nlist);
374
647
  read_ArrayInvertedLists_sizes(f, sizes);
375
- for (size_t i = 0; i < ails->nlist; i++) {
376
- ails->ids[i].resize(sizes[i]);
377
- ails->codes[i].resize(sizes[i] * ails->code_size);
378
- }
379
- for (size_t i = 0; i < ails->nlist; i++) {
380
- 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);
381
672
  if (n > 0) {
382
673
  read_vector_with_known_size(
383
- ails->codes[i], f, n * ails->code_size);
674
+ ails->codes[i],
675
+ f,
676
+ mul_no_overflow(
677
+ n, ails->code_size, "inverted list codes"));
384
678
  read_vector_with_known_size(ails->ids[i], f, n);
385
679
  }
386
680
  }
@@ -393,240 +687,686 @@ InvertedLists* read_InvertedLists(IOReader* f, int io_flags) {
393
687
  int h2 = (io_flags & 0xffff0000) | (fourcc("il__") & 0x0000ffff);
394
688
  size_t nlist, code_size;
395
689
  READ1(nlist);
690
+ FAISS_CHECK_DESERIALIZATION_LOOP_LIMIT(nlist, "ilar skip nlist");
396
691
  READ1(code_size);
397
692
  std::vector<size_t> sizes(nlist);
398
693
  read_ArrayInvertedLists_sizes(f, sizes);
399
- return InvertedListsIOHook::lookup(h2)->read_ArrayInvertedLists(
400
- f, io_flags, nlist, code_size, sizes);
694
+ return std::unique_ptr<InvertedLists>(
695
+ InvertedListsIOHook::lookup(h2)->read_ArrayInvertedLists(
696
+ f, io_flags, nlist, code_size, sizes));
401
697
  } else {
402
- return InvertedListsIOHook::lookup(h)->read(f, io_flags);
698
+ return std::unique_ptr<InvertedLists>(
699
+ InvertedListsIOHook::lookup(h)->read(f, io_flags));
403
700
  }
404
701
  }
405
702
 
406
- void read_InvertedLists(IndexIVF* ivf, IOReader* f, int io_flags) {
407
- InvertedLists* ils = read_InvertedLists(f, io_flags);
703
+ InvertedLists* read_InvertedLists(IOReader* f, int io_flags) {
704
+ return read_InvertedLists_up(f, io_flags).release();
705
+ }
706
+
707
+ void read_InvertedLists(IndexIVF& ivf, IOReader* f, int io_flags) {
708
+ auto ils = read_InvertedLists_up(f, io_flags);
408
709
  if (ils) {
409
- FAISS_THROW_IF_NOT(ils->nlist == ivf->nlist);
710
+ FAISS_THROW_IF_NOT(ils->nlist == ivf.nlist);
410
711
  FAISS_THROW_IF_NOT(
411
712
  ils->code_size == InvertedLists::INVALID_CODE_SIZE ||
412
- ils->code_size == ivf->code_size);
713
+ ils->code_size == ivf.code_size);
413
714
  }
414
- ivf->invlists = ils;
415
- ivf->own_invlists = true;
715
+ ivf.invlists = ils.release();
716
+ ivf.own_invlists = true;
416
717
  }
417
718
 
418
719
  void read_ProductQuantizer(ProductQuantizer* pq, IOReader* f) {
419
720
  READ1(pq->d);
420
721
  READ1(pq->M);
421
722
  READ1(pq->nbits);
723
+ FAISS_THROW_IF_NOT_FMT(
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
+ }
422
741
  pq->set_derived_values();
423
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);
424
750
  }
425
751
 
426
- static void read_ResidualQuantizer_old(ResidualQuantizer* rq, IOReader* f) {
427
- READ1(rq->d);
428
- READ1(rq->M);
429
- READVECTOR(rq->nbits);
430
- READ1(rq->is_trained);
431
- READ1(rq->train_type);
432
- READ1(rq->max_beam_size);
433
- READVECTOR(rq->codebooks);
434
- READ1(rq->search_type);
435
- READ1(rq->norm_min);
436
- READ1(rq->norm_max);
437
- rq->set_derived_values();
752
+ static void read_ResidualQuantizer_old(ResidualQuantizer& rq, IOReader* f) {
753
+ READ1(rq.d);
754
+ FAISS_THROW_IF_NOT_FMT(
755
+ rq.d > 0, "invalid AdditiveQuantizer d %zd, must be > 0", rq.d);
756
+ READ1(rq.M);
757
+ FAISS_THROW_IF_NOT_FMT(
758
+ rq.M > 0, "invalid AdditiveQuantizer M %zd, must be > 0", rq.M);
759
+ READVECTOR(rq.nbits);
760
+ FAISS_THROW_IF_NOT_FMT(
761
+ rq.nbits.size() == rq.M,
762
+ "ResidualQuantizer nbits size %zd != M %zd",
763
+ rq.nbits.size(),
764
+ rq.M);
765
+ READ1(rq.is_trained);
766
+ READ1(rq.train_type);
767
+ READ1(rq.max_beam_size);
768
+ READVECTOR(rq.codebooks);
769
+ READ1(rq.search_type);
770
+ READ1(rq.norm_min);
771
+ READ1(rq.norm_max);
772
+ rq.set_derived_values();
438
773
  }
439
774
 
440
- static void read_AdditiveQuantizer(AdditiveQuantizer* aq, IOReader* f) {
441
- READ1(aq->d);
442
- READ1(aq->M);
443
- READVECTOR(aq->nbits);
444
- READ1(aq->is_trained);
445
- READVECTOR(aq->codebooks);
446
- READ1(aq->search_type);
447
- READ1(aq->norm_min);
448
- READ1(aq->norm_max);
449
- if (aq->search_type == AdditiveQuantizer::ST_norm_cqint8 ||
450
- aq->search_type == AdditiveQuantizer::ST_norm_cqint4 ||
451
- aq->search_type == AdditiveQuantizer::ST_norm_lsq2x4 ||
452
- aq->search_type == AdditiveQuantizer::ST_norm_rq2x4) {
453
- read_xb_vector(aq->qnorm.codes, f);
454
- aq->qnorm.ntotal = aq->qnorm.codes.size() / 4;
455
- aq->qnorm.update_permutation();
775
+ static void read_AdditiveQuantizer(AdditiveQuantizer& aq, IOReader* f) {
776
+ READ1(aq.d);
777
+ FAISS_THROW_IF_NOT_FMT(
778
+ aq.d > 0, "invalid AdditiveQuantizer d %zd, must be > 0", aq.d);
779
+ READ1(aq.M);
780
+ FAISS_THROW_IF_NOT_FMT(
781
+ aq.M > 0, "invalid AdditiveQuantizer M %zd, must be > 0", aq.M);
782
+ READVECTOR(aq.nbits);
783
+ READ1(aq.is_trained);
784
+ READVECTOR(aq.codebooks);
785
+ FAISS_THROW_IF_NOT_FMT(
786
+ aq.nbits.size() == aq.M,
787
+ "AdditiveQuantizer nbits size %zd != M %zd",
788
+ aq.nbits.size(),
789
+ aq.M);
790
+ READ1(aq.search_type);
791
+ READ1(aq.norm_min);
792
+ READ1(aq.norm_max);
793
+ if (aq.search_type == AdditiveQuantizer::ST_norm_cqint8 ||
794
+ aq.search_type == AdditiveQuantizer::ST_norm_cqint4 ||
795
+ aq.search_type == AdditiveQuantizer::ST_norm_lsq2x4 ||
796
+ aq.search_type == AdditiveQuantizer::ST_norm_rq2x4) {
797
+ read_xb_vector(aq.qnorm.codes, f);
798
+ aq.qnorm.ntotal = aq.qnorm.codes.size() / 4;
799
+ aq.qnorm.update_permutation();
456
800
  }
457
801
 
458
- if (aq->search_type == AdditiveQuantizer::ST_norm_lsq2x4 ||
459
- aq->search_type == AdditiveQuantizer::ST_norm_rq2x4) {
460
- READVECTOR(aq->norm_tabs);
802
+ if (aq.search_type == AdditiveQuantizer::ST_norm_lsq2x4 ||
803
+ aq.search_type == AdditiveQuantizer::ST_norm_rq2x4) {
804
+ READVECTOR(aq.norm_tabs);
461
805
  }
462
806
 
463
- aq->set_derived_values();
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);
464
899
  }
465
900
 
466
901
  static void read_ResidualQuantizer(
467
- ResidualQuantizer* rq,
902
+ ResidualQuantizer& rq,
468
903
  IOReader* f,
469
904
  int io_flags) {
470
905
  read_AdditiveQuantizer(rq, f);
471
- READ1(rq->train_type);
472
- READ1(rq->max_beam_size);
473
- if ((rq->train_type & ResidualQuantizer::Skip_codebook_tables) ||
906
+ validate_codebooks_size(rq, rq.d);
907
+ READ1(rq.train_type);
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
+ }
929
+ if ((rq.train_type & ResidualQuantizer::Skip_codebook_tables) ||
474
930
  (io_flags & IO_FLAG_SKIP_PRECOMPUTE_TABLE)) {
475
931
  // don't precompute the tables
476
932
  } else {
477
- rq->compute_codebook_tables();
933
+ rq.compute_codebook_tables();
478
934
  }
479
935
  }
480
936
 
481
- static void read_LocalSearchQuantizer(LocalSearchQuantizer* lsq, IOReader* f) {
937
+ static void read_LocalSearchQuantizer(LocalSearchQuantizer& lsq, IOReader* f) {
482
938
  read_AdditiveQuantizer(lsq, f);
483
- READ1(lsq->K);
484
- READ1(lsq->train_iters);
485
- READ1(lsq->encode_ils_iters);
486
- READ1(lsq->train_ils_iters);
487
- READ1(lsq->icm_iters);
488
- READ1(lsq->p);
489
- READ1(lsq->lambd);
490
- READ1(lsq->chunk_size);
491
- READ1(lsq->random_seed);
492
- READ1(lsq->nperts);
493
- READ1(lsq->update_codebooks_with_double);
939
+ validate_codebooks_size(lsq, lsq.d);
940
+ READ1(lsq.K);
941
+ READ1(lsq.train_iters);
942
+ READ1(lsq.encode_ils_iters);
943
+ READ1(lsq.train_ils_iters);
944
+ READ1(lsq.icm_iters);
945
+ READ1(lsq.p);
946
+ READ1(lsq.lambd);
947
+ READ1(lsq.chunk_size);
948
+ READ1(lsq.random_seed);
949
+ READ1(lsq.nperts);
950
+ READ1(lsq.update_codebooks_with_double);
494
951
  }
495
952
 
496
953
  static void read_ProductAdditiveQuantizer(
497
- ProductAdditiveQuantizer* paq,
954
+ ProductAdditiveQuantizer& paq,
498
955
  IOReader* f) {
499
956
  read_AdditiveQuantizer(paq, f);
500
- READ1(paq->nsplits);
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);
501
969
  }
502
970
 
503
971
  static void read_ProductResidualQuantizer(
504
- ProductResidualQuantizer* prq,
972
+ ProductResidualQuantizer& prq,
505
973
  IOReader* f,
506
974
  int io_flags) {
507
975
  read_ProductAdditiveQuantizer(prq, f);
508
976
 
509
- for (size_t i = 0; i < prq->nsplits; i++) {
510
- auto rq = new ResidualQuantizer();
511
- read_ResidualQuantizer(rq, f, io_flags);
512
- prq->quantizers.push_back(rq);
977
+ size_t d_sub = prq.d / prq.nsplits;
978
+ for (size_t i = 0; i < prq.nsplits; i++) {
979
+ auto rq = std::make_unique<ResidualQuantizer>();
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);
990
+ prq.quantizers.push_back(rq.release());
513
991
  }
514
992
  }
515
993
 
516
994
  static void read_ProductLocalSearchQuantizer(
517
- ProductLocalSearchQuantizer* plsq,
995
+ ProductLocalSearchQuantizer& plsq,
518
996
  IOReader* f) {
519
997
  read_ProductAdditiveQuantizer(plsq, f);
520
998
 
521
- for (size_t i = 0; i < plsq->nsplits; i++) {
522
- auto lsq = new LocalSearchQuantizer();
523
- read_LocalSearchQuantizer(lsq, f);
524
- plsq->quantizers.push_back(lsq);
999
+ size_t d_sub = plsq.d / plsq.nsplits;
1000
+ for (size_t i = 0; i < plsq.nsplits; i++) {
1001
+ auto lsq = std::make_unique<LocalSearchQuantizer>();
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);
1012
+ plsq.quantizers.push_back(lsq.release());
525
1013
  }
526
1014
  }
527
1015
 
528
- void read_ScalarQuantizer(ScalarQuantizer* ivsc, IOReader* f) {
529
- 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);
530
1028
  READ1(ivsc->rangestat);
531
1029
  READ1(ivsc->rangestat_arg);
532
1030
  READ1(ivsc->d);
533
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);
534
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
+ }
535
1105
  ivsc->set_derived_sizes();
536
1106
  }
537
1107
 
538
- static void read_HNSW(HNSW* hnsw, IOReader* f) {
539
- READVECTOR(hnsw->assign_probas);
540
- READVECTOR(hnsw->cum_nneighbor_per_level);
541
- READVECTOR(hnsw->levels);
542
- READVECTOR(hnsw->offsets);
543
- read_vector(hnsw->neighbors, f);
1108
+ static void validate_HNSW(const HNSW& hnsw) {
1109
+ size_t ntotal = hnsw.levels.size();
1110
+ size_t nb_neighbors_size = hnsw.neighbors.size();
1111
+
1112
+ // cum_nneighbor_per_level must be non-empty and monotonically
1113
+ // non-decreasing, starting at 0
1114
+ if (!hnsw.cum_nneighbor_per_level.empty()) {
1115
+ FAISS_THROW_IF_NOT_FMT(
1116
+ hnsw.cum_nneighbor_per_level[0] == 0,
1117
+ "HNSW cum_nneighbor_per_level[0] = %d, expected 0",
1118
+ hnsw.cum_nneighbor_per_level[0]);
1119
+ for (size_t i = 1; i < hnsw.cum_nneighbor_per_level.size(); i++) {
1120
+ FAISS_THROW_IF_NOT_FMT(
1121
+ hnsw.cum_nneighbor_per_level[i] >=
1122
+ hnsw.cum_nneighbor_per_level[i - 1],
1123
+ "HNSW cum_nneighbor_per_level not monotonic at %zd: "
1124
+ "%d < %d",
1125
+ i,
1126
+ hnsw.cum_nneighbor_per_level[i],
1127
+ hnsw.cum_nneighbor_per_level[i - 1]);
1128
+ }
1129
+ }
1130
+
1131
+ // every levels[i] must be a valid index into cum_nneighbor_per_level
1132
+ size_t cum_size = hnsw.cum_nneighbor_per_level.size();
1133
+ for (size_t i = 0; i < ntotal; i++) {
1134
+ FAISS_THROW_IF_NOT_FMT(
1135
+ hnsw.levels[i] >= 0 &&
1136
+ static_cast<size_t>(hnsw.levels[i]) < cum_size,
1137
+ "HNSW levels[%zd] = %d out of range [0, %zd)",
1138
+ i,
1139
+ hnsw.levels[i],
1140
+ cum_size);
1141
+ }
1142
+
1143
+ // offsets must have size ntotal + 1, be monotonically non-decreasing,
1144
+ // and all values must be <= neighbors.size()
1145
+ FAISS_THROW_IF_NOT_FMT(
1146
+ hnsw.offsets.size() == ntotal + 1,
1147
+ "HNSW offsets size %zd != levels size %zd + 1",
1148
+ hnsw.offsets.size(),
1149
+ ntotal);
1150
+ for (size_t i = 0; i < hnsw.offsets.size(); i++) {
1151
+ FAISS_THROW_IF_NOT_FMT(
1152
+ hnsw.offsets[i] <= nb_neighbors_size,
1153
+ "HNSW offsets[%zd] = %zd > neighbors.size() = %zd",
1154
+ i,
1155
+ hnsw.offsets[i],
1156
+ nb_neighbors_size);
1157
+ if (i > 0) {
1158
+ FAISS_THROW_IF_NOT_FMT(
1159
+ hnsw.offsets[i] ==
1160
+ hnsw.offsets[i - 1] +
1161
+ hnsw.cum_nneighbor_per_level
1162
+ [hnsw.levels[i - 1]],
1163
+ "HNSW offsets not increasing by cum_neighbor_per_level at %zd: %zd + %d != %zd",
1164
+ i,
1165
+ hnsw.offsets[i - 1],
1166
+ hnsw.cum_nneighbor_per_level[hnsw.levels[i - 1]],
1167
+ hnsw.offsets[i]);
1168
+ }
1169
+ }
1170
+
1171
+ // max_level must be valid
1172
+ FAISS_THROW_IF_NOT_FMT(
1173
+ hnsw.max_level < (int)hnsw.cum_nneighbor_per_level.size(),
1174
+ "HNSW max_level %d >= cum_nneighbor_per_level size %zd",
1175
+ hnsw.max_level,
1176
+ hnsw.cum_nneighbor_per_level.size());
1177
+
1178
+ // entry_point must be -1 (empty) or a valid node id
1179
+ FAISS_THROW_IF_NOT_FMT(
1180
+ hnsw.entry_point >= -1 && hnsw.entry_point < (int)ntotal,
1181
+ "HNSW entry_point %d out of range [-1, %zd)",
1182
+ (int)hnsw.entry_point,
1183
+ ntotal);
1184
+
1185
+ // All neighbor ids must be -1 or in [0, ntotal)
1186
+ for (size_t i = 0; i < nb_neighbors_size; i++) {
1187
+ auto id = hnsw.neighbors[i];
1188
+ FAISS_THROW_IF_NOT_FMT(
1189
+ id >= -1 && id < (int)ntotal,
1190
+ "HNSW neighbors[%zd] = %d out of range [-1, %zd)",
1191
+ i,
1192
+ (int)id,
1193
+ ntotal);
1194
+ }
1195
+
1196
+ // For each node, verify that its level is valid and that
1197
+ // offsets[i] + cum_nneighbor_per_level[levels[i]] <= neighbors.size().
1198
+ // This ensures neighbor_range() can never produce an out-of-bounds offset
1199
+ // into neighbors.
1200
+ int cum_levels = (int)hnsw.cum_nneighbor_per_level.size();
1201
+ for (size_t i = 0; i < ntotal; i++) {
1202
+ int level = hnsw.levels[i];
1203
+ FAISS_CHECK_RANGE(level, 1, cum_levels + 1);
1204
+ size_t end = hnsw.offsets[i] + hnsw.cum_nneighbor_per_level[level];
1205
+ FAISS_THROW_IF_NOT_FMT(
1206
+ end <= nb_neighbors_size,
1207
+ "HNSW neighbor range overflow for node %zd: "
1208
+ "offsets[%zd] (%zd) + cum_nneighbor_per_level[%d] (%d) "
1209
+ "= %zd > neighbors.size() (%zd)",
1210
+ i,
1211
+ i,
1212
+ hnsw.offsets[i],
1213
+ level,
1214
+ hnsw.cum_nneighbor_per_level[level],
1215
+ end,
1216
+ nb_neighbors_size);
1217
+ }
1218
+ }
1219
+
1220
+ static void read_HNSW(HNSW& hnsw, IOReader* f) {
1221
+ READVECTOR(hnsw.assign_probas);
1222
+ READVECTOR(hnsw.cum_nneighbor_per_level);
1223
+ READVECTOR(hnsw.levels);
1224
+ READVECTOR(hnsw.offsets);
1225
+ read_vector(hnsw.neighbors, f);
544
1226
 
545
- READ1(hnsw->entry_point);
546
- READ1(hnsw->max_level);
547
- READ1(hnsw->efConstruction);
548
- READ1(hnsw->efSearch);
1227
+ READ1(hnsw.entry_point);
1228
+ READ1(hnsw.max_level);
1229
+ READ1(hnsw.efConstruction);
1230
+ READ1(hnsw.efSearch);
549
1231
 
550
1232
  // // deprecated field
551
- // READ1(hnsw->upper_beam);
1233
+ // READ1(hnsw.upper_beam);
552
1234
  READ1_DUMMY(int)
553
- }
554
1235
 
555
- static void read_NSG(NSG* nsg, IOReader* f) {
556
- READ1(nsg->ntotal);
557
- READ1(nsg->R);
558
- READ1(nsg->L);
559
- READ1(nsg->C);
560
- READ1(nsg->search_L);
561
- READ1(nsg->enterpoint);
562
- READ1(nsg->is_built);
1236
+ validate_HNSW(hnsw);
1237
+ }
563
1238
 
564
- if (!nsg->is_built) {
1239
+ static void read_NSG(NSG& nsg, IOReader* f) {
1240
+ READ1(nsg.ntotal);
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);
1245
+ READ1(nsg.L);
1246
+ READ1(nsg.C);
1247
+ READ1(nsg.search_L);
1248
+ READ1(nsg.enterpoint);
1249
+ READ1(nsg.is_built);
1250
+
1251
+ FAISS_THROW_IF_NOT_FMT(
1252
+ nsg.ntotal >= 0, "invalid NSG ntotal %d", nsg.ntotal);
1253
+
1254
+ if (!nsg.is_built) {
565
1255
  return;
566
1256
  }
567
1257
 
568
1258
  constexpr int EMPTY_ID = -1;
569
- int N = nsg->ntotal;
570
- int R = nsg->R;
571
- auto& graph = nsg->final_graph;
1259
+ int N = nsg.ntotal;
1260
+ int R = nsg.R;
1261
+
1262
+ auto& graph = nsg.final_graph;
572
1263
  graph = std::make_shared<nsg::Graph<int>>(N, R);
573
- std::fill_n(graph->data, N * R, EMPTY_ID);
1264
+ std::fill_n(graph->data, (size_t)N * R, EMPTY_ID);
574
1265
 
575
1266
  for (int i = 0; i < N; i++) {
576
- for (int j = 0; j < R + 1; j++) {
1267
+ int j;
1268
+ for (j = 0; j < R; j++) {
577
1269
  int id;
578
1270
  READ1(id);
579
1271
  if (id != EMPTY_ID) {
1272
+ FAISS_CHECK_RANGE(id, 0, N);
580
1273
  graph->at(i, j) = id;
581
1274
  } else {
582
1275
  break;
583
1276
  }
584
1277
  }
1278
+ if (j == R) {
1279
+ // All R neighbor slots were filled; consume the trailing
1280
+ // EMPTY_ID sentinel that write_NSG always appends.
1281
+ int sentinel;
1282
+ READ1(sentinel);
1283
+ FAISS_THROW_IF_NOT(sentinel == EMPTY_ID);
1284
+ }
585
1285
  }
1286
+
1287
+ // enterpoint must be a valid node id
1288
+ FAISS_CHECK_RANGE(nsg.enterpoint, 0, N);
586
1289
  }
587
1290
 
588
- static void read_NNDescent(NNDescent* nnd, IOReader* f) {
589
- READ1(nnd->ntotal);
590
- READ1(nnd->d);
591
- READ1(nnd->K);
592
- READ1(nnd->S);
593
- READ1(nnd->R);
594
- READ1(nnd->L);
595
- READ1(nnd->iter);
596
- READ1(nnd->search_L);
597
- READ1(nnd->random_seed);
598
- READ1(nnd->has_built);
599
-
600
- READVECTOR(nnd->final_graph);
1291
+ static void read_NNDescent(NNDescent& nnd, IOReader* f) {
1292
+ READ1(nnd.ntotal);
1293
+ READ1(nnd.d);
1294
+ READ1(nnd.K);
1295
+ READ1(nnd.S);
1296
+ READ1(nnd.R);
1297
+ READ1(nnd.L);
1298
+ READ1(nnd.iter);
1299
+ READ1(nnd.search_L);
1300
+ READ1(nnd.random_seed);
1301
+ READ1(nnd.has_built);
1302
+
1303
+ FAISS_THROW_IF_NOT_FMT(
1304
+ nnd.ntotal >= 0, "invalid NNDescent ntotal %d", nnd.ntotal);
1305
+
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
+ }
601
1327
  }
602
1328
 
603
- ProductQuantizer* read_ProductQuantizer(const char* fname) {
1329
+ std::unique_ptr<ProductQuantizer> read_ProductQuantizer_up(const char* fname) {
604
1330
  FileIOReader reader(fname);
605
- return read_ProductQuantizer(&reader);
1331
+ return read_ProductQuantizer_up(&reader);
606
1332
  }
607
1333
 
608
- ProductQuantizer* read_ProductQuantizer(IOReader* reader) {
609
- ProductQuantizer* pq = new ProductQuantizer();
610
- std::unique_ptr<ProductQuantizer> del(pq);
1334
+ ProductQuantizer* read_ProductQuantizer(const char* fname) {
1335
+ return read_ProductQuantizer_up(fname).release();
1336
+ }
611
1337
 
612
- read_ProductQuantizer(pq, reader);
613
- del.release();
1338
+ std::unique_ptr<ProductQuantizer> read_ProductQuantizer_up(IOReader* reader) {
1339
+ auto pq = std::make_unique<ProductQuantizer>();
1340
+ read_ProductQuantizer(pq.get(), reader);
614
1341
  return pq;
615
1342
  }
616
1343
 
1344
+ ProductQuantizer* read_ProductQuantizer(IOReader* reader) {
1345
+ return read_ProductQuantizer_up(reader).release();
1346
+ }
1347
+
617
1348
  static void read_RaBitQuantizer(
618
- RaBitQuantizer* rabitq,
1349
+ RaBitQuantizer& rabitq,
619
1350
  IOReader* f,
1351
+ int expected_d,
620
1352
  bool multi_bit = true) {
621
- READ1(rabitq->d);
622
- READ1(rabitq->code_size);
623
- READ1(rabitq->metric_type);
1353
+ READ1(rabitq.d);
1354
+ READ1(rabitq.code_size);
1355
+ int metric_type_int;
1356
+ READ1(metric_type_int);
1357
+ rabitq.metric_type = metric_type_from_int(metric_type_int);
624
1358
 
625
1359
  if (multi_bit) {
626
- READ1(rabitq->nb_bits);
1360
+ READ1(rabitq.nb_bits);
627
1361
  } else {
628
- rabitq->nb_bits = 1;
1362
+ rabitq.nb_bits = 1;
629
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);
630
1370
  }
631
1371
 
632
1372
  void read_direct_map(DirectMap* dm, IOReader* f) {
@@ -649,8 +1389,9 @@ void read_ivf_header(
649
1389
  IndexIVF* ivf,
650
1390
  IOReader* f,
651
1391
  std::vector<std::vector<idx_t>>* ids) {
652
- read_index_header(ivf, f);
1392
+ read_index_header(*ivf, f);
653
1393
  READ1(ivf->nlist);
1394
+ FAISS_CHECK_DESERIALIZATION_LOOP_LIMIT(ivf->nlist, "nlist");
654
1395
  READ1(ivf->nprobe);
655
1396
  ivf->quantizer = read_index(f);
656
1397
  ivf->own_fields = true;
@@ -666,39 +1407,46 @@ void read_ivf_header(
666
1407
  ArrayInvertedLists* set_array_invlist(
667
1408
  IndexIVF* ivf,
668
1409
  std::vector<std::vector<idx_t>>& ids) {
669
- ArrayInvertedLists* ail =
670
- new ArrayInvertedLists(ivf->nlist, ivf->code_size);
1410
+ auto ail = std::make_unique<ArrayInvertedLists>(ivf->nlist, ivf->code_size);
671
1411
 
672
1412
  ail->ids.resize(ids.size());
673
1413
  for (size_t i = 0; i < ids.size(); i++) {
674
1414
  ail->ids[i] = MaybeOwnedVector<idx_t>(std::move(ids[i]));
675
1415
  }
676
1416
 
677
- ivf->invlists = ail;
1417
+ ArrayInvertedLists* result = ail.get();
1418
+ ivf->invlists = ail.release();
678
1419
  ivf->own_invlists = true;
679
- return ail;
1420
+ return result;
680
1421
  }
681
1422
 
682
- static IndexIVFPQ* read_ivfpq(IOReader* f, uint32_t h, int io_flags) {
1423
+ static std::unique_ptr<IndexIVFPQ> read_ivfpq(
1424
+ IOReader* f,
1425
+ uint32_t h,
1426
+ int io_flags) {
683
1427
  bool legacy = h == fourcc("IvQR") || h == fourcc("IvPQ");
684
1428
 
685
- IndexIVFPQR* ivfpqr = h == fourcc("IvQR") || h == fourcc("IwQR")
686
- ? new IndexIVFPQR()
687
- : nullptr;
688
- IndexIVFPQ* ivpq = ivfpqr ? ivfpqr : new IndexIVFPQ();
1429
+ IndexIVFPQR* ivfpqr = nullptr;
1430
+ std::unique_ptr<IndexIVFPQ> ivpq;
1431
+ if (h == fourcc("IvQR") || h == fourcc("IwQR")) {
1432
+ ivpq = std::make_unique<IndexIVFPQR>();
1433
+ ivfpqr = static_cast<IndexIVFPQR*>(ivpq.get());
1434
+ } else {
1435
+ ivpq = std::make_unique<IndexIVFPQ>();
1436
+ }
689
1437
 
690
1438
  std::vector<std::vector<idx_t>> ids;
691
- read_ivf_header(ivpq, f, legacy ? &ids : nullptr);
1439
+ read_ivf_header(ivpq.get(), f, legacy ? &ids : nullptr);
692
1440
  READ1(ivpq->by_residual);
693
1441
  READ1(ivpq->code_size);
694
1442
  read_ProductQuantizer(&ivpq->pq, f);
695
1443
 
696
1444
  if (legacy) {
697
- ArrayInvertedLists* ail = set_array_invlist(ivpq, ids);
1445
+ ArrayInvertedLists* ail = set_array_invlist(ivpq.get(), ids);
698
1446
  for (size_t i = 0; i < ail->nlist; i++)
699
1447
  READVECTOR(ail->codes[i]);
700
1448
  } else {
701
- read_InvertedLists(ivpq, f, io_flags);
1449
+ read_InvertedLists(*ivpq, f, io_flags);
702
1450
  }
703
1451
 
704
1452
  if (ivpq->is_trained) {
@@ -714,6 +1462,20 @@ static IndexIVFPQ* read_ivfpq(IOReader* f, uint32_t h, int io_flags) {
714
1462
  read_ProductQuantizer(&ivfpqr->refine_pq, f);
715
1463
  READVECTOR(ivfpqr->refine_codes);
716
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);
717
1479
  }
718
1480
  }
719
1481
  return ivpq;
@@ -721,53 +1483,63 @@ static IndexIVFPQ* read_ivfpq(IOReader* f, uint32_t h, int io_flags) {
721
1483
 
722
1484
  int read_old_fmt_hack = 0;
723
1485
 
724
- Index* read_index(IOReader* f, int io_flags) {
725
- Index* idx = nullptr;
1486
+ std::unique_ptr<Index> read_index_up(IOReader* f, int io_flags) {
1487
+ std::unique_ptr<Index> idx;
726
1488
  uint32_t h;
727
1489
  READ1(h);
728
1490
  if (h == fourcc("null")) {
729
1491
  // denotes a missing index, useful for some cases
730
- return nullptr;
731
- } else if (h == fourcc("IxFP")) {
1492
+ return idx;
1493
+ } else if (h == fourcc("IxFP") || h == fourcc("IxFp")) {
732
1494
  int d;
733
1495
  size_t n_levels, batch_size;
734
1496
  READ1(d);
735
1497
  READ1(n_levels);
1498
+ FAISS_THROW_IF_NOT_FMT(n_levels > 0, "invalid n_levels %zd", n_levels);
736
1499
  READ1(batch_size);
737
- IndexFlatL2Panorama* idxp =
738
- new IndexFlatL2Panorama(d, n_levels, batch_size);
1500
+ std::unique_ptr<IndexFlatPanorama> idxp;
1501
+ if (h == fourcc("IxFP")) {
1502
+ idxp = std::make_unique<IndexFlatL2Panorama>(
1503
+ d, n_levels, batch_size);
1504
+ } else {
1505
+ idxp = std::make_unique<IndexFlatIPPanorama>(
1506
+ d, n_levels, batch_size);
1507
+ }
739
1508
  READ1(idxp->ntotal);
740
1509
  READ1(idxp->is_trained);
741
1510
  READVECTOR(idxp->codes);
742
1511
  READVECTOR(idxp->cum_sums);
743
1512
  idxp->verbose = false;
744
- idx = idxp;
1513
+ idx = std::move(idxp);
745
1514
  } else if (
746
1515
  h == fourcc("IxFI") || h == fourcc("IxF2") || h == fourcc("IxFl")) {
747
- IndexFlat* idxf;
1516
+ std::unique_ptr<IndexFlat> idxf;
748
1517
  if (h == fourcc("IxFI")) {
749
- idxf = new IndexFlatIP();
1518
+ idxf = std::make_unique<IndexFlatIP>();
750
1519
  } else if (h == fourcc("IxF2")) {
751
- idxf = new IndexFlatL2();
1520
+ idxf = std::make_unique<IndexFlatL2>();
752
1521
  } else {
753
- idxf = new IndexFlat();
1522
+ idxf = std::make_unique<IndexFlat>();
754
1523
  }
755
- read_index_header(idxf, f);
1524
+ read_index_header(*idxf, f);
756
1525
  idxf->code_size = idxf->d * sizeof(float);
757
1526
  read_xb_vector(idxf->codes, f);
758
1527
  FAISS_THROW_IF_NOT(
759
1528
  idxf->codes.size() == idxf->ntotal * idxf->code_size);
760
- // leak!
761
- idx = idxf;
1529
+ idx = std::move(idxf);
762
1530
  } else if (h == fourcc("IxHE") || h == fourcc("IxHe")) {
763
- IndexLSH* idxl = new IndexLSH();
764
- read_index_header(idxl, f);
1531
+ auto idxl = std::make_unique<IndexLSH>();
1532
+ read_index_header(*idxl, f);
765
1533
  READ1(idxl->nbits);
766
1534
  READ1(idxl->rotate_data);
767
1535
  READ1(idxl->train_thresholds);
768
1536
  READVECTOR(idxl->thresholds);
769
1537
  int code_size_i;
770
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);
771
1543
  idxl->code_size = code_size_i;
772
1544
  if (h == fourcc("IxHE")) {
773
1545
  FAISS_THROW_IF_NOT_FMT(
@@ -778,27 +1550,32 @@ Index* read_index(IOReader* f, int io_flags) {
778
1550
  // leak
779
1551
  idxl->code_size *= 8;
780
1552
  }
1553
+ validate_code_size_match(
1554
+ idxl->code_size, (idxl->nbits + 7) / 8, "IndexLSH");
781
1555
  {
782
- RandomRotationMatrix* rrot = dynamic_cast<RandomRotationMatrix*>(
783
- read_VectorTransform(f));
1556
+ // Read, dereference, discard.
1557
+ auto sub_vt = read_VectorTransform_up(f);
1558
+ RandomRotationMatrix* rrot =
1559
+ dynamic_cast<RandomRotationMatrix*>(sub_vt.get());
784
1560
  FAISS_THROW_IF_NOT_MSG(rrot, "expected a random rotation");
785
1561
  idxl->rrot = *rrot;
786
- delete rrot;
787
1562
  }
788
1563
  read_vector(idxl->codes, f);
789
1564
  FAISS_THROW_IF_NOT(
790
1565
  idxl->rrot.d_in == idxl->d && idxl->rrot.d_out == idxl->nbits);
791
1566
  FAISS_THROW_IF_NOT(
792
1567
  idxl->codes.size() == idxl->ntotal * idxl->code_size);
793
- idx = idxl;
1568
+ idx = std::move(idxl);
794
1569
  } else if (
795
1570
  h == fourcc("IxPQ") || h == fourcc("IxPo") || h == fourcc("IxPq")) {
796
1571
  // IxPQ and IxPo were merged into the same IndexPQ object
797
- IndexPQ* idxp = new IndexPQ();
798
- read_index_header(idxp, f);
1572
+ auto idxp = std::make_unique<IndexPQ>();
1573
+ read_index_header(*idxp, f);
799
1574
  read_ProductQuantizer(&idxp->pq, f);
800
1575
  idxp->code_size = idxp->pq.code_size;
801
1576
  read_vector(idxp->codes, f);
1577
+ FAISS_THROW_IF_NOT(
1578
+ idxp->codes.size() == idxp->ntotal * idxp->code_size);
802
1579
  if (h == fourcc("IxPo") || h == fourcc("IxPq")) {
803
1580
  READ1(idxp->search_type);
804
1581
  READ1(idxp->encode_signs);
@@ -810,51 +1587,113 @@ Index* read_index(IOReader* f, int io_flags) {
810
1587
  if (h == fourcc("IxPQ") || h == fourcc("IxPo")) {
811
1588
  idxp->metric_type = METRIC_L2;
812
1589
  }
813
- idx = idxp;
1590
+ idx = std::move(idxp);
814
1591
  } else if (h == fourcc("IxRQ") || h == fourcc("IxRq")) {
815
- IndexResidualQuantizer* idxr = new IndexResidualQuantizer();
816
- read_index_header(idxr, f);
1592
+ auto idxr = std::make_unique<IndexResidualQuantizer>();
1593
+ read_index_header(*idxr, f);
817
1594
  if (h == fourcc("IxRQ")) {
818
- read_ResidualQuantizer_old(&idxr->rq, f);
1595
+ read_ResidualQuantizer_old(idxr->rq, f);
819
1596
  } else {
820
- read_ResidualQuantizer(&idxr->rq, f, io_flags);
1597
+ read_ResidualQuantizer(idxr->rq, f, io_flags);
821
1598
  }
1599
+ validate_aq_dimension_match(
1600
+ idxr->rq, idxr->d, "IndexResidualQuantizer");
822
1601
  READ1(idxr->code_size);
1602
+ validate_code_size_match(
1603
+ idxr->code_size, idxr->rq.code_size, "IndexResidualQuantizer");
823
1604
  read_vector(idxr->codes, f);
824
- idx = idxr;
1605
+ FAISS_THROW_IF_NOT(
1606
+ idxr->codes.size() == idxr->ntotal * idxr->code_size);
1607
+ idx = std::move(idxr);
825
1608
  } else if (h == fourcc("IxLS")) {
826
- auto idxr = new IndexLocalSearchQuantizer();
827
- read_index_header(idxr, f);
828
- read_LocalSearchQuantizer(&idxr->lsq, f);
1609
+ auto idxr = std::make_unique<IndexLocalSearchQuantizer>();
1610
+ read_index_header(*idxr, f);
1611
+ read_LocalSearchQuantizer(idxr->lsq, f);
1612
+ validate_aq_dimension_match(
1613
+ idxr->lsq, idxr->d, "IndexLocalSearchQuantizer");
829
1614
  READ1(idxr->code_size);
1615
+ validate_code_size_match(
1616
+ idxr->code_size,
1617
+ idxr->lsq.code_size,
1618
+ "IndexLocalSearchQuantizer");
830
1619
  read_vector(idxr->codes, f);
831
- idx = idxr;
1620
+ FAISS_THROW_IF_NOT(
1621
+ idxr->codes.size() == idxr->ntotal * idxr->code_size);
1622
+ idx = std::move(idxr);
832
1623
  } else if (h == fourcc("IxPR")) {
833
- auto idxpr = new IndexProductResidualQuantizer();
834
- read_index_header(idxpr, f);
835
- read_ProductResidualQuantizer(&idxpr->prq, f, io_flags);
1624
+ auto idxpr = std::make_unique<IndexProductResidualQuantizer>();
1625
+ read_index_header(*idxpr, f);
1626
+ read_ProductResidualQuantizer(idxpr->prq, f, io_flags);
1627
+ validate_aq_dimension_match(
1628
+ idxpr->prq, idxpr->d, "IndexProductResidualQuantizer");
836
1629
  READ1(idxpr->code_size);
1630
+ validate_code_size_match(
1631
+ idxpr->code_size,
1632
+ idxpr->prq.code_size,
1633
+ "IndexProductResidualQuantizer");
837
1634
  read_vector(idxpr->codes, f);
838
- idx = idxpr;
1635
+ FAISS_THROW_IF_NOT(
1636
+ idxpr->codes.size() == idxpr->ntotal * idxpr->code_size);
1637
+ idx = std::move(idxpr);
839
1638
  } else if (h == fourcc("IxPL")) {
840
- auto idxpl = new IndexProductLocalSearchQuantizer();
841
- read_index_header(idxpl, f);
842
- read_ProductLocalSearchQuantizer(&idxpl->plsq, f);
1639
+ auto idxpl = std::make_unique<IndexProductLocalSearchQuantizer>();
1640
+ read_index_header(*idxpl, f);
1641
+ read_ProductLocalSearchQuantizer(idxpl->plsq, f);
1642
+ validate_aq_dimension_match(
1643
+ idxpl->plsq, idxpl->d, "IndexProductLocalSearchQuantizer");
843
1644
  READ1(idxpl->code_size);
1645
+ validate_code_size_match(
1646
+ idxpl->code_size,
1647
+ idxpl->plsq.code_size,
1648
+ "IndexProductLocalSearchQuantizer");
844
1649
  read_vector(idxpl->codes, f);
845
- idx = idxpl;
1650
+ FAISS_THROW_IF_NOT(
1651
+ idxpl->codes.size() == idxpl->ntotal * idxpl->code_size);
1652
+ idx = std::move(idxpl);
846
1653
  } else if (h == fourcc("ImRQ")) {
847
- ResidualCoarseQuantizer* idxr = new ResidualCoarseQuantizer();
848
- read_index_header(idxr, f);
849
- read_ResidualQuantizer(&idxr->rq, f, io_flags);
1654
+ auto idxr = std::make_unique<ResidualCoarseQuantizer>();
1655
+ read_index_header(*idxr, f);
1656
+ read_ResidualQuantizer(idxr->rq, f, io_flags);
1657
+ validate_aq_dimension_match(
1658
+ idxr->rq, idxr->d, "ResidualCoarseQuantizer");
850
1659
  READ1(idxr->beam_factor);
851
1660
  if (io_flags & IO_FLAG_SKIP_PRECOMPUTE_TABLE) {
852
1661
  // then we force the beam factor to -1
853
1662
  // which skips the table precomputation.
854
1663
  idxr->beam_factor = -1;
855
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
+ }
856
1695
  idxr->set_beam_factor(idxr->beam_factor);
857
- idx = idxr;
1696
+ idx = std::move(idxr);
858
1697
  } else if (
859
1698
  h == fourcc("ILfs") || h == fourcc("IRfs") || h == fourcc("IPRf") ||
860
1699
  h == fourcc("IPLf")) {
@@ -862,34 +1701,38 @@ Index* read_index(IOReader* f, int io_flags) {
862
1701
  bool is_RQ = h == fourcc("IRfs");
863
1702
  bool is_PLSQ = h == fourcc("IPLf");
864
1703
 
865
- IndexAdditiveQuantizerFastScan* idxaqfs;
1704
+ std::unique_ptr<IndexAdditiveQuantizerFastScan> idxaqfs;
866
1705
  if (is_LSQ) {
867
- idxaqfs = new IndexLocalSearchQuantizerFastScan();
1706
+ idxaqfs = std::make_unique<IndexLocalSearchQuantizerFastScan>();
868
1707
  } else if (is_RQ) {
869
- idxaqfs = new IndexResidualQuantizerFastScan();
1708
+ idxaqfs = std::make_unique<IndexResidualQuantizerFastScan>();
870
1709
  } else if (is_PLSQ) {
871
- idxaqfs = new IndexProductLocalSearchQuantizerFastScan();
1710
+ idxaqfs = std::make_unique<
1711
+ IndexProductLocalSearchQuantizerFastScan>();
872
1712
  } else {
873
- idxaqfs = new IndexProductResidualQuantizerFastScan();
1713
+ idxaqfs = std::make_unique<IndexProductResidualQuantizerFastScan>();
874
1714
  }
875
- read_index_header(idxaqfs, f);
1715
+ read_index_header(*idxaqfs, f);
876
1716
 
877
1717
  if (is_LSQ) {
878
- read_LocalSearchQuantizer((LocalSearchQuantizer*)idxaqfs->aq, f);
1718
+ read_LocalSearchQuantizer(*(LocalSearchQuantizer*)idxaqfs->aq, f);
879
1719
  } else if (is_RQ) {
880
1720
  read_ResidualQuantizer(
881
- (ResidualQuantizer*)idxaqfs->aq, f, io_flags);
1721
+ *(ResidualQuantizer*)idxaqfs->aq, f, io_flags);
882
1722
  } else if (is_PLSQ) {
883
1723
  read_ProductLocalSearchQuantizer(
884
- (ProductLocalSearchQuantizer*)idxaqfs->aq, f);
1724
+ *(ProductLocalSearchQuantizer*)idxaqfs->aq, f);
885
1725
  } else {
886
1726
  read_ProductResidualQuantizer(
887
- (ProductResidualQuantizer*)idxaqfs->aq, f, io_flags);
1727
+ *(ProductResidualQuantizer*)idxaqfs->aq, f, io_flags);
888
1728
  }
1729
+ validate_aq_dimension_match(
1730
+ *idxaqfs->aq, idxaqfs->d, "IndexAdditiveQuantizerFastScan");
889
1731
 
890
1732
  READ1(idxaqfs->implem);
891
1733
  READ1(idxaqfs->bbs);
892
1734
  READ1(idxaqfs->qbs);
1735
+ FAISS_THROW_IF_NOT_MSG(idxaqfs->qbs >= 0, "qbs must be non-negative");
893
1736
 
894
1737
  READ1(idxaqfs->M);
895
1738
  READ1(idxaqfs->nbits);
@@ -903,7 +1746,15 @@ Index* read_index(IOReader* f, int io_flags) {
903
1746
  READ1(idxaqfs->max_train_points);
904
1747
 
905
1748
  READVECTOR(idxaqfs->codes);
906
- idx = idxaqfs;
1749
+
1750
+ validate_fastscan_fields(
1751
+ idxaqfs->M,
1752
+ idxaqfs->M2,
1753
+ idxaqfs->ksub,
1754
+ idxaqfs->bbs,
1755
+ "IndexAdditiveQuantizerFastScan");
1756
+
1757
+ idx = std::move(idxaqfs);
907
1758
  } else if (
908
1759
  h == fourcc("IVLf") || h == fourcc("IVRf") || h == fourcc("NPLf") ||
909
1760
  h == fourcc("NPRf")) {
@@ -911,34 +1762,40 @@ Index* read_index(IOReader* f, int io_flags) {
911
1762
  bool is_RQ = h == fourcc("IVRf");
912
1763
  bool is_PLSQ = h == fourcc("NPLf");
913
1764
 
914
- IndexIVFAdditiveQuantizerFastScan* ivaqfs;
1765
+ std::unique_ptr<IndexIVFAdditiveQuantizerFastScan> ivaqfs;
915
1766
  if (is_LSQ) {
916
- ivaqfs = new IndexIVFLocalSearchQuantizerFastScan();
1767
+ ivaqfs = std::make_unique<IndexIVFLocalSearchQuantizerFastScan>();
917
1768
  } else if (is_RQ) {
918
- ivaqfs = new IndexIVFResidualQuantizerFastScan();
1769
+ ivaqfs = std::make_unique<IndexIVFResidualQuantizerFastScan>();
919
1770
  } else if (is_PLSQ) {
920
- ivaqfs = new IndexIVFProductLocalSearchQuantizerFastScan();
1771
+ ivaqfs = std::make_unique<
1772
+ IndexIVFProductLocalSearchQuantizerFastScan>();
921
1773
  } else {
922
- ivaqfs = new IndexIVFProductResidualQuantizerFastScan();
1774
+ ivaqfs = std::make_unique<
1775
+ IndexIVFProductResidualQuantizerFastScan>();
923
1776
  }
924
- read_ivf_header(ivaqfs, f);
1777
+ read_ivf_header(ivaqfs.get(), f);
925
1778
 
926
1779
  if (is_LSQ) {
927
- read_LocalSearchQuantizer((LocalSearchQuantizer*)ivaqfs->aq, f);
1780
+ read_LocalSearchQuantizer(*(LocalSearchQuantizer*)ivaqfs->aq, f);
928
1781
  } else if (is_RQ) {
929
- read_ResidualQuantizer((ResidualQuantizer*)ivaqfs->aq, f, io_flags);
1782
+ read_ResidualQuantizer(
1783
+ *(ResidualQuantizer*)ivaqfs->aq, f, io_flags);
930
1784
  } else if (is_PLSQ) {
931
1785
  read_ProductLocalSearchQuantizer(
932
- (ProductLocalSearchQuantizer*)ivaqfs->aq, f);
1786
+ *(ProductLocalSearchQuantizer*)ivaqfs->aq, f);
933
1787
  } else {
934
1788
  read_ProductResidualQuantizer(
935
- (ProductResidualQuantizer*)ivaqfs->aq, f, io_flags);
1789
+ *(ProductResidualQuantizer*)ivaqfs->aq, f, io_flags);
936
1790
  }
1791
+ validate_aq_dimension_match(
1792
+ *ivaqfs->aq, ivaqfs->d, "IndexIVFAdditiveQuantizerFastScan");
937
1793
 
938
1794
  READ1(ivaqfs->by_residual);
939
1795
  READ1(ivaqfs->implem);
940
1796
  READ1(ivaqfs->bbs);
941
1797
  READ1(ivaqfs->qbs);
1798
+ FAISS_THROW_IF_NOT_MSG(ivaqfs->qbs >= 0, "qbs must be non-negative");
942
1799
 
943
1800
  READ1(ivaqfs->M);
944
1801
  READ1(ivaqfs->nbits);
@@ -951,15 +1808,23 @@ Index* read_index(IOReader* f, int io_flags) {
951
1808
  READ1(ivaqfs->norm_scale);
952
1809
  READ1(ivaqfs->max_train_points);
953
1810
 
954
- read_InvertedLists(ivaqfs, f, io_flags);
1811
+ read_InvertedLists(*ivaqfs, f, io_flags);
955
1812
  ivaqfs->init_code_packer();
956
- idx = ivaqfs;
1813
+
1814
+ validate_fastscan_fields(
1815
+ ivaqfs->M,
1816
+ ivaqfs->M2,
1817
+ ivaqfs->ksub,
1818
+ ivaqfs->bbs,
1819
+ "IndexIVFAdditiveQuantizerFastScan");
1820
+
1821
+ idx = std::move(ivaqfs);
957
1822
  } else if (h == fourcc("IvFl") || h == fourcc("IvFL")) { // legacy
958
- IndexIVFFlat* ivfl = new IndexIVFFlat();
1823
+ auto ivfl = std::make_unique<IndexIVFFlat>();
959
1824
  std::vector<std::vector<idx_t>> ids;
960
- read_ivf_header(ivfl, f, &ids);
1825
+ read_ivf_header(ivfl.get(), f, &ids);
961
1826
  ivfl->code_size = ivfl->d * sizeof(float);
962
- ArrayInvertedLists* ail = set_array_invlist(ivfl, ids);
1827
+ ArrayInvertedLists* ail = set_array_invlist(ivfl.get(), ids);
963
1828
 
964
1829
  if (h == fourcc("IvFL")) {
965
1830
  for (size_t i = 0; i < ivfl->nlist; i++) {
@@ -973,109 +1838,170 @@ Index* read_index(IOReader* f, int io_flags) {
973
1838
  memcpy(ail->codes[i].data(), vec.data(), ail->codes[i].size());
974
1839
  }
975
1840
  }
976
- idx = ivfl;
1841
+ idx = std::move(ivfl);
977
1842
  } else if (h == fourcc("IwFd")) {
978
- IndexIVFFlatDedup* ivfl = new IndexIVFFlatDedup();
979
- read_ivf_header(ivfl, f);
1843
+ auto ivfl = std::make_unique<IndexIVFFlatDedup>();
1844
+ read_ivf_header(ivfl.get(), f);
980
1845
  ivfl->code_size = ivfl->d * sizeof(float);
981
1846
  {
982
1847
  std::vector<idx_t> tab;
983
1848
  READVECTOR(tab);
984
- for (long i = 0; i < tab.size(); i += 2) {
1849
+ FAISS_THROW_IF_NOT_FMT(
1850
+ tab.size() % 2 == 0,
1851
+ "invalid IVFFlatDedup instances table size: %zd "
1852
+ "(must be even)",
1853
+ tab.size());
1854
+ for (size_t i = 0; i < tab.size(); i += 2) {
985
1855
  std::pair<idx_t, idx_t> pair(tab[i], tab[i + 1]);
986
1856
  ivfl->instances.insert(pair);
987
1857
  }
988
1858
  }
989
- read_InvertedLists(ivfl, f, io_flags);
990
- idx = ivfl;
1859
+ read_InvertedLists(*ivfl, f, io_flags);
1860
+ idx = std::move(ivfl);
991
1861
  } else if (h == fourcc("IwPn")) {
992
- IndexIVFFlatPanorama* ivfp = new IndexIVFFlatPanorama();
993
- read_ivf_header(ivfp, f);
1862
+ auto ivfp = std::make_unique<IndexIVFFlatPanorama>();
1863
+ read_ivf_header(ivfp.get(), f);
1864
+ ivfp->code_size = ivfp->d * sizeof(float);
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);
994
1872
  ivfp->code_size = ivfp->d * sizeof(float);
995
1873
  READ1(ivfp->n_levels);
996
- read_InvertedLists(ivfp, f, io_flags);
997
- idx = ivfp;
1874
+ READ1(ivfp->batch_size);
1875
+ read_InvertedLists(*ivfp, f, io_flags);
1876
+ idx = std::move(ivfp);
998
1877
  } else if (h == fourcc("IwFl")) {
999
- IndexIVFFlat* ivfl = new IndexIVFFlat();
1000
- read_ivf_header(ivfl, f);
1878
+ auto ivfl = std::make_unique<IndexIVFFlat>();
1879
+ read_ivf_header(ivfl.get(), f);
1001
1880
  ivfl->code_size = ivfl->d * sizeof(float);
1002
- read_InvertedLists(ivfl, f, io_flags);
1003
- idx = ivfl;
1881
+ read_InvertedLists(*ivfl, f, io_flags);
1882
+ idx = std::move(ivfl);
1004
1883
  } else if (h == fourcc("IxSQ")) {
1005
- IndexScalarQuantizer* idxs = new IndexScalarQuantizer();
1006
- read_index_header(idxs, f);
1007
- read_ScalarQuantizer(&idxs->sq, f);
1884
+ auto idxs = std::make_unique<IndexScalarQuantizer>();
1885
+ read_index_header(*idxs, f);
1886
+ read_ScalarQuantizer(&idxs->sq, f, *idxs);
1008
1887
  read_vector(idxs->codes, f);
1009
1888
  idxs->code_size = idxs->sq.code_size;
1010
- idx = idxs;
1889
+ idx = std::move(idxs);
1011
1890
  } else if (h == fourcc("IxLa")) {
1012
1891
  int d, nsq, scale_nbit, r2;
1013
1892
  READ1(d);
1014
1893
  READ1(nsq);
1015
1894
  READ1(scale_nbit);
1016
1895
  READ1(r2);
1017
- IndexLattice* idxl = new IndexLattice(d, nsq, scale_nbit, r2);
1018
- read_index_header(idxl, f);
1896
+ FAISS_THROW_IF_NOT_FMT(
1897
+ nsq > 0, "invalid IndexLattice nsq %d (must be > 0)", nsq);
1898
+ FAISS_THROW_IF_NOT_FMT(
1899
+ d > 0 && d % nsq == 0,
1900
+ "invalid IndexLattice d=%d, nsq=%d (d must be > 0 and divisible by nsq)",
1901
+ d,
1902
+ nsq);
1903
+ FAISS_THROW_IF_NOT_FMT(
1904
+ r2 > 0, "invalid IndexLattice r2 %d (must be > 0)", r2);
1905
+ int dsq = d / nsq;
1906
+ FAISS_THROW_IF_NOT_FMT(
1907
+ dsq >= 2 && (dsq & (dsq - 1)) == 0,
1908
+ "invalid IndexLattice d=%d, nsq=%d: d/nsq=%d must be a power of 2 >= 2",
1909
+ d,
1910
+ nsq,
1911
+ dsq);
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
+ }
1934
+ read_index_header(*idxl, f);
1019
1935
  READVECTOR(idxl->trained);
1020
- idx = idxl;
1936
+ idx = std::move(idxl);
1021
1937
  } else if (h == fourcc("IvSQ")) { // legacy
1022
- IndexIVFScalarQuantizer* ivsc = new IndexIVFScalarQuantizer();
1938
+ auto ivsc = std::make_unique<IndexIVFScalarQuantizer>();
1023
1939
  std::vector<std::vector<idx_t>> ids;
1024
- read_ivf_header(ivsc, f, &ids);
1025
- read_ScalarQuantizer(&ivsc->sq, f);
1940
+ read_ivf_header(ivsc.get(), f, &ids);
1941
+ read_ScalarQuantizer(&ivsc->sq, f, *ivsc);
1026
1942
  READ1(ivsc->code_size);
1027
- ArrayInvertedLists* ail = set_array_invlist(ivsc, ids);
1028
- for (int i = 0; i < ivsc->nlist; i++)
1943
+ validate_code_size_match(
1944
+ ivsc->code_size, ivsc->sq.code_size, "IndexIVFScalarQuantizer");
1945
+ ArrayInvertedLists* ail = set_array_invlist(ivsc.get(), ids);
1946
+ for (size_t i = 0; i < ivsc->nlist; i++)
1029
1947
  READVECTOR(ail->codes[i]);
1030
- idx = ivsc;
1948
+ idx = std::move(ivsc);
1031
1949
  } else if (h == fourcc("IwSQ") || h == fourcc("IwSq")) {
1032
- IndexIVFScalarQuantizer* ivsc = new IndexIVFScalarQuantizer();
1033
- read_ivf_header(ivsc, f);
1034
- read_ScalarQuantizer(&ivsc->sq, f);
1950
+ auto ivsc = std::make_unique<IndexIVFScalarQuantizer>();
1951
+ read_ivf_header(ivsc.get(), f);
1952
+ read_ScalarQuantizer(&ivsc->sq, f, *ivsc);
1035
1953
  READ1(ivsc->code_size);
1954
+ validate_code_size_match(
1955
+ ivsc->code_size, ivsc->sq.code_size, "IndexIVFScalarQuantizer");
1036
1956
  if (h == fourcc("IwSQ")) {
1037
1957
  ivsc->by_residual = true;
1038
1958
  } else {
1039
1959
  READ1(ivsc->by_residual);
1040
1960
  }
1041
- read_InvertedLists(ivsc, f, io_flags);
1042
- idx = ivsc;
1961
+ read_InvertedLists(*ivsc, f, io_flags);
1962
+ idx = std::move(ivsc);
1043
1963
  } else if (
1044
1964
  h == fourcc("IwLS") || h == fourcc("IwRQ") || h == fourcc("IwPL") ||
1045
1965
  h == fourcc("IwPR")) {
1046
1966
  bool is_LSQ = h == fourcc("IwLS");
1047
1967
  bool is_RQ = h == fourcc("IwRQ");
1048
1968
  bool is_PLSQ = h == fourcc("IwPL");
1049
- IndexIVFAdditiveQuantizer* iva;
1969
+ std::unique_ptr<IndexIVFAdditiveQuantizer> iva;
1050
1970
  if (is_LSQ) {
1051
- iva = new IndexIVFLocalSearchQuantizer();
1971
+ iva = std::make_unique<IndexIVFLocalSearchQuantizer>();
1052
1972
  } else if (is_RQ) {
1053
- iva = new IndexIVFResidualQuantizer();
1973
+ iva = std::make_unique<IndexIVFResidualQuantizer>();
1054
1974
  } else if (is_PLSQ) {
1055
- iva = new IndexIVFProductLocalSearchQuantizer();
1975
+ iva = std::make_unique<IndexIVFProductLocalSearchQuantizer>();
1056
1976
  } else {
1057
- iva = new IndexIVFProductResidualQuantizer();
1977
+ iva = std::make_unique<IndexIVFProductResidualQuantizer>();
1058
1978
  }
1059
- read_ivf_header(iva, f);
1979
+ read_ivf_header(iva.get(), f);
1060
1980
  READ1(iva->code_size);
1061
1981
  if (is_LSQ) {
1062
- read_LocalSearchQuantizer((LocalSearchQuantizer*)iva->aq, f);
1982
+ read_LocalSearchQuantizer(*(LocalSearchQuantizer*)iva->aq, f);
1063
1983
  } else if (is_RQ) {
1064
- read_ResidualQuantizer((ResidualQuantizer*)iva->aq, f, io_flags);
1984
+ read_ResidualQuantizer(*(ResidualQuantizer*)iva->aq, f, io_flags);
1065
1985
  } else if (is_PLSQ) {
1066
1986
  read_ProductLocalSearchQuantizer(
1067
- (ProductLocalSearchQuantizer*)iva->aq, f);
1987
+ *(ProductLocalSearchQuantizer*)iva->aq, f);
1068
1988
  } else {
1069
1989
  read_ProductResidualQuantizer(
1070
- (ProductResidualQuantizer*)iva->aq, f, io_flags);
1990
+ *(ProductResidualQuantizer*)iva->aq, f, io_flags);
1071
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");
1072
1998
  READ1(iva->by_residual);
1073
1999
  READ1(iva->use_precomputed_table);
1074
- read_InvertedLists(iva, f, io_flags);
1075
- idx = iva;
2000
+ read_InvertedLists(*iva, f, io_flags);
2001
+ idx = std::move(iva);
1076
2002
  } else if (h == fourcc("IwSh")) {
1077
- IndexIVFSpectralHash* ivsp = new IndexIVFSpectralHash();
1078
- read_ivf_header(ivsp, f);
2003
+ auto ivsp = std::make_unique<IndexIVFSpectralHash>();
2004
+ read_ivf_header(ivsp.get(), f);
1079
2005
  ivsp->vt = read_VectorTransform(f);
1080
2006
  ivsp->own_fields = true;
1081
2007
  READ1(ivsp->nbit);
@@ -1084,132 +2010,217 @@ Index* read_index(IOReader* f, int io_flags) {
1084
2010
  READ1(ivsp->period);
1085
2011
  READ1(ivsp->threshold_type);
1086
2012
  READVECTOR(ivsp->trained);
1087
- read_InvertedLists(ivsp, f, io_flags);
1088
- idx = ivsp;
2013
+ read_InvertedLists(*ivsp, f, io_flags);
2014
+ idx = std::move(ivsp);
1089
2015
  } else if (
1090
2016
  h == fourcc("IvPQ") || h == fourcc("IvQR") || h == fourcc("IwPQ") ||
1091
2017
  h == fourcc("IwQR")) {
1092
2018
  idx = read_ivfpq(f, h, io_flags);
1093
2019
  } else if (h == fourcc("IwIQ")) {
1094
- auto* indep = new IndexIVFIndependentQuantizer();
2020
+ auto indep = std::make_unique<IndexIVFIndependentQuantizer>();
1095
2021
  indep->own_fields = true;
1096
- read_index_header(indep, f);
2022
+ read_index_header(*indep, f);
1097
2023
  indep->quantizer = read_index(f, io_flags);
1098
2024
  bool has_vt;
1099
2025
  READ1(has_vt);
1100
2026
  if (has_vt) {
1101
2027
  indep->vt = read_VectorTransform(f);
1102
2028
  }
1103
- indep->index_ivf = dynamic_cast<IndexIVF*>(read_index(f, io_flags));
2029
+ auto ivf_idx = read_index_up(f, io_flags);
2030
+ indep->index_ivf = dynamic_cast<IndexIVF*>(ivf_idx.get());
1104
2031
  FAISS_THROW_IF_NOT(indep->index_ivf);
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
+ }
1105
2045
  if (auto index_ivfpq = dynamic_cast<IndexIVFPQ*>(indep->index_ivf)) {
1106
2046
  READ1(index_ivfpq->use_precomputed_table);
1107
2047
  }
1108
- idx = indep;
2048
+ idx = std::move(indep);
1109
2049
  } else if (h == fourcc("IxPT")) {
1110
- IndexPreTransform* ixpt = new IndexPreTransform();
2050
+ auto ixpt = std::make_unique<IndexPreTransform>();
1111
2051
  ixpt->own_fields = true;
1112
- read_index_header(ixpt, f);
2052
+ read_index_header(*ixpt, f);
1113
2053
  int nt;
1114
2054
  if (read_old_fmt_hack == 2) {
1115
2055
  nt = 1;
1116
2056
  } else {
1117
2057
  READ1(nt);
1118
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");
1119
2065
  for (int i = 0; i < nt; i++) {
1120
2066
  ixpt->chain.push_back(read_VectorTransform(f));
1121
2067
  }
1122
2068
  ixpt->index = read_index(f, io_flags);
1123
- idx = ixpt;
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
+ }
2099
+ idx = std::move(ixpt);
1124
2100
  } else if (h == fourcc("Imiq")) {
1125
- MultiIndexQuantizer* imiq = new MultiIndexQuantizer();
1126
- read_index_header(imiq, f);
2101
+ auto imiq = std::make_unique<MultiIndexQuantizer>();
2102
+ read_index_header(*imiq, f);
1127
2103
  read_ProductQuantizer(&imiq->pq, f);
1128
- idx = imiq;
2104
+ idx = std::move(imiq);
1129
2105
  } else if (h == fourcc("IxRF") || h == fourcc("IxRP")) {
1130
- IndexRefine* idxrf = new IndexRefine();
1131
- read_index_header(idxrf, f);
1132
- idxrf->base_index = read_index(f, io_flags);
1133
- idxrf->refine_index = read_index(f, io_flags);
2106
+ auto idxrf = std::make_unique<IndexRefine>();
2107
+ read_index_header(*idxrf, f);
2108
+ auto base = read_index_up(f, io_flags);
2109
+ auto refine = read_index_up(f, io_flags);
1134
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);
1135
2117
  if (h == fourcc("IxRP")) {
1136
2118
  // then make a RefineFlatPanorama with it
1137
- IndexRefine* idxrf_old = idxrf;
1138
- idxrf = new IndexRefinePanorama();
1139
- *idxrf = *idxrf_old;
1140
- delete idxrf_old;
1141
- } else if (dynamic_cast<IndexFlat*>(idxrf->refine_index)) {
2119
+ auto idxrf_new = std::make_unique<IndexRefinePanorama>();
2120
+ static_cast<IndexRefine&>(*idxrf_new) = *idxrf;
2121
+ idxrf = std::move(idxrf_new);
2122
+ } else if (dynamic_cast<IndexFlat*>(refine.get())) {
1142
2123
  // then make a RefineFlat with it
1143
- IndexRefine* idxrf_old = idxrf;
1144
- idxrf = new IndexRefineFlat();
1145
- *idxrf = *idxrf_old;
1146
- delete idxrf_old;
2124
+ auto idxrf_new = std::make_unique<IndexRefineFlat>();
2125
+ static_cast<IndexRefine&>(*idxrf_new) = *idxrf;
2126
+ idxrf = std::move(idxrf_new);
1147
2127
  }
2128
+ idxrf->base_index = base.release();
2129
+ idxrf->refine_index = refine.release();
1148
2130
  idxrf->own_fields = true;
1149
2131
  idxrf->own_refine_index = true;
1150
- idx = idxrf;
2132
+ idx = std::move(idxrf);
1151
2133
  } else if (h == fourcc("IxMp") || h == fourcc("IxM2")) {
1152
2134
  bool is_map2 = h == fourcc("IxM2");
1153
- IndexIDMap* idxmap = is_map2 ? new IndexIDMap2() : new IndexIDMap();
1154
- read_index_header(idxmap, f);
2135
+ std::unique_ptr<IndexIDMap> idxmap = is_map2
2136
+ ? std::make_unique<IndexIDMap2>()
2137
+ : std::make_unique<IndexIDMap>();
2138
+ read_index_header(*idxmap, f);
1155
2139
  idxmap->index = read_index(f, io_flags);
1156
2140
  idxmap->own_fields = true;
1157
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);
1158
2154
  if (is_map2) {
1159
- static_cast<IndexIDMap2*>(idxmap)->construct_rev_map();
2155
+ static_cast<IndexIDMap2*>(idxmap.get())->construct_rev_map();
1160
2156
  }
1161
- idx = idxmap;
2157
+ idx = std::move(idxmap);
1162
2158
  } else if (h == fourcc("Ix2L")) {
1163
- Index2Layer* idxp = new Index2Layer();
1164
- read_index_header(idxp, f);
2159
+ auto idxp = std::make_unique<Index2Layer>();
2160
+ read_index_header(*idxp, f);
1165
2161
  idxp->q1.quantizer = read_index(f, io_flags);
2162
+ idxp->q1.own_fields = true;
1166
2163
  READ1(idxp->q1.nlist);
1167
2164
  READ1(idxp->q1.quantizer_trains_alone);
1168
2165
  read_ProductQuantizer(&idxp->pq, f);
1169
2166
  READ1(idxp->code_size_1);
1170
2167
  READ1(idxp->code_size_2);
1171
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");
1172
2177
  read_vector(idxp->codes, f);
1173
- idx = idxp;
2178
+ idx = std::move(idxp);
1174
2179
  } else if (
1175
2180
  h == fourcc("IHNf") || h == fourcc("IHNp") || h == fourcc("IHNs") ||
1176
2181
  h == fourcc("IHN2") || h == fourcc("IHNc") || h == fourcc("IHc2") ||
1177
2182
  h == fourcc("IHfP")) {
1178
- IndexHNSW* idxhnsw = nullptr;
2183
+ std::unique_ptr<IndexHNSW> idxhnsw;
1179
2184
  if (h == fourcc("IHNf")) {
1180
- idxhnsw = new IndexHNSWFlat();
2185
+ idxhnsw = std::make_unique<IndexHNSWFlat>();
1181
2186
  }
1182
2187
  if (h == fourcc("IHfP")) {
1183
- idxhnsw = new IndexHNSWFlatPanorama();
2188
+ idxhnsw = std::make_unique<IndexHNSWFlatPanorama>();
1184
2189
  }
1185
2190
  if (h == fourcc("IHNp")) {
1186
- idxhnsw = new IndexHNSWPQ();
2191
+ idxhnsw = std::make_unique<IndexHNSWPQ>();
1187
2192
  }
1188
2193
  if (h == fourcc("IHNs")) {
1189
- idxhnsw = new IndexHNSWSQ();
2194
+ idxhnsw = std::make_unique<IndexHNSWSQ>();
1190
2195
  }
1191
2196
  if (h == fourcc("IHN2")) {
1192
- idxhnsw = new IndexHNSW2Level();
2197
+ idxhnsw = std::make_unique<IndexHNSW2Level>();
1193
2198
  }
1194
2199
  if (h == fourcc("IHNc")) {
1195
- idxhnsw = new IndexHNSWCagra();
2200
+ idxhnsw = std::make_unique<IndexHNSWCagra>();
1196
2201
  }
1197
2202
  if (h == fourcc("IHc2")) {
1198
- idxhnsw = new IndexHNSWCagra();
2203
+ idxhnsw = std::make_unique<IndexHNSWCagra>();
1199
2204
  }
1200
- read_index_header(idxhnsw, f);
2205
+ read_index_header(*idxhnsw, f);
1201
2206
  if (h == fourcc("IHfP")) {
1202
- auto idx_panorama = dynamic_cast<IndexHNSWFlatPanorama*>(idxhnsw);
2207
+ auto idx_panorama =
2208
+ dynamic_cast<IndexHNSWFlatPanorama*>(idxhnsw.get());
2209
+ FAISS_THROW_IF_NOT_MSG(
2210
+ idx_panorama,
2211
+ "dynamic_cast to IndexHNSWFlatPanorama failed");
1203
2212
  size_t nlevels;
1204
2213
  READ1(nlevels);
1205
2214
  const_cast<size_t&>(idx_panorama->num_panorama_levels) = nlevels;
1206
- const_cast<size_t&>(idx_panorama->panorama_level_width) =
1207
- (idx_panorama->d + nlevels - 1) / nlevels;
2215
+ const_cast<Panorama&>(idx_panorama->pano) =
2216
+ Panorama(idx_panorama->d * sizeof(float), nlevels, 1);
1208
2217
  READVECTOR(idx_panorama->cum_sums);
1209
2218
  }
1210
2219
  if (h == fourcc("IHNc") || h == fourcc("IHc2")) {
1211
2220
  READ1(idxhnsw->keep_max_size_level0);
1212
- auto idx_hnsw_cagra = dynamic_cast<IndexHNSWCagra*>(idxhnsw);
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");
1213
2224
  READ1(idx_hnsw_cagra->base_level_only);
1214
2225
  READ1(idx_hnsw_cagra->num_base_level_search_entrypoints);
1215
2226
  if (h == fourcc("IHc2")) {
@@ -1218,51 +2229,127 @@ Index* read_index(IOReader* f, int io_flags) {
1218
2229
  idx_hnsw_cagra->set_numeric_type(faiss::Float32);
1219
2230
  }
1220
2231
  }
1221
- read_HNSW(&idxhnsw->hnsw, f);
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);
1222
2239
  idxhnsw->hnsw.is_panorama = (h == fourcc("IHfP"));
1223
2240
  idxhnsw->storage = read_index(f, io_flags);
1224
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
+ }
1225
2264
  if (h == fourcc("IHNp") && !(io_flags & IO_FLAG_PQ_SKIP_SDC_TABLE)) {
1226
- 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();
1227
2270
  }
1228
- idx = idxhnsw;
2271
+ idx = std::move(idxhnsw);
1229
2272
  } else if (
1230
2273
  h == fourcc("INSf") || h == fourcc("INSp") || h == fourcc("INSs")) {
1231
- IndexNSG* idxnsg;
2274
+ std::unique_ptr<IndexNSG> idxnsg;
1232
2275
  if (h == fourcc("INSf")) {
1233
- idxnsg = new IndexNSGFlat();
2276
+ idxnsg = std::make_unique<IndexNSGFlat>();
1234
2277
  }
1235
2278
  if (h == fourcc("INSp")) {
1236
- idxnsg = new IndexNSGPQ();
2279
+ idxnsg = std::make_unique<IndexNSGPQ>();
1237
2280
  }
1238
2281
  if (h == fourcc("INSs")) {
1239
- idxnsg = new IndexNSGSQ();
2282
+ idxnsg = std::make_unique<IndexNSGSQ>();
1240
2283
  }
1241
- read_index_header(idxnsg, f);
2284
+ read_index_header(*idxnsg, f);
1242
2285
  READ1(idxnsg->GK);
1243
2286
  READ1(idxnsg->build_type);
1244
2287
  READ1(idxnsg->nndescent_S);
1245
2288
  READ1(idxnsg->nndescent_R);
1246
2289
  READ1(idxnsg->nndescent_L);
1247
2290
  READ1(idxnsg->nndescent_iter);
1248
- read_NSG(&idxnsg->nsg, f);
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
+ }
1249
2300
  idxnsg->storage = read_index(f, io_flags);
1250
2301
  idxnsg->own_fields = true;
1251
- idx = idxnsg;
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
+ }
2315
+ idx = std::move(idxnsg);
1252
2316
  } else if (h == fourcc("INNf")) {
1253
- IndexNNDescent* idxnnd = new IndexNNDescentFlat();
1254
- read_index_header(idxnnd, f);
1255
- read_NNDescent(&idxnnd->nndescent, f);
2317
+ auto idxnnd = std::make_unique<IndexNNDescentFlat>();
2318
+ read_index_header(*idxnnd, f);
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
+ }
1256
2328
  idxnnd->storage = read_index(f, io_flags);
1257
2329
  idxnnd->own_fields = true;
1258
- idx = idxnnd;
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
+ }
2344
+ idx = std::move(idxnnd);
1259
2345
  } else if (h == fourcc("IPfs")) {
1260
- IndexPQFastScan* idxpqfs = new IndexPQFastScan();
1261
- read_index_header(idxpqfs, f);
2346
+ auto idxpqfs = std::make_unique<IndexPQFastScan>();
2347
+ read_index_header(*idxpqfs, f);
1262
2348
  read_ProductQuantizer(&idxpqfs->pq, f);
1263
2349
  READ1(idxpqfs->implem);
1264
2350
  READ1(idxpqfs->bbs);
1265
2351
  READ1(idxpqfs->qbs);
2352
+ FAISS_THROW_IF_NOT_MSG(idxpqfs->qbs >= 0, "qbs must be non-negative");
1266
2353
  READ1(idxpqfs->ntotal2);
1267
2354
  READ1(idxpqfs->M2);
1268
2355
  READVECTOR(idxpqfs->codes);
@@ -1273,11 +2360,18 @@ Index* read_index(IOReader* f, int io_flags) {
1273
2360
  idxpqfs->ksub = (1 << pq.nbits);
1274
2361
  idxpqfs->code_size = pq.code_size;
1275
2362
 
1276
- idx = idxpqfs;
2363
+ validate_fastscan_fields(
2364
+ idxpqfs->M,
2365
+ idxpqfs->M2,
2366
+ idxpqfs->ksub,
2367
+ idxpqfs->bbs,
2368
+ "IndexPQFastScan");
2369
+
2370
+ idx = std::move(idxpqfs);
1277
2371
 
1278
2372
  } else if (h == fourcc("IwPf")) {
1279
- IndexIVFPQFastScan* ivpq = new IndexIVFPQFastScan();
1280
- read_ivf_header(ivpq, f);
2373
+ auto ivpq = std::make_unique<IndexIVFPQFastScan>();
2374
+ read_ivf_header(ivpq.get(), f);
1281
2375
  READ1(ivpq->by_residual);
1282
2376
  READ1(ivpq->code_size);
1283
2377
  READ1(ivpq->bbs);
@@ -1285,7 +2379,7 @@ Index* read_index(IOReader* f, int io_flags) {
1285
2379
  READ1(ivpq->implem);
1286
2380
  READ1(ivpq->qbs2);
1287
2381
  read_ProductQuantizer(&ivpq->pq, f);
1288
- read_InvertedLists(ivpq, f, io_flags);
2382
+ read_InvertedLists(*ivpq, f, io_flags);
1289
2383
  ivpq->precompute_table();
1290
2384
 
1291
2385
  const auto& pq = ivpq->pq;
@@ -1295,30 +2389,45 @@ Index* read_index(IOReader* f, int io_flags) {
1295
2389
  ivpq->code_size = pq.code_size;
1296
2390
  ivpq->init_code_packer();
1297
2391
 
1298
- idx = ivpq;
2392
+ validate_fastscan_fields(
2393
+ ivpq->M, ivpq->M2, ivpq->ksub, ivpq->bbs, "IndexIVFPQFastScan");
2394
+
2395
+ idx = std::move(ivpq);
1299
2396
  } else if (h == fourcc("IRMf")) {
1300
- IndexRowwiseMinMax* imm = new IndexRowwiseMinMax();
1301
- read_index_header(imm, f);
2397
+ auto imm = std::make_unique<IndexRowwiseMinMax>();
2398
+ read_index_header(*imm, f);
1302
2399
 
1303
2400
  imm->index = read_index(f, io_flags);
1304
2401
  imm->own_fields = true;
1305
2402
 
1306
- idx = imm;
2403
+ idx = std::move(imm);
1307
2404
  } else if (h == fourcc("IRMh")) {
1308
- IndexRowwiseMinMaxFP16* imm = new IndexRowwiseMinMaxFP16();
1309
- read_index_header(imm, f);
2405
+ auto imm = std::make_unique<IndexRowwiseMinMaxFP16>();
2406
+ read_index_header(*imm, f);
1310
2407
 
1311
2408
  imm->index = read_index(f, io_flags);
1312
2409
  imm->own_fields = true;
1313
2410
 
1314
- idx = imm;
1315
- } else if (h == fourcc("Irfs")) {
1316
- IndexRaBitQFastScan* idxqfs = new IndexRaBitQFastScan();
1317
- read_index_header(idxqfs, f);
1318
- read_RaBitQuantizer(&idxqfs->rabitq, f, true);
2411
+ idx = std::move(imm);
2412
+ } else if (h == fourcc("Irfn") || h == fourcc("Irfs")) {
2413
+ // Irfn = new format (aux data embedded in SIMD blocks)
2414
+ // Irfs = legacy format (flat_storage separate, needs migration)
2415
+ const bool is_legacy = (h == fourcc("Irfs"));
2416
+
2417
+ auto idxqfs = std::make_unique<IndexRaBitQFastScan>();
2418
+ read_index_header(*idxqfs, f);
2419
+ read_RaBitQuantizer(idxqfs->rabitq, f, idxqfs->d, true);
1319
2420
  READVECTOR(idxqfs->center);
1320
2421
  READ1(idxqfs->qb);
1321
- READVECTOR(idxqfs->flat_storage);
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);
2426
+
2427
+ std::vector<uint8_t> legacy_flat_storage;
2428
+ if (is_legacy) {
2429
+ READVECTOR(legacy_flat_storage);
2430
+ }
1322
2431
 
1323
2432
  READ1(idxqfs->bbs);
1324
2433
  READ1(idxqfs->ntotal2);
@@ -1331,74 +2440,131 @@ Index* read_index(IOReader* f, int io_flags) {
1331
2440
  idxqfs->nbits = nbits_fastscan;
1332
2441
  idxqfs->ksub = (1 << nbits_fastscan);
1333
2442
 
2443
+ validate_fastscan_fields(
2444
+ idxqfs->M,
2445
+ idxqfs->M2,
2446
+ idxqfs->ksub,
2447
+ idxqfs->bbs,
2448
+ "IndexRaBitQFastScan");
2449
+
1334
2450
  READVECTOR(idxqfs->codes);
1335
- idx = idxqfs;
2451
+
2452
+ if (is_legacy) {
2453
+ const size_t storage_size =
2454
+ rabitq_utils::compute_per_vector_storage_size(
2455
+ idxqfs->rabitq.nb_bits, idxqfs->d);
2456
+
2457
+ FAISS_THROW_IF_NOT_MSG(
2458
+ legacy_flat_storage.size() ==
2459
+ static_cast<size_t>(idxqfs->ntotal) * storage_size,
2460
+ "legacy flat_storage size mismatch during migration");
2461
+
2462
+ rabitq_utils::populate_block_aux_from_flat_storage(
2463
+ legacy_flat_storage,
2464
+ idxqfs->codes,
2465
+ static_cast<size_t>(idxqfs->ntotal),
2466
+ idxqfs->bbs,
2467
+ idxqfs->M2,
2468
+ ((idxqfs->M2 + 1) / 2) * idxqfs->bbs,
2469
+ idxqfs->get_block_stride(),
2470
+ storage_size);
2471
+ }
2472
+
2473
+ idx = std::move(idxqfs);
1336
2474
  } else if (h == fourcc("Ixrq")) {
1337
- IndexRaBitQ* idxq = new IndexRaBitQ();
1338
- read_index_header(idxq, f);
1339
- read_RaBitQuantizer(&idxq->rabitq, f, false);
2475
+ // Ixrq = original single-bit format
2476
+ auto idxq = std::make_unique<IndexRaBitQ>();
2477
+ read_index_header(*idxq, f);
2478
+ read_RaBitQuantizer(idxq->rabitq, f, idxq->d, false);
1340
2479
  READVECTOR(idxq->codes);
1341
2480
  READVECTOR(idxq->center);
1342
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);
1343
2488
 
1344
2489
  // rabitq.nb_bits is already set to 1 by read_RaBitQuantizer
1345
2490
  idxq->code_size = idxq->rabitq.code_size;
1346
- idx = idxq;
2491
+ idx = std::move(idxq);
1347
2492
  } else if (h == fourcc("Ixrr")) {
1348
2493
  // Ixrr = multi-bit format (new)
1349
- IndexRaBitQ* idxq = new IndexRaBitQ();
1350
- read_index_header(idxq, f);
1351
- read_RaBitQuantizer(&idxq->rabitq, f, true); // Reads nb_bits from file
2494
+ auto idxq = std::make_unique<IndexRaBitQ>();
2495
+ read_index_header(*idxq, f);
2496
+ read_RaBitQuantizer(
2497
+ idxq->rabitq, f, idxq->d, true); // Reads nb_bits from file
1352
2498
  READVECTOR(idxq->codes);
1353
2499
  READVECTOR(idxq->center);
1354
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);
1355
2507
 
1356
2508
  idxq->code_size = idxq->rabitq.code_size;
1357
- idx = idxq;
2509
+ idx = std::move(idxq);
1358
2510
  } else if (h == fourcc("Iwrq")) {
1359
- IndexIVFRaBitQ* ivrq = new IndexIVFRaBitQ();
1360
- read_ivf_header(ivrq, f);
1361
- read_RaBitQuantizer(&ivrq->rabitq, f, false);
2511
+ auto ivrq = std::make_unique<IndexIVFRaBitQ>();
2512
+ read_ivf_header(ivrq.get(), f);
2513
+ read_RaBitQuantizer(ivrq->rabitq, f, ivrq->d, false);
1362
2514
  READ1(ivrq->code_size);
1363
2515
  READ1(ivrq->by_residual);
1364
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);
1365
2523
 
1366
2524
  // rabitq.nb_bits is already set to 1 by read_RaBitQuantizer
1367
2525
  // Update rabitq to match nb_bits
1368
2526
  ivrq->rabitq.code_size =
1369
2527
  ivrq->rabitq.compute_code_size(ivrq->d, ivrq->rabitq.nb_bits);
1370
2528
  ivrq->code_size = ivrq->rabitq.code_size;
1371
- read_InvertedLists(ivrq, f, io_flags);
1372
- idx = ivrq;
2529
+ read_InvertedLists(*ivrq, f, io_flags);
2530
+ idx = std::move(ivrq);
1373
2531
  } else if (h == fourcc("Iwrr")) {
1374
2532
  // Iwrr = multi-bit format (new)
1375
- IndexIVFRaBitQ* ivrq = new IndexIVFRaBitQ();
1376
- read_ivf_header(ivrq, f);
1377
- read_RaBitQuantizer(&ivrq->rabitq, f, true); // Reads nb_bits from file
2533
+ auto ivrq = std::make_unique<IndexIVFRaBitQ>();
2534
+ read_ivf_header(ivrq.get(), f);
2535
+ read_RaBitQuantizer(
2536
+ ivrq->rabitq, f, ivrq->d, true); // Reads nb_bits from file
1378
2537
  READ1(ivrq->code_size);
1379
2538
  READ1(ivrq->by_residual);
1380
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);
1381
2546
 
1382
2547
  // Update rabitq to match nb_bits
1383
2548
  ivrq->rabitq.code_size =
1384
2549
  ivrq->rabitq.compute_code_size(ivrq->d, ivrq->rabitq.nb_bits);
1385
2550
  ivrq->code_size = ivrq->rabitq.code_size;
1386
- read_InvertedLists(ivrq, f, io_flags);
1387
- idx = ivrq;
2551
+ read_InvertedLists(*ivrq, f, io_flags);
2552
+ idx = std::move(ivrq);
1388
2553
  }
1389
2554
  #ifdef FAISS_ENABLE_SVS
1390
2555
  else if (
1391
- h == fourcc("ILVQ") || h == fourcc("ISVL") || h == fourcc("ISVD")) {
1392
- IndexSVSVamana* svs;
2556
+ h == fourcc("ILVQ") || h == fourcc("ISVL") || h == fourcc("ISVD") ||
2557
+ h == fourcc("ISV2")) {
2558
+ std::unique_ptr<IndexSVSVamana> svs;
1393
2559
  if (h == fourcc("ILVQ")) {
1394
- svs = new IndexSVSVamanaLVQ();
2560
+ svs = std::make_unique<IndexSVSVamanaLVQ>();
1395
2561
  } else if (h == fourcc("ISVL")) {
1396
- svs = new IndexSVSVamanaLeanVec();
1397
- } else if (h == fourcc("ISVD")) {
1398
- svs = new IndexSVSVamana();
2562
+ svs = std::make_unique<IndexSVSVamanaLeanVec>();
2563
+ } else if (h == fourcc("ISVD") || h == fourcc("ISV2")) {
2564
+ svs = std::make_unique<IndexSVSVamana>();
1399
2565
  }
1400
2566
 
1401
- read_index_header(svs, f);
2567
+ read_index_header(*svs, f);
1402
2568
  READ1(svs->graph_max_degree);
1403
2569
  READ1(svs->alpha);
1404
2570
  READ1(svs->search_window_size);
@@ -1407,15 +2573,21 @@ Index* read_index(IOReader* f, int io_flags) {
1407
2573
  READ1(svs->max_candidate_pool_size);
1408
2574
  READ1(svs->prune_to);
1409
2575
  READ1(svs->use_full_search_history);
1410
- READ1(svs->storage_kind);
2576
+
2577
+ svs->storage_kind = read_svs_storage_kind(f);
2578
+
1411
2579
  if (h == fourcc("ISVL")) {
1412
- READ1(dynamic_cast<IndexSVSVamanaLeanVec*>(svs)->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);
1413
2584
  }
1414
2585
 
1415
2586
  bool initialized;
1416
2587
  READ1(initialized);
1417
2588
  if (initialized) {
1418
- faiss::svs_io::ReaderStreambuf rbuf(f);
2589
+ faiss::svs_io::ReaderStreambuf rbuf(
2590
+ f, get_deserialization_vector_byte_limit());
1419
2591
  std::istream is(&rbuf);
1420
2592
  svs->deserialize_impl(is);
1421
2593
  }
@@ -1423,31 +2595,98 @@ Index* read_index(IOReader* f, int io_flags) {
1423
2595
  bool trained;
1424
2596
  READ1(trained);
1425
2597
  if (trained) {
1426
- faiss::svs_io::ReaderStreambuf rbuf(f);
2598
+ faiss::svs_io::ReaderStreambuf rbuf(
2599
+ f, get_deserialization_vector_byte_limit());
1427
2600
  std::istream is(&rbuf);
1428
- dynamic_cast<IndexSVSVamanaLeanVec*>(svs)
1429
- ->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);
1430
2606
  }
1431
2607
  }
1432
- idx = svs;
2608
+ if (h == fourcc("ISV2")) {
2609
+ READVECTOR(svs->stored_vectors);
2610
+ } else {
2611
+ svs->stored_vectors_valid = false;
2612
+ }
2613
+ idx = std::move(svs);
1433
2614
  } else if (h == fourcc("ISVF")) {
1434
- IndexSVSFlat* svs = new IndexSVSFlat();
1435
- read_index_header(svs, f);
2615
+ auto svs = std::make_unique<IndexSVSFlat>();
2616
+ read_index_header(*svs, f);
1436
2617
 
1437
2618
  bool initialized;
1438
2619
  READ1(initialized);
1439
2620
  if (initialized) {
1440
- faiss::svs_io::ReaderStreambuf rbuf(f);
2621
+ faiss::svs_io::ReaderStreambuf rbuf(
2622
+ f, get_deserialization_vector_byte_limit());
1441
2623
  std::istream is(&rbuf);
1442
2624
  svs->deserialize_impl(is);
1443
- idx = svs;
1444
2625
  }
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);
1445
2680
  }
1446
2681
  #endif // FAISS_ENABLE_SVS
1447
- else if (h == fourcc("Iwrf")) {
1448
- IndexIVFRaBitQFastScan* ivrqfs = new IndexIVFRaBitQFastScan();
1449
- read_ivf_header(ivrqfs, f);
1450
- read_RaBitQuantizer(&ivrqfs->rabitq, f);
2682
+ else if (h == fourcc("Iwrn") || h == fourcc("Iwrf")) {
2683
+ // Iwrn = new format (aux data embedded in SIMD blocks)
2684
+ // Iwrf = legacy format (flat_storage separate, needs migration)
2685
+ const bool is_legacy = (h == fourcc("Iwrf"));
2686
+
2687
+ auto ivrqfs = std::make_unique<IndexIVFRaBitQFastScan>();
2688
+ read_ivf_header(ivrqfs.get(), f);
2689
+ read_RaBitQuantizer(ivrqfs->rabitq, f, ivrqfs->d);
1451
2690
  READ1(ivrqfs->by_residual);
1452
2691
  READ1(ivrqfs->code_size);
1453
2692
  READ1(ivrqfs->bbs);
@@ -1455,8 +2694,16 @@ Index* read_index(IOReader* f, int io_flags) {
1455
2694
  READ1(ivrqfs->M2);
1456
2695
  READ1(ivrqfs->implem);
1457
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);
1458
2701
  READ1(ivrqfs->centered);
1459
- READVECTOR(ivrqfs->flat_storage);
2702
+
2703
+ std::vector<uint8_t> legacy_flat_storage;
2704
+ if (is_legacy) {
2705
+ READVECTOR(legacy_flat_storage);
2706
+ }
1460
2707
 
1461
2708
  // Initialize FastScan base class fields
1462
2709
  const size_t M_fastscan = (ivrqfs->d + 3) / 4;
@@ -1465,100 +2712,194 @@ Index* read_index(IOReader* f, int io_flags) {
1465
2712
  ivrqfs->nbits = nbits_fastscan;
1466
2713
  ivrqfs->ksub = (1 << nbits_fastscan);
1467
2714
 
1468
- read_InvertedLists(ivrqfs, f, io_flags);
2715
+ validate_fastscan_fields(
2716
+ ivrqfs->M,
2717
+ ivrqfs->M2,
2718
+ ivrqfs->ksub,
2719
+ ivrqfs->bbs,
2720
+ "IndexIVFRaBitQFastScan");
2721
+
2722
+ read_InvertedLists(*ivrqfs, f, io_flags);
1469
2723
  ivrqfs->init_code_packer();
1470
- idx = ivrqfs;
2724
+
2725
+ if (is_legacy) {
2726
+ auto* bil = dynamic_cast<BlockInvertedLists*>(ivrqfs->invlists);
2727
+ FAISS_THROW_IF_NOT(bil);
2728
+
2729
+ const size_t storage_size =
2730
+ rabitq_utils::compute_per_vector_storage_size(
2731
+ ivrqfs->rabitq.nb_bits, ivrqfs->d);
2732
+ const size_t new_block_stride = ivrqfs->get_block_stride();
2733
+
2734
+ for (size_t list_no = 0; list_no < ivrqfs->nlist; list_no++) {
2735
+ if (bil->list_size(list_no) == 0) {
2736
+ continue;
2737
+ }
2738
+ rabitq_utils::populate_block_aux_from_flat_storage(
2739
+ legacy_flat_storage,
2740
+ bil->codes[list_no],
2741
+ bil->list_size(list_no),
2742
+ ivrqfs->bbs,
2743
+ ivrqfs->M2,
2744
+ bil->block_size,
2745
+ new_block_stride,
2746
+ storage_size,
2747
+ bil->ids[list_no].data());
2748
+ }
2749
+
2750
+ if (bil->block_size < new_block_stride) {
2751
+ bil->block_size = new_block_stride;
2752
+ }
2753
+ }
2754
+
2755
+ idx = std::move(ivrqfs);
1471
2756
  } else {
1472
2757
  FAISS_THROW_FMT(
1473
2758
  "Index type 0x%08x (\"%s\") not recognized",
1474
2759
  h,
1475
2760
  fourcc_inv_printable(h).c_str());
1476
- idx = nullptr;
2761
+ idx.reset();
1477
2762
  }
1478
2763
  return idx;
1479
2764
  }
1480
2765
 
1481
- Index* read_index(FILE* f, int io_flags) {
2766
+ Index* read_index(IOReader* f, int io_flags) {
2767
+ return read_index_up(f, io_flags).release();
2768
+ }
2769
+
2770
+ std::unique_ptr<Index> read_index_up(FILE* f, int io_flags) {
1482
2771
  if ((io_flags & IO_FLAG_MMAP_IFC) == IO_FLAG_MMAP_IFC) {
1483
2772
  // enable mmap-supporting IOReader
1484
2773
  auto owner = std::make_shared<MmappedFileMappingOwner>(f);
1485
2774
  MappedFileIOReader reader(owner);
1486
- return read_index(&reader, io_flags);
2775
+ return read_index_up(&reader, io_flags);
1487
2776
  } else {
1488
2777
  FileIOReader reader(f);
1489
- return read_index(&reader, io_flags);
2778
+ return read_index_up(&reader, io_flags);
1490
2779
  }
1491
2780
  }
1492
2781
 
1493
- Index* read_index(const char* fname, int io_flags) {
2782
+ Index* read_index(FILE* f, int io_flags) {
2783
+ return read_index_up(f, io_flags).release();
2784
+ }
2785
+
2786
+ std::unique_ptr<Index> read_index_up(const char* fname, int io_flags) {
1494
2787
  if ((io_flags & IO_FLAG_MMAP_IFC) == IO_FLAG_MMAP_IFC) {
1495
2788
  // enable mmap-supporting IOReader
1496
2789
  auto owner = std::make_shared<MmappedFileMappingOwner>(fname);
1497
2790
  MappedFileIOReader reader(owner);
1498
- return read_index(&reader, io_flags);
2791
+ return read_index_up(&reader, io_flags);
1499
2792
  } else {
1500
2793
  FileIOReader reader(fname);
1501
- Index* idx = read_index(&reader, io_flags);
1502
- return idx;
2794
+ return read_index_up(&reader, io_flags);
1503
2795
  }
1504
2796
  }
1505
2797
 
1506
- VectorTransform* read_VectorTransform(const char* fname) {
2798
+ Index* read_index(const char* fname, int io_flags) {
2799
+ return read_index_up(fname, io_flags).release();
2800
+ }
2801
+
2802
+ std::unique_ptr<VectorTransform> read_VectorTransform_up(const char* fname) {
1507
2803
  FileIOReader reader(fname);
1508
- VectorTransform* vt = read_VectorTransform(&reader);
1509
- return vt;
2804
+ return read_VectorTransform_up(&reader);
2805
+ }
2806
+
2807
+ VectorTransform* read_VectorTransform(const char* fname) {
2808
+ return read_VectorTransform_up(fname).release();
1510
2809
  }
1511
2810
 
1512
2811
  /*************************************************************
1513
2812
  * Read binary indexes
1514
2813
  **************************************************************/
1515
2814
 
1516
- static void read_InvertedLists(IndexBinaryIVF* ivf, IOReader* f, int io_flags) {
1517
- InvertedLists* ils = read_InvertedLists(f, io_flags);
2815
+ static void read_InvertedLists(IndexBinaryIVF& ivf, IOReader* f, int io_flags) {
2816
+ auto ils = read_InvertedLists_up(f, io_flags);
1518
2817
  FAISS_THROW_IF_NOT(
1519
2818
  !ils ||
1520
- (ils->nlist == ivf->nlist && ils->code_size == ivf->code_size));
1521
- ivf->invlists = ils;
1522
- ivf->own_invlists = true;
2819
+ (ils->nlist == ivf.nlist && ils->code_size == ivf.code_size));
2820
+ ivf.invlists = ils.release();
2821
+ ivf.own_invlists = true;
1523
2822
  }
1524
2823
 
1525
- static void read_index_binary_header(IndexBinary* idx, IOReader* f) {
1526
- READ1(idx->d);
1527
- READ1(idx->code_size);
1528
- READ1(idx->ntotal);
1529
- READ1(idx->is_trained);
1530
- READ1(idx->metric_type);
1531
- idx->verbose = false;
2824
+ static void read_index_binary_header(IndexBinary& idx, IOReader* f) {
2825
+ READ1(idx.d);
2826
+ READ1(idx.code_size);
2827
+ READ1(idx.ntotal);
2828
+ READ1(idx.is_trained);
2829
+ int metric_type_int;
2830
+ READ1(metric_type_int);
2831
+ idx.metric_type = metric_type_from_int(metric_type_int);
2832
+ FAISS_THROW_IF_NOT_FMT(
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);
2841
+ FAISS_THROW_IF_NOT_FMT(
2842
+ idx.ntotal >= 0,
2843
+ "invalid binary index ntotal %" PRId64,
2844
+ (int64_t)idx.ntotal);
2845
+ idx.verbose = false;
1532
2846
  }
1533
2847
 
1534
2848
  static void read_binary_ivf_header(
1535
- IndexBinaryIVF* ivf,
2849
+ IndexBinaryIVF& ivf,
1536
2850
  IOReader* f,
1537
2851
  std::vector<std::vector<idx_t>>* ids = nullptr) {
1538
2852
  read_index_binary_header(ivf, f);
1539
- READ1(ivf->nlist);
1540
- READ1(ivf->nprobe);
1541
- ivf->quantizer = read_index_binary(f);
1542
- ivf->own_fields = true;
2853
+ READ1(ivf.nlist);
2854
+ FAISS_CHECK_DESERIALIZATION_LOOP_LIMIT(ivf.nlist, "nlist");
2855
+ READ1(ivf.nprobe);
2856
+ ivf.quantizer = read_index_binary(f);
2857
+ ivf.own_fields = true;
1543
2858
  if (ids) { // used in legacy "Iv" formats
1544
- ids->resize(ivf->nlist);
1545
- for (size_t i = 0; i < ivf->nlist; i++)
2859
+ ids->resize(ivf.nlist);
2860
+ for (size_t i = 0; i < ivf.nlist; i++)
1546
2861
  READVECTOR((*ids)[i]);
1547
2862
  }
1548
- read_direct_map(&ivf->direct_map, f);
2863
+ read_direct_map(&ivf.direct_map, f);
1549
2864
  }
1550
2865
 
1551
2866
  static void read_binary_hash_invlists(
1552
2867
  IndexBinaryHash::InvertedListMap& invlists,
1553
2868
  int b,
2869
+ size_t code_size,
1554
2870
  IOReader* f) {
1555
2871
  size_t sz;
1556
2872
  READ1(sz);
2873
+ FAISS_CHECK_DESERIALIZATION_LOOP_LIMIT(sz, "binary hash invlists sz");
1557
2874
  int il_nbit = 0;
1558
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
+ }
1559
2888
  // buffer for bitstrings
1560
- std::vector<uint8_t> buf((b + il_nbit) * sz);
2889
+ size_t bits_per_entry = (size_t)b + (size_t)il_nbit;
2890
+ size_t total_bits =
2891
+ mul_no_overflow(bits_per_entry, sz, "binary hash invlists");
2892
+ size_t needed_bytes = (total_bits + 7) / 8;
2893
+ std::vector<uint8_t> buf;
1561
2894
  READVECTOR(buf);
2895
+ FAISS_THROW_IF_NOT_FMT(
2896
+ buf.size() >= needed_bytes,
2897
+ "binary hash invlists: buffer size %zd < needed %zd bytes "
2898
+ "for %zd entries of %zd bits each",
2899
+ buf.size(),
2900
+ needed_bytes,
2901
+ sz,
2902
+ bits_per_entry);
1562
2903
  BitstringReader rd(buf.data(), buf.size());
1563
2904
  invlists.reserve(sz);
1564
2905
  for (size_t i = 0; i < sz; i++) {
@@ -1568,6 +2909,13 @@ static void read_binary_hash_invlists(
1568
2909
  READVECTOR(il.ids);
1569
2910
  FAISS_THROW_IF_NOT(il.ids.size() == ilsz);
1570
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);
1571
2919
  }
1572
2920
  }
1573
2921
 
@@ -1580,131 +2928,228 @@ static void read_binary_multi_hash_map(
1580
2928
  size_t sz;
1581
2929
  READ1(id_bits);
1582
2930
  READ1(sz);
2931
+ FAISS_CHECK_DESERIALIZATION_LOOP_LIMIT(sz, "multi hash map sz");
1583
2932
  std::vector<uint8_t> buf;
1584
2933
  READVECTOR(buf);
1585
- size_t nbit = (b + id_bits) * sz + ntotal * id_bits;
2934
+ size_t nbit = add_no_overflow(
2935
+ mul_no_overflow((size_t)(b + id_bits), sz, "multi hash map"),
2936
+ mul_no_overflow(ntotal, (size_t)id_bits, "multi hash map"),
2937
+ "multi hash map total bits");
1586
2938
  FAISS_THROW_IF_NOT(buf.size() == (nbit + 7) / 8);
1587
2939
  BitstringReader rd(buf.data(), buf.size());
1588
2940
  map.reserve(sz);
2941
+ size_t total_ids = 0;
1589
2942
  for (size_t i = 0; i < sz; i++) {
1590
2943
  uint64_t hash = rd.read(b);
1591
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;
1592
2954
  auto& il = map[hash];
1593
2955
  for (size_t j = 0; j < ilsz; j++) {
1594
- 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);
1595
2963
  }
1596
2964
  }
1597
2965
  }
1598
2966
 
1599
- IndexBinary* read_index_binary(IOReader* f, int io_flags) {
1600
- IndexBinary* idx = nullptr;
2967
+ std::unique_ptr<IndexBinary> read_index_binary_up(IOReader* f, int io_flags) {
2968
+ std::unique_ptr<IndexBinary> idx;
1601
2969
  uint32_t h;
1602
2970
  READ1(h);
1603
2971
  if (h == fourcc("IBxF")) {
1604
- IndexBinaryFlat* idxf = new IndexBinaryFlat();
1605
- read_index_binary_header(idxf, f);
2972
+ auto idxf = std::make_unique<IndexBinaryFlat>();
2973
+ read_index_binary_header(*idxf, f);
1606
2974
  read_vector(idxf->xb, f);
1607
2975
  FAISS_THROW_IF_NOT(idxf->xb.size() == idxf->ntotal * idxf->code_size);
1608
- // leak!
1609
- idx = idxf;
2976
+ idx = std::move(idxf);
1610
2977
  } else if (h == fourcc("IBwF")) {
1611
- IndexBinaryIVF* ivf = new IndexBinaryIVF();
1612
- read_binary_ivf_header(ivf, f);
1613
- read_InvertedLists(ivf, f, io_flags);
1614
- idx = ivf;
2978
+ auto ivf = std::make_unique<IndexBinaryIVF>();
2979
+ read_binary_ivf_header(*ivf, f);
2980
+ read_InvertedLists(*ivf, f, io_flags);
2981
+ idx = std::move(ivf);
1615
2982
  } else if (h == fourcc("IBFf")) {
1616
- IndexBinaryFromFloat* idxff = new IndexBinaryFromFloat();
1617
- read_index_binary_header(idxff, f);
2983
+ auto idxff = std::make_unique<IndexBinaryFromFloat>();
2984
+ read_index_binary_header(*idxff, f);
1618
2985
  idxff->own_fields = true;
1619
2986
  idxff->index = read_index(f, io_flags);
1620
- idx = idxff;
2987
+ idx = std::move(idxff);
1621
2988
  } else if (h == fourcc("IBHf")) {
1622
- IndexBinaryHNSW* idxhnsw = new IndexBinaryHNSW();
1623
- read_index_binary_header(idxhnsw, f);
1624
- read_HNSW(&idxhnsw->hnsw, f);
2989
+ auto idxhnsw = std::make_unique<IndexBinaryHNSW>();
2990
+ read_index_binary_header(*idxhnsw, f);
2991
+ read_HNSW(idxhnsw->hnsw, f);
1625
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);
1626
2998
  idxhnsw->storage = read_index_binary(f, io_flags);
1627
2999
  idxhnsw->own_fields = true;
1628
- idx = idxhnsw;
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");
3008
+ idx = std::move(idxhnsw);
1629
3009
  } else if (h == fourcc("IBHc")) {
1630
- IndexBinaryHNSWCagra* idxhnsw = new IndexBinaryHNSWCagra();
1631
- read_index_binary_header(idxhnsw, f);
3010
+ auto idxhnsw = std::make_unique<IndexBinaryHNSWCagra>();
3011
+ read_index_binary_header(*idxhnsw, f);
1632
3012
  READ1(idxhnsw->keep_max_size_level0);
1633
3013
  READ1(idxhnsw->base_level_only);
1634
3014
  READ1(idxhnsw->num_base_level_search_entrypoints);
1635
- read_HNSW(&idxhnsw->hnsw, f);
3015
+ read_HNSW(idxhnsw->hnsw, f);
1636
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);
1637
3022
  idxhnsw->storage = read_index_binary(f, io_flags);
1638
3023
  idxhnsw->own_fields = true;
1639
- idx = idxhnsw;
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");
3032
+ idx = std::move(idxhnsw);
1640
3033
  } else if (h == fourcc("IBMp") || h == fourcc("IBM2")) {
1641
3034
  bool is_map2 = h == fourcc("IBM2");
1642
- IndexBinaryIDMap* idxmap =
1643
- is_map2 ? new IndexBinaryIDMap2() : new IndexBinaryIDMap();
1644
- read_index_binary_header(idxmap, f);
3035
+ std::unique_ptr<IndexBinaryIDMap> idxmap = is_map2
3036
+ ? std::make_unique<IndexBinaryIDMap2>()
3037
+ : std::make_unique<IndexBinaryIDMap>();
3038
+ read_index_binary_header(*idxmap, f);
1645
3039
  idxmap->index = read_index_binary(f, io_flags);
1646
3040
  idxmap->own_fields = true;
1647
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);
1648
3054
  if (is_map2) {
1649
- static_cast<IndexBinaryIDMap2*>(idxmap)->construct_rev_map();
3055
+ static_cast<IndexBinaryIDMap2*>(idxmap.get())->construct_rev_map();
1650
3056
  }
1651
- idx = idxmap;
3057
+ idx = std::move(idxmap);
1652
3058
  } else if (h == fourcc("IBHh")) {
1653
- IndexBinaryHash* idxh = new IndexBinaryHash();
1654
- read_index_binary_header(idxh, f);
3059
+ auto idxh = std::make_unique<IndexBinaryHash>();
3060
+ read_index_binary_header(*idxh, f);
1655
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);
1656
3071
  READ1(idxh->nflip);
1657
- read_binary_hash_invlists(idxh->invlists, idxh->b, f);
1658
- idx = idxh;
3072
+ read_binary_hash_invlists(idxh->invlists, idxh->b, idxh->code_size, f);
3073
+ idx = std::move(idxh);
1659
3074
  } else if (h == fourcc("IBHm")) {
1660
- IndexBinaryMultiHash* idxmh = new IndexBinaryMultiHash();
1661
- read_index_binary_header(idxmh, f);
1662
- idxmh->storage = dynamic_cast<IndexBinaryFlat*>(read_index_binary(f));
1663
- FAISS_THROW_IF_NOT(
1664
- idxmh->storage && idxmh->storage->ntotal == idxmh->ntotal);
3075
+ auto idxmh = std::make_unique<IndexBinaryMultiHash>();
3076
+ read_index_binary_header(*idxmh, f);
3077
+ auto storage_idx = read_index_binary_up(f);
3078
+ auto* flat_ptr = dynamic_cast<IndexBinaryFlat*>(storage_idx.get());
3079
+ FAISS_THROW_IF_NOT(flat_ptr && flat_ptr->ntotal == idxmh->ntotal);
3080
+ idxmh->storage = flat_ptr;
3081
+ storage_idx.release();
1665
3082
  idxmh->own_fields = true;
1666
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);
1667
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);
1668
3101
  READ1(idxmh->nflip);
1669
3102
  idxmh->maps.resize(idxmh->nhash);
1670
3103
  for (int i = 0; i < idxmh->nhash; i++) {
1671
3104
  read_binary_multi_hash_map(
1672
3105
  idxmh->maps[i], idxmh->b, idxmh->ntotal, f);
1673
3106
  }
1674
- idx = idxmh;
3107
+ idx = std::move(idxmh);
1675
3108
  } else {
1676
3109
  FAISS_THROW_FMT(
1677
3110
  "Index type %08x (\"%s\") not recognized",
1678
3111
  h,
1679
3112
  fourcc_inv_printable(h).c_str());
1680
- idx = nullptr;
1681
3113
  }
1682
3114
  return idx;
1683
3115
  }
1684
3116
 
1685
- IndexBinary* read_index_binary(FILE* f, int io_flags) {
3117
+ IndexBinary* read_index_binary(IOReader* f, int io_flags) {
3118
+ return read_index_binary_up(f, io_flags).release();
3119
+ }
3120
+
3121
+ std::unique_ptr<IndexBinary> read_index_binary_up(FILE* f, int io_flags) {
1686
3122
  if ((io_flags & IO_FLAG_MMAP_IFC) == IO_FLAG_MMAP_IFC) {
1687
3123
  // enable mmap-supporting IOReader
1688
3124
  auto owner = std::make_shared<MmappedFileMappingOwner>(f);
1689
3125
  MappedFileIOReader reader(owner);
1690
- return read_index_binary(&reader, io_flags);
3126
+ return read_index_binary_up(&reader, io_flags);
1691
3127
  } else {
1692
3128
  FileIOReader reader(f);
1693
- return read_index_binary(&reader, io_flags);
3129
+ return read_index_binary_up(&reader, io_flags);
1694
3130
  }
1695
3131
  }
1696
3132
 
1697
- IndexBinary* read_index_binary(const char* fname, int io_flags) {
3133
+ IndexBinary* read_index_binary(FILE* f, int io_flags) {
3134
+ return read_index_binary_up(f, io_flags).release();
3135
+ }
3136
+
3137
+ std::unique_ptr<IndexBinary> read_index_binary_up(
3138
+ const char* fname,
3139
+ int io_flags) {
1698
3140
  if ((io_flags & IO_FLAG_MMAP_IFC) == IO_FLAG_MMAP_IFC) {
1699
3141
  // enable mmap-supporting IOReader
1700
3142
  auto owner = std::make_shared<MmappedFileMappingOwner>(fname);
1701
3143
  MappedFileIOReader reader(owner);
1702
- return read_index_binary(&reader, io_flags);
3144
+ return read_index_binary_up(&reader, io_flags);
1703
3145
  } else {
1704
3146
  FileIOReader reader(fname);
1705
- IndexBinary* idx = read_index_binary(&reader, io_flags);
1706
- return idx;
3147
+ return read_index_binary_up(&reader, io_flags);
1707
3148
  }
1708
3149
  }
1709
3150
 
3151
+ IndexBinary* read_index_binary(const char* fname, int io_flags) {
3152
+ return read_index_binary_up(fname, io_flags).release();
3153
+ }
3154
+
1710
3155
  } // namespace faiss