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
@@ -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;
@@ -416,6 +435,34 @@ ScalarQuantizer::SQuantizer* sq_select_quantizer<THE_LEVEL_TO_DISPATCH>(
416
435
  return new QuantizerTurboQuantMSE<4, SL>(d, trained);
417
436
  case ScalarQuantizer::QT_8bit_tqmse:
418
437
  return new QuantizerTurboQuantMSE<8, SL>(d, trained);
438
+ case ScalarQuantizer::QT_1bit_eden:
439
+ return new QuantizerLloydMax<1, SL>(d, trained);
440
+ case ScalarQuantizer::QT_2bit_eden:
441
+ return new QuantizerLloydMax<2, SL>(d, trained);
442
+ case ScalarQuantizer::QT_3bit_eden:
443
+ return new QuantizerLloydMax<3, SL>(d, trained);
444
+ case ScalarQuantizer::QT_4bit_eden:
445
+ return new QuantizerLloydMax<4, SL>(d, trained);
446
+ case ScalarQuantizer::QT_5bit_eden:
447
+ if constexpr (SL != SIMDLevel::NONE) {
448
+ return nullptr;
449
+ } else {
450
+ return new QuantizerLloydMax<5, SL>(d, trained);
451
+ }
452
+ case ScalarQuantizer::QT_6bit_eden:
453
+ if constexpr (SL != SIMDLevel::NONE) {
454
+ return nullptr;
455
+ } else {
456
+ return new QuantizerLloydMax<6, SL>(d, trained);
457
+ }
458
+ case ScalarQuantizer::QT_7bit_eden:
459
+ if constexpr (SL != SIMDLevel::NONE) {
460
+ return nullptr;
461
+ } else {
462
+ return new QuantizerLloydMax<7, SL>(d, trained);
463
+ }
464
+ case ScalarQuantizer::QT_8bit_eden:
465
+ return new QuantizerLloydMax<8, SL>(d, trained);
419
466
  case ScalarQuantizer::QT_2bit_tq:
420
467
  return new QuantizerTurboQuantFull<2, SL>(d, trained);
421
468
  case ScalarQuantizer::QT_3bit_tq:
@@ -497,13 +544,13 @@ SQDistanceComputer* select_distance_computer_body(
497
544
  return new DCTemplate<QuantizerBF16<SL2>, Sim, SL2>(d, trained);
498
545
 
499
546
  case ScalarQuantizer::QT_8bit_direct:
500
- if constexpr (
501
- SL2 == SIMDLevel::AVX512 || SL2 == SIMDLevel::AVX512_SPR) {
547
+ if constexpr (is_avx512_family<SL2>) {
502
548
  if (d % 32 == 0) {
503
549
  return new DistanceComputerByte<Sim, SL2>(
504
550
  static_cast<int>(d), trained);
505
551
  }
506
- } else if constexpr (SL2 == SIMDLevel::AVX2) {
552
+ } else if constexpr (
553
+ SL2 == SIMDLevel::AVX2 || SL2 == SIMDLevel::ARM_NEON) {
507
554
  if (d % 16 == 0) {
508
555
  return new DistanceComputerByte<Sim, SL2>(
509
556
  static_cast<int>(d), trained);
@@ -518,6 +565,17 @@ SQDistanceComputer* select_distance_computer_body(
518
565
  return new DistanceComputerByteSigned<Sim, SL2>(
519
566
  static_cast<int>(d), trained);
520
567
  }
568
+ } else if constexpr (is_avx512_family<SL2>) {
569
+ if (d % 32 == 0) {
570
+ return new DistanceComputerByteSigned<Sim, SL2>(
571
+ static_cast<int>(d), trained);
572
+ }
573
+ } else if constexpr (
574
+ SL2 == SIMDLevel::AVX2 || SL2 == SIMDLevel::ARM_NEON) {
575
+ if (d % 16 == 0) {
576
+ return new DistanceComputerByteSigned<Sim, SL2>(
577
+ static_cast<int>(d), trained);
578
+ }
521
579
  }
522
580
  return new DCTemplate<Quantizer8bitDirectSigned<SL2>, Sim, SL2>(
523
581
  d, trained);
@@ -539,6 +597,42 @@ SQDistanceComputer* select_distance_computer_body(
539
597
  case ScalarQuantizer::QT_8bit_tqmse:
540
598
  return new DCTemplate<QuantizerTurboQuantMSE<8, SL2>, Sim, SL2>(
541
599
  d, trained);
600
+ case ScalarQuantizer::QT_1bit_eden:
601
+ return new DCTemplate<QuantizerLloydMax<1, SL2>, Sim, SL2>(
602
+ d, trained);
603
+ case ScalarQuantizer::QT_2bit_eden:
604
+ return new DCTemplate<QuantizerLloydMax<2, SL2>, Sim, SL2>(
605
+ d, trained);
606
+ case ScalarQuantizer::QT_3bit_eden:
607
+ return new DCTemplate<QuantizerLloydMax<3, SL2>, Sim, SL2>(
608
+ d, trained);
609
+ case ScalarQuantizer::QT_4bit_eden:
610
+ return new DCTemplate<QuantizerLloydMax<4, SL2>, Sim, SL2>(
611
+ d, trained);
612
+ case ScalarQuantizer::QT_5bit_eden:
613
+ if constexpr (SL2 != SIMDLevel::NONE) {
614
+ return nullptr;
615
+ } else {
616
+ return new DCTemplate<QuantizerLloydMax<5, SL2>, Sim, SL2>(
617
+ d, trained);
618
+ }
619
+ case ScalarQuantizer::QT_6bit_eden:
620
+ if constexpr (SL2 != SIMDLevel::NONE) {
621
+ return nullptr;
622
+ } else {
623
+ return new DCTemplate<QuantizerLloydMax<6, SL2>, Sim, SL2>(
624
+ d, trained);
625
+ }
626
+ case ScalarQuantizer::QT_7bit_eden:
627
+ if constexpr (SL2 != SIMDLevel::NONE) {
628
+ return nullptr;
629
+ } else {
630
+ return new DCTemplate<QuantizerLloydMax<7, SL2>, Sim, SL2>(
631
+ d, trained);
632
+ }
633
+ case ScalarQuantizer::QT_8bit_eden:
634
+ return new DCTemplate<QuantizerLloydMax<8, SL2>, Sim, SL2>(
635
+ d, trained);
542
636
  case ScalarQuantizer::QT_2bit_tq:
543
637
  // FRICTION: bypasses DCTemplate entirely — custom DC
544
638
  // that doesn't fit the Quantizer+Similarity decomposition
@@ -663,14 +757,13 @@ InvertedListScanner* sq_select_InvertedListScanner<THE_LEVEL_TO_DISPATCH>(
663
757
  return scan.template
664
758
  operator()<DCTemplate<QuantizerBF16<SL2>, Similarity, SL2>>();
665
759
  case ScalarQuantizer::QT_8bit_direct:
666
- if constexpr (
667
- SL2 == SIMDLevel::AVX512 ||
668
- SL2 == SIMDLevel::AVX512_SPR) {
760
+ if constexpr (is_avx512_family<SL2>) {
669
761
  if (d % 32 == 0) {
670
762
  return scan.template
671
763
  operator()<DistanceComputerByte<Similarity, SL2>>();
672
764
  }
673
- } else if constexpr (SL2 == SIMDLevel::AVX2) {
765
+ } else if constexpr (
766
+ SL2 == SIMDLevel::AVX2 || SL2 == SIMDLevel::ARM_NEON) {
674
767
  if (d % 16 == 0) {
675
768
  return scan.template
676
769
  operator()<DistanceComputerByte<Similarity, SL2>>();
@@ -686,6 +779,17 @@ InvertedListScanner* sq_select_InvertedListScanner<THE_LEVEL_TO_DISPATCH>(
686
779
  return scan.template operator()<
687
780
  DistanceComputerByteSigned<Similarity, SL2>>();
688
781
  }
782
+ } else if constexpr (is_avx512_family<SL2>) {
783
+ if (d % 32 == 0) {
784
+ return scan.template operator()<
785
+ DistanceComputerByteSigned<Similarity, SL2>>();
786
+ }
787
+ } else if constexpr (
788
+ SL2 == SIMDLevel::AVX2 || SL2 == SIMDLevel::ARM_NEON) {
789
+ if (d % 16 == 0) {
790
+ return scan.template operator()<
791
+ DistanceComputerByteSigned<Similarity, SL2>>();
792
+ }
689
793
  }
690
794
  return scan.template operator()<DCTemplate<
691
795
  Quantizer8bitDirectSigned<SL2>,
@@ -719,6 +823,58 @@ InvertedListScanner* sq_select_InvertedListScanner<THE_LEVEL_TO_DISPATCH>(
719
823
  QuantizerTurboQuantMSE<8, SL2>,
720
824
  Similarity,
721
825
  SL2>>();
826
+ case ScalarQuantizer::QT_1bit_eden:
827
+ return scan.template operator()<DCTemplate<
828
+ QuantizerLloydMax<1, SL2>,
829
+ Similarity,
830
+ SL2>>();
831
+ case ScalarQuantizer::QT_2bit_eden:
832
+ return scan.template operator()<DCTemplate<
833
+ QuantizerLloydMax<2, SL2>,
834
+ Similarity,
835
+ SL2>>();
836
+ case ScalarQuantizer::QT_3bit_eden:
837
+ return scan.template operator()<DCTemplate<
838
+ QuantizerLloydMax<3, SL2>,
839
+ Similarity,
840
+ SL2>>();
841
+ case ScalarQuantizer::QT_4bit_eden:
842
+ return scan.template operator()<DCTemplate<
843
+ QuantizerLloydMax<4, SL2>,
844
+ Similarity,
845
+ SL2>>();
846
+ case ScalarQuantizer::QT_5bit_eden:
847
+ if constexpr (SL2 != SIMDLevel::NONE) {
848
+ return nullptr;
849
+ } else {
850
+ return scan.template operator()<DCTemplate<
851
+ QuantizerLloydMax<5, SL2>,
852
+ Similarity,
853
+ SL2>>();
854
+ }
855
+ case ScalarQuantizer::QT_6bit_eden:
856
+ if constexpr (SL2 != SIMDLevel::NONE) {
857
+ return nullptr;
858
+ } else {
859
+ return scan.template operator()<DCTemplate<
860
+ QuantizerLloydMax<6, SL2>,
861
+ Similarity,
862
+ SL2>>();
863
+ }
864
+ case ScalarQuantizer::QT_7bit_eden:
865
+ if constexpr (SL2 != SIMDLevel::NONE) {
866
+ return nullptr;
867
+ } else {
868
+ return scan.template operator()<DCTemplate<
869
+ QuantizerLloydMax<7, SL2>,
870
+ Similarity,
871
+ SL2>>();
872
+ }
873
+ case ScalarQuantizer::QT_8bit_eden:
874
+ return scan.template operator()<DCTemplate<
875
+ QuantizerLloydMax<8, SL2>,
876
+ Similarity,
877
+ SL2>>();
722
878
  case ScalarQuantizer::QT_2bit_tq:
723
879
  return scan.template
724
880
  operator()<DCTurboQuantFull<2, Similarity, SL2>>();
@@ -223,18 +223,18 @@ struct QuantizerTemplate<
223
223
  };
224
224
 
225
225
  /**********************************************************
226
- * TurboQuant MSE quantizer
226
+ * Lloyd-Max scalar quantizer
227
227
  **********************************************************/
228
228
 
229
- // NEON TurboQuantMSE: decode via gather, encode stays scalar.
229
+ // NEON Lloyd-Max: decode via gather, encode stays scalar.
230
230
  // NEON doesn't have movemask so 1-bit encode is also scalar.
231
- #define DEFINE_TQMSE_NEON_SPECIALIZATION(NBITS, UNPACK_FN) \
231
+ #define DEFINE_LLOYD_MAX_NEON_SPECIALIZATION(NBITS, UNPACK_FN) \
232
232
  template <> \
233
- struct QuantizerTurboQuantMSE<NBITS, SIMDLevel::ARM_NEON> \
234
- : QuantizerTurboQuantMSE<NBITS, SIMDLevel::NONE> { \
235
- using Base = QuantizerTurboQuantMSE<NBITS, SIMDLevel::NONE>; \
233
+ struct QuantizerLloydMax<NBITS, SIMDLevel::ARM_NEON> \
234
+ : QuantizerLloydMax<NBITS, SIMDLevel::NONE> { \
235
+ using Base = QuantizerLloydMax<NBITS, SIMDLevel::NONE>; \
236
236
  \
237
- QuantizerTurboQuantMSE(size_t d, const std::vector<float>& trained) \
237
+ QuantizerLloydMax(size_t d, const std::vector<float>& trained) \
238
238
  : Base(d, trained) { \
239
239
  assert(d % 8 == 0); \
240
240
  } \
@@ -256,19 +256,19 @@ struct QuantizerTemplate<
256
256
  } \
257
257
  }
258
258
 
259
- DEFINE_TQMSE_NEON_SPECIALIZATION(1, unpack_8x1bit_to_u8);
260
- DEFINE_TQMSE_NEON_SPECIALIZATION(2, unpack_8x2bit_to_u8);
261
- DEFINE_TQMSE_NEON_SPECIALIZATION(3, unpack_8x3bit_to_u8);
262
- DEFINE_TQMSE_NEON_SPECIALIZATION(4, unpack_8x4bit_to_u8);
259
+ DEFINE_LLOYD_MAX_NEON_SPECIALIZATION(1, unpack_8x1bit_to_u8);
260
+ DEFINE_LLOYD_MAX_NEON_SPECIALIZATION(2, unpack_8x2bit_to_u8);
261
+ DEFINE_LLOYD_MAX_NEON_SPECIALIZATION(3, unpack_8x3bit_to_u8);
262
+ DEFINE_LLOYD_MAX_NEON_SPECIALIZATION(4, unpack_8x4bit_to_u8);
263
263
 
264
- #undef DEFINE_TQMSE_NEON_SPECIALIZATION
264
+ #undef DEFINE_LLOYD_MAX_NEON_SPECIALIZATION
265
265
 
266
266
  template <>
267
- struct QuantizerTurboQuantMSE<8, SIMDLevel::ARM_NEON>
268
- : QuantizerTurboQuantMSE<8, SIMDLevel::NONE> {
269
- using Base = QuantizerTurboQuantMSE<8, SIMDLevel::NONE>;
267
+ struct QuantizerLloydMax<8, SIMDLevel::ARM_NEON>
268
+ : QuantizerLloydMax<8, SIMDLevel::NONE> {
269
+ using Base = QuantizerLloydMax<8, SIMDLevel::NONE>;
270
270
 
271
- QuantizerTurboQuantMSE(size_t d, const std::vector<float>& trained)
271
+ QuantizerLloydMax(size_t d, const std::vector<float>& trained)
272
272
  : Base(d, trained) {
273
273
  assert(d % 8 == 0);
274
274
  }
@@ -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;
@@ -84,6 +86,24 @@ inline auto dispatch_with_fallback(LambdaType&& action) {
84
86
  }
85
87
  }
86
88
 
89
+ /** Run action at current_level; on a null result, retry the next-lower level,
90
+ * down to NONE (terminal). action is called per level tried, so never moved. */
91
+ template <int available_levels, SIMDLevel current_level, typename LambdaType>
92
+ inline auto dispatch_simd_level_or_lower(LambdaType& action) {
93
+ if constexpr (current_level == SIMDLevel::NONE) {
94
+ return action.template operator()<SIMDLevel::NONE>();
95
+ } else {
96
+ if constexpr (available_levels & (1 << int(current_level))) {
97
+ if (auto result = action.template operator()<current_level>()) {
98
+ return result;
99
+ }
100
+ }
101
+ return dispatch_simd_level_or_lower<
102
+ available_levels,
103
+ get_simd_fallback(current_level)>(action);
104
+ }
105
+ }
106
+
87
107
  /** The complete dispatching function. It takes into account:
88
108
  * - the currently selected SIMD level
89
109
  * - the compiled in SIMD levels (given by COMPILE_SIMD_XXX)
@@ -106,6 +126,15 @@ inline auto with_selected_simd_levels(LambdaType&& action) {
106
126
  [[fallthrough]];
107
127
  #endif
108
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
+
109
138
  #ifdef COMPILE_SIMD_AVX512
110
139
  case SIMDLevel::AVX512:
111
140
  if constexpr (available_levels & (1 << int(SIMDLevel::AVX512))) {
@@ -153,7 +182,7 @@ inline auto with_selected_simd_levels(LambdaType&& action) {
153
182
  // In static mode, SINGLE_SIMD_LEVEL is a constexpr resolved at compile
154
183
  // time. We mirror the DD fallthrough behavior at compile time via
155
184
  // dispatch_with_fallback, which recursively walks get_simd_fallback:
156
- // x86: AVX512_SPR -> AVX512 -> AVX2 -> NONE
185
+ // x86: AVX512_SPR -> AVX512_VPOPCNT -> AVX512 -> AVX2 -> NONE
157
186
  // ARM: ARM_SVE -> ARM_NEON -> NONE
158
187
  // RISCV: RISCV_RVV -> NONE
159
188
  // The first level in the chain that appears in available_levels is
@@ -163,6 +192,18 @@ inline auto with_selected_simd_levels(LambdaType&& action) {
163
192
  #endif
164
193
  }
165
194
 
195
+ /** Like with_selected_simd_levels, but for factory actions that return null to
196
+ * decline a level (e.g. AVX-512 needing d % 16 == 0). Falls back to the next
197
+ * lower level, down to NONE. */
198
+ template <int available_levels, typename LambdaType>
199
+ inline auto with_simd_level_fallback(const LambdaType& action) {
200
+ return with_selected_simd_levels<available_levels>(
201
+ [&action]<SIMDLevel SL>() {
202
+ return dispatch_simd_level_or_lower<available_levels, SL>(
203
+ action);
204
+ });
205
+ }
206
+
166
207
  /**
167
208
  * Dispatch to a lambda with SIMDLevel as a compile-time constant.
168
209
  *
@@ -194,16 +235,7 @@ inline auto with_selected_simd_levels(LambdaType&& action) {
194
235
  */
195
236
  template <typename LambdaType>
196
237
  inline auto with_simd_level(LambdaType&& action) {
197
- return with_selected_simd_levels<AVAILABLE_SIMD_LEVELS_A0>(
198
- std::forward<LambdaType>(action));
199
- }
200
-
201
- /**
202
- * Use for functions with AVX512_SPR-specific implementations.
203
- */
204
- template <typename LambdaType>
205
- inline auto with_simd_level_spr(LambdaType&& action) {
206
- return with_selected_simd_levels<AVAILABLE_SIMD_LEVELS_A0_SPR>(
238
+ return with_selected_simd_levels<AVAILABLE_SIMD_LEVELS_BASE>(
207
239
  std::forward<LambdaType>(action));
208
240
  }
209
241
 
@@ -217,13 +249,20 @@ inline auto with_simd_level_256bit(LambdaType&& action) {
217
249
  std::forward<LambdaType>(action));
218
250
  }
219
251
 
220
- /**
221
- * Use for functions that have A0-level implementations plus an AVX512_SPR
222
- * specialization (e.g. using VPOPCNTDQ).
223
- */
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.
224
263
  template <typename LambdaType>
225
- inline auto with_simd_level_a0_spr(LambdaType&& action) {
226
- 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>(
227
266
  std::forward<LambdaType>(action));
228
267
  }
229
268