faiss 0.6.1 → 0.6.3

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 (186) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/lib/faiss/version.rb +1 -1
  4. data/vendor/faiss/faiss/AutoTune.cpp +3 -1
  5. data/vendor/faiss/faiss/Clustering.cpp +9 -1
  6. data/vendor/faiss/faiss/IVFlib.cpp +14 -3
  7. data/vendor/faiss/faiss/Index.h +2 -2
  8. data/vendor/faiss/faiss/IndexAdditiveQuantizer.cpp +9 -10
  9. data/vendor/faiss/faiss/IndexAdditiveQuantizerFastScan.cpp +2 -3
  10. data/vendor/faiss/faiss/IndexBinaryFromFloat.cpp +1 -2
  11. data/vendor/faiss/faiss/IndexBinaryHNSW.cpp +10 -12
  12. data/vendor/faiss/faiss/IndexBinaryHash.cpp +5 -9
  13. data/vendor/faiss/faiss/IndexBinaryIVF.cpp +5 -7
  14. data/vendor/faiss/faiss/IndexEDEN.cpp +273 -0
  15. data/vendor/faiss/faiss/IndexEDEN.h +57 -0
  16. data/vendor/faiss/faiss/IndexFastScan.cpp +15 -4
  17. data/vendor/faiss/faiss/IndexFlat.cpp +13 -50
  18. data/vendor/faiss/faiss/IndexHNSW.cpp +177 -148
  19. data/vendor/faiss/faiss/IndexIDMap.cpp +16 -3
  20. data/vendor/faiss/faiss/IndexIDMap.h +2 -0
  21. data/vendor/faiss/faiss/IndexIVF.cpp +19 -8
  22. data/vendor/faiss/faiss/IndexIVFAdditiveQuantizer.cpp +3 -3
  23. data/vendor/faiss/faiss/IndexIVFAdditiveQuantizerFastScan.cpp +3 -4
  24. data/vendor/faiss/faiss/IndexIVFEDEN.cpp +302 -0
  25. data/vendor/faiss/faiss/IndexIVFEDEN.h +70 -0
  26. data/vendor/faiss/faiss/IndexIVFFastScan.cpp +5 -6
  27. data/vendor/faiss/faiss/IndexIVFFlat.cpp +6 -5
  28. data/vendor/faiss/faiss/IndexIVFFlatPanorama.cpp +3 -3
  29. data/vendor/faiss/faiss/IndexIVFIndependentQuantizer.cpp +1 -1
  30. data/vendor/faiss/faiss/IndexIVFPQ.cpp +42 -25
  31. data/vendor/faiss/faiss/IndexIVFPQFastScan.cpp +0 -1
  32. data/vendor/faiss/faiss/IndexIVFPQR.cpp +2 -3
  33. data/vendor/faiss/faiss/IndexIVFRaBitQ.cpp +23 -62
  34. data/vendor/faiss/faiss/IndexIVFRaBitQFastScan.cpp +180 -76
  35. data/vendor/faiss/faiss/IndexIVFRaBitQFastScan.h +5 -4
  36. data/vendor/faiss/faiss/IndexIVFSpectralHash.cpp +8 -6
  37. data/vendor/faiss/faiss/IndexLSH.cpp +2 -3
  38. data/vendor/faiss/faiss/IndexLattice.cpp +5 -0
  39. data/vendor/faiss/faiss/IndexNNDescent.cpp +10 -3
  40. data/vendor/faiss/faiss/IndexNSG.cpp +8 -4
  41. data/vendor/faiss/faiss/IndexPQ.cpp +6 -8
  42. data/vendor/faiss/faiss/IndexPreTransform.cpp +15 -0
  43. data/vendor/faiss/faiss/IndexRaBitQ.cpp +2 -2
  44. data/vendor/faiss/faiss/IndexRaBitQFastScan.cpp +1 -2
  45. data/vendor/faiss/faiss/IndexRaBitQFastScan.h +5 -1
  46. data/vendor/faiss/faiss/IndexRefine.cpp +30 -1
  47. data/vendor/faiss/faiss/IndexReplicas.cpp +1 -2
  48. data/vendor/faiss/faiss/IndexScalarQuantizer.cpp +68 -6
  49. data/vendor/faiss/faiss/IndexScalarQuantizer.h +10 -0
  50. data/vendor/faiss/faiss/IndexShards.cpp +2 -2
  51. data/vendor/faiss/faiss/IndexShardsIVF.cpp +2 -2
  52. data/vendor/faiss/faiss/MetaIndexes.cpp +2 -4
  53. data/vendor/faiss/faiss/SuperKMeans.cpp +256 -240
  54. data/vendor/faiss/faiss/SuperKMeans.h +30 -0
  55. data/vendor/faiss/faiss/VectorTransform.cpp +33 -2
  56. data/vendor/faiss/faiss/clone_index.cpp +5 -0
  57. data/vendor/faiss/faiss/cppcontrib/SaDecodeKernels.h +1 -1
  58. data/vendor/faiss/faiss/cppcontrib/sa_decode/Level2-neon-inl.h +902 -12
  59. data/vendor/faiss/faiss/cppcontrib/sa_decode/PQ-neon-inl.h +702 -10
  60. data/vendor/faiss/faiss/factory_tools.cpp +51 -4
  61. data/vendor/faiss/faiss/gpu/GpuCloner.cpp +11 -11
  62. data/vendor/faiss/faiss/gpu/GpuIndex.h +34 -11
  63. data/vendor/faiss/faiss/gpu/GpuIndexCagra.h +47 -0
  64. data/vendor/faiss/faiss/gpu/GpuIndexIVF.h +17 -0
  65. data/vendor/faiss/faiss/gpu/GpuIndexIVFScalarQuantizer.h +16 -0
  66. data/vendor/faiss/faiss/gpu/GpuResources.h +3 -2
  67. data/vendor/faiss/faiss/gpu/StandardGpuResources.cpp +11 -12
  68. data/vendor/faiss/faiss/gpu/StandardGpuResources.h +3 -3
  69. data/vendor/faiss/faiss/gpu/perf/PerfClustering.cpp +1 -1
  70. data/vendor/faiss/faiss/gpu/perf/PerfIVFPQAdd.cpp +2 -2
  71. data/vendor/faiss/faiss/gpu/test/TestGpuIndexIVFScalarQuantizer.cpp +180 -0
  72. data/vendor/faiss/faiss/gpu_metal/MetalDistance.h +87 -0
  73. data/vendor/faiss/faiss/gpu_metal/MetalIndex.h +7 -0
  74. data/vendor/faiss/faiss/gpu_metal/MetalIndexIVFFlat.h +177 -0
  75. data/vendor/faiss/faiss/gpu_metal/MetalIndexIVFPQ.h +88 -0
  76. data/vendor/faiss/faiss/gpu_metal/MetalKernels.h +48 -3
  77. data/vendor/faiss/faiss/gpu_metal/MetalPythonBridge.h +45 -0
  78. data/vendor/faiss/faiss/gpu_metal/impl/MetalIVFFlat.h +193 -0
  79. data/vendor/faiss/faiss/gpu_metal/impl/MetalIVFPQ.h +134 -0
  80. data/vendor/faiss/faiss/impl/ClusteringInitialization.cpp +2 -2
  81. data/vendor/faiss/faiss/impl/DistanceComputer.h +34 -0
  82. data/vendor/faiss/faiss/impl/EDENQuantizer.h +119 -0
  83. data/vendor/faiss/faiss/impl/HNSW.cpp +658 -344
  84. data/vendor/faiss/faiss/impl/HNSW.h +51 -13
  85. data/vendor/faiss/faiss/impl/LocalSearchQuantizer.cpp +2 -2
  86. data/vendor/faiss/faiss/impl/NSG.cpp +18 -12
  87. data/vendor/faiss/faiss/impl/Panorama.h +20 -7
  88. data/vendor/faiss/faiss/impl/PolysemousTraining.cpp +152 -84
  89. data/vendor/faiss/faiss/impl/ProductQuantizer.cpp +59 -24
  90. data/vendor/faiss/faiss/impl/RaBitQUtils.cpp +45 -37
  91. data/vendor/faiss/faiss/impl/RaBitQUtils.h +35 -0
  92. data/vendor/faiss/faiss/impl/RaBitQuantizer.cpp +175 -68
  93. data/vendor/faiss/faiss/impl/RaBitQuantizer.h +19 -0
  94. data/vendor/faiss/faiss/impl/RaBitQuantizerMultiBit.cpp +2 -11
  95. data/vendor/faiss/faiss/impl/ResultHandler.h +26 -31
  96. data/vendor/faiss/faiss/impl/ScalarQuantizer.cpp +522 -58
  97. data/vendor/faiss/faiss/impl/ScalarQuantizer.h +70 -0
  98. data/vendor/faiss/faiss/impl/ThreadedIndex-inl.h +2 -2
  99. data/vendor/faiss/faiss/impl/VisitedTable.cpp +33 -13
  100. data/vendor/faiss/faiss/impl/VisitedTable.h +88 -33
  101. data/vendor/faiss/faiss/impl/binary_hamming/IndexBinaryIVF_impl.h +1 -1
  102. data/vendor/faiss/faiss/impl/binary_hamming/avx2.cpp +4 -4
  103. data/vendor/faiss/faiss/impl/fast_scan/dispatching.h +38 -3
  104. data/vendor/faiss/faiss/impl/hnsw/LockVector.cpp +1 -1
  105. data/vendor/faiss/faiss/impl/hnsw/MinimaxHeap.cpp +35 -43
  106. data/vendor/faiss/faiss/impl/hnsw/MinimaxHeap.h +64 -15
  107. data/vendor/faiss/faiss/impl/hnsw/avx2.cpp +86 -40
  108. data/vendor/faiss/faiss/impl/hnsw/avx512.cpp +81 -50
  109. data/vendor/faiss/faiss/impl/index_read.cpp +476 -75
  110. data/vendor/faiss/faiss/impl/index_write.cpp +56 -4
  111. data/vendor/faiss/faiss/impl/io_macros.h +25 -0
  112. data/vendor/faiss/faiss/impl/lattice_Zn.cpp +8 -9
  113. data/vendor/faiss/faiss/impl/platform_macros.h +15 -9
  114. data/vendor/faiss/faiss/impl/polysemous_training/avx512.cpp +284 -0
  115. data/vendor/faiss/faiss/impl/polysemous_training/dispatch.h +115 -0
  116. data/vendor/faiss/faiss/impl/pq_code_distance/IVFPQ_QueryTables.cpp +0 -1
  117. data/vendor/faiss/faiss/impl/pq_code_distance/PQDistanceComputer_impl.h +26 -15
  118. data/vendor/faiss/faiss/impl/pq_code_distance/avx2.cpp +6 -4
  119. data/vendor/faiss/faiss/impl/pq_code_distance/avx512.cpp +2 -0
  120. data/vendor/faiss/faiss/impl/pq_code_distance/neon.cpp +2 -0
  121. data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-generic.cpp +20 -0
  122. data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-inl.h +36 -0
  123. data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-sve.cpp +5 -0
  124. data/vendor/faiss/faiss/impl/pq_code_distance/pq_scan_impl.h +105 -0
  125. data/vendor/faiss/faiss/impl/pq_code_distance/rvv.cpp +2 -0
  126. data/vendor/faiss/faiss/impl/result_handler/ResultHandler.cpp +195 -0
  127. data/vendor/faiss/faiss/impl/result_handler/avx2.cpp +133 -0
  128. data/vendor/faiss/faiss/impl/result_handler/avx512.cpp +281 -0
  129. data/vendor/faiss/faiss/impl/scalar_quantizer/EDENQuantizer-avx2.cpp +72 -0
  130. data/vendor/faiss/faiss/impl/scalar_quantizer/EDENQuantizer-avx512.cpp +228 -0
  131. data/vendor/faiss/faiss/impl/scalar_quantizer/EDENQuantizer.cpp +882 -0
  132. data/vendor/faiss/faiss/impl/scalar_quantizer/distance_computers.h +6 -0
  133. data/vendor/faiss/faiss/impl/scalar_quantizer/quantizers.h +336 -26
  134. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx2.cpp +331 -32
  135. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512-impl.h +553 -0
  136. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512-spr.cpp +558 -0
  137. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512.cpp +284 -45
  138. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-dispatch.h +502 -3
  139. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-neon.cpp +157 -32
  140. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-rvv.cpp +26 -0
  141. data/vendor/faiss/faiss/impl/simd_dispatch.h +86 -8
  142. data/vendor/faiss/faiss/index_factory.cpp +37 -7
  143. data/vendor/faiss/faiss/index_io.h +16 -0
  144. data/vendor/faiss/faiss/invlists/DirectMap.cpp +5 -2
  145. data/vendor/faiss/faiss/invlists/InvertedLists.cpp +15 -15
  146. data/vendor/faiss/faiss/invlists/InvertedLists.h +2 -2
  147. data/vendor/faiss/faiss/invlists/OnDiskInvertedLists.cpp +19 -4
  148. data/vendor/faiss/faiss/python/python_callbacks.cpp +3 -1
  149. data/vendor/faiss/faiss/svs/IndexSVSFaissUtils.h +60 -0
  150. data/vendor/faiss/faiss/svs/IndexSVSFlat.cpp +26 -1
  151. data/vendor/faiss/faiss/svs/IndexSVSFlat.h +13 -0
  152. data/vendor/faiss/faiss/svs/IndexSVSIVF.cpp +1 -1
  153. data/vendor/faiss/faiss/svs/IndexSVSIVFLeanVec.cpp +1 -1
  154. data/vendor/faiss/faiss/svs/IndexSVSVamana.cpp +150 -23
  155. data/vendor/faiss/faiss/svs/IndexSVSVamana.h +30 -7
  156. data/vendor/faiss/faiss/svs/IndexSVSVamanaLVQ.cpp +3 -2
  157. data/vendor/faiss/faiss/svs/IndexSVSVamanaLVQ.h +2 -1
  158. data/vendor/faiss/faiss/svs/IndexSVSVamanaLeanVec.cpp +65 -25
  159. data/vendor/faiss/faiss/svs/IndexSVSVamanaLeanVec.h +3 -2
  160. data/vendor/faiss/faiss/utils/approx_topk_hamming/approx_topk_hamming.h +1 -1
  161. data/vendor/faiss/faiss/utils/bf16.h +34 -0
  162. data/vendor/faiss/faiss/utils/distances.cpp +14 -2
  163. data/vendor/faiss/faiss/utils/distances_simd.cpp +4 -4
  164. data/vendor/faiss/faiss/utils/extra_distances.cpp +4 -14
  165. data/vendor/faiss/faiss/utils/extra_distances.h +1 -2
  166. data/vendor/faiss/faiss/utils/hamming.cpp +9 -9
  167. data/vendor/faiss/faiss/utils/hamming_distance/hamming_avx2.cpp +2 -1
  168. data/vendor/faiss/faiss/utils/hamming_distance/hamming_avx512_spr.cpp +15 -0
  169. data/vendor/faiss/faiss/utils/hamming_distance/hamming_computer-avx512.h +6 -30
  170. data/vendor/faiss/faiss/utils/hamming_distance/hamming_computer-avx512_spr.h +171 -0
  171. data/vendor/faiss/faiss/utils/partitioning.cpp +0 -2
  172. data/vendor/faiss/faiss/utils/quantize_lut.cpp +29 -8
  173. data/vendor/faiss/faiss/utils/rabitq_simd.h +202 -0
  174. data/vendor/faiss/faiss/utils/simd_impl/distances_avx2.cpp +0 -1
  175. data/vendor/faiss/faiss/utils/simd_impl/distances_avx512.cpp +263 -15
  176. data/vendor/faiss/faiss/utils/simd_impl/distances_rvv.cpp +160 -18
  177. data/vendor/faiss/faiss/utils/simd_impl/partitioning_simdlib256.h +14 -68
  178. data/vendor/faiss/faiss/utils/simd_impl/rabitq_avx2.cpp +245 -0
  179. data/vendor/faiss/faiss/utils/simd_impl/rabitq_avx512.cpp +273 -0
  180. data/vendor/faiss/faiss/utils/simd_impl/rabitq_avx512_spr.cpp +435 -0
  181. data/vendor/faiss/faiss/utils/simd_impl/rabitq_neon.cpp +11 -0
  182. data/vendor/faiss/faiss/utils/simd_impl/rabitq_rvv.cpp +143 -6
  183. data/vendor/faiss/faiss/utils/simd_levels.cpp +56 -2
  184. data/vendor/faiss/faiss/utils/simd_levels.h +14 -0
  185. data/vendor/faiss/faiss/utils/utils.cpp +9 -27
  186. metadata +27 -2
@@ -7,6 +7,8 @@
7
7
 
8
8
  #pragma once
9
9
 
10
+ #include <cstring>
11
+
10
12
  #include <faiss/impl/AuxIndexStructures.h>
11
13
  #include <faiss/impl/DistanceComputer.h>
12
14
  #include <faiss/impl/Quantizer.h>
@@ -39,6 +41,18 @@ struct ScalarQuantizer : Quantizer {
39
41
  QT_3bit_tqmse, ///< TurboQuant MSE-optimized, 3 bits per component
40
42
  QT_4bit_tqmse, ///< TurboQuant MSE-optimized, 4 bits per component
41
43
  QT_8bit_tqmse, ///< TurboQuant MSE-optimized, 8 bits per component
44
+ QT_2bit_tq, ///< Full TurboQuant (1-bit MSE + 1-bit QJL + factors)
45
+ QT_3bit_tq, ///< Full TurboQuant (2-bit MSE + 1-bit QJL + factors)
46
+ QT_4bit_tq, ///< Full TurboQuant (3-bit MSE + 1-bit QJL + factors)
47
+ QT_5bit_tq, ///< Full TurboQuant (4-bit MSE + 1-bit QJL + factors)
48
+ QT_1bit_eden, ///< EDEN Lloyd-Max scalar code, 1 bit per component
49
+ QT_2bit_eden, ///< EDEN Lloyd-Max scalar code, 2 bits per component
50
+ QT_3bit_eden, ///< EDEN Lloyd-Max scalar code, 3 bits per component
51
+ QT_4bit_eden, ///< EDEN Lloyd-Max scalar code, 4 bits per component
52
+ QT_5bit_eden, ///< EDEN Lloyd-Max scalar code, 5 bits per component
53
+ QT_6bit_eden, ///< EDEN Lloyd-Max scalar code, 6 bits per component
54
+ QT_7bit_eden, ///< EDEN Lloyd-Max scalar code, 7 bits per component
55
+ QT_8bit_eden, ///< EDEN Lloyd-Max scalar code, 8 bits per component
42
56
  QT_count
43
57
  };
44
58
 
@@ -129,8 +143,64 @@ struct ScalarQuantizer : Quantizer {
129
143
  float distance_to_code(const uint8_t* code) final {
130
144
  return query_to_code(code);
131
145
  }
146
+
147
+ void distance_to_code_batch_4(
148
+ const uint8_t* c1,
149
+ const uint8_t* c2,
150
+ const uint8_t* c3,
151
+ const uint8_t* c4,
152
+ float& d1,
153
+ float& d2,
154
+ float& d3,
155
+ float& d4) override {
156
+ query_to_codes_batch_4(c1, c2, c3, c4, d1, d2, d3, d4);
157
+ }
158
+ };
159
+
160
+ /// TurboQuant full (QT_*_tq) refinement state, isolated from the
161
+ /// main ScalarQuantizer to avoid polluting it with TQ-specific data.
162
+ struct TurboQuantRefine {
163
+ static bool is_turboq_full(QuantizerType qt) {
164
+ return qt >= QT_2bit_tq && qt <= QT_5bit_tq;
165
+ }
166
+
167
+ static void pack_seed(uint64_t seed, float out[2]) {
168
+ static_assert(sizeof(uint64_t) == 2 * sizeof(float));
169
+ std::memcpy(out, &seed, sizeof(uint64_t));
170
+ }
171
+
172
+ static uint64_t unpack_seed(float lo, float hi) {
173
+ float tmp[2] = {lo, hi};
174
+ uint64_t s;
175
+ static_assert(sizeof(uint64_t) == 2 * sizeof(float));
176
+ std::memcpy(&s, tmp, sizeof(uint64_t));
177
+ return s;
178
+ }
179
+
180
+ uint8_t qjl_type = 0;
181
+ uint64_t seed = 42;
182
+ size_t padded_d = 0;
183
+ std::vector<float> fwht_signs;
184
+ std::vector<float> rr_matrix;
185
+ size_t nb_bits_lo = 0;
186
+ size_t n_hi_dims = 0;
187
+
188
+ void init_projection(size_t d);
189
+ bool use_fwht() const {
190
+ return qjl_type == 0;
191
+ }
192
+
193
+ struct DistanceComputer : SQDistanceComputer {
194
+ virtual void configure(uint8_t qb, bool int_qjl) = 0;
195
+ virtual void set_prescreen_threshold(
196
+ const float* t,
197
+ bool minimize) = 0;
198
+ virtual void clear_prescreen_threshold() = 0;
199
+ };
132
200
  };
133
201
 
202
+ TurboQuantRefine turboq_refine;
203
+
134
204
  SQDistanceComputer* get_distance_computer(
135
205
  MetricType metric = METRIC_L2) const;
136
206
 
@@ -70,8 +70,8 @@ void ThreadedIndex<IndexT>::addIndex(IndexT* index) {
70
70
 
71
71
  // Make sure this index is not duplicated
72
72
  for (auto& p : indices_) {
73
- FAISS_THROW_IF_NOT_MSG(
74
- p.first != index,
73
+ FAISS_THROW_IF_MSG(
74
+ p.first == index,
75
75
  "addIndex: attempting to add index "
76
76
  "that is already in the collection");
77
77
  }
@@ -15,22 +15,42 @@ namespace faiss {
15
15
  // advance() is O(1) except every 250 calls, which are O(size).
16
16
  // The hash set strategy is a constant factor slower for get()/set(),
17
17
  // but O(1) to construct and O(visits) to advance.
18
- // A size of ~1M seems to be the threshold where the hash set wins.
19
- size_t visited_table_hashset_threshold = 500000;
20
-
21
- VisitedTable::VisitedTable(size_t size, std::optional<bool> use_hashset)
22
- : visno(use_hashset.value_or(size >= visited_table_hashset_threshold)
23
- ? 0
24
- : 1) {
25
- if (visno != 0) {
26
- visited.resize(size, 0);
18
+ // 10M is only a current estimated threshold, not a proven crossover: we are not
19
+ // sure the array still wins at 10M. The point where the array stops paying off
20
+ // varies by dataset (it shifts with dimension, working-set / cache pressure,
21
+ // etc.), so this is a coarse default that should eventually be replaced by
22
+ // smarter per-index tuning.
23
+ size_t visited_table_hashset_threshold = 10000000;
24
+
25
+ std::unique_ptr<VisitedTable> VisitedTable::create(
26
+ size_t size,
27
+ std::optional<bool> use_hashset) {
28
+ bool use_set =
29
+ use_hashset.value_or(size >= visited_table_hashset_threshold);
30
+ if (use_set) {
31
+ return std::make_unique<VisitedTableSet>();
32
+ }
33
+ return std::make_unique<VisitedTableVector>(size);
34
+ }
35
+
36
+ VisitedTable& VisitedTable::get_reusable(
37
+ size_t size,
38
+ std::optional<bool> use_hashset) {
39
+ bool use_set =
40
+ use_hashset.value_or(size >= visited_table_hashset_threshold);
41
+ if (use_set) {
42
+ thread_local VisitedTableSet tls_set;
43
+ tls_set.advance();
44
+ return tls_set;
27
45
  }
46
+ thread_local VisitedTableVector tls_vec(0);
47
+ tls_vec.ensure_size(size);
48
+ tls_vec.advance();
49
+ return tls_vec;
28
50
  }
29
51
 
30
- void VisitedTable::advance() {
31
- if (visno == 0) {
32
- visited_set.clear();
33
- } else if (visno < 254) {
52
+ void VisitedTableVector::advance() {
53
+ if (visno < 254) {
34
54
  // 254 rather than 255 because sometimes we use visno and visno+1
35
55
  ++visno;
36
56
  } else {
@@ -10,6 +10,7 @@
10
10
 
11
11
  #include <stdint.h>
12
12
 
13
+ #include <memory>
13
14
  #include <optional>
14
15
  #include <unordered_set>
15
16
  #include <vector>
@@ -21,54 +22,108 @@ namespace faiss {
21
22
 
22
23
  FAISS_API extern size_t visited_table_hashset_threshold;
23
24
 
24
- /// A fast, reusable Visited Set for graph search algorithms.
25
+ /// Abstract base class for a fast, reusable Visited Set for graph search
26
+ /// algorithms.
25
27
  struct VisitedTable {
26
- std::vector<uint8_t> visited;
27
- std::unordered_set<size_t> visited_set;
28
- uint8_t visno; // 0 if using visited_set, 1..250 if using vector.
28
+ virtual ~VisitedTable() = default;
29
+
30
+ /// set flag #no to true, return whether this changed it.
31
+ virtual bool set(size_t no) = 0;
32
+
33
+ /// get flag #no
34
+ virtual bool get(size_t no) const = 0;
35
+
36
+ /// prefetch flag #no
37
+ virtual void prefetch(size_t no) const = 0;
38
+
39
+ /// pre-allocate bucket space to avoid rehashing during repeated set() calls
40
+ virtual void reserve(size_t /*n*/) {}
41
+
42
+ /// reset all flags to false
43
+ virtual void advance() = 0;
29
44
 
30
- // If use_hashset is nullopt, the use of a hashset will be determined by
31
- // size >= visited_table_hashset_threshold.
32
- explicit VisitedTable(
45
+ /// Factory method to create appropriate implementation.
46
+ /// If use_hashset is nullopt, the use of a hashset will be determined by
47
+ /// size >= visited_table_hashset_threshold.
48
+ static std::unique_ptr<VisitedTable> create(
33
49
  size_t size,
34
50
  std::optional<bool> use_hashset = std::nullopt);
35
51
 
36
- /// set flag #no to true, return whether this changed it.
37
- bool set(size_t no) {
38
- if (visno == 0) {
39
- return visited_set.insert(no).second;
40
- } else if (visited[no] == visno) {
41
- return false;
42
- } else {
43
- visited[no] = visno;
44
- return true;
45
- }
52
+ /// Returns a thread-local, reusable table sized for at least `size` and
53
+ /// reset to a clean state. Unlike create(), it does not allocate on each
54
+ /// call: the O(size) versioned array is allocated once per thread and
55
+ /// reused across searches, avoiding a per-search alloc+zero of the whole
56
+ /// array when a static index is searched repeatedly.
57
+ ///
58
+ /// The returned reference is owned by thread-local storage: do not delete
59
+ /// it and do not use it beyond the current search on the calling thread.
60
+ static VisitedTable& get_reusable(
61
+ size_t size,
62
+ std::optional<bool> use_hashset = std::nullopt);
63
+ };
64
+
65
+ /// Set-based implementation using unordered_set.
66
+ /// O(1) to construct and O(visits) to advance.
67
+ struct VisitedTableSet FAISS_FINAL : VisitedTable {
68
+ std::unordered_set<size_t> visited_set;
69
+
70
+ VisitedTableSet() = default;
71
+
72
+ bool set(size_t no) final {
73
+ return visited_set.insert(no).second;
46
74
  }
47
75
 
48
- /// pre-allocate bucket space to avoid rehashing during repeated set() calls
49
- void reserve(size_t n) {
50
- if (visno == 0) {
51
- visited_set.reserve(n);
52
- }
76
+ bool get(size_t no) const final {
77
+ return visited_set.count(no) != 0;
53
78
  }
54
79
 
55
- /// get flag #no
56
- bool get(size_t no) const {
57
- if (visno == 0) {
58
- return visited_set.count(no) != 0;
59
- } else {
60
- return visited[no] == visno;
80
+ void prefetch(size_t /*no*/) const final {
81
+ // No-op for set-based implementation
82
+ }
83
+
84
+ void reserve(size_t n) final {
85
+ visited_set.reserve(n);
86
+ }
87
+
88
+ void advance() final {
89
+ visited_set.clear();
90
+ }
91
+ };
92
+
93
+ /// Vector-based implementation using a versioned byte array.
94
+ /// Faster for get()/set(), but O(size) to initialize.
95
+ /// advance() is O(1) except every 250 calls, which are O(size).
96
+ struct VisitedTableVector FAISS_FINAL : VisitedTable {
97
+ std::vector<uint8_t> visited;
98
+ uint8_t visno{1}; // Version number, 1..254
99
+
100
+ explicit VisitedTableVector(size_t size) : visited(size, 0) {}
101
+
102
+ /// Grow so indices in [0, size) are valid; new slots read as unvisited.
103
+ /// Never shrinks, so capacity is retained when the table is reused.
104
+ void ensure_size(size_t size) {
105
+ if (visited.size() < size) {
106
+ visited.resize(size, 0);
61
107
  }
62
108
  }
63
109
 
64
- void prefetch(size_t no) const {
65
- if (visno != 0) {
66
- prefetch_L2(&visited[no]);
110
+ bool set(size_t no) final {
111
+ if (visited[no] == visno) {
112
+ return false;
67
113
  }
114
+ visited[no] = visno;
115
+ return true;
68
116
  }
69
117
 
70
- /// reset all flags to false
71
- void advance();
118
+ bool get(size_t no) const final {
119
+ return visited[no] == visno;
120
+ }
121
+
122
+ void prefetch(size_t no) const final {
123
+ prefetch_L2(&visited[no]);
124
+ }
125
+
126
+ void advance() final;
72
127
  };
73
128
 
74
129
  } // namespace faiss
@@ -292,7 +292,7 @@ void search_knn_hamming_per_invlist(
292
292
  nprobe = std::min((idx_t)ivf->nlist, nprobe);
293
293
  idx_t max_codes = params ? params->max_codes : ivf->max_codes;
294
294
  FAISS_THROW_IF_NOT(max_codes == 0);
295
- FAISS_THROW_IF_NOT(!store_pairs);
295
+ FAISS_THROW_IF_MSG(store_pairs, "store_pairs is not supported here");
296
296
 
297
297
  // reorder buckets
298
298
  std::vector<int64_t> lims(n + 1);
@@ -9,17 +9,17 @@
9
9
 
10
10
  #define THE_SIMD_LEVEL SIMDLevel::AVX2
11
11
 
12
- // NOLINTNEXTLINE(facebook-hte-InlineHeader)
12
+ // NOLINTNEXTLINE(facebook-hte-InlineHeader,facebook-unused-include-check)
13
13
  #include <faiss/utils/hamming_distance/hamming_computer-avx2.h>
14
14
 
15
15
  // NOLINTNEXTLINE(facebook-hte-InlineHeader)
16
- #include <faiss/impl/binary_hamming/IndexBinaryHNSW_impl.h>
16
+ #include <faiss/impl/binary_hamming/IndexBinaryHNSW_impl.h> // IWYU pragma: keep
17
17
  // NOLINTNEXTLINE(facebook-hte-InlineHeader)
18
- #include <faiss/impl/binary_hamming/IndexBinaryHash_impl.h>
18
+ #include <faiss/impl/binary_hamming/IndexBinaryHash_impl.h> // IWYU pragma: keep
19
19
  // NOLINTNEXTLINE(facebook-hte-InlineHeader)
20
20
  #include <faiss/impl/binary_hamming/IndexBinaryIVF_impl.h>
21
21
  // NOLINTNEXTLINE(facebook-hte-InlineHeader)
22
- #include <faiss/impl/binary_hamming/IndexIVFSpectralHash_impl.h>
22
+ #include <faiss/impl/binary_hamming/IndexIVFSpectralHash_impl.h> // IWYU pragma: keep
23
23
  // NOLINTNEXTLINE(facebook-hte-InlineHeader)
24
24
  #include <faiss/impl/binary_hamming/IndexPQ_impl.h>
25
25
 
@@ -33,6 +33,7 @@
33
33
 
34
34
  #include <faiss/impl/fast_scan/accumulate_loops.h>
35
35
  #include <faiss/impl/fast_scan/fast_scan.h>
36
+ #include <faiss/utils/simd_levels.h>
36
37
 
37
38
  #if defined(COMPILE_SIMD_AVX512) && defined(__AVX512F__)
38
39
  #include <faiss/impl/fast_scan/accumulate_loops_512.h>
@@ -48,7 +49,9 @@ using namespace simd_result_handlers;
48
49
  * so callers don't need to know the handler type.
49
50
  ***************************************************************/
50
51
 
51
- template <class Handler>
52
+ // SIMDLevel SL = THE_LEVEL_TO_DISPATCH added to make the mangled
53
+ // symbol name unique per translation unit.
54
+ template <class Handler, SIMDLevel SL = THE_LEVEL_TO_DISPATCH>
52
55
  struct ScannerMixIn : FastScanCodeScanner {
53
56
  Handler handler_;
54
57
 
@@ -112,8 +115,40 @@ struct ScannerMixIn : FastScanCodeScanner {
112
115
  constexpr bool use_avx512_qbs = false;
113
116
  #endif
114
117
  if constexpr (use_avx512_qbs) {
115
- // Use 512-bit QBS kernels with properly-leveled scalers.
116
- if (pq2x4_scale) {
118
+ // AMD Zen 4 / Zen 4c ("Bergamo", family 0x19) split 512-bit
119
+ // ops over a 256-bit datapath, so the 512-bit QBS kernel yields no
120
+ // throughput gain but pays extra per-block LUT-assembly and
121
+ // cross-lane reduction overhead (measured ~14% search regression
122
+ // for PQ8x4fs / PQ16x4fs). Route those CPUs to the 256-bit (AVX2)
123
+ // QBS kernel instead -- same output, no downside on Zen 4. This is
124
+ // a process-constant runtime branch, hoisted out of the inner
125
+ // accumulate loop. Intel AVX-512 keeps the 512-bit kernel.
126
+ if (SIMDConfig::avx512_split) {
127
+ if (pq2x4_scale) {
128
+ NormTableScaler<SIMDLevel::AVX2> scaler(pq2x4_scale);
129
+ pq4_accumulate_loop_qbs_fixed_scaler_256<SIMDLevel::AVX2>(
130
+ qbs,
131
+ nb,
132
+ nsq,
133
+ codes,
134
+ LUT,
135
+ handler_,
136
+ scaler,
137
+ block_stride);
138
+ } else {
139
+ DummyScaler<SIMDLevel::AVX2> dummy;
140
+ pq4_accumulate_loop_qbs_fixed_scaler_256<SIMDLevel::AVX2>(
141
+ qbs,
142
+ nb,
143
+ nsq,
144
+ codes,
145
+ LUT,
146
+ handler_,
147
+ dummy,
148
+ block_stride);
149
+ }
150
+ } else if (pq2x4_scale) {
151
+ // Use 512-bit QBS kernels with properly-leveled scalers.
117
152
  NormTableScaler<THE_LEVEL_TO_DISPATCH> scaler(pq2x4_scale);
118
153
  pq4_accumulate_loop_qbs_fixed_scaler_512(
119
154
  qbs,
@@ -30,7 +30,7 @@ void LockVector::prepare(size_t new_size) {
30
30
  // Just destroy old and init fresh; omp_lock_t is not copyable.
31
31
  clear();
32
32
  data_ = static_cast<omp_lock_t*>(malloc(new_cap * sizeof(omp_lock_t)));
33
- FAISS_THROW_IF_NOT(data_ != nullptr);
33
+ FAISS_THROW_IF_NOT(data_);
34
34
  capacity_ = new_cap;
35
35
  }
36
36
  for (size_t i = size_; i < new_size; i++) {
@@ -5,39 +5,32 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
- #include <cmath>
9
-
10
8
  #include <faiss/impl/hnsw/MinimaxHeap.h>
11
9
 
12
- #include <cassert>
13
-
14
10
  #include <faiss/impl/simd_dispatch.h>
15
11
 
16
12
  namespace faiss {
17
13
 
18
- void MinimaxHeap::push(storage_idx_t i, float v) {
19
- // Treat NaN distances as infinitely far away so heap ordering is preserved.
20
- if (std::isnan(v)) {
21
- v = HC::neutral();
22
- }
23
- if (k == n) {
24
- if (v >= dis[0]) {
25
- return;
26
- }
27
- if (ids[0] != -1) {
28
- --nvalid;
29
- }
30
- faiss::heap_pop<HC>(k--, dis.data(), ids.data());
31
- }
32
- faiss::heap_push<HC>(++k, dis.data(), ids.data(), v, i);
33
- ++nvalid;
14
+ // Runtime-dispatched pop_min (NONE + AVX2 + AVX512 only).
15
+ constexpr int MINIMAX_HEAP_SIMD_LEVELS = (1 << int(SIMDLevel::NONE)) |
16
+ (1 << int(SIMDLevel::AVX2)) | (1 << int(SIMDLevel::AVX512));
17
+
18
+ template <class HC_>
19
+ int MinimaxHeapT<HC_>::pop_min(float* vmin_out) {
20
+ return with_selected_simd_levels<MINIMAX_HEAP_SIMD_LEVELS>(
21
+ [&]<SIMDLevel SL>() {
22
+ return pop_min_tpl<HC_, SL>(this, vmin_out);
23
+ });
34
24
  }
35
25
 
36
- // Scalar (NONE) specialization of pop_min_tpl
37
- template <>
38
- int MinimaxHeap::pop_min_tpl<SIMDLevel::NONE>(float* vmin_out) {
26
+ // Primary-template scalar implementation. Used directly when SL==NONE
27
+ template <class HC>
28
+ int pop_min_simd_none(MinimaxHeapT<HC>* heap, float* vmin_out) {
29
+ int k = heap->k;
30
+ int* ids = heap->ids.data();
31
+ float* dis = heap->dis.data();
39
32
  assert(k > 0);
40
- // returns min. This is an O(n) operation
33
+ // Returns the "best" entry. This is an O(n) operation.
41
34
  int i = k - 1;
42
35
  while (i >= 0) {
43
36
  if (ids[i] != -1) {
@@ -52,7 +45,8 @@ int MinimaxHeap::pop_min_tpl<SIMDLevel::NONE>(float* vmin_out) {
52
45
  float vmin = dis[i];
53
46
  i--;
54
47
  while (i >= 0) {
55
- if (ids[i] != -1 && dis[i] < vmin) {
48
+ // HC::cmp(vmin, dis[i]) "dis[i] is better than vmin".
49
+ if (ids[i] != -1 && HC::cmp(vmin, dis[i])) {
56
50
  vmin = dis[i];
57
51
  imin = i;
58
52
  }
@@ -63,29 +57,27 @@ int MinimaxHeap::pop_min_tpl<SIMDLevel::NONE>(float* vmin_out) {
63
57
  }
64
58
  int ret = ids[imin];
65
59
  ids[imin] = -1;
66
- --nvalid;
67
-
60
+ --heap->nvalid;
68
61
  return ret;
69
62
  }
70
63
 
71
- // Runtime-dispatched pop_min (NONE + AVX2 + AVX512 only)
72
- constexpr int MINIMAX_HEAP_SIMD_LEVELS = (1 << int(SIMDLevel::NONE)) |
73
- (1 << int(SIMDLevel::AVX2)) | (1 << int(SIMDLevel::AVX512));
74
-
75
- int MinimaxHeap::pop_min(float* vmin_out) {
76
- return with_selected_simd_levels<MINIMAX_HEAP_SIMD_LEVELS>(
77
- [&]<SIMDLevel SL>() { return pop_min_tpl<SL>(vmin_out); });
64
+ // declare for min and max heap at simd level NONE
65
+ template <>
66
+ int pop_min_tpl<CMin<float, int32_t>, SIMDLevel::NONE>(
67
+ MinimaxHeapT<CMin<float, int32_t>>* heap,
68
+ float* vmin_out) {
69
+ return pop_min_simd_none(heap, vmin_out);
78
70
  }
79
71
 
80
- int MinimaxHeap::count_below(float thresh) {
81
- int n_below = 0;
82
- for (int i = 0; i < k; i++) {
83
- if (dis[i] < thresh) {
84
- n_below++;
85
- }
86
- }
87
-
88
- return n_below;
72
+ template <>
73
+ int pop_min_tpl<CMax<float, int32_t>, SIMDLevel::NONE>(
74
+ MinimaxHeapT<CMax<float, int32_t>>* heap,
75
+ float* vmin_out) {
76
+ return pop_min_simd_none(heap, vmin_out);
89
77
  }
90
78
 
79
+ // Explicit instantiations of pop_min for the two HC variants
80
+ template int MinimaxHeapT<CMax<float, int32_t>>::pop_min(float*);
81
+ template int MinimaxHeapT<CMin<float, int32_t>>::pop_min(float*);
82
+
91
83
  } // namespace faiss
@@ -7,21 +7,30 @@
7
7
 
8
8
  #pragma once
9
9
 
10
+ #include <cassert>
11
+ #include <cmath>
10
12
  #include <cstdint>
11
13
  #include <vector>
12
14
 
13
15
  #include <faiss/utils/Heap.h>
16
+ #include <faiss/utils/ordered_key_value.h>
14
17
  #include <faiss/utils/simd_levels.h>
15
18
 
16
19
  namespace faiss {
17
20
 
18
21
  /** Heap structure that allows fast access and updates.
19
22
  *
20
- * Supports both max-heap operations (via the underlying CMax heap)
21
- * and efficient min extraction via linear scan (with optional SIMD
22
- * acceleration).
23
+ * Templated on the comparator HC_ so that the same data structure can
24
+ * service both distance-style searches (HC_ = CMax<float, int32_t>, smaller
25
+ * is better) and similarity-style searches (HC_ = CMin<float, int32_t>,
26
+ * larger is better). For the distance variant the underlying heap is a
27
+ * max-heap and "pop_min" returns the closest element; for similarity the
28
+ * underlying heap is a min-heap and "pop_min" returns the most similar
29
+ * element.
23
30
  */
24
- struct MinimaxHeap {
31
+ template <class HC_ = CMax<float, int32_t>>
32
+ struct MinimaxHeapT {
33
+ using HC = HC_;
25
34
  using storage_idx_t = int32_t;
26
35
 
27
36
  int n;
@@ -30,12 +39,34 @@ struct MinimaxHeap {
30
39
 
31
40
  std::vector<storage_idx_t> ids;
32
41
  std::vector<float> dis;
33
- using HC = faiss::CMax<float, storage_idx_t>;
34
42
 
35
- explicit MinimaxHeap(int n_in)
43
+ explicit MinimaxHeapT(int n_in)
36
44
  : n(n_in), k(0), nvalid(0), ids(n_in), dis(n_in) {}
37
45
 
38
- void push(storage_idx_t i, float v);
46
+ void push(storage_idx_t i, float v) {
47
+ // Treat NaN distances as the "worst" value so heap ordering is
48
+ // preserved (insertion is then guaranteed to fall through the
49
+ // not-better-than-top early-reject branch when the heap is full).
50
+ if (std::isnan(v)) {
51
+ v = HC::neutral();
52
+ }
53
+ if (k == n) {
54
+ // top of the heap is the "worst" entry under HC. If the new
55
+ // value is not strictly better than the worst, drop it.
56
+ // HC::cmp(top, v) means "v is better than top" for both CMax
57
+ // (cmp = a > b → top > v → v < top) and CMin (cmp = a < b →
58
+ // top < v → v > top).
59
+ if (!HC::cmp(dis[0], v)) {
60
+ return;
61
+ }
62
+ if (ids[0] != -1) {
63
+ --nvalid;
64
+ }
65
+ faiss::heap_pop<HC>(k--, dis.data(), ids.data());
66
+ }
67
+ faiss::heap_push<HC>(++k, dis.data(), ids.data(), v, i);
68
+ ++nvalid;
69
+ }
39
70
 
40
71
  float max() const {
41
72
  return dis[0];
@@ -49,16 +80,34 @@ struct MinimaxHeap {
49
80
  nvalid = k = 0;
50
81
  }
51
82
 
52
- /// SIMD-templated pop_min implementation.
53
- /// Specializations exist for NONE, AVX2, and AVX512.
54
- template <SIMDLevel SL>
55
- int pop_min_tpl(float* vmin_out = nullptr);
56
-
57
- /// Runtime-dispatched pop_min (calls pop_min_tpl with best available
58
- /// SIMD level).
83
+ /// Runtime-dispatched best-element extraction (NONE + AVX2 + AVX512).
59
84
  int pop_min(float* vmin_out = nullptr);
60
85
 
61
- int count_below(float thresh);
86
+ int count_below(float thresh) {
87
+ int n_below = 0;
88
+ for (int i = 0; i < k; i++) {
89
+ // Count entries that are strictly "better than" thresh.
90
+ // HC::cmp(thresh, dis[i]) → for CMax: thresh > dis[i]
91
+ // (i.e., dis[i] < thresh, the historical L2 semantics);
92
+ // for CMin: thresh < dis[i] (similarity above threshold).
93
+ if (HC::cmp(thresh, dis[i])) {
94
+ n_below++;
95
+ }
96
+ }
97
+ return n_below;
98
+ }
62
99
  };
63
100
 
101
+ // Default `MinimaxHeap` keeps the historical max-heap semantics (smaller
102
+ // distance is better). The CMin instantiation is used when the owning
103
+ // HNSW has `is_similarity = true`. The alias itself is declared once,
104
+ // alongside the forward declaration in HNSW.h, to avoid duplicate
105
+ // `using` declarations that SWIG treats as redundant.
106
+
107
+ // Forward declarations of the SIMD specializations. The actual bodies live
108
+ // in the SIMD-specific translation units (avx2.cpp, avx512.cpp) and are
109
+ // resolved at link time.
110
+ template <class HC_, SIMDLevel SL>
111
+ int pop_min_tpl(MinimaxHeapT<HC_>* heap, float* vmin_out);
112
+
64
113
  } // namespace faiss