faiss 0.6.2 → 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 +8 -0
- data/lib/faiss/version.rb +1 -1
- data/vendor/faiss/faiss/AutoTune.cpp +3 -1
- data/vendor/faiss/faiss/Clustering.cpp +9 -1
- data/vendor/faiss/faiss/Clustering.h +8 -0
- data/vendor/faiss/faiss/IVFlib.cpp +14 -3
- data/vendor/faiss/faiss/Index.h +2 -2
- data/vendor/faiss/faiss/IndexAdditiveQuantizer.cpp +9 -10
- data/vendor/faiss/faiss/IndexAdditiveQuantizerFastScan.cpp +2 -3
- data/vendor/faiss/faiss/IndexBinaryFromFloat.cpp +24 -4
- data/vendor/faiss/faiss/IndexBinaryHNSW.cpp +16 -145
- data/vendor/faiss/faiss/IndexBinaryHNSW.h +0 -6
- data/vendor/faiss/faiss/IndexBinaryHash.cpp +5 -9
- data/vendor/faiss/faiss/IndexBinaryIVF.cpp +8 -18
- data/vendor/faiss/faiss/IndexBinaryIVF.h +8 -1
- data/vendor/faiss/faiss/IndexEDEN.cpp +273 -0
- data/vendor/faiss/faiss/IndexEDEN.h +57 -0
- data/vendor/faiss/faiss/IndexFastScan.cpp +15 -4
- data/vendor/faiss/faiss/IndexFlat.cpp +21 -54
- data/vendor/faiss/faiss/IndexFlat.h +2 -2
- data/vendor/faiss/faiss/IndexHNSW.cpp +311 -102
- data/vendor/faiss/faiss/IndexHNSW.h +31 -7
- data/vendor/faiss/faiss/IndexIDMap.cpp +26 -8
- data/vendor/faiss/faiss/IndexIDMap.h +2 -0
- data/vendor/faiss/faiss/IndexIVF.cpp +36 -10
- data/vendor/faiss/faiss/IndexIVFAdditiveQuantizer.cpp +1 -1
- data/vendor/faiss/faiss/IndexIVFAdditiveQuantizerFastScan.cpp +3 -4
- data/vendor/faiss/faiss/IndexIVFEDEN.cpp +302 -0
- data/vendor/faiss/faiss/IndexIVFEDEN.h +70 -0
- data/vendor/faiss/faiss/IndexIVFFastScan.cpp +5 -6
- data/vendor/faiss/faiss/IndexIVFFlat.cpp +3 -4
- data/vendor/faiss/faiss/IndexIVFIndependentQuantizer.cpp +1 -1
- data/vendor/faiss/faiss/IndexIVFPQ.cpp +49 -23
- data/vendor/faiss/faiss/IndexIVFPQ.h +11 -0
- data/vendor/faiss/faiss/IndexIVFPQFastScan.cpp +0 -1
- data/vendor/faiss/faiss/IndexIVFRaBitQ.cpp +19 -49
- data/vendor/faiss/faiss/IndexIVFRaBitQFastScan.cpp +180 -76
- data/vendor/faiss/faiss/IndexIVFRaBitQFastScan.h +5 -4
- data/vendor/faiss/faiss/IndexIVFSpectralHash.cpp +8 -6
- data/vendor/faiss/faiss/IndexLSH.cpp +2 -3
- data/vendor/faiss/faiss/IndexLattice.cpp +5 -0
- data/vendor/faiss/faiss/IndexNNDescent.cpp +9 -2
- data/vendor/faiss/faiss/IndexNSG.cpp +7 -2
- data/vendor/faiss/faiss/IndexPQ.cpp +6 -8
- data/vendor/faiss/faiss/IndexPreTransform.cpp +15 -0
- data/vendor/faiss/faiss/IndexRaBitQ.cpp +2 -2
- data/vendor/faiss/faiss/IndexRaBitQFastScan.cpp +1 -2
- data/vendor/faiss/faiss/IndexRaBitQFastScan.h +5 -1
- data/vendor/faiss/faiss/IndexRefine.cpp +30 -1
- data/vendor/faiss/faiss/IndexReplicas.cpp +1 -2
- data/vendor/faiss/faiss/IndexShards.cpp +5 -5
- data/vendor/faiss/faiss/IndexShardsIVF.cpp +6 -5
- data/vendor/faiss/faiss/MetaIndexes.cpp +2 -4
- data/vendor/faiss/faiss/SuperKMeans.cpp +286 -247
- data/vendor/faiss/faiss/SuperKMeans.h +33 -2
- data/vendor/faiss/faiss/VectorTransform.cpp +71 -2
- data/vendor/faiss/faiss/VectorTransform.h +3 -0
- data/vendor/faiss/faiss/clone_index.cpp +8 -0
- data/vendor/faiss/faiss/factory_tools.cpp +47 -4
- data/vendor/faiss/faiss/gpu/GpuCloner.cpp +11 -11
- 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 +38 -16
- data/vendor/faiss/faiss/gpu/GpuIndexCagra.h +71 -1
- data/vendor/faiss/faiss/gpu/GpuIndexIVF.h +17 -0
- data/vendor/faiss/faiss/gpu/GpuIndexIVFScalarQuantizer.h +16 -0
- data/vendor/faiss/faiss/gpu/perf/PerfClustering.cpp +1 -1
- data/vendor/faiss/faiss/gpu/perf/PerfIVFPQAdd.cpp +2 -2
- data/vendor/faiss/faiss/gpu/test/TestGpuIndexIVFScalarQuantizer.cpp +180 -0
- data/vendor/faiss/faiss/gpu_metal/MetalIndexIVFFlat.h +1 -5
- data/vendor/faiss/faiss/gpu_metal/MetalIndexIVFPQ.h +88 -0
- data/vendor/faiss/faiss/gpu_metal/impl/MetalIVFPQ.h +134 -0
- data/vendor/faiss/faiss/impl/AdditiveQuantizer.cpp +1 -1
- data/vendor/faiss/faiss/impl/ClusteringInitialization.cpp +7 -4
- data/vendor/faiss/faiss/impl/DistanceComputer.h +34 -0
- data/vendor/faiss/faiss/impl/EDENQuantizer.h +119 -0
- data/vendor/faiss/faiss/impl/HNSW.cpp +528 -267
- data/vendor/faiss/faiss/impl/HNSW.h +46 -7
- data/vendor/faiss/faiss/impl/IDSelector.h +44 -0
- data/vendor/faiss/faiss/impl/LocalSearchQuantizer.cpp +2 -2
- data/vendor/faiss/faiss/impl/NNDescent.cpp +10 -3
- data/vendor/faiss/faiss/impl/NSG.cpp +3 -1
- data/vendor/faiss/faiss/impl/Panorama.h +20 -9
- data/vendor/faiss/faiss/impl/PolysemousTraining.cpp +152 -84
- data/vendor/faiss/faiss/impl/ProductQuantizer.cpp +38 -26
- data/vendor/faiss/faiss/impl/RaBitQUtils.cpp +45 -37
- data/vendor/faiss/faiss/impl/RaBitQUtils.h +35 -0
- data/vendor/faiss/faiss/impl/RaBitQuantizer.cpp +239 -72
- data/vendor/faiss/faiss/impl/RaBitQuantizer.h +66 -4
- data/vendor/faiss/faiss/impl/RaBitQuantizerMultiBit.cpp +4 -13
- data/vendor/faiss/faiss/impl/ResultHandler.h +34 -34
- data/vendor/faiss/faiss/impl/ScalarQuantizer.cpp +287 -84
- data/vendor/faiss/faiss/impl/ScalarQuantizer.h +26 -10
- data/vendor/faiss/faiss/impl/ThreadedIndex-inl.h +2 -2
- data/vendor/faiss/faiss/impl/VisitedTable.cpp +22 -2
- data/vendor/faiss/faiss/impl/VisitedTable.h +20 -0
- data/vendor/faiss/faiss/impl/binary_hamming/IndexBinaryIVF_impl.h +90 -14
- data/vendor/faiss/faiss/impl/binary_hamming/avx2.cpp +4 -4
- 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/fast_scan/dispatching.h +35 -2
- data/vendor/faiss/faiss/impl/hnsw/LockVector.cpp +1 -1
- data/vendor/faiss/faiss/impl/index_read.cpp +491 -50
- data/vendor/faiss/faiss/impl/index_write.cpp +86 -30
- data/vendor/faiss/faiss/impl/lattice_Zn.cpp +8 -9
- data/vendor/faiss/faiss/impl/platform_macros.h +3 -1
- data/vendor/faiss/faiss/impl/polysemous_training/avx512.cpp +284 -0
- data/vendor/faiss/faiss/impl/polysemous_training/dispatch.h +115 -0
- data/vendor/faiss/faiss/impl/pq_code_distance/IVFPQScanner_impl.h +73 -39
- data/vendor/faiss/faiss/impl/pq_code_distance/IVFPQ_QueryTables.cpp +0 -1
- data/vendor/faiss/faiss/impl/pq_code_distance/PQDistanceComputer_impl.h +26 -15
- data/vendor/faiss/faiss/impl/pq_code_distance/avx2.cpp +4 -4
- data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-generic.cpp +4 -4
- data/vendor/faiss/faiss/impl/result_handler/ResultHandler.cpp +195 -0
- data/vendor/faiss/faiss/impl/result_handler/avx2.cpp +133 -0
- data/vendor/faiss/faiss/impl/result_handler/avx512.cpp +281 -0
- data/vendor/faiss/faiss/impl/scalar_quantizer/EDENQuantizer-avx2.cpp +72 -0
- data/vendor/faiss/faiss/impl/scalar_quantizer/EDENQuantizer-avx512.cpp +228 -0
- data/vendor/faiss/faiss/impl/scalar_quantizer/EDENQuantizer.cpp +887 -0
- data/vendor/faiss/faiss/impl/scalar_quantizer/distance_computers.h +2 -2
- data/vendor/faiss/faiss/impl/scalar_quantizer/quantizers.h +9 -8
- data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx2.cpp +90 -24
- data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512-impl.h +30 -30
- data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512-spr.cpp +4 -5
- data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512.cpp +101 -34
- data/vendor/faiss/faiss/impl/scalar_quantizer/sq-dispatch.h +169 -13
- data/vendor/faiss/faiss/impl/scalar_quantizer/sq-neon.cpp +125 -26
- data/vendor/faiss/faiss/impl/simd_dispatch.h +70 -31
- data/vendor/faiss/faiss/index_factory.cpp +40 -7
- data/vendor/faiss/faiss/invlists/DirectMap.cpp +1 -1
- data/vendor/faiss/faiss/invlists/InvertedLists.cpp +9 -6
- data/vendor/faiss/faiss/invlists/OnDiskInvertedLists.cpp +29 -8
- data/vendor/faiss/faiss/python/python_callbacks.cpp +3 -1
- data/vendor/faiss/faiss/svs/IndexSVSFaissUtils.h +60 -0
- data/vendor/faiss/faiss/svs/IndexSVSFlat.cpp +26 -1
- data/vendor/faiss/faiss/svs/IndexSVSFlat.h +13 -0
- data/vendor/faiss/faiss/svs/IndexSVSIVF.cpp +1 -1
- data/vendor/faiss/faiss/svs/IndexSVSIVFLeanVec.cpp +1 -1
- data/vendor/faiss/faiss/svs/IndexSVSVamana.cpp +47 -5
- data/vendor/faiss/faiss/svs/IndexSVSVamana.h +23 -3
- 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 +10 -4
- data/vendor/faiss/faiss/svs/IndexSVSVamanaLeanVec.h +2 -1
- data/vendor/faiss/faiss/utils/approx_topk_hamming/approx_topk_hamming.h +1 -1
- data/vendor/faiss/faiss/utils/distances.cpp +30 -11
- 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/distances_simd.cpp +4 -3
- data/vendor/faiss/faiss/utils/extra_distances.cpp +4 -14
- data/vendor/faiss/faiss/utils/extra_distances.h +1 -2
- data/vendor/faiss/faiss/utils/hamming.cpp +16 -10
- 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/quantize_lut.cpp +29 -8
- data/vendor/faiss/faiss/utils/rabitq_simd.h +202 -0
- data/vendor/faiss/faiss/utils/simd_impl/distances_arm_sve.cpp +194 -30
- data/vendor/faiss/faiss/utils/simd_impl/distances_avx2.cpp +0 -1
- data/vendor/faiss/faiss/utils/simd_impl/distances_avx512.cpp +263 -15
- data/vendor/faiss/faiss/utils/simd_impl/distances_rvv.cpp +198 -18
- data/vendor/faiss/faiss/utils/simd_impl/rabitq_avx2.cpp +245 -0
- data/vendor/faiss/faiss/utils/simd_impl/rabitq_avx512.cpp +330 -40
- data/vendor/faiss/faiss/utils/simd_impl/{rabitq_avx512_spr.cpp → rabitq_avx512_vpopcnt.cpp} +112 -23
- data/vendor/faiss/faiss/utils/simd_impl/rabitq_neon.cpp +11 -0
- data/vendor/faiss/faiss/utils/simd_impl/rabitq_rvv.cpp +143 -6
- 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 +196 -47
- data/vendor/faiss/faiss/utils/simd_levels.h +33 -8
- data/vendor/faiss/faiss/utils/utils.cpp +9 -27
- metadata +21 -5
- data/vendor/faiss/faiss/utils/hamming_distance/hamming_avx512_spr.cpp +0 -15
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
#include <faiss/impl/HNSW.h>
|
|
9
9
|
|
|
10
|
+
#include <array>
|
|
10
11
|
#include <cinttypes>
|
|
11
12
|
#include <cstddef>
|
|
12
13
|
#include <cstdlib>
|
|
@@ -16,8 +17,10 @@
|
|
|
16
17
|
|
|
17
18
|
#include <faiss/impl/DistanceComputer.h>
|
|
18
19
|
#include <faiss/impl/IDSelector.h>
|
|
20
|
+
#include <faiss/impl/RaBitQuantizer.h>
|
|
19
21
|
#include <faiss/impl/ResultHandler.h>
|
|
20
22
|
#include <faiss/impl/VisitedTable.h>
|
|
23
|
+
#include <faiss/impl/hnsw/LockVector.h>
|
|
21
24
|
#include <faiss/impl/hnsw/MinimaxHeap.h>
|
|
22
25
|
|
|
23
26
|
namespace faiss {
|
|
@@ -172,17 +175,21 @@ void HNSW::print_neighbor_stats(int level) const {
|
|
|
172
175
|
}
|
|
173
176
|
|
|
174
177
|
void HNSW::fill_with_random_links(size_t n) {
|
|
175
|
-
|
|
178
|
+
if (n == 0) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
max_level = prepare_level_tab(n);
|
|
182
|
+
entry_point = 0;
|
|
183
|
+
|
|
176
184
|
RandomGenerator rng2(456);
|
|
177
185
|
|
|
178
|
-
for (int level =
|
|
186
|
+
for (int level = max_level - 1; level >= 0; --level) {
|
|
179
187
|
std::vector<int> elts;
|
|
180
188
|
for (size_t i = 0; i < n; i++) {
|
|
181
189
|
if (levels[i] > level) {
|
|
182
190
|
elts.push_back(i);
|
|
183
191
|
}
|
|
184
192
|
}
|
|
185
|
-
printf("linking %zd elements in level %d\n", elts.size(), level);
|
|
186
193
|
|
|
187
194
|
if (elts.size() == 1) {
|
|
188
195
|
continue;
|
|
@@ -217,17 +224,17 @@ int HNSW::prepare_level_tab(size_t n, bool preset_levels) {
|
|
|
217
224
|
}
|
|
218
225
|
}
|
|
219
226
|
|
|
220
|
-
int
|
|
227
|
+
int local_max_level = 0;
|
|
221
228
|
for (size_t i = 0; i < n; i++) {
|
|
222
229
|
int pt_level = levels[i + n0] - 1;
|
|
223
|
-
if (pt_level >
|
|
224
|
-
|
|
230
|
+
if (pt_level > local_max_level) {
|
|
231
|
+
local_max_level = pt_level;
|
|
225
232
|
}
|
|
226
233
|
offsets.push_back(offsets.back() + cum_nb_neighbors(pt_level + 1));
|
|
227
234
|
}
|
|
228
235
|
neighbors.resize(offsets.back(), -1);
|
|
229
236
|
|
|
230
|
-
return
|
|
237
|
+
return local_max_level;
|
|
231
238
|
}
|
|
232
239
|
|
|
233
240
|
/** Enumerate vertices from nearest to farthest from query, keep a
|
|
@@ -300,6 +307,11 @@ namespace {
|
|
|
300
307
|
|
|
301
308
|
using storage_idx_t = HNSW::storage_idx_t;
|
|
302
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
|
+
|
|
303
315
|
// Map a (high-level) HNSW comparator C — which uses int64_t IDs — to the
|
|
304
316
|
// (low-level) MinimaxHeap comparator HC, which uses int32_t IDs.
|
|
305
317
|
template <class C>
|
|
@@ -779,83 +791,226 @@ HNSWStats hnsw_detail::greedy_update_nearest(
|
|
|
779
791
|
hnsw, qdis, level, nearest, d_nearest);
|
|
780
792
|
}
|
|
781
793
|
|
|
794
|
+
/**************************************************************
|
|
795
|
+
* Deterministic addition subroutines
|
|
796
|
+
**************************************************************/
|
|
797
|
+
|
|
782
798
|
namespace {
|
|
783
799
|
|
|
784
800
|
template <class C>
|
|
785
|
-
void
|
|
801
|
+
void compute_forward_links_impl(
|
|
786
802
|
HNSW& hnsw,
|
|
787
803
|
DistanceComputer& ptdis,
|
|
788
804
|
int pt_level,
|
|
789
|
-
|
|
790
|
-
LockVector& locks,
|
|
805
|
+
storage_idx_t pt_id,
|
|
791
806
|
VisitedTable& vt,
|
|
807
|
+
std::vector<std::pair<storage_idx_t, int>>& pt_reverse_edges,
|
|
792
808
|
bool keep_max_size_level0) {
|
|
793
809
|
storage_idx_t nearest = hnsw.entry_point;
|
|
794
|
-
|
|
795
|
-
#pragma omp critical
|
|
796
|
-
if (hnsw.entry_point == -1) { // double-check under lock.
|
|
797
|
-
hnsw.max_level = pt_level;
|
|
798
|
-
hnsw.entry_point = pt_id;
|
|
799
|
-
// leave nearest = -1 to trigger early exit after critical block.
|
|
800
|
-
} else {
|
|
801
|
-
// else: Another thread set the entry point.
|
|
802
|
-
nearest = hnsw.entry_point;
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
if (nearest < 0) {
|
|
807
|
-
return;
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
locks.lock(pt_id);
|
|
811
|
-
|
|
812
|
-
int level = hnsw.max_level; // level at which we start adding neighbors
|
|
810
|
+
FAISS_ASSERT(nearest >= 0);
|
|
813
811
|
float d_nearest = ptdis(nearest);
|
|
814
812
|
|
|
815
|
-
|
|
813
|
+
int level = hnsw.max_level;
|
|
814
|
+
// greedy descent on the upper levels the point does not live on
|
|
816
815
|
for (; level > pt_level; level--) {
|
|
817
816
|
greedy_update_nearest_impl<C>(hnsw, ptdis, level, nearest, d_nearest);
|
|
818
817
|
}
|
|
819
818
|
|
|
819
|
+
// levels the point lives on: search from the same entry point
|
|
820
820
|
for (; level >= 0; level--) {
|
|
821
|
-
|
|
821
|
+
std::priority_queue<HNSW::NodeDistCloserT<C>> link_targets;
|
|
822
|
+
search_neighbors_to_add_dispatch<C>(
|
|
822
823
|
hnsw,
|
|
823
824
|
ptdis,
|
|
824
|
-
|
|
825
|
+
link_targets,
|
|
825
826
|
nearest,
|
|
826
827
|
d_nearest,
|
|
827
828
|
level,
|
|
828
|
-
locks,
|
|
829
829
|
vt,
|
|
830
|
-
|
|
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
|
+
}
|
|
831
855
|
}
|
|
856
|
+
}
|
|
832
857
|
|
|
833
|
-
|
|
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;
|
|
834
869
|
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
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;
|
|
840
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
|
+
}
|
|
909
|
+
|
|
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
|
+
}
|
|
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;
|
|
841
969
|
}
|
|
842
970
|
}
|
|
843
971
|
|
|
844
972
|
} // namespace
|
|
845
973
|
|
|
846
|
-
void HNSW::
|
|
974
|
+
void HNSW::compute_forward_links_deterministic(
|
|
847
975
|
DistanceComputer& ptdis,
|
|
848
976
|
int pt_level,
|
|
849
|
-
|
|
850
|
-
LockVector& locks,
|
|
977
|
+
storage_idx_t pt_id,
|
|
851
978
|
VisitedTable& vt,
|
|
979
|
+
std::vector<std::pair<storage_idx_t, int>>& pt_reverse_edges,
|
|
852
980
|
bool keep_max_size_level0) {
|
|
853
981
|
if (is_similarity) {
|
|
854
|
-
|
|
855
|
-
*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);
|
|
856
990
|
} else {
|
|
857
|
-
|
|
858
|
-
*this,
|
|
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);
|
|
1011
|
+
} else {
|
|
1012
|
+
merge_reverse_links_impl<C_distance>(
|
|
1013
|
+
*this, dis, node, level, incoming, keep_max_size_level0);
|
|
859
1014
|
}
|
|
860
1015
|
}
|
|
861
1016
|
|
|
@@ -886,13 +1041,82 @@ inline void extract_search_params(
|
|
|
886
1041
|
}
|
|
887
1042
|
}
|
|
888
1043
|
|
|
889
|
-
|
|
890
|
-
|
|
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.
|
|
891
1080
|
*/
|
|
892
|
-
|
|
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>
|
|
893
1117
|
int search_from_candidates_fixVT(
|
|
894
1118
|
const HNSW& hnsw,
|
|
895
|
-
|
|
1119
|
+
DistanceEvaluator& evaluator,
|
|
896
1120
|
ResultHandler& res,
|
|
897
1121
|
MinimaxHeapT<HC_for<C>>& candidates,
|
|
898
1122
|
VTType& vt,
|
|
@@ -901,7 +1125,7 @@ int search_from_candidates_fixVT(
|
|
|
901
1125
|
int nres_in,
|
|
902
1126
|
const SearchParameters* params) {
|
|
903
1127
|
int nres = nres_in;
|
|
904
|
-
|
|
1128
|
+
size_t ndis = 0;
|
|
905
1129
|
|
|
906
1130
|
bool do_dis_check;
|
|
907
1131
|
int efSearch;
|
|
@@ -959,7 +1183,7 @@ int search_from_candidates_fixVT(
|
|
|
959
1183
|
}
|
|
960
1184
|
|
|
961
1185
|
int counter = 0;
|
|
962
|
-
|
|
1186
|
+
storage_idx_t saved_j[4];
|
|
963
1187
|
|
|
964
1188
|
threshold = res.threshold;
|
|
965
1189
|
|
|
@@ -982,32 +1206,18 @@ int search_from_candidates_fixVT(
|
|
|
982
1206
|
counter += vt.set(v1) ? 1 : 0;
|
|
983
1207
|
|
|
984
1208
|
if (counter == 4) {
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
saved_j[3],
|
|
991
|
-
dis[0],
|
|
992
|
-
dis[1],
|
|
993
|
-
dis[2],
|
|
994
|
-
dis[3]);
|
|
995
|
-
|
|
996
|
-
for (size_t id4 = 0; id4 < 4; id4++) {
|
|
997
|
-
add_to_heap(saved_j[id4], dis[id4]);
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
ndis += 4;
|
|
1001
|
-
|
|
1209
|
+
ndis += evaluator.evaluate(
|
|
1210
|
+
saved_j,
|
|
1211
|
+
counter,
|
|
1212
|
+
[&] { return threshold; },
|
|
1213
|
+
add_to_heap);
|
|
1002
1214
|
counter = 0;
|
|
1003
1215
|
}
|
|
1004
1216
|
}
|
|
1005
1217
|
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
ndis += 1;
|
|
1218
|
+
if (counter > 0) {
|
|
1219
|
+
ndis += evaluator.evaluate(
|
|
1220
|
+
saved_j, counter, [&] { return threshold; }, add_to_heap);
|
|
1011
1221
|
}
|
|
1012
1222
|
|
|
1013
1223
|
nstep++;
|
|
@@ -1028,12 +1238,12 @@ int search_from_candidates_fixVT(
|
|
|
1028
1238
|
return nres;
|
|
1029
1239
|
}
|
|
1030
1240
|
|
|
1031
|
-
/// Dispatches the VisitedTable concrete type
|
|
1032
|
-
/// the
|
|
1033
|
-
template <class C>
|
|
1034
|
-
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(
|
|
1035
1245
|
const HNSW& hnsw,
|
|
1036
|
-
|
|
1246
|
+
DistanceEvaluator& evaluator,
|
|
1037
1247
|
ResultHandler& res,
|
|
1038
1248
|
MinimaxHeapT<HC_for<C>>& candidates,
|
|
1039
1249
|
VisitedTable& vt,
|
|
@@ -1044,7 +1254,7 @@ int search_from_candidates_dispatch(
|
|
|
1044
1254
|
auto call = [&]<typename VTType>(VTType& vt_concrete) -> int {
|
|
1045
1255
|
return search_from_candidates_fixVT<VTType, C>(
|
|
1046
1256
|
hnsw,
|
|
1047
|
-
|
|
1257
|
+
evaluator,
|
|
1048
1258
|
res,
|
|
1049
1259
|
candidates,
|
|
1050
1260
|
vt_concrete,
|
|
@@ -1060,6 +1270,59 @@ int search_from_candidates_dispatch(
|
|
|
1060
1270
|
return call(vts);
|
|
1061
1271
|
}
|
|
1062
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
|
+
|
|
1063
1326
|
} // namespace
|
|
1064
1327
|
|
|
1065
1328
|
/** Do a BFS on the candidates list. Public dispatcher: only handles the
|
|
@@ -1092,8 +1355,8 @@ int hnsw_detail::search_from_candidates_panorama(
|
|
|
1092
1355
|
const SearchParameters* params) {
|
|
1093
1356
|
// Panorama's progressive-bound math is L2-specific: refuse to run in
|
|
1094
1357
|
// similarity mode.
|
|
1095
|
-
|
|
1096
|
-
|
|
1358
|
+
FAISS_THROW_IF_MSG(
|
|
1359
|
+
hnsw.is_similarity,
|
|
1097
1360
|
"search_from_candidates_panorama does not support is_similarity=true");
|
|
1098
1361
|
|
|
1099
1362
|
using C = HNSW::C_distance;
|
|
@@ -1132,177 +1395,129 @@ int hnsw_detail::search_from_candidates_panorama(
|
|
|
1132
1395
|
flat_codes_qdis,
|
|
1133
1396
|
"DistanceComputer must be a FlatCodesDistanceComputer");
|
|
1134
1397
|
|
|
1135
|
-
|
|
1136
|
-
size_t
|
|
1137
|
-
|
|
1138
|
-
|
|
1398
|
+
const auto& pano = panorama_index->pano;
|
|
1399
|
+
const size_t nb_per_parent = static_cast<size_t>(hnsw.nb_neighbors(level));
|
|
1400
|
+
const size_t num_panorama_levels = pano.n_levels;
|
|
1401
|
+
const size_t level_width_floats = pano.level_width_floats;
|
|
1139
1402
|
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
float
|
|
1403
|
+
constexpr size_t kTargetBatch = 64;
|
|
1404
|
+
const size_t buf_cap = kTargetBatch + nb_per_parent;
|
|
1405
|
+
std::vector<uint32_t> index_array(buf_cap);
|
|
1406
|
+
std::vector<float> exact_distances(buf_cap);
|
|
1407
|
+
std::vector<float> dot_buffer(buf_cap);
|
|
1408
|
+
std::vector<float> query_cum_sums_buf(num_panorama_levels + 1);
|
|
1144
1409
|
|
|
1145
|
-
|
|
1410
|
+
const float* query = flat_codes_qdis->q;
|
|
1146
1411
|
const size_t d = static_cast<size_t>(panorama_index->d);
|
|
1147
1412
|
|
|
1148
|
-
|
|
1149
|
-
|
|
1413
|
+
const float* cum_base = panorama_index->get_cum_sum(0);
|
|
1414
|
+
const size_t cum_stride = num_panorama_levels + 1;
|
|
1415
|
+
const auto* flat_storage =
|
|
1416
|
+
static_cast<const IndexFlat*>(panorama_index->storage);
|
|
1417
|
+
const float* xb_base =
|
|
1418
|
+
reinterpret_cast<const float*>(flat_storage->codes.data());
|
|
1419
|
+
const size_t feat_stride = d;
|
|
1150
1420
|
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1421
|
+
pano.compute_query_cum_sums(query, query_cum_sums_buf.data());
|
|
1422
|
+
const float* query_cum_sums = query_cum_sums_buf.data();
|
|
1423
|
+
const float query_norm_sq = query_cum_sums[0] * query_cum_sums[0];
|
|
1154
1424
|
|
|
1155
|
-
|
|
1156
|
-
// tricky stopping condition: there are more than ef
|
|
1157
|
-
// distances that are processed already that are smaller
|
|
1158
|
-
// than d0
|
|
1159
|
-
|
|
1160
|
-
int n_dis_below = candidates.count_below(d0);
|
|
1161
|
-
if (n_dis_below >= efSearch) {
|
|
1162
|
-
break;
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1425
|
+
int nstep = 0;
|
|
1165
1426
|
|
|
1166
|
-
|
|
1167
|
-
|
|
1427
|
+
PanoramaStats local_pano_stats;
|
|
1428
|
+
local_pano_stats.reset();
|
|
1168
1429
|
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
// that are not selected.
|
|
1430
|
+
bool stop_flag = false;
|
|
1431
|
+
while (candidates.size() > 0 && !stop_flag) {
|
|
1172
1432
|
size_t initial_size = 0;
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1433
|
+
size_t k_popped = 0;
|
|
1434
|
+
while (initial_size < kTargetBatch && candidates.size() > 0) {
|
|
1435
|
+
float d0 = 0;
|
|
1436
|
+
int v0 = candidates.pop_min(&d0);
|
|
1437
|
+
if (do_dis_check) {
|
|
1438
|
+
int n_dis_below = candidates.count_below(d0);
|
|
1439
|
+
if (n_dis_below >= efSearch) {
|
|
1440
|
+
if (k_popped == 0) {
|
|
1441
|
+
// Standard early-stop: nothing queued this
|
|
1442
|
+
// iteration, terminate the outer loop entirely
|
|
1443
|
+
// (matches the single-pop path's behavior).
|
|
1444
|
+
stop_flag = true;
|
|
1445
|
+
} else {
|
|
1446
|
+
// We already have parents queued; un-pop this
|
|
1447
|
+
// one so the next outer iteration sees it and
|
|
1448
|
+
// re-applies the stop check from a clean state.
|
|
1449
|
+
candidates.push(v0, d0);
|
|
1450
|
+
}
|
|
1451
|
+
break;
|
|
1452
|
+
}
|
|
1177
1453
|
}
|
|
1454
|
+
k_popped++;
|
|
1178
1455
|
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1456
|
+
size_t begin, end;
|
|
1457
|
+
hnsw.neighbor_range(v0, level, &begin, &end);
|
|
1458
|
+
for (size_t j = begin; j < end; j++) {
|
|
1459
|
+
int v1 = hnsw.neighbors[j];
|
|
1460
|
+
if (v1 < 0) {
|
|
1461
|
+
break;
|
|
1462
|
+
}
|
|
1183
1463
|
|
|
1184
|
-
|
|
1185
|
-
|
|
1464
|
+
bool is_new = vt.set(v1);
|
|
1465
|
+
bool is_selected = !sel || sel->is_member(v1);
|
|
1466
|
+
if (is_new && is_selected) {
|
|
1467
|
+
const float vsum =
|
|
1468
|
+
cum_base[static_cast<size_t>(v1) * cum_stride];
|
|
1469
|
+
index_array[initial_size] = v1;
|
|
1470
|
+
exact_distances[initial_size] = query_norm_sq + vsum * vsum;
|
|
1471
|
+
initial_size++;
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1186
1474
|
}
|
|
1187
1475
|
|
|
1188
1476
|
local_pano_stats.total_dims += initial_size * d;
|
|
1477
|
+
|
|
1189
1478
|
size_t batch_size = initial_size;
|
|
1190
1479
|
size_t curr_panorama_level = 0;
|
|
1191
|
-
const size_t num_panorama_levels = panorama_index->pano.n_levels;
|
|
1192
1480
|
while (curr_panorama_level < num_panorama_levels && batch_size > 0) {
|
|
1193
|
-
|
|
1481
|
+
const size_t cs_level_idx = curr_panorama_level + 1;
|
|
1482
|
+
const float query_cum_norm = query_cum_sums[cs_level_idx];
|
|
1483
|
+
const float two_qc = 2.0f * query_cum_norm;
|
|
1484
|
+
|
|
1485
|
+
const size_t start_dim = curr_panorama_level * level_width_floats;
|
|
1486
|
+
size_t end_dim = (curr_panorama_level + 1) * level_width_floats;
|
|
1487
|
+
end_dim = std::min(end_dim, d);
|
|
1488
|
+
const size_t dim_span = end_dim - start_dim;
|
|
1489
|
+
|
|
1490
|
+
const float* level_base = xb_base + start_dim;
|
|
1491
|
+
with_level_width(dim_span, [&]<size_t W>() {
|
|
1492
|
+
compute_level_dot_kernel<false, W>(
|
|
1493
|
+
query + start_dim,
|
|
1494
|
+
level_base,
|
|
1495
|
+
index_array.data(),
|
|
1496
|
+
batch_size,
|
|
1497
|
+
dim_span,
|
|
1498
|
+
dot_buffer.data(),
|
|
1499
|
+
feat_stride);
|
|
1500
|
+
});
|
|
1501
|
+
ndis += batch_size;
|
|
1194
1502
|
|
|
1195
|
-
size_t start_dim = curr_panorama_level *
|
|
1196
|
-
panorama_index->pano.level_width_floats;
|
|
1197
|
-
size_t end_dim = (curr_panorama_level + 1) *
|
|
1198
|
-
panorama_index->pano.level_width_floats;
|
|
1199
|
-
end_dim = std::min(end_dim, static_cast<size_t>(panorama_index->d));
|
|
1200
|
-
|
|
1201
|
-
size_t i = 0;
|
|
1202
1503
|
size_t next_batch_size = 0;
|
|
1203
|
-
for (; i + 3 < batch_size; i += 4) {
|
|
1204
|
-
idx_t idx_0 = index_array[i];
|
|
1205
|
-
idx_t idx_1 = index_array[i + 1];
|
|
1206
|
-
idx_t idx_2 = index_array[i + 2];
|
|
1207
|
-
idx_t idx_3 = index_array[i + 3];
|
|
1208
|
-
|
|
1209
|
-
float dp[4];
|
|
1210
|
-
flat_codes_qdis->partial_dot_product_batch_4(
|
|
1211
|
-
idx_0,
|
|
1212
|
-
idx_1,
|
|
1213
|
-
idx_2,
|
|
1214
|
-
idx_3,
|
|
1215
|
-
dp[0],
|
|
1216
|
-
dp[1],
|
|
1217
|
-
dp[2],
|
|
1218
|
-
dp[3],
|
|
1219
|
-
start_dim,
|
|
1220
|
-
end_dim - start_dim);
|
|
1221
|
-
ndis += 4;
|
|
1222
1504
|
|
|
1223
|
-
|
|
1224
|
-
float
|
|
1225
|
-
float
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
float
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
idx_2)[curr_panorama_level + 1];
|
|
1234
|
-
float cum_sum_3 = panorama_index->get_cum_sum(
|
|
1235
|
-
idx_3)[curr_panorama_level + 1];
|
|
1236
|
-
|
|
1237
|
-
float cs_bound_0 = 2.0f * cum_sum_0 * query_cum_norm;
|
|
1238
|
-
float cs_bound_1 = 2.0f * cum_sum_1 * query_cum_norm;
|
|
1239
|
-
float cs_bound_2 = 2.0f * cum_sum_2 * query_cum_norm;
|
|
1240
|
-
float cs_bound_3 = 2.0f * cum_sum_3 * query_cum_norm;
|
|
1241
|
-
|
|
1242
|
-
float lower_bound_0 = new_exact_0 - cs_bound_0;
|
|
1243
|
-
float lower_bound_1 = new_exact_1 - cs_bound_1;
|
|
1244
|
-
float lower_bound_2 = new_exact_2 - cs_bound_2;
|
|
1245
|
-
float lower_bound_3 = new_exact_3 - cs_bound_3;
|
|
1246
|
-
|
|
1247
|
-
// The following code is not the most branch friendly (due to
|
|
1248
|
-
// the maintenance of the candidate heap), but micro-benchmarks
|
|
1249
|
-
// have shown that it is not worth it to write horrible code to
|
|
1250
|
-
// squeeze out those cycles.
|
|
1251
|
-
if (!C::cmp(lower_bound_0, threshold)) {
|
|
1252
|
-
exact_distances[next_batch_size] = new_exact_0;
|
|
1253
|
-
index_array[next_batch_size] = idx_0;
|
|
1254
|
-
next_batch_size += 1;
|
|
1255
|
-
} else {
|
|
1256
|
-
candidates.push(idx_0, new_exact_0);
|
|
1257
|
-
}
|
|
1258
|
-
if (!C::cmp(lower_bound_1, threshold)) {
|
|
1259
|
-
exact_distances[next_batch_size] = new_exact_1;
|
|
1260
|
-
index_array[next_batch_size] = idx_1;
|
|
1261
|
-
next_batch_size += 1;
|
|
1262
|
-
} else {
|
|
1263
|
-
candidates.push(idx_1, new_exact_1);
|
|
1264
|
-
}
|
|
1265
|
-
if (!C::cmp(lower_bound_2, threshold)) {
|
|
1266
|
-
exact_distances[next_batch_size] = new_exact_2;
|
|
1267
|
-
index_array[next_batch_size] = idx_2;
|
|
1268
|
-
next_batch_size += 1;
|
|
1269
|
-
} else {
|
|
1270
|
-
candidates.push(idx_2, new_exact_2);
|
|
1271
|
-
}
|
|
1272
|
-
if (!C::cmp(lower_bound_3, threshold)) {
|
|
1273
|
-
exact_distances[next_batch_size] = new_exact_3;
|
|
1274
|
-
index_array[next_batch_size] = idx_3;
|
|
1275
|
-
next_batch_size += 1;
|
|
1505
|
+
for (size_t i = 0; i < batch_size; i++) {
|
|
1506
|
+
float ne = exact_distances[i] - 2.0f * dot_buffer[i];
|
|
1507
|
+
float cum = cum_base
|
|
1508
|
+
[static_cast<size_t>(index_array[i]) * cum_stride +
|
|
1509
|
+
cs_level_idx];
|
|
1510
|
+
float lb = ne - two_qc * cum;
|
|
1511
|
+
if (lb <= threshold) {
|
|
1512
|
+
exact_distances[next_batch_size] = ne;
|
|
1513
|
+
index_array[next_batch_size] = index_array[i];
|
|
1514
|
+
next_batch_size++;
|
|
1276
1515
|
} else {
|
|
1277
|
-
candidates.push(
|
|
1516
|
+
candidates.push(index_array[i], ne);
|
|
1278
1517
|
}
|
|
1279
1518
|
}
|
|
1280
1519
|
|
|
1281
|
-
|
|
1282
|
-
for (; i < batch_size; i++) {
|
|
1283
|
-
idx_t idx = index_array[i];
|
|
1284
|
-
|
|
1285
|
-
float dp = flat_codes_qdis->partial_dot_product(
|
|
1286
|
-
idx, start_dim, end_dim - start_dim);
|
|
1287
|
-
ndis += 1;
|
|
1288
|
-
float new_exact = exact_distances[i] - 2.0f * dp;
|
|
1289
|
-
|
|
1290
|
-
float cum_sum = panorama_index->get_cum_sum(
|
|
1291
|
-
idx)[curr_panorama_level + 1];
|
|
1292
|
-
float cs_bound = 2.0f * cum_sum * query_cum_norm;
|
|
1293
|
-
float lower_bound = new_exact - cs_bound;
|
|
1294
|
-
|
|
1295
|
-
if (!C::cmp(lower_bound, threshold)) {
|
|
1296
|
-
exact_distances[next_batch_size] = new_exact;
|
|
1297
|
-
index_array[next_batch_size] = idx;
|
|
1298
|
-
next_batch_size += 1;
|
|
1299
|
-
} else {
|
|
1300
|
-
candidates.push(idx, new_exact);
|
|
1301
|
-
}
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
local_pano_stats.total_dims_scanned +=
|
|
1305
|
-
batch_size * (end_dim - start_dim);
|
|
1520
|
+
local_pano_stats.total_dims_scanned += batch_size * dim_span;
|
|
1306
1521
|
batch_size = next_batch_size;
|
|
1307
1522
|
curr_panorama_level++;
|
|
1308
1523
|
}
|
|
@@ -1317,7 +1532,7 @@ int hnsw_detail::search_from_candidates_panorama(
|
|
|
1317
1532
|
candidates.push(idx, exact_distances[i]);
|
|
1318
1533
|
}
|
|
1319
1534
|
|
|
1320
|
-
nstep
|
|
1535
|
+
nstep += static_cast<int>(k_popped);
|
|
1321
1536
|
if (!do_dis_check && nstep > efSearch) {
|
|
1322
1537
|
break;
|
|
1323
1538
|
}
|
|
@@ -1534,37 +1749,49 @@ HNSWStats search_impl(
|
|
|
1534
1749
|
}
|
|
1535
1750
|
|
|
1536
1751
|
int ef = std::max(cur_efSearch, k);
|
|
1537
|
-
if (bounded_queue) {
|
|
1538
|
-
// support Panorama search in this branch
|
|
1752
|
+
if (bounded_queue) {
|
|
1539
1753
|
MinimaxHeapT<HC_for<C>> candidates(ef);
|
|
1540
1754
|
|
|
1541
1755
|
candidates.push(nearest, d_nearest);
|
|
1542
1756
|
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
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");
|
|
1566
1786
|
}
|
|
1567
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");
|
|
1568
1795
|
auto call = [&]<typename VTType>(VTType& vt_concrete) {
|
|
1569
1796
|
return search_from_candidate_unbounded_fixVT<VTType, C>(
|
|
1570
1797
|
hnsw,
|
|
@@ -1622,6 +1849,50 @@ void search_level_0_impl(
|
|
|
1622
1849
|
|
|
1623
1850
|
int k = extract_k_from_ResultHandler<C>(res);
|
|
1624
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
|
+
|
|
1625
1896
|
if (search_type == 1) {
|
|
1626
1897
|
int nres = 0;
|
|
1627
1898
|
|
|
@@ -1641,16 +1912,7 @@ void search_level_0_impl(
|
|
|
1641
1912
|
|
|
1642
1913
|
candidates.push(cj, nearest_d[j]);
|
|
1643
1914
|
|
|
1644
|
-
nres =
|
|
1645
|
-
hnsw,
|
|
1646
|
-
qdis,
|
|
1647
|
-
res,
|
|
1648
|
-
candidates,
|
|
1649
|
-
vt,
|
|
1650
|
-
search_stats,
|
|
1651
|
-
0,
|
|
1652
|
-
nres,
|
|
1653
|
-
params);
|
|
1915
|
+
nres = search_candidates(candidates, nres);
|
|
1654
1916
|
nres = std::min(nres, candidates_size);
|
|
1655
1917
|
}
|
|
1656
1918
|
} else if (search_type == 2) {
|
|
@@ -1667,8 +1929,7 @@ void search_level_0_impl(
|
|
|
1667
1929
|
candidates.push(cj, nearest_d[j]);
|
|
1668
1930
|
}
|
|
1669
1931
|
|
|
1670
|
-
|
|
1671
|
-
hnsw, qdis, res, candidates, vt, search_stats, 0, 0, params);
|
|
1932
|
+
search_candidates(candidates, 0);
|
|
1672
1933
|
}
|
|
1673
1934
|
}
|
|
1674
1935
|
|