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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/lib/faiss/version.rb +1 -1
- data/vendor/faiss/faiss/Clustering.h +8 -0
- data/vendor/faiss/faiss/Index.h +1 -1
- data/vendor/faiss/faiss/IndexBinaryFromFloat.cpp +23 -2
- data/vendor/faiss/faiss/IndexBinaryHNSW.cpp +12 -140
- data/vendor/faiss/faiss/IndexBinaryHNSW.h +0 -6
- data/vendor/faiss/faiss/IndexBinaryIVF.cpp +6 -14
- data/vendor/faiss/faiss/IndexBinaryIVF.h +8 -1
- data/vendor/faiss/faiss/IndexFlat.cpp +8 -4
- data/vendor/faiss/faiss/IndexFlat.h +2 -2
- data/vendor/faiss/faiss/IndexHNSW.cpp +301 -91
- data/vendor/faiss/faiss/IndexHNSW.h +31 -7
- data/vendor/faiss/faiss/IndexIDMap.cpp +11 -6
- data/vendor/faiss/faiss/IndexIVF.cpp +19 -4
- data/vendor/faiss/faiss/IndexIVFPQ.cpp +9 -1
- data/vendor/faiss/faiss/IndexIVFPQ.h +11 -0
- data/vendor/faiss/faiss/IndexShards.cpp +3 -3
- data/vendor/faiss/faiss/IndexShardsIVF.cpp +4 -3
- data/vendor/faiss/faiss/SuperKMeans.cpp +31 -8
- data/vendor/faiss/faiss/SuperKMeans.h +3 -2
- data/vendor/faiss/faiss/VectorTransform.cpp +38 -0
- data/vendor/faiss/faiss/VectorTransform.h +3 -0
- data/vendor/faiss/faiss/clone_index.cpp +3 -0
- data/vendor/faiss/faiss/gpu/GpuClonerOptions.h +1 -5
- data/vendor/faiss/faiss/gpu/GpuDistance.h +2 -5
- data/vendor/faiss/faiss/gpu/GpuIndex.h +4 -5
- data/vendor/faiss/faiss/gpu/GpuIndexCagra.h +65 -42
- data/vendor/faiss/faiss/impl/AdditiveQuantizer.cpp +1 -1
- data/vendor/faiss/faiss/impl/ClusteringInitialization.cpp +5 -2
- data/vendor/faiss/faiss/impl/HNSW.cpp +419 -115
- data/vendor/faiss/faiss/impl/HNSW.h +46 -7
- data/vendor/faiss/faiss/impl/IDSelector.h +44 -0
- data/vendor/faiss/faiss/impl/NNDescent.cpp +10 -3
- data/vendor/faiss/faiss/impl/Panorama.h +11 -2
- data/vendor/faiss/faiss/impl/ProductQuantizer.cpp +4 -4
- data/vendor/faiss/faiss/impl/RaBitQUtils.cpp +1 -1
- data/vendor/faiss/faiss/impl/RaBitQuantizer.cpp +74 -8
- data/vendor/faiss/faiss/impl/RaBitQuantizer.h +47 -4
- data/vendor/faiss/faiss/impl/RaBitQuantizerMultiBit.cpp +2 -2
- data/vendor/faiss/faiss/impl/ResultHandler.h +9 -3
- data/vendor/faiss/faiss/impl/ScalarQuantizer.cpp +32 -30
- data/vendor/faiss/faiss/impl/ScalarQuantizer.h +6 -10
- data/vendor/faiss/faiss/impl/binary_hamming/IndexBinaryIVF_impl.h +89 -13
- data/vendor/faiss/faiss/impl/expanded_scanners.h +5 -1
- data/vendor/faiss/faiss/impl/fast_scan/decompose_qbs.h +1 -0
- data/vendor/faiss/faiss/impl/index_read.cpp +130 -29
- data/vendor/faiss/faiss/impl/index_write.cpp +31 -26
- data/vendor/faiss/faiss/impl/pq_code_distance/IVFPQScanner_impl.h +73 -39
- data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-generic.cpp +4 -4
- data/vendor/faiss/faiss/impl/scalar_quantizer/EDENQuantizer.cpp +6 -1
- data/vendor/faiss/faiss/impl/scalar_quantizer/distance_computers.h +2 -2
- data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx2.cpp +6 -2
- data/vendor/faiss/faiss/impl/scalar_quantizer/sq-dispatch.h +37 -17
- data/vendor/faiss/faiss/impl/scalar_quantizer/sq-neon.cpp +109 -10
- data/vendor/faiss/faiss/impl/simd_dispatch.h +40 -22
- data/vendor/faiss/faiss/index_factory.cpp +8 -1
- data/vendor/faiss/faiss/invlists/InvertedLists.cpp +7 -4
- data/vendor/faiss/faiss/invlists/OnDiskInvertedLists.cpp +10 -4
- data/vendor/faiss/faiss/svs/IndexSVSVamana.cpp +16 -4
- data/vendor/faiss/faiss/svs/IndexSVSVamana.h +8 -1
- data/vendor/faiss/faiss/svs/IndexSVSVamanaLVQ.cpp +4 -2
- data/vendor/faiss/faiss/svs/IndexSVSVamanaLVQ.h +2 -1
- data/vendor/faiss/faiss/svs/IndexSVSVamanaLeanVec.cpp +9 -2
- data/vendor/faiss/faiss/svs/IndexSVSVamanaLeanVec.h +2 -1
- data/vendor/faiss/faiss/utils/distances.cpp +20 -13
- data/vendor/faiss/faiss/utils/distances_dispatch.h +30 -24
- data/vendor/faiss/faiss/utils/distances_fused/distances_fused.cpp +1 -1
- data/vendor/faiss/faiss/utils/hamming.cpp +15 -9
- data/vendor/faiss/faiss/utils/hamming.h +10 -1
- data/vendor/faiss/faiss/utils/hamming_distance/common.h +14 -3
- data/vendor/faiss/faiss/utils/hamming_distance/hamming_avx512_vpopcnt.cpp +24 -0
- data/vendor/faiss/faiss/utils/hamming_distance/hamming_computer-avx512.h +1 -1
- data/vendor/faiss/faiss/utils/hamming_distance/{hamming_computer-avx512_spr.h → hamming_computer-avx512_vpopcnt.h} +85 -24
- data/vendor/faiss/faiss/utils/hamming_distance/hamming_impl.h +141 -0
- data/vendor/faiss/faiss/utils/simd_impl/distances_arm_sve.cpp +194 -30
- data/vendor/faiss/faiss/utils/simd_impl/distances_rvv.cpp +93 -55
- data/vendor/faiss/faiss/utils/simd_impl/rabitq_avx512.cpp +57 -40
- data/vendor/faiss/faiss/utils/simd_impl/{rabitq_avx512_spr.cpp → rabitq_avx512_vpopcnt.cpp} +21 -24
- data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_dispatch.h +2 -7
- data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_kernels.h +6 -1
- data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_kernels_sve.cpp +34 -0
- data/vendor/faiss/faiss/utils/simd_levels.cpp +172 -67
- data/vendor/faiss/faiss/utils/simd_levels.h +26 -15
- metadata +6 -5
- 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),
|
|
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 &&
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
485
|
+
return this->dis0 +
|
|
465
486
|
PQCodeDist::distance_single_code(
|
|
466
|
-
|
|
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,
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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).
|
|
82
|
-
//
|
|
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<
|
|
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
|
|
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 =
|
|
320
|
-
|
|
321
|
-
|
|
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
|
|
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 (
|
|
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
|
|
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 (
|
|
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 (
|
|
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
|
|
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 (
|
|
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
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
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
|
-
//
|
|
36
|
-
constexpr int
|
|
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
|
-
//
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
//
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
//
|
|
50
|
-
// implementations
|
|
51
|
-
constexpr int
|
|
52
|
-
|
|
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<
|
|
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
|
-
|
|
243
|
-
|
|
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
|
|
247
|
-
return with_selected_simd_levels<
|
|
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
|
|