faiss 0.6.2 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/lib/faiss/version.rb +1 -1
  4. data/vendor/faiss/faiss/AutoTune.cpp +3 -1
  5. data/vendor/faiss/faiss/Clustering.cpp +9 -1
  6. data/vendor/faiss/faiss/Clustering.h +8 -0
  7. data/vendor/faiss/faiss/IVFlib.cpp +14 -3
  8. data/vendor/faiss/faiss/Index.h +2 -2
  9. data/vendor/faiss/faiss/IndexAdditiveQuantizer.cpp +9 -10
  10. data/vendor/faiss/faiss/IndexAdditiveQuantizerFastScan.cpp +2 -3
  11. data/vendor/faiss/faiss/IndexBinaryFromFloat.cpp +24 -4
  12. data/vendor/faiss/faiss/IndexBinaryHNSW.cpp +16 -145
  13. data/vendor/faiss/faiss/IndexBinaryHNSW.h +0 -6
  14. data/vendor/faiss/faiss/IndexBinaryHash.cpp +5 -9
  15. data/vendor/faiss/faiss/IndexBinaryIVF.cpp +8 -18
  16. data/vendor/faiss/faiss/IndexBinaryIVF.h +8 -1
  17. data/vendor/faiss/faiss/IndexEDEN.cpp +273 -0
  18. data/vendor/faiss/faiss/IndexEDEN.h +57 -0
  19. data/vendor/faiss/faiss/IndexFastScan.cpp +15 -4
  20. data/vendor/faiss/faiss/IndexFlat.cpp +21 -54
  21. data/vendor/faiss/faiss/IndexFlat.h +2 -2
  22. data/vendor/faiss/faiss/IndexHNSW.cpp +311 -102
  23. data/vendor/faiss/faiss/IndexHNSW.h +31 -7
  24. data/vendor/faiss/faiss/IndexIDMap.cpp +26 -8
  25. data/vendor/faiss/faiss/IndexIDMap.h +2 -0
  26. data/vendor/faiss/faiss/IndexIVF.cpp +36 -10
  27. data/vendor/faiss/faiss/IndexIVFAdditiveQuantizer.cpp +1 -1
  28. data/vendor/faiss/faiss/IndexIVFAdditiveQuantizerFastScan.cpp +3 -4
  29. data/vendor/faiss/faiss/IndexIVFEDEN.cpp +302 -0
  30. data/vendor/faiss/faiss/IndexIVFEDEN.h +70 -0
  31. data/vendor/faiss/faiss/IndexIVFFastScan.cpp +5 -6
  32. data/vendor/faiss/faiss/IndexIVFFlat.cpp +3 -4
  33. data/vendor/faiss/faiss/IndexIVFIndependentQuantizer.cpp +1 -1
  34. data/vendor/faiss/faiss/IndexIVFPQ.cpp +49 -23
  35. data/vendor/faiss/faiss/IndexIVFPQ.h +11 -0
  36. data/vendor/faiss/faiss/IndexIVFPQFastScan.cpp +0 -1
  37. data/vendor/faiss/faiss/IndexIVFRaBitQ.cpp +19 -49
  38. data/vendor/faiss/faiss/IndexIVFRaBitQFastScan.cpp +180 -76
  39. data/vendor/faiss/faiss/IndexIVFRaBitQFastScan.h +5 -4
  40. data/vendor/faiss/faiss/IndexIVFSpectralHash.cpp +8 -6
  41. data/vendor/faiss/faiss/IndexLSH.cpp +2 -3
  42. data/vendor/faiss/faiss/IndexLattice.cpp +5 -0
  43. data/vendor/faiss/faiss/IndexNNDescent.cpp +9 -2
  44. data/vendor/faiss/faiss/IndexNSG.cpp +7 -2
  45. data/vendor/faiss/faiss/IndexPQ.cpp +6 -8
  46. data/vendor/faiss/faiss/IndexPreTransform.cpp +15 -0
  47. data/vendor/faiss/faiss/IndexRaBitQ.cpp +2 -2
  48. data/vendor/faiss/faiss/IndexRaBitQFastScan.cpp +1 -2
  49. data/vendor/faiss/faiss/IndexRaBitQFastScan.h +5 -1
  50. data/vendor/faiss/faiss/IndexRefine.cpp +30 -1
  51. data/vendor/faiss/faiss/IndexReplicas.cpp +1 -2
  52. data/vendor/faiss/faiss/IndexShards.cpp +5 -5
  53. data/vendor/faiss/faiss/IndexShardsIVF.cpp +6 -5
  54. data/vendor/faiss/faiss/MetaIndexes.cpp +2 -4
  55. data/vendor/faiss/faiss/SuperKMeans.cpp +286 -247
  56. data/vendor/faiss/faiss/SuperKMeans.h +33 -2
  57. data/vendor/faiss/faiss/VectorTransform.cpp +71 -2
  58. data/vendor/faiss/faiss/VectorTransform.h +3 -0
  59. data/vendor/faiss/faiss/clone_index.cpp +8 -0
  60. data/vendor/faiss/faiss/factory_tools.cpp +47 -4
  61. data/vendor/faiss/faiss/gpu/GpuCloner.cpp +11 -11
  62. data/vendor/faiss/faiss/gpu/GpuClonerOptions.h +1 -5
  63. data/vendor/faiss/faiss/gpu/GpuDistance.h +2 -5
  64. data/vendor/faiss/faiss/gpu/GpuIndex.h +38 -16
  65. data/vendor/faiss/faiss/gpu/GpuIndexCagra.h +71 -1
  66. data/vendor/faiss/faiss/gpu/GpuIndexIVF.h +17 -0
  67. data/vendor/faiss/faiss/gpu/GpuIndexIVFScalarQuantizer.h +16 -0
  68. data/vendor/faiss/faiss/gpu/perf/PerfClustering.cpp +1 -1
  69. data/vendor/faiss/faiss/gpu/perf/PerfIVFPQAdd.cpp +2 -2
  70. data/vendor/faiss/faiss/gpu/test/TestGpuIndexIVFScalarQuantizer.cpp +180 -0
  71. data/vendor/faiss/faiss/gpu_metal/MetalIndexIVFFlat.h +1 -5
  72. data/vendor/faiss/faiss/gpu_metal/MetalIndexIVFPQ.h +88 -0
  73. data/vendor/faiss/faiss/gpu_metal/impl/MetalIVFPQ.h +134 -0
  74. data/vendor/faiss/faiss/impl/AdditiveQuantizer.cpp +1 -1
  75. data/vendor/faiss/faiss/impl/ClusteringInitialization.cpp +7 -4
  76. data/vendor/faiss/faiss/impl/DistanceComputer.h +34 -0
  77. data/vendor/faiss/faiss/impl/EDENQuantizer.h +119 -0
  78. data/vendor/faiss/faiss/impl/HNSW.cpp +528 -267
  79. data/vendor/faiss/faiss/impl/HNSW.h +46 -7
  80. data/vendor/faiss/faiss/impl/IDSelector.h +44 -0
  81. data/vendor/faiss/faiss/impl/LocalSearchQuantizer.cpp +2 -2
  82. data/vendor/faiss/faiss/impl/NNDescent.cpp +10 -3
  83. data/vendor/faiss/faiss/impl/NSG.cpp +3 -1
  84. data/vendor/faiss/faiss/impl/Panorama.h +20 -9
  85. data/vendor/faiss/faiss/impl/PolysemousTraining.cpp +152 -84
  86. data/vendor/faiss/faiss/impl/ProductQuantizer.cpp +38 -26
  87. data/vendor/faiss/faiss/impl/RaBitQUtils.cpp +45 -37
  88. data/vendor/faiss/faiss/impl/RaBitQUtils.h +35 -0
  89. data/vendor/faiss/faiss/impl/RaBitQuantizer.cpp +239 -72
  90. data/vendor/faiss/faiss/impl/RaBitQuantizer.h +66 -4
  91. data/vendor/faiss/faiss/impl/RaBitQuantizerMultiBit.cpp +4 -13
  92. data/vendor/faiss/faiss/impl/ResultHandler.h +34 -34
  93. data/vendor/faiss/faiss/impl/ScalarQuantizer.cpp +287 -84
  94. data/vendor/faiss/faiss/impl/ScalarQuantizer.h +26 -10
  95. data/vendor/faiss/faiss/impl/ThreadedIndex-inl.h +2 -2
  96. data/vendor/faiss/faiss/impl/VisitedTable.cpp +22 -2
  97. data/vendor/faiss/faiss/impl/VisitedTable.h +20 -0
  98. data/vendor/faiss/faiss/impl/binary_hamming/IndexBinaryIVF_impl.h +90 -14
  99. data/vendor/faiss/faiss/impl/binary_hamming/avx2.cpp +4 -4
  100. data/vendor/faiss/faiss/impl/expanded_scanners.h +5 -1
  101. data/vendor/faiss/faiss/impl/fast_scan/decompose_qbs.h +1 -0
  102. data/vendor/faiss/faiss/impl/fast_scan/dispatching.h +35 -2
  103. data/vendor/faiss/faiss/impl/hnsw/LockVector.cpp +1 -1
  104. data/vendor/faiss/faiss/impl/index_read.cpp +491 -50
  105. data/vendor/faiss/faiss/impl/index_write.cpp +86 -30
  106. data/vendor/faiss/faiss/impl/lattice_Zn.cpp +8 -9
  107. data/vendor/faiss/faiss/impl/platform_macros.h +3 -1
  108. data/vendor/faiss/faiss/impl/polysemous_training/avx512.cpp +284 -0
  109. data/vendor/faiss/faiss/impl/polysemous_training/dispatch.h +115 -0
  110. data/vendor/faiss/faiss/impl/pq_code_distance/IVFPQScanner_impl.h +73 -39
  111. data/vendor/faiss/faiss/impl/pq_code_distance/IVFPQ_QueryTables.cpp +0 -1
  112. data/vendor/faiss/faiss/impl/pq_code_distance/PQDistanceComputer_impl.h +26 -15
  113. data/vendor/faiss/faiss/impl/pq_code_distance/avx2.cpp +4 -4
  114. data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-generic.cpp +4 -4
  115. data/vendor/faiss/faiss/impl/result_handler/ResultHandler.cpp +195 -0
  116. data/vendor/faiss/faiss/impl/result_handler/avx2.cpp +133 -0
  117. data/vendor/faiss/faiss/impl/result_handler/avx512.cpp +281 -0
  118. data/vendor/faiss/faiss/impl/scalar_quantizer/EDENQuantizer-avx2.cpp +72 -0
  119. data/vendor/faiss/faiss/impl/scalar_quantizer/EDENQuantizer-avx512.cpp +228 -0
  120. data/vendor/faiss/faiss/impl/scalar_quantizer/EDENQuantizer.cpp +887 -0
  121. data/vendor/faiss/faiss/impl/scalar_quantizer/distance_computers.h +2 -2
  122. data/vendor/faiss/faiss/impl/scalar_quantizer/quantizers.h +9 -8
  123. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx2.cpp +90 -24
  124. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512-impl.h +30 -30
  125. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512-spr.cpp +4 -5
  126. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512.cpp +101 -34
  127. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-dispatch.h +169 -13
  128. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-neon.cpp +125 -26
  129. data/vendor/faiss/faiss/impl/simd_dispatch.h +70 -31
  130. data/vendor/faiss/faiss/index_factory.cpp +40 -7
  131. data/vendor/faiss/faiss/invlists/DirectMap.cpp +1 -1
  132. data/vendor/faiss/faiss/invlists/InvertedLists.cpp +9 -6
  133. data/vendor/faiss/faiss/invlists/OnDiskInvertedLists.cpp +29 -8
  134. data/vendor/faiss/faiss/python/python_callbacks.cpp +3 -1
  135. data/vendor/faiss/faiss/svs/IndexSVSFaissUtils.h +60 -0
  136. data/vendor/faiss/faiss/svs/IndexSVSFlat.cpp +26 -1
  137. data/vendor/faiss/faiss/svs/IndexSVSFlat.h +13 -0
  138. data/vendor/faiss/faiss/svs/IndexSVSIVF.cpp +1 -1
  139. data/vendor/faiss/faiss/svs/IndexSVSIVFLeanVec.cpp +1 -1
  140. data/vendor/faiss/faiss/svs/IndexSVSVamana.cpp +47 -5
  141. data/vendor/faiss/faiss/svs/IndexSVSVamana.h +23 -3
  142. data/vendor/faiss/faiss/svs/IndexSVSVamanaLVQ.cpp +4 -2
  143. data/vendor/faiss/faiss/svs/IndexSVSVamanaLVQ.h +2 -1
  144. data/vendor/faiss/faiss/svs/IndexSVSVamanaLeanVec.cpp +10 -4
  145. data/vendor/faiss/faiss/svs/IndexSVSVamanaLeanVec.h +2 -1
  146. data/vendor/faiss/faiss/utils/approx_topk_hamming/approx_topk_hamming.h +1 -1
  147. data/vendor/faiss/faiss/utils/distances.cpp +30 -11
  148. data/vendor/faiss/faiss/utils/distances_dispatch.h +30 -24
  149. data/vendor/faiss/faiss/utils/distances_fused/distances_fused.cpp +1 -1
  150. data/vendor/faiss/faiss/utils/distances_simd.cpp +4 -3
  151. data/vendor/faiss/faiss/utils/extra_distances.cpp +4 -14
  152. data/vendor/faiss/faiss/utils/extra_distances.h +1 -2
  153. data/vendor/faiss/faiss/utils/hamming.cpp +16 -10
  154. data/vendor/faiss/faiss/utils/hamming.h +10 -1
  155. data/vendor/faiss/faiss/utils/hamming_distance/common.h +14 -3
  156. data/vendor/faiss/faiss/utils/hamming_distance/hamming_avx512_vpopcnt.cpp +24 -0
  157. data/vendor/faiss/faiss/utils/hamming_distance/hamming_computer-avx512.h +1 -1
  158. data/vendor/faiss/faiss/utils/hamming_distance/{hamming_computer-avx512_spr.h → hamming_computer-avx512_vpopcnt.h} +85 -24
  159. data/vendor/faiss/faiss/utils/hamming_distance/hamming_impl.h +141 -0
  160. data/vendor/faiss/faiss/utils/quantize_lut.cpp +29 -8
  161. data/vendor/faiss/faiss/utils/rabitq_simd.h +202 -0
  162. data/vendor/faiss/faiss/utils/simd_impl/distances_arm_sve.cpp +194 -30
  163. data/vendor/faiss/faiss/utils/simd_impl/distances_avx2.cpp +0 -1
  164. data/vendor/faiss/faiss/utils/simd_impl/distances_avx512.cpp +263 -15
  165. data/vendor/faiss/faiss/utils/simd_impl/distances_rvv.cpp +198 -18
  166. data/vendor/faiss/faiss/utils/simd_impl/rabitq_avx2.cpp +245 -0
  167. data/vendor/faiss/faiss/utils/simd_impl/rabitq_avx512.cpp +330 -40
  168. data/vendor/faiss/faiss/utils/simd_impl/{rabitq_avx512_spr.cpp → rabitq_avx512_vpopcnt.cpp} +112 -23
  169. data/vendor/faiss/faiss/utils/simd_impl/rabitq_neon.cpp +11 -0
  170. data/vendor/faiss/faiss/utils/simd_impl/rabitq_rvv.cpp +143 -6
  171. data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_dispatch.h +2 -7
  172. data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_kernels.h +6 -1
  173. data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_kernels_sve.cpp +34 -0
  174. data/vendor/faiss/faiss/utils/simd_levels.cpp +196 -47
  175. data/vendor/faiss/faiss/utils/simd_levels.h +33 -8
  176. data/vendor/faiss/faiss/utils/utils.cpp +9 -27
  177. metadata +21 -5
  178. data/vendor/faiss/faiss/utils/hamming_distance/hamming_avx512_spr.cpp +0 -15
@@ -8,9 +8,12 @@
8
8
  #include <faiss/impl/RaBitQuantizer.h>
9
9
 
10
10
  #include <faiss/impl/FaissAssert.h>
11
+ #include <faiss/impl/IDSelector.h>
11
12
  #include <faiss/impl/RaBitQUtils.h>
12
13
  #include <faiss/impl/RaBitQuantizerMultiBit.h>
14
+ #include <faiss/impl/ResultHandler.h>
13
15
  #include <faiss/impl/simd_dispatch.h>
16
+ #include <faiss/invlists/DirectMap.h>
14
17
  #include <faiss/utils/distances.h>
15
18
  #include <faiss/utils/rabitq_simd.h>
16
19
 
@@ -22,6 +25,8 @@
22
25
 
23
26
  namespace faiss {
24
27
 
28
+ RaBitQStats rabitq_stats;
29
+
25
30
  // Import shared utilities from RaBitQUtils
26
31
  using rabitq_utils::ExtraBitsFactors;
27
32
  using rabitq_utils::QueryFactorsData;
@@ -183,8 +188,10 @@ void RaBitQuantizer::decode_core(
183
188
  float* x,
184
189
  size_t n,
185
190
  const float* centroid_in) const {
186
- FAISS_ASSERT(codes != nullptr);
187
- FAISS_ASSERT(x != nullptr);
191
+ FAISS_THROW_IF_MSG(
192
+ codes == nullptr, "RaBitQuantizer::decode_core: null codes buffer");
193
+ FAISS_THROW_IF_MSG(
194
+ x == nullptr, "RaBitQuantizer::decode_core: null output buffer");
188
195
 
189
196
  const float inv_d_sqrt = (d == 0) ? 1.0f : (1.0f / std::sqrt((float)d));
190
197
  const size_t ex_bits = nb_bits - 1;
@@ -220,6 +227,51 @@ void RaBitQuantizer::decode_core(
220
227
  }
221
228
  }
222
229
 
230
+ template <SIMDLevel SL>
231
+ float symmetric_dis_1bit(const RaBitQDistanceComputer& dc, idx_t i, idx_t j) {
232
+ FAISS_THROW_IF_NOT_MSG(
233
+ dc.metric_type == MetricType::METRIC_L2,
234
+ "RaBitQ symmetric distance supports only L2");
235
+ FAISS_ASSERT(i >= 0 && j >= 0);
236
+ FAISS_ASSERT(dc.codes != nullptr);
237
+
238
+ const size_t sign_bytes = (dc.d + 7) / 8;
239
+ const uint8_t* code_i = dc.codes + static_cast<size_t>(i) * dc.code_size;
240
+ const uint8_t* code_j = dc.codes + static_cast<size_t>(j) * dc.code_size;
241
+ const auto* factors_i =
242
+ reinterpret_cast<const SignBitFactors*>(code_i + sign_bytes);
243
+ const auto* factors_j =
244
+ reinterpret_cast<const SignBitFactors*>(code_j + sign_bytes);
245
+
246
+ const uint64_t xor_popcount =
247
+ rabitq::bitwise_xor_dot_product<SL>(code_i, code_j, sign_bytes, 1);
248
+ const float sign_dot =
249
+ static_cast<float>(dc.d) - 2.0f * static_cast<float>(xor_popcount);
250
+
251
+ // The L2-optimal reconstruction of residual r is alpha * sign(r), where
252
+ // alpha = ||r||_1 / d. The stored factors give
253
+ // alpha_i * alpha_j = ||r_i||^2 * ||r_j||^2 /
254
+ // (d * dp_multiplier_i * dp_multiplier_j).
255
+ float cross_term = 0.0f;
256
+ if (factors_i->dp_multiplier != 0.0f && factors_j->dp_multiplier != 0.0f) {
257
+ // Dividing each norm first avoids overflowing the product of two
258
+ // squared norms even when the final distance is representable.
259
+ const float scaled_norm_i =
260
+ factors_i->or_minus_c_l2sqr / factors_i->dp_multiplier;
261
+ const float scaled_norm_j =
262
+ factors_j->or_minus_c_l2sqr / factors_j->dp_multiplier;
263
+ cross_term = (scaled_norm_i * (sign_dot / static_cast<float>(dc.d))) *
264
+ scaled_norm_j;
265
+ }
266
+ const float distance = factors_i->or_minus_c_l2sqr +
267
+ factors_j->or_minus_c_l2sqr - 2.0f * cross_term;
268
+ return std::max(0.0f, distance);
269
+ }
270
+
271
+ float RaBitQDistanceComputer::symmetric_dis(idx_t i, idx_t j) {
272
+ return symmetric_dis_1bit<SIMDLevel::NONE>(*this, i, j);
273
+ }
274
+
223
275
  namespace {
224
276
 
225
277
  // Distance computers templatized on SIMDLevel to avoid per-call dynamic
@@ -228,7 +280,7 @@ namespace {
228
280
  // directly to the SIMD-specialized code.
229
281
 
230
282
  template <SIMDLevel SL>
231
- struct RaBitQDistanceComputerNotQ : RaBitQDistanceComputer {
283
+ struct RaBitQDistanceComputerNotQ final : RaBitQDistanceComputer {
232
284
  // the rotated query (qr - c)
233
285
  std::vector<float> rotated_q;
234
286
  // some additional numbers for the query
@@ -236,27 +288,14 @@ struct RaBitQDistanceComputerNotQ : RaBitQDistanceComputer {
236
288
 
237
289
  RaBitQDistanceComputerNotQ() = default;
238
290
 
239
- // Compute distance using only 1-bit codes (fast)
240
- float distance_to_code_1bit(const uint8_t* code) override {
241
- FAISS_ASSERT(code != nullptr);
242
- FAISS_ASSERT(
243
- (metric_type == MetricType::METRIC_L2 ||
244
- metric_type == MetricType::METRIC_INNER_PRODUCT));
245
- FAISS_ASSERT(rotated_q.size() == d);
246
-
247
- // split the code into parts
248
- const uint8_t* binary_data = code;
249
-
250
- // Cast to appropriate type based on nb_bits
251
- // For 1-bit: use SignBitFactors (8 bytes)
252
- // For multi-bit: use SignBitFactorsWithError (12 bytes) which includes
253
- // f_error
254
- size_t ex_bits = nb_bits - 1;
255
- const SignBitFactors* base_fac = (ex_bits == 0)
256
- ? reinterpret_cast<const SignBitFactors*>(code + (d + 7) / 8)
257
- : reinterpret_cast<const SignBitFactorsWithError*>(
258
- code + (d + 7) / 8);
291
+ float symmetric_dis(idx_t i, idx_t j) final {
292
+ return symmetric_dis_1bit<SL>(*this, i, j);
293
+ }
259
294
 
295
+ // Compute distance using only 1-bit codes (fast)
296
+ float distance_to_code_1bit_impl(
297
+ const uint8_t* binary_data,
298
+ const SignBitFactors* base_fac) const {
260
299
  // this is the baseline code
261
300
  //
262
301
  // compute <q,o> using floats
@@ -286,7 +325,7 @@ struct RaBitQDistanceComputerNotQ : RaBitQDistanceComputer {
286
325
 
287
326
  if (metric_type == MetricType::METRIC_L2) {
288
327
  // ||or - q||^ 2
289
- return pre_dist;
328
+ return std::max(0.0f, pre_dist);
290
329
  } else {
291
330
  // metric == MetricType::METRIC_INNER_PRODUCT
292
331
  // 2 * (or, q) = (||or - q||^2 - ||q||^2 - ||or||^2)
@@ -294,8 +333,24 @@ struct RaBitQDistanceComputerNotQ : RaBitQDistanceComputer {
294
333
  }
295
334
  }
296
335
 
336
+ float distance_to_code_1bit(const uint8_t* code) final {
337
+ FAISS_ASSERT(code != nullptr);
338
+ FAISS_ASSERT(
339
+ (metric_type == MetricType::METRIC_L2 ||
340
+ metric_type == MetricType::METRIC_INNER_PRODUCT));
341
+ FAISS_ASSERT(rotated_q.size() == d);
342
+
343
+ const size_t code_size_base = (d + 7) / 8;
344
+ const size_t ex_bits = nb_bits - 1;
345
+ const SignBitFactors* base_fac = (ex_bits == 0)
346
+ ? reinterpret_cast<const SignBitFactors*>(code + code_size_base)
347
+ : reinterpret_cast<const SignBitFactorsWithError*>(
348
+ code + code_size_base);
349
+ return distance_to_code_1bit_impl(code, base_fac);
350
+ }
351
+
297
352
  // Compute full distance using 1-bit + ex-bits (accurate)
298
- float distance_to_code_full(const uint8_t* code) override {
353
+ float distance_to_code_full(const uint8_t* code) final {
299
354
  FAISS_ASSERT(code != nullptr);
300
355
  FAISS_ASSERT(
301
356
  (metric_type == MetricType::METRIC_L2 ||
@@ -331,7 +386,7 @@ struct RaBitQDistanceComputerNotQ : RaBitQDistanceComputer {
331
386
  metric_type);
332
387
  }
333
388
 
334
- void set_query(const float* x) override {
389
+ void set_query(const float* x) final {
335
390
  q = x;
336
391
  FAISS_ASSERT(x != nullptr);
337
392
  FAISS_ASSERT(
@@ -372,10 +427,68 @@ struct RaBitQDistanceComputerNotQ : RaBitQDistanceComputer {
372
427
  centroid ? fvec_inner_product(x, centroid, d) : 0.0f;
373
428
  }
374
429
  }
430
+
431
+ size_t scan_codes_multibit(
432
+ size_t list_size,
433
+ const uint8_t* codes,
434
+ const idx_t* ids,
435
+ size_t code_size,
436
+ idx_t list_no,
437
+ bool store_pairs,
438
+ const IDSelector* sel,
439
+ bool keep_max,
440
+ ResultHandler& handler) final {
441
+ const size_t code_size_base = (d + 7) / 8;
442
+ const size_t ex_bits = nb_bits - 1;
443
+ FAISS_ASSERT(ex_bits > 0);
444
+
445
+ // Honor IDSelectorWithContext on the multibit path too, so a RaBitQ
446
+ // index does not silently lose the context hook once nb_bits >= 2 (the
447
+ // 1-bit path already routes through run_scan_codes1).
448
+ const IDSelectorContextDispatch sel_dispatch(sel, store_pairs);
449
+
450
+ size_t nup = 0;
451
+ for (size_t j = 0; j < list_size; j++) {
452
+ if (sel != nullptr) {
453
+ idx_t id = store_pairs ? lo_build(list_no, j) : ids[j];
454
+ if (!sel_dispatch.is_member(
455
+ id, IDScanContext{ids, list_size, j})) {
456
+ codes += code_size;
457
+ continue;
458
+ }
459
+ }
460
+
461
+ const auto* base_fac =
462
+ reinterpret_cast<const SignBitFactorsWithError*>(
463
+ codes + code_size_base);
464
+ const float est_distance =
465
+ distance_to_code_1bit_impl(codes, base_fac);
466
+
467
+ const bool should_refine = rabitq_utils::should_refine_candidate(
468
+ est_distance,
469
+ base_fac->f_error,
470
+ g_error,
471
+ handler.threshold,
472
+ keep_max);
473
+ if (should_refine) {
474
+ handler.stats.scan_cnt++;
475
+ const float dis = distance_to_code_full(codes);
476
+ idx_t id = store_pairs ? lo_build(list_no, j) : ids[j];
477
+
478
+ if (handler.add_result(dis, id)) {
479
+ handler.stats.nheap_updates++;
480
+ nup++;
481
+ }
482
+ }
483
+ codes += code_size;
484
+ }
485
+
486
+ return nup;
487
+ }
375
488
  };
376
489
 
377
490
  template <SIMDLevel SL>
378
- struct RaBitQDistanceComputerQ : RaBitQDistanceComputer {
491
+ struct RaBitQDistanceComputerQ final : RaBitQDistanceComputer {
379
492
  // the rotated and quantized query (qr - c)
380
493
  std::vector<float> rotated_q;
381
494
  // the rotated and quantized query (qr - c) for fast 1-bit computation
@@ -394,26 +507,15 @@ struct RaBitQDistanceComputerQ : RaBitQDistanceComputer {
394
507
 
395
508
  RaBitQDistanceComputerQ() = default;
396
509
 
397
- // Compute distance using only 1-bit codes (fast)
398
- float distance_to_code_1bit(const uint8_t* code) override {
399
- FAISS_ASSERT(code != nullptr);
400
- FAISS_ASSERT(
401
- (metric_type == MetricType::METRIC_L2 ||
402
- metric_type == MetricType::METRIC_INNER_PRODUCT));
403
-
404
- // split the code into parts
405
- size_t size = (d + 7) / 8;
406
- const uint8_t* binary_data = code;
407
-
408
- // Cast to appropriate type based on nb_bits
409
- // For 1-bit: use SignBitFactors (8 bytes)
410
- // For multi-bit: use SignBitFactorsWithError (12 bytes) which
411
- // includes f_error
412
- size_t ex_bits = nb_bits - 1;
413
- const SignBitFactors* base_fac = (ex_bits == 0)
414
- ? reinterpret_cast<const SignBitFactors*>(code + size)
415
- : reinterpret_cast<const SignBitFactorsWithError*>(code + size);
510
+ float symmetric_dis(idx_t i, idx_t j) final {
511
+ return symmetric_dis_1bit<SL>(*this, i, j);
512
+ }
416
513
 
514
+ // Compute distance using only 1-bit codes (fast)
515
+ float distance_to_code_1bit_impl(
516
+ const uint8_t* binary_data,
517
+ const SignBitFactors* base_fac,
518
+ size_t size) const {
417
519
  // this is ||or - c||^2 - (IP ? ||or||^2 : 0)
418
520
  float final_dot = 0;
419
521
  if (centered) {
@@ -428,17 +530,16 @@ struct RaBitQDistanceComputerQ : RaBitQDistanceComputer {
428
530
  qb);
429
531
  final_dot += int_dot * query_fac.int_dot_scale;
430
532
  } else {
431
- auto dot_qo = rabitq::bitwise_and_dot_product<SL>(
432
- rearranged_rotated_qq.data(), binary_data, size, qb);
433
- // It was a willful decision (after the discussion) to not to
434
- // pre-cache the sum of all bits, just in order to reduce the
435
- // overhead per vector.
436
- // process 64-bit popcounts
437
- auto sum_q = rabitq::popcount<SL>(binary_data, size);
533
+ auto bitwise_result =
534
+ rabitq::bitwise_and_dot_product_with_popcount<SL>(
535
+ rearranged_rotated_qq.data(),
536
+ binary_data,
537
+ size,
538
+ qb);
438
539
  // dot-product itself
439
- final_dot += query_fac.c1 * dot_qo;
540
+ final_dot += query_fac.c1 * bitwise_result.dot_product;
440
541
  // normalizer coefficients
441
- final_dot += query_fac.c2 * sum_q;
542
+ final_dot += query_fac.c2 * bitwise_result.popcount;
442
543
  // normalizer coefficients
443
544
  final_dot -= query_fac.c34;
444
545
  }
@@ -449,7 +550,7 @@ struct RaBitQDistanceComputerQ : RaBitQDistanceComputer {
449
550
 
450
551
  if (metric_type == MetricType::METRIC_L2) {
451
552
  // ||or - q||^ 2
452
- return pre_dist;
553
+ return std::max(0.0f, pre_dist);
453
554
  } else {
454
555
  // metric == MetricType::METRIC_INNER_PRODUCT
455
556
  // 2 * (or, q) = (||or - q||^2 - ||q||^2 - ||or||^2)
@@ -457,8 +558,22 @@ struct RaBitQDistanceComputerQ : RaBitQDistanceComputer {
457
558
  }
458
559
  }
459
560
 
561
+ float distance_to_code_1bit(const uint8_t* code) final {
562
+ FAISS_ASSERT(code != nullptr);
563
+ FAISS_ASSERT(
564
+ (metric_type == MetricType::METRIC_L2 ||
565
+ metric_type == MetricType::METRIC_INNER_PRODUCT));
566
+
567
+ const size_t size = (d + 7) / 8;
568
+ const size_t ex_bits = nb_bits - 1;
569
+ const SignBitFactors* base_fac = (ex_bits == 0)
570
+ ? reinterpret_cast<const SignBitFactors*>(code + size)
571
+ : reinterpret_cast<const SignBitFactorsWithError*>(code + size);
572
+ return distance_to_code_1bit_impl(code, base_fac, size);
573
+ }
574
+
460
575
  // Compute full distance using 1-bit + ex-bits (accurate)
461
- float distance_to_code_full(const uint8_t* code) override {
576
+ float distance_to_code_full(const uint8_t* code) final {
462
577
  FAISS_ASSERT(code != nullptr);
463
578
  FAISS_ASSERT(
464
579
  (metric_type == MetricType::METRIC_L2 ||
@@ -494,7 +609,7 @@ struct RaBitQDistanceComputerQ : RaBitQDistanceComputer {
494
609
  metric_type);
495
610
  }
496
611
 
497
- void set_query(const float* x) override {
612
+ void set_query(const float* x) final {
498
613
  q = x;
499
614
  FAISS_ASSERT(x != nullptr);
500
615
  FAISS_ASSERT(
@@ -526,16 +641,70 @@ struct RaBitQDistanceComputerQ : RaBitQDistanceComputer {
526
641
  size_t offset = (d + 7) / 8;
527
642
 
528
643
  rearranged_rotated_qq.resize(offset * qb);
529
- std::fill(
530
- rearranged_rotated_qq.begin(), rearranged_rotated_qq.end(), 0);
531
-
532
- for (size_t idim = 0; idim < d; idim++) {
533
- for (size_t iv = 0; iv < qb; iv++) {
534
- const bool bit = ((rotated_qq[idim] & (1 << iv)) != 0);
535
- rearranged_rotated_qq[iv * offset + idim / 8] |=
536
- bit ? (1 << (idim % 8)) : 0;
644
+ with_selected_simd_levels<
645
+ AVAILABLE_SIMD_LEVELS_NONE | (1 << int(SIMDLevel::AVX2)) |
646
+ (1 << int(SIMDLevel::AVX512))>([&]<SIMDLevel RSL>() {
647
+ rabitq::rearrange_bit_planes<RSL>(
648
+ rotated_qq.data(), d, qb, rearranged_rotated_qq.data());
649
+ });
650
+ }
651
+
652
+ size_t scan_codes_multibit(
653
+ size_t list_size,
654
+ const uint8_t* codes,
655
+ const idx_t* ids,
656
+ size_t code_size,
657
+ idx_t list_no,
658
+ bool store_pairs,
659
+ const IDSelector* sel,
660
+ bool keep_max,
661
+ ResultHandler& handler) final {
662
+ const size_t code_size_base = (d + 7) / 8;
663
+ const size_t ex_bits = nb_bits - 1;
664
+ FAISS_ASSERT(ex_bits > 0);
665
+
666
+ // Honor IDSelectorWithContext on the multibit path too, so a RaBitQ
667
+ // index does not silently lose the context hook once nb_bits >= 2 (the
668
+ // 1-bit path already routes through run_scan_codes1).
669
+ const IDSelectorContextDispatch sel_dispatch(sel, store_pairs);
670
+
671
+ size_t nup = 0;
672
+ for (size_t j = 0; j < list_size; j++) {
673
+ if (sel != nullptr) {
674
+ idx_t id = store_pairs ? lo_build(list_no, j) : ids[j];
675
+ if (!sel_dispatch.is_member(
676
+ id, IDScanContext{ids, list_size, j})) {
677
+ codes += code_size;
678
+ continue;
679
+ }
537
680
  }
681
+
682
+ const auto* base_fac =
683
+ reinterpret_cast<const SignBitFactorsWithError*>(
684
+ codes + code_size_base);
685
+ const float est_distance =
686
+ distance_to_code_1bit_impl(codes, base_fac, code_size_base);
687
+
688
+ const bool should_refine = rabitq_utils::should_refine_candidate(
689
+ est_distance,
690
+ base_fac->f_error,
691
+ g_error,
692
+ handler.threshold,
693
+ keep_max);
694
+ if (should_refine) {
695
+ handler.stats.scan_cnt++;
696
+ const float dis = distance_to_code_full(codes);
697
+ idx_t id = store_pairs ? lo_build(list_no, j) : ids[j];
698
+
699
+ if (handler.add_result(dis, id)) {
700
+ handler.stats.nheap_updates++;
701
+ nup++;
702
+ }
703
+ }
704
+ codes += code_size;
538
705
  }
706
+
707
+ return nup;
539
708
  }
540
709
  };
541
710
 
@@ -552,12 +721,10 @@ FlatCodesDistanceComputer* RaBitQuantizer::get_distance_computer(
552
721
  // call the SIMD-specialized rabitq functions directly (no per-call
553
722
  // with_simd_level overhead).
554
723
  //
555
- // Use A0_SPR (which includes AVX512_SPR) so that on Sapphire Rapids
556
- // and later x86 microarchitectures the VPOPCNTDQ-based RaBitQ
557
- // specialization in rabitq_avx512_spr.cpp is selected. On AVX-512
558
- // CPUs without VPOPCNTDQ, dispatch falls through to the AVX512
559
- // specialization in rabitq_avx512.cpp.
560
- return with_selected_simd_levels<AVAILABLE_SIMD_LEVELS_A0_SPR>(
724
+ // VPOPCNT rather than SPR: Ice Lake and Zen 4 have VPOPCNTDQ without the
725
+ // rest of the SPR feature set. Below it, dispatch falls through to
726
+ // rabitq_avx512.cpp.
727
+ return with_selected_simd_levels<AVAILABLE_SIMD_LEVELS_BASE_WITH_VPOPCNT>(
561
728
  [&]<SIMDLevel SL>() -> FlatCodesDistanceComputer* {
562
729
  if (qb == 0) {
563
730
  auto dc =
@@ -13,9 +13,40 @@
13
13
  #include <faiss/MetricType.h>
14
14
  #include <faiss/impl/DistanceComputer.h>
15
15
  #include <faiss/impl/Quantizer.h>
16
+ #include <faiss/impl/RaBitQUtils.h>
17
+ #include <faiss/impl/platform_macros.h>
16
18
 
17
19
  namespace faiss {
18
20
 
21
+ struct IDSelector;
22
+ template <typename T, typename TI>
23
+ struct ResultHandlerUnordered;
24
+
25
+ /** Statistics for staged RaBitQ distance evaluation.
26
+ *
27
+ * These counters belong to the quantizer rather than to a particular ANN
28
+ * graph. They are currently populated by HNSW's staged search path.
29
+ */
30
+ struct RaBitQStats {
31
+ uint64_t n_1bit = 0;
32
+ uint64_t n_refine = 0;
33
+
34
+ void reset() {
35
+ n_1bit = n_refine = 0;
36
+ }
37
+
38
+ void add(const RaBitQStats& other) {
39
+ n_1bit += other.n_1bit;
40
+ n_refine += other.n_refine;
41
+ }
42
+
43
+ double refine_ratio() const {
44
+ return n_1bit ? double(n_refine) / double(n_1bit) : 0.0;
45
+ }
46
+ };
47
+
48
+ FAISS_API extern RaBitQStats rabitq_stats;
49
+
19
50
  // the reference implementation of the https://arxiv.org/pdf/2405.12497
20
51
  // Jianyang Gao, Cheng Long, "RaBitQ: Quantizing High-Dimensional Vectors
21
52
  // with a Theoretical Error Bound for Approximate Nearest Neighbor Search".
@@ -118,10 +149,10 @@ struct RaBitQDistanceComputer : FlatCodesDistanceComputer {
118
149
  // Used with f_error to compute error bounds for two-stage filtering
119
150
  float g_error = 0.0f;
120
151
 
121
- float symmetric_dis(idx_t /*i*/, idx_t /*j*/) override {
122
- // Not used for RaBitQ
123
- FAISS_THROW_MSG("Not implemented");
124
- }
152
+ /// Per-distance-computer counters, aggregated by the owning index.
153
+ RaBitQStats stats;
154
+
155
+ float symmetric_dis(idx_t i, idx_t j) override;
125
156
 
126
157
  // Compute 1-bit distance estimate (fast)
127
158
  virtual float distance_to_code_1bit(const uint8_t* code) = 0;
@@ -129,6 +160,37 @@ struct RaBitQDistanceComputer : FlatCodesDistanceComputer {
129
160
  // Compute full multi-bit distance (accurate)
130
161
  virtual float distance_to_code_full(const uint8_t* code) = 0;
131
162
 
163
+ /// Apply the RaBitQ error bound without exposing the packed code layout to
164
+ /// ANN consumers. Precondition: nb_bits >= 2.
165
+ #ifndef SWIG
166
+ FAISS_ALWAYS_INLINE bool should_refine(
167
+ const uint8_t* code,
168
+ float estimate,
169
+ float threshold,
170
+ bool is_similarity) const {
171
+ const auto* factors =
172
+ reinterpret_cast<const rabitq_utils::SignBitFactorsWithError*>(
173
+ code + (d + 7) / 8);
174
+ return rabitq_utils::should_refine_candidate(
175
+ estimate, factors->f_error, g_error, threshold, is_similarity);
176
+ }
177
+ #endif
178
+
179
+ virtual void set_centroid(const float* centroid_in) {
180
+ centroid = centroid_in;
181
+ }
182
+
183
+ virtual size_t scan_codes_multibit(
184
+ size_t list_size,
185
+ const uint8_t* codes,
186
+ const idx_t* ids,
187
+ size_t code_size,
188
+ idx_t list_no,
189
+ bool store_pairs,
190
+ const IDSelector* sel,
191
+ bool keep_max,
192
+ ResultHandlerUnordered<float, idx_t>& handler) = 0;
193
+
132
194
  // Override from FlatCodesDistanceComputer
133
195
  // Delegates to distance_to_code_full() for multi-bit distance computation
134
196
  float distance_to_code(const uint8_t* code) final {
@@ -257,8 +257,8 @@ void quantize_ex_bits(
257
257
  const size_t ex_bits = nb_bits - 1;
258
258
  FAISS_THROW_IF_NOT_MSG(
259
259
  ex_bits >= 1 && ex_bits <= 8, "ex_bits must be in range [1, 8]");
260
- FAISS_THROW_IF_NOT_MSG(residual != nullptr, "residual cannot be null");
261
- FAISS_THROW_IF_NOT_MSG(ex_code != nullptr, "ex_code cannot be null");
260
+ FAISS_THROW_IF_MSG(residual == nullptr, "residual cannot be null");
261
+ FAISS_THROW_IF_MSG(ex_code == nullptr, "ex_code cannot be null");
262
262
 
263
263
  // Step 1: Compute L2 norm of residual
264
264
  float norm_sqr = fvec_norm_L2sqr(residual, d);
@@ -302,9 +302,9 @@ void quantize_ex_bits(
302
302
  }
303
303
 
304
304
  // Step 6: Handle negative dimensions (flip bits)
305
- // For negative residuals, flip all bits: code' = ~code & max_code
305
+ // For negative or zero residuals, flip all bits: code' = ~code & max_code
306
306
  for (size_t i = 0; i < d; i++) {
307
- if (residual[i] < 0) {
307
+ if (residual[i] <= 0) {
308
308
  tmp_code[i] = (~tmp_code[i]) & max_code;
309
309
  }
310
310
  }
@@ -312,15 +312,6 @@ void quantize_ex_bits(
312
312
  // Step 7: Pack codes into byte array
313
313
  pack_multibit_codes(tmp_code.data(), ex_code, d, nb_bits);
314
314
 
315
- // Step 8: Compute factors for distance computation
316
- // Reconstruct total_code for factor computation
317
- std::vector<int> total_code(d);
318
- for (size_t i = 0; i < d; i++) {
319
- // Form total_code = (sign << ex_bits) + ex_code
320
- bool sign_bit = (residual[i] >= 0);
321
- total_code[i] = tmp_code[i] + ((sign_bit ? 1 : 0) << ex_bits);
322
- }
323
-
324
315
  // Compute ex-factors; centroid is needed for IP metric correction
325
316
  compute_ex_factors(
326
317
  residual,
@@ -18,6 +18,7 @@
18
18
  #include <faiss/impl/InvertedListScannerStats.h>
19
19
  #include <faiss/utils/Heap.h>
20
20
  #include <faiss/utils/partitioning.h>
21
+ #include <faiss/utils/simd_levels.h>
21
22
  #include <algorithm>
22
23
  #include <iostream>
23
24
 
@@ -122,14 +123,20 @@ struct SingleQueryBlockResultHandler : BlockResultHandler<C, use_sel> {
122
123
  using ResultHandlerT<C>::threshold;
123
124
 
124
125
  explicit SingleResultHandler(SingleQueryBlockResultHandler& hr)
125
- : the_handler(hr.the_handler) {}
126
+ : the_handler(hr.the_handler) {
127
+ threshold = the_handler.threshold;
128
+ }
126
129
 
127
130
  /// begin results for query # i
128
- void begin(const size_t /* qid */) {}
131
+ void begin(const size_t /* qid */) {
132
+ threshold = the_handler.threshold;
133
+ }
129
134
 
130
135
  /// add one result for query i
131
136
  bool add_result(T dis, TI idx) final {
132
- return the_handler.add_result(dis, idx);
137
+ bool updated = the_handler.add_result(dis, idx);
138
+ threshold = the_handler.threshold;
139
+ return updated;
133
140
  }
134
141
 
135
142
  /// series of results for query i is done
@@ -234,23 +241,9 @@ struct Top1BlockResultHandler : TopkBlockResultHandler<C, use_sel> {
234
241
  }
235
242
 
236
243
  /// add results for query i0..i1 and j0..j1
237
- void add_results(size_t j0, size_t j1, const T* dis_tab_2) final {
238
- for (size_t i = i0; i < i1; i++) {
239
- const T* dis_tab_i = dis_tab_2 + (j1 - j0) * (i - i0) - j0;
240
-
241
- auto& min_distance = this->dis_tab[i];
242
- auto& min_index = this->ids_tab[i];
243
-
244
- for (size_t j = j0; j < j1; j++) {
245
- const T distance = dis_tab_i[j];
246
-
247
- if (C::cmp(min_distance, distance)) {
248
- min_distance = distance;
249
- min_index = j;
250
- }
251
- }
252
- }
253
- }
244
+ /// Implemented in result_handler.cpp; dispatches via
245
+ /// with_selected_simd_levels to top1_add_results_tpl<C, use_sel, SL>.
246
+ void add_results(size_t j0, size_t j1, const T* dis_tab_2) final;
254
247
 
255
248
  void add_result(const size_t i, const T dis, const TI idx) {
256
249
  auto& min_distance = this->dis_tab[i];
@@ -553,20 +546,9 @@ struct ReservoirBlockResultHandler : TopkBlockResultHandler<C, use_sel> {
553
546
  }
554
547
 
555
548
  /// add results for query i0..i1 and j0..j1
556
- void add_results(size_t j0, size_t j1, const T* dis_in) {
557
- #pragma omp parallel for
558
- for (int64_t i = static_cast<int64_t>(i0); i < static_cast<int64_t>(i1);
559
- i++) {
560
- ReservoirTopN<C>& reservoir =
561
- reservoirs[i - static_cast<int64_t>(i0)];
562
- const T* dis_tab_i =
563
- dis_in + (j1 - j0) * (i - static_cast<int64_t>(i0)) - j0;
564
- for (size_t j = j0; j < j1; j++) {
565
- T dis = dis_tab_i[j];
566
- reservoir.add_result(dis, j);
567
- }
568
- }
569
- }
549
+ /// Implemented in result_handler.cpp; dispatches via
550
+ /// with_selected_simd_levels to reservoir_add_results_tpl<C, use_sel, SL>.
551
+ void add_results(size_t j0, size_t j1, const T* dis_in);
570
552
 
571
553
  /// series of results for queries i0..i1 is done
572
554
  void end_multiple() final {
@@ -799,4 +781,22 @@ typename Consumer::T dispatch_range_ResultHandler(
799
781
  #undef DISPATCH_C_SEL
800
782
  }
801
783
 
784
+ // ------------------------------------------------------------------
785
+ // SIMD-dispatch entry points for Top1 and Reservoir add_results.
786
+ // ------------------------------------------------------------------
787
+
788
+ template <class C, bool use_sel, SIMDLevel SL>
789
+ void top1_add_results_tpl(
790
+ Top1BlockResultHandler<C, use_sel>* self,
791
+ size_t j0,
792
+ size_t j1,
793
+ const typename C::T* dis_tab);
794
+
795
+ template <class C, bool use_sel, SIMDLevel SL>
796
+ void reservoir_add_results_tpl(
797
+ ReservoirBlockResultHandler<C, use_sel>* self,
798
+ size_t j0,
799
+ size_t j1,
800
+ const typename C::T* dis_in);
801
+
802
802
  } // namespace faiss