faiss 0.6.3 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/lib/faiss/version.rb +1 -1
  4. data/vendor/faiss/faiss/Clustering.h +8 -0
  5. data/vendor/faiss/faiss/Index.h +1 -1
  6. data/vendor/faiss/faiss/IndexBinaryFromFloat.cpp +23 -2
  7. data/vendor/faiss/faiss/IndexBinaryHNSW.cpp +12 -140
  8. data/vendor/faiss/faiss/IndexBinaryHNSW.h +0 -6
  9. data/vendor/faiss/faiss/IndexBinaryIVF.cpp +6 -14
  10. data/vendor/faiss/faiss/IndexBinaryIVF.h +8 -1
  11. data/vendor/faiss/faiss/IndexFlat.cpp +8 -4
  12. data/vendor/faiss/faiss/IndexFlat.h +2 -2
  13. data/vendor/faiss/faiss/IndexHNSW.cpp +301 -91
  14. data/vendor/faiss/faiss/IndexHNSW.h +31 -7
  15. data/vendor/faiss/faiss/IndexIDMap.cpp +11 -6
  16. data/vendor/faiss/faiss/IndexIVF.cpp +19 -4
  17. data/vendor/faiss/faiss/IndexIVFPQ.cpp +9 -1
  18. data/vendor/faiss/faiss/IndexIVFPQ.h +11 -0
  19. data/vendor/faiss/faiss/IndexShards.cpp +3 -3
  20. data/vendor/faiss/faiss/IndexShardsIVF.cpp +4 -3
  21. data/vendor/faiss/faiss/SuperKMeans.cpp +31 -8
  22. data/vendor/faiss/faiss/SuperKMeans.h +3 -2
  23. data/vendor/faiss/faiss/VectorTransform.cpp +38 -0
  24. data/vendor/faiss/faiss/VectorTransform.h +3 -0
  25. data/vendor/faiss/faiss/clone_index.cpp +3 -0
  26. data/vendor/faiss/faiss/gpu/GpuClonerOptions.h +1 -5
  27. data/vendor/faiss/faiss/gpu/GpuDistance.h +2 -5
  28. data/vendor/faiss/faiss/gpu/GpuIndex.h +4 -5
  29. data/vendor/faiss/faiss/gpu/GpuIndexCagra.h +65 -42
  30. data/vendor/faiss/faiss/impl/AdditiveQuantizer.cpp +1 -1
  31. data/vendor/faiss/faiss/impl/ClusteringInitialization.cpp +5 -2
  32. data/vendor/faiss/faiss/impl/HNSW.cpp +419 -115
  33. data/vendor/faiss/faiss/impl/HNSW.h +46 -7
  34. data/vendor/faiss/faiss/impl/IDSelector.h +44 -0
  35. data/vendor/faiss/faiss/impl/NNDescent.cpp +10 -3
  36. data/vendor/faiss/faiss/impl/Panorama.h +11 -2
  37. data/vendor/faiss/faiss/impl/ProductQuantizer.cpp +4 -4
  38. data/vendor/faiss/faiss/impl/RaBitQUtils.cpp +1 -1
  39. data/vendor/faiss/faiss/impl/RaBitQuantizer.cpp +74 -8
  40. data/vendor/faiss/faiss/impl/RaBitQuantizer.h +47 -4
  41. data/vendor/faiss/faiss/impl/RaBitQuantizerMultiBit.cpp +2 -2
  42. data/vendor/faiss/faiss/impl/ResultHandler.h +9 -3
  43. data/vendor/faiss/faiss/impl/ScalarQuantizer.cpp +32 -30
  44. data/vendor/faiss/faiss/impl/ScalarQuantizer.h +6 -10
  45. data/vendor/faiss/faiss/impl/binary_hamming/IndexBinaryIVF_impl.h +89 -13
  46. data/vendor/faiss/faiss/impl/expanded_scanners.h +5 -1
  47. data/vendor/faiss/faiss/impl/fast_scan/decompose_qbs.h +1 -0
  48. data/vendor/faiss/faiss/impl/index_read.cpp +130 -29
  49. data/vendor/faiss/faiss/impl/index_write.cpp +31 -26
  50. data/vendor/faiss/faiss/impl/pq_code_distance/IVFPQScanner_impl.h +73 -39
  51. data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-generic.cpp +4 -4
  52. data/vendor/faiss/faiss/impl/scalar_quantizer/EDENQuantizer.cpp +6 -1
  53. data/vendor/faiss/faiss/impl/scalar_quantizer/distance_computers.h +2 -2
  54. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx2.cpp +6 -2
  55. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-dispatch.h +37 -17
  56. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-neon.cpp +109 -10
  57. data/vendor/faiss/faiss/impl/simd_dispatch.h +40 -22
  58. data/vendor/faiss/faiss/index_factory.cpp +8 -1
  59. data/vendor/faiss/faiss/invlists/InvertedLists.cpp +7 -4
  60. data/vendor/faiss/faiss/invlists/OnDiskInvertedLists.cpp +10 -4
  61. data/vendor/faiss/faiss/svs/IndexSVSVamana.cpp +16 -4
  62. data/vendor/faiss/faiss/svs/IndexSVSVamana.h +8 -1
  63. data/vendor/faiss/faiss/svs/IndexSVSVamanaLVQ.cpp +4 -2
  64. data/vendor/faiss/faiss/svs/IndexSVSVamanaLVQ.h +2 -1
  65. data/vendor/faiss/faiss/svs/IndexSVSVamanaLeanVec.cpp +9 -2
  66. data/vendor/faiss/faiss/svs/IndexSVSVamanaLeanVec.h +2 -1
  67. data/vendor/faiss/faiss/utils/distances.cpp +20 -13
  68. data/vendor/faiss/faiss/utils/distances_dispatch.h +30 -24
  69. data/vendor/faiss/faiss/utils/distances_fused/distances_fused.cpp +1 -1
  70. data/vendor/faiss/faiss/utils/hamming.cpp +15 -9
  71. data/vendor/faiss/faiss/utils/hamming.h +10 -1
  72. data/vendor/faiss/faiss/utils/hamming_distance/common.h +14 -3
  73. data/vendor/faiss/faiss/utils/hamming_distance/hamming_avx512_vpopcnt.cpp +24 -0
  74. data/vendor/faiss/faiss/utils/hamming_distance/hamming_computer-avx512.h +1 -1
  75. data/vendor/faiss/faiss/utils/hamming_distance/{hamming_computer-avx512_spr.h → hamming_computer-avx512_vpopcnt.h} +85 -24
  76. data/vendor/faiss/faiss/utils/hamming_distance/hamming_impl.h +141 -0
  77. data/vendor/faiss/faiss/utils/simd_impl/distances_arm_sve.cpp +194 -30
  78. data/vendor/faiss/faiss/utils/simd_impl/distances_rvv.cpp +93 -55
  79. data/vendor/faiss/faiss/utils/simd_impl/rabitq_avx512.cpp +57 -40
  80. data/vendor/faiss/faiss/utils/simd_impl/{rabitq_avx512_spr.cpp → rabitq_avx512_vpopcnt.cpp} +21 -24
  81. data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_dispatch.h +2 -7
  82. data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_kernels.h +6 -1
  83. data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_kernels_sve.cpp +34 -0
  84. data/vendor/faiss/faiss/utils/simd_levels.cpp +172 -67
  85. data/vendor/faiss/faiss/utils/simd_levels.h +26 -15
  86. metadata +6 -5
  87. data/vendor/faiss/faiss/utils/hamming_distance/hamming_avx512_spr.cpp +0 -15
@@ -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 max_level_2 = 0;
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 > max_level_2) {
229
- max_level_2 = pt_level;
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 max_level_2;
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 add_with_locks_impl(
801
+ void compute_forward_links_impl(
791
802
  HNSW& hnsw,
792
803
  DistanceComputer& ptdis,
793
804
  int pt_level,
794
- int pt_id,
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
- if (nearest == -1) { // avoid locking after the first point.
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
- // greedy search on upper levels
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
- add_links_starting_from_impl<C>(
821
+ std::priority_queue<HNSW::NodeDistCloserT<C>> link_targets;
822
+ search_neighbors_to_add_dispatch<C>(
827
823
  hnsw,
828
824
  ptdis,
829
- pt_id,
825
+ link_targets,
830
826
  nearest,
831
827
  d_nearest,
832
828
  level,
833
- locks,
834
829
  vt,
835
- keep_max_size_level0);
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
- locks.unlock(pt_id);
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
- #pragma omp critical
841
- {
842
- if (pt_level > hnsw.max_level) {
843
- hnsw.max_level = pt_level;
844
- hnsw.entry_point = pt_id;
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::add_with_locks(
974
+ void HNSW::compute_forward_links_deterministic(
852
975
  DistanceComputer& ptdis,
853
976
  int pt_level,
854
- int pt_id,
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
- add_with_locks_impl<C_similarity>(
860
- *this, ptdis, pt_level, pt_id, locks, vt, keep_max_size_level0);
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
- add_with_locks_impl<C_distance>(
863
- *this, ptdis, pt_level, pt_id, locks, vt, keep_max_size_level0);
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
- /** Templated body of `search_from_candidates` — instantiated once per
895
- * VisitedTable subclass × comparator.
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
- template <typename VTType, class C>
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
- DistanceComputer& qdis,
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
- int ndis = 0;
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
- size_t saved_j[4];
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
- float dis[4];
991
- qdis.distances_batch_4(
992
- saved_j[0],
993
- saved_j[1],
994
- saved_j[2],
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
- for (int icnt = 0; icnt < counter; icnt++) {
1012
- float dis = qdis(saved_j[icnt]);
1013
- add_to_heap(saved_j[icnt], dis);
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 for a given C, then calls
1037
- /// the templated `search_from_candidates_fixVT<VTType, C>`.
1038
- template <class C>
1039
- int search_from_candidates_dispatch(
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
- DistanceComputer& qdis,
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
- qdis,
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) { // this is the most common branch, for now we only
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
- if (!hnsw.is_panorama) {
1501
- search_from_candidates_dispatch<C>(
1502
- hnsw, qdis, res, candidates, vt, stats, 0, 0, params);
1503
- } else {
1504
- // Panorama is L2-specific and is only valid for C_distance.
1505
- // The public dispatch ensures we never reach this code path
1506
- // with C != C_distance, but assert in debug builds.
1507
- if constexpr (std::is_same_v<C, HNSW::C_distance>) {
1508
- hnsw_detail::search_from_candidates_panorama(
1509
- hnsw,
1510
- index,
1511
- qdis,
1512
- res,
1513
- candidates,
1514
- vt,
1515
- stats,
1516
- 0,
1517
- 0,
1518
- params);
1519
- } else {
1520
- FAISS_THROW_MSG(
1521
- "Panorama search is not supported with is_similarity=true");
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 = search_from_candidates_dispatch<C>(
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
- search_from_candidates_dispatch<C>(
1628
- hnsw, qdis, res, candidates, vt, search_stats, 0, 0, params);
1932
+ search_candidates(candidates, 0);
1629
1933
  }
1630
1934
  }
1631
1935