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
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
|
|
8
8
|
#pragma once
|
|
9
9
|
|
|
10
|
+
#include <functional>
|
|
10
11
|
#include <optional>
|
|
11
12
|
#include <queue>
|
|
13
|
+
#include <utility>
|
|
12
14
|
#include <vector>
|
|
13
15
|
|
|
14
16
|
#include <omp.h>
|
|
@@ -152,8 +154,14 @@ struct HNSW {
|
|
|
152
154
|
/// use bounded queue during exploration
|
|
153
155
|
bool search_bounded_queue = true;
|
|
154
156
|
|
|
155
|
-
///
|
|
156
|
-
|
|
157
|
+
/// Specialized level-0 search implementations. This state is derived from
|
|
158
|
+
/// the owning IndexHNSW subtype after construction or deserialization.
|
|
159
|
+
enum Search_method_t : uint8_t {
|
|
160
|
+
SM_DEFAULT,
|
|
161
|
+
SM_PANORAMA,
|
|
162
|
+
SM_RABITQ,
|
|
163
|
+
};
|
|
164
|
+
Search_method_t search_method = SM_DEFAULT;
|
|
157
165
|
|
|
158
166
|
/// distance comparison semantics: when true, distances are treated as
|
|
159
167
|
/// similarity scores (larger is better). Default false matches the
|
|
@@ -204,14 +212,27 @@ struct HNSW {
|
|
|
204
212
|
VisitedTable& vt,
|
|
205
213
|
bool keep_max_size_level0 = false);
|
|
206
214
|
|
|
207
|
-
/**
|
|
208
|
-
*
|
|
209
|
-
|
|
215
|
+
/** Deterministic build, phase A: write pt_id's forward links against an
|
|
216
|
+
* immutable snapshot, touching only pt_id's own slots. Reciprocal edges
|
|
217
|
+
* are collected in `pt_reverse_edges` for phase B, not applied. Requires
|
|
218
|
+
* entry_point set. */
|
|
219
|
+
void compute_forward_links_deterministic(
|
|
210
220
|
DistanceComputer& ptdis,
|
|
211
221
|
int pt_level,
|
|
212
|
-
|
|
213
|
-
LockVector& locks,
|
|
222
|
+
storage_idx_t pt_id,
|
|
214
223
|
VisitedTable& vt,
|
|
224
|
+
std::vector<std::pair<storage_idx_t, int>>& pt_reverse_edges,
|
|
225
|
+
bool keep_max_size_level0 = false);
|
|
226
|
+
|
|
227
|
+
/** Deterministic build, phase B: merge `incoming` into `node` and
|
|
228
|
+
* re-prune in a total order (distance, ties by id) so the result is
|
|
229
|
+
* order-independent. Touches only `node`'s slots; `incoming` is sorted
|
|
230
|
+
* and deduplicated in place. */
|
|
231
|
+
void merge_reverse_links_deterministic(
|
|
232
|
+
DistanceComputer& dis,
|
|
233
|
+
storage_idx_t node,
|
|
234
|
+
int level,
|
|
235
|
+
std::vector<storage_idx_t>& incoming,
|
|
215
236
|
bool keep_max_size_level0 = false);
|
|
216
237
|
|
|
217
238
|
/// Search interface for 1 point, single thread
|
|
@@ -257,6 +278,24 @@ struct HNSW {
|
|
|
257
278
|
void permute_entries(const idx_t* map);
|
|
258
279
|
};
|
|
259
280
|
|
|
281
|
+
/** Deterministic, lock-free HNSW graph build. This is the only graph build
|
|
282
|
+
* that add() uses, and it is shared by IndexHNSW and
|
|
283
|
+
* IndexBinaryHNSW. The callbacks let both share the algorithm:
|
|
284
|
+
* `make_distance_computer()` returns a fresh DistanceComputer per thread
|
|
285
|
+
* (caller-owned) and `set_query(dc, pt_id)` points it at pt_id's vector. */
|
|
286
|
+
void hnsw_add_vertices_deterministic(
|
|
287
|
+
HNSW& hnsw,
|
|
288
|
+
size_t n0,
|
|
289
|
+
size_t n,
|
|
290
|
+
int d,
|
|
291
|
+
bool init_level0,
|
|
292
|
+
bool keep_max_size_level0,
|
|
293
|
+
bool preset_levels,
|
|
294
|
+
bool verbose,
|
|
295
|
+
const std::function<DistanceComputer*()>& make_distance_computer,
|
|
296
|
+
const std::function<void(DistanceComputer&, HNSW::storage_idx_t)>&
|
|
297
|
+
set_query);
|
|
298
|
+
|
|
260
299
|
struct HNSWStats {
|
|
261
300
|
size_t n1 = 0; /// number of vectors searched
|
|
262
301
|
size_t n2 =
|
|
@@ -23,6 +23,50 @@ struct IDSelector {
|
|
|
23
23
|
virtual ~IDSelector() {}
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
+
/** Scan context handed to IDSelectorWithContext::is_member_with_context: the
|
|
27
|
+
* contiguous id block being scanned and the position of the id under test. */
|
|
28
|
+
struct IDScanContext {
|
|
29
|
+
/// the contiguous block of ids being scanned
|
|
30
|
+
const idx_t* ids;
|
|
31
|
+
/// number of entries in `ids`
|
|
32
|
+
size_t list_size;
|
|
33
|
+
/// index of the tested id within `ids` (i.e. ids[j] == id)
|
|
34
|
+
size_t j;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** IDSelector that also receives the surrounding scan context on each
|
|
38
|
+
* membership test, letting an implementation exploit locality across a scan
|
|
39
|
+
* (for example, prefetching data for an entry it will be asked about soon).
|
|
40
|
+
* The policy is entirely up to the implementation. */
|
|
41
|
+
struct IDSelectorWithContext : IDSelector {
|
|
42
|
+
virtual bool is_member_with_context(idx_t id, const IDScanContext& ctx)
|
|
43
|
+
const = 0;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/** Routes each per-candidate membership test to is_member_with_context() when
|
|
47
|
+
* the selector implements IDSelectorWithContext, else to plain is_member().
|
|
48
|
+
* Construct one per inverted-list scan: the dynamic_cast is the only RTTI cost
|
|
49
|
+
* and the per-candidate cost is a single predicted branch (cf. the
|
|
50
|
+
* IDSelectorRange dynamic_cast in IndexIVF.cpp). The scan context is only
|
|
51
|
+
* meaningful when the scan exposes a real id array (i.e. !store_pairs); when
|
|
52
|
+
* store_pairs the context path is disabled and every test falls back to
|
|
53
|
+
* is_member. */
|
|
54
|
+
struct IDSelectorContextDispatch {
|
|
55
|
+
const IDSelector* sel;
|
|
56
|
+
const IDSelectorWithContext* ctx_sel;
|
|
57
|
+
|
|
58
|
+
IDSelectorContextDispatch(const IDSelector* sel, bool store_pairs)
|
|
59
|
+
: sel(sel),
|
|
60
|
+
ctx_sel((sel != nullptr && !store_pairs)
|
|
61
|
+
? dynamic_cast<const IDSelectorWithContext*>(sel)
|
|
62
|
+
: nullptr) {}
|
|
63
|
+
|
|
64
|
+
bool is_member(idx_t id, const IDScanContext& ctx) const {
|
|
65
|
+
return ctx_sel ? ctx_sel->is_member_with_context(id, ctx)
|
|
66
|
+
: sel->is_member(id);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
26
70
|
/** ids between [imin, imax) */
|
|
27
71
|
struct IDSelectorRange : IDSelector {
|
|
28
72
|
idx_t imin, imax;
|
|
@@ -264,6 +264,8 @@ void NNDescent::update() {
|
|
|
264
264
|
auto& nn_new = node.nn_new;
|
|
265
265
|
auto& nn_old = node.nn_old;
|
|
266
266
|
|
|
267
|
+
// other.pool.back() below is read without other.lock: in this
|
|
268
|
+
// phase no thread reorders or resizes a pool. Lock guards rnn_*.
|
|
267
269
|
for (int l = 0; l < node.M; ++l) {
|
|
268
270
|
auto& nn = node.pool[l];
|
|
269
271
|
auto& other = graph[nn.id]; // the other side of the edge
|
|
@@ -283,7 +285,6 @@ void NNDescent::update() {
|
|
|
283
285
|
}
|
|
284
286
|
}
|
|
285
287
|
nn.flag = false;
|
|
286
|
-
|
|
287
288
|
} else { // the node is old
|
|
288
289
|
// push the neighbor into nn_old
|
|
289
290
|
nn_old.push_back(nn.id);
|
|
@@ -300,11 +301,17 @@ void NNDescent::update() {
|
|
|
300
301
|
}
|
|
301
302
|
}
|
|
302
303
|
}
|
|
303
|
-
// make heap to join later (in join() function)
|
|
304
|
-
std::make_heap(node.pool.begin(), node.pool.end());
|
|
305
304
|
}
|
|
306
305
|
}
|
|
307
306
|
|
|
307
|
+
// Restore the max-heap invariant for the next join()'s insert(). Must
|
|
308
|
+
// stay a separate region: re-heapifying during Step 3 would race with
|
|
309
|
+
// its unlocked other.pool.back() reads.
|
|
310
|
+
#pragma omp parallel for
|
|
311
|
+
for (int n = 0; n < ntotal; ++n) {
|
|
312
|
+
std::make_heap(graph[n].pool.begin(), graph[n].pool.end());
|
|
313
|
+
}
|
|
314
|
+
|
|
308
315
|
// Step 4.
|
|
309
316
|
// Combine the forward and the reverse links
|
|
310
317
|
// R = 0 means no reverse links are used.
|
|
@@ -235,7 +235,8 @@ inline auto with_bool(bool value, Lambda&& fn) {
|
|
|
235
235
|
* accelerating the refinement stage.
|
|
236
236
|
*/
|
|
237
237
|
struct Panorama {
|
|
238
|
-
static constexpr size_t kDefaultBatchSize =
|
|
238
|
+
static constexpr size_t kDefaultBatchSize = 1024;
|
|
239
|
+
static constexpr size_t kLegacyBatchSize = 128;
|
|
239
240
|
|
|
240
241
|
size_t d = 0;
|
|
241
242
|
size_t code_size = 0;
|
|
@@ -322,12 +323,20 @@ struct Panorama {
|
|
|
322
323
|
size_t batch_offset = batch_no * batch_size * code_size;
|
|
323
324
|
const uint8_t* storage_base = codes_base + batch_offset;
|
|
324
325
|
|
|
326
|
+
// Honor IDSelectorWithContext: the scan-order position within the whole
|
|
327
|
+
// list is global_idx, so a lookahead crosses batch boundaries correctly
|
|
328
|
+
// (ids is the full list, length list_size).
|
|
329
|
+
IDSelectorContextDispatch sel_dispatch(
|
|
330
|
+
sel, /*store_pairs=*/ids == nullptr);
|
|
331
|
+
|
|
325
332
|
// Initialize active set with ID-filtered vectors.
|
|
326
333
|
size_t num_active = 0;
|
|
327
334
|
for (size_t i = 0; i < curr_batch_size; i++) {
|
|
328
335
|
size_t global_idx = batch_start + i;
|
|
329
336
|
idx_t id = (ids == nullptr) ? global_idx : ids[global_idx];
|
|
330
|
-
bool include = !use_sel ||
|
|
337
|
+
bool include = !use_sel ||
|
|
338
|
+
sel_dispatch.is_member(
|
|
339
|
+
id, IDScanContext{ids, list_size, global_idx});
|
|
331
340
|
|
|
332
341
|
active_indices[num_active] = i;
|
|
333
342
|
float cum_sum = batch_cum_sums[i];
|
|
@@ -280,7 +280,7 @@ void compute_1_code(const ProductQuantizer& pq, const float* x, uint8_t* code) {
|
|
|
280
280
|
} // namespace
|
|
281
281
|
|
|
282
282
|
void ProductQuantizer::compute_code(const float* x, uint8_t* code) const {
|
|
283
|
-
|
|
283
|
+
with_simd_level_with_sve([&]<SIMDLevel SL>() {
|
|
284
284
|
switch (nbits) {
|
|
285
285
|
case 8:
|
|
286
286
|
compute_1_code<PQEncoder8, SL>(*this, x, code);
|
|
@@ -294,7 +294,7 @@ void ProductQuantizer::compute_code(const float* x, uint8_t* code) const {
|
|
|
294
294
|
compute_1_code<PQEncoderGeneric, SL>(*this, x, code);
|
|
295
295
|
break;
|
|
296
296
|
}
|
|
297
|
-
}); //
|
|
297
|
+
}); // with_simd_level_with_sve
|
|
298
298
|
}
|
|
299
299
|
|
|
300
300
|
template <class PQDecoder>
|
|
@@ -442,7 +442,7 @@ void ProductQuantizer::compute_codes(const float* x, uint8_t* codes, size_t n)
|
|
|
442
442
|
|
|
443
443
|
void ProductQuantizer::compute_distance_table(const float* x, float* dis_table)
|
|
444
444
|
const {
|
|
445
|
-
|
|
445
|
+
with_simd_level_with_sve([&]<SIMDLevel SL>() {
|
|
446
446
|
if (transposed_centroids.empty()) {
|
|
447
447
|
// use regular version
|
|
448
448
|
for (size_t m = 0; m < M; m++) {
|
|
@@ -824,7 +824,7 @@ void ProductQuantizer::compute_sdc_table() {
|
|
|
824
824
|
sdc_table.resize(M * ksub * ksub);
|
|
825
825
|
|
|
826
826
|
if (dsub < 4) {
|
|
827
|
-
|
|
827
|
+
with_simd_level_with_sve([&]<SIMDLevel SL>() {
|
|
828
828
|
#pragma omp parallel for
|
|
829
829
|
for (int64_t mk = 0; mk < static_cast<int64_t>(M * ksub); mk++) {
|
|
830
830
|
// allow omp to schedule in a more fine-grained way
|
|
@@ -329,7 +329,7 @@ float compute_full_multibit_distance(
|
|
|
329
329
|
size_t d,
|
|
330
330
|
size_t ex_bits,
|
|
331
331
|
MetricType metric_type) {
|
|
332
|
-
return with_selected_simd_levels<
|
|
332
|
+
return with_selected_simd_levels<AVAILABLE_SIMD_LEVELS_BASE_WITH_VPOPCNT>(
|
|
333
333
|
[&]<SIMDLevel SL>() {
|
|
334
334
|
return compute_full_multibit_distance<SL>(
|
|
335
335
|
sign_bits,
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
#include <faiss/utils/distances.h>
|
|
18
18
|
#include <faiss/utils/rabitq_simd.h>
|
|
19
19
|
|
|
20
|
+
#include <algorithm>
|
|
20
21
|
#include <cmath>
|
|
21
22
|
#include <cstring>
|
|
22
23
|
#include <memory>
|
|
@@ -24,6 +25,8 @@
|
|
|
24
25
|
|
|
25
26
|
namespace faiss {
|
|
26
27
|
|
|
28
|
+
RaBitQStats rabitq_stats;
|
|
29
|
+
|
|
27
30
|
// Import shared utilities from RaBitQUtils
|
|
28
31
|
using rabitq_utils::ExtraBitsFactors;
|
|
29
32
|
using rabitq_utils::QueryFactorsData;
|
|
@@ -224,6 +227,51 @@ void RaBitQuantizer::decode_core(
|
|
|
224
227
|
}
|
|
225
228
|
}
|
|
226
229
|
|
|
230
|
+
template <SIMDLevel SL>
|
|
231
|
+
float symmetric_dis_1bit(const RaBitQDistanceComputer& dc, idx_t i, idx_t j) {
|
|
232
|
+
FAISS_THROW_IF_NOT_MSG(
|
|
233
|
+
dc.metric_type == MetricType::METRIC_L2,
|
|
234
|
+
"RaBitQ symmetric distance supports only L2");
|
|
235
|
+
FAISS_ASSERT(i >= 0 && j >= 0);
|
|
236
|
+
FAISS_ASSERT(dc.codes != nullptr);
|
|
237
|
+
|
|
238
|
+
const size_t sign_bytes = (dc.d + 7) / 8;
|
|
239
|
+
const uint8_t* code_i = dc.codes + static_cast<size_t>(i) * dc.code_size;
|
|
240
|
+
const uint8_t* code_j = dc.codes + static_cast<size_t>(j) * dc.code_size;
|
|
241
|
+
const auto* factors_i =
|
|
242
|
+
reinterpret_cast<const SignBitFactors*>(code_i + sign_bytes);
|
|
243
|
+
const auto* factors_j =
|
|
244
|
+
reinterpret_cast<const SignBitFactors*>(code_j + sign_bytes);
|
|
245
|
+
|
|
246
|
+
const uint64_t xor_popcount =
|
|
247
|
+
rabitq::bitwise_xor_dot_product<SL>(code_i, code_j, sign_bytes, 1);
|
|
248
|
+
const float sign_dot =
|
|
249
|
+
static_cast<float>(dc.d) - 2.0f * static_cast<float>(xor_popcount);
|
|
250
|
+
|
|
251
|
+
// The L2-optimal reconstruction of residual r is alpha * sign(r), where
|
|
252
|
+
// alpha = ||r||_1 / d. The stored factors give
|
|
253
|
+
// alpha_i * alpha_j = ||r_i||^2 * ||r_j||^2 /
|
|
254
|
+
// (d * dp_multiplier_i * dp_multiplier_j).
|
|
255
|
+
float cross_term = 0.0f;
|
|
256
|
+
if (factors_i->dp_multiplier != 0.0f && factors_j->dp_multiplier != 0.0f) {
|
|
257
|
+
// Dividing each norm first avoids overflowing the product of two
|
|
258
|
+
// squared norms even when the final distance is representable.
|
|
259
|
+
const float scaled_norm_i =
|
|
260
|
+
factors_i->or_minus_c_l2sqr / factors_i->dp_multiplier;
|
|
261
|
+
const float scaled_norm_j =
|
|
262
|
+
factors_j->or_minus_c_l2sqr / factors_j->dp_multiplier;
|
|
263
|
+
cross_term = (scaled_norm_i * (sign_dot / static_cast<float>(dc.d))) *
|
|
264
|
+
scaled_norm_j;
|
|
265
|
+
}
|
|
266
|
+
const float distance = factors_i->or_minus_c_l2sqr +
|
|
267
|
+
factors_j->or_minus_c_l2sqr - 2.0f * cross_term;
|
|
268
|
+
return std::max(0.0f, distance);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
float RaBitQDistanceComputer::symmetric_dis(idx_t i, idx_t j) {
|
|
272
|
+
return symmetric_dis_1bit<SIMDLevel::NONE>(*this, i, j);
|
|
273
|
+
}
|
|
274
|
+
|
|
227
275
|
namespace {
|
|
228
276
|
|
|
229
277
|
// Distance computers templatized on SIMDLevel to avoid per-call dynamic
|
|
@@ -240,6 +288,10 @@ struct RaBitQDistanceComputerNotQ final : RaBitQDistanceComputer {
|
|
|
240
288
|
|
|
241
289
|
RaBitQDistanceComputerNotQ() = default;
|
|
242
290
|
|
|
291
|
+
float symmetric_dis(idx_t i, idx_t j) final {
|
|
292
|
+
return symmetric_dis_1bit<SL>(*this, i, j);
|
|
293
|
+
}
|
|
294
|
+
|
|
243
295
|
// Compute distance using only 1-bit codes (fast)
|
|
244
296
|
float distance_to_code_1bit_impl(
|
|
245
297
|
const uint8_t* binary_data,
|
|
@@ -390,11 +442,17 @@ struct RaBitQDistanceComputerNotQ final : RaBitQDistanceComputer {
|
|
|
390
442
|
const size_t ex_bits = nb_bits - 1;
|
|
391
443
|
FAISS_ASSERT(ex_bits > 0);
|
|
392
444
|
|
|
445
|
+
// Honor IDSelectorWithContext on the multibit path too, so a RaBitQ
|
|
446
|
+
// index does not silently lose the context hook once nb_bits >= 2 (the
|
|
447
|
+
// 1-bit path already routes through run_scan_codes1).
|
|
448
|
+
const IDSelectorContextDispatch sel_dispatch(sel, store_pairs);
|
|
449
|
+
|
|
393
450
|
size_t nup = 0;
|
|
394
451
|
for (size_t j = 0; j < list_size; j++) {
|
|
395
452
|
if (sel != nullptr) {
|
|
396
453
|
idx_t id = store_pairs ? lo_build(list_no, j) : ids[j];
|
|
397
|
-
if (!
|
|
454
|
+
if (!sel_dispatch.is_member(
|
|
455
|
+
id, IDScanContext{ids, list_size, j})) {
|
|
398
456
|
codes += code_size;
|
|
399
457
|
continue;
|
|
400
458
|
}
|
|
@@ -449,6 +507,10 @@ struct RaBitQDistanceComputerQ final : RaBitQDistanceComputer {
|
|
|
449
507
|
|
|
450
508
|
RaBitQDistanceComputerQ() = default;
|
|
451
509
|
|
|
510
|
+
float symmetric_dis(idx_t i, idx_t j) final {
|
|
511
|
+
return symmetric_dis_1bit<SL>(*this, i, j);
|
|
512
|
+
}
|
|
513
|
+
|
|
452
514
|
// Compute distance using only 1-bit codes (fast)
|
|
453
515
|
float distance_to_code_1bit_impl(
|
|
454
516
|
const uint8_t* binary_data,
|
|
@@ -601,11 +663,17 @@ struct RaBitQDistanceComputerQ final : RaBitQDistanceComputer {
|
|
|
601
663
|
const size_t ex_bits = nb_bits - 1;
|
|
602
664
|
FAISS_ASSERT(ex_bits > 0);
|
|
603
665
|
|
|
666
|
+
// Honor IDSelectorWithContext on the multibit path too, so a RaBitQ
|
|
667
|
+
// index does not silently lose the context hook once nb_bits >= 2 (the
|
|
668
|
+
// 1-bit path already routes through run_scan_codes1).
|
|
669
|
+
const IDSelectorContextDispatch sel_dispatch(sel, store_pairs);
|
|
670
|
+
|
|
604
671
|
size_t nup = 0;
|
|
605
672
|
for (size_t j = 0; j < list_size; j++) {
|
|
606
673
|
if (sel != nullptr) {
|
|
607
674
|
idx_t id = store_pairs ? lo_build(list_no, j) : ids[j];
|
|
608
|
-
if (!
|
|
675
|
+
if (!sel_dispatch.is_member(
|
|
676
|
+
id, IDScanContext{ids, list_size, j})) {
|
|
609
677
|
codes += code_size;
|
|
610
678
|
continue;
|
|
611
679
|
}
|
|
@@ -653,12 +721,10 @@ FlatCodesDistanceComputer* RaBitQuantizer::get_distance_computer(
|
|
|
653
721
|
// call the SIMD-specialized rabitq functions directly (no per-call
|
|
654
722
|
// with_simd_level overhead).
|
|
655
723
|
//
|
|
656
|
-
//
|
|
657
|
-
//
|
|
658
|
-
//
|
|
659
|
-
|
|
660
|
-
// specialization in rabitq_avx512.cpp.
|
|
661
|
-
return with_selected_simd_levels<AVAILABLE_SIMD_LEVELS_A0_SPR>(
|
|
724
|
+
// VPOPCNT rather than SPR: Ice Lake and Zen 4 have VPOPCNTDQ without the
|
|
725
|
+
// rest of the SPR feature set. Below it, dispatch falls through to
|
|
726
|
+
// rabitq_avx512.cpp.
|
|
727
|
+
return with_selected_simd_levels<AVAILABLE_SIMD_LEVELS_BASE_WITH_VPOPCNT>(
|
|
662
728
|
[&]<SIMDLevel SL>() -> FlatCodesDistanceComputer* {
|
|
663
729
|
if (qb == 0) {
|
|
664
730
|
auto dc =
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
#include <faiss/MetricType.h>
|
|
14
14
|
#include <faiss/impl/DistanceComputer.h>
|
|
15
15
|
#include <faiss/impl/Quantizer.h>
|
|
16
|
+
#include <faiss/impl/RaBitQUtils.h>
|
|
17
|
+
#include <faiss/impl/platform_macros.h>
|
|
16
18
|
|
|
17
19
|
namespace faiss {
|
|
18
20
|
|
|
@@ -20,6 +22,31 @@ struct IDSelector;
|
|
|
20
22
|
template <typename T, typename TI>
|
|
21
23
|
struct ResultHandlerUnordered;
|
|
22
24
|
|
|
25
|
+
/** Statistics for staged RaBitQ distance evaluation.
|
|
26
|
+
*
|
|
27
|
+
* These counters belong to the quantizer rather than to a particular ANN
|
|
28
|
+
* graph. They are currently populated by HNSW's staged search path.
|
|
29
|
+
*/
|
|
30
|
+
struct RaBitQStats {
|
|
31
|
+
uint64_t n_1bit = 0;
|
|
32
|
+
uint64_t n_refine = 0;
|
|
33
|
+
|
|
34
|
+
void reset() {
|
|
35
|
+
n_1bit = n_refine = 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
void add(const RaBitQStats& other) {
|
|
39
|
+
n_1bit += other.n_1bit;
|
|
40
|
+
n_refine += other.n_refine;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
double refine_ratio() const {
|
|
44
|
+
return n_1bit ? double(n_refine) / double(n_1bit) : 0.0;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
FAISS_API extern RaBitQStats rabitq_stats;
|
|
49
|
+
|
|
23
50
|
// the reference implementation of the https://arxiv.org/pdf/2405.12497
|
|
24
51
|
// Jianyang Gao, Cheng Long, "RaBitQ: Quantizing High-Dimensional Vectors
|
|
25
52
|
// with a Theoretical Error Bound for Approximate Nearest Neighbor Search".
|
|
@@ -122,10 +149,10 @@ struct RaBitQDistanceComputer : FlatCodesDistanceComputer {
|
|
|
122
149
|
// Used with f_error to compute error bounds for two-stage filtering
|
|
123
150
|
float g_error = 0.0f;
|
|
124
151
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
152
|
+
/// Per-distance-computer counters, aggregated by the owning index.
|
|
153
|
+
RaBitQStats stats;
|
|
154
|
+
|
|
155
|
+
float symmetric_dis(idx_t i, idx_t j) override;
|
|
129
156
|
|
|
130
157
|
// Compute 1-bit distance estimate (fast)
|
|
131
158
|
virtual float distance_to_code_1bit(const uint8_t* code) = 0;
|
|
@@ -133,6 +160,22 @@ struct RaBitQDistanceComputer : FlatCodesDistanceComputer {
|
|
|
133
160
|
// Compute full multi-bit distance (accurate)
|
|
134
161
|
virtual float distance_to_code_full(const uint8_t* code) = 0;
|
|
135
162
|
|
|
163
|
+
/// Apply the RaBitQ error bound without exposing the packed code layout to
|
|
164
|
+
/// ANN consumers. Precondition: nb_bits >= 2.
|
|
165
|
+
#ifndef SWIG
|
|
166
|
+
FAISS_ALWAYS_INLINE bool should_refine(
|
|
167
|
+
const uint8_t* code,
|
|
168
|
+
float estimate,
|
|
169
|
+
float threshold,
|
|
170
|
+
bool is_similarity) const {
|
|
171
|
+
const auto* factors =
|
|
172
|
+
reinterpret_cast<const rabitq_utils::SignBitFactorsWithError*>(
|
|
173
|
+
code + (d + 7) / 8);
|
|
174
|
+
return rabitq_utils::should_refine_candidate(
|
|
175
|
+
estimate, factors->f_error, g_error, threshold, is_similarity);
|
|
176
|
+
}
|
|
177
|
+
#endif
|
|
178
|
+
|
|
136
179
|
virtual void set_centroid(const float* centroid_in) {
|
|
137
180
|
centroid = centroid_in;
|
|
138
181
|
}
|
|
@@ -302,9 +302,9 @@ void quantize_ex_bits(
|
|
|
302
302
|
}
|
|
303
303
|
|
|
304
304
|
// Step 6: Handle negative dimensions (flip bits)
|
|
305
|
-
// For negative residuals, flip all bits: code' = ~code & max_code
|
|
305
|
+
// For negative or zero residuals, flip all bits: code' = ~code & max_code
|
|
306
306
|
for (size_t i = 0; i < d; i++) {
|
|
307
|
-
if (residual[i]
|
|
307
|
+
if (residual[i] <= 0) {
|
|
308
308
|
tmp_code[i] = (~tmp_code[i]) & max_code;
|
|
309
309
|
}
|
|
310
310
|
}
|
|
@@ -123,14 +123,20 @@ struct SingleQueryBlockResultHandler : BlockResultHandler<C, use_sel> {
|
|
|
123
123
|
using ResultHandlerT<C>::threshold;
|
|
124
124
|
|
|
125
125
|
explicit SingleResultHandler(SingleQueryBlockResultHandler& hr)
|
|
126
|
-
: the_handler(hr.the_handler) {
|
|
126
|
+
: the_handler(hr.the_handler) {
|
|
127
|
+
threshold = the_handler.threshold;
|
|
128
|
+
}
|
|
127
129
|
|
|
128
130
|
/// begin results for query # i
|
|
129
|
-
void begin(const size_t /* qid */) {
|
|
131
|
+
void begin(const size_t /* qid */) {
|
|
132
|
+
threshold = the_handler.threshold;
|
|
133
|
+
}
|
|
130
134
|
|
|
131
135
|
/// add one result for query i
|
|
132
136
|
bool add_result(T dis, TI idx) final {
|
|
133
|
-
|
|
137
|
+
bool updated = the_handler.add_result(dis, idx);
|
|
138
|
+
threshold = the_handler.threshold;
|
|
139
|
+
return updated;
|
|
134
140
|
}
|
|
135
141
|
|
|
136
142
|
/// series of results for query i is done
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
// -*- c++ -*-
|
|
9
9
|
|
|
10
|
+
#include <cmath>
|
|
10
11
|
#include <cstring>
|
|
11
12
|
#include <memory>
|
|
12
13
|
|
|
@@ -425,14 +426,29 @@ const LloydMaxTable kLloydMaxTables[] = {
|
|
|
425
426
|
{kLloydMaxCentroids8, kLloydMaxBoundaries8}, // 8
|
|
426
427
|
};
|
|
427
428
|
|
|
428
|
-
|
|
429
|
+
// The tables are Lloyd-Max optimal for N(0, 1) input. Callers whose input has
|
|
430
|
+
// a different standard deviation pass it as `scale` to stretch the table.
|
|
431
|
+
void populate_lloyd_max_trained(
|
|
432
|
+
size_t mse_bits,
|
|
433
|
+
std::vector<float>& trained,
|
|
434
|
+
float scale = 1.0f) {
|
|
429
435
|
FAISS_THROW_IF_NOT(mse_bits >= 1 && mse_bits <= 8);
|
|
430
436
|
FAISS_THROW_IF_NOT(kLloydMaxTables[mse_bits].centroids);
|
|
431
437
|
size_t k = size_t(1) << mse_bits;
|
|
432
438
|
const auto& t = kLloydMaxTables[mse_bits];
|
|
433
439
|
trained.resize(k + (k - 1));
|
|
434
|
-
|
|
435
|
-
|
|
440
|
+
for (size_t i = 0; i < k; i++) {
|
|
441
|
+
trained[i] = t.centroids[i] * scale;
|
|
442
|
+
}
|
|
443
|
+
for (size_t i = 0; i + 1 < k; i++) {
|
|
444
|
+
trained[k + i] = t.boundaries[i] * scale;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Component scale of a unit-norm vector in R^d.
|
|
449
|
+
float unit_norm_component_scale(size_t d) {
|
|
450
|
+
FAISS_THROW_IF_NOT(d > 0);
|
|
451
|
+
return 1.0f / std::sqrt(static_cast<float>(d));
|
|
436
452
|
}
|
|
437
453
|
|
|
438
454
|
} // namespace
|
|
@@ -588,19 +604,24 @@ void ScalarQuantizer::train(size_t n, const float* x) {
|
|
|
588
604
|
populate_lloyd_max_trained(bits, trained);
|
|
589
605
|
break;
|
|
590
606
|
case QT_1bit_tqmse:
|
|
591
|
-
populate_lloyd_max_trained(
|
|
607
|
+
populate_lloyd_max_trained(
|
|
608
|
+
1, trained, unit_norm_component_scale(d));
|
|
592
609
|
break;
|
|
593
610
|
case QT_2bit_tqmse:
|
|
594
|
-
populate_lloyd_max_trained(
|
|
611
|
+
populate_lloyd_max_trained(
|
|
612
|
+
2, trained, unit_norm_component_scale(d));
|
|
595
613
|
break;
|
|
596
614
|
case QT_3bit_tqmse:
|
|
597
|
-
populate_lloyd_max_trained(
|
|
615
|
+
populate_lloyd_max_trained(
|
|
616
|
+
3, trained, unit_norm_component_scale(d));
|
|
598
617
|
break;
|
|
599
618
|
case QT_4bit_tqmse:
|
|
600
|
-
populate_lloyd_max_trained(
|
|
619
|
+
populate_lloyd_max_trained(
|
|
620
|
+
4, trained, unit_norm_component_scale(d));
|
|
601
621
|
break;
|
|
602
622
|
case QT_8bit_tqmse:
|
|
603
|
-
populate_lloyd_max_trained(
|
|
623
|
+
populate_lloyd_max_trained(
|
|
624
|
+
8, trained, unit_norm_component_scale(d));
|
|
604
625
|
break;
|
|
605
626
|
case QT_2bit_tq:
|
|
606
627
|
case QT_3bit_tq:
|
|
@@ -614,7 +635,6 @@ void ScalarQuantizer::train(size_t n, const float* x) {
|
|
|
614
635
|
trained.push_back(seed_f[0]);
|
|
615
636
|
trained.push_back(seed_f[1]);
|
|
616
637
|
trained.push_back(static_cast<float>(turboq_refine.qjl_type));
|
|
617
|
-
turboq_refine.init_projection(d);
|
|
618
638
|
break;
|
|
619
639
|
}
|
|
620
640
|
default:
|
|
@@ -622,29 +642,11 @@ void ScalarQuantizer::train(size_t n, const float* x) {
|
|
|
622
642
|
}
|
|
623
643
|
}
|
|
624
644
|
|
|
625
|
-
void ScalarQuantizer::TurboQuantRefine::init_projection(size_t d) {
|
|
626
|
-
if (use_fwht()) {
|
|
627
|
-
padded_d = 1;
|
|
628
|
-
while (padded_d < d) {
|
|
629
|
-
padded_d <<= 1;
|
|
630
|
-
}
|
|
631
|
-
fwht_signs.resize(padded_d);
|
|
632
|
-
RandomGenerator rng(seed);
|
|
633
|
-
for (size_t i = 0; i < padded_d; i++) {
|
|
634
|
-
fwht_signs[i] = (rng.rand_int(2) == 0) ? 1.0f : -1.0f;
|
|
635
|
-
}
|
|
636
|
-
} else {
|
|
637
|
-
rr_matrix.resize(d * d);
|
|
638
|
-
float_randn(rr_matrix.data(), d * d, seed);
|
|
639
|
-
matrix_qr(static_cast<int>(d), static_cast<int>(d), rr_matrix.data());
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
|
|
643
645
|
ScalarQuantizer::SQuantizer* ScalarQuantizer::select_quantizer() const {
|
|
644
646
|
// A SIMD level's factory returns nullptr when the dimension is
|
|
645
647
|
// incompatible (e.g. AVX-512 needs d % 16 == 0); the dispatcher then falls
|
|
646
648
|
// back to the next-lower level (AVX-512 -> AVX2 -> scalar).
|
|
647
|
-
return with_simd_level_fallback<
|
|
649
|
+
return with_simd_level_fallback<AVAILABLE_SIMD_LEVELS_BASE_WITH_SPR>(
|
|
648
650
|
[&]<SIMDLevel SL>() -> SQuantizer* {
|
|
649
651
|
return scalar_quantizer::sq_select_quantizer<SL>(
|
|
650
652
|
qtype, d, trained);
|
|
@@ -681,7 +683,7 @@ void ScalarQuantizer::decode(const uint8_t* codes, float* x, size_t n) const {
|
|
|
681
683
|
ScalarQuantizer::SQDistanceComputer* ScalarQuantizer::get_distance_computer(
|
|
682
684
|
MetricType metric) const {
|
|
683
685
|
FAISS_THROW_IF_NOT(metric == METRIC_L2 || metric == METRIC_INNER_PRODUCT);
|
|
684
|
-
return with_simd_level_fallback<
|
|
686
|
+
return with_simd_level_fallback<AVAILABLE_SIMD_LEVELS_BASE_WITH_SPR>(
|
|
685
687
|
[&]<SIMDLevel SL>() -> SQDistanceComputer* {
|
|
686
688
|
return scalar_quantizer::sq_select_distance_computer<SL>(
|
|
687
689
|
metric, qtype, d, trained);
|
|
@@ -694,7 +696,7 @@ InvertedListScanner* ScalarQuantizer::select_InvertedListScanner(
|
|
|
694
696
|
bool store_pairs,
|
|
695
697
|
const IDSelector* sel,
|
|
696
698
|
bool by_residual) const {
|
|
697
|
-
return with_simd_level_fallback<
|
|
699
|
+
return with_simd_level_fallback<AVAILABLE_SIMD_LEVELS_BASE_WITH_SPR>(
|
|
698
700
|
[&]<SIMDLevel SL>() -> InvertedListScanner* {
|
|
699
701
|
return scalar_quantizer::sq_select_InvertedListScanner<SL>(
|
|
700
702
|
qtype,
|
|
@@ -177,18 +177,9 @@ struct ScalarQuantizer : Quantizer {
|
|
|
177
177
|
return s;
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
/// The selected projection is built from `trained` by the quantizer.
|
|
180
181
|
uint8_t qjl_type = 0;
|
|
181
182
|
uint64_t seed = 42;
|
|
182
|
-
size_t padded_d = 0;
|
|
183
|
-
std::vector<float> fwht_signs;
|
|
184
|
-
std::vector<float> rr_matrix;
|
|
185
|
-
size_t nb_bits_lo = 0;
|
|
186
|
-
size_t n_hi_dims = 0;
|
|
187
|
-
|
|
188
|
-
void init_projection(size_t d);
|
|
189
|
-
bool use_fwht() const {
|
|
190
|
-
return qjl_type == 0;
|
|
191
|
-
}
|
|
192
183
|
|
|
193
184
|
struct DistanceComputer : SQDistanceComputer {
|
|
194
185
|
virtual void configure(uint8_t qb, bool int_qjl) = 0;
|
|
@@ -199,6 +190,11 @@ struct ScalarQuantizer : Quantizer {
|
|
|
199
190
|
};
|
|
200
191
|
};
|
|
201
192
|
|
|
193
|
+
static_assert(
|
|
194
|
+
sizeof(TurboQuantRefine) <= 16,
|
|
195
|
+
"keep this a small config struct -- do not add projection buffers "
|
|
196
|
+
"here (T287092602)");
|
|
197
|
+
|
|
202
198
|
TurboQuantRefine turboq_refine;
|
|
203
199
|
|
|
204
200
|
SQDistanceComputer* get_distance_computer(
|