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
@@ -5,17 +5,19 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
- #ifndef HAMMING_COMPUTER_AVX512_SPR_H
9
- #define HAMMING_COMPUTER_AVX512_SPR_H
8
+ #ifndef HAMMING_COMPUTER_AVX512_VPOPCNT_H
9
+ #define HAMMING_COMPUTER_AVX512_VPOPCNT_H
10
10
 
11
- // AVX512_SPR HammingComputer specializations using VPOPCNTDQ.
12
- // On Sapphire Rapids+, _mm512_popcnt_epi64 (and _mm256_popcnt_epi64 with VL)
13
- // are unconditionally available. This gives a faster path than the scalar
14
- // popcount fallback used in the base AVX512 specializations when compiled
15
- // without -mavx512vpopcntdq.
11
+ // AVX512_VPOPCNT HammingComputer specializations. The 32/64/Default kernels
12
+ // use VPOPCNTDQ; the batched 20-byte kernel uses AVX512_BITALG. This gives
13
+ // a faster path than the scalar popcount fallback used in the base AVX512
14
+ // specializations when compiled without -mavx512vpopcntdq.
16
15
 
17
16
  #include <cassert>
18
17
  #include <cstdint>
18
+ #include <cstring>
19
+
20
+ #include <faiss/utils/popcount.h>
19
21
 
20
22
  #include <faiss/impl/platform_macros.h>
21
23
  #include <faiss/utils/hamming_distance/hamming_computer-avx512.h>
@@ -25,31 +27,90 @@
25
27
  namespace faiss {
26
28
 
27
29
  /***************************************************************************
28
- * AVX512_SPR inheriting specializations for types without custom SPR code.
30
+ * AVX512_VPOPCNT inheriting specializations without custom VPOPCNT code.
29
31
  ***************************************************************************/
30
32
 
31
- #define FAISS_INHERIT_HAMMING_SPR(Class) \
32
- template <> \
33
- struct Class## \
34
- _tpl<SIMDLevel::AVX512_SPR> : Class##_tpl<SIMDLevel::AVX512> { \
35
- using Class##_tpl<SIMDLevel::AVX512>::Class##_tpl; \
33
+ #define FAISS_INHERIT_HAMMING_VPOPCNT(Class) \
34
+ template <> \
35
+ struct Class## \
36
+ _tpl<SIMDLevel::AVX512_VPOPCNT> : Class##_tpl<SIMDLevel::AVX512> { \
37
+ using Class##_tpl<SIMDLevel::AVX512>::Class##_tpl; \
36
38
  }
37
39
 
38
- FAISS_INHERIT_HAMMING_SPR(HammingComputer16);
39
- FAISS_INHERIT_HAMMING_SPR(HammingComputer20);
40
- FAISS_INHERIT_HAMMING_SPR(GenHammingComputer8);
41
- FAISS_INHERIT_HAMMING_SPR(GenHammingComputer16);
42
- FAISS_INHERIT_HAMMING_SPR(GenHammingComputer32);
43
- FAISS_INHERIT_HAMMING_SPR(GenHammingComputerM8);
40
+ FAISS_INHERIT_HAMMING_VPOPCNT(HammingComputer16);
41
+ FAISS_INHERIT_HAMMING_VPOPCNT(GenHammingComputer8);
42
+ FAISS_INHERIT_HAMMING_VPOPCNT(GenHammingComputer16);
43
+ FAISS_INHERIT_HAMMING_VPOPCNT(GenHammingComputer32);
44
+ FAISS_INHERIT_HAMMING_VPOPCNT(GenHammingComputerM8);
44
45
 
45
- #undef FAISS_INHERIT_HAMMING_SPR
46
+ #undef FAISS_INHERIT_HAMMING_VPOPCNT
46
47
 
47
48
  /***************************************************************************
48
- * Custom AVX512_SPR specializations using VPOPCNTDQ.
49
+ * Custom AVX512_VPOPCNT specializations using VPOPCNTDQ.
49
50
  ***************************************************************************/
50
51
 
51
52
  template <>
52
- struct HammingComputer32_tpl<SIMDLevel::AVX512_SPR> {
53
+ struct HammingComputer20_tpl<SIMDLevel::AVX512_VPOPCNT>
54
+ : HammingComputer20_tpl<SIMDLevel::AVX512> {
55
+ using HammingComputer20_tpl<SIMDLevel::AVX512>::HammingComputer20_tpl;
56
+
57
+ static constexpr size_t batch_size = 8;
58
+ static constexpr size_t kStride = get_code_size();
59
+ // 160 bytes is what the three loads in hamming_batch() cover, and the
60
+ // 16+4 or 4+16 split it applies per lane is written out for 20 bytes:
61
+ // the literal offsets and the two group indices are not derived from
62
+ // kStride, so another width needs the body reworked, not just retuned.
63
+ static_assert(batch_size * kStride == 160);
64
+ static_assert(kStride == 20, "hamming_batch() hardcodes the 16+4 split");
65
+ static constexpr __mmask64 kTailMask = 0xFFFFFFFFull;
66
+
67
+ /// Writes the query repeated batch_size times. The caller owns the buffer,
68
+ /// so a computer used only through hamming() carries no batch state.
69
+ static void build_batch_query(const uint8_t* a8, uint8_t* tile) {
70
+ for (size_t k = 0; k < batch_size; k++) {
71
+ memcpy(tile + k * kStride, a8, kStride);
72
+ }
73
+ }
74
+
75
+ static void hamming_batch(
76
+ const uint8_t* tile,
77
+ const uint8_t* codes,
78
+ int32_t* dis) {
79
+ const __m512i zero = _mm512_setzero_si512();
80
+ const __m512i p0 = _mm512_popcnt_epi8(_mm512_xor_si512(
81
+ _mm512_loadu_si512(codes), _mm512_loadu_si512(tile)));
82
+ const __m512i p1 = _mm512_popcnt_epi8(_mm512_xor_si512(
83
+ _mm512_loadu_si512(codes + 64), _mm512_loadu_si512(tile + 64)));
84
+ const __m512i p2 = _mm512_popcnt_epi8(_mm512_xor_si512(
85
+ _mm512_maskz_loadu_epi8(kTailMask, codes + 128),
86
+ _mm512_maskz_loadu_epi8(kTailMask, tile + 128)));
87
+
88
+ alignas(64) uint64_t grp[24];
89
+ _mm512_store_si512(grp, _mm512_sad_epu8(p0, zero));
90
+ _mm512_store_si512(grp + 8, _mm512_sad_epu8(p1, zero));
91
+ _mm512_store_si512(grp + 16, _mm512_sad_epu8(p2, zero));
92
+
93
+ for (size_t k = 0; k < batch_size; k++) {
94
+ const size_t s = k * kStride;
95
+ const size_t g = s / 8;
96
+ uint32_t xh, qh;
97
+ if (s % 8 == 0) {
98
+ memcpy(&xh, codes + s + 16, 4);
99
+ memcpy(&qh, tile + s + 16, 4);
100
+ dis[k] = static_cast<int32_t>(
101
+ grp[g] + grp[g + 1] + popcount32(xh ^ qh));
102
+ } else {
103
+ memcpy(&xh, codes + s, 4);
104
+ memcpy(&qh, tile + s, 4);
105
+ dis[k] = static_cast<int32_t>(
106
+ popcount32(xh ^ qh) + grp[g + 1] + grp[g + 2]);
107
+ }
108
+ }
109
+ }
110
+ };
111
+
112
+ template <>
113
+ struct HammingComputer32_tpl<SIMDLevel::AVX512_VPOPCNT> {
53
114
  const uint8_t* a8;
54
115
 
55
116
  HammingComputer32_tpl() {}
@@ -81,7 +142,7 @@ struct HammingComputer32_tpl<SIMDLevel::AVX512_SPR> {
81
142
  };
82
143
 
83
144
  template <>
84
- struct HammingComputer64_tpl<SIMDLevel::AVX512_SPR> {
145
+ struct HammingComputer64_tpl<SIMDLevel::AVX512_VPOPCNT> {
85
146
  const uint8_t* a8;
86
147
 
87
148
  HammingComputer64_tpl() {}
@@ -108,7 +169,7 @@ struct HammingComputer64_tpl<SIMDLevel::AVX512_SPR> {
108
169
  };
109
170
 
110
171
  template <>
111
- struct HammingComputerDefault_tpl<SIMDLevel::AVX512_SPR> {
172
+ struct HammingComputerDefault_tpl<SIMDLevel::AVX512_VPOPCNT> {
112
173
  const uint8_t* a8;
113
174
  int quotient8;
114
175
  int remainder8;
@@ -36,6 +36,7 @@
36
36
 
37
37
  #include <algorithm>
38
38
  #include <cstdio>
39
+ #include <cstring>
39
40
  #include <limits>
40
41
  #include <memory>
41
42
  #include <vector>
@@ -148,6 +149,7 @@ void hammings_knn_mc_impl(
148
149
  std::unique_ptr<int64_t[]> all_ids_per_dis(new int64_t[na * nBuckets * k]);
149
150
 
150
151
  std::vector<HCounterState<HammingComputer>> cs;
152
+ cs.reserve(na);
151
153
  for (size_t i = 0; i < na; ++i) {
152
154
  cs.push_back(
153
155
  HCounterState<HammingComputer>(
@@ -298,6 +300,132 @@ void generalized_hammings_knn_hc_impl(
298
300
  }
299
301
  }
300
302
 
303
+ /* Both number of words and remainder are constants, so the compiler flattens
304
+ * the per-chunk loop. The query is copied into qw/qt once, not re-read for
305
+ * every candidate. */
306
+ template <int NW, int REM>
307
+ void hammings_ragged_fixed(
308
+ const uint8_t* __restrict a,
309
+ const uint8_t* __restrict b,
310
+ size_t na,
311
+ size_t nb,
312
+ size_t ncodes,
313
+ hamdis_t* __restrict dis) {
314
+ for (size_t i = 0; i < na; i++) {
315
+ const uint8_t* __restrict ai = a + i * ncodes;
316
+ hamdis_t* __restrict dis_ = dis + i * nb;
317
+ // Read the query once here, not once per candidate.
318
+ uint64_t qw[NW == 0 ? 1 : NW];
319
+ for (int k = 0; k < NW; k++) {
320
+ memcpy(&qw[k], ai + k * 8, 8);
321
+ }
322
+ uint64_t qt = 0;
323
+ if constexpr (REM > 0) {
324
+ memcpy(&qt, ai + NW * 8, REM);
325
+ }
326
+ for (size_t j = 0; j < nb; j++) {
327
+ const uint8_t* __restrict bj = b + j * ncodes;
328
+ hamdis_t h = 0;
329
+ uint64_t y;
330
+ for (int k = 0; k < NW; k++) {
331
+ memcpy(&y, bj + k * 8, 8);
332
+ h += popcount64(qw[k] ^ y);
333
+ }
334
+ if constexpr (REM > 0) {
335
+ y = 0;
336
+ memcpy(&y, bj + NW * 8, REM);
337
+ h += popcount64(qt ^ y);
338
+ }
339
+ dis_[j] = h;
340
+ }
341
+ }
342
+ }
343
+
344
+ /* Same loop with the word count read at run time, for codes long enough that
345
+ * a constant count gives no performance improvement. */
346
+ template <int REM>
347
+ void hammings_ragged_var(
348
+ const uint8_t* __restrict a,
349
+ const uint8_t* __restrict b,
350
+ size_t na,
351
+ size_t nb,
352
+ size_t ncodes,
353
+ hamdis_t* __restrict dis) {
354
+ const size_t nwords = ncodes / 8;
355
+ for (size_t i = 0; i < na; i++) {
356
+ const uint8_t* __restrict ai = a + i * ncodes;
357
+ hamdis_t* __restrict dis_ = dis + i * nb;
358
+ for (size_t j = 0; j < nb; j++) {
359
+ const uint8_t* __restrict bj = b + j * ncodes;
360
+ hamdis_t h = 0;
361
+ uint64_t x, y;
362
+ for (size_t k = 0; k < nwords; k++) {
363
+ memcpy(&x, ai + k * 8, 8);
364
+ memcpy(&y, bj + k * 8, 8);
365
+ h += popcount64(x ^ y);
366
+ }
367
+ if constexpr (REM > 0) {
368
+ x = 0;
369
+ y = 0;
370
+ memcpy(&x, ai + nwords * 8, REM);
371
+ memcpy(&y, bj + nwords * 8, REM);
372
+ h += popcount64(x ^ y);
373
+ }
374
+ dis_[j] = h;
375
+ }
376
+ }
377
+ }
378
+
379
+ /* Makes the word count a constant for short codes when calling
380
+ * hammings_ragged_fixed, worth roughly 2x perf improvement. Longer codes
381
+ * gain nothing from it, so they share hammings_ragged_var. */
382
+ template <int REM>
383
+ void hammings_ragged_by_nwords(
384
+ const uint8_t* __restrict a,
385
+ const uint8_t* __restrict b,
386
+ size_t na,
387
+ size_t nb,
388
+ size_t ncodes,
389
+ hamdis_t* __restrict dis) {
390
+ switch (ncodes / 8) {
391
+ case 0:
392
+ return hammings_ragged_fixed<0, REM>(a, b, na, nb, ncodes, dis);
393
+ case 1:
394
+ return hammings_ragged_fixed<1, REM>(a, b, na, nb, ncodes, dis);
395
+ case 2:
396
+ return hammings_ragged_fixed<2, REM>(a, b, na, nb, ncodes, dis);
397
+ case 3:
398
+ return hammings_ragged_fixed<3, REM>(a, b, na, nb, ncodes, dis);
399
+ default:
400
+ return hammings_ragged_var<REM>(a, b, na, nb, ncodes, dis);
401
+ }
402
+ }
403
+
404
+ void hammings_ragged_dispatch(
405
+ const uint8_t* __restrict a,
406
+ const uint8_t* __restrict b,
407
+ size_t na,
408
+ size_t nb,
409
+ size_t ncodes,
410
+ hamdis_t* __restrict dis) {
411
+ switch (ncodes % 8) {
412
+ case 1:
413
+ return hammings_ragged_by_nwords<1>(a, b, na, nb, ncodes, dis);
414
+ case 2:
415
+ return hammings_ragged_by_nwords<2>(a, b, na, nb, ncodes, dis);
416
+ case 3:
417
+ return hammings_ragged_by_nwords<3>(a, b, na, nb, ncodes, dis);
418
+ case 4:
419
+ return hammings_ragged_by_nwords<4>(a, b, na, nb, ncodes, dis);
420
+ case 5:
421
+ return hammings_ragged_by_nwords<5>(a, b, na, nb, ncodes, dis);
422
+ case 6:
423
+ return hammings_ragged_by_nwords<6>(a, b, na, nb, ncodes, dis);
424
+ default:
425
+ return hammings_ragged_by_nwords<7>(a, b, na, nb, ncodes, dis);
426
+ }
427
+ }
428
+
301
429
  } // anonymous namespace
302
430
 
303
431
  /******************************************************************
@@ -356,6 +484,19 @@ void hamming_range_search_fixSL<THE_SIMD_LEVEL>(
356
484
  });
357
485
  }
358
486
 
487
+ /* Its own entry point, so the word-multiple kernels keep the code the
488
+ * compiler already generates for them. */
489
+ template <>
490
+ void hammings_ragged_fixSL<THE_SIMD_LEVEL>(
491
+ const uint8_t* a,
492
+ const uint8_t* b,
493
+ size_t na,
494
+ size_t nb,
495
+ size_t ncodes,
496
+ hamdis_t* dis) {
497
+ hammings_ragged_dispatch(a, b, na, nb, ncodes, dis);
498
+ }
499
+
359
500
  template <>
360
501
  void hammings_fixSL<THE_SIMD_LEVEL>(
361
502
  const uint8_t* a,
@@ -129,6 +129,22 @@ struct ElementOpIP {
129
129
  }
130
130
  };
131
131
 
132
+ struct ElementOpL2 {
133
+ static svfloat32_t op(svbool_t pg, svfloat32_t x, svfloat32_t y) {
134
+ const svfloat32_t diff = svsub_f32_x(pg, x, y);
135
+ return svmul_f32_x(pg, diff, diff);
136
+ }
137
+
138
+ static svfloat32_t merge(
139
+ svbool_t pg,
140
+ svfloat32_t z,
141
+ svfloat32_t x,
142
+ svfloat32_t y) {
143
+ const svfloat32_t diff = svsub_f32_x(pg, x, y);
144
+ return svmla_f32_x(pg, z, diff, diff);
145
+ }
146
+ };
147
+
132
148
  template <typename ElementOp>
133
149
  void fvec_op_ny_sve_d1(float* dis, const float* x, const float* y, size_t ny) {
134
150
  const size_t lanes = svcntw();
@@ -513,13 +529,87 @@ void fvec_L2sqr_ny<SIMDLevel::ARM_SVE>(
513
529
  const float* y,
514
530
  size_t d,
515
531
  size_t ny) {
516
- // Use autovectorized L2sqr in a loop
517
- for (size_t i = 0; i < ny; i++) {
518
- dis[i] = fvec_L2sqr<SIMDLevel::ARM_SVE>(x, y, d);
519
- y += d;
532
+ const size_t lanes = static_cast<size_t>(svcntw());
533
+
534
+ switch (d) {
535
+ case 1:
536
+ fvec_op_ny_sve_d1<ElementOpL2>(dis, x, y, ny);
537
+ break;
538
+
539
+ case 2:
540
+ fvec_op_ny_sve_d2<ElementOpL2>(dis, x, y, ny);
541
+ break;
542
+
543
+ case 4:
544
+ fvec_op_ny_sve_d4<ElementOpL2>(dis, x, y, ny);
545
+ break;
546
+
547
+ case 8:
548
+ fvec_op_ny_sve_d8<ElementOpL2>(dis, x, y, ny);
549
+ break;
550
+
551
+ default:
552
+ if (d == lanes)
553
+ fvec_op_ny_sve_lanes1<ElementOpL2>(dis, x, y, ny);
554
+ else if (d == lanes * 2)
555
+ fvec_op_ny_sve_lanes2<ElementOpL2>(dis, x, y, ny);
556
+ else if (d == lanes * 3)
557
+ fvec_op_ny_sve_lanes3<ElementOpL2>(dis, x, y, ny);
558
+ else if (d == lanes * 4)
559
+ fvec_op_ny_sve_lanes4<ElementOpL2>(dis, x, y, ny);
560
+ else {
561
+ // Fallback: use autovectorized L2sqr
562
+ for (size_t i = 0; i < ny; i++) {
563
+ dis[i] = fvec_L2sqr<SIMDLevel::ARM_SVE>(x, y, d);
564
+ y += d;
565
+ }
566
+ }
567
+ break;
568
+ }
569
+ }
570
+
571
+ namespace {
572
+
573
+ /// Low-dimensional L2sqr nearest (D in {2,4,8}). The data is row-major
574
+ /// (centroid c is y[c*D .. c*D+D]). Each SVE lane tracks one centroid across
575
+ /// batches, keeping the lane-local minimum and index in registers. The scratch
576
+ /// buffer is not written, matching the AVX2/AVX512 D2/D4/D8 implementations.
577
+ template <int D>
578
+ size_t fvec_L2sqr_ny_nearest_lowdim(
579
+ float* /*distances_tmp_buffer*/,
580
+ const float* x,
581
+ const float* y,
582
+ size_t ny) {
583
+ const size_t lanes = svcntw();
584
+ svfloat32_t global_mins = svdup_n_f32(HUGE_VALF);
585
+ svuint32_t global_ids = svdup_n_u32(0);
586
+ svuint32_t current_ids = svindex_u32(0, 1);
587
+
588
+ for (size_t c = 0; c < ny; c += lanes) {
589
+ const svbool_t pg = svwhilelt_b32_u64(c, ny);
590
+ svfloat32_t distances = svdup_n_f32(0.0f);
591
+ for (uint32_t j = 0; j < D; ++j) {
592
+ const svuint32_t offsets = svindex_u32(j, D);
593
+ const svfloat32_t yv =
594
+ svld1_gather_u32index_f32(pg, y + c * D, offsets);
595
+ const svfloat32_t diff = svsub_n_f32_x(pg, yv, x[j]);
596
+ distances = svmla_f32_m(pg, distances, diff, diff);
597
+ }
598
+
599
+ const svbool_t closer = svcmplt_f32(pg, distances, global_mins);
600
+ global_mins = svsel_f32(closer, distances, global_mins);
601
+ global_ids = svsel_u32(closer, current_ids, global_ids);
602
+ current_ids =
603
+ svadd_n_u32_x(pg, current_ids, static_cast<uint32_t>(lanes));
520
604
  }
605
+
606
+ const svbool_t all = svptrue_b32();
607
+ const float global_min = svminv_f32(all, global_mins);
608
+ return svminv_u32(svcmpeq_n_f32(all, global_mins, global_min), global_ids);
521
609
  }
522
610
 
611
+ } // namespace
612
+
523
613
  template <>
524
614
  size_t fvec_L2sqr_ny_nearest<SIMDLevel::ARM_SVE>(
525
615
  float* distances_tmp_buffer,
@@ -527,14 +617,52 @@ size_t fvec_L2sqr_ny_nearest<SIMDLevel::ARM_SVE>(
527
617
  const float* y,
528
618
  size_t d,
529
619
  size_t ny) {
530
- fvec_L2sqr_ny<SIMDLevel::ARM_SVE>(distances_tmp_buffer, x, y, d, ny);
620
+ switch (d) {
621
+ case 2:
622
+ return fvec_L2sqr_ny_nearest_lowdim<2>(
623
+ distances_tmp_buffer, x, y, ny);
624
+ case 4:
625
+ return fvec_L2sqr_ny_nearest_lowdim<4>(
626
+ distances_tmp_buffer, x, y, ny);
627
+ case 8:
628
+ return fvec_L2sqr_ny_nearest_lowdim<8>(
629
+ distances_tmp_buffer, x, y, ny);
630
+ }
631
+
632
+ const size_t lanes = static_cast<size_t>(svcntw());
531
633
 
532
634
  size_t nearest_idx = 0;
533
635
  float min_dis = HUGE_VALF;
534
636
 
535
- for (size_t i = 0; i < ny; i++) {
536
- if (distances_tmp_buffer[i] < min_dis) {
537
- min_dis = distances_tmp_buffer[i];
637
+ for (size_t i = 0; i < ny; ++i) {
638
+ const float* yi = y + i * d;
639
+ size_t j = 0;
640
+
641
+ svfloat32_t accv = svdup_n_f32(0.0f);
642
+
643
+ for (; j + lanes <= d; j += lanes) {
644
+ const svbool_t pg = svptrue_b32();
645
+ const svfloat32_t xv = svld1_f32(pg, x + j);
646
+ const svfloat32_t yv = svld1_f32(pg, yi + j);
647
+ const svfloat32_t diff = svsub_f32_x(pg, xv, yv);
648
+ accv = svmla_f32_x(pg, accv, diff, diff);
649
+ }
650
+
651
+ if (j < d) {
652
+ const svbool_t pg = svwhilelt_b32_u64(j, d);
653
+ const svfloat32_t xv = svld1_f32(pg, x + j);
654
+ const svfloat32_t yv = svld1_f32(pg, yi + j);
655
+ const svfloat32_t diff = svsub_f32_x(pg, xv, yv);
656
+ // Merging predication: lanes outside the tail keep the partial
657
+ // sums accumulated above, which the reduction below still adds in.
658
+ // `_x` would leave them unspecified.
659
+ accv = svmla_f32_m(pg, accv, diff, diff);
660
+ }
661
+
662
+ const float dist = svaddv_f32(svptrue_b32(), accv);
663
+ distances_tmp_buffer[i] = dist;
664
+ if (dist < min_dis) {
665
+ min_dis = dist;
538
666
  nearest_idx = i;
539
667
  }
540
668
  }
@@ -542,7 +670,6 @@ size_t fvec_L2sqr_ny_nearest<SIMDLevel::ARM_SVE>(
542
670
  return nearest_idx;
543
671
  }
544
672
 
545
- FAISS_PRAGMA_IMPRECISE_FUNCTION_BEGIN
546
673
  template <>
547
674
  void fvec_L2sqr_ny_transposed<SIMDLevel::ARM_SVE>(
548
675
  float* dis,
@@ -552,22 +679,26 @@ void fvec_L2sqr_ny_transposed<SIMDLevel::ARM_SVE>(
552
679
  size_t d,
553
680
  size_t d_offset,
554
681
  size_t ny) {
555
- float x_sqlen = 0;
556
- FAISS_PRAGMA_IMPRECISE_LOOP
557
- for (size_t j = 0; j < d; j++) {
558
- x_sqlen += x[j] * x[j];
559
- }
682
+ const size_t lanes = static_cast<size_t>(svcntw());
683
+ const float x_sq = fvec_norm_L2sqr(x, d);
684
+
685
+ for (size_t k = 0; k < ny; k += lanes) {
686
+ svbool_t pg = svwhilelt_b32_u64(k, ny);
687
+ svfloat32_t acc = svdup_n_f32(0.0f);
560
688
 
561
- for (size_t i = 0; i < ny; i++) {
562
- float dp = 0;
563
- FAISS_PRAGMA_IMPRECISE_LOOP
564
- for (size_t j = 0; j < d; j++) {
565
- dp += x[j] * y[i + j * d_offset];
689
+ for (size_t j = 0; j < d; ++j) {
690
+ svfloat32_t ychunk = svld1_f32(pg, y + j * d_offset + k);
691
+ svfloat32_t xj = svdup_n_f32(x[j]);
692
+ acc = svmla_f32_x(pg, acc, xj, ychunk);
566
693
  }
567
- dis[i] = x_sqlen + y_sqlen[i] - 2 * dp;
694
+
695
+ svfloat32_t ysq = svld1_f32(pg, y_sqlen + k);
696
+ svfloat32_t two_acc = svmul_f32_x(pg, acc, svdup_n_f32(2.0f));
697
+ svfloat32_t sum = svadd_f32_x(pg, svdup_n_f32(x_sq), ysq);
698
+ svfloat32_t res = svsub_f32_x(pg, sum, two_acc);
699
+ svst1_f32(pg, dis + k, res);
568
700
  }
569
701
  }
570
- FAISS_PRAGMA_IMPRECISE_FUNCTION_END
571
702
 
572
703
  template <>
573
704
  size_t fvec_L2sqr_ny_nearest_y_transposed<SIMDLevel::ARM_SVE>(
@@ -578,20 +709,53 @@ size_t fvec_L2sqr_ny_nearest_y_transposed<SIMDLevel::ARM_SVE>(
578
709
  size_t d,
579
710
  size_t d_offset,
580
711
  size_t ny) {
581
- fvec_L2sqr_ny_transposed<SIMDLevel::ARM_SVE>(
582
- distances_tmp_buffer, x, y, y_sqlen, d, d_offset, ny);
712
+ const size_t lanes = svcntw();
713
+ const float x_sq = fvec_norm_L2sqr(x, d);
583
714
 
584
- size_t nearest_idx = 0;
585
- float min_dis = HUGE_VALF;
715
+ size_t current_min_idx = 0;
716
+ svfloat32_t current_min_v = svdup_n_f32(HUGE_VALF);
586
717
 
587
- for (size_t i = 0; i < ny; i++) {
588
- if (distances_tmp_buffer[i] < min_dis) {
589
- min_dis = distances_tmp_buffer[i];
590
- nearest_idx = i;
718
+ // Max SVE vector length is 2048 bits 64 fp32 lanes
719
+ float tmp_buf[64];
720
+
721
+ for (size_t k = 0; k < ny; k += lanes) {
722
+ svbool_t pg = svwhilelt_b32_u64(k, ny);
723
+ svfloat32_t acc = svdup_n_f32(0.0f);
724
+
725
+ for (size_t j = 0; j < d; ++j) {
726
+ svfloat32_t ychunk = svld1_f32(pg, y + j * d_offset + k);
727
+ svfloat32_t xj = svdup_n_f32(x[j]);
728
+ acc = svmla_f32_x(pg, acc, xj, ychunk);
729
+ }
730
+
731
+ svfloat32_t ysq = svld1_f32(pg, y_sqlen + k);
732
+ svfloat32_t two_acc = svmul_f32_x(pg, acc, svdup_n_f32(2.0f));
733
+ svfloat32_t sum = svadd_f32_x(pg, svdup_n_f32(x_sq), ysq);
734
+ svfloat32_t res = svsub_f32_x(pg, sum, two_acc);
735
+
736
+ svst1_f32(pg, distances_tmp_buffer + k, res);
737
+
738
+ svbool_t less_mask = svcmplt_f32(pg, res, current_min_v);
739
+
740
+ if (svptest_any(pg, less_mask)) {
741
+ float vec_min = svminv_f32(pg, res);
742
+
743
+ current_min_v =
744
+ svmin_f32_x(pg, current_min_v, svdup_n_f32(vec_min));
745
+
746
+ svst1_f32(pg, tmp_buf, res);
747
+
748
+ size_t cnt = (size_t)svcntw();
749
+ for (size_t lane = 0; lane < cnt && (k + lane) < ny; ++lane) {
750
+ if (tmp_buf[lane] == vec_min) {
751
+ current_min_idx = k + lane;
752
+ break;
753
+ }
754
+ }
591
755
  }
592
756
  }
593
757
 
594
- return nearest_idx;
758
+ return current_min_idx;
595
759
  }
596
760
 
597
761
  template <>