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
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
#include <faiss/IndexIVF.h>
|
|
9
|
+
#include <faiss/SuperKMeans.h>
|
|
9
10
|
|
|
10
11
|
#include <omp.h>
|
|
11
12
|
#include <atomic>
|
|
@@ -78,13 +79,27 @@ void Level1Quantizer::train_q1(
|
|
|
78
79
|
printf("Training level-1 quantizer on %zd vectors in %zdD\n", n, d);
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
|
|
82
|
+
FAISS_THROW_IF_MSG(
|
|
83
|
+
cp.use_super_kmeans && clustering_index,
|
|
84
|
+
"cp.use_super_kmeans is incompatible with a user-provided "
|
|
85
|
+
"clustering_index: SuperKMeans assigns with its own index");
|
|
86
|
+
|
|
82
87
|
quantizer->reset();
|
|
83
|
-
if (
|
|
84
|
-
|
|
88
|
+
if (cp.use_super_kmeans) {
|
|
89
|
+
SuperKMeansParameters super_cp;
|
|
90
|
+
static_cast<ClusteringParameters&>(super_cp) = cp;
|
|
91
|
+
SuperKMeans clus(
|
|
92
|
+
static_cast<int>(d), static_cast<int>(nlist), super_cp);
|
|
93
|
+
clus.train(n, x);
|
|
85
94
|
quantizer->add(nlist, clus.centroids.data());
|
|
86
95
|
} else {
|
|
87
|
-
clus
|
|
96
|
+
Clustering clus(static_cast<int>(d), static_cast<int>(nlist), cp);
|
|
97
|
+
if (clustering_index) {
|
|
98
|
+
clus.train(n, x, *clustering_index);
|
|
99
|
+
quantizer->add(nlist, clus.centroids.data());
|
|
100
|
+
} else {
|
|
101
|
+
clus.train(n, x, *quantizer);
|
|
102
|
+
}
|
|
88
103
|
}
|
|
89
104
|
quantizer->is_trained = true;
|
|
90
105
|
} else if (quantizer_trains_alone == 2) {
|
|
@@ -526,9 +526,17 @@ InvertedListScanner* IndexIVFPQ::get_InvertedListScanner(
|
|
|
526
526
|
bool store_pairs,
|
|
527
527
|
const IDSelector* sel,
|
|
528
528
|
const IVFSearchParameters*) const {
|
|
529
|
+
return get_InvertedListScanner(
|
|
530
|
+
store_pairs, sel, IndexIVFPQ::ScannerMode::Precomputed);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
InvertedListScanner* IndexIVFPQ::get_InvertedListScanner(
|
|
534
|
+
bool store_pairs,
|
|
535
|
+
const IDSelector* sel,
|
|
536
|
+
IndexIVFPQ::ScannerMode mode) const {
|
|
529
537
|
return with_simd_level([&]<SIMDLevel SL>() -> InvertedListScanner* {
|
|
530
538
|
return pq_code_distance::make_IVFPQInvertedListScanner<SL>(
|
|
531
|
-
*this, store_pairs, sel);
|
|
539
|
+
*this, store_pairs, sel, mode);
|
|
532
540
|
});
|
|
533
541
|
}
|
|
534
542
|
|
|
@@ -32,6 +32,11 @@ FAISS_API extern size_t precomputed_table_max_bytes;
|
|
|
32
32
|
* vector is encoded as a product quantizer code.
|
|
33
33
|
*/
|
|
34
34
|
struct IndexIVFPQ : IndexIVF {
|
|
35
|
+
enum class ScannerMode {
|
|
36
|
+
Precomputed,
|
|
37
|
+
OnTheFly,
|
|
38
|
+
};
|
|
39
|
+
|
|
35
40
|
ProductQuantizer pq; ///< produces the codes
|
|
36
41
|
|
|
37
42
|
bool do_polysemous_training; ///< reorder PQ centroids after training?
|
|
@@ -144,6 +149,12 @@ struct IndexIVFPQ : IndexIVF {
|
|
|
144
149
|
const IDSelector* sel,
|
|
145
150
|
const IVFSearchParameters* params) const override;
|
|
146
151
|
|
|
152
|
+
/** Returns a scanner with a fixed PQ distance-computation strategy. */
|
|
153
|
+
InvertedListScanner* get_InvertedListScanner(
|
|
154
|
+
bool store_pairs,
|
|
155
|
+
const IDSelector* sel,
|
|
156
|
+
ScannerMode mode) const;
|
|
157
|
+
|
|
147
158
|
/// build precomputed table
|
|
148
159
|
void precompute_table();
|
|
149
160
|
|
|
@@ -243,8 +243,8 @@ void IndexShardsTemplate<IndexT>::search(
|
|
|
243
243
|
|
|
244
244
|
this->runOnIndex(fn);
|
|
245
245
|
|
|
246
|
-
if (this->metric_type
|
|
247
|
-
merge_knn_results<idx_t,
|
|
246
|
+
if (is_similarity_metric(this->metric_type)) {
|
|
247
|
+
merge_knn_results<idx_t, CMax<distance_t, int>>(
|
|
248
248
|
n,
|
|
249
249
|
k,
|
|
250
250
|
nshard,
|
|
@@ -253,7 +253,7 @@ void IndexShardsTemplate<IndexT>::search(
|
|
|
253
253
|
distances,
|
|
254
254
|
labels);
|
|
255
255
|
} else {
|
|
256
|
-
merge_knn_results<idx_t,
|
|
256
|
+
merge_knn_results<idx_t, CMin<distance_t, int>>(
|
|
257
257
|
n,
|
|
258
258
|
k,
|
|
259
259
|
nshard,
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
#include <cstdio>
|
|
14
14
|
#include <functional>
|
|
15
15
|
|
|
16
|
+
#include <faiss/MetricType.h>
|
|
16
17
|
#include <faiss/impl/FaissAssert.h>
|
|
17
18
|
#include <faiss/utils/Heap.h>
|
|
18
19
|
#include <faiss/utils/WorkerThread.h>
|
|
@@ -227,8 +228,8 @@ void IndexShardsIVF::search(
|
|
|
227
228
|
|
|
228
229
|
this->runOnIndex(fn);
|
|
229
230
|
|
|
230
|
-
if (
|
|
231
|
-
merge_knn_results<idx_t,
|
|
231
|
+
if (is_similarity_metric(metric_type)) {
|
|
232
|
+
merge_knn_results<idx_t, CMax<distance_t, int>>(
|
|
232
233
|
n,
|
|
233
234
|
k,
|
|
234
235
|
nshard,
|
|
@@ -237,7 +238,7 @@ void IndexShardsIVF::search(
|
|
|
237
238
|
distances,
|
|
238
239
|
labels);
|
|
239
240
|
} else {
|
|
240
|
-
merge_knn_results<idx_t,
|
|
241
|
+
merge_knn_results<idx_t, CMin<distance_t, int>>(
|
|
241
242
|
n,
|
|
242
243
|
k,
|
|
243
244
|
nshard,
|
|
@@ -59,7 +59,7 @@ namespace {
|
|
|
59
59
|
struct TrainState {
|
|
60
60
|
/// Orthogonal rotation. Train in rotated space (X_tilde = X * R);
|
|
61
61
|
/// un-rotate centroids before return.
|
|
62
|
-
faiss::
|
|
62
|
+
std::unique_ptr<faiss::VectorTransform> R;
|
|
63
63
|
|
|
64
64
|
std::vector<float> X_tilde; // (n, d) row-major
|
|
65
65
|
int n = 0;
|
|
@@ -77,7 +77,17 @@ struct TrainState {
|
|
|
77
77
|
int low_pruning_streak = 0;
|
|
78
78
|
bool low_pruning_warning_printed = false;
|
|
79
79
|
|
|
80
|
-
explicit TrainState(int d
|
|
80
|
+
explicit TrainState(int d, bool spherical)
|
|
81
|
+
: R([d, spherical]() -> std::unique_ptr<faiss::VectorTransform> {
|
|
82
|
+
// Spherical (inner-product) clustering only: a power-of-two
|
|
83
|
+
// dimension can use the fast Hadamard rotation instead of
|
|
84
|
+
// the generic random rotation. L2 training keeps the
|
|
85
|
+
// original RandomRotationMatrix path unchanged.
|
|
86
|
+
if (spherical && d > 0 && (d & (d - 1)) == 0) {
|
|
87
|
+
return std::make_unique<faiss::HadamardRotation>(d);
|
|
88
|
+
}
|
|
89
|
+
return std::make_unique<faiss::RandomRotationMatrix>(d, d);
|
|
90
|
+
}()) {}
|
|
81
91
|
};
|
|
82
92
|
|
|
83
93
|
/// PDX block layout for the trailing pruning sweep: block b covers original
|
|
@@ -295,10 +305,17 @@ std::unique_ptr<uint8_t[]> setup_train_state(
|
|
|
295
305
|
"SuperKMeans: training set size exceeds INT_MAX after sampling");
|
|
296
306
|
state.n = static_cast<int>(nx);
|
|
297
307
|
|
|
298
|
-
state.R.
|
|
308
|
+
if (auto* R = dynamic_cast<HadamardRotation*>(state.R.get())) {
|
|
309
|
+
R->init(cp.seed);
|
|
310
|
+
} else {
|
|
311
|
+
auto* dense_rotation =
|
|
312
|
+
dynamic_cast<RandomRotationMatrix*>(state.R.get());
|
|
313
|
+
FAISS_ASSERT(dense_rotation != nullptr);
|
|
314
|
+
dense_rotation->init(cp.seed);
|
|
315
|
+
}
|
|
299
316
|
|
|
300
317
|
state.X_tilde.resize(static_cast<size_t>(state.n) * d);
|
|
301
|
-
state.R
|
|
318
|
+
state.R->apply_noalloc(state.n, x_sampled, state.X_tilde.data());
|
|
302
319
|
|
|
303
320
|
// Forgy init: pick k random rows from the rotated pool as initial
|
|
304
321
|
// centroids. These remain in rotated space; un-rotation happens
|
|
@@ -313,6 +330,9 @@ std::unique_ptr<uint8_t[]> setup_train_state(
|
|
|
313
330
|
state.X_tilde.data() + static_cast<size_t>(perm[j]) * d,
|
|
314
331
|
sizeof(float) * d);
|
|
315
332
|
}
|
|
333
|
+
if (cp.spherical) {
|
|
334
|
+
fvec_renorm_L2(d, k, state.Y_tilde.data());
|
|
335
|
+
}
|
|
316
336
|
}
|
|
317
337
|
|
|
318
338
|
state.d_prime =
|
|
@@ -339,7 +359,7 @@ std::unique_ptr<uint8_t[]> setup_train_state(
|
|
|
339
359
|
/// reverse_transform applies R^T = R^-1.
|
|
340
360
|
void untransform_centroids(
|
|
341
361
|
std::vector<float>& centroids,
|
|
342
|
-
const
|
|
362
|
+
const VectorTransform& R,
|
|
343
363
|
int d,
|
|
344
364
|
int k,
|
|
345
365
|
const float* Y_tilde) {
|
|
@@ -411,7 +431,7 @@ void SuperKMeans::train(idx_t n, const float* x) {
|
|
|
411
431
|
static_cast<idx_t>(k) * cp.min_points_per_centroid);
|
|
412
432
|
}
|
|
413
433
|
|
|
414
|
-
TrainState state(d);
|
|
434
|
+
TrainState state(d, cp.spherical);
|
|
415
435
|
std::vector<int64_t> labels64;
|
|
416
436
|
SuperKMeansAssignScratch assign_scratch;
|
|
417
437
|
std::vector<float> hassign;
|
|
@@ -446,6 +466,9 @@ void SuperKMeans::train(idx_t n, const float* x) {
|
|
|
446
466
|
|
|
447
467
|
const int nsplit =
|
|
448
468
|
update_centroids_and_split(d, k, state, labels64, hassign);
|
|
469
|
+
if (cp.spherical) {
|
|
470
|
+
fvec_renorm_L2(d, k, state.Y_tilde.data());
|
|
471
|
+
}
|
|
449
472
|
const float pruning_rate = (iter == 0)
|
|
450
473
|
? 0.0f
|
|
451
474
|
: adapt_d_prime(d, cp, state, total_pairs, pruned_at_gemm);
|
|
@@ -492,7 +515,7 @@ void SuperKMeans::train(idx_t n, const float* x) {
|
|
|
492
515
|
(getmillisecs() - t_train_start) / 1000.0);
|
|
493
516
|
}
|
|
494
517
|
|
|
495
|
-
untransform_centroids(centroids, state.R, d, k, state.Y_tilde.data());
|
|
518
|
+
untransform_centroids(centroids, *state.R, d, k, state.Y_tilde.data());
|
|
496
519
|
}
|
|
497
520
|
|
|
498
521
|
void super_kmeans_assign_iteration(
|
|
@@ -598,7 +621,7 @@ void super_kmeans_assign_iteration(
|
|
|
598
621
|
}
|
|
599
622
|
|
|
600
623
|
// One SIMD dispatch per (xi, yj) tile.
|
|
601
|
-
|
|
624
|
+
with_simd_level_with_sve([&]<SIMDLevel SL>() {
|
|
602
625
|
[[maybe_unused]] const int omp_chunk_local = cp.omp_chunk;
|
|
603
626
|
int64_t tile_total = 0;
|
|
604
627
|
int64_t tile_pruned = 0;
|
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
// "A Super Fast K-means for Indexing Vector Embeddings."
|
|
14
14
|
// arXiv preprint arXiv:2603.20009.
|
|
15
15
|
//
|
|
16
|
-
// Use when: L2 metric,
|
|
17
|
-
//
|
|
16
|
+
// Use when: L2 metric, or spherical inner-product clustering with
|
|
17
|
+
// cp.spherical=true, k >= 1024, d >= 128, dense float embeddings.
|
|
18
|
+
// Do not use for: small k,
|
|
18
19
|
// binary data (use IndexBinaryIVF), or near-unit-sphere embeddings with
|
|
19
20
|
// k < 4096 (chi-squared assumption breaks down).
|
|
20
21
|
//
|
|
@@ -513,6 +513,44 @@ void HadamardRotation::apply_noalloc(idx_t n, const float* x, float* xt) const {
|
|
|
513
513
|
}
|
|
514
514
|
}
|
|
515
515
|
|
|
516
|
+
void HadamardRotation::reverse_transform(idx_t n, const float* xt, float* x)
|
|
517
|
+
const {
|
|
518
|
+
FAISS_THROW_IF_NOT_MSG(is_trained, "Transformation not trained yet");
|
|
519
|
+
FAISS_THROW_IF_NOT_MSG(
|
|
520
|
+
d_in == d_out,
|
|
521
|
+
"HadamardRotation inverse requires equal input/output dimensions");
|
|
522
|
+
|
|
523
|
+
const size_t p = d_out;
|
|
524
|
+
// Reverse of apply_noalloc: three unnormalized FWHT rounds scale norms
|
|
525
|
+
// by (sqrt(p))^3 = p*sqrt(p); the forward pass cancels this with
|
|
526
|
+
// total_scale = 1/(p*sqrt(p)), so the inverse applies the same factor.
|
|
527
|
+
const float inverse_scale = 1.0f / (p * std::sqrt(static_cast<float>(p)));
|
|
528
|
+
|
|
529
|
+
#pragma omp parallel for schedule(dynamic)
|
|
530
|
+
for (idx_t i = 0; i < n; i++) {
|
|
531
|
+
const float* xi = xt + i * p;
|
|
532
|
+
float* xo = x + i * p;
|
|
533
|
+
|
|
534
|
+
// The inverse reverses the three sign-flip/Hadamard factors.
|
|
535
|
+
std::memcpy(xo, xi, p * sizeof(float));
|
|
536
|
+
fwht_inplace(xo, p);
|
|
537
|
+
|
|
538
|
+
for (size_t j = 0; j < p; j++) {
|
|
539
|
+
xo[j] *= signs3[j];
|
|
540
|
+
}
|
|
541
|
+
fwht_inplace(xo, p);
|
|
542
|
+
|
|
543
|
+
for (size_t j = 0; j < p; j++) {
|
|
544
|
+
xo[j] *= signs2[j];
|
|
545
|
+
}
|
|
546
|
+
fwht_inplace(xo, p);
|
|
547
|
+
|
|
548
|
+
for (size_t j = 0; j < p; j++) {
|
|
549
|
+
xo[j] *= signs1[j] * inverse_scale;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
516
554
|
void HadamardRotation::check_identical(const VectorTransform& other) const {
|
|
517
555
|
auto* hr = dynamic_cast<const HadamardRotation*>(&other);
|
|
518
556
|
FAISS_THROW_IF_NOT_MSG(hr, "failed to cast to HadamardRotation");
|
|
@@ -144,6 +144,9 @@ struct HadamardRotation : VectorTransform {
|
|
|
144
144
|
|
|
145
145
|
void apply_noalloc(idx_t n, const float* x, float* xt) const override;
|
|
146
146
|
|
|
147
|
+
/// Apply the inverse transform when d_in == d_out.
|
|
148
|
+
void reverse_transform(idx_t n, const float* xt, float* x) const override;
|
|
149
|
+
|
|
147
150
|
void check_identical(const VectorTransform& other) const override;
|
|
148
151
|
|
|
149
152
|
HadamardRotation() {}
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
#include <faiss/IndexPQ.h>
|
|
39
39
|
#include <faiss/IndexPQFastScan.h>
|
|
40
40
|
#include <faiss/IndexPreTransform.h>
|
|
41
|
+
#include <faiss/IndexRaBitQ.h>
|
|
41
42
|
#include <faiss/IndexRefine.h>
|
|
42
43
|
#include <faiss/IndexRowwiseMinMax.h>
|
|
43
44
|
#include <faiss/IndexScalarQuantizer.h>
|
|
@@ -139,6 +140,7 @@ IndexIDMap* clone_IndexIDMap(const IndexIDMap* im) {
|
|
|
139
140
|
IndexHNSW* clone_IndexHNSW(const IndexHNSW* ihnsw) {
|
|
140
141
|
TRYCLONE(IndexHNSW2Level, ihnsw)
|
|
141
142
|
TRYCLONE(IndexHNSWFlatPanorama, ihnsw)
|
|
143
|
+
TRYCLONE(IndexHNSWRaBitQ, ihnsw)
|
|
142
144
|
TRYCLONE(IndexHNSWFlat, ihnsw)
|
|
143
145
|
TRYCLONE(IndexHNSWPQ, ihnsw)
|
|
144
146
|
TRYCLONE(IndexHNSWSQ, ihnsw)
|
|
@@ -294,6 +296,7 @@ Index* Cloner::clone_Index(const Index* index) {
|
|
|
294
296
|
TRYCLONE(IndexEDEN, index)
|
|
295
297
|
|
|
296
298
|
TRYCLONE(IndexScalarQuantizer, index)
|
|
299
|
+
TRYCLONE(IndexRaBitQ, index)
|
|
297
300
|
TRYCLONE(MultiIndexQuantizer, index)
|
|
298
301
|
|
|
299
302
|
if (const IndexIVF* ivf = dynamic_cast<const IndexIVF*>(index)) {
|
|
@@ -39,12 +39,8 @@ struct GpuClonerOptions {
|
|
|
39
39
|
/// Set verbose options on the index
|
|
40
40
|
bool verbose = false;
|
|
41
41
|
|
|
42
|
-
///
|
|
43
|
-
#if defined USE_NVIDIA_CUVS
|
|
44
|
-
bool use_cuvs = true;
|
|
45
|
-
#else
|
|
42
|
+
/// Use the cuVS implementation. Opt-in: see GpuIndexConfig::use_cuvs.
|
|
46
43
|
bool use_cuvs = false;
|
|
47
|
-
#endif
|
|
48
44
|
|
|
49
45
|
/// This flag controls the CPU fallback logic for coarse quantizer
|
|
50
46
|
/// component of the index. When set to false (default), the cloner will
|
|
@@ -107,12 +107,9 @@ struct GpuDistanceParams {
|
|
|
107
107
|
/// execution
|
|
108
108
|
int device = -1;
|
|
109
109
|
|
|
110
|
-
/// Should the
|
|
111
|
-
|
|
112
|
-
bool use_cuvs = true;
|
|
113
|
-
#else
|
|
110
|
+
/// Should the search dispatch down to cuVS? Opt-in: see
|
|
111
|
+
/// GpuIndexConfig::use_cuvs.
|
|
114
112
|
bool use_cuvs = false;
|
|
115
|
-
#endif
|
|
116
113
|
};
|
|
117
114
|
|
|
118
115
|
/// A function that determines whether cuVS should be used based on various
|
|
@@ -38,12 +38,11 @@ struct GpuIndexConfig {
|
|
|
38
38
|
/// more memory than is available on the GPU.
|
|
39
39
|
MemorySpace memorySpace = MemorySpace::Device;
|
|
40
40
|
|
|
41
|
-
/// Should the index dispatch down to cuVS?
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
/// Should the index dispatch down to cuVS? Opt-in: dispatching to cuVS
|
|
42
|
+
/// selects a different implementation with its own numerical behaviour, so
|
|
43
|
+
/// it must be an explicit choice by the caller rather than a consequence of
|
|
44
|
+
/// how the binary happened to be built.
|
|
45
45
|
bool use_cuvs = false;
|
|
46
|
-
#endif
|
|
47
46
|
};
|
|
48
47
|
|
|
49
48
|
/// A centralized function that determines whether cuVS should
|
|
@@ -47,7 +47,9 @@ enum class graph_build_algo {
|
|
|
47
47
|
/// Use NN-Descent to build all-neighbors knn graph
|
|
48
48
|
NN_DESCENT,
|
|
49
49
|
/// Use iterative search to build knn graph
|
|
50
|
-
ITERATIVE_SEARCH
|
|
50
|
+
ITERATIVE_SEARCH,
|
|
51
|
+
/// Exact knn graph via tiled brute force
|
|
52
|
+
BRUTE_FORCE
|
|
51
53
|
};
|
|
52
54
|
|
|
53
55
|
/// A type for specifying how PQ codebooks are created.
|
|
@@ -173,6 +175,34 @@ struct IVFPQSearchCagraConfig {
|
|
|
173
175
|
uint32_t max_internal_batch_size = 4096;
|
|
174
176
|
};
|
|
175
177
|
|
|
178
|
+
/// Knobs for the multi-GPU build path, selected by listing more than one
|
|
179
|
+
/// device in GpuIndexCagraConfig::devices. Degrees, build_algo and metric
|
|
180
|
+
/// still come from GpuIndexCagraConfig.
|
|
181
|
+
struct AllNeighborsCagraConfig {
|
|
182
|
+
/// Number of overlapping clusters the dataset is partitioned into.
|
|
183
|
+
/// 0 selects max(2 * number of devices, 4).
|
|
184
|
+
size_t n_clusters = 0;
|
|
185
|
+
|
|
186
|
+
/// Clusters each vector is assigned to. Must be >= 2: with 1 there are no
|
|
187
|
+
/// cross-cluster edges and recall collapses. 0 selects 2.
|
|
188
|
+
size_t overlap_factor = 2;
|
|
189
|
+
|
|
190
|
+
/// Bounds IVF-PQ search memory during the knn build. cuVS's default can
|
|
191
|
+
/// exhaust device memory around 100M vectors; 8192 avoids that and does
|
|
192
|
+
/// not change recall. 0 keeps cuVS's default.
|
|
193
|
+
uint32_t ivf_pq_search_batch_size = 0;
|
|
194
|
+
|
|
195
|
+
/// IVF-PQ refinement multiplier. The refine pass runs on the CPU, so cost
|
|
196
|
+
/// scales with this. Raising it above 1.0 measured both slower and less
|
|
197
|
+
/// accurate, so it is separate from the shared refine_rate.
|
|
198
|
+
float refinement_rate = 1.0f;
|
|
199
|
+
|
|
200
|
+
/// Size the IVF-PQ index for one cluster rather than the whole dataset.
|
|
201
|
+
/// cuVS reuses these params for every cluster without rescaling, so
|
|
202
|
+
/// sizing from the full dataset badly over-partitions each one.
|
|
203
|
+
bool ivf_pq_size_from_cluster = true;
|
|
204
|
+
};
|
|
205
|
+
|
|
176
206
|
struct GpuIndexCagraConfig : public GpuIndexConfig {
|
|
177
207
|
/// Degree of input graph for pruning.
|
|
178
208
|
size_t intermediate_graph_degree = 128;
|
|
@@ -190,6 +220,27 @@ struct GpuIndexCagraConfig : public GpuIndexConfig {
|
|
|
190
220
|
|
|
191
221
|
/// Whether to use MST optimization to guarantee graph connectivity.
|
|
192
222
|
bool guarantee_connectivity = false;
|
|
223
|
+
|
|
224
|
+
/// Devices to build on. More than one selects the multi-GPU build in
|
|
225
|
+
/// train(); see AllNeighborsCagraConfig and train() for its restrictions.
|
|
226
|
+
/// Empty or one device uses GpuIndexConfig::device as usual.
|
|
227
|
+
std::vector<int> devices;
|
|
228
|
+
|
|
229
|
+
AllNeighborsCagraConfig all_neighbors_params;
|
|
230
|
+
|
|
231
|
+
/// Build the HNSW upper levels on the GPU during copyTo() instead of by
|
|
232
|
+
/// CPU insertion. Roughly 10x faster, but measured worse recall than
|
|
233
|
+
/// base_level_only, which is cheaper still. Off by default.
|
|
234
|
+
bool gpu_hnsw_upper_levels = false;
|
|
235
|
+
|
|
236
|
+
/// intermediate_graph_degree for the per-level subgraph builds.
|
|
237
|
+
/// 0 = twice the upper-level degree.
|
|
238
|
+
size_t gpu_hnsw_intermediate_degree = 0;
|
|
239
|
+
|
|
240
|
+
/// MST connectivity pass on the per-level subgraphs. On by default:
|
|
241
|
+
/// upper levels are walked greedily with no backtracking, so a
|
|
242
|
+
/// disconnected component is a trap the descent cannot escape.
|
|
243
|
+
bool gpu_hnsw_guarantee_connectivity = true;
|
|
193
244
|
};
|
|
194
245
|
|
|
195
246
|
enum class search_algo {
|
|
@@ -284,46 +335,6 @@ struct GpuIndexCagra : public GpuIndex {
|
|
|
284
335
|
/// in the index instance
|
|
285
336
|
void copyTo(faiss::IndexHNSWCagra* index) const;
|
|
286
337
|
|
|
287
|
-
/// Train CAGRA using multiple GPUs by sharding the dataset.
|
|
288
|
-
/// Uses cuVS native SNMG (single-node multi-GPU) CAGRA build.
|
|
289
|
-
/// Each device builds one shard in parallel via OpenMP.
|
|
290
|
-
/// Float32 only. After training, call copyTo() to produce a CPU
|
|
291
|
-
/// IndexHNSWCagra with full HNSW upper levels.
|
|
292
|
-
/// The training data pointer must remain valid until copyTo() completes.
|
|
293
|
-
/// stitch_mode: 0=CPU HNSW (Approach C), 1=GPU brute-force (Approach B)
|
|
294
|
-
void trainMultiGpu(
|
|
295
|
-
idx_t n,
|
|
296
|
-
const float* x,
|
|
297
|
-
std::vector<GpuResourcesProvider*>& providers,
|
|
298
|
-
std::vector<int>& devices,
|
|
299
|
-
idx_t stitch_per_shard = 0,
|
|
300
|
-
int stitch_k = 2,
|
|
301
|
-
int stitch_mode = 0);
|
|
302
|
-
|
|
303
|
-
/// Build a unified CAGRA graph using cuVS all_neighbors
|
|
304
|
-
/// (multi-GPU kNN graph construction with overlapping clusters) followed
|
|
305
|
-
/// by cagra::optimize (graph pruning). Produces a single unified graph
|
|
306
|
-
/// without stitching. The training data pointer must remain valid until
|
|
307
|
-
/// copyTo() completes.
|
|
308
|
-
/// build_algo: 0=NN-descent (default), 1=brute-force, 2=IVF-PQ
|
|
309
|
-
/// refinement_rate: IVF-PQ refinement multiplier (only used when
|
|
310
|
-
/// build_algo==2). Higher values trade build time for recall; the cuVS
|
|
311
|
-
/// default is 2.0.
|
|
312
|
-
/// ivfpq_search_batch: cap the IVF-PQ search `max_internal_batch_size` used
|
|
313
|
-
/// during the all_neighbors kNN build (build_algo==2). 0 = cuVS default
|
|
314
|
-
/// (128*1024), which can OOM at 100M; a smaller value (e.g. 8192) bounds
|
|
315
|
-
/// the GPU search workspace with no effect on results (recall-neutral).
|
|
316
|
-
void trainAllNeighbors(
|
|
317
|
-
idx_t n,
|
|
318
|
-
const float* x,
|
|
319
|
-
std::vector<int>& devices,
|
|
320
|
-
int n_clusters = 0,
|
|
321
|
-
int overlap_factor = 0,
|
|
322
|
-
bool multi_gpu_optimize = false,
|
|
323
|
-
int build_algo = 0,
|
|
324
|
-
float refinement_rate = 2.0f,
|
|
325
|
-
int ivfpq_search_batch = 0);
|
|
326
|
-
|
|
327
338
|
void reset() override;
|
|
328
339
|
|
|
329
340
|
std::vector<idx_t> get_knngraph() const;
|
|
@@ -357,6 +368,18 @@ struct GpuIndexCagra : public GpuIndex {
|
|
|
357
368
|
idx_t* labels,
|
|
358
369
|
const SearchParameters* search_params) const override;
|
|
359
370
|
|
|
371
|
+
/// Multi-GPU build path taken by train() when cagraConfig_.devices lists
|
|
372
|
+
/// more than one device: cuVS `all_neighbors` knn graph construction over
|
|
373
|
+
/// overlapping clusters, followed by graph pruning. Leaves the result in
|
|
374
|
+
/// merged_knngraph_ for copyTo(); index_ stays empty.
|
|
375
|
+
void trainAllNeighbors_(idx_t n, const float* x);
|
|
376
|
+
|
|
377
|
+
/// Populate HNSW levels >= 1 of `index` by building a CAGRA graph over
|
|
378
|
+
/// each level's node subset on the GPU. Requires the level table to be
|
|
379
|
+
/// prepared and the storage populated. Returns the max level.
|
|
380
|
+
void buildHnswUpperLevelsGpu_(faiss::IndexHNSWCagra* index, int max_lvl)
|
|
381
|
+
const;
|
|
382
|
+
|
|
360
383
|
void copyToMultiGpu_(faiss::IndexHNSWCagra* index) const;
|
|
361
384
|
|
|
362
385
|
/// Our configuration options
|
|
@@ -372,7 +395,7 @@ struct GpuIndexCagra : public GpuIndex {
|
|
|
372
395
|
std::shared_ptr<CuvsCagra<int8_t>>>
|
|
373
396
|
index_;
|
|
374
397
|
|
|
375
|
-
/// Multi-GPU state: populated by
|
|
398
|
+
/// Multi-GPU state: populated by trainAllNeighbors_(), used by copyTo()
|
|
376
399
|
std::vector<idx_t> merged_knngraph_;
|
|
377
400
|
idx_t merged_knngraph_degree_ = 0;
|
|
378
401
|
const float* multi_gpu_dataset_ = nullptr;
|
|
@@ -376,7 +376,7 @@ void AdditiveQuantizer::compute_centroid_norms(float* norms) const {
|
|
|
376
376
|
size_t ntotal = (size_t)1 << tot_bits;
|
|
377
377
|
int64_t ntotal_signed = ntotal;
|
|
378
378
|
// TODO: make tree of partial sums
|
|
379
|
-
|
|
379
|
+
with_simd_level_with_sve([&]<SIMDLevel SL>() {
|
|
380
380
|
#pragma omp parallel
|
|
381
381
|
{
|
|
382
382
|
std::vector<float> tmp(d);
|
|
@@ -235,8 +235,11 @@ void ClusteringInitialization::init_kmeans_plus_plus(
|
|
|
235
235
|
float* new_centroid = centroids + c * d;
|
|
236
236
|
std::memcpy(new_centroid, x + next_idx * d, d * sizeof(float));
|
|
237
237
|
|
|
238
|
-
// Update min distances incrementally
|
|
239
|
-
|
|
238
|
+
// Update min distances incrementally. The writes are independent,
|
|
239
|
+
// so the loop parallelizes without changing the output.
|
|
240
|
+
const int64_t ni = static_cast<int64_t>(n);
|
|
241
|
+
#pragma omp parallel for
|
|
242
|
+
for (int64_t i = 0; i < ni; i++) {
|
|
240
243
|
double dist = fvec_L2sqr<SL>(x + i * d, new_centroid, d);
|
|
241
244
|
min_distances[i] = std::min(min_distances[i], dist);
|
|
242
245
|
}
|