faiss 0.6.1 → 0.6.3
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/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 +1 -2
- data/vendor/faiss/faiss/IndexBinaryHNSW.cpp +10 -12
- data/vendor/faiss/faiss/IndexBinaryHash.cpp +5 -9
- data/vendor/faiss/faiss/IndexBinaryIVF.cpp +5 -7
- 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 +13 -50
- data/vendor/faiss/faiss/IndexHNSW.cpp +177 -148
- data/vendor/faiss/faiss/IndexIDMap.cpp +16 -3
- data/vendor/faiss/faiss/IndexIDMap.h +2 -0
- data/vendor/faiss/faiss/IndexIVF.cpp +19 -8
- data/vendor/faiss/faiss/IndexIVFAdditiveQuantizer.cpp +3 -3
- 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 +6 -5
- data/vendor/faiss/faiss/IndexIVFFlatPanorama.cpp +3 -3
- data/vendor/faiss/faiss/IndexIVFIndependentQuantizer.cpp +1 -1
- data/vendor/faiss/faiss/IndexIVFPQ.cpp +42 -25
- data/vendor/faiss/faiss/IndexIVFPQFastScan.cpp +0 -1
- data/vendor/faiss/faiss/IndexIVFPQR.cpp +2 -3
- data/vendor/faiss/faiss/IndexIVFRaBitQ.cpp +23 -62
- 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 +10 -3
- data/vendor/faiss/faiss/IndexNSG.cpp +8 -4
- 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/IndexScalarQuantizer.cpp +68 -6
- data/vendor/faiss/faiss/IndexScalarQuantizer.h +10 -0
- data/vendor/faiss/faiss/IndexShards.cpp +2 -2
- data/vendor/faiss/faiss/IndexShardsIVF.cpp +2 -2
- data/vendor/faiss/faiss/MetaIndexes.cpp +2 -4
- data/vendor/faiss/faiss/SuperKMeans.cpp +256 -240
- data/vendor/faiss/faiss/SuperKMeans.h +30 -0
- data/vendor/faiss/faiss/VectorTransform.cpp +33 -2
- data/vendor/faiss/faiss/clone_index.cpp +5 -0
- data/vendor/faiss/faiss/cppcontrib/SaDecodeKernels.h +1 -1
- data/vendor/faiss/faiss/cppcontrib/sa_decode/Level2-neon-inl.h +902 -12
- data/vendor/faiss/faiss/cppcontrib/sa_decode/PQ-neon-inl.h +702 -10
- data/vendor/faiss/faiss/factory_tools.cpp +51 -4
- data/vendor/faiss/faiss/gpu/GpuCloner.cpp +11 -11
- data/vendor/faiss/faiss/gpu/GpuIndex.h +34 -11
- data/vendor/faiss/faiss/gpu/GpuIndexCagra.h +47 -0
- data/vendor/faiss/faiss/gpu/GpuIndexIVF.h +17 -0
- data/vendor/faiss/faiss/gpu/GpuIndexIVFScalarQuantizer.h +16 -0
- data/vendor/faiss/faiss/gpu/GpuResources.h +3 -2
- data/vendor/faiss/faiss/gpu/StandardGpuResources.cpp +11 -12
- data/vendor/faiss/faiss/gpu/StandardGpuResources.h +3 -3
- 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/MetalDistance.h +87 -0
- data/vendor/faiss/faiss/gpu_metal/MetalIndex.h +7 -0
- data/vendor/faiss/faiss/gpu_metal/MetalIndexIVFFlat.h +177 -0
- data/vendor/faiss/faiss/gpu_metal/MetalIndexIVFPQ.h +88 -0
- data/vendor/faiss/faiss/gpu_metal/MetalKernels.h +48 -3
- data/vendor/faiss/faiss/gpu_metal/MetalPythonBridge.h +45 -0
- data/vendor/faiss/faiss/gpu_metal/impl/MetalIVFFlat.h +193 -0
- data/vendor/faiss/faiss/gpu_metal/impl/MetalIVFPQ.h +134 -0
- data/vendor/faiss/faiss/impl/ClusteringInitialization.cpp +2 -2
- 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 +658 -344
- data/vendor/faiss/faiss/impl/HNSW.h +51 -13
- data/vendor/faiss/faiss/impl/LocalSearchQuantizer.cpp +2 -2
- data/vendor/faiss/faiss/impl/NSG.cpp +18 -12
- data/vendor/faiss/faiss/impl/Panorama.h +20 -7
- data/vendor/faiss/faiss/impl/PolysemousTraining.cpp +152 -84
- data/vendor/faiss/faiss/impl/ProductQuantizer.cpp +59 -24
- 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 +175 -68
- data/vendor/faiss/faiss/impl/RaBitQuantizer.h +19 -0
- data/vendor/faiss/faiss/impl/RaBitQuantizerMultiBit.cpp +2 -11
- data/vendor/faiss/faiss/impl/ResultHandler.h +26 -31
- data/vendor/faiss/faiss/impl/ScalarQuantizer.cpp +522 -58
- data/vendor/faiss/faiss/impl/ScalarQuantizer.h +70 -0
- data/vendor/faiss/faiss/impl/ThreadedIndex-inl.h +2 -2
- data/vendor/faiss/faiss/impl/VisitedTable.cpp +33 -13
- data/vendor/faiss/faiss/impl/VisitedTable.h +88 -33
- data/vendor/faiss/faiss/impl/binary_hamming/IndexBinaryIVF_impl.h +1 -1
- data/vendor/faiss/faiss/impl/binary_hamming/avx2.cpp +4 -4
- data/vendor/faiss/faiss/impl/fast_scan/dispatching.h +38 -3
- data/vendor/faiss/faiss/impl/hnsw/LockVector.cpp +1 -1
- data/vendor/faiss/faiss/impl/hnsw/MinimaxHeap.cpp +35 -43
- data/vendor/faiss/faiss/impl/hnsw/MinimaxHeap.h +64 -15
- data/vendor/faiss/faiss/impl/hnsw/avx2.cpp +86 -40
- data/vendor/faiss/faiss/impl/hnsw/avx512.cpp +81 -50
- data/vendor/faiss/faiss/impl/index_read.cpp +476 -75
- data/vendor/faiss/faiss/impl/index_write.cpp +56 -4
- data/vendor/faiss/faiss/impl/io_macros.h +25 -0
- data/vendor/faiss/faiss/impl/lattice_Zn.cpp +8 -9
- data/vendor/faiss/faiss/impl/platform_macros.h +15 -9
- 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/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 +6 -4
- data/vendor/faiss/faiss/impl/pq_code_distance/avx512.cpp +2 -0
- data/vendor/faiss/faiss/impl/pq_code_distance/neon.cpp +2 -0
- data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-generic.cpp +20 -0
- data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-inl.h +36 -0
- data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-sve.cpp +5 -0
- data/vendor/faiss/faiss/impl/pq_code_distance/pq_scan_impl.h +105 -0
- data/vendor/faiss/faiss/impl/pq_code_distance/rvv.cpp +2 -0
- 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 +882 -0
- data/vendor/faiss/faiss/impl/scalar_quantizer/distance_computers.h +6 -0
- data/vendor/faiss/faiss/impl/scalar_quantizer/quantizers.h +336 -26
- data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx2.cpp +331 -32
- data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512-impl.h +553 -0
- data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512-spr.cpp +558 -0
- data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512.cpp +284 -45
- data/vendor/faiss/faiss/impl/scalar_quantizer/sq-dispatch.h +502 -3
- data/vendor/faiss/faiss/impl/scalar_quantizer/sq-neon.cpp +157 -32
- data/vendor/faiss/faiss/impl/scalar_quantizer/sq-rvv.cpp +26 -0
- data/vendor/faiss/faiss/impl/simd_dispatch.h +86 -8
- data/vendor/faiss/faiss/index_factory.cpp +37 -7
- data/vendor/faiss/faiss/index_io.h +16 -0
- data/vendor/faiss/faiss/invlists/DirectMap.cpp +5 -2
- data/vendor/faiss/faiss/invlists/InvertedLists.cpp +15 -15
- data/vendor/faiss/faiss/invlists/InvertedLists.h +2 -2
- data/vendor/faiss/faiss/invlists/OnDiskInvertedLists.cpp +19 -4
- 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 +150 -23
- data/vendor/faiss/faiss/svs/IndexSVSVamana.h +30 -7
- data/vendor/faiss/faiss/svs/IndexSVSVamanaLVQ.cpp +3 -2
- data/vendor/faiss/faiss/svs/IndexSVSVamanaLVQ.h +2 -1
- data/vendor/faiss/faiss/svs/IndexSVSVamanaLeanVec.cpp +65 -25
- data/vendor/faiss/faiss/svs/IndexSVSVamanaLeanVec.h +3 -2
- data/vendor/faiss/faiss/utils/approx_topk_hamming/approx_topk_hamming.h +1 -1
- data/vendor/faiss/faiss/utils/bf16.h +34 -0
- data/vendor/faiss/faiss/utils/distances.cpp +14 -2
- data/vendor/faiss/faiss/utils/distances_simd.cpp +4 -4
- 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 +9 -9
- data/vendor/faiss/faiss/utils/hamming_distance/hamming_avx2.cpp +2 -1
- data/vendor/faiss/faiss/utils/hamming_distance/hamming_avx512_spr.cpp +15 -0
- data/vendor/faiss/faiss/utils/hamming_distance/hamming_computer-avx512.h +6 -30
- data/vendor/faiss/faiss/utils/hamming_distance/hamming_computer-avx512_spr.h +171 -0
- data/vendor/faiss/faiss/utils/partitioning.cpp +0 -2
- 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_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 +160 -18
- data/vendor/faiss/faiss/utils/simd_impl/partitioning_simdlib256.h +14 -68
- data/vendor/faiss/faiss/utils/simd_impl/rabitq_avx2.cpp +245 -0
- data/vendor/faiss/faiss/utils/simd_impl/rabitq_avx512.cpp +273 -0
- data/vendor/faiss/faiss/utils/simd_impl/rabitq_avx512_spr.cpp +435 -0
- 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_levels.cpp +56 -2
- data/vendor/faiss/faiss/utils/simd_levels.h +14 -0
- data/vendor/faiss/faiss/utils/utils.cpp +9 -27
- metadata +27 -2
|
@@ -7,9 +7,11 @@
|
|
|
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>
|
|
14
|
+
#include <type_traits>
|
|
13
15
|
|
|
14
16
|
#include <faiss/IndexHNSW.h>
|
|
15
17
|
|
|
@@ -171,17 +173,21 @@ void HNSW::print_neighbor_stats(int level) const {
|
|
|
171
173
|
}
|
|
172
174
|
|
|
173
175
|
void HNSW::fill_with_random_links(size_t n) {
|
|
174
|
-
|
|
176
|
+
if (n == 0) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
max_level = prepare_level_tab(n);
|
|
180
|
+
entry_point = 0;
|
|
181
|
+
|
|
175
182
|
RandomGenerator rng2(456);
|
|
176
183
|
|
|
177
|
-
for (int level =
|
|
184
|
+
for (int level = max_level - 1; level >= 0; --level) {
|
|
178
185
|
std::vector<int> elts;
|
|
179
186
|
for (size_t i = 0; i < n; i++) {
|
|
180
187
|
if (levels[i] > level) {
|
|
181
188
|
elts.push_back(i);
|
|
182
189
|
}
|
|
183
190
|
}
|
|
184
|
-
printf("linking %zd elements in level %d\n", elts.size(), level);
|
|
185
191
|
|
|
186
192
|
if (elts.size() == 1) {
|
|
187
193
|
continue;
|
|
@@ -233,28 +239,32 @@ int HNSW::prepare_level_tab(size_t n, bool preset_levels) {
|
|
|
233
239
|
* neighbor only if there is no previous neighbor that is closer to
|
|
234
240
|
* that vertex than the query.
|
|
235
241
|
*/
|
|
242
|
+
template <class Comp>
|
|
236
243
|
void HNSW::shrink_neighbor_list(
|
|
237
244
|
DistanceComputer& qdis,
|
|
238
|
-
std::priority_queue<
|
|
239
|
-
std::vector<
|
|
245
|
+
std::priority_queue<NodeDistFartherT<Comp>>& input,
|
|
246
|
+
std::vector<NodeDistFartherT<Comp>>& output,
|
|
240
247
|
size_t max_size,
|
|
241
248
|
bool keep_max_size_level0) {
|
|
242
249
|
// This prevents number of neighbors at
|
|
243
250
|
// level 0 from being shrunk to less than 2 * M.
|
|
244
251
|
// This is essential in making sure
|
|
245
252
|
// `faiss::gpu::GpuIndexCagra::copyFrom(IndexHNSWCagra*)` is functional
|
|
246
|
-
std::vector<
|
|
253
|
+
std::vector<NodeDistFartherT<Comp>> outsiders;
|
|
247
254
|
|
|
248
255
|
while (input.size() > 0) {
|
|
249
|
-
|
|
256
|
+
NodeDistFartherT<Comp> v1 = input.top();
|
|
250
257
|
input.pop();
|
|
251
258
|
float dist_v1_q = v1.d;
|
|
252
259
|
|
|
253
260
|
bool good = true;
|
|
254
|
-
for (
|
|
261
|
+
for (NodeDistFartherT<Comp> v2 : output) {
|
|
255
262
|
float dist_v1_v2 = qdis.symmetric_dis(v2.id, v1.id);
|
|
256
263
|
|
|
257
|
-
if
|
|
264
|
+
// "v1 is bad" if some previously-kept neighbor v2 is closer
|
|
265
|
+
// (more similar, under CMin) to v1 than the query is. Encoded
|
|
266
|
+
// generically as: v1v2 is "better than" v1q under Comp.
|
|
267
|
+
if (Comp::cmp(dist_v1_q, dist_v1_v2)) {
|
|
258
268
|
good = false;
|
|
259
269
|
break;
|
|
260
270
|
}
|
|
@@ -277,44 +287,88 @@ void HNSW::shrink_neighbor_list(
|
|
|
277
287
|
}
|
|
278
288
|
}
|
|
279
289
|
|
|
290
|
+
// Explicit instantiations for the two supported comparators.
|
|
291
|
+
template void HNSW::shrink_neighbor_list<HNSW::C_distance>(
|
|
292
|
+
DistanceComputer&,
|
|
293
|
+
std::priority_queue<HNSW::NodeDistFartherT<HNSW::C_distance>>&,
|
|
294
|
+
std::vector<HNSW::NodeDistFartherT<HNSW::C_distance>>&,
|
|
295
|
+
size_t,
|
|
296
|
+
bool);
|
|
297
|
+
template void HNSW::shrink_neighbor_list<HNSW::C_similarity>(
|
|
298
|
+
DistanceComputer&,
|
|
299
|
+
std::priority_queue<HNSW::NodeDistFartherT<HNSW::C_similarity>>&,
|
|
300
|
+
std::vector<HNSW::NodeDistFartherT<HNSW::C_similarity>>&,
|
|
301
|
+
size_t,
|
|
302
|
+
bool);
|
|
303
|
+
|
|
280
304
|
namespace {
|
|
281
305
|
|
|
282
306
|
using storage_idx_t = HNSW::storage_idx_t;
|
|
283
|
-
|
|
284
|
-
|
|
307
|
+
|
|
308
|
+
// Map a (high-level) HNSW comparator C — which uses int64_t IDs — to the
|
|
309
|
+
// (low-level) MinimaxHeap comparator HC, which uses int32_t IDs.
|
|
310
|
+
template <class C>
|
|
311
|
+
using HC_for = std::
|
|
312
|
+
conditional_t<C::is_max, CMax<float, int32_t>, CMin<float, int32_t>>;
|
|
313
|
+
|
|
314
|
+
// Priority queue types used by the unbounded search variant. For CMax
|
|
315
|
+
// (distance) "top_candidates" is a max-heap of the kept-so-far results
|
|
316
|
+
// (top is the farthest) and "candidates" is a min-heap of the next nodes
|
|
317
|
+
// to explore (top is the closest). For CMin (similarity) the orderings are
|
|
318
|
+
// swapped: top_candidates is a min-heap (top is the least similar) and
|
|
319
|
+
// candidates is a max-heap (top is the most similar).
|
|
320
|
+
template <class C>
|
|
321
|
+
using TopCandidatesQueue = std::conditional_t<
|
|
322
|
+
C::is_max,
|
|
323
|
+
std::priority_queue<HNSW::Node>,
|
|
324
|
+
std::priority_queue<
|
|
325
|
+
HNSW::Node,
|
|
326
|
+
std::vector<HNSW::Node>,
|
|
327
|
+
std::greater<HNSW::Node>>>;
|
|
328
|
+
|
|
329
|
+
template <class C>
|
|
330
|
+
using CandidatesQueue = std::conditional_t<
|
|
331
|
+
C::is_max,
|
|
332
|
+
std::priority_queue<
|
|
333
|
+
HNSW::Node,
|
|
334
|
+
std::vector<HNSW::Node>,
|
|
335
|
+
std::greater<HNSW::Node>>,
|
|
336
|
+
std::priority_queue<HNSW::Node>>;
|
|
285
337
|
|
|
286
338
|
/**************************************************************
|
|
287
339
|
* Addition subroutines
|
|
288
340
|
**************************************************************/
|
|
289
341
|
|
|
290
342
|
/// remove neighbors from the list to make it smaller than max_size
|
|
291
|
-
|
|
343
|
+
template <class C>
|
|
344
|
+
void shrink_neighbor_list_inner(
|
|
292
345
|
DistanceComputer& qdis,
|
|
293
|
-
std::priority_queue<
|
|
346
|
+
std::priority_queue<HNSW::NodeDistCloserT<C>>& resultSet1,
|
|
294
347
|
size_t max_size,
|
|
295
348
|
bool keep_max_size_level0 = false) {
|
|
296
349
|
if (resultSet1.size() < static_cast<size_t>(max_size)) {
|
|
297
350
|
return;
|
|
298
351
|
}
|
|
299
|
-
std::priority_queue<
|
|
300
|
-
std::vector<
|
|
352
|
+
std::priority_queue<HNSW::NodeDistFartherT<C>> resultSet;
|
|
353
|
+
std::vector<HNSW::NodeDistFartherT<C>> returnlist;
|
|
301
354
|
|
|
302
355
|
while (resultSet1.size() > 0) {
|
|
303
356
|
resultSet.emplace(resultSet1.top().d, resultSet1.top().id);
|
|
304
357
|
resultSet1.pop();
|
|
305
358
|
}
|
|
306
359
|
|
|
307
|
-
HNSW::shrink_neighbor_list(
|
|
360
|
+
HNSW::shrink_neighbor_list<C>(
|
|
308
361
|
qdis, resultSet, returnlist, max_size, keep_max_size_level0);
|
|
309
362
|
|
|
310
|
-
for (
|
|
363
|
+
for (HNSW::NodeDistFartherT<C> curen2 : returnlist) {
|
|
311
364
|
resultSet1.emplace(curen2.d, curen2.id);
|
|
312
365
|
}
|
|
313
366
|
}
|
|
314
367
|
|
|
315
368
|
/// add a link between two elements, possibly shrinking the list
|
|
316
369
|
/// of links to make room for it.
|
|
317
|
-
|
|
370
|
+
template <class C>
|
|
371
|
+
void add_link_tpl(
|
|
318
372
|
HNSW& hnsw,
|
|
319
373
|
DistanceComputer& qdis,
|
|
320
374
|
storage_idx_t src,
|
|
@@ -339,16 +393,17 @@ void add_link(
|
|
|
339
393
|
// otherwise we let them fight out which to keep
|
|
340
394
|
|
|
341
395
|
// copy to resultSet...
|
|
342
|
-
std::priority_queue<
|
|
396
|
+
std::priority_queue<HNSW::NodeDistCloserT<C>> resultSet;
|
|
343
397
|
resultSet.emplace(qdis.symmetric_dis(src, dest), dest);
|
|
344
|
-
for (size_t i = begin; i < end; i++) {
|
|
398
|
+
for (size_t i = begin; i < end; i++) {
|
|
345
399
|
storage_idx_t neigh = hnsw.neighbors[i];
|
|
346
400
|
resultSet.emplace(qdis.symmetric_dis(src, neigh), neigh);
|
|
347
401
|
}
|
|
348
402
|
|
|
349
403
|
size_t max_size = end - begin;
|
|
350
404
|
max_size -= max_size * std::clamp(hnsw.prune_headroom, 0.0f, 0.5f);
|
|
351
|
-
|
|
405
|
+
shrink_neighbor_list_inner<C>(
|
|
406
|
+
qdis, resultSet, max_size, keep_max_size_level0);
|
|
352
407
|
|
|
353
408
|
// ...and back
|
|
354
409
|
size_t i = begin;
|
|
@@ -362,31 +417,33 @@ void add_link(
|
|
|
362
417
|
}
|
|
363
418
|
}
|
|
364
419
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
420
|
+
/** Templated body of `search_neighbors_to_add` — instantiated once per final
|
|
421
|
+
* VisitedTable subclass × comparator so that `vt.set/advance` are inlined
|
|
422
|
+
* and the cost of virtual dispatch is paid only once at the top of the call.
|
|
423
|
+
*/
|
|
424
|
+
template <typename VTType, class C>
|
|
425
|
+
static void search_neighbors_to_add_fixVT(
|
|
369
426
|
HNSW& hnsw,
|
|
370
427
|
DistanceComputer& qdis,
|
|
371
|
-
std::priority_queue<
|
|
428
|
+
std::priority_queue<HNSW::NodeDistCloserT<C>>& results,
|
|
372
429
|
int entry_point,
|
|
373
430
|
float d_entry_point,
|
|
374
431
|
int level,
|
|
375
|
-
|
|
432
|
+
VTType& vt,
|
|
376
433
|
bool reference_version) {
|
|
377
434
|
// top is nearest candidate
|
|
378
|
-
std::priority_queue<
|
|
435
|
+
std::priority_queue<HNSW::NodeDistFartherT<C>> candidates;
|
|
379
436
|
|
|
380
|
-
|
|
437
|
+
HNSW::NodeDistFartherT<C> ev(d_entry_point, entry_point);
|
|
381
438
|
candidates.push(ev);
|
|
382
439
|
results.emplace(d_entry_point, entry_point);
|
|
383
440
|
vt.set(entry_point);
|
|
384
441
|
|
|
385
442
|
while (!candidates.empty()) {
|
|
386
443
|
// get nearest
|
|
387
|
-
const
|
|
444
|
+
const HNSW::NodeDistFartherT<C>& currEv = candidates.top();
|
|
388
445
|
|
|
389
|
-
if (currEv.d
|
|
446
|
+
if (C::cmp(currEv.d, results.top().d)) {
|
|
390
447
|
break;
|
|
391
448
|
}
|
|
392
449
|
int currNode = currEv.id;
|
|
@@ -407,7 +464,7 @@ void search_neighbors_to_add(
|
|
|
407
464
|
if (reference_version) {
|
|
408
465
|
// a reference version
|
|
409
466
|
for (size_t i = begin; i < end; i++) {
|
|
410
|
-
storage_idx_t nodeId = hnsw.neighbors[i];
|
|
467
|
+
HNSW::storage_idx_t nodeId = hnsw.neighbors[i];
|
|
411
468
|
if (nodeId < 0) {
|
|
412
469
|
break;
|
|
413
470
|
}
|
|
@@ -416,10 +473,10 @@ void search_neighbors_to_add(
|
|
|
416
473
|
}
|
|
417
474
|
|
|
418
475
|
float dis = qdis(nodeId);
|
|
419
|
-
|
|
476
|
+
HNSW::NodeDistFartherT<C> evE1(dis, nodeId);
|
|
420
477
|
|
|
421
478
|
if (results.size() < static_cast<size_t>(hnsw.efConstruction) ||
|
|
422
|
-
results.top().d
|
|
479
|
+
C::cmp(results.top().d, dis)) {
|
|
423
480
|
results.emplace(dis, nodeId);
|
|
424
481
|
candidates.emplace(dis, nodeId);
|
|
425
482
|
if (results.size() >
|
|
@@ -432,10 +489,10 @@ void search_neighbors_to_add(
|
|
|
432
489
|
// a faster version
|
|
433
490
|
|
|
434
491
|
// the following version processes 4 neighbors at a time
|
|
435
|
-
auto update_with_candidate = [&](const storage_idx_t idx,
|
|
492
|
+
auto update_with_candidate = [&](const HNSW::storage_idx_t idx,
|
|
436
493
|
const float dis) {
|
|
437
494
|
if (results.size() < static_cast<size_t>(hnsw.efConstruction) ||
|
|
438
|
-
results.top().d
|
|
495
|
+
C::cmp(results.top().d, dis)) {
|
|
439
496
|
results.emplace(dis, idx);
|
|
440
497
|
candidates.emplace(dis, idx);
|
|
441
498
|
if (results.size() >
|
|
@@ -446,10 +503,10 @@ void search_neighbors_to_add(
|
|
|
446
503
|
};
|
|
447
504
|
|
|
448
505
|
int n_buffered = 0;
|
|
449
|
-
storage_idx_t buffered_ids[4];
|
|
506
|
+
HNSW::storage_idx_t buffered_ids[4];
|
|
450
507
|
|
|
451
508
|
for (size_t j = begin; j < end; j++) {
|
|
452
|
-
storage_idx_t nodeId = hnsw.neighbors[j];
|
|
509
|
+
HNSW::storage_idx_t nodeId = hnsw.neighbors[j];
|
|
453
510
|
if (nodeId < 0) {
|
|
454
511
|
break;
|
|
455
512
|
}
|
|
@@ -491,9 +548,41 @@ void search_neighbors_to_add(
|
|
|
491
548
|
vt.advance();
|
|
492
549
|
}
|
|
493
550
|
|
|
494
|
-
///
|
|
495
|
-
///
|
|
496
|
-
|
|
551
|
+
/// Dispatches the VisitedTable concrete type for a given C, then calls
|
|
552
|
+
/// the templated `search_neighbors_to_add_fixVT<VTType, C>`.
|
|
553
|
+
template <class C>
|
|
554
|
+
void search_neighbors_to_add_dispatch(
|
|
555
|
+
HNSW& hnsw,
|
|
556
|
+
DistanceComputer& qdis,
|
|
557
|
+
std::priority_queue<HNSW::NodeDistCloserT<C>>& results,
|
|
558
|
+
int entry_point,
|
|
559
|
+
float d_entry_point,
|
|
560
|
+
int level,
|
|
561
|
+
VisitedTable& vt,
|
|
562
|
+
bool reference_version) {
|
|
563
|
+
auto call = [&]<typename VTType>(VTType& vt_concrete) {
|
|
564
|
+
search_neighbors_to_add_fixVT<VTType, C>(
|
|
565
|
+
hnsw,
|
|
566
|
+
qdis,
|
|
567
|
+
results,
|
|
568
|
+
entry_point,
|
|
569
|
+
d_entry_point,
|
|
570
|
+
level,
|
|
571
|
+
vt_concrete,
|
|
572
|
+
reference_version);
|
|
573
|
+
};
|
|
574
|
+
if (VisitedTableVector* vtv = dynamic_cast<VisitedTableVector*>(&vt)) {
|
|
575
|
+
call(*vtv);
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
VisitedTableSet& vts = dynamic_cast<VisitedTableSet&>(vt);
|
|
579
|
+
call(vts);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/// Templated implementation of `HNSW::add_links_starting_from`.
|
|
583
|
+
template <class C>
|
|
584
|
+
void add_links_starting_from_impl(
|
|
585
|
+
HNSW& hnsw,
|
|
497
586
|
DistanceComputer& ptdis,
|
|
498
587
|
storage_idx_t pt_id,
|
|
499
588
|
storage_idx_t nearest,
|
|
@@ -502,21 +591,22 @@ void HNSW::add_links_starting_from(
|
|
|
502
591
|
LockVector& locks,
|
|
503
592
|
VisitedTable& vt,
|
|
504
593
|
bool keep_max_size_level0) {
|
|
505
|
-
std::priority_queue<
|
|
594
|
+
std::priority_queue<HNSW::NodeDistCloserT<C>> link_targets;
|
|
506
595
|
|
|
507
|
-
|
|
508
|
-
|
|
596
|
+
search_neighbors_to_add_dispatch<C>(
|
|
597
|
+
hnsw, ptdis, link_targets, nearest, d_nearest, level, vt, false);
|
|
509
598
|
|
|
510
599
|
// but we can afford only this many neighbors
|
|
511
|
-
int M = nb_neighbors(level);
|
|
600
|
+
int M = hnsw.nb_neighbors(level);
|
|
512
601
|
|
|
513
|
-
|
|
602
|
+
shrink_neighbor_list_inner<C>(ptdis, link_targets, M, keep_max_size_level0);
|
|
514
603
|
|
|
515
604
|
std::vector<storage_idx_t> neighbors_to_add;
|
|
516
605
|
neighbors_to_add.reserve(link_targets.size());
|
|
517
606
|
while (!link_targets.empty()) {
|
|
518
607
|
storage_idx_t other_id = link_targets.top().id;
|
|
519
|
-
|
|
608
|
+
add_link_tpl<C>(
|
|
609
|
+
hnsw, ptdis, pt_id, other_id, level, keep_max_size_level0);
|
|
520
610
|
neighbors_to_add.push_back(other_id);
|
|
521
611
|
link_targets.pop();
|
|
522
612
|
}
|
|
@@ -524,33 +614,197 @@ void HNSW::add_links_starting_from(
|
|
|
524
614
|
locks.unlock(pt_id);
|
|
525
615
|
for (storage_idx_t other_id : neighbors_to_add) {
|
|
526
616
|
locks.lock(other_id);
|
|
527
|
-
|
|
617
|
+
add_link_tpl<C>(
|
|
618
|
+
hnsw, ptdis, other_id, pt_id, level, keep_max_size_level0);
|
|
528
619
|
locks.unlock(other_id);
|
|
529
620
|
}
|
|
530
621
|
locks.lock(pt_id);
|
|
531
622
|
}
|
|
532
623
|
|
|
624
|
+
} // namespace
|
|
625
|
+
|
|
626
|
+
/// Finds neighbors and builds links with them, starting from an entry
|
|
627
|
+
/// point. The own neighbor list is assumed to be locked.
|
|
628
|
+
void HNSW::add_links_starting_from(
|
|
629
|
+
DistanceComputer& ptdis,
|
|
630
|
+
storage_idx_t pt_id,
|
|
631
|
+
storage_idx_t nearest,
|
|
632
|
+
float d_nearest,
|
|
633
|
+
int level,
|
|
634
|
+
LockVector& locks,
|
|
635
|
+
VisitedTable& vt,
|
|
636
|
+
bool keep_max_size_level0) {
|
|
637
|
+
if (is_similarity) {
|
|
638
|
+
add_links_starting_from_impl<C_similarity>(
|
|
639
|
+
*this,
|
|
640
|
+
ptdis,
|
|
641
|
+
pt_id,
|
|
642
|
+
nearest,
|
|
643
|
+
d_nearest,
|
|
644
|
+
level,
|
|
645
|
+
locks,
|
|
646
|
+
vt,
|
|
647
|
+
keep_max_size_level0);
|
|
648
|
+
} else {
|
|
649
|
+
add_links_starting_from_impl<C_distance>(
|
|
650
|
+
*this,
|
|
651
|
+
ptdis,
|
|
652
|
+
pt_id,
|
|
653
|
+
nearest,
|
|
654
|
+
d_nearest,
|
|
655
|
+
level,
|
|
656
|
+
locks,
|
|
657
|
+
vt,
|
|
658
|
+
keep_max_size_level0);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/// search neighbors on a single level, starting from an entry point.
|
|
663
|
+
/// Public dispatcher: always operates in distance (CMax) mode because its
|
|
664
|
+
/// `priority_queue<HNSW::NodeDistCloser>` signature is the back-compat
|
|
665
|
+
/// distance flavor. Internal callers that need similarity mode reach the
|
|
666
|
+
/// templated implementation directly via `search_neighbors_to_add_dispatch`.
|
|
667
|
+
void hnsw_detail::search_neighbors_to_add(
|
|
668
|
+
HNSW& hnsw,
|
|
669
|
+
DistanceComputer& qdis,
|
|
670
|
+
std::priority_queue<HNSW::NodeDistCloser>& results,
|
|
671
|
+
int entry_point,
|
|
672
|
+
float d_entry_point,
|
|
673
|
+
int level,
|
|
674
|
+
VisitedTable& vt,
|
|
675
|
+
bool reference_version) {
|
|
676
|
+
search_neighbors_to_add_dispatch<HNSW::C_distance>(
|
|
677
|
+
hnsw,
|
|
678
|
+
qdis,
|
|
679
|
+
results,
|
|
680
|
+
entry_point,
|
|
681
|
+
d_entry_point,
|
|
682
|
+
level,
|
|
683
|
+
vt,
|
|
684
|
+
reference_version);
|
|
685
|
+
}
|
|
686
|
+
|
|
533
687
|
/**************************************************************
|
|
534
688
|
* Building, parallel
|
|
535
689
|
**************************************************************/
|
|
536
690
|
|
|
537
|
-
|
|
691
|
+
namespace {
|
|
692
|
+
|
|
693
|
+
/// Greedy update of the nearest entry point at a given level.
|
|
694
|
+
template <class C>
|
|
695
|
+
HNSWStats greedy_update_nearest_impl(
|
|
696
|
+
const HNSW& hnsw,
|
|
697
|
+
DistanceComputer& qdis,
|
|
698
|
+
int level,
|
|
699
|
+
storage_idx_t& nearest,
|
|
700
|
+
float& d_nearest) {
|
|
701
|
+
HNSWStats stats;
|
|
702
|
+
|
|
703
|
+
for (;;) {
|
|
704
|
+
storage_idx_t prev_nearest = nearest;
|
|
705
|
+
|
|
706
|
+
size_t begin, end;
|
|
707
|
+
hnsw.neighbor_range(nearest, level, &begin, &end);
|
|
708
|
+
|
|
709
|
+
size_t ndis = 0;
|
|
710
|
+
|
|
711
|
+
// a faster version: reference version in unit test test_hnsw.cpp
|
|
712
|
+
// the following version processes 4 neighbors at a time
|
|
713
|
+
auto update_with_candidate = [&](const storage_idx_t idx,
|
|
714
|
+
const float dis) {
|
|
715
|
+
if (C::cmp(d_nearest, dis)) {
|
|
716
|
+
nearest = idx;
|
|
717
|
+
d_nearest = dis;
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
int n_buffered = 0;
|
|
722
|
+
storage_idx_t buffered_ids[4];
|
|
723
|
+
|
|
724
|
+
for (size_t j = begin; j < end; j++) {
|
|
725
|
+
storage_idx_t v = hnsw.neighbors[j];
|
|
726
|
+
if (v < 0) {
|
|
727
|
+
break;
|
|
728
|
+
}
|
|
729
|
+
ndis += 1;
|
|
730
|
+
|
|
731
|
+
buffered_ids[n_buffered] = v;
|
|
732
|
+
n_buffered += 1;
|
|
733
|
+
|
|
734
|
+
if (n_buffered == 4) {
|
|
735
|
+
float dis[4];
|
|
736
|
+
qdis.distances_batch_4(
|
|
737
|
+
buffered_ids[0],
|
|
738
|
+
buffered_ids[1],
|
|
739
|
+
buffered_ids[2],
|
|
740
|
+
buffered_ids[3],
|
|
741
|
+
dis[0],
|
|
742
|
+
dis[1],
|
|
743
|
+
dis[2],
|
|
744
|
+
dis[3]);
|
|
745
|
+
|
|
746
|
+
for (size_t id4 = 0; id4 < 4; id4++) {
|
|
747
|
+
update_with_candidate(buffered_ids[id4], dis[id4]);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
n_buffered = 0;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// process leftovers
|
|
755
|
+
for (int icnt = 0; icnt < n_buffered; icnt++) {
|
|
756
|
+
float dis = qdis(buffered_ids[icnt]);
|
|
757
|
+
update_with_candidate(buffered_ids[icnt], dis);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// update stats
|
|
761
|
+
stats.ndis += ndis;
|
|
762
|
+
stats.nhops += 1;
|
|
763
|
+
|
|
764
|
+
if (nearest == prev_nearest) {
|
|
765
|
+
return stats;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
} // namespace
|
|
771
|
+
|
|
772
|
+
/// greedily update a nearest vector at a given level
|
|
773
|
+
HNSWStats hnsw_detail::greedy_update_nearest(
|
|
774
|
+
const HNSW& hnsw,
|
|
775
|
+
DistanceComputer& qdis,
|
|
776
|
+
int level,
|
|
777
|
+
storage_idx_t& nearest,
|
|
778
|
+
float& d_nearest) {
|
|
779
|
+
if (hnsw.is_similarity) {
|
|
780
|
+
return greedy_update_nearest_impl<HNSW::C_similarity>(
|
|
781
|
+
hnsw, qdis, level, nearest, d_nearest);
|
|
782
|
+
}
|
|
783
|
+
return greedy_update_nearest_impl<HNSW::C_distance>(
|
|
784
|
+
hnsw, qdis, level, nearest, d_nearest);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
namespace {
|
|
788
|
+
|
|
789
|
+
template <class C>
|
|
790
|
+
void add_with_locks_impl(
|
|
791
|
+
HNSW& hnsw,
|
|
538
792
|
DistanceComputer& ptdis,
|
|
539
793
|
int pt_level,
|
|
540
794
|
int pt_id,
|
|
541
795
|
LockVector& locks,
|
|
542
796
|
VisitedTable& vt,
|
|
543
797
|
bool keep_max_size_level0) {
|
|
544
|
-
storage_idx_t nearest = entry_point;
|
|
798
|
+
storage_idx_t nearest = hnsw.entry_point;
|
|
545
799
|
if (nearest == -1) { // avoid locking after the first point.
|
|
546
800
|
#pragma omp critical
|
|
547
|
-
if (entry_point == -1) { // double-check under lock.
|
|
548
|
-
max_level = pt_level;
|
|
549
|
-
entry_point = pt_id;
|
|
801
|
+
if (hnsw.entry_point == -1) { // double-check under lock.
|
|
802
|
+
hnsw.max_level = pt_level;
|
|
803
|
+
hnsw.entry_point = pt_id;
|
|
550
804
|
// leave nearest = -1 to trigger early exit after critical block.
|
|
551
805
|
} else {
|
|
552
806
|
// else: Another thread set the entry point.
|
|
553
|
-
nearest = entry_point;
|
|
807
|
+
nearest = hnsw.entry_point;
|
|
554
808
|
}
|
|
555
809
|
}
|
|
556
810
|
|
|
@@ -560,16 +814,17 @@ void HNSW::add_with_locks(
|
|
|
560
814
|
|
|
561
815
|
locks.lock(pt_id);
|
|
562
816
|
|
|
563
|
-
int level = max_level; // level at which we start adding neighbors
|
|
817
|
+
int level = hnsw.max_level; // level at which we start adding neighbors
|
|
564
818
|
float d_nearest = ptdis(nearest);
|
|
565
819
|
|
|
566
820
|
// greedy search on upper levels
|
|
567
821
|
for (; level > pt_level; level--) {
|
|
568
|
-
|
|
822
|
+
greedy_update_nearest_impl<C>(hnsw, ptdis, level, nearest, d_nearest);
|
|
569
823
|
}
|
|
570
824
|
|
|
571
825
|
for (; level >= 0; level--) {
|
|
572
|
-
|
|
826
|
+
add_links_starting_from_impl<C>(
|
|
827
|
+
hnsw,
|
|
573
828
|
ptdis,
|
|
574
829
|
pt_id,
|
|
575
830
|
nearest,
|
|
@@ -584,22 +839,39 @@ void HNSW::add_with_locks(
|
|
|
584
839
|
|
|
585
840
|
#pragma omp critical
|
|
586
841
|
{
|
|
587
|
-
if (pt_level > max_level) {
|
|
588
|
-
max_level = pt_level;
|
|
589
|
-
entry_point = pt_id;
|
|
842
|
+
if (pt_level > hnsw.max_level) {
|
|
843
|
+
hnsw.max_level = pt_level;
|
|
844
|
+
hnsw.entry_point = pt_id;
|
|
590
845
|
}
|
|
591
846
|
}
|
|
592
847
|
}
|
|
593
848
|
|
|
849
|
+
} // namespace
|
|
850
|
+
|
|
851
|
+
void HNSW::add_with_locks(
|
|
852
|
+
DistanceComputer& ptdis,
|
|
853
|
+
int pt_level,
|
|
854
|
+
int pt_id,
|
|
855
|
+
LockVector& locks,
|
|
856
|
+
VisitedTable& vt,
|
|
857
|
+
bool keep_max_size_level0) {
|
|
858
|
+
if (is_similarity) {
|
|
859
|
+
add_with_locks_impl<C_similarity>(
|
|
860
|
+
*this, ptdis, pt_level, pt_id, locks, vt, keep_max_size_level0);
|
|
861
|
+
} else {
|
|
862
|
+
add_with_locks_impl<C_distance>(
|
|
863
|
+
*this, ptdis, pt_level, pt_id, locks, vt, keep_max_size_level0);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
594
867
|
/**************************************************************
|
|
595
868
|
* Searching
|
|
596
869
|
**************************************************************/
|
|
597
870
|
|
|
598
|
-
|
|
599
|
-
using C = HNSW::C;
|
|
871
|
+
namespace {
|
|
600
872
|
|
|
601
873
|
/** Helper to extract search parameters from HNSW and SearchParameters */
|
|
602
|
-
|
|
874
|
+
inline void extract_search_params(
|
|
603
875
|
const HNSW& hnsw,
|
|
604
876
|
const SearchParameters* params,
|
|
605
877
|
bool& do_dis_check,
|
|
@@ -619,13 +891,16 @@ static inline void extract_search_params(
|
|
|
619
891
|
}
|
|
620
892
|
}
|
|
621
893
|
|
|
622
|
-
/**
|
|
623
|
-
|
|
894
|
+
/** Templated body of `search_from_candidates` — instantiated once per
|
|
895
|
+
* VisitedTable subclass × comparator.
|
|
896
|
+
*/
|
|
897
|
+
template <typename VTType, class C>
|
|
898
|
+
int search_from_candidates_fixVT(
|
|
624
899
|
const HNSW& hnsw,
|
|
625
900
|
DistanceComputer& qdis,
|
|
626
901
|
ResultHandler& res,
|
|
627
|
-
|
|
628
|
-
|
|
902
|
+
MinimaxHeapT<HC_for<C>>& candidates,
|
|
903
|
+
VTType& vt,
|
|
629
904
|
HNSWStats& stats,
|
|
630
905
|
int level,
|
|
631
906
|
int nres_in,
|
|
@@ -638,13 +913,15 @@ int search_from_candidates(
|
|
|
638
913
|
const IDSelector* sel;
|
|
639
914
|
extract_search_params(hnsw, params, do_dis_check, efSearch, sel);
|
|
640
915
|
|
|
641
|
-
|
|
916
|
+
vt.reserve(efSearch);
|
|
917
|
+
|
|
918
|
+
typename C::T threshold = res.threshold;
|
|
642
919
|
for (int i = 0; i < candidates.size(); i++) {
|
|
643
920
|
idx_t v1 = candidates.ids[i];
|
|
644
921
|
float d = candidates.dis[i];
|
|
645
922
|
FAISS_ASSERT(v1 >= 0);
|
|
646
923
|
if (!sel || sel->is_member(v1)) {
|
|
647
|
-
if (d
|
|
924
|
+
if (C::cmp(threshold, d)) {
|
|
648
925
|
if (res.add_result(d, v1)) {
|
|
649
926
|
threshold = res.threshold;
|
|
650
927
|
}
|
|
@@ -693,7 +970,7 @@ int search_from_candidates(
|
|
|
693
970
|
|
|
694
971
|
auto add_to_heap = [&](const size_t idx, const float dis) {
|
|
695
972
|
if (!sel || sel->is_member(idx)) {
|
|
696
|
-
if (dis
|
|
973
|
+
if (C::cmp(threshold, dis)) {
|
|
697
974
|
if (res.add_result(dis, idx)) {
|
|
698
975
|
threshold = res.threshold;
|
|
699
976
|
nres += 1;
|
|
@@ -756,7 +1033,58 @@ int search_from_candidates(
|
|
|
756
1033
|
return nres;
|
|
757
1034
|
}
|
|
758
1035
|
|
|
759
|
-
|
|
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(
|
|
1040
|
+
const HNSW& hnsw,
|
|
1041
|
+
DistanceComputer& qdis,
|
|
1042
|
+
ResultHandler& res,
|
|
1043
|
+
MinimaxHeapT<HC_for<C>>& candidates,
|
|
1044
|
+
VisitedTable& vt,
|
|
1045
|
+
HNSWStats& stats,
|
|
1046
|
+
int level,
|
|
1047
|
+
int nres_in,
|
|
1048
|
+
const SearchParameters* params) {
|
|
1049
|
+
auto call = [&]<typename VTType>(VTType& vt_concrete) -> int {
|
|
1050
|
+
return search_from_candidates_fixVT<VTType, C>(
|
|
1051
|
+
hnsw,
|
|
1052
|
+
qdis,
|
|
1053
|
+
res,
|
|
1054
|
+
candidates,
|
|
1055
|
+
vt_concrete,
|
|
1056
|
+
stats,
|
|
1057
|
+
level,
|
|
1058
|
+
nres_in,
|
|
1059
|
+
params);
|
|
1060
|
+
};
|
|
1061
|
+
if (VisitedTableVector* vtv = dynamic_cast<VisitedTableVector*>(&vt)) {
|
|
1062
|
+
return call(*vtv);
|
|
1063
|
+
}
|
|
1064
|
+
VisitedTableSet& vts = dynamic_cast<VisitedTableSet&>(vt);
|
|
1065
|
+
return call(vts);
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
} // namespace
|
|
1069
|
+
|
|
1070
|
+
/** Do a BFS on the candidates list. Public dispatcher: only handles the
|
|
1071
|
+
* distance (CMax) flavor because its `MinimaxHeap` parameter is the
|
|
1072
|
+
* CMax instantiation. */
|
|
1073
|
+
int hnsw_detail::search_from_candidates(
|
|
1074
|
+
const HNSW& hnsw,
|
|
1075
|
+
DistanceComputer& qdis,
|
|
1076
|
+
ResultHandler& res,
|
|
1077
|
+
MinimaxHeap& candidates,
|
|
1078
|
+
VisitedTable& vt,
|
|
1079
|
+
HNSWStats& stats,
|
|
1080
|
+
int level,
|
|
1081
|
+
int nres_in,
|
|
1082
|
+
const SearchParameters* params) {
|
|
1083
|
+
return search_from_candidates_dispatch<HNSW::C_distance>(
|
|
1084
|
+
hnsw, qdis, res, candidates, vt, stats, level, nres_in, params);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
int hnsw_detail::search_from_candidates_panorama(
|
|
760
1088
|
const HNSW& hnsw,
|
|
761
1089
|
const IndexHNSW* index,
|
|
762
1090
|
DistanceComputer& qdis,
|
|
@@ -767,6 +1095,14 @@ int search_from_candidates_panorama(
|
|
|
767
1095
|
int level,
|
|
768
1096
|
int nres_in,
|
|
769
1097
|
const SearchParameters* params) {
|
|
1098
|
+
// Panorama's progressive-bound math is L2-specific: refuse to run in
|
|
1099
|
+
// similarity mode.
|
|
1100
|
+
FAISS_THROW_IF_MSG(
|
|
1101
|
+
hnsw.is_similarity,
|
|
1102
|
+
"search_from_candidates_panorama does not support is_similarity=true");
|
|
1103
|
+
|
|
1104
|
+
using C = HNSW::C_distance;
|
|
1105
|
+
|
|
770
1106
|
int nres = nres_in;
|
|
771
1107
|
int ndis = 0;
|
|
772
1108
|
|
|
@@ -781,7 +1117,7 @@ int search_from_candidates_panorama(
|
|
|
781
1117
|
float d = candidates.dis[i];
|
|
782
1118
|
FAISS_ASSERT(v1 >= 0);
|
|
783
1119
|
if (!sel || sel->is_member(v1)) {
|
|
784
|
-
if (d
|
|
1120
|
+
if (C::cmp(threshold, d)) {
|
|
785
1121
|
if (res.add_result(d, v1)) {
|
|
786
1122
|
threshold = res.threshold;
|
|
787
1123
|
}
|
|
@@ -801,177 +1137,129 @@ int search_from_candidates_panorama(
|
|
|
801
1137
|
flat_codes_qdis,
|
|
802
1138
|
"DistanceComputer must be a FlatCodesDistanceComputer");
|
|
803
1139
|
|
|
804
|
-
|
|
805
|
-
size_t
|
|
806
|
-
|
|
807
|
-
|
|
1140
|
+
const auto& pano = panorama_index->pano;
|
|
1141
|
+
const size_t nb_per_parent = static_cast<size_t>(hnsw.nb_neighbors(level));
|
|
1142
|
+
const size_t num_panorama_levels = pano.n_levels;
|
|
1143
|
+
const size_t level_width_floats = pano.level_width_floats;
|
|
808
1144
|
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
float
|
|
1145
|
+
constexpr size_t kTargetBatch = 64;
|
|
1146
|
+
const size_t buf_cap = kTargetBatch + nb_per_parent;
|
|
1147
|
+
std::vector<uint32_t> index_array(buf_cap);
|
|
1148
|
+
std::vector<float> exact_distances(buf_cap);
|
|
1149
|
+
std::vector<float> dot_buffer(buf_cap);
|
|
1150
|
+
std::vector<float> query_cum_sums_buf(num_panorama_levels + 1);
|
|
813
1151
|
|
|
814
|
-
|
|
1152
|
+
const float* query = flat_codes_qdis->q;
|
|
815
1153
|
const size_t d = static_cast<size_t>(panorama_index->d);
|
|
816
1154
|
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
1155
|
+
const float* cum_base = panorama_index->get_cum_sum(0);
|
|
1156
|
+
const size_t cum_stride = num_panorama_levels + 1;
|
|
1157
|
+
const auto* flat_storage =
|
|
1158
|
+
static_cast<const IndexFlat*>(panorama_index->storage);
|
|
1159
|
+
const float* xb_base =
|
|
1160
|
+
reinterpret_cast<const float*>(flat_storage->codes.data());
|
|
1161
|
+
const size_t feat_stride = d;
|
|
823
1162
|
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
// than d0
|
|
1163
|
+
pano.compute_query_cum_sums(query, query_cum_sums_buf.data());
|
|
1164
|
+
const float* query_cum_sums = query_cum_sums_buf.data();
|
|
1165
|
+
const float query_norm_sq = query_cum_sums[0] * query_cum_sums[0];
|
|
828
1166
|
|
|
829
|
-
|
|
830
|
-
if (n_dis_below >= efSearch) {
|
|
831
|
-
break;
|
|
832
|
-
}
|
|
833
|
-
}
|
|
1167
|
+
int nstep = 0;
|
|
834
1168
|
|
|
835
|
-
|
|
836
|
-
|
|
1169
|
+
PanoramaStats local_pano_stats;
|
|
1170
|
+
local_pano_stats.reset();
|
|
837
1171
|
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
// that are not selected.
|
|
1172
|
+
bool stop_flag = false;
|
|
1173
|
+
while (candidates.size() > 0 && !stop_flag) {
|
|
841
1174
|
size_t initial_size = 0;
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
1175
|
+
size_t k_popped = 0;
|
|
1176
|
+
while (initial_size < kTargetBatch && candidates.size() > 0) {
|
|
1177
|
+
float d0 = 0;
|
|
1178
|
+
int v0 = candidates.pop_min(&d0);
|
|
1179
|
+
if (do_dis_check) {
|
|
1180
|
+
int n_dis_below = candidates.count_below(d0);
|
|
1181
|
+
if (n_dis_below >= efSearch) {
|
|
1182
|
+
if (k_popped == 0) {
|
|
1183
|
+
// Standard early-stop: nothing queued this
|
|
1184
|
+
// iteration, terminate the outer loop entirely
|
|
1185
|
+
// (matches the single-pop path's behavior).
|
|
1186
|
+
stop_flag = true;
|
|
1187
|
+
} else {
|
|
1188
|
+
// We already have parents queued; un-pop this
|
|
1189
|
+
// one so the next outer iteration sees it and
|
|
1190
|
+
// re-applies the stop check from a clean state.
|
|
1191
|
+
candidates.push(v0, d0);
|
|
1192
|
+
}
|
|
1193
|
+
break;
|
|
1194
|
+
}
|
|
846
1195
|
}
|
|
1196
|
+
k_popped++;
|
|
847
1197
|
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
1198
|
+
size_t begin, end;
|
|
1199
|
+
hnsw.neighbor_range(v0, level, &begin, &end);
|
|
1200
|
+
for (size_t j = begin; j < end; j++) {
|
|
1201
|
+
int v1 = hnsw.neighbors[j];
|
|
1202
|
+
if (v1 < 0) {
|
|
1203
|
+
break;
|
|
1204
|
+
}
|
|
852
1205
|
|
|
853
|
-
|
|
854
|
-
|
|
1206
|
+
bool is_new = vt.set(v1);
|
|
1207
|
+
bool is_selected = !sel || sel->is_member(v1);
|
|
1208
|
+
if (is_new && is_selected) {
|
|
1209
|
+
const float vsum =
|
|
1210
|
+
cum_base[static_cast<size_t>(v1) * cum_stride];
|
|
1211
|
+
index_array[initial_size] = v1;
|
|
1212
|
+
exact_distances[initial_size] = query_norm_sq + vsum * vsum;
|
|
1213
|
+
initial_size++;
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
855
1216
|
}
|
|
856
1217
|
|
|
857
1218
|
local_pano_stats.total_dims += initial_size * d;
|
|
1219
|
+
|
|
858
1220
|
size_t batch_size = initial_size;
|
|
859
1221
|
size_t curr_panorama_level = 0;
|
|
860
|
-
const size_t num_panorama_levels = panorama_index->pano.n_levels;
|
|
861
1222
|
while (curr_panorama_level < num_panorama_levels && batch_size > 0) {
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
size_t
|
|
867
|
-
|
|
868
|
-
end_dim = std::min(end_dim,
|
|
1223
|
+
const size_t cs_level_idx = curr_panorama_level + 1;
|
|
1224
|
+
const float query_cum_norm = query_cum_sums[cs_level_idx];
|
|
1225
|
+
const float two_qc = 2.0f * query_cum_norm;
|
|
1226
|
+
|
|
1227
|
+
const size_t start_dim = curr_panorama_level * level_width_floats;
|
|
1228
|
+
size_t end_dim = (curr_panorama_level + 1) * level_width_floats;
|
|
1229
|
+
end_dim = std::min(end_dim, d);
|
|
1230
|
+
const size_t dim_span = end_dim - start_dim;
|
|
1231
|
+
|
|
1232
|
+
const float* level_base = xb_base + start_dim;
|
|
1233
|
+
with_level_width(dim_span, [&]<size_t W>() {
|
|
1234
|
+
compute_level_dot_kernel<false, W>(
|
|
1235
|
+
query + start_dim,
|
|
1236
|
+
level_base,
|
|
1237
|
+
index_array.data(),
|
|
1238
|
+
batch_size,
|
|
1239
|
+
dim_span,
|
|
1240
|
+
dot_buffer.data(),
|
|
1241
|
+
feat_stride);
|
|
1242
|
+
});
|
|
1243
|
+
ndis += batch_size;
|
|
869
1244
|
|
|
870
|
-
size_t i = 0;
|
|
871
1245
|
size_t next_batch_size = 0;
|
|
872
|
-
for (; i + 3 < batch_size; i += 4) {
|
|
873
|
-
idx_t idx_0 = index_array[i];
|
|
874
|
-
idx_t idx_1 = index_array[i + 1];
|
|
875
|
-
idx_t idx_2 = index_array[i + 2];
|
|
876
|
-
idx_t idx_3 = index_array[i + 3];
|
|
877
|
-
|
|
878
|
-
float dp[4];
|
|
879
|
-
flat_codes_qdis->partial_dot_product_batch_4(
|
|
880
|
-
idx_0,
|
|
881
|
-
idx_1,
|
|
882
|
-
idx_2,
|
|
883
|
-
idx_3,
|
|
884
|
-
dp[0],
|
|
885
|
-
dp[1],
|
|
886
|
-
dp[2],
|
|
887
|
-
dp[3],
|
|
888
|
-
start_dim,
|
|
889
|
-
end_dim - start_dim);
|
|
890
|
-
ndis += 4;
|
|
891
1246
|
|
|
892
|
-
|
|
893
|
-
float
|
|
894
|
-
float
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
float
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
idx_2)[curr_panorama_level + 1];
|
|
903
|
-
float cum_sum_3 = panorama_index->get_cum_sum(
|
|
904
|
-
idx_3)[curr_panorama_level + 1];
|
|
905
|
-
|
|
906
|
-
float cs_bound_0 = 2.0f * cum_sum_0 * query_cum_norm;
|
|
907
|
-
float cs_bound_1 = 2.0f * cum_sum_1 * query_cum_norm;
|
|
908
|
-
float cs_bound_2 = 2.0f * cum_sum_2 * query_cum_norm;
|
|
909
|
-
float cs_bound_3 = 2.0f * cum_sum_3 * query_cum_norm;
|
|
910
|
-
|
|
911
|
-
float lower_bound_0 = new_exact_0 - cs_bound_0;
|
|
912
|
-
float lower_bound_1 = new_exact_1 - cs_bound_1;
|
|
913
|
-
float lower_bound_2 = new_exact_2 - cs_bound_2;
|
|
914
|
-
float lower_bound_3 = new_exact_3 - cs_bound_3;
|
|
915
|
-
|
|
916
|
-
// The following code is not the most branch friendly (due to
|
|
917
|
-
// the maintenance of the candidate heap), but micro-benchmarks
|
|
918
|
-
// have shown that it is not worth it to write horrible code to
|
|
919
|
-
// squeeze out those cycles.
|
|
920
|
-
if (lower_bound_0 <= threshold) {
|
|
921
|
-
exact_distances[next_batch_size] = new_exact_0;
|
|
922
|
-
index_array[next_batch_size] = idx_0;
|
|
923
|
-
next_batch_size += 1;
|
|
1247
|
+
for (size_t i = 0; i < batch_size; i++) {
|
|
1248
|
+
float ne = exact_distances[i] - 2.0f * dot_buffer[i];
|
|
1249
|
+
float cum = cum_base
|
|
1250
|
+
[static_cast<size_t>(index_array[i]) * cum_stride +
|
|
1251
|
+
cs_level_idx];
|
|
1252
|
+
float lb = ne - two_qc * cum;
|
|
1253
|
+
if (lb <= threshold) {
|
|
1254
|
+
exact_distances[next_batch_size] = ne;
|
|
1255
|
+
index_array[next_batch_size] = index_array[i];
|
|
1256
|
+
next_batch_size++;
|
|
924
1257
|
} else {
|
|
925
|
-
candidates.push(
|
|
926
|
-
}
|
|
927
|
-
if (lower_bound_1 <= threshold) {
|
|
928
|
-
exact_distances[next_batch_size] = new_exact_1;
|
|
929
|
-
index_array[next_batch_size] = idx_1;
|
|
930
|
-
next_batch_size += 1;
|
|
931
|
-
} else {
|
|
932
|
-
candidates.push(idx_1, new_exact_1);
|
|
933
|
-
}
|
|
934
|
-
if (lower_bound_2 <= threshold) {
|
|
935
|
-
exact_distances[next_batch_size] = new_exact_2;
|
|
936
|
-
index_array[next_batch_size] = idx_2;
|
|
937
|
-
next_batch_size += 1;
|
|
938
|
-
} else {
|
|
939
|
-
candidates.push(idx_2, new_exact_2);
|
|
940
|
-
}
|
|
941
|
-
if (lower_bound_3 <= threshold) {
|
|
942
|
-
exact_distances[next_batch_size] = new_exact_3;
|
|
943
|
-
index_array[next_batch_size] = idx_3;
|
|
944
|
-
next_batch_size += 1;
|
|
945
|
-
} else {
|
|
946
|
-
candidates.push(idx_3, new_exact_3);
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
// Process the remaining candidates.
|
|
951
|
-
for (; i < batch_size; i++) {
|
|
952
|
-
idx_t idx = index_array[i];
|
|
953
|
-
|
|
954
|
-
float dp = flat_codes_qdis->partial_dot_product(
|
|
955
|
-
idx, start_dim, end_dim - start_dim);
|
|
956
|
-
ndis += 1;
|
|
957
|
-
float new_exact = exact_distances[i] - 2.0f * dp;
|
|
958
|
-
|
|
959
|
-
float cum_sum = panorama_index->get_cum_sum(
|
|
960
|
-
idx)[curr_panorama_level + 1];
|
|
961
|
-
float cs_bound = 2.0f * cum_sum * query_cum_norm;
|
|
962
|
-
float lower_bound = new_exact - cs_bound;
|
|
963
|
-
|
|
964
|
-
if (lower_bound <= threshold) {
|
|
965
|
-
exact_distances[next_batch_size] = new_exact;
|
|
966
|
-
index_array[next_batch_size] = idx;
|
|
967
|
-
next_batch_size += 1;
|
|
968
|
-
} else {
|
|
969
|
-
candidates.push(idx, new_exact);
|
|
1258
|
+
candidates.push(index_array[i], ne);
|
|
970
1259
|
}
|
|
971
1260
|
}
|
|
972
1261
|
|
|
973
|
-
local_pano_stats.total_dims_scanned +=
|
|
974
|
-
batch_size * (end_dim - start_dim);
|
|
1262
|
+
local_pano_stats.total_dims_scanned += batch_size * dim_span;
|
|
975
1263
|
batch_size = next_batch_size;
|
|
976
1264
|
curr_panorama_level++;
|
|
977
1265
|
}
|
|
@@ -986,7 +1274,7 @@ int search_from_candidates_panorama(
|
|
|
986
1274
|
candidates.push(idx, exact_distances[i]);
|
|
987
1275
|
}
|
|
988
1276
|
|
|
989
|
-
nstep
|
|
1277
|
+
nstep += static_cast<int>(k_popped);
|
|
990
1278
|
if (!do_dis_check && nstep > efSearch) {
|
|
991
1279
|
break;
|
|
992
1280
|
}
|
|
@@ -1005,6 +1293,8 @@ int search_from_candidates_panorama(
|
|
|
1005
1293
|
return nres;
|
|
1006
1294
|
}
|
|
1007
1295
|
|
|
1296
|
+
namespace {
|
|
1297
|
+
|
|
1008
1298
|
template <typename T, typename Container, typename Compare>
|
|
1009
1299
|
void reservePriorityQueue(
|
|
1010
1300
|
std::priority_queue<T, Container, Compare>& q,
|
|
@@ -1017,31 +1307,35 @@ void reservePriorityQueue(
|
|
|
1017
1307
|
q = std::move(access);
|
|
1018
1308
|
}
|
|
1019
1309
|
|
|
1020
|
-
|
|
1310
|
+
/// Templated body of `search_from_candidate_unbounded`. The choice of
|
|
1311
|
+
/// max-heap vs min-heap for both `top_candidates` and `candidates` is
|
|
1312
|
+
/// derived from C via `TopCandidatesQueue` / `CandidatesQueue`.
|
|
1313
|
+
template <typename VTType, class C>
|
|
1314
|
+
TopCandidatesQueue<C> search_from_candidate_unbounded_fixVT(
|
|
1021
1315
|
const HNSW& hnsw,
|
|
1022
|
-
const Node& node,
|
|
1316
|
+
const HNSW::Node& node,
|
|
1023
1317
|
DistanceComputer& qdis,
|
|
1024
1318
|
int ef,
|
|
1025
|
-
|
|
1319
|
+
VTType& vt,
|
|
1026
1320
|
HNSWStats& stats) {
|
|
1027
1321
|
int ndis = 0;
|
|
1028
|
-
|
|
1322
|
+
TopCandidatesQueue<C> top_candidates;
|
|
1029
1323
|
reservePriorityQueue(top_candidates, ef);
|
|
1030
1324
|
|
|
1031
|
-
|
|
1325
|
+
CandidatesQueue<C> candidates;
|
|
1032
1326
|
reservePriorityQueue(candidates, ef);
|
|
1033
1327
|
|
|
1034
1328
|
top_candidates.push(node);
|
|
1035
1329
|
candidates.push(node);
|
|
1036
1330
|
|
|
1037
|
-
vt
|
|
1331
|
+
vt.set(node.second);
|
|
1038
1332
|
|
|
1039
1333
|
while (!candidates.empty()) {
|
|
1040
1334
|
float d0;
|
|
1041
1335
|
storage_idx_t v0;
|
|
1042
1336
|
std::tie(d0, v0) = candidates.top();
|
|
1043
1337
|
|
|
1044
|
-
if (d0
|
|
1338
|
+
if (C::cmp(d0, top_candidates.top().first)) {
|
|
1045
1339
|
break;
|
|
1046
1340
|
}
|
|
1047
1341
|
|
|
@@ -1059,7 +1353,7 @@ std::priority_queue<HNSW::Node> search_from_candidate_unbounded(
|
|
|
1059
1353
|
break;
|
|
1060
1354
|
}
|
|
1061
1355
|
|
|
1062
|
-
vt
|
|
1356
|
+
vt.prefetch(v1);
|
|
1063
1357
|
jmax += 1;
|
|
1064
1358
|
}
|
|
1065
1359
|
|
|
@@ -1067,12 +1361,12 @@ std::priority_queue<HNSW::Node> search_from_candidate_unbounded(
|
|
|
1067
1361
|
size_t saved_j[4];
|
|
1068
1362
|
|
|
1069
1363
|
auto add_to_heap = [&](const size_t idx, const float dis) {
|
|
1070
|
-
if (top_candidates.top().first
|
|
1071
|
-
top_candidates.size() < ef) {
|
|
1364
|
+
if (C::cmp(top_candidates.top().first, dis) ||
|
|
1365
|
+
top_candidates.size() < static_cast<size_t>(ef)) {
|
|
1072
1366
|
candidates.emplace(dis, idx);
|
|
1073
1367
|
top_candidates.emplace(dis, idx);
|
|
1074
1368
|
|
|
1075
|
-
if (top_candidates.size() > ef) {
|
|
1369
|
+
if (top_candidates.size() > static_cast<size_t>(ef)) {
|
|
1076
1370
|
top_candidates.pop();
|
|
1077
1371
|
}
|
|
1078
1372
|
}
|
|
@@ -1082,7 +1376,7 @@ std::priority_queue<HNSW::Node> search_from_candidate_unbounded(
|
|
|
1082
1376
|
int v1 = hnsw.neighbors[j];
|
|
1083
1377
|
|
|
1084
1378
|
saved_j[counter] = v1;
|
|
1085
|
-
counter += vt
|
|
1379
|
+
counter += vt.set(v1) ? 1 : 0;
|
|
1086
1380
|
|
|
1087
1381
|
if (counter == 4) {
|
|
1088
1382
|
float dis[4];
|
|
@@ -1125,111 +1419,59 @@ std::priority_queue<HNSW::Node> search_from_candidate_unbounded(
|
|
|
1125
1419
|
return top_candidates;
|
|
1126
1420
|
}
|
|
1127
1421
|
|
|
1128
|
-
|
|
1129
|
-
|
|
1422
|
+
} // namespace
|
|
1423
|
+
|
|
1424
|
+
/// Public dispatcher: only the distance (CMax) flavor is exposed because
|
|
1425
|
+
/// its return type — `std::priority_queue<HNSW::Node>` — is the CMax
|
|
1426
|
+
/// max-heap. Internal callers that need similarity mode use the same
|
|
1427
|
+
/// dispatch pattern inline.
|
|
1428
|
+
std::priority_queue<HNSW::Node> hnsw_detail::search_from_candidate_unbounded(
|
|
1130
1429
|
const HNSW& hnsw,
|
|
1430
|
+
const HNSW::Node& node,
|
|
1131
1431
|
DistanceComputer& qdis,
|
|
1132
|
-
int
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
size_t ndis = 0;
|
|
1144
|
-
|
|
1145
|
-
// a faster version: reference version in unit test test_hnsw.cpp
|
|
1146
|
-
// the following version processes 4 neighbors at a time
|
|
1147
|
-
auto update_with_candidate = [&](const storage_idx_t idx,
|
|
1148
|
-
const float dis) {
|
|
1149
|
-
if (dis < d_nearest) {
|
|
1150
|
-
nearest = idx;
|
|
1151
|
-
d_nearest = dis;
|
|
1152
|
-
}
|
|
1153
|
-
};
|
|
1154
|
-
|
|
1155
|
-
int n_buffered = 0;
|
|
1156
|
-
storage_idx_t buffered_ids[4];
|
|
1157
|
-
|
|
1158
|
-
for (size_t j = begin; j < end; j++) {
|
|
1159
|
-
storage_idx_t v = hnsw.neighbors[j];
|
|
1160
|
-
if (v < 0) {
|
|
1161
|
-
break;
|
|
1162
|
-
}
|
|
1163
|
-
ndis += 1;
|
|
1164
|
-
|
|
1165
|
-
buffered_ids[n_buffered] = v;
|
|
1166
|
-
n_buffered += 1;
|
|
1167
|
-
|
|
1168
|
-
if (n_buffered == 4) {
|
|
1169
|
-
float dis[4];
|
|
1170
|
-
qdis.distances_batch_4(
|
|
1171
|
-
buffered_ids[0],
|
|
1172
|
-
buffered_ids[1],
|
|
1173
|
-
buffered_ids[2],
|
|
1174
|
-
buffered_ids[3],
|
|
1175
|
-
dis[0],
|
|
1176
|
-
dis[1],
|
|
1177
|
-
dis[2],
|
|
1178
|
-
dis[3]);
|
|
1179
|
-
|
|
1180
|
-
for (size_t id4 = 0; id4 < 4; id4++) {
|
|
1181
|
-
update_with_candidate(buffered_ids[id4], dis[id4]);
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
n_buffered = 0;
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
// process leftovers
|
|
1189
|
-
for (int icnt = 0; icnt < n_buffered; icnt++) {
|
|
1190
|
-
float dis = qdis(buffered_ids[icnt]);
|
|
1191
|
-
update_with_candidate(buffered_ids[icnt], dis);
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
// update stats
|
|
1195
|
-
stats.ndis += ndis;
|
|
1196
|
-
stats.nhops += 1;
|
|
1197
|
-
|
|
1198
|
-
if (nearest == prev_nearest) {
|
|
1199
|
-
return stats;
|
|
1200
|
-
}
|
|
1432
|
+
int ef,
|
|
1433
|
+
VisitedTable* vt,
|
|
1434
|
+
HNSWStats& stats) {
|
|
1435
|
+
using C = HNSW::C_distance;
|
|
1436
|
+
auto call = [&]<typename VTType>(VTType& vt_concrete) {
|
|
1437
|
+
return search_from_candidate_unbounded_fixVT<VTType, C>(
|
|
1438
|
+
hnsw, node, qdis, ef, vt_concrete, stats);
|
|
1439
|
+
};
|
|
1440
|
+
if (VisitedTableVector* vtv = dynamic_cast<VisitedTableVector*>(vt)) {
|
|
1441
|
+
return call(*vtv);
|
|
1201
1442
|
}
|
|
1443
|
+
VisitedTableSet& vts = dynamic_cast<VisitedTableSet&>(*vt);
|
|
1444
|
+
return call(vts);
|
|
1202
1445
|
}
|
|
1203
1446
|
|
|
1204
1447
|
namespace {
|
|
1205
|
-
using Node = HNSW::Node;
|
|
1206
|
-
using C = HNSW::C;
|
|
1207
1448
|
|
|
1208
1449
|
// just used as a lower bound for the minmaxheap, but it is set for heap search
|
|
1450
|
+
template <class C>
|
|
1209
1451
|
int extract_k_from_ResultHandler(ResultHandler& res) {
|
|
1210
1452
|
using RH = HeapBlockResultHandler<C>;
|
|
1211
|
-
if (auto hres = dynamic_cast<RH::SingleResultHandler*>(&res)) {
|
|
1453
|
+
if (auto hres = dynamic_cast<typename RH::SingleResultHandler*>(&res)) {
|
|
1212
1454
|
return hres->k;
|
|
1213
1455
|
}
|
|
1214
1456
|
return 1;
|
|
1215
1457
|
}
|
|
1216
1458
|
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1459
|
+
template <class C>
|
|
1460
|
+
HNSWStats search_impl(
|
|
1461
|
+
const HNSW& hnsw,
|
|
1220
1462
|
DistanceComputer& qdis,
|
|
1221
1463
|
const IndexHNSW* index,
|
|
1222
1464
|
ResultHandler& res,
|
|
1223
1465
|
VisitedTable& vt,
|
|
1224
|
-
const SearchParameters* params)
|
|
1466
|
+
const SearchParameters* params) {
|
|
1225
1467
|
HNSWStats stats;
|
|
1226
|
-
if (entry_point == -1) {
|
|
1468
|
+
if (hnsw.entry_point == -1) {
|
|
1227
1469
|
return stats;
|
|
1228
1470
|
}
|
|
1229
|
-
int k = extract_k_from_ResultHandler(res);
|
|
1471
|
+
int k = extract_k_from_ResultHandler<C>(res);
|
|
1230
1472
|
|
|
1231
|
-
bool bounded_queue =
|
|
1232
|
-
int cur_efSearch =
|
|
1473
|
+
bool bounded_queue = hnsw.search_bounded_queue;
|
|
1474
|
+
int cur_efSearch = hnsw.efSearch;
|
|
1233
1475
|
if (params) {
|
|
1234
1476
|
if (const SearchParametersHNSW* hnsw_params =
|
|
1235
1477
|
dynamic_cast<const SearchParametersHNSW*>(params)) {
|
|
@@ -1239,42 +1481,63 @@ HNSWStats HNSW::search(
|
|
|
1239
1481
|
}
|
|
1240
1482
|
|
|
1241
1483
|
// greedy search on upper levels
|
|
1242
|
-
storage_idx_t nearest = entry_point;
|
|
1484
|
+
storage_idx_t nearest = hnsw.entry_point;
|
|
1243
1485
|
float d_nearest = qdis(nearest);
|
|
1244
1486
|
|
|
1245
|
-
for (int level = max_level; level >= 1; level--) {
|
|
1246
|
-
HNSWStats local_stats =
|
|
1247
|
-
|
|
1487
|
+
for (int level = hnsw.max_level; level >= 1; level--) {
|
|
1488
|
+
HNSWStats local_stats = greedy_update_nearest_impl<C>(
|
|
1489
|
+
hnsw, qdis, level, nearest, d_nearest);
|
|
1248
1490
|
stats.combine(local_stats);
|
|
1249
1491
|
}
|
|
1250
1492
|
|
|
1251
1493
|
int ef = std::max(cur_efSearch, k);
|
|
1252
1494
|
if (bounded_queue) { // this is the most common branch, for now we only
|
|
1253
1495
|
// support Panorama search in this branch
|
|
1254
|
-
|
|
1496
|
+
MinimaxHeapT<HC_for<C>> candidates(ef);
|
|
1255
1497
|
|
|
1256
1498
|
candidates.push(nearest, d_nearest);
|
|
1257
1499
|
|
|
1258
|
-
if (!is_panorama) {
|
|
1259
|
-
|
|
1260
|
-
|
|
1500
|
+
if (!hnsw.is_panorama) {
|
|
1501
|
+
search_from_candidates_dispatch<C>(
|
|
1502
|
+
hnsw, qdis, res, candidates, vt, stats, 0, 0, params);
|
|
1261
1503
|
} else {
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
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
|
+
}
|
|
1273
1523
|
}
|
|
1274
1524
|
} else {
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1525
|
+
auto call = [&]<typename VTType>(VTType& vt_concrete) {
|
|
1526
|
+
return search_from_candidate_unbounded_fixVT<VTType, C>(
|
|
1527
|
+
hnsw,
|
|
1528
|
+
HNSW::Node(d_nearest, nearest),
|
|
1529
|
+
qdis,
|
|
1530
|
+
ef,
|
|
1531
|
+
vt_concrete,
|
|
1532
|
+
stats);
|
|
1533
|
+
};
|
|
1534
|
+
TopCandidatesQueue<C> top_candidates;
|
|
1535
|
+
if (VisitedTableVector* vtv = dynamic_cast<VisitedTableVector*>(&vt)) {
|
|
1536
|
+
top_candidates = call(*vtv);
|
|
1537
|
+
} else {
|
|
1538
|
+
VisitedTableSet& vts = dynamic_cast<VisitedTableSet&>(vt);
|
|
1539
|
+
top_candidates = call(vts);
|
|
1540
|
+
}
|
|
1278
1541
|
|
|
1279
1542
|
while (top_candidates.size() > static_cast<size_t>(k)) {
|
|
1280
1543
|
top_candidates.pop();
|
|
@@ -1294,7 +1557,9 @@ HNSWStats HNSW::search(
|
|
|
1294
1557
|
return stats;
|
|
1295
1558
|
}
|
|
1296
1559
|
|
|
1297
|
-
|
|
1560
|
+
template <class C>
|
|
1561
|
+
void search_level_0_impl(
|
|
1562
|
+
const HNSW& hnsw,
|
|
1298
1563
|
DistanceComputer& qdis,
|
|
1299
1564
|
ResultHandler& res,
|
|
1300
1565
|
idx_t nprobe,
|
|
@@ -1303,9 +1568,7 @@ void HNSW::search_level_0(
|
|
|
1303
1568
|
int search_type,
|
|
1304
1569
|
HNSWStats& search_stats,
|
|
1305
1570
|
VisitedTable& vt,
|
|
1306
|
-
const SearchParameters* params)
|
|
1307
|
-
const HNSW& hnsw = *this;
|
|
1308
|
-
|
|
1571
|
+
const SearchParameters* params) {
|
|
1309
1572
|
auto cur_efSearch = hnsw.efSearch;
|
|
1310
1573
|
if (params) {
|
|
1311
1574
|
if (const SearchParametersHNSW* hnsw_params =
|
|
@@ -1314,7 +1577,7 @@ void HNSW::search_level_0(
|
|
|
1314
1577
|
}
|
|
1315
1578
|
}
|
|
1316
1579
|
|
|
1317
|
-
int k = extract_k_from_ResultHandler(res);
|
|
1580
|
+
int k = extract_k_from_ResultHandler<C>(res);
|
|
1318
1581
|
|
|
1319
1582
|
if (search_type == 1) {
|
|
1320
1583
|
int nres = 0;
|
|
@@ -1331,11 +1594,11 @@ void HNSW::search_level_0(
|
|
|
1331
1594
|
}
|
|
1332
1595
|
|
|
1333
1596
|
int candidates_size = std::max(cur_efSearch, k);
|
|
1334
|
-
|
|
1597
|
+
MinimaxHeapT<HC_for<C>> candidates(candidates_size);
|
|
1335
1598
|
|
|
1336
1599
|
candidates.push(cj, nearest_d[j]);
|
|
1337
1600
|
|
|
1338
|
-
nres =
|
|
1601
|
+
nres = search_from_candidates_dispatch<C>(
|
|
1339
1602
|
hnsw,
|
|
1340
1603
|
qdis,
|
|
1341
1604
|
res,
|
|
@@ -1351,7 +1614,7 @@ void HNSW::search_level_0(
|
|
|
1351
1614
|
int candidates_size = std::max(cur_efSearch, int(k));
|
|
1352
1615
|
candidates_size = std::max(candidates_size, int(nprobe));
|
|
1353
1616
|
|
|
1354
|
-
|
|
1617
|
+
MinimaxHeapT<HC_for<C>> candidates(candidates_size);
|
|
1355
1618
|
for (idx_t j = 0; j < nprobe; j++) {
|
|
1356
1619
|
storage_idx_t cj = nearest_i[j];
|
|
1357
1620
|
|
|
@@ -1361,11 +1624,62 @@ void HNSW::search_level_0(
|
|
|
1361
1624
|
candidates.push(cj, nearest_d[j]);
|
|
1362
1625
|
}
|
|
1363
1626
|
|
|
1364
|
-
|
|
1627
|
+
search_from_candidates_dispatch<C>(
|
|
1365
1628
|
hnsw, qdis, res, candidates, vt, search_stats, 0, 0, params);
|
|
1366
1629
|
}
|
|
1367
1630
|
}
|
|
1368
1631
|
|
|
1632
|
+
} // namespace
|
|
1633
|
+
|
|
1634
|
+
HNSWStats HNSW::search(
|
|
1635
|
+
DistanceComputer& qdis,
|
|
1636
|
+
const IndexHNSW* index,
|
|
1637
|
+
ResultHandler& res,
|
|
1638
|
+
VisitedTable& vt,
|
|
1639
|
+
const SearchParameters* params) const {
|
|
1640
|
+
if (is_similarity) {
|
|
1641
|
+
return search_impl<C_similarity>(*this, qdis, index, res, vt, params);
|
|
1642
|
+
}
|
|
1643
|
+
return search_impl<C_distance>(*this, qdis, index, res, vt, params);
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
void HNSW::search_level_0(
|
|
1647
|
+
DistanceComputer& qdis,
|
|
1648
|
+
ResultHandler& res,
|
|
1649
|
+
idx_t nprobe,
|
|
1650
|
+
const storage_idx_t* nearest_i,
|
|
1651
|
+
const float* nearest_d,
|
|
1652
|
+
int search_type,
|
|
1653
|
+
HNSWStats& search_stats,
|
|
1654
|
+
VisitedTable& vt,
|
|
1655
|
+
const SearchParameters* params) const {
|
|
1656
|
+
if (is_similarity) {
|
|
1657
|
+
search_level_0_impl<C_similarity>(
|
|
1658
|
+
*this,
|
|
1659
|
+
qdis,
|
|
1660
|
+
res,
|
|
1661
|
+
nprobe,
|
|
1662
|
+
nearest_i,
|
|
1663
|
+
nearest_d,
|
|
1664
|
+
search_type,
|
|
1665
|
+
search_stats,
|
|
1666
|
+
vt,
|
|
1667
|
+
params);
|
|
1668
|
+
} else {
|
|
1669
|
+
search_level_0_impl<C_distance>(
|
|
1670
|
+
*this,
|
|
1671
|
+
qdis,
|
|
1672
|
+
res,
|
|
1673
|
+
nprobe,
|
|
1674
|
+
nearest_i,
|
|
1675
|
+
nearest_d,
|
|
1676
|
+
search_type,
|
|
1677
|
+
search_stats,
|
|
1678
|
+
vt,
|
|
1679
|
+
params);
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1369
1683
|
void HNSW::permute_entries(const idx_t* map) {
|
|
1370
1684
|
// remap levels
|
|
1371
1685
|
storage_idx_t ntotal = levels.size();
|