faiss 0.6.4 → 0.6.5

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 (228) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/ext/faiss/extconf.rb +1 -1
  4. data/lib/faiss/version.rb +1 -1
  5. data/vendor/faiss/faiss/AutoTune.cpp +175 -0
  6. data/vendor/faiss/faiss/Clustering.cpp +150 -60
  7. data/vendor/faiss/faiss/Clustering.h +29 -0
  8. data/vendor/faiss/faiss/IVFlib.cpp +99 -0
  9. data/vendor/faiss/faiss/IVFlib.h +15 -0
  10. data/vendor/faiss/faiss/Index.cpp +44 -0
  11. data/vendor/faiss/faiss/Index.h +19 -14
  12. data/vendor/faiss/faiss/IndexBinary.cpp +8 -0
  13. data/vendor/faiss/faiss/IndexBinary.h +8 -0
  14. data/vendor/faiss/faiss/IndexBinaryHNSW.cpp +82 -4
  15. data/vendor/faiss/faiss/IndexBinaryHNSW.h +19 -0
  16. data/vendor/faiss/faiss/IndexBinaryIVF.cpp +34 -0
  17. data/vendor/faiss/faiss/IndexFastScan.cpp +33 -17
  18. data/vendor/faiss/faiss/IndexFastScan.h +6 -3
  19. data/vendor/faiss/faiss/IndexFlat.cpp +56 -0
  20. data/vendor/faiss/faiss/IndexFlat.h +31 -0
  21. data/vendor/faiss/faiss/IndexFlatCodes.cpp +6 -1
  22. data/vendor/faiss/faiss/IndexHNSW.cpp +456 -10
  23. data/vendor/faiss/faiss/IndexHNSW.h +38 -0
  24. data/vendor/faiss/faiss/IndexIDMap.cpp +32 -0
  25. data/vendor/faiss/faiss/IndexIDMap.h +7 -0
  26. data/vendor/faiss/faiss/IndexIVF.cpp +281 -31
  27. data/vendor/faiss/faiss/IndexIVF.h +53 -1
  28. data/vendor/faiss/faiss/IndexIVFFastScan.cpp +46 -14
  29. data/vendor/faiss/faiss/IndexIVFFastScan.h +4 -2
  30. data/vendor/faiss/faiss/IndexIVFFlat.cpp +33 -0
  31. data/vendor/faiss/faiss/IndexIVFFlat.h +16 -0
  32. data/vendor/faiss/faiss/IndexIVFPQFastScan.cpp +1 -0
  33. data/vendor/faiss/faiss/IndexIVFRaBitQ.cpp +3 -20
  34. data/vendor/faiss/faiss/IndexIVFRaBitQFastScan.cpp +50 -14
  35. data/vendor/faiss/faiss/IndexIVFRaBitQFastScan.h +5 -2
  36. data/vendor/faiss/faiss/IndexIVFSQFastScan.cpp +855 -0
  37. data/vendor/faiss/faiss/IndexIVFSQFastScan.h +145 -0
  38. data/vendor/faiss/faiss/IndexNNDescent.cpp +0 -3
  39. data/vendor/faiss/faiss/IndexPiPNN.cpp +201 -0
  40. data/vendor/faiss/faiss/IndexPiPNN.h +83 -0
  41. data/vendor/faiss/faiss/IndexRaBitQFastScan.cpp +6 -3
  42. data/vendor/faiss/faiss/IndexRaBitQFastScan.h +10 -4
  43. data/vendor/faiss/faiss/IndexReplicas.cpp +20 -0
  44. data/vendor/faiss/faiss/IndexReplicas.h +9 -0
  45. data/vendor/faiss/faiss/IndexSQFastScan.cpp +648 -0
  46. data/vendor/faiss/faiss/IndexSQFastScan.h +129 -0
  47. data/vendor/faiss/faiss/IndexShards.cpp +10 -0
  48. data/vendor/faiss/faiss/IndexShards.h +5 -0
  49. data/vendor/faiss/faiss/SuperKMeans.cpp +57 -15
  50. data/vendor/faiss/faiss/SuperKMeans.h +9 -4
  51. data/vendor/faiss/faiss/VectorTransform.cpp +3 -2
  52. data/vendor/faiss/faiss/clone_index.cpp +24 -0
  53. data/vendor/faiss/faiss/gpu/Dataset.h +112 -0
  54. data/vendor/faiss/faiss/gpu/GpuCloner.cpp +16 -13
  55. data/vendor/faiss/faiss/gpu/GpuCloner.h +4 -0
  56. data/vendor/faiss/faiss/gpu/GpuClonerOptions.h +3 -0
  57. data/vendor/faiss/faiss/gpu/GpuIndex.h +4 -13
  58. data/vendor/faiss/faiss/gpu/GpuIndexBinaryCagra.h +26 -13
  59. data/vendor/faiss/faiss/gpu/GpuIndexCagra.h +45 -22
  60. data/vendor/faiss/faiss/gpu/GpuIndexFlat.h +11 -0
  61. data/vendor/faiss/faiss/gpu/GpuIndexIVF.h +6 -0
  62. data/vendor/faiss/faiss/gpu/test/TestGpuDatasetHeader.cpp +18 -0
  63. data/vendor/faiss/faiss/gpu/test/TestGpuIndexFlat.cpp +215 -0
  64. data/vendor/faiss/faiss/gpu/test/TestGpuIndexIVFFlat.cpp +98 -0
  65. data/vendor/faiss/faiss/gpu/utils/DeviceUtils.h +11 -0
  66. data/vendor/faiss/faiss/gpu/utils/StackDeviceMemory.cpp +1 -1
  67. data/vendor/faiss/faiss/gpu_metal/MetalCloner.h +11 -0
  68. data/vendor/faiss/faiss/gpu_metal/MetalDistance.h +92 -0
  69. data/vendor/faiss/faiss/gpu_metal/MetalIndex.h +4 -0
  70. data/vendor/faiss/faiss/gpu_metal/MetalIndexBinaryFlat.h +69 -0
  71. data/vendor/faiss/faiss/gpu_metal/MetalIndexIVFPQ.h +38 -0
  72. data/vendor/faiss/faiss/gpu_metal/MetalKernels.h +106 -0
  73. data/vendor/faiss/faiss/gpu_metal/MetalPythonBridge.h +10 -0
  74. data/vendor/faiss/faiss/impl/AuxIndexStructures.cpp +2 -0
  75. data/vendor/faiss/faiss/impl/AuxIndexStructures.h +1 -6
  76. data/vendor/faiss/faiss/impl/ClusteringHelpers.cpp +134 -45
  77. data/vendor/faiss/faiss/impl/ClusteringHelpers.h +36 -0
  78. data/vendor/faiss/faiss/impl/ClusteringInitialization.cpp +286 -136
  79. data/vendor/faiss/faiss/impl/DistanceComputer.cpp +63 -0
  80. data/vendor/faiss/faiss/impl/DistanceComputer.h +3 -29
  81. data/vendor/faiss/faiss/impl/HNSW.cpp +46 -10
  82. data/vendor/faiss/faiss/impl/HNSW.h +72 -0
  83. data/vendor/faiss/faiss/impl/LocalSearchQuantizer.cpp +6 -19
  84. data/vendor/faiss/faiss/impl/NNDescent.cpp +2 -1
  85. data/vendor/faiss/faiss/impl/NSG.cpp +3 -2
  86. data/vendor/faiss/faiss/impl/Panorama.h +3 -2
  87. data/vendor/faiss/faiss/impl/PiPNN.cpp +822 -0
  88. data/vendor/faiss/faiss/impl/PiPNN.h +122 -0
  89. data/vendor/faiss/faiss/impl/ProductQuantizer.cpp +54 -11
  90. data/vendor/faiss/faiss/impl/RaBitQUtils.h +1 -1
  91. data/vendor/faiss/faiss/impl/RaBitQuantizer.cpp +81 -57
  92. data/vendor/faiss/faiss/impl/RaBitQuantizer.h +15 -0
  93. data/vendor/faiss/faiss/impl/ScalarQuantizer.cpp +29 -0
  94. data/vendor/faiss/faiss/impl/ScalarQuantizer.h +1 -6
  95. data/vendor/faiss/faiss/impl/approx_topk/avx2.cpp +0 -4
  96. data/vendor/faiss/faiss/impl/approx_topk/neon.cpp +0 -4
  97. data/vendor/faiss/faiss/impl/binary_hamming/IndexBinaryHash_impl.h +5 -2
  98. data/vendor/faiss/faiss/impl/binary_hamming/avx2.cpp +0 -4
  99. data/vendor/faiss/faiss/impl/binary_hamming/avx512.cpp +0 -4
  100. data/vendor/faiss/faiss/impl/binary_hamming/neon.cpp +0 -4
  101. data/vendor/faiss/faiss/impl/binary_hamming/rvv.cpp +0 -4
  102. data/vendor/faiss/faiss/impl/expanded_scanners.h +88 -0
  103. data/vendor/faiss/faiss/impl/fast_scan/FastScanDistancePostProcessing.h +4 -0
  104. data/vendor/faiss/faiss/impl/fast_scan/accumulate_loops.h +61 -64
  105. data/vendor/faiss/faiss/impl/fast_scan/accumulate_loops_512.h +13 -14
  106. data/vendor/faiss/faiss/impl/fast_scan/decompose_qbs.h +17 -129
  107. data/vendor/faiss/faiss/impl/fast_scan/dispatching.h +25 -19
  108. data/vendor/faiss/faiss/impl/fast_scan/fast_scan.cpp +5 -0
  109. data/vendor/faiss/faiss/impl/fast_scan/impl-avx2.cpp +0 -4
  110. data/vendor/faiss/faiss/impl/fast_scan/impl-avx512.cpp +0 -4
  111. data/vendor/faiss/faiss/impl/fast_scan/impl-neon.cpp +0 -88
  112. data/vendor/faiss/faiss/impl/fast_scan/impl-riscv.cpp +0 -4
  113. data/vendor/faiss/faiss/impl/fast_scan/kernels_neon.h +155 -0
  114. data/vendor/faiss/faiss/impl/fast_scan/kernels_simd256.h +22 -1
  115. data/vendor/faiss/faiss/impl/fast_scan/kernels_simd512.h +3 -2
  116. data/vendor/faiss/faiss/impl/fast_scan/simd_result_handlers.h +41 -23
  117. data/vendor/faiss/faiss/impl/hnsw/avx2.cpp +0 -4
  118. data/vendor/faiss/faiss/impl/hnsw/avx512.cpp +0 -4
  119. data/vendor/faiss/faiss/impl/index_read.cpp +172 -0
  120. data/vendor/faiss/faiss/impl/index_write.cpp +95 -0
  121. data/vendor/faiss/faiss/impl/pipnn/HashPrune.cpp +159 -0
  122. data/vendor/faiss/faiss/impl/pipnn/HashPrune.h +69 -0
  123. data/vendor/faiss/faiss/impl/pipnn/Partition.cpp +587 -0
  124. data/vendor/faiss/faiss/impl/pipnn/Partition.h +93 -0
  125. data/vendor/faiss/faiss/impl/pipnn/kernels.cpp +326 -0
  126. data/vendor/faiss/faiss/impl/pipnn/kernels.h +92 -0
  127. data/vendor/faiss/faiss/impl/platform_macros.h +45 -0
  128. data/vendor/faiss/faiss/impl/polysemous_training/avx512.cpp +0 -4
  129. data/vendor/faiss/faiss/impl/pq_code_distance/IVFPQScanner_impl.h +79 -4
  130. data/vendor/faiss/faiss/impl/pq_code_distance/PQDistanceComputer_impl.h +6 -0
  131. data/vendor/faiss/faiss/impl/pq_code_distance/avx2.cpp +0 -4
  132. data/vendor/faiss/faiss/impl/pq_code_distance/avx512.cpp +0 -4
  133. data/vendor/faiss/faiss/impl/pq_code_distance/neon.cpp +0 -4
  134. data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-avx2.h +4 -0
  135. data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-avx512.h +4 -1
  136. data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-generic.cpp +19 -0
  137. data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-generic.h +6 -6
  138. data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-inl.h +131 -6
  139. data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-sve.cpp +0 -4
  140. data/vendor/faiss/faiss/impl/pq_code_distance/pq_rvv_kernel.h +89 -0
  141. data/vendor/faiss/faiss/impl/pq_code_distance/pq_scan_impl.h +21 -10
  142. data/vendor/faiss/faiss/impl/pq_code_distance/rvv.cpp +45 -18
  143. data/vendor/faiss/faiss/impl/residual_quantizer_encode_steps.cpp +2 -1
  144. data/vendor/faiss/faiss/impl/result_handler/avx2.cpp +0 -4
  145. data/vendor/faiss/faiss/impl/result_handler/avx512.cpp +0 -4
  146. data/vendor/faiss/faiss/impl/scalar_quantizer/EDENQuantizer-avx2.cpp +0 -4
  147. data/vendor/faiss/faiss/impl/scalar_quantizer/EDENQuantizer-avx512.cpp +0 -4
  148. data/vendor/faiss/faiss/impl/scalar_quantizer/distance_computers.h +5 -1
  149. data/vendor/faiss/faiss/impl/scalar_quantizer/quantizers.h +28 -52
  150. data/vendor/faiss/faiss/impl/scalar_quantizer/scanners.h +59 -11
  151. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx2.cpp +226 -77
  152. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512-impl.h +482 -103
  153. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512-spr.cpp +2 -149
  154. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512.cpp +3 -698
  155. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-dispatch.h +66 -57
  156. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-neon.cpp +101 -96
  157. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-rvv.cpp +3185 -144
  158. data/vendor/faiss/faiss/impl/scalar_quantizer/sq8_coefficients.h +48 -0
  159. data/vendor/faiss/faiss/impl/scalar_quantizer/sq8_rvv_kernel.h +546 -0
  160. data/vendor/faiss/faiss/impl/scalar_quantizer/training.cpp +2 -2
  161. data/vendor/faiss/faiss/impl/simd_dispatch.h +7 -0
  162. data/vendor/faiss/faiss/impl/simdlib/simdlib_avx2.h +14 -0
  163. data/vendor/faiss/faiss/impl/simdlib/simdlib_dispatch.h +7 -8
  164. data/vendor/faiss/faiss/impl/sq_fastscan_utils.h +232 -0
  165. data/vendor/faiss/faiss/index_factory.cpp +76 -0
  166. data/vendor/faiss/faiss/invlists/InvertedLists.cpp +3 -1
  167. data/vendor/faiss/faiss/svs/IndexSVSVamana.h +3 -2
  168. data/vendor/faiss/faiss/utils/AlignedTable.h +6 -1
  169. data/vendor/faiss/faiss/utils/Heap.cpp +4 -3
  170. data/vendor/faiss/faiss/utils/approx_topk_hamming/approx_topk_hamming.h +16 -5
  171. data/vendor/faiss/faiss/utils/bf16.h +36 -0
  172. data/vendor/faiss/faiss/utils/distances.cpp +226 -15
  173. data/vendor/faiss/faiss/utils/distances.h +34 -0
  174. data/vendor/faiss/faiss/utils/distances_dispatch.h +6 -4
  175. data/vendor/faiss/faiss/utils/distances_fused/avx512.cpp +2 -5
  176. data/vendor/faiss/faiss/utils/distances_fused/distances_fused.cpp +0 -14
  177. data/vendor/faiss/faiss/utils/distances_fused/rvv.cpp +25 -0
  178. data/vendor/faiss/faiss/utils/distances_fused/simdlib_based.cpp +0 -4
  179. data/vendor/faiss/faiss/utils/distances_fused/simdlib_based_neon.cpp +0 -4
  180. data/vendor/faiss/faiss/utils/distances_fused/simdlib_kernel-inl.h +2 -1
  181. data/vendor/faiss/faiss/utils/distances_simd.cpp +9 -0
  182. data/vendor/faiss/faiss/utils/fp16-fp16c.h +4 -0
  183. data/vendor/faiss/faiss/utils/fp16-inl.h +26 -52
  184. data/vendor/faiss/faiss/utils/hamming_distance/hamming_avx2.cpp +0 -4
  185. data/vendor/faiss/faiss/utils/hamming_distance/hamming_avx512.cpp +0 -4
  186. data/vendor/faiss/faiss/utils/hamming_distance/hamming_avx512_vpopcnt.cpp +0 -4
  187. data/vendor/faiss/faiss/utils/hamming_distance/hamming_computer-avx512.h +33 -5
  188. data/vendor/faiss/faiss/utils/hamming_distance/hamming_computer-avx512_vpopcnt.h +15 -30
  189. data/vendor/faiss/faiss/utils/hamming_distance/hamming_computer-neon.h +0 -4
  190. data/vendor/faiss/faiss/utils/hamming_distance/hamming_impl.h +2 -1
  191. data/vendor/faiss/faiss/utils/hamming_distance/hamming_neon.cpp +0 -4
  192. data/vendor/faiss/faiss/utils/hamming_distance/hamming_rvv.cpp +0 -4
  193. data/vendor/faiss/faiss/utils/pq_code_distance.h +8 -4
  194. data/vendor/faiss/faiss/utils/prefetch.h +1 -26
  195. data/vendor/faiss/faiss/utils/quantize_lut.cpp +26 -27
  196. data/vendor/faiss/faiss/utils/quantize_lut.h +11 -0
  197. data/vendor/faiss/faiss/utils/rabitq_simd.h +51 -16
  198. data/vendor/faiss/faiss/utils/simd_impl/distances_aarch64.cpp +9 -4
  199. data/vendor/faiss/faiss/utils/simd_impl/distances_arm_sve.cpp +37 -5
  200. data/vendor/faiss/faiss/utils/simd_impl/distances_avx2.cpp +64 -5
  201. data/vendor/faiss/faiss/utils/simd_impl/distances_avx512.cpp +64 -5
  202. data/vendor/faiss/faiss/utils/simd_impl/distances_rvv.cpp +353 -71
  203. data/vendor/faiss/faiss/utils/simd_impl/distances_rvv_kernel.h +68 -0
  204. data/vendor/faiss/faiss/utils/simd_impl/exhaustive_L2sqr_blas_cmax.h +93 -0
  205. data/vendor/faiss/faiss/utils/simd_impl/fp16_kernels.h +76 -0
  206. data/vendor/faiss/faiss/utils/simd_impl/fp16_kernels_avx2.cpp +52 -0
  207. data/vendor/faiss/faiss/utils/simd_impl/fp16_kernels_avx512.cpp +52 -0
  208. data/vendor/faiss/faiss/utils/simd_impl/fp16_kernels_neon.cpp +48 -0
  209. data/vendor/faiss/faiss/utils/simd_impl/masked_sum-avx2.h +44 -0
  210. data/vendor/faiss/faiss/utils/simd_impl/masked_sum-avx512.h +43 -0
  211. data/vendor/faiss/faiss/utils/simd_impl/masked_sum.h +38 -0
  212. data/vendor/faiss/faiss/utils/simd_impl/partitioning_avx2.cpp +0 -4
  213. data/vendor/faiss/faiss/utils/simd_impl/partitioning_neon.cpp +0 -4
  214. data/vendor/faiss/faiss/utils/simd_impl/rabitq_avx2.cpp +54 -15
  215. data/vendor/faiss/faiss/utils/simd_impl/rabitq_avx512.cpp +38 -21
  216. data/vendor/faiss/faiss/utils/simd_impl/rabitq_avx512_vpopcnt.cpp +51 -14
  217. data/vendor/faiss/faiss/utils/simd_impl/rabitq_neon.cpp +297 -10
  218. data/vendor/faiss/faiss/utils/simd_impl/rabitq_rvv.cpp +0 -4
  219. data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_kernels_avx2.cpp +0 -4
  220. data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_kernels_avx512.cpp +0 -4
  221. data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_kernels_sve.cpp +0 -4
  222. data/vendor/faiss/faiss/utils/simd_levels.cpp +19 -3
  223. data/vendor/faiss/faiss/utils/simd_levels.h +74 -1
  224. data/vendor/faiss/faiss/utils/transpose/transpose-avx2-inl.h +0 -4
  225. data/vendor/faiss/faiss/utils/transpose/transpose-avx512-inl.h +0 -4
  226. data/vendor/faiss/faiss/utils/utils.cpp +80 -0
  227. data/vendor/faiss/faiss/utils/utils.h +24 -0
  228. metadata +33 -1
@@ -0,0 +1,822 @@
1
+ /*
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ #include <faiss/impl/PiPNN.h>
9
+
10
+ #include <omp.h>
11
+
12
+ #include <algorithm>
13
+ #include <array>
14
+ #include <atomic>
15
+ #include <cfloat>
16
+ #include <cmath>
17
+ #include <cstdint>
18
+ #include <cstdio>
19
+ #include <cstring>
20
+ #include <exception>
21
+ #include <limits>
22
+ #include <memory>
23
+ #include <mutex>
24
+ #include <numeric>
25
+ #include <utility>
26
+ #include <vector>
27
+
28
+ #include <faiss/Index.h>
29
+ #include <faiss/IndexFlatCodes.h>
30
+ #include <faiss/impl/AuxIndexStructures.h>
31
+ #include <faiss/impl/DistanceComputer.h>
32
+ #include <faiss/impl/FaissAssert.h>
33
+ #include <faiss/impl/FaissException.h>
34
+ #include <faiss/impl/IDSelector.h>
35
+ #include <faiss/impl/VisitedTable.h>
36
+ #include <faiss/impl/pipnn/HashPrune.h>
37
+ #include <faiss/impl/pipnn/Partition.h>
38
+ #include <faiss/impl/pipnn/kernels.h>
39
+ #include <faiss/utils/AlignedTable.h>
40
+ #include <faiss/utils/Heap.h>
41
+ #include <faiss/utils/distances.h>
42
+ #include <faiss/utils/ordered_key_value.h>
43
+ #include <faiss/utils/prefetch.h>
44
+ #include <faiss/utils/utils.h>
45
+
46
+ namespace faiss {
47
+
48
+ namespace pipnn {
49
+
50
+ namespace {
51
+
52
+ constexpr size_t kMedoidBatch = 1024;
53
+ // distances stay finite: at most 4x this in GEMM form
54
+ constexpr float kMaxSquaredNorm = FLT_MAX / 8;
55
+ constexpr size_t kNumLocks = size_t(1) << 16;
56
+
57
+ /// Reservoirs are allocated, and released after their final prune, in chunks
58
+ /// of this many points.
59
+ constexpr size_t kReservoirChunk = size_t(1) << 20;
60
+
61
+ /// Reservoirs of points [begin, begin + np) in one buffer: np * capacity
62
+ /// slots, then np counts, then np cached-farthest indices.
63
+ struct ReservoirChunk {
64
+ std::unique_ptr<uint8_t[]> buf;
65
+ size_t begin = 0;
66
+ size_t np = 0;
67
+ };
68
+
69
+ /// Reservoirs of one build and the graph its final prune writes.
70
+ struct BuildState {
71
+ size_t capacity;
72
+ size_t chunk_size;
73
+ std::vector<ReservoirChunk> chunks;
74
+ // uninitialized; rows written by the final prune and link_copies
75
+ std::unique_ptr<int32_t[]> graph;
76
+
77
+ BuildState(size_t n_slots, size_t n, const PiPNN& p, size_t chunk)
78
+ : capacity(size_t(p.reservoir_size)), chunk_size(chunk) {
79
+ const size_t per_point = capacity * sizeof(Slot) + 2;
80
+ for (size_t begin = 0; begin < n_slots; begin += chunk_size) {
81
+ ReservoirChunk ch;
82
+ ch.begin = begin;
83
+ ch.np = std::min(chunk_size, n_slots - begin);
84
+ ch.buf.reset(new uint8_t[ch.np * per_point]);
85
+ chunks.push_back(std::move(ch));
86
+ }
87
+ graph.reset(new int32_t[n * size_t(p.R)]);
88
+ // Touch every reservoir page now so an oversized build fails early;
89
+ // small static blocks interleave the pages over NUMA nodes.
90
+ #pragma omp parallel for schedule(static, 8)
91
+ for (int64_t i = 0; i < int64_t(n_slots); i++) {
92
+ const ReservoirRef r = reservoir(size_t(i));
93
+ std::memset(r.slots, 0, capacity * sizeof(Slot));
94
+ *r.count = 0;
95
+ *r.farthest = kNoFarthest;
96
+ }
97
+ }
98
+
99
+ ReservoirRef reservoir(size_t i) {
100
+ ReservoirChunk& ch = chunks[i / chunk_size];
101
+ const size_t off = i - ch.begin;
102
+ Slot* slots = reinterpret_cast<Slot*>(ch.buf.get());
103
+ uint8_t* counts = reinterpret_cast<uint8_t*>(slots + ch.np * capacity);
104
+ return ReservoirRef{
105
+ slots + off * capacity,
106
+ counts + off,
107
+ counts + ch.np + off,
108
+ int(capacity)};
109
+ }
110
+ };
111
+
112
+ /// Keeps the graph alive as long as a MaybeOwnedVector views it.
113
+ struct GraphOwner : MaybeOwnedVectorOwner {
114
+ std::unique_ptr<int32_t[]> data;
115
+ explicit GraphOwner(std::unique_ptr<int32_t[]> d) : data(std::move(d)) {}
116
+ };
117
+
118
+ /// Rows with equal stored codes: the graph is built over the lowest, the
119
+ /// others (copies) are linked behind it. Empty if all differ.
120
+ struct Duplicates {
121
+ /// ascending ids of the rows the graph is built over
122
+ std::vector<int32_t> ids;
123
+ /// slot_of[i]: slot of the lowest id with the code of row i
124
+ std::vector<int32_t> slot_of;
125
+
126
+ explicit Duplicates(const Index& storage) {
127
+ const auto* flat = dynamic_cast<const IndexFlatCodes*>(&storage);
128
+ if (!flat) {
129
+ return;
130
+ }
131
+ const size_t n = size_t(storage.ntotal);
132
+ std::vector<int64_t> rep(n);
133
+ if (find_duplicate_codes(
134
+ n, flat->code_size, flat->codes.data(), rep.data()) == n) {
135
+ return;
136
+ }
137
+ slot_of.resize(n);
138
+ for (size_t i = 0; i < n; i++) {
139
+ if (rep[i] == int64_t(i)) {
140
+ slot_of[i] = int32_t(ids.size());
141
+ ids.push_back(int32_t(i));
142
+ } else {
143
+ slot_of[i] = slot_of[rep[i]];
144
+ }
145
+ }
146
+ }
147
+
148
+ size_t n_slots(size_t n) const {
149
+ return ids.empty() ? n : ids.size();
150
+ }
151
+ size_t slot(int32_t id) const {
152
+ return slot_of.empty() ? size_t(id) : size_t(slot_of[id]);
153
+ }
154
+ int32_t id(size_t slot) const {
155
+ return ids.empty() ? int32_t(slot) : ids[slot];
156
+ }
157
+
158
+ /// Returns next_copy and clears the graph rows of the copies; consumes ids.
159
+ std::vector<int32_t> link_copies(int32_t* graph, size_t R) {
160
+ std::vector<int32_t> next(slot_of.size(), -1);
161
+ for (size_t i = 0; i < next.size(); i++) {
162
+ int32_t& tail = ids[slot_of[i]];
163
+ if (tail != int32_t(i)) {
164
+ std::fill(graph + i * R, graph + (i + 1) * R, -1);
165
+ next[tail] = int32_t(i);
166
+ tail = int32_t(i);
167
+ }
168
+ }
169
+ return next;
170
+ }
171
+ };
172
+
173
+ } // namespace
174
+
175
+ int32_t medoid_and_validate(const Index& storage, float* norm_cv) {
176
+ const size_t d = size_t(storage.d);
177
+ const size_t n = size_t(storage.ntotal);
178
+ FAISS_THROW_IF_MSG(n == 0, "PiPNN: the medoid of 0 vectors is undefined");
179
+ FAISS_THROW_IF_MSG(
180
+ n > size_t(std::numeric_limits<int32_t>::max()),
181
+ "PiPNN: more than 2^31 - 1 vectors");
182
+ const size_t nblocks = (n + kMedoidBlock - 1) / kMedoidBlock;
183
+ std::vector<double> sums(nblocks * d);
184
+ std::vector<float> mean(d);
185
+ // (distance to the mean, id): pairs order ties by the lower id
186
+ std::vector<std::pair<float, int32_t>> block_best(nblocks);
187
+ std::vector<std::array<double, 3>> norm_sums(norm_cv ? nblocks : 0);
188
+
189
+ // pass 0: validate and sum per block; pass 1: per-block argmin
190
+ for (int pass = 0; pass < 2; pass++) {
191
+ std::exception_ptr ex;
192
+ std::atomic<bool> interrupt{false};
193
+ #pragma omp parallel
194
+ {
195
+ AlignedTable<float, 64> buf;
196
+ int32_t ids[kMedoidBatch];
197
+ #pragma omp for schedule(dynamic)
198
+ for (int64_t b = 0; b < int64_t(nblocks); b++) {
199
+ if (interrupt.load(std::memory_order_relaxed)) {
200
+ continue;
201
+ }
202
+ try {
203
+ ensure_size(buf, kMedoidBatch * d);
204
+ const size_t i0 = size_t(b) * kMedoidBlock;
205
+ const size_t i1 = std::min(n, i0 + kMedoidBlock);
206
+ double* acc = sums.data() + size_t(b) * d;
207
+ std::pair<float, int32_t> best{
208
+ std::numeric_limits<float>::infinity(), -1};
209
+ for (size_t j0 = i0; j0 < i1; j0 += kMedoidBatch) {
210
+ const size_t k = std::min(kMedoidBatch, i1 - j0);
211
+ std::iota(ids, ids + k, int32_t(j0));
212
+ gather_rows(storage, ids, k, buf.get());
213
+ for (size_t r = 0; r < k; r++) {
214
+ const float* x = buf.get() + r * d;
215
+ if (pass == 0) {
216
+ // NaN and inf fail this comparison too
217
+ const float nrm = fvec_norm_L2sqr(x, d);
218
+ FAISS_THROW_IF_NOT_FMT(
219
+ nrm <= kMaxSquaredNorm,
220
+ "PiPNN: vector %zu has squared norm "
221
+ "%g; need finite and <= FLT_MAX / 8",
222
+ j0 + r,
223
+ double(nrm));
224
+ if (norm_cv && nrm > 0) {
225
+ norm_sums[b][0] += 1;
226
+ norm_sums[b][1] += std::sqrt(double(nrm));
227
+ norm_sums[b][2] += nrm;
228
+ }
229
+ for (size_t j = 0; j < d; j++) {
230
+ acc[j] += double(x[j]);
231
+ }
232
+ } else {
233
+ best = std::min(
234
+ best,
235
+ {fvec_L2sqr(x, mean.data(), d),
236
+ int32_t(j0 + r)});
237
+ }
238
+ }
239
+ }
240
+ if (pass == 1) {
241
+ block_best[b] = best;
242
+ }
243
+ // blocks are large: poll after every one
244
+ if (omp_get_thread_num() == 0 &&
245
+ InterruptCallback::is_interrupted()) {
246
+ FAISS_THROW_MSG("computation interrupted");
247
+ }
248
+ } catch (...) {
249
+ omp_capture_exception(ex, [&] { interrupt = true; });
250
+ }
251
+ }
252
+ }
253
+ omp_rethrow_if_exception(ex);
254
+ if (pass == 0) {
255
+ for (size_t j = 0; j < d; j++) {
256
+ double s = 0;
257
+ for (size_t b = 0; b < nblocks; b++) {
258
+ s += sums[b * d + j];
259
+ }
260
+ mean[j] = float(s / double(n));
261
+ }
262
+ }
263
+ }
264
+ if (norm_cv) {
265
+ double c = 0, s1 = 0, s2 = 0;
266
+ for (const auto& [bc, b1, b2] : norm_sums) {
267
+ c += bc;
268
+ s1 += b1;
269
+ s2 += b2;
270
+ }
271
+ if (c == 0) {
272
+ *norm_cv = 0;
273
+ } else {
274
+ const double mean = s1 / c;
275
+ *norm_cv = std::sqrt(std::max(s2 / c - mean * mean, 0.0)) / mean;
276
+ }
277
+ }
278
+ return std::min_element(block_best.begin(), block_best.end())->second;
279
+ }
280
+
281
+ namespace {
282
+
283
+ PartitionParams partition_params(const PiPNN& p, MetricType metric) {
284
+ return {.c_max = p.c_max,
285
+ .c_min = p.c_min,
286
+ .leader_cap = p.leader_cap,
287
+ .leader_fraction = p.leader_fraction,
288
+ .fanout = p.fanout,
289
+ .metric = metric,
290
+ .ip_partition_by_angle = p.ip_partition_by_angle,
291
+ .wave_budget = p.partition_wave_budget};
292
+ }
293
+
294
+ /// Offers the nearest neighbours of every leaf point to the HashPrune
295
+ /// reservoirs.
296
+ void fill_reservoirs(
297
+ BuildState& st,
298
+ const Index& storage,
299
+ const Duplicates& dup,
300
+ const PiPNN& p,
301
+ bool verbose) {
302
+ const size_t n = dup.n_slots(size_t(storage.ntotal));
303
+ const size_t d = size_t(storage.d);
304
+ const MetricType metric = storage.metric_type;
305
+ const bool is_ip = metric == METRIC_INNER_PRODUCT;
306
+ // n <= c_max is a single leaf; there each point offers its
307
+ // reservoir_size nearest instead of its leaf_k nearest.
308
+ const size_t k =
309
+ n <= size_t(p.c_max) ? size_t(p.reservoir_size) : size_t(p.leaf_k);
310
+
311
+ const Hyperplanes hp(storage.d, p.hash_bits, p.seed);
312
+ std::vector<std::mutex> locks(kNumLocks);
313
+
314
+ struct LeafScratch {
315
+ AlignedTable<float, 64> vecs, gram;
316
+ std::vector<float> norms, nn_dist, sketches;
317
+ std::vector<int32_t> nn_idx;
318
+ };
319
+ std::vector<LeafScratch> scratch(omp_get_max_threads());
320
+ const LeafVisitor on_leaf = [&](const int32_t* ids, size_t s) {
321
+ LeafScratch& sc = scratch[omp_get_thread_num()];
322
+ ensure_size(sc.vecs, s * d);
323
+ gather_rows(storage, ids, s, sc.vecs.get());
324
+ sc.norms.resize(s);
325
+ sc.sketches.resize(s * 16);
326
+ for (size_t i = 0; i < s; i++) {
327
+ const float* x = sc.vecs.get() + i * d;
328
+ sc.norms[i] = fvec_norm_L2sqr(x, d);
329
+ hp.sketch(x, sc.sketches.data() + i * 16);
330
+ }
331
+ ensure_size(sc.gram, s * s);
332
+ sc.nn_idx.resize(s * k);
333
+ sc.nn_dist.resize(s * k);
334
+ leaf_knn(
335
+ sc.vecs.get(),
336
+ sc.norms.data(),
337
+ s,
338
+ d,
339
+ metric,
340
+ int(k),
341
+ sc.gram.get(),
342
+ sc.nn_idx.data(),
343
+ sc.nn_dist.data());
344
+ // Offers leaf point b to the reservoir of leaf point a. Keys are
345
+ // dist - d(a, a), so near candidates sit near 0 where bf16 is finest.
346
+ auto offer = [&](size_t a, size_t b, float dist) {
347
+ const uint16_t hash = residual_hash(
348
+ sc.sketches.data() + a * 16,
349
+ sc.sketches.data() + b * 16,
350
+ p.hash_bits);
351
+ const uint16_t key =
352
+ distance_key(is_ip ? dist + sc.norms[a] : dist);
353
+ std::lock_guard<std::mutex> guard(
354
+ locks[uint32_t(ids[a]) & (kNumLocks - 1)]);
355
+ st.reservoir(dup.slot(ids[a])).offer(ids[b], hash, key);
356
+ };
357
+ for (size_t i = 0; i < s; i++) {
358
+ for (size_t t = 0; t < k; t++) {
359
+ const int32_t j = sc.nn_idx[i * k + t];
360
+ if (j < 0) {
361
+ break;
362
+ }
363
+ offer(i, size_t(j), sc.nn_dist[i * k + t]);
364
+ offer(size_t(j), i, sc.nn_dist[i * k + t]);
365
+ }
366
+ }
367
+ };
368
+
369
+ const double t0 = getmillisecs();
370
+ PartitionStats stats;
371
+ partition_and_visit(
372
+ storage,
373
+ partition_params(p, metric),
374
+ mix_seed(uint64_t(p.seed), 0),
375
+ on_leaf,
376
+ &stats,
377
+ dup.ids);
378
+ if (verbose) {
379
+ printf("PiPNN: partition: %.3f s, %zu leaves, replication %.2f\n",
380
+ (getmillisecs() - t0) / 1000,
381
+ stats.n_leaves,
382
+ double(stats.n_point_instances) / double(n));
383
+ }
384
+ }
385
+
386
+ /// Prunes each reservoir into its graph row, freeing each chunk once pruned.
387
+ void final_prune(
388
+ BuildState& st,
389
+ const Index& storage,
390
+ const Duplicates& dup,
391
+ float alpha,
392
+ int R,
393
+ bool cosine) {
394
+ const size_t d = size_t(storage.d);
395
+ const MetricType metric = storage.metric_type;
396
+ int32_t* graph = st.graph.get();
397
+ for (ReservoirChunk& ch : st.chunks) {
398
+ std::exception_ptr ex;
399
+ std::atomic<bool> interrupt{false};
400
+ #pragma omp parallel
401
+ {
402
+ AlignedTable<float, 64> X;
403
+ AlignedTable<float, 64> D;
404
+ std::vector<float> norms;
405
+ std::vector<int32_t> ids;
406
+ std::vector<int> order;
407
+ size_t n_done = 0;
408
+ #pragma omp for schedule(dynamic, 64)
409
+ for (int64_t i = int64_t(ch.begin); i < int64_t(ch.begin + ch.np);
410
+ i++) {
411
+ if (interrupt.load(std::memory_order_relaxed)) {
412
+ continue;
413
+ }
414
+ try {
415
+ const ReservoirRef r = st.reservoir(size_t(i));
416
+ const size_t l = *r.count;
417
+ const size_t m = l + 1;
418
+ ensure_size(X, m * d);
419
+ ensure_size(D, m * m);
420
+ norms.resize(m);
421
+ ids.resize(m);
422
+ order.resize(l);
423
+ ids[0] = dup.id(size_t(i));
424
+ for (size_t t = 0; t < l; t++) {
425
+ ids[1 + t] = r.slots[t].id;
426
+ }
427
+ gather_rows(storage, ids.data(), m, X.get());
428
+ if (cosine) {
429
+ fvec_renorm_L2(d, m, X.get());
430
+ }
431
+ for (size_t t = 0; t < m; t++) {
432
+ norms[t] = fvec_norm_L2sqr(X.get() + t * d, d);
433
+ }
434
+ pairwise_distances(
435
+ X.get(), norms.data(), m, d, metric, D.get());
436
+ robust_prune(
437
+ D.get(),
438
+ m,
439
+ ids.data() + 1,
440
+ metric,
441
+ alpha,
442
+ R,
443
+ order.data(),
444
+ graph + size_t(ids[0]) * size_t(R));
445
+ if (omp_get_thread_num() == 0 && n_done++ % 64 == 0 &&
446
+ InterruptCallback::is_interrupted()) {
447
+ FAISS_THROW_MSG("computation interrupted");
448
+ }
449
+ } catch (...) {
450
+ omp_capture_exception(ex, [&] { interrupt = true; });
451
+ }
452
+ }
453
+ }
454
+ omp_rethrow_if_exception(ex);
455
+ ch.buf.reset();
456
+ }
457
+ }
458
+
459
+ } // namespace
460
+
461
+ void robust_prune(
462
+ const float* D,
463
+ size_t m,
464
+ const int32_t* cand,
465
+ MetricType metric,
466
+ float alpha,
467
+ int R,
468
+ int* order,
469
+ int32_t* out) {
470
+ const size_t l = m - 1;
471
+ std::iota(order, order + l, 0);
472
+ std::sort(order, order + l, [&](int a, int b) {
473
+ return CMin<float, int32_t>::cmp2(D[1 + a], D[1 + b], cand[a], cand[b]);
474
+ });
475
+ const bool is_ip = metric == METRIC_INNER_PRODUCT;
476
+ int na = 0;
477
+ for (size_t t = 0; t < l && na < R; t++) {
478
+ const int c = order[t];
479
+ const float dxc = D[1 + c];
480
+ const bool kept = std::none_of(order, order + na, [&](int y) {
481
+ const float dyc = D[size_t(1 + y) * m + 1 + c];
482
+ return is_ip ? dyc < alpha * dxc : alpha * dyc < dxc;
483
+ });
484
+ if (kept) {
485
+ order[na++] = c; // na <= t: never overwrites an unread entry
486
+ }
487
+ }
488
+ for (int u = 0; u < na; u++) {
489
+ out[u] = cand[order[u]];
490
+ }
491
+ std::fill(out + na, out + R, -1);
492
+ }
493
+
494
+ namespace detail {
495
+
496
+ void build_with_chunk(
497
+ PiPNN& p,
498
+ const Index& storage,
499
+ size_t reservoir_chunk,
500
+ bool verbose) {
501
+ p.check_params();
502
+ const size_t n = size_t(storage.ntotal);
503
+ if (n == 0) {
504
+ p.effective_alpha(storage.metric_type);
505
+ p.reset();
506
+ return;
507
+ }
508
+
509
+ const double t0 = getmillisecs();
510
+ InterruptCallback::check();
511
+ float norm_cv = 0;
512
+ const int32_t ep = medoid_and_validate(
513
+ storage,
514
+ storage.metric_type == METRIC_INNER_PRODUCT ? &norm_cv : nullptr);
515
+ const bool cosine = norm_cv > 0.05f;
516
+ const float alpha = p.effective_alpha(storage.metric_type);
517
+ if (verbose) {
518
+ printf("PiPNN: medoid and validation of %zu vectors (d=%d): %.3f s\n",
519
+ n,
520
+ storage.d,
521
+ (getmillisecs() - t0) / 1000);
522
+ }
523
+
524
+ Duplicates dup(storage);
525
+ BuildState st(dup.n_slots(n), n, p, reservoir_chunk);
526
+ fill_reservoirs(st, storage, dup, p, verbose);
527
+
528
+ const double t1 = getmillisecs();
529
+ final_prune(st, storage, dup, alpha, p.R, cosine);
530
+ std::vector<int32_t> next_copy =
531
+ dup.link_copies(st.graph.get(), size_t(p.R));
532
+ InterruptCallback::check();
533
+ if (verbose) {
534
+ printf("PiPNN: final prune: %.3f s\n", (getmillisecs() - t1) / 1000);
535
+ }
536
+
537
+ // Publish: nothing below can throw after the members start changing.
538
+ auto owner = std::make_shared<GraphOwner>(std::move(st.graph));
539
+ p.graph = MaybeOwnedVector<int32_t>::create_view(
540
+ owner->data.get(), n * size_t(p.R), owner);
541
+ p.next_copy = std::move(next_copy);
542
+ p.entry_point = ep;
543
+ p.ntotal = idx_t(n);
544
+ }
545
+
546
+ } // namespace detail
547
+
548
+ namespace {
549
+
550
+ /// Up to cap entries in (dis, id) order; all before cursor are expanded.
551
+ struct SearchBeam {
552
+ struct Entry {
553
+ float dis;
554
+ int32_t id;
555
+ bool expanded;
556
+
557
+ bool operator<(const Entry& o) const {
558
+ return CMin<float, int32_t>::cmp2(dis, o.dis, id, o.id);
559
+ }
560
+ };
561
+
562
+ std::vector<Entry> entries;
563
+ size_t cap;
564
+ size_t cursor = 0;
565
+
566
+ explicit SearchBeam(size_t cap_in) : cap(cap_in) {
567
+ entries.reserve(cap_in + 1);
568
+ }
569
+
570
+ void insert(float dis, int32_t id) {
571
+ const Entry e{dis, id, false};
572
+ if (entries.size() == cap && !(e < entries.back())) {
573
+ return;
574
+ }
575
+ const auto it = std::lower_bound(entries.begin(), entries.end(), e);
576
+ cursor = std::min(cursor, size_t(it - entries.begin()));
577
+ entries.insert(it, e);
578
+ if (entries.size() > cap) {
579
+ entries.pop_back();
580
+ }
581
+ }
582
+
583
+ /// The closest unexpanded entry after the cursor, or -1.
584
+ int32_t peek_next() const {
585
+ for (size_t c = cursor + 1; c < entries.size(); c++) {
586
+ if (!entries[c].expanded) {
587
+ return entries[c].id;
588
+ }
589
+ }
590
+ return -1;
591
+ }
592
+
593
+ /// Marks the closest unexpanded entry expanded; returns its id, or -1.
594
+ int32_t expand_next() {
595
+ while (cursor < entries.size() && entries[cursor].expanded) {
596
+ cursor++;
597
+ }
598
+ if (cursor == entries.size()) {
599
+ return -1;
600
+ }
601
+ entries[cursor].expanded = true;
602
+ return entries[cursor].id;
603
+ }
604
+ };
605
+
606
+ template <class VT>
607
+ void beam_search(
608
+ const PiPNN& pipnn,
609
+ DistanceComputer& qdis,
610
+ const uint8_t* codes,
611
+ size_t code_size,
612
+ size_t cap,
613
+ size_t k,
614
+ idx_t* I,
615
+ float* D,
616
+ VT& vt,
617
+ const IDSelector* sel) {
618
+ const uint32_t n = uint32_t(pipnn.ntotal);
619
+ const size_t R = size_t(pipnn.R);
620
+ const int32_t* graph = pipnn.graph.data();
621
+ const int32_t* next_copy =
622
+ pipnn.next_copy.size() == 0 ? nullptr : pipnn.next_copy.data();
623
+ SearchBeam beam(cap);
624
+
625
+ // id breaks ties, so the result does not depend on visit order
626
+ auto offer = [&](int32_t id, float dis) {
627
+ const bool better =
628
+ CMax<float, idx_t>::cmp2(D[0], dis, I[0], idx_t(id));
629
+ if (better && (sel == nullptr || sel->is_member(id))) {
630
+ maxheap_replace_top(k, D, I, dis, idx_t(id));
631
+ }
632
+ return better;
633
+ };
634
+ // Copies skip the beam. Their ids increase, so the first one the heap
635
+ // rejects ends the chain; checking that also stops a corrupt chain.
636
+ auto visit = [&](int32_t id, float dis) {
637
+ int32_t c = id;
638
+ while (offer(c, dis) && next_copy && next_copy[c] > c &&
639
+ uint32_t(next_copy[c]) < n) {
640
+ c = next_copy[c];
641
+ }
642
+ beam.insert(dis, id);
643
+ };
644
+
645
+ vt.set(size_t(pipnn.entry_point));
646
+ visit(pipnn.entry_point, qdis(pipnn.entry_point));
647
+
648
+ // Prefetching more than 1 KiB per vector costs more than it saves.
649
+ const size_t prefetch_bytes = std::min(code_size, size_t(1024));
650
+ auto prefetch = [](const void* p, size_t nbytes) {
651
+ const uintptr_t a = reinterpret_cast<uintptr_t>(p);
652
+ for (uintptr_t b = a & ~uintptr_t(63); b < a + nbytes; b += 64) {
653
+ prefetch_L1(reinterpret_cast<const void*>(b));
654
+ }
655
+ };
656
+ auto prefetch_code = [&](int32_t id) {
657
+ prefetch(codes + size_t(id) * code_size, prefetch_bytes);
658
+ };
659
+ // Vectors are prefetched about 4 KiB ahead of the distance computation,
660
+ // at least one batch: prefetching all neighbors of a large-vector graph at
661
+ // once evicts the ones about to be read when two threads share a core.
662
+ const size_t ahead = std::max(
663
+ size_t(4), size_t(4096) / std::max(prefetch_bytes, size_t(64)));
664
+ std::vector<int32_t> todo(R);
665
+ for (int32_t v0 = beam.expand_next(); v0 >= 0; v0 = beam.expand_next()) {
666
+ // The closest unexpanded entry usually comes next.
667
+ const int32_t next = beam.peek_next();
668
+ if (next >= 0) {
669
+ prefetch(graph + size_t(next) * R, R * sizeof(int32_t));
670
+ }
671
+ const int32_t* row = graph + size_t(v0) * R;
672
+ // One unsigned compare stops at the -1 padding and at any id that is
673
+ // out of range in a corrupt (e.g. mmapped) graph.
674
+ size_t jmax = 0;
675
+ while (jmax < R && uint32_t(row[jmax]) < n) {
676
+ vt.prefetch(size_t(row[jmax]));
677
+ jmax++;
678
+ }
679
+ size_t nt = 0;
680
+ for (size_t j = 0; j < jmax; j++) {
681
+ if (vt.set(size_t(row[j]))) {
682
+ if (nt < ahead) {
683
+ prefetch_code(row[j]);
684
+ }
685
+ todo[nt++] = row[j];
686
+ }
687
+ }
688
+ size_t i = 0;
689
+ for (; i + 4 <= nt; i += 4) {
690
+ for (size_t t = i + ahead; t < std::min(i + ahead + 4, nt); t++) {
691
+ prefetch_code(todo[t]);
692
+ }
693
+ float d4[4];
694
+ qdis.distances_batch_4(
695
+ todo[i],
696
+ todo[i + 1],
697
+ todo[i + 2],
698
+ todo[i + 3],
699
+ d4[0],
700
+ d4[1],
701
+ d4[2],
702
+ d4[3]);
703
+ for (size_t t = 0; t < 4; t++) {
704
+ visit(todo[i + t], d4[t]);
705
+ }
706
+ }
707
+ for (; i < nt; i++) {
708
+ visit(todo[i], qdis(todo[i]));
709
+ }
710
+ }
711
+ }
712
+
713
+ } // namespace
714
+
715
+ } // namespace pipnn
716
+
717
+ float PiPNN::effective_alpha(MetricType metric) const {
718
+ FAISS_THROW_IF_NOT_MSG(
719
+ metric == METRIC_L2 || metric == METRIC_INNER_PRODUCT,
720
+ "PiPNN supports only METRIC_L2 and METRIC_INNER_PRODUCT");
721
+ return alpha != 0 ? alpha : 1.2f;
722
+ }
723
+
724
+ void PiPNN::check_params() const {
725
+ FAISS_THROW_IF_NOT_FMT(R >= 1, "PiPNN: R must be >= 1, got %d", R);
726
+ FAISS_THROW_IF_NOT_FMT(
727
+ leaf_k >= 1 && leaf_k <= c_max,
728
+ "PiPNN: need 1 <= leaf_k <= c_max, got leaf_k=%d c_max=%d",
729
+ leaf_k,
730
+ c_max);
731
+ FAISS_THROW_IF_NOT_FMT(
732
+ hash_bits >= 1 && hash_bits <= 16,
733
+ "PiPNN: hash_bits must be in [1, 16], got %d",
734
+ hash_bits);
735
+ FAISS_THROW_IF_NOT_FMT(
736
+ reservoir_size >= 1 && reservoir_size <= 255,
737
+ "PiPNN: reservoir_size must be in [1, 255], got %d",
738
+ reservoir_size);
739
+ FAISS_THROW_IF_NOT_FMT(
740
+ R <= reservoir_size,
741
+ "PiPNN: need R <= reservoir_size, got R=%d reservoir_size=%d",
742
+ R,
743
+ reservoir_size);
744
+ for (int f : fanout) {
745
+ FAISS_THROW_IF_NOT_FMT(
746
+ f >= 1, "PiPNN: every fanout must be >= 1, got %d", f);
747
+ }
748
+ FAISS_THROW_IF_NOT_FMT(
749
+ c_min >= 1 && c_min <= c_max,
750
+ "PiPNN: need 1 <= c_min <= c_max, got c_min=%d c_max=%d",
751
+ c_min,
752
+ c_max);
753
+ FAISS_THROW_IF_NOT_FMT(
754
+ c_max <= pipnn::kMaxCMax,
755
+ "PiPNN: c_max must be <= %d, got %d",
756
+ pipnn::kMaxCMax,
757
+ c_max);
758
+ FAISS_THROW_IF_NOT_FMT(
759
+ leader_cap >= 1 && leader_cap <= pipnn::kMaxLeaderCap,
760
+ "PiPNN: leader_cap must be in [1, %d], got %d",
761
+ pipnn::kMaxLeaderCap,
762
+ leader_cap);
763
+ FAISS_THROW_IF_NOT_FMT(
764
+ leader_fraction >= 0 && leader_fraction <= 1,
765
+ "PiPNN: leader_fraction must be in [0, 1], got %g",
766
+ double(leader_fraction));
767
+ FAISS_THROW_IF_NOT_FMT(
768
+ alpha == 0 || (std::isfinite(alpha) && alpha >= 1),
769
+ "PiPNN: alpha must be 0 (default) or finite and >= 1, got %g",
770
+ double(alpha));
771
+ }
772
+
773
+ void PiPNN::build(const Index& storage, bool verbose) {
774
+ pipnn::detail::build_with_chunk(
775
+ *this, storage, pipnn::kReservoirChunk, verbose);
776
+ }
777
+
778
+ void PiPNN::search(
779
+ DistanceComputer& qdis,
780
+ int L,
781
+ idx_t k,
782
+ idx_t* I,
783
+ float* D,
784
+ VisitedTable& vt,
785
+ const IDSelector* sel) const {
786
+ FAISS_THROW_IF_NOT_MSG(k > 0, "PiPNN::search: k must be positive");
787
+ maxheap_heapify(size_t(k), D, I);
788
+ if (ntotal == 0) {
789
+ return;
790
+ }
791
+ // At most ntotal distinct nodes enter the beam.
792
+ const size_t cap = size_t(std::min(std::max(idx_t(L), k), ntotal));
793
+ // The codes of Flat and SQ storage, to prefetch.
794
+ DistanceComputer* base = &qdis;
795
+ if (auto* neg = dynamic_cast<NegativeDistanceComputer*>(base)) {
796
+ base = neg->basedis;
797
+ }
798
+ const auto* fc = dynamic_cast<const FlatCodesDistanceComputer*>(base);
799
+ const uint8_t* codes = fc ? fc->codes : nullptr;
800
+ const size_t code_size = fc ? fc->code_size : 0;
801
+ auto run = [&](auto& vtc) {
802
+ pipnn::beam_search(
803
+ *this, qdis, codes, code_size, cap, size_t(k), I, D, vtc, sel);
804
+ };
805
+ if (auto* vtv = dynamic_cast<VisitedTableVector*>(&vt)) {
806
+ run(*vtv);
807
+ } else {
808
+ run(dynamic_cast<VisitedTableSet&>(vt));
809
+ }
810
+ maxheap_reorder(size_t(k), D, I);
811
+ vt.advance();
812
+ }
813
+
814
+ void PiPNN::reset() {
815
+ // not graph.clear(): it asserts on the view build() publishes
816
+ graph = MaybeOwnedVector<int32_t>();
817
+ next_copy = MaybeOwnedVector<int32_t>();
818
+ entry_point = -1;
819
+ ntotal = 0;
820
+ }
821
+
822
+ } // namespace faiss