faiss 0.6.3 → 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 (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/lib/faiss/version.rb +1 -1
  4. data/vendor/faiss/faiss/Clustering.h +8 -0
  5. data/vendor/faiss/faiss/Index.h +1 -1
  6. data/vendor/faiss/faiss/IndexBinaryFromFloat.cpp +23 -2
  7. data/vendor/faiss/faiss/IndexBinaryHNSW.cpp +12 -140
  8. data/vendor/faiss/faiss/IndexBinaryHNSW.h +0 -6
  9. data/vendor/faiss/faiss/IndexBinaryIVF.cpp +6 -14
  10. data/vendor/faiss/faiss/IndexBinaryIVF.h +8 -1
  11. data/vendor/faiss/faiss/IndexFlat.cpp +8 -4
  12. data/vendor/faiss/faiss/IndexFlat.h +2 -2
  13. data/vendor/faiss/faiss/IndexHNSW.cpp +301 -91
  14. data/vendor/faiss/faiss/IndexHNSW.h +31 -7
  15. data/vendor/faiss/faiss/IndexIDMap.cpp +11 -6
  16. data/vendor/faiss/faiss/IndexIVF.cpp +19 -4
  17. data/vendor/faiss/faiss/IndexIVFPQ.cpp +9 -1
  18. data/vendor/faiss/faiss/IndexIVFPQ.h +11 -0
  19. data/vendor/faiss/faiss/IndexShards.cpp +3 -3
  20. data/vendor/faiss/faiss/IndexShardsIVF.cpp +4 -3
  21. data/vendor/faiss/faiss/SuperKMeans.cpp +31 -8
  22. data/vendor/faiss/faiss/SuperKMeans.h +3 -2
  23. data/vendor/faiss/faiss/VectorTransform.cpp +38 -0
  24. data/vendor/faiss/faiss/VectorTransform.h +3 -0
  25. data/vendor/faiss/faiss/clone_index.cpp +3 -0
  26. data/vendor/faiss/faiss/gpu/GpuClonerOptions.h +1 -5
  27. data/vendor/faiss/faiss/gpu/GpuDistance.h +2 -5
  28. data/vendor/faiss/faiss/gpu/GpuIndex.h +4 -5
  29. data/vendor/faiss/faiss/gpu/GpuIndexCagra.h +65 -42
  30. data/vendor/faiss/faiss/impl/AdditiveQuantizer.cpp +1 -1
  31. data/vendor/faiss/faiss/impl/ClusteringInitialization.cpp +5 -2
  32. data/vendor/faiss/faiss/impl/HNSW.cpp +419 -115
  33. data/vendor/faiss/faiss/impl/HNSW.h +46 -7
  34. data/vendor/faiss/faiss/impl/IDSelector.h +44 -0
  35. data/vendor/faiss/faiss/impl/NNDescent.cpp +10 -3
  36. data/vendor/faiss/faiss/impl/Panorama.h +11 -2
  37. data/vendor/faiss/faiss/impl/ProductQuantizer.cpp +4 -4
  38. data/vendor/faiss/faiss/impl/RaBitQUtils.cpp +1 -1
  39. data/vendor/faiss/faiss/impl/RaBitQuantizer.cpp +74 -8
  40. data/vendor/faiss/faiss/impl/RaBitQuantizer.h +47 -4
  41. data/vendor/faiss/faiss/impl/RaBitQuantizerMultiBit.cpp +2 -2
  42. data/vendor/faiss/faiss/impl/ResultHandler.h +9 -3
  43. data/vendor/faiss/faiss/impl/ScalarQuantizer.cpp +32 -30
  44. data/vendor/faiss/faiss/impl/ScalarQuantizer.h +6 -10
  45. data/vendor/faiss/faiss/impl/binary_hamming/IndexBinaryIVF_impl.h +89 -13
  46. data/vendor/faiss/faiss/impl/expanded_scanners.h +5 -1
  47. data/vendor/faiss/faiss/impl/fast_scan/decompose_qbs.h +1 -0
  48. data/vendor/faiss/faiss/impl/index_read.cpp +130 -29
  49. data/vendor/faiss/faiss/impl/index_write.cpp +31 -26
  50. data/vendor/faiss/faiss/impl/pq_code_distance/IVFPQScanner_impl.h +73 -39
  51. data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-generic.cpp +4 -4
  52. data/vendor/faiss/faiss/impl/scalar_quantizer/EDENQuantizer.cpp +6 -1
  53. data/vendor/faiss/faiss/impl/scalar_quantizer/distance_computers.h +2 -2
  54. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx2.cpp +6 -2
  55. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-dispatch.h +37 -17
  56. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-neon.cpp +109 -10
  57. data/vendor/faiss/faiss/impl/simd_dispatch.h +40 -22
  58. data/vendor/faiss/faiss/index_factory.cpp +8 -1
  59. data/vendor/faiss/faiss/invlists/InvertedLists.cpp +7 -4
  60. data/vendor/faiss/faiss/invlists/OnDiskInvertedLists.cpp +10 -4
  61. data/vendor/faiss/faiss/svs/IndexSVSVamana.cpp +16 -4
  62. data/vendor/faiss/faiss/svs/IndexSVSVamana.h +8 -1
  63. data/vendor/faiss/faiss/svs/IndexSVSVamanaLVQ.cpp +4 -2
  64. data/vendor/faiss/faiss/svs/IndexSVSVamanaLVQ.h +2 -1
  65. data/vendor/faiss/faiss/svs/IndexSVSVamanaLeanVec.cpp +9 -2
  66. data/vendor/faiss/faiss/svs/IndexSVSVamanaLeanVec.h +2 -1
  67. data/vendor/faiss/faiss/utils/distances.cpp +20 -13
  68. data/vendor/faiss/faiss/utils/distances_dispatch.h +30 -24
  69. data/vendor/faiss/faiss/utils/distances_fused/distances_fused.cpp +1 -1
  70. data/vendor/faiss/faiss/utils/hamming.cpp +15 -9
  71. data/vendor/faiss/faiss/utils/hamming.h +10 -1
  72. data/vendor/faiss/faiss/utils/hamming_distance/common.h +14 -3
  73. data/vendor/faiss/faiss/utils/hamming_distance/hamming_avx512_vpopcnt.cpp +24 -0
  74. data/vendor/faiss/faiss/utils/hamming_distance/hamming_computer-avx512.h +1 -1
  75. data/vendor/faiss/faiss/utils/hamming_distance/{hamming_computer-avx512_spr.h → hamming_computer-avx512_vpopcnt.h} +85 -24
  76. data/vendor/faiss/faiss/utils/hamming_distance/hamming_impl.h +141 -0
  77. data/vendor/faiss/faiss/utils/simd_impl/distances_arm_sve.cpp +194 -30
  78. data/vendor/faiss/faiss/utils/simd_impl/distances_rvv.cpp +93 -55
  79. data/vendor/faiss/faiss/utils/simd_impl/rabitq_avx512.cpp +57 -40
  80. data/vendor/faiss/faiss/utils/simd_impl/{rabitq_avx512_spr.cpp → rabitq_avx512_vpopcnt.cpp} +21 -24
  81. data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_dispatch.h +2 -7
  82. data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_kernels.h +6 -1
  83. data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_kernels_sve.cpp +34 -0
  84. data/vendor/faiss/faiss/utils/simd_levels.cpp +172 -67
  85. data/vendor/faiss/faiss/utils/simd_levels.h +26 -15
  86. metadata +6 -5
  87. data/vendor/faiss/faiss/utils/hamming_distance/hamming_avx512_spr.cpp +0 -15
@@ -29,19 +29,31 @@ struct WrappedSearchResult {
29
29
  ResultHandler& res;
30
30
  size_t nup = 0;
31
31
  idx_t list_no;
32
-
32
+ size_t list_size;
33
33
  const idx_t* ids;
34
34
  const IDSelector* sel;
35
+ IDSelectorContextDispatch dispatch;
35
36
 
36
37
  WrappedSearchResult(
37
38
  idx_t list_no_in,
39
+ size_t list_size_in,
38
40
  const idx_t* ids_in,
39
41
  const IDSelector* sel_in,
40
42
  ResultHandler& res_in)
41
- : res(res_in), list_no(list_no_in), ids(ids_in), sel(sel_in) {}
43
+ : res(res_in),
44
+ list_no(list_no_in),
45
+ list_size(list_size_in),
46
+ ids(ids_in),
47
+ sel(sel_in),
48
+ // A selector implies real ids, so ids==nullptr iff store_pairs;
49
+ // that disables the context path exactly when ids[] is synthetic.
50
+ dispatch(sel_in, /*store_pairs=*/ids_in == nullptr) {}
42
51
 
43
52
  inline bool skip_entry(idx_t j) {
44
- return use_sel && !sel->is_member(ids[j]);
53
+ return use_sel &&
54
+ !dispatch.is_member(
55
+ ids[j],
56
+ IDScanContext{ids, list_size, static_cast<size_t>(j)});
45
57
  }
46
58
 
47
59
  inline void add(idx_t j, float dis) {
@@ -204,41 +216,41 @@ struct IVFPQScannerT : QueryTables {
204
216
  }
205
217
  }
206
218
 
219
+ float on_the_fly_dis0 = 0;
220
+
221
+ void prepare_on_the_fly_distance() {
222
+ on_the_fly_dis0 = 0;
223
+ if (!by_residual) {
224
+ return;
225
+ }
226
+ if (METRIC_TYPE == METRIC_INNER_PRODUCT) {
227
+ ivfpq.quantizer->reconstruct(key, residual_vec);
228
+ on_the_fly_dis0 = fvec_inner_product_dispatch(residual_vec, qi, d);
229
+ } else {
230
+ ivfpq.quantizer->compute_residual(qi, residual_vec, key);
231
+ }
232
+ }
233
+
234
+ float distance_to_code_on_the_fly(const uint8_t* code) const {
235
+ pq.decode(code, decoded_vec);
236
+ if (METRIC_TYPE == METRIC_INNER_PRODUCT) {
237
+ return on_the_fly_dis0 +
238
+ fvec_inner_product_dispatch(decoded_vec, qi, d);
239
+ }
240
+ const float* dvec = by_residual ? residual_vec : qi;
241
+ return fvec_L2sqr_dispatch(decoded_vec, dvec, d);
242
+ }
243
+
207
244
  /// nothing is precomputed: access residuals on-the-fly
208
245
  template <class SearchResultType>
209
246
  void scan_on_the_fly_dist(
210
247
  size_t ncode,
211
248
  const uint8_t* codes,
212
249
  SearchResultType& res) const {
213
- const float* dvec;
214
- float local_dis0 = 0;
215
- if (by_residual) {
216
- if (METRIC_TYPE == METRIC_INNER_PRODUCT) {
217
- ivfpq.quantizer->reconstruct(key, residual_vec);
218
- local_dis0 = fvec_inner_product_dispatch(residual_vec, qi, d);
219
- } else {
220
- ivfpq.quantizer->compute_residual(qi, residual_vec, key);
221
- }
222
- dvec = residual_vec;
223
- } else {
224
- dvec = qi;
225
- local_dis0 = 0;
226
- }
227
-
228
250
  for (size_t j = 0; j < ncode; j++, codes += pq.code_size) {
229
- if (res.skip_entry(j)) {
230
- continue;
231
- }
232
- pq.decode(codes, decoded_vec);
233
-
234
- float dis;
235
- if (METRIC_TYPE == METRIC_INNER_PRODUCT) {
236
- dis = local_dis0 +
237
- fvec_inner_product_dispatch(decoded_vec, qi, d);
238
- } else {
239
- dis = fvec_L2sqr_dispatch(decoded_vec, dvec, d);
251
+ if (!res.skip_entry(j)) {
252
+ res.add(j, distance_to_code_on_the_fly(codes));
240
253
  }
241
- res.add(j, dis);
242
254
  }
243
255
  }
244
256
 
@@ -426,7 +438,6 @@ struct IVFPQScannerT : QueryTables {
426
438
  *
427
439
  * precompute_mode is how much we precompute (2 = precompute distance tables,
428
440
  * 1 = precompute pointers to distances, 0 = compute distances one by one).
429
- * Currently only 2 is supported
430
441
  *
431
442
  * use_sel: store or ignore the IDSelector
432
443
  */
@@ -451,20 +462,29 @@ struct IVFPQScanner : IVFPQScannerT<idx_t, METRIC_TYPE, PQCodeDist>,
451
462
  }
452
463
 
453
464
  void set_query(const float* query) override {
454
- this->init_query(query);
465
+ if (precompute_mode == 0) {
466
+ this->qi = query;
467
+ } else {
468
+ this->init_query(query);
469
+ }
455
470
  }
456
471
 
457
472
  void set_list(idx_t list_no_in, float coarse_dis_in) override {
458
473
  this->list_no = list_no_in;
459
474
  this->init_list(list_no_in, coarse_dis_in, precompute_mode);
475
+ if (precompute_mode == 0) {
476
+ this->prepare_on_the_fly_distance();
477
+ }
460
478
  }
461
479
 
462
480
  float distance_to_code(const uint8_t* code) const override {
481
+ if (precompute_mode == 0) {
482
+ return this->distance_to_code_on_the_fly(code);
483
+ }
463
484
  FAISS_THROW_IF_NOT(precompute_mode == 2);
464
- float dis = this->dis0 +
485
+ return this->dis0 +
465
486
  PQCodeDist::distance_single_code(
466
- this->pq.M, this->pq.nbits, this->sim_table, code);
467
- return dis;
487
+ this->pq.M, this->pq.nbits, this->sim_table, code);
468
488
  }
469
489
 
470
490
  size_t scan_codes(
@@ -474,6 +494,7 @@ struct IVFPQScanner : IVFPQScannerT<idx_t, METRIC_TYPE, PQCodeDist>,
474
494
  ResultHandler& handler) const override {
475
495
  WrappedSearchResult<C, use_sel> res(
476
496
  this->key,
497
+ ncode,
477
498
  this->store_pairs ? nullptr : ids,
478
499
  this->sel,
479
500
  handler);
@@ -498,27 +519,40 @@ template <SIMDLevel SL>
498
519
  InvertedListScanner* make_IVFPQInvertedListScanner(
499
520
  const IndexIVFPQ& ivfpq,
500
521
  bool store_pairs,
501
- const IDSelector* sel);
522
+ const IDSelector* sel,
523
+ IndexIVFPQ::ScannerMode mode);
524
+
525
+ template <SIMDLevel SL>
526
+ inline InvertedListScanner* make_IVFPQInvertedListScanner(
527
+ const IndexIVFPQ& ivfpq,
528
+ bool store_pairs,
529
+ const IDSelector* sel) {
530
+ return make_IVFPQInvertedListScanner<SL>(
531
+ ivfpq, store_pairs, sel, IndexIVFPQ::ScannerMode::Precomputed);
532
+ }
502
533
 
503
534
  // NOLINTNEXTLINE(facebook-hte-MisplacedTemplateSpecialization)
504
535
  template <>
505
536
  InvertedListScanner* make_IVFPQInvertedListScanner<THE_SIMD_LEVEL>(
506
537
  const IndexIVFPQ& ivfpq,
507
538
  bool store_pairs,
508
- const IDSelector* sel) {
539
+ const IDSelector* sel,
540
+ IndexIVFPQ::ScannerMode mode) {
541
+ const int precompute_mode =
542
+ mode == IndexIVFPQ::ScannerMode::Precomputed ? 2 : 0;
509
543
  auto make = [&]<class PQCodeDist, bool use_sel>() -> InvertedListScanner* {
510
544
  if (ivfpq.metric_type == METRIC_INNER_PRODUCT) {
511
545
  return new IVFPQScanner<
512
546
  METRIC_INNER_PRODUCT,
513
547
  CMin<float, idx_t>,
514
548
  PQCodeDist,
515
- use_sel>(ivfpq, store_pairs, 2, sel);
549
+ use_sel>(ivfpq, store_pairs, precompute_mode, sel);
516
550
  } else if (ivfpq.metric_type == METRIC_L2) {
517
551
  return new IVFPQScanner<
518
552
  METRIC_L2,
519
553
  CMax<float, idx_t>,
520
554
  PQCodeDist,
521
- use_sel>(ivfpq, store_pairs, 2, sel);
555
+ use_sel>(ivfpq, store_pairs, precompute_mode, sel);
522
556
  } else {
523
557
  FAISS_THROW_MSG("unsupported metric type");
524
558
  }
@@ -7,7 +7,7 @@
7
7
 
8
8
  // This TU provides non-templated PQ code distance dispatch wrappers
9
9
  // (pq_code_distance_8bit_single, pq_code_distance_8bit_four) declared
10
- // in pq_code_distance-inl.h. These use with_simd_level to route to the
10
+ // in pq_code_distance-inl.h. These use with_simd_level_with_sve to route to the
11
11
  // best available SIMD implementation via pq_code_distance_8bit_*_impl
12
12
  // function template specializations.
13
13
  //
@@ -34,7 +34,7 @@ void pq_scan_8bit(
34
34
  float* heap_dis,
35
35
  int64_t* heap_ids,
36
36
  bool max_heap) {
37
- with_simd_level([&]<SIMDLevel SL>() {
37
+ with_simd_level_with_sve([&]<SIMDLevel SL>() {
38
38
  pq_scan_8bit_impl<SL>(
39
39
  M, dis_table, codes, ncodes, k, heap_dis, heap_ids, max_heap);
40
40
  });
@@ -44,7 +44,7 @@ float pq_code_distance_8bit_single(
44
44
  size_t M,
45
45
  const float* sim_table,
46
46
  const uint8_t* code) {
47
- return with_simd_level([&]<SIMDLevel SL>() {
47
+ return with_simd_level_with_sve([&]<SIMDLevel SL>() {
48
48
  return pq_code_distance_8bit_single_impl<SL>(M, sim_table, code);
49
49
  });
50
50
  }
@@ -60,7 +60,7 @@ void pq_code_distance_8bit_four(
60
60
  float& result1,
61
61
  float& result2,
62
62
  float& result3) {
63
- with_simd_level([&]<SIMDLevel SL>() {
63
+ with_simd_level_with_sve([&]<SIMDLevel SL>() {
64
64
  pq_code_distance_8bit_four_impl<SL>(
65
65
  M,
66
66
  sim_table,
@@ -319,8 +319,13 @@ struct EDENDistanceComputerBase : EDENFlatCodesDistanceComputer {
319
319
  const EDENCodeFactors* factors,
320
320
  float code_dot_query) const {
321
321
  if (metric_type == MetricType::METRIC_L2) {
322
- return query_base + factors->l2_norm_term -
322
+ // The unbiased scale reports an unbiased estimator of the squared
323
+ // L2 distance rather than a true squared distance, so the raw
324
+ // value can dip below zero for strongly-aligned residuals. Clamp
325
+ // to keep the METRIC_L2 contract (distances are never negative).
326
+ const float dist = query_base + factors->l2_norm_term -
323
327
  2.0f * factors->scale * code_dot_query;
328
+ return dist < 0.0f ? 0.0f : dist;
324
329
  }
325
330
  return query_base + factors->scale * code_dot_query;
326
331
  }
@@ -78,8 +78,8 @@ template <class Similarity, SIMDLevel SL>
78
78
  struct DistanceComputerByte : SQDistanceComputer {};
79
79
 
80
80
  // Byte-domain distance computer for QT_8bit_direct_signed (storage is
81
- // value+128). Only specialized for AVX512_SPR; other levels fall back to
82
- // the float-domain DCTemplate path via the dispatch logic.
81
+ // value+128). A level added to the sq-dispatch.h chain without a
82
+ // specialization here instantiates this empty template.
83
83
  template <class Similarity, SIMDLevel SL>
84
84
  struct DistanceComputerByteSigned : SQDistanceComputer {};
85
85
 
@@ -733,7 +733,9 @@ struct DistanceComputerByte<Similarity, SIMDLevel::AVX2> : SQDistanceComputer {
733
733
  int d;
734
734
  std::vector<uint8_t> tmp;
735
735
 
736
- DistanceComputerByte(int d, const std::vector<float>&) : d(d), tmp(d) {}
736
+ DistanceComputerByte(int d, const std::vector<float>&) : d(d), tmp(d) {
737
+ FAISS_THROW_IF_NOT(d % 16 == 0);
738
+ }
737
739
 
738
740
  int compute_code_distance(const uint8_t* code1, const uint8_t* code2)
739
741
  const {
@@ -796,7 +798,9 @@ struct DistanceComputerByteSigned<Similarity, SIMDLevel::AVX2>
796
798
  std::vector<uint8_t> tmp;
797
799
 
798
800
  DistanceComputerByteSigned(int d, const std::vector<float>&)
799
- : d(d), tmp(d) {}
801
+ : d(d), tmp(d) {
802
+ FAISS_THROW_IF_NOT(d % 16 == 0);
803
+ }
800
804
 
801
805
  int compute_code_distance(const uint8_t* code1, const uint8_t* code2)
802
806
  const {
@@ -28,6 +28,14 @@ namespace scalar_quantizer {
28
28
  // Define SL as alias for THE_LEVEL_TO_DISPATCH for use in this file
29
29
  constexpr SIMDLevel SL = THE_LEVEL_TO_DISPATCH;
30
30
 
31
+ // The SPR scalar-quantizer implementation uses VPOPCNTDQ for its RaBitQ
32
+ // popcount helpers, but those helpers belong to the narrower VPOPCNT
33
+ // capability. Keep the SQ implementation at AVX512_SPR while explicitly
34
+ // reusing the independently dispatched VPOPCNT kernels.
35
+ template <SIMDLevel SL0>
36
+ inline constexpr SIMDLevel rabitq_popcount_level =
37
+ SL0 == SIMDLevel::AVX512_SPR ? SIMDLevel::AVX512_VPOPCNT : SL0;
38
+
31
39
  /*******************************************************************
32
40
  * TurboQuant SIMD kernel: masked_sum
33
41
  * Compute sum of arr[j] where bit j of the bitmask is set.
@@ -259,9 +267,11 @@ struct DCTurboQuantFull : ScalarQuantizer::TurboQuantRefine::DistanceComputer {
259
267
  if (qb > 0) {
260
268
  // Integer popcount path for 1-bit MSE
261
269
  size_t byte_size = (d + 7) / 8;
262
- uint64_t and_result = rabitq::bitwise_and_dot_product<SL2>(
270
+ uint64_t and_result = rabitq::bitwise_and_dot_product<
271
+ rabitq_popcount_level<SL2>>(
263
272
  rearranged_q.data(), code, byte_size, qb);
264
- uint64_t pop = rabitq::popcount<SL2>(code, byte_size);
273
+ uint64_t pop = rabitq::popcount<rabitq_popcount_level<SL2>>(
274
+ code, byte_size);
265
275
  mse_dot = mse_base +
266
276
  mse_int_scale * static_cast<float>(and_result) +
267
277
  mse_popcnt_scale * static_cast<float>(pop);
@@ -316,9 +326,11 @@ struct DCTurboQuantFull : ScalarQuantizer::TurboQuantRefine::DistanceComputer {
316
326
  float qjl_dot;
317
327
  if (qb > 0 && int_qjl) {
318
328
  size_t byte_size = (d + 7) / 8;
319
- uint64_t and_result = rabitq::bitwise_and_dot_product<SL2>(
320
- rearranged_qproj.data(), qjl_code, byte_size, qb);
321
- uint64_t pop = rabitq::popcount<SL2>(qjl_code, byte_size);
329
+ uint64_t and_result =
330
+ rabitq::bitwise_and_dot_product<rabitq_popcount_level<SL2>>(
331
+ rearranged_qproj.data(), qjl_code, byte_size, qb);
332
+ uint64_t pop = rabitq::popcount<rabitq_popcount_level<SL2>>(
333
+ qjl_code, byte_size);
322
334
  float pos_sum = qjl_popcnt_scale * static_cast<float>(pop) +
323
335
  qjl_int_scale * static_cast<float>(and_result);
324
336
  qjl_dot = qjl_coeff * gamma * (2.0f * pos_sum - total_qproj_sum);
@@ -342,10 +354,17 @@ struct DCTurboQuantFull : ScalarQuantizer::TurboQuantRefine::DistanceComputer {
342
354
  }
343
355
  };
344
356
 
357
+ // True for every level that runs the 512-bit kernels. AVX512_VPOPCNT is
358
+ // unreachable here until an SQ translation unit is compiled at that level;
359
+ // naming it keeps the 512-bit alignment rule correct when one is.
360
+ template <SIMDLevel SL2>
361
+ constexpr bool is_avx512_family = SL2 == SIMDLevel::AVX512 ||
362
+ SL2 == SIMDLevel::AVX512_VPOPCNT || SL2 == SIMDLevel::AVX512_SPR;
363
+
345
364
  // Returns true if dimension d is compatible with the given SIMD level
346
365
  template <SIMDLevel SL2>
347
366
  constexpr bool is_dimension_compatible(size_t d) {
348
- if constexpr (SL2 == SIMDLevel::AVX512 || SL2 == SIMDLevel::AVX512_SPR) {
367
+ if constexpr (is_avx512_family<SL2>) {
349
368
  return d % 16 == 0;
350
369
  } else if constexpr (SL2 == SIMDLevel::AVX2 || SL2 == SIMDLevel::ARM_NEON) {
351
370
  return d % 8 == 0;
@@ -525,13 +544,13 @@ SQDistanceComputer* select_distance_computer_body(
525
544
  return new DCTemplate<QuantizerBF16<SL2>, Sim, SL2>(d, trained);
526
545
 
527
546
  case ScalarQuantizer::QT_8bit_direct:
528
- if constexpr (
529
- SL2 == SIMDLevel::AVX512 || SL2 == SIMDLevel::AVX512_SPR) {
547
+ if constexpr (is_avx512_family<SL2>) {
530
548
  if (d % 32 == 0) {
531
549
  return new DistanceComputerByte<Sim, SL2>(
532
550
  static_cast<int>(d), trained);
533
551
  }
534
- } else if constexpr (SL2 == SIMDLevel::AVX2) {
552
+ } else if constexpr (
553
+ SL2 == SIMDLevel::AVX2 || SL2 == SIMDLevel::ARM_NEON) {
535
554
  if (d % 16 == 0) {
536
555
  return new DistanceComputerByte<Sim, SL2>(
537
556
  static_cast<int>(d), trained);
@@ -546,12 +565,13 @@ SQDistanceComputer* select_distance_computer_body(
546
565
  return new DistanceComputerByteSigned<Sim, SL2>(
547
566
  static_cast<int>(d), trained);
548
567
  }
549
- } else if constexpr (SL2 == SIMDLevel::AVX512) {
568
+ } else if constexpr (is_avx512_family<SL2>) {
550
569
  if (d % 32 == 0) {
551
570
  return new DistanceComputerByteSigned<Sim, SL2>(
552
571
  static_cast<int>(d), trained);
553
572
  }
554
- } else if constexpr (SL2 == SIMDLevel::AVX2) {
573
+ } else if constexpr (
574
+ SL2 == SIMDLevel::AVX2 || SL2 == SIMDLevel::ARM_NEON) {
555
575
  if (d % 16 == 0) {
556
576
  return new DistanceComputerByteSigned<Sim, SL2>(
557
577
  static_cast<int>(d), trained);
@@ -737,14 +757,13 @@ InvertedListScanner* sq_select_InvertedListScanner<THE_LEVEL_TO_DISPATCH>(
737
757
  return scan.template
738
758
  operator()<DCTemplate<QuantizerBF16<SL2>, Similarity, SL2>>();
739
759
  case ScalarQuantizer::QT_8bit_direct:
740
- if constexpr (
741
- SL2 == SIMDLevel::AVX512 ||
742
- SL2 == SIMDLevel::AVX512_SPR) {
760
+ if constexpr (is_avx512_family<SL2>) {
743
761
  if (d % 32 == 0) {
744
762
  return scan.template
745
763
  operator()<DistanceComputerByte<Similarity, SL2>>();
746
764
  }
747
- } else if constexpr (SL2 == SIMDLevel::AVX2) {
765
+ } else if constexpr (
766
+ SL2 == SIMDLevel::AVX2 || SL2 == SIMDLevel::ARM_NEON) {
748
767
  if (d % 16 == 0) {
749
768
  return scan.template
750
769
  operator()<DistanceComputerByte<Similarity, SL2>>();
@@ -760,12 +779,13 @@ InvertedListScanner* sq_select_InvertedListScanner<THE_LEVEL_TO_DISPATCH>(
760
779
  return scan.template operator()<
761
780
  DistanceComputerByteSigned<Similarity, SL2>>();
762
781
  }
763
- } else if constexpr (SL2 == SIMDLevel::AVX512) {
782
+ } else if constexpr (is_avx512_family<SL2>) {
764
783
  if (d % 32 == 0) {
765
784
  return scan.template operator()<
766
785
  DistanceComputerByteSigned<Similarity, SL2>>();
767
786
  }
768
- } else if constexpr (SL2 == SIMDLevel::AVX2) {
787
+ } else if constexpr (
788
+ SL2 == SIMDLevel::AVX2 || SL2 == SIMDLevel::ARM_NEON) {
769
789
  if (d % 16 == 0) {
770
790
  return scan.template operator()<
771
791
  DistanceComputerByteSigned<Similarity, SL2>>();
@@ -618,6 +618,64 @@ struct DCTemplate<Quantizer, Similarity, SIMDLevel::ARM_NEON>
618
618
  }
619
619
  };
620
620
 
621
+ // Byte-domain kernels for QT_8bit_direct{,_signed}. The dispatch only
622
+ // selects them when d % 16 == 0, so no loop needs a tail.
623
+
624
+ namespace {
625
+
626
+ // The accumulator stays unsigned: a vmull_u8 square reaches 255*255 = 65025,
627
+ // which an int16 lane would read as negative.
628
+ FAISS_ALWAYS_INLINE int neon_byte_l2sqr(
629
+ const uint8_t* code1,
630
+ const uint8_t* code2,
631
+ int d) {
632
+ uint32x4_t accu = vdupq_n_u32(0);
633
+ for (int i = 0; i < d; i += 16) {
634
+ const uint8x16_t diff =
635
+ vabdq_u8(vld1q_u8(code1 + i), vld1q_u8(code2 + i));
636
+ accu = vpadalq_u16(
637
+ accu, vmull_u8(vget_low_u8(diff), vget_low_u8(diff)));
638
+ accu = vpadalq_u16(
639
+ accu, vmull_u8(vget_high_u8(diff), vget_high_u8(diff)));
640
+ }
641
+ return static_cast<int>(vaddvq_u32(accu));
642
+ }
643
+
644
+ FAISS_ALWAYS_INLINE int neon_byte_ip(
645
+ const uint8_t* code1,
646
+ const uint8_t* code2,
647
+ int d) {
648
+ uint32x4_t accu = vdupq_n_u32(0);
649
+ for (int i = 0; i < d; i += 16) {
650
+ const uint8x16_t c1 = vld1q_u8(code1 + i);
651
+ const uint8x16_t c2 = vld1q_u8(code2 + i);
652
+ accu = vpadalq_u16(accu, vmull_u8(vget_low_u8(c1), vget_low_u8(c2)));
653
+ accu = vpadalq_u16(accu, vmull_u8(vget_high_u8(c1), vget_high_u8(c2)));
654
+ }
655
+ return static_cast<int>(vaddvq_u32(accu));
656
+ }
657
+
658
+ // The codes store value + 128. For x in 0 to 255, x ^ 0x80 read as int8 is
659
+ // exactly x - 128, which is how the bias comes off before vmull_s8.
660
+ FAISS_ALWAYS_INLINE int neon_byte_ip_unbias(
661
+ const uint8_t* code1,
662
+ const uint8_t* code2,
663
+ int d) {
664
+ const uint8x16_t bias = vdupq_n_u8(0x80);
665
+ int32x4_t accu = vdupq_n_s32(0);
666
+ for (int i = 0; i < d; i += 16) {
667
+ const int8x16_t c1 =
668
+ vreinterpretq_s8_u8(veorq_u8(vld1q_u8(code1 + i), bias));
669
+ const int8x16_t c2 =
670
+ vreinterpretq_s8_u8(veorq_u8(vld1q_u8(code2 + i), bias));
671
+ accu = vpadalq_s16(accu, vmull_s8(vget_low_s8(c1), vget_low_s8(c2)));
672
+ accu = vpadalq_s16(accu, vmull_s8(vget_high_s8(c1), vget_high_s8(c2)));
673
+ }
674
+ return static_cast<int>(vaddvq_s32(accu));
675
+ }
676
+
677
+ } // namespace
678
+
621
679
  template <class Similarity>
622
680
  struct DistanceComputerByte<Similarity, SIMDLevel::ARM_NEON>
623
681
  : SQDistanceComputer {
@@ -626,20 +684,17 @@ struct DistanceComputerByte<Similarity, SIMDLevel::ARM_NEON>
626
684
  int d;
627
685
  std::vector<uint8_t> tmp;
628
686
 
629
- DistanceComputerByte(int d, const std::vector<float>&) : d(d), tmp(d) {}
687
+ DistanceComputerByte(int d, const std::vector<float>&) : d(d), tmp(d) {
688
+ FAISS_THROW_IF_NOT(d % 16 == 0);
689
+ }
630
690
 
631
691
  int compute_code_distance(const uint8_t* code1, const uint8_t* code2)
632
692
  const {
633
- int accu = 0;
634
- for (int i = 0; i < d; i++) {
635
- if (Sim::metric_type == METRIC_INNER_PRODUCT) {
636
- accu += int(code1[i]) * code2[i];
637
- } else {
638
- int diff = int(code1[i]) - code2[i];
639
- accu += diff * diff;
640
- }
693
+ if constexpr (Sim::metric_type == METRIC_INNER_PRODUCT) {
694
+ return neon_byte_ip(code1, code2, d);
695
+ } else {
696
+ return neon_byte_l2sqr(code1, code2, d);
641
697
  }
642
- return accu;
643
698
  }
644
699
 
645
700
  void set_query(const float* x) final {
@@ -663,6 +718,50 @@ struct DistanceComputerByte<Similarity, SIMDLevel::ARM_NEON>
663
718
  }
664
719
  };
665
720
 
721
+ template <class Similarity>
722
+ struct DistanceComputerByteSigned<Similarity, SIMDLevel::ARM_NEON>
723
+ : SQDistanceComputer {
724
+ using Sim = Similarity;
725
+
726
+ int d;
727
+ std::vector<uint8_t> tmp;
728
+
729
+ DistanceComputerByteSigned(int d, const std::vector<float>&)
730
+ : d(d), tmp(d) {
731
+ FAISS_THROW_IF_NOT(d % 16 == 0);
732
+ }
733
+
734
+ int compute_code_distance(const uint8_t* code1, const uint8_t* code2)
735
+ const {
736
+ if constexpr (Sim::metric_type == METRIC_INNER_PRODUCT) {
737
+ return neon_byte_ip_unbias(code1, code2, d);
738
+ } else {
739
+ // The bias cancels in the difference.
740
+ return neon_byte_l2sqr(code1, code2, d);
741
+ }
742
+ }
743
+
744
+ void set_query(const float* x) final {
745
+ for (int i = 0; i < d; i++) {
746
+ tmp[i] = uint8_t(int(x[i]) + 128);
747
+ }
748
+ }
749
+
750
+ int compute_distance(const float* x, const uint8_t* code) {
751
+ set_query(x);
752
+ return compute_code_distance(tmp.data(), code);
753
+ }
754
+
755
+ float symmetric_dis(idx_t i, idx_t j) override {
756
+ return compute_code_distance(
757
+ codes + i * code_size, codes + j * code_size);
758
+ }
759
+
760
+ float query_to_code(const uint8_t* code) const final {
761
+ return compute_code_distance(tmp.data(), code);
762
+ }
763
+ };
764
+
666
765
  /**********************************************************
667
766
  * TurboQuant masked_sum NEON specialization (scalar fallback)
668
767
  **********************************************************/
@@ -32,30 +32,32 @@ constexpr int AVAILABLE_SIMD_LEVELS_NONE = (1 << int(SIMDLevel::NONE));
32
32
  constexpr int AVAILABLE_SIMD_LEVELS_AVX2_NEON = AVAILABLE_SIMD_LEVELS_NONE |
33
33
  (1 << int(SIMDLevel::AVX2)) | (1 << int(SIMDLevel::ARM_NEON));
34
34
 
35
- // A0: same + AVX512 + RISCV_RVV
36
- constexpr int AVAILABLE_SIMD_LEVELS_A0 = AVAILABLE_SIMD_LEVELS_AVX2_NEON |
35
+ // BASE: AVX2_NEON + AVX512 + RISCV_RVV
36
+ constexpr int AVAILABLE_SIMD_LEVELS_BASE = AVAILABLE_SIMD_LEVELS_AVX2_NEON |
37
37
  (1 << int(SIMDLevel::AVX512)) | (1 << int(SIMDLevel::RISCV_RVV));
38
38
 
39
- // A0_SPR: same as A0 + AVX512_SPR (for functions with a dedicated SPR
40
- // specialization on top of an AVX512 fallback). Currently used by the
41
- // RaBitQ popcount kernels, which use VPOPCNTDQ on SPR+.
42
- constexpr int AVAILABLE_SIMD_LEVELS_A0_SPR =
43
- AVAILABLE_SIMD_LEVELS_A0 | (1 << int(SIMDLevel::AVX512_SPR));
39
+ // BASE_WITH_VPOPCNT: BASE + AVX512_VPOPCNT, for kernels that need only
40
+ // VPOPCNTDQ on top of baseline AVX-512 (Ice Lake, Zen 4, Zen 5).
41
+ constexpr int AVAILABLE_SIMD_LEVELS_BASE_WITH_VPOPCNT =
42
+ AVAILABLE_SIMD_LEVELS_BASE | (1 << int(SIMDLevel::AVX512_VPOPCNT));
44
43
 
45
- // A1: same + ARM_SVE (for functions with dedicated SVE implementations)
46
- constexpr int AVAILABLE_SIMD_LEVELS_A1 =
47
- AVAILABLE_SIMD_LEVELS_A0 | (1 << int(SIMDLevel::ARM_SVE));
44
+ // BASE_WITH_SPR: BASE + AVX512_SPR, for kernels that need the whole SPR
45
+ // feature set rather than VPOPCNTDQ alone.
46
+ constexpr int AVAILABLE_SIMD_LEVELS_BASE_WITH_SPR =
47
+ AVAILABLE_SIMD_LEVELS_BASE | (1 << int(SIMDLevel::AVX512_SPR));
48
48
 
49
- // A2: NONE + AVX2 + ARM_SVE only (for functions with only these
50
- // implementations)
51
- constexpr int AVAILABLE_SIMD_LEVELS_A2 = AVAILABLE_SIMD_LEVELS_NONE |
52
- (1 << int(SIMDLevel::AVX2)) | (1 << int(SIMDLevel::ARM_SVE));
49
+ // BASE_WITH_SVE: BASE + ARM_SVE, for functions with dedicated SVE
50
+ // implementations.
51
+ constexpr int AVAILABLE_SIMD_LEVELS_BASE_WITH_SVE =
52
+ AVAILABLE_SIMD_LEVELS_BASE | (1 << int(SIMDLevel::ARM_SVE));
53
53
 
54
54
  constexpr int AVAILABLE_SIMD_LEVELS_ALL = -1;
55
55
 
56
56
  constexpr SIMDLevel get_simd_fallback(SIMDLevel level) {
57
57
  switch (level) {
58
58
  case SIMDLevel::AVX512_SPR:
59
+ return SIMDLevel::AVX512_VPOPCNT;
60
+ case SIMDLevel::AVX512_VPOPCNT:
59
61
  return SIMDLevel::AVX512;
60
62
  case SIMDLevel::AVX512:
61
63
  return SIMDLevel::AVX2;
@@ -124,6 +126,15 @@ inline auto with_selected_simd_levels(LambdaType&& action) {
124
126
  [[fallthrough]];
125
127
  #endif
126
128
 
129
+ #ifdef COMPILE_SIMD_AVX512_VPOPCNT
130
+ case SIMDLevel::AVX512_VPOPCNT:
131
+ if constexpr (
132
+ available_levels & (1 << int(SIMDLevel::AVX512_VPOPCNT))) {
133
+ return action.template operator()<SIMDLevel::AVX512_VPOPCNT>();
134
+ }
135
+ [[fallthrough]];
136
+ #endif
137
+
127
138
  #ifdef COMPILE_SIMD_AVX512
128
139
  case SIMDLevel::AVX512:
129
140
  if constexpr (available_levels & (1 << int(SIMDLevel::AVX512))) {
@@ -171,7 +182,7 @@ inline auto with_selected_simd_levels(LambdaType&& action) {
171
182
  // In static mode, SINGLE_SIMD_LEVEL is a constexpr resolved at compile
172
183
  // time. We mirror the DD fallthrough behavior at compile time via
173
184
  // dispatch_with_fallback, which recursively walks get_simd_fallback:
174
- // x86: AVX512_SPR -> AVX512 -> AVX2 -> NONE
185
+ // x86: AVX512_SPR -> AVX512_VPOPCNT -> AVX512 -> AVX2 -> NONE
175
186
  // ARM: ARM_SVE -> ARM_NEON -> NONE
176
187
  // RISCV: RISCV_RVV -> NONE
177
188
  // The first level in the chain that appears in available_levels is
@@ -224,7 +235,7 @@ inline auto with_simd_level_fallback(const LambdaType& action) {
224
235
  */
225
236
  template <typename LambdaType>
226
237
  inline auto with_simd_level(LambdaType&& action) {
227
- return with_selected_simd_levels<AVAILABLE_SIMD_LEVELS_A0>(
238
+ return with_selected_simd_levels<AVAILABLE_SIMD_LEVELS_BASE>(
228
239
  std::forward<LambdaType>(action));
229
240
  }
230
241
 
@@ -238,13 +249,20 @@ inline auto with_simd_level_256bit(LambdaType&& action) {
238
249
  std::forward<LambdaType>(action));
239
250
  }
240
251
 
241
- /**
242
- * Use for functions that have A0-level implementations plus an AVX512_SPR
243
- * specialization (e.g. using VPOPCNTDQ).
244
- */
252
+ // Plain with_simd_level() uses BASE, which omits the optional levels below.
253
+ // A call site must opt in, or its specialization is never instantiated.
254
+
255
+ /// BASE + ARM_SVE.
256
+ template <typename LambdaType>
257
+ inline auto with_simd_level_with_sve(LambdaType&& action) {
258
+ return with_selected_simd_levels<AVAILABLE_SIMD_LEVELS_BASE_WITH_SVE>(
259
+ std::forward<LambdaType>(action));
260
+ }
261
+
262
+ /// BASE + AVX512_VPOPCNT.
245
263
  template <typename LambdaType>
246
- inline auto with_simd_level_a0_spr(LambdaType&& action) {
247
- return with_selected_simd_levels<AVAILABLE_SIMD_LEVELS_A0_SPR>(
264
+ inline auto with_simd_level_with_vpopcnt(LambdaType&& action) {
265
+ return with_selected_simd_levels<AVAILABLE_SIMD_LEVELS_BASE_WITH_VPOPCNT>(
248
266
  std::forward<LambdaType>(action));
249
267
  }
250
268