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
|
@@ -17,8 +17,10 @@
|
|
|
17
17
|
|
|
18
18
|
#include <faiss/impl/DistanceComputer.h>
|
|
19
19
|
#include <faiss/impl/IDSelector.h>
|
|
20
|
+
#include <faiss/impl/RaBitQuantizer.h>
|
|
20
21
|
#include <faiss/impl/ResultHandler.h>
|
|
21
22
|
#include <faiss/impl/VisitedTable.h>
|
|
23
|
+
#include <faiss/impl/hnsw/LockVector.h>
|
|
22
24
|
#include <faiss/impl/hnsw/MinimaxHeap.h>
|
|
23
25
|
|
|
24
26
|
namespace faiss {
|
|
@@ -222,17 +224,17 @@ int HNSW::prepare_level_tab(size_t n, bool preset_levels) {
|
|
|
222
224
|
}
|
|
223
225
|
}
|
|
224
226
|
|
|
225
|
-
int
|
|
227
|
+
int local_max_level = 0;
|
|
226
228
|
for (size_t i = 0; i < n; i++) {
|
|
227
229
|
int pt_level = levels[i + n0] - 1;
|
|
228
|
-
if (pt_level >
|
|
229
|
-
|
|
230
|
+
if (pt_level > local_max_level) {
|
|
231
|
+
local_max_level = pt_level;
|
|
230
232
|
}
|
|
231
233
|
offsets.push_back(offsets.back() + cum_nb_neighbors(pt_level + 1));
|
|
232
234
|
}
|
|
233
235
|
neighbors.resize(offsets.back(), -1);
|
|
234
236
|
|
|
235
|
-
return
|
|
237
|
+
return local_max_level;
|
|
236
238
|
}
|
|
237
239
|
|
|
238
240
|
/** Enumerate vertices from nearest to farthest from query, keep a
|
|
@@ -305,6 +307,11 @@ namespace {
|
|
|
305
307
|
|
|
306
308
|
using storage_idx_t = HNSW::storage_idx_t;
|
|
307
309
|
|
|
310
|
+
inline size_t pruned_neighbor_size(size_t max_size, float prune_headroom) {
|
|
311
|
+
return static_cast<size_t>(
|
|
312
|
+
max_size - max_size * std::clamp(prune_headroom, 0.0f, 0.5f));
|
|
313
|
+
}
|
|
314
|
+
|
|
308
315
|
// Map a (high-level) HNSW comparator C — which uses int64_t IDs — to the
|
|
309
316
|
// (low-level) MinimaxHeap comparator HC, which uses int32_t IDs.
|
|
310
317
|
template <class C>
|
|
@@ -784,83 +791,226 @@ HNSWStats hnsw_detail::greedy_update_nearest(
|
|
|
784
791
|
hnsw, qdis, level, nearest, d_nearest);
|
|
785
792
|
}
|
|
786
793
|
|
|
794
|
+
/**************************************************************
|
|
795
|
+
* Deterministic addition subroutines
|
|
796
|
+
**************************************************************/
|
|
797
|
+
|
|
787
798
|
namespace {
|
|
788
799
|
|
|
789
800
|
template <class C>
|
|
790
|
-
void
|
|
801
|
+
void compute_forward_links_impl(
|
|
791
802
|
HNSW& hnsw,
|
|
792
803
|
DistanceComputer& ptdis,
|
|
793
804
|
int pt_level,
|
|
794
|
-
|
|
795
|
-
LockVector& locks,
|
|
805
|
+
storage_idx_t pt_id,
|
|
796
806
|
VisitedTable& vt,
|
|
807
|
+
std::vector<std::pair<storage_idx_t, int>>& pt_reverse_edges,
|
|
797
808
|
bool keep_max_size_level0) {
|
|
798
809
|
storage_idx_t nearest = hnsw.entry_point;
|
|
799
|
-
|
|
800
|
-
#pragma omp critical
|
|
801
|
-
if (hnsw.entry_point == -1) { // double-check under lock.
|
|
802
|
-
hnsw.max_level = pt_level;
|
|
803
|
-
hnsw.entry_point = pt_id;
|
|
804
|
-
// leave nearest = -1 to trigger early exit after critical block.
|
|
805
|
-
} else {
|
|
806
|
-
// else: Another thread set the entry point.
|
|
807
|
-
nearest = hnsw.entry_point;
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
if (nearest < 0) {
|
|
812
|
-
return;
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
locks.lock(pt_id);
|
|
816
|
-
|
|
817
|
-
int level = hnsw.max_level; // level at which we start adding neighbors
|
|
810
|
+
FAISS_ASSERT(nearest >= 0);
|
|
818
811
|
float d_nearest = ptdis(nearest);
|
|
819
812
|
|
|
820
|
-
|
|
813
|
+
int level = hnsw.max_level;
|
|
814
|
+
// greedy descent on the upper levels the point does not live on
|
|
821
815
|
for (; level > pt_level; level--) {
|
|
822
816
|
greedy_update_nearest_impl<C>(hnsw, ptdis, level, nearest, d_nearest);
|
|
823
817
|
}
|
|
824
818
|
|
|
819
|
+
// levels the point lives on: search from the same entry point
|
|
825
820
|
for (; level >= 0; level--) {
|
|
826
|
-
|
|
821
|
+
std::priority_queue<HNSW::NodeDistCloserT<C>> link_targets;
|
|
822
|
+
search_neighbors_to_add_dispatch<C>(
|
|
827
823
|
hnsw,
|
|
828
824
|
ptdis,
|
|
829
|
-
|
|
825
|
+
link_targets,
|
|
830
826
|
nearest,
|
|
831
827
|
d_nearest,
|
|
832
828
|
level,
|
|
833
|
-
locks,
|
|
834
829
|
vt,
|
|
835
|
-
|
|
830
|
+
false);
|
|
831
|
+
|
|
832
|
+
int M = hnsw.nb_neighbors(level);
|
|
833
|
+
shrink_neighbor_list_inner<C>(
|
|
834
|
+
ptdis, link_targets, M, keep_max_size_level0 && (level == 0));
|
|
835
|
+
|
|
836
|
+
// pt_id is not reachable yet, so the snapshot stays immutable for the
|
|
837
|
+
// other points in this batch
|
|
838
|
+
size_t begin, end;
|
|
839
|
+
hnsw.neighbor_range(pt_id, level, &begin, &end);
|
|
840
|
+
size_t i = begin;
|
|
841
|
+
while (!link_targets.empty()) {
|
|
842
|
+
storage_idx_t other_id = link_targets.top().id;
|
|
843
|
+
link_targets.pop();
|
|
844
|
+
// pt_id is in its own candidate list when it is the entry point
|
|
845
|
+
if (other_id == pt_id) {
|
|
846
|
+
continue;
|
|
847
|
+
}
|
|
848
|
+
FAISS_ASSERT(i < end);
|
|
849
|
+
hnsw.neighbors[i++] = other_id;
|
|
850
|
+
pt_reverse_edges.emplace_back(other_id, level);
|
|
851
|
+
}
|
|
852
|
+
while (i < end) {
|
|
853
|
+
hnsw.neighbors[i++] = -1;
|
|
854
|
+
}
|
|
836
855
|
}
|
|
856
|
+
}
|
|
837
857
|
|
|
838
|
-
|
|
858
|
+
template <class C>
|
|
859
|
+
void merge_reverse_links_impl(
|
|
860
|
+
HNSW& hnsw,
|
|
861
|
+
DistanceComputer& dis,
|
|
862
|
+
storage_idx_t node,
|
|
863
|
+
int level,
|
|
864
|
+
std::vector<storage_idx_t>& incoming,
|
|
865
|
+
bool keep_max_size_level0) {
|
|
866
|
+
size_t begin, end;
|
|
867
|
+
hnsw.neighbor_range(node, level, &begin, &end);
|
|
868
|
+
size_t max_size = end - begin;
|
|
869
|
+
|
|
870
|
+
std::vector<storage_idx_t> cands;
|
|
871
|
+
cands.reserve(max_size + incoming.size());
|
|
872
|
+
for (size_t i = begin; i < end; i++) {
|
|
873
|
+
if (hnsw.neighbors[i] < 0) {
|
|
874
|
+
break;
|
|
875
|
+
}
|
|
876
|
+
cands.push_back(hnsw.neighbors[i]);
|
|
877
|
+
}
|
|
878
|
+
size_t n_existing = cands.size();
|
|
879
|
+
|
|
880
|
+
std::sort(incoming.begin(), incoming.end());
|
|
881
|
+
incoming.erase(
|
|
882
|
+
std::unique(incoming.begin(), incoming.end()), incoming.end());
|
|
883
|
+
for (storage_idx_t v : incoming) {
|
|
884
|
+
if (v == node) {
|
|
885
|
+
continue;
|
|
886
|
+
}
|
|
887
|
+
bool present = false;
|
|
888
|
+
for (size_t i = 0; i < n_existing; i++) {
|
|
889
|
+
if (cands[i] == v) {
|
|
890
|
+
present = true;
|
|
891
|
+
break;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
if (!present) {
|
|
895
|
+
cands.push_back(v);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
if (cands.size() <= max_size) {
|
|
900
|
+
size_t i = begin;
|
|
901
|
+
for (storage_idx_t v : cands) {
|
|
902
|
+
hnsw.neighbors[i++] = v;
|
|
903
|
+
}
|
|
904
|
+
while (i < end) {
|
|
905
|
+
hnsw.neighbors[i++] = -1;
|
|
906
|
+
}
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
839
909
|
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
910
|
+
// Ties broken by id, so the result is independent of collection order.
|
|
911
|
+
std::vector<std::pair<float, storage_idx_t>> arr;
|
|
912
|
+
arr.reserve(cands.size());
|
|
913
|
+
for (storage_idx_t v : cands) {
|
|
914
|
+
arr.emplace_back(dis.symmetric_dis(node, v), v);
|
|
915
|
+
}
|
|
916
|
+
std::sort(
|
|
917
|
+
arr.begin(),
|
|
918
|
+
arr.end(),
|
|
919
|
+
[](const std::pair<float, storage_idx_t>& a,
|
|
920
|
+
const std::pair<float, storage_idx_t>& b) {
|
|
921
|
+
if (a.first != b.first) {
|
|
922
|
+
// C::cmp(x, y) is true when y is "better" than x, so
|
|
923
|
+
// a before b iff a is better than b.
|
|
924
|
+
return C::cmp(b.first, a.first);
|
|
925
|
+
}
|
|
926
|
+
return a.second < b.second;
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
// Same headroom as add_link's reciprocal pruning, so degrees stay
|
|
930
|
+
// comparable between the two builds.
|
|
931
|
+
size_t pruned_size = pruned_neighbor_size(max_size, hnsw.prune_headroom);
|
|
932
|
+
if (pruned_size < 1) {
|
|
933
|
+
pruned_size = 1;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
std::vector<std::pair<float, storage_idx_t>> kept;
|
|
937
|
+
std::vector<std::pair<float, storage_idx_t>> outsiders;
|
|
938
|
+
for (const auto& cand : arr) {
|
|
939
|
+
float dist_v1_q = cand.first;
|
|
940
|
+
bool good = true;
|
|
941
|
+
for (const auto& k : kept) {
|
|
942
|
+
float dist_v1_v2 = dis.symmetric_dis(k.second, cand.second);
|
|
943
|
+
if (C::cmp(dist_v1_q, dist_v1_v2)) {
|
|
944
|
+
good = false;
|
|
945
|
+
break;
|
|
946
|
+
}
|
|
845
947
|
}
|
|
948
|
+
if (good) {
|
|
949
|
+
kept.push_back(cand);
|
|
950
|
+
if (kept.size() >= pruned_size) {
|
|
951
|
+
break;
|
|
952
|
+
}
|
|
953
|
+
} else if (keep_max_size_level0) {
|
|
954
|
+
outsiders.push_back(cand);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
for (size_t idx = 0; keep_max_size_level0 && kept.size() < max_size &&
|
|
958
|
+
idx < outsiders.size();
|
|
959
|
+
idx++) {
|
|
960
|
+
kept.push_back(outsiders[idx]);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
size_t i = begin;
|
|
964
|
+
for (const auto& k : kept) {
|
|
965
|
+
hnsw.neighbors[i++] = k.second;
|
|
966
|
+
}
|
|
967
|
+
while (i < end) {
|
|
968
|
+
hnsw.neighbors[i++] = -1;
|
|
846
969
|
}
|
|
847
970
|
}
|
|
848
971
|
|
|
849
972
|
} // namespace
|
|
850
973
|
|
|
851
|
-
void HNSW::
|
|
974
|
+
void HNSW::compute_forward_links_deterministic(
|
|
852
975
|
DistanceComputer& ptdis,
|
|
853
976
|
int pt_level,
|
|
854
|
-
|
|
855
|
-
LockVector& locks,
|
|
977
|
+
storage_idx_t pt_id,
|
|
856
978
|
VisitedTable& vt,
|
|
979
|
+
std::vector<std::pair<storage_idx_t, int>>& pt_reverse_edges,
|
|
857
980
|
bool keep_max_size_level0) {
|
|
858
981
|
if (is_similarity) {
|
|
859
|
-
|
|
860
|
-
*this,
|
|
982
|
+
compute_forward_links_impl<C_similarity>(
|
|
983
|
+
*this,
|
|
984
|
+
ptdis,
|
|
985
|
+
pt_level,
|
|
986
|
+
pt_id,
|
|
987
|
+
vt,
|
|
988
|
+
pt_reverse_edges,
|
|
989
|
+
keep_max_size_level0);
|
|
990
|
+
} else {
|
|
991
|
+
compute_forward_links_impl<C_distance>(
|
|
992
|
+
*this,
|
|
993
|
+
ptdis,
|
|
994
|
+
pt_level,
|
|
995
|
+
pt_id,
|
|
996
|
+
vt,
|
|
997
|
+
pt_reverse_edges,
|
|
998
|
+
keep_max_size_level0);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
void HNSW::merge_reverse_links_deterministic(
|
|
1003
|
+
DistanceComputer& dis,
|
|
1004
|
+
storage_idx_t node,
|
|
1005
|
+
int level,
|
|
1006
|
+
std::vector<storage_idx_t>& incoming,
|
|
1007
|
+
bool keep_max_size_level0) {
|
|
1008
|
+
if (is_similarity) {
|
|
1009
|
+
merge_reverse_links_impl<C_similarity>(
|
|
1010
|
+
*this, dis, node, level, incoming, keep_max_size_level0);
|
|
861
1011
|
} else {
|
|
862
|
-
|
|
863
|
-
*this,
|
|
1012
|
+
merge_reverse_links_impl<C_distance>(
|
|
1013
|
+
*this, dis, node, level, incoming, keep_max_size_level0);
|
|
864
1014
|
}
|
|
865
1015
|
}
|
|
866
1016
|
|
|
@@ -891,13 +1041,82 @@ inline void extract_search_params(
|
|
|
891
1041
|
}
|
|
892
1042
|
}
|
|
893
1043
|
|
|
894
|
-
|
|
895
|
-
|
|
1044
|
+
struct DefaultCandidateDistanceEvaluator {
|
|
1045
|
+
DistanceComputer& qdis;
|
|
1046
|
+
|
|
1047
|
+
template <typename GetThreshold, typename AddResult>
|
|
1048
|
+
size_t evaluate(
|
|
1049
|
+
const storage_idx_t* ids,
|
|
1050
|
+
int count,
|
|
1051
|
+
GetThreshold&& /* get_threshold */,
|
|
1052
|
+
AddResult&& add_result) {
|
|
1053
|
+
if (count == 4) {
|
|
1054
|
+
float distances[4];
|
|
1055
|
+
qdis.distances_batch_4(
|
|
1056
|
+
ids[0],
|
|
1057
|
+
ids[1],
|
|
1058
|
+
ids[2],
|
|
1059
|
+
ids[3],
|
|
1060
|
+
distances[0],
|
|
1061
|
+
distances[1],
|
|
1062
|
+
distances[2],
|
|
1063
|
+
distances[3]);
|
|
1064
|
+
for (int i = 0; i < 4; ++i) {
|
|
1065
|
+
add_result(ids[i], distances[i]);
|
|
1066
|
+
}
|
|
1067
|
+
} else {
|
|
1068
|
+
for (int i = 0; i < count; ++i) {
|
|
1069
|
+
add_result(ids[i], qdis(ids[i]));
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
return count;
|
|
1073
|
+
}
|
|
1074
|
+
};
|
|
1075
|
+
|
|
1076
|
+
/** RaBitQ's staged evaluator plugs into the same graph traversal as the
|
|
1077
|
+
* default distance computer. Each candidate first receives a 1-bit estimate;
|
|
1078
|
+
* the full code is evaluated only when its error bound can still beat the
|
|
1079
|
+
* current result threshold.
|
|
896
1080
|
*/
|
|
897
|
-
|
|
1081
|
+
struct RaBitQCandidateDistanceEvaluator {
|
|
1082
|
+
RaBitQDistanceComputer& rq;
|
|
1083
|
+
const bool is_similarity;
|
|
1084
|
+
|
|
1085
|
+
template <typename GetThreshold, typename AddResult>
|
|
1086
|
+
size_t evaluate(
|
|
1087
|
+
const storage_idx_t* ids,
|
|
1088
|
+
int count,
|
|
1089
|
+
GetThreshold&& get_threshold,
|
|
1090
|
+
AddResult&& add_result) {
|
|
1091
|
+
size_t ndis = 0;
|
|
1092
|
+
for (int i = 0; i < count; ++i) {
|
|
1093
|
+
const uint8_t* code =
|
|
1094
|
+
rq.codes + static_cast<size_t>(ids[i]) * rq.code_size;
|
|
1095
|
+
const float estimate = rq.distance_to_code_1bit(code);
|
|
1096
|
+
rq.stats.n_1bit++;
|
|
1097
|
+
ndis++;
|
|
1098
|
+
|
|
1099
|
+
float distance = estimate;
|
|
1100
|
+
if (rq.should_refine(
|
|
1101
|
+
code, estimate, get_threshold(), is_similarity)) {
|
|
1102
|
+
distance = rq.distance_to_code_full(code);
|
|
1103
|
+
rq.stats.n_refine++;
|
|
1104
|
+
ndis++;
|
|
1105
|
+
}
|
|
1106
|
+
add_result(ids[i], distance);
|
|
1107
|
+
}
|
|
1108
|
+
return ndis;
|
|
1109
|
+
}
|
|
1110
|
+
};
|
|
1111
|
+
|
|
1112
|
+
/** Templated body shared by the default and staged RaBitQ searches. The
|
|
1113
|
+
* evaluator changes only how a group of unvisited neighbors receives its
|
|
1114
|
+
* distance; traversal, result handling and stopping conditions stay identical.
|
|
1115
|
+
*/
|
|
1116
|
+
template <typename VTType, class C, typename DistanceEvaluator>
|
|
898
1117
|
int search_from_candidates_fixVT(
|
|
899
1118
|
const HNSW& hnsw,
|
|
900
|
-
|
|
1119
|
+
DistanceEvaluator& evaluator,
|
|
901
1120
|
ResultHandler& res,
|
|
902
1121
|
MinimaxHeapT<HC_for<C>>& candidates,
|
|
903
1122
|
VTType& vt,
|
|
@@ -906,7 +1125,7 @@ int search_from_candidates_fixVT(
|
|
|
906
1125
|
int nres_in,
|
|
907
1126
|
const SearchParameters* params) {
|
|
908
1127
|
int nres = nres_in;
|
|
909
|
-
|
|
1128
|
+
size_t ndis = 0;
|
|
910
1129
|
|
|
911
1130
|
bool do_dis_check;
|
|
912
1131
|
int efSearch;
|
|
@@ -964,7 +1183,7 @@ int search_from_candidates_fixVT(
|
|
|
964
1183
|
}
|
|
965
1184
|
|
|
966
1185
|
int counter = 0;
|
|
967
|
-
|
|
1186
|
+
storage_idx_t saved_j[4];
|
|
968
1187
|
|
|
969
1188
|
threshold = res.threshold;
|
|
970
1189
|
|
|
@@ -987,32 +1206,18 @@ int search_from_candidates_fixVT(
|
|
|
987
1206
|
counter += vt.set(v1) ? 1 : 0;
|
|
988
1207
|
|
|
989
1208
|
if (counter == 4) {
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
saved_j[3],
|
|
996
|
-
dis[0],
|
|
997
|
-
dis[1],
|
|
998
|
-
dis[2],
|
|
999
|
-
dis[3]);
|
|
1000
|
-
|
|
1001
|
-
for (size_t id4 = 0; id4 < 4; id4++) {
|
|
1002
|
-
add_to_heap(saved_j[id4], dis[id4]);
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
ndis += 4;
|
|
1006
|
-
|
|
1209
|
+
ndis += evaluator.evaluate(
|
|
1210
|
+
saved_j,
|
|
1211
|
+
counter,
|
|
1212
|
+
[&] { return threshold; },
|
|
1213
|
+
add_to_heap);
|
|
1007
1214
|
counter = 0;
|
|
1008
1215
|
}
|
|
1009
1216
|
}
|
|
1010
1217
|
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
ndis += 1;
|
|
1218
|
+
if (counter > 0) {
|
|
1219
|
+
ndis += evaluator.evaluate(
|
|
1220
|
+
saved_j, counter, [&] { return threshold; }, add_to_heap);
|
|
1016
1221
|
}
|
|
1017
1222
|
|
|
1018
1223
|
nstep++;
|
|
@@ -1033,12 +1238,12 @@ int search_from_candidates_fixVT(
|
|
|
1033
1238
|
return nres;
|
|
1034
1239
|
}
|
|
1035
1240
|
|
|
1036
|
-
/// Dispatches the VisitedTable concrete type
|
|
1037
|
-
/// the
|
|
1038
|
-
template <class C>
|
|
1039
|
-
int
|
|
1241
|
+
/// Dispatches the VisitedTable concrete type while keeping the distance
|
|
1242
|
+
/// evaluator statically selected outside the graph traversal loop.
|
|
1243
|
+
template <class C, typename DistanceEvaluator>
|
|
1244
|
+
int search_from_candidates_evaluator_dispatch(
|
|
1040
1245
|
const HNSW& hnsw,
|
|
1041
|
-
|
|
1246
|
+
DistanceEvaluator& evaluator,
|
|
1042
1247
|
ResultHandler& res,
|
|
1043
1248
|
MinimaxHeapT<HC_for<C>>& candidates,
|
|
1044
1249
|
VisitedTable& vt,
|
|
@@ -1049,7 +1254,7 @@ int search_from_candidates_dispatch(
|
|
|
1049
1254
|
auto call = [&]<typename VTType>(VTType& vt_concrete) -> int {
|
|
1050
1255
|
return search_from_candidates_fixVT<VTType, C>(
|
|
1051
1256
|
hnsw,
|
|
1052
|
-
|
|
1257
|
+
evaluator,
|
|
1053
1258
|
res,
|
|
1054
1259
|
candidates,
|
|
1055
1260
|
vt_concrete,
|
|
@@ -1065,6 +1270,59 @@ int search_from_candidates_dispatch(
|
|
|
1065
1270
|
return call(vts);
|
|
1066
1271
|
}
|
|
1067
1272
|
|
|
1273
|
+
template <class C>
|
|
1274
|
+
int search_from_candidates_dispatch(
|
|
1275
|
+
const HNSW& hnsw,
|
|
1276
|
+
DistanceComputer& qdis,
|
|
1277
|
+
ResultHandler& res,
|
|
1278
|
+
MinimaxHeapT<HC_for<C>>& candidates,
|
|
1279
|
+
VisitedTable& vt,
|
|
1280
|
+
HNSWStats& stats,
|
|
1281
|
+
int level,
|
|
1282
|
+
int nres_in,
|
|
1283
|
+
const SearchParameters* params) {
|
|
1284
|
+
DefaultCandidateDistanceEvaluator evaluator{qdis};
|
|
1285
|
+
return search_from_candidates_evaluator_dispatch<C>(
|
|
1286
|
+
hnsw,
|
|
1287
|
+
evaluator,
|
|
1288
|
+
res,
|
|
1289
|
+
candidates,
|
|
1290
|
+
vt,
|
|
1291
|
+
stats,
|
|
1292
|
+
level,
|
|
1293
|
+
nres_in,
|
|
1294
|
+
params);
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
template <class C>
|
|
1298
|
+
int search_from_candidates_rabitq_dispatch(
|
|
1299
|
+
const HNSW& hnsw,
|
|
1300
|
+
DistanceComputer& qdis,
|
|
1301
|
+
ResultHandler& res,
|
|
1302
|
+
MinimaxHeapT<HC_for<C>>& candidates,
|
|
1303
|
+
VisitedTable& vt,
|
|
1304
|
+
HNSWStats& stats,
|
|
1305
|
+
int level,
|
|
1306
|
+
int nres_in,
|
|
1307
|
+
const SearchParameters* params) {
|
|
1308
|
+
auto* rq = dynamic_cast<RaBitQDistanceComputer*>(&qdis);
|
|
1309
|
+
FAISS_THROW_IF_NOT_MSG(
|
|
1310
|
+
rq, "staged RaBitQ search requires an IndexRaBitQ storage");
|
|
1311
|
+
FAISS_THROW_IF_NOT_MSG(
|
|
1312
|
+
rq->nb_bits >= 2, "staged RaBitQ search requires nb_bits >= 2");
|
|
1313
|
+
RaBitQCandidateDistanceEvaluator evaluator{*rq, hnsw.is_similarity};
|
|
1314
|
+
return search_from_candidates_evaluator_dispatch<C>(
|
|
1315
|
+
hnsw,
|
|
1316
|
+
evaluator,
|
|
1317
|
+
res,
|
|
1318
|
+
candidates,
|
|
1319
|
+
vt,
|
|
1320
|
+
stats,
|
|
1321
|
+
level,
|
|
1322
|
+
nres_in,
|
|
1323
|
+
params);
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1068
1326
|
} // namespace
|
|
1069
1327
|
|
|
1070
1328
|
/** Do a BFS on the candidates list. Public dispatcher: only handles the
|
|
@@ -1491,37 +1749,49 @@ HNSWStats search_impl(
|
|
|
1491
1749
|
}
|
|
1492
1750
|
|
|
1493
1751
|
int ef = std::max(cur_efSearch, k);
|
|
1494
|
-
if (bounded_queue) {
|
|
1495
|
-
// support Panorama search in this branch
|
|
1752
|
+
if (bounded_queue) {
|
|
1496
1753
|
MinimaxHeapT<HC_for<C>> candidates(ef);
|
|
1497
1754
|
|
|
1498
1755
|
candidates.push(nearest, d_nearest);
|
|
1499
1756
|
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1757
|
+
switch (hnsw.search_method) {
|
|
1758
|
+
case HNSW::SM_DEFAULT:
|
|
1759
|
+
search_from_candidates_dispatch<C>(
|
|
1760
|
+
hnsw, qdis, res, candidates, vt, stats, 0, 0, params);
|
|
1761
|
+
break;
|
|
1762
|
+
case HNSW::SM_RABITQ:
|
|
1763
|
+
search_from_candidates_rabitq_dispatch<C>(
|
|
1764
|
+
hnsw, qdis, res, candidates, vt, stats, 0, 0, params);
|
|
1765
|
+
break;
|
|
1766
|
+
case HNSW::SM_PANORAMA:
|
|
1767
|
+
if constexpr (std::is_same_v<C, HNSW::C_distance>) {
|
|
1768
|
+
hnsw_detail::search_from_candidates_panorama(
|
|
1769
|
+
hnsw,
|
|
1770
|
+
index,
|
|
1771
|
+
qdis,
|
|
1772
|
+
res,
|
|
1773
|
+
candidates,
|
|
1774
|
+
vt,
|
|
1775
|
+
stats,
|
|
1776
|
+
0,
|
|
1777
|
+
0,
|
|
1778
|
+
params);
|
|
1779
|
+
} else {
|
|
1780
|
+
FAISS_THROW_MSG(
|
|
1781
|
+
"Panorama search does not support similarity metrics");
|
|
1782
|
+
}
|
|
1783
|
+
break;
|
|
1784
|
+
default:
|
|
1785
|
+
FAISS_THROW_MSG("invalid HNSW search method");
|
|
1523
1786
|
}
|
|
1524
1787
|
} else {
|
|
1788
|
+
FAISS_THROW_IF_NOT_MSG(
|
|
1789
|
+
hnsw.search_method != HNSW::SM_RABITQ,
|
|
1790
|
+
"staged RaBitQ search requires bounded_queue=true");
|
|
1791
|
+
FAISS_THROW_IF_NOT_MSG(
|
|
1792
|
+
hnsw.search_method == HNSW::SM_DEFAULT ||
|
|
1793
|
+
hnsw.search_method == HNSW::SM_PANORAMA,
|
|
1794
|
+
"invalid HNSW search method");
|
|
1525
1795
|
auto call = [&]<typename VTType>(VTType& vt_concrete) {
|
|
1526
1796
|
return search_from_candidate_unbounded_fixVT<VTType, C>(
|
|
1527
1797
|
hnsw,
|
|
@@ -1579,6 +1849,50 @@ void search_level_0_impl(
|
|
|
1579
1849
|
|
|
1580
1850
|
int k = extract_k_from_ResultHandler<C>(res);
|
|
1581
1851
|
|
|
1852
|
+
auto search_candidates = [&](MinimaxHeapT<HC_for<C>>& candidates,
|
|
1853
|
+
int nres) {
|
|
1854
|
+
switch (hnsw.search_method) {
|
|
1855
|
+
case HNSW::SM_DEFAULT:
|
|
1856
|
+
return search_from_candidates_dispatch<C>(
|
|
1857
|
+
hnsw,
|
|
1858
|
+
qdis,
|
|
1859
|
+
res,
|
|
1860
|
+
candidates,
|
|
1861
|
+
vt,
|
|
1862
|
+
search_stats,
|
|
1863
|
+
0,
|
|
1864
|
+
nres,
|
|
1865
|
+
params);
|
|
1866
|
+
case HNSW::SM_RABITQ:
|
|
1867
|
+
return search_from_candidates_rabitq_dispatch<C>(
|
|
1868
|
+
hnsw,
|
|
1869
|
+
qdis,
|
|
1870
|
+
res,
|
|
1871
|
+
candidates,
|
|
1872
|
+
vt,
|
|
1873
|
+
search_stats,
|
|
1874
|
+
0,
|
|
1875
|
+
nres,
|
|
1876
|
+
params);
|
|
1877
|
+
case HNSW::SM_PANORAMA:
|
|
1878
|
+
// Preserve the pre-enum behavior: search_level_0 did not use
|
|
1879
|
+
// Panorama's progressive traversal and fell back to the
|
|
1880
|
+
// ordinary HNSW candidate search.
|
|
1881
|
+
return search_from_candidates_dispatch<C>(
|
|
1882
|
+
hnsw,
|
|
1883
|
+
qdis,
|
|
1884
|
+
res,
|
|
1885
|
+
candidates,
|
|
1886
|
+
vt,
|
|
1887
|
+
search_stats,
|
|
1888
|
+
0,
|
|
1889
|
+
nres,
|
|
1890
|
+
params);
|
|
1891
|
+
default:
|
|
1892
|
+
FAISS_THROW_MSG("invalid HNSW search method");
|
|
1893
|
+
}
|
|
1894
|
+
};
|
|
1895
|
+
|
|
1582
1896
|
if (search_type == 1) {
|
|
1583
1897
|
int nres = 0;
|
|
1584
1898
|
|
|
@@ -1598,16 +1912,7 @@ void search_level_0_impl(
|
|
|
1598
1912
|
|
|
1599
1913
|
candidates.push(cj, nearest_d[j]);
|
|
1600
1914
|
|
|
1601
|
-
nres =
|
|
1602
|
-
hnsw,
|
|
1603
|
-
qdis,
|
|
1604
|
-
res,
|
|
1605
|
-
candidates,
|
|
1606
|
-
vt,
|
|
1607
|
-
search_stats,
|
|
1608
|
-
0,
|
|
1609
|
-
nres,
|
|
1610
|
-
params);
|
|
1915
|
+
nres = search_candidates(candidates, nres);
|
|
1611
1916
|
nres = std::min(nres, candidates_size);
|
|
1612
1917
|
}
|
|
1613
1918
|
} else if (search_type == 2) {
|
|
@@ -1624,8 +1929,7 @@ void search_level_0_impl(
|
|
|
1624
1929
|
candidates.push(cj, nearest_d[j]);
|
|
1625
1930
|
}
|
|
1626
1931
|
|
|
1627
|
-
|
|
1628
|
-
hnsw, qdis, res, candidates, vt, search_stats, 0, 0, params);
|
|
1932
|
+
search_candidates(candidates, 0);
|
|
1629
1933
|
}
|
|
1630
1934
|
}
|
|
1631
1935
|
|