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.
Files changed (178) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/lib/faiss/version.rb +1 -1
  4. data/vendor/faiss/faiss/AutoTune.cpp +3 -1
  5. data/vendor/faiss/faiss/Clustering.cpp +9 -1
  6. data/vendor/faiss/faiss/Clustering.h +8 -0
  7. data/vendor/faiss/faiss/IVFlib.cpp +14 -3
  8. data/vendor/faiss/faiss/Index.h +2 -2
  9. data/vendor/faiss/faiss/IndexAdditiveQuantizer.cpp +9 -10
  10. data/vendor/faiss/faiss/IndexAdditiveQuantizerFastScan.cpp +2 -3
  11. data/vendor/faiss/faiss/IndexBinaryFromFloat.cpp +24 -4
  12. data/vendor/faiss/faiss/IndexBinaryHNSW.cpp +16 -145
  13. data/vendor/faiss/faiss/IndexBinaryHNSW.h +0 -6
  14. data/vendor/faiss/faiss/IndexBinaryHash.cpp +5 -9
  15. data/vendor/faiss/faiss/IndexBinaryIVF.cpp +8 -18
  16. data/vendor/faiss/faiss/IndexBinaryIVF.h +8 -1
  17. data/vendor/faiss/faiss/IndexEDEN.cpp +273 -0
  18. data/vendor/faiss/faiss/IndexEDEN.h +57 -0
  19. data/vendor/faiss/faiss/IndexFastScan.cpp +15 -4
  20. data/vendor/faiss/faiss/IndexFlat.cpp +21 -54
  21. data/vendor/faiss/faiss/IndexFlat.h +2 -2
  22. data/vendor/faiss/faiss/IndexHNSW.cpp +311 -102
  23. data/vendor/faiss/faiss/IndexHNSW.h +31 -7
  24. data/vendor/faiss/faiss/IndexIDMap.cpp +26 -8
  25. data/vendor/faiss/faiss/IndexIDMap.h +2 -0
  26. data/vendor/faiss/faiss/IndexIVF.cpp +36 -10
  27. data/vendor/faiss/faiss/IndexIVFAdditiveQuantizer.cpp +1 -1
  28. data/vendor/faiss/faiss/IndexIVFAdditiveQuantizerFastScan.cpp +3 -4
  29. data/vendor/faiss/faiss/IndexIVFEDEN.cpp +302 -0
  30. data/vendor/faiss/faiss/IndexIVFEDEN.h +70 -0
  31. data/vendor/faiss/faiss/IndexIVFFastScan.cpp +5 -6
  32. data/vendor/faiss/faiss/IndexIVFFlat.cpp +3 -4
  33. data/vendor/faiss/faiss/IndexIVFIndependentQuantizer.cpp +1 -1
  34. data/vendor/faiss/faiss/IndexIVFPQ.cpp +49 -23
  35. data/vendor/faiss/faiss/IndexIVFPQ.h +11 -0
  36. data/vendor/faiss/faiss/IndexIVFPQFastScan.cpp +0 -1
  37. data/vendor/faiss/faiss/IndexIVFRaBitQ.cpp +19 -49
  38. data/vendor/faiss/faiss/IndexIVFRaBitQFastScan.cpp +180 -76
  39. data/vendor/faiss/faiss/IndexIVFRaBitQFastScan.h +5 -4
  40. data/vendor/faiss/faiss/IndexIVFSpectralHash.cpp +8 -6
  41. data/vendor/faiss/faiss/IndexLSH.cpp +2 -3
  42. data/vendor/faiss/faiss/IndexLattice.cpp +5 -0
  43. data/vendor/faiss/faiss/IndexNNDescent.cpp +9 -2
  44. data/vendor/faiss/faiss/IndexNSG.cpp +7 -2
  45. data/vendor/faiss/faiss/IndexPQ.cpp +6 -8
  46. data/vendor/faiss/faiss/IndexPreTransform.cpp +15 -0
  47. data/vendor/faiss/faiss/IndexRaBitQ.cpp +2 -2
  48. data/vendor/faiss/faiss/IndexRaBitQFastScan.cpp +1 -2
  49. data/vendor/faiss/faiss/IndexRaBitQFastScan.h +5 -1
  50. data/vendor/faiss/faiss/IndexRefine.cpp +30 -1
  51. data/vendor/faiss/faiss/IndexReplicas.cpp +1 -2
  52. data/vendor/faiss/faiss/IndexShards.cpp +5 -5
  53. data/vendor/faiss/faiss/IndexShardsIVF.cpp +6 -5
  54. data/vendor/faiss/faiss/MetaIndexes.cpp +2 -4
  55. data/vendor/faiss/faiss/SuperKMeans.cpp +286 -247
  56. data/vendor/faiss/faiss/SuperKMeans.h +33 -2
  57. data/vendor/faiss/faiss/VectorTransform.cpp +71 -2
  58. data/vendor/faiss/faiss/VectorTransform.h +3 -0
  59. data/vendor/faiss/faiss/clone_index.cpp +8 -0
  60. data/vendor/faiss/faiss/factory_tools.cpp +47 -4
  61. data/vendor/faiss/faiss/gpu/GpuCloner.cpp +11 -11
  62. data/vendor/faiss/faiss/gpu/GpuClonerOptions.h +1 -5
  63. data/vendor/faiss/faiss/gpu/GpuDistance.h +2 -5
  64. data/vendor/faiss/faiss/gpu/GpuIndex.h +38 -16
  65. data/vendor/faiss/faiss/gpu/GpuIndexCagra.h +71 -1
  66. data/vendor/faiss/faiss/gpu/GpuIndexIVF.h +17 -0
  67. data/vendor/faiss/faiss/gpu/GpuIndexIVFScalarQuantizer.h +16 -0
  68. data/vendor/faiss/faiss/gpu/perf/PerfClustering.cpp +1 -1
  69. data/vendor/faiss/faiss/gpu/perf/PerfIVFPQAdd.cpp +2 -2
  70. data/vendor/faiss/faiss/gpu/test/TestGpuIndexIVFScalarQuantizer.cpp +180 -0
  71. data/vendor/faiss/faiss/gpu_metal/MetalIndexIVFFlat.h +1 -5
  72. data/vendor/faiss/faiss/gpu_metal/MetalIndexIVFPQ.h +88 -0
  73. data/vendor/faiss/faiss/gpu_metal/impl/MetalIVFPQ.h +134 -0
  74. data/vendor/faiss/faiss/impl/AdditiveQuantizer.cpp +1 -1
  75. data/vendor/faiss/faiss/impl/ClusteringInitialization.cpp +7 -4
  76. data/vendor/faiss/faiss/impl/DistanceComputer.h +34 -0
  77. data/vendor/faiss/faiss/impl/EDENQuantizer.h +119 -0
  78. data/vendor/faiss/faiss/impl/HNSW.cpp +528 -267
  79. data/vendor/faiss/faiss/impl/HNSW.h +46 -7
  80. data/vendor/faiss/faiss/impl/IDSelector.h +44 -0
  81. data/vendor/faiss/faiss/impl/LocalSearchQuantizer.cpp +2 -2
  82. data/vendor/faiss/faiss/impl/NNDescent.cpp +10 -3
  83. data/vendor/faiss/faiss/impl/NSG.cpp +3 -1
  84. data/vendor/faiss/faiss/impl/Panorama.h +20 -9
  85. data/vendor/faiss/faiss/impl/PolysemousTraining.cpp +152 -84
  86. data/vendor/faiss/faiss/impl/ProductQuantizer.cpp +38 -26
  87. data/vendor/faiss/faiss/impl/RaBitQUtils.cpp +45 -37
  88. data/vendor/faiss/faiss/impl/RaBitQUtils.h +35 -0
  89. data/vendor/faiss/faiss/impl/RaBitQuantizer.cpp +239 -72
  90. data/vendor/faiss/faiss/impl/RaBitQuantizer.h +66 -4
  91. data/vendor/faiss/faiss/impl/RaBitQuantizerMultiBit.cpp +4 -13
  92. data/vendor/faiss/faiss/impl/ResultHandler.h +34 -34
  93. data/vendor/faiss/faiss/impl/ScalarQuantizer.cpp +287 -84
  94. data/vendor/faiss/faiss/impl/ScalarQuantizer.h +26 -10
  95. data/vendor/faiss/faiss/impl/ThreadedIndex-inl.h +2 -2
  96. data/vendor/faiss/faiss/impl/VisitedTable.cpp +22 -2
  97. data/vendor/faiss/faiss/impl/VisitedTable.h +20 -0
  98. data/vendor/faiss/faiss/impl/binary_hamming/IndexBinaryIVF_impl.h +90 -14
  99. data/vendor/faiss/faiss/impl/binary_hamming/avx2.cpp +4 -4
  100. data/vendor/faiss/faiss/impl/expanded_scanners.h +5 -1
  101. data/vendor/faiss/faiss/impl/fast_scan/decompose_qbs.h +1 -0
  102. data/vendor/faiss/faiss/impl/fast_scan/dispatching.h +35 -2
  103. data/vendor/faiss/faiss/impl/hnsw/LockVector.cpp +1 -1
  104. data/vendor/faiss/faiss/impl/index_read.cpp +491 -50
  105. data/vendor/faiss/faiss/impl/index_write.cpp +86 -30
  106. data/vendor/faiss/faiss/impl/lattice_Zn.cpp +8 -9
  107. data/vendor/faiss/faiss/impl/platform_macros.h +3 -1
  108. data/vendor/faiss/faiss/impl/polysemous_training/avx512.cpp +284 -0
  109. data/vendor/faiss/faiss/impl/polysemous_training/dispatch.h +115 -0
  110. data/vendor/faiss/faiss/impl/pq_code_distance/IVFPQScanner_impl.h +73 -39
  111. data/vendor/faiss/faiss/impl/pq_code_distance/IVFPQ_QueryTables.cpp +0 -1
  112. data/vendor/faiss/faiss/impl/pq_code_distance/PQDistanceComputer_impl.h +26 -15
  113. data/vendor/faiss/faiss/impl/pq_code_distance/avx2.cpp +4 -4
  114. data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-generic.cpp +4 -4
  115. data/vendor/faiss/faiss/impl/result_handler/ResultHandler.cpp +195 -0
  116. data/vendor/faiss/faiss/impl/result_handler/avx2.cpp +133 -0
  117. data/vendor/faiss/faiss/impl/result_handler/avx512.cpp +281 -0
  118. data/vendor/faiss/faiss/impl/scalar_quantizer/EDENQuantizer-avx2.cpp +72 -0
  119. data/vendor/faiss/faiss/impl/scalar_quantizer/EDENQuantizer-avx512.cpp +228 -0
  120. data/vendor/faiss/faiss/impl/scalar_quantizer/EDENQuantizer.cpp +887 -0
  121. data/vendor/faiss/faiss/impl/scalar_quantizer/distance_computers.h +2 -2
  122. data/vendor/faiss/faiss/impl/scalar_quantizer/quantizers.h +9 -8
  123. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx2.cpp +90 -24
  124. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512-impl.h +30 -30
  125. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512-spr.cpp +4 -5
  126. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512.cpp +101 -34
  127. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-dispatch.h +169 -13
  128. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-neon.cpp +125 -26
  129. data/vendor/faiss/faiss/impl/simd_dispatch.h +70 -31
  130. data/vendor/faiss/faiss/index_factory.cpp +40 -7
  131. data/vendor/faiss/faiss/invlists/DirectMap.cpp +1 -1
  132. data/vendor/faiss/faiss/invlists/InvertedLists.cpp +9 -6
  133. data/vendor/faiss/faiss/invlists/OnDiskInvertedLists.cpp +29 -8
  134. data/vendor/faiss/faiss/python/python_callbacks.cpp +3 -1
  135. data/vendor/faiss/faiss/svs/IndexSVSFaissUtils.h +60 -0
  136. data/vendor/faiss/faiss/svs/IndexSVSFlat.cpp +26 -1
  137. data/vendor/faiss/faiss/svs/IndexSVSFlat.h +13 -0
  138. data/vendor/faiss/faiss/svs/IndexSVSIVF.cpp +1 -1
  139. data/vendor/faiss/faiss/svs/IndexSVSIVFLeanVec.cpp +1 -1
  140. data/vendor/faiss/faiss/svs/IndexSVSVamana.cpp +47 -5
  141. data/vendor/faiss/faiss/svs/IndexSVSVamana.h +23 -3
  142. data/vendor/faiss/faiss/svs/IndexSVSVamanaLVQ.cpp +4 -2
  143. data/vendor/faiss/faiss/svs/IndexSVSVamanaLVQ.h +2 -1
  144. data/vendor/faiss/faiss/svs/IndexSVSVamanaLeanVec.cpp +10 -4
  145. data/vendor/faiss/faiss/svs/IndexSVSVamanaLeanVec.h +2 -1
  146. data/vendor/faiss/faiss/utils/approx_topk_hamming/approx_topk_hamming.h +1 -1
  147. data/vendor/faiss/faiss/utils/distances.cpp +30 -11
  148. data/vendor/faiss/faiss/utils/distances_dispatch.h +30 -24
  149. data/vendor/faiss/faiss/utils/distances_fused/distances_fused.cpp +1 -1
  150. data/vendor/faiss/faiss/utils/distances_simd.cpp +4 -3
  151. data/vendor/faiss/faiss/utils/extra_distances.cpp +4 -14
  152. data/vendor/faiss/faiss/utils/extra_distances.h +1 -2
  153. data/vendor/faiss/faiss/utils/hamming.cpp +16 -10
  154. data/vendor/faiss/faiss/utils/hamming.h +10 -1
  155. data/vendor/faiss/faiss/utils/hamming_distance/common.h +14 -3
  156. data/vendor/faiss/faiss/utils/hamming_distance/hamming_avx512_vpopcnt.cpp +24 -0
  157. data/vendor/faiss/faiss/utils/hamming_distance/hamming_computer-avx512.h +1 -1
  158. data/vendor/faiss/faiss/utils/hamming_distance/{hamming_computer-avx512_spr.h → hamming_computer-avx512_vpopcnt.h} +85 -24
  159. data/vendor/faiss/faiss/utils/hamming_distance/hamming_impl.h +141 -0
  160. data/vendor/faiss/faiss/utils/quantize_lut.cpp +29 -8
  161. data/vendor/faiss/faiss/utils/rabitq_simd.h +202 -0
  162. data/vendor/faiss/faiss/utils/simd_impl/distances_arm_sve.cpp +194 -30
  163. data/vendor/faiss/faiss/utils/simd_impl/distances_avx2.cpp +0 -1
  164. data/vendor/faiss/faiss/utils/simd_impl/distances_avx512.cpp +263 -15
  165. data/vendor/faiss/faiss/utils/simd_impl/distances_rvv.cpp +198 -18
  166. data/vendor/faiss/faiss/utils/simd_impl/rabitq_avx2.cpp +245 -0
  167. data/vendor/faiss/faiss/utils/simd_impl/rabitq_avx512.cpp +330 -40
  168. data/vendor/faiss/faiss/utils/simd_impl/{rabitq_avx512_spr.cpp → rabitq_avx512_vpopcnt.cpp} +112 -23
  169. data/vendor/faiss/faiss/utils/simd_impl/rabitq_neon.cpp +11 -0
  170. data/vendor/faiss/faiss/utils/simd_impl/rabitq_rvv.cpp +143 -6
  171. data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_dispatch.h +2 -7
  172. data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_kernels.h +6 -1
  173. data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_kernels_sve.cpp +34 -0
  174. data/vendor/faiss/faiss/utils/simd_levels.cpp +196 -47
  175. data/vendor/faiss/faiss/utils/simd_levels.h +33 -8
  176. data/vendor/faiss/faiss/utils/utils.cpp +9 -27
  177. metadata +21 -5
  178. data/vendor/faiss/faiss/utils/hamming_distance/hamming_avx512_spr.cpp +0 -15
@@ -7,8 +7,10 @@
7
7
 
8
8
  #pragma once
9
9
 
10
+ #include <functional>
10
11
  #include <optional>
11
12
  #include <queue>
13
+ #include <utility>
12
14
  #include <vector>
13
15
 
14
16
  #include <omp.h>
@@ -152,8 +154,14 @@ struct HNSW {
152
154
  /// use bounded queue during exploration
153
155
  bool search_bounded_queue = true;
154
156
 
155
- /// use Panorama progressive pruning in search
156
- bool is_panorama = false;
157
+ /// Specialized level-0 search implementations. This state is derived from
158
+ /// the owning IndexHNSW subtype after construction or deserialization.
159
+ enum Search_method_t : uint8_t {
160
+ SM_DEFAULT,
161
+ SM_PANORAMA,
162
+ SM_RABITQ,
163
+ };
164
+ Search_method_t search_method = SM_DEFAULT;
157
165
 
158
166
  /// distance comparison semantics: when true, distances are treated as
159
167
  /// similarity scores (larger is better). Default false matches the
@@ -204,14 +212,27 @@ struct HNSW {
204
212
  VisitedTable& vt,
205
213
  bool keep_max_size_level0 = false);
206
214
 
207
- /** add point pt_id on all levels <= pt_level and build the link
208
- * structure for them. */
209
- void add_with_locks(
215
+ /** Deterministic build, phase A: write pt_id's forward links against an
216
+ * immutable snapshot, touching only pt_id's own slots. Reciprocal edges
217
+ * are collected in `pt_reverse_edges` for phase B, not applied. Requires
218
+ * entry_point set. */
219
+ void compute_forward_links_deterministic(
210
220
  DistanceComputer& ptdis,
211
221
  int pt_level,
212
- int pt_id,
213
- LockVector& locks,
222
+ storage_idx_t pt_id,
214
223
  VisitedTable& vt,
224
+ std::vector<std::pair<storage_idx_t, int>>& pt_reverse_edges,
225
+ bool keep_max_size_level0 = false);
226
+
227
+ /** Deterministic build, phase B: merge `incoming` into `node` and
228
+ * re-prune in a total order (distance, ties by id) so the result is
229
+ * order-independent. Touches only `node`'s slots; `incoming` is sorted
230
+ * and deduplicated in place. */
231
+ void merge_reverse_links_deterministic(
232
+ DistanceComputer& dis,
233
+ storage_idx_t node,
234
+ int level,
235
+ std::vector<storage_idx_t>& incoming,
215
236
  bool keep_max_size_level0 = false);
216
237
 
217
238
  /// Search interface for 1 point, single thread
@@ -257,6 +278,24 @@ struct HNSW {
257
278
  void permute_entries(const idx_t* map);
258
279
  };
259
280
 
281
+ /** Deterministic, lock-free HNSW graph build. This is the only graph build
282
+ * that add() uses, and it is shared by IndexHNSW and
283
+ * IndexBinaryHNSW. The callbacks let both share the algorithm:
284
+ * `make_distance_computer()` returns a fresh DistanceComputer per thread
285
+ * (caller-owned) and `set_query(dc, pt_id)` points it at pt_id's vector. */
286
+ void hnsw_add_vertices_deterministic(
287
+ HNSW& hnsw,
288
+ size_t n0,
289
+ size_t n,
290
+ int d,
291
+ bool init_level0,
292
+ bool keep_max_size_level0,
293
+ bool preset_levels,
294
+ bool verbose,
295
+ const std::function<DistanceComputer*()>& make_distance_computer,
296
+ const std::function<void(DistanceComputer&, HNSW::storage_idx_t)>&
297
+ set_query);
298
+
260
299
  struct HNSWStats {
261
300
  size_t n1 = 0; /// number of vectors searched
262
301
  size_t n2 =
@@ -23,6 +23,50 @@ struct IDSelector {
23
23
  virtual ~IDSelector() {}
24
24
  };
25
25
 
26
+ /** Scan context handed to IDSelectorWithContext::is_member_with_context: the
27
+ * contiguous id block being scanned and the position of the id under test. */
28
+ struct IDScanContext {
29
+ /// the contiguous block of ids being scanned
30
+ const idx_t* ids;
31
+ /// number of entries in `ids`
32
+ size_t list_size;
33
+ /// index of the tested id within `ids` (i.e. ids[j] == id)
34
+ size_t j;
35
+ };
36
+
37
+ /** IDSelector that also receives the surrounding scan context on each
38
+ * membership test, letting an implementation exploit locality across a scan
39
+ * (for example, prefetching data for an entry it will be asked about soon).
40
+ * The policy is entirely up to the implementation. */
41
+ struct IDSelectorWithContext : IDSelector {
42
+ virtual bool is_member_with_context(idx_t id, const IDScanContext& ctx)
43
+ const = 0;
44
+ };
45
+
46
+ /** Routes each per-candidate membership test to is_member_with_context() when
47
+ * the selector implements IDSelectorWithContext, else to plain is_member().
48
+ * Construct one per inverted-list scan: the dynamic_cast is the only RTTI cost
49
+ * and the per-candidate cost is a single predicted branch (cf. the
50
+ * IDSelectorRange dynamic_cast in IndexIVF.cpp). The scan context is only
51
+ * meaningful when the scan exposes a real id array (i.e. !store_pairs); when
52
+ * store_pairs the context path is disabled and every test falls back to
53
+ * is_member. */
54
+ struct IDSelectorContextDispatch {
55
+ const IDSelector* sel;
56
+ const IDSelectorWithContext* ctx_sel;
57
+
58
+ IDSelectorContextDispatch(const IDSelector* sel, bool store_pairs)
59
+ : sel(sel),
60
+ ctx_sel((sel != nullptr && !store_pairs)
61
+ ? dynamic_cast<const IDSelectorWithContext*>(sel)
62
+ : nullptr) {}
63
+
64
+ bool is_member(idx_t id, const IDScanContext& ctx) const {
65
+ return ctx_sel ? ctx_sel->is_member_with_context(id, ctx)
66
+ : sel->is_member(id);
67
+ }
68
+ };
69
+
26
70
  /** ids between [imin, imax) */
27
71
  struct IDSelectorRange : IDSelector {
28
72
  idx_t imin, imax;
@@ -597,8 +597,8 @@ void LocalSearchQuantizer::icm_encode_step(
597
597
  const float* binaries,
598
598
  size_t n,
599
599
  size_t n_iters) const {
600
- FAISS_THROW_IF_NOT(M != 0 && K != 0);
601
- FAISS_THROW_IF_NOT(binaries != nullptr);
600
+ FAISS_THROW_IF_MSG(M == 0 || K == 0, "M and K must be nonzero");
601
+ FAISS_THROW_IF_NOT(binaries);
602
602
 
603
603
  // Resolve SIMD level once, not per iteration of the n × n_iters × M loop.
604
604
  with_simd_level_256bit([&]<SIMDLevel SL>() {
@@ -264,6 +264,8 @@ void NNDescent::update() {
264
264
  auto& nn_new = node.nn_new;
265
265
  auto& nn_old = node.nn_old;
266
266
 
267
+ // other.pool.back() below is read without other.lock: in this
268
+ // phase no thread reorders or resizes a pool. Lock guards rnn_*.
267
269
  for (int l = 0; l < node.M; ++l) {
268
270
  auto& nn = node.pool[l];
269
271
  auto& other = graph[nn.id]; // the other side of the edge
@@ -283,7 +285,6 @@ void NNDescent::update() {
283
285
  }
284
286
  }
285
287
  nn.flag = false;
286
-
287
288
  } else { // the node is old
288
289
  // push the neighbor into nn_old
289
290
  nn_old.push_back(nn.id);
@@ -300,11 +301,17 @@ void NNDescent::update() {
300
301
  }
301
302
  }
302
303
  }
303
- // make heap to join later (in join() function)
304
- std::make_heap(node.pool.begin(), node.pool.end());
305
304
  }
306
305
  }
307
306
 
307
+ // Restore the max-heap invariant for the next join()'s insert(). Must
308
+ // stay a separate region: re-heapifying during Step 3 would race with
309
+ // its unlocked other.pool.back() reads.
310
+ #pragma omp parallel for
311
+ for (int n = 0; n < ntotal; ++n) {
312
+ std::make_heap(graph[n].pool.begin(), graph[n].pool.end());
313
+ }
314
+
308
315
  // Step 4.
309
316
  // Combine the forward and the reverse links
310
317
  // R = 0 means no reverse links are used.
@@ -142,7 +142,9 @@ void NSG::build(
142
142
  idx_t n,
143
143
  const nsg::Graph<idx_t>& knn_graph,
144
144
  bool verbose) {
145
- FAISS_THROW_IF_NOT(!is_built && ntotal == 0);
145
+ FAISS_THROW_IF_MSG(
146
+ is_built || ntotal != 0,
147
+ "NSG graph must be empty and not yet built");
146
148
 
147
149
  if (verbose) {
148
150
  printf("NSG::build R=%d, L=%d, C=%d\n", R, L, C);
@@ -49,18 +49,20 @@ static inline void compute_level_dot_kernel(
49
49
  const uint32_t* active_indices,
50
50
  const size_t num_active,
51
51
  const size_t level_width_dims,
52
- float* FAISS_RESTRICT dot_products) {
52
+ float* FAISS_RESTRICT dot_products,
53
+ size_t stride = 0) {
53
54
  const size_t width = LevelWidth > 0 ? LevelWidth : level_width_dims;
55
+ const size_t row_stride = stride == 0 ? width : stride;
54
56
  size_t i = 0;
55
57
  for (; i + 4 <= num_active; i += 4) {
56
58
  const float* y0 = level_storage +
57
- (AllActive ? (i + 0) : active_indices[i + 0]) * width;
59
+ (AllActive ? (i + 0) : active_indices[i + 0]) * row_stride;
58
60
  const float* y1 = level_storage +
59
- (AllActive ? (i + 1) : active_indices[i + 1]) * width;
61
+ (AllActive ? (i + 1) : active_indices[i + 1]) * row_stride;
60
62
  const float* y2 = level_storage +
61
- (AllActive ? (i + 2) : active_indices[i + 2]) * width;
63
+ (AllActive ? (i + 2) : active_indices[i + 2]) * row_stride;
62
64
  const float* y3 = level_storage +
63
- (AllActive ? (i + 3) : active_indices[i + 3]) * width;
65
+ (AllActive ? (i + 3) : active_indices[i + 3]) * row_stride;
64
66
 
65
67
  float dp0 = 0, dp1 = 0, dp2 = 0, dp3 = 0;
66
68
  FAISS_PRAGMA_IMPRECISE_LOOP
@@ -78,8 +80,8 @@ static inline void compute_level_dot_kernel(
78
80
  dot_products[i + 3] = dp3;
79
81
  }
80
82
  for (; i < num_active; i++) {
81
- const float* yj =
82
- level_storage + (AllActive ? i : active_indices[i]) * width;
83
+ const float* yj = level_storage +
84
+ (AllActive ? i : active_indices[i]) * row_stride;
83
85
  float dp = 0;
84
86
  FAISS_PRAGMA_IMPRECISE_LOOP
85
87
  for (size_t j = 0; j < width; j++) {
@@ -233,7 +235,8 @@ inline auto with_bool(bool value, Lambda&& fn) {
233
235
  * accelerating the refinement stage.
234
236
  */
235
237
  struct Panorama {
236
- static constexpr size_t kDefaultBatchSize = 128;
238
+ static constexpr size_t kDefaultBatchSize = 1024;
239
+ static constexpr size_t kLegacyBatchSize = 128;
237
240
 
238
241
  size_t d = 0;
239
242
  size_t code_size = 0;
@@ -320,12 +323,20 @@ struct Panorama {
320
323
  size_t batch_offset = batch_no * batch_size * code_size;
321
324
  const uint8_t* storage_base = codes_base + batch_offset;
322
325
 
326
+ // Honor IDSelectorWithContext: the scan-order position within the whole
327
+ // list is global_idx, so a lookahead crosses batch boundaries correctly
328
+ // (ids is the full list, length list_size).
329
+ IDSelectorContextDispatch sel_dispatch(
330
+ sel, /*store_pairs=*/ids == nullptr);
331
+
323
332
  // Initialize active set with ID-filtered vectors.
324
333
  size_t num_active = 0;
325
334
  for (size_t i = 0; i < curr_batch_size; i++) {
326
335
  size_t global_idx = batch_start + i;
327
336
  idx_t id = (ids == nullptr) ? global_idx : ids[global_idx];
328
- bool include = !use_sel || sel->is_member(id);
337
+ bool include = !use_sel ||
338
+ sel_dispatch.is_member(
339
+ id, IDScanContext{ids, list_size, global_idx});
329
340
 
330
341
  active_indices[num_active] = i;
331
342
  float cum_sum = batch_cum_sums[i];
@@ -26,6 +26,8 @@
26
26
 
27
27
  #include <faiss/impl/FaissAssert.h>
28
28
 
29
+ #include <faiss/impl/polysemous_training/dispatch.h>
30
+
29
31
  /*****************************************
30
32
  * Mixed PQ / Hamming
31
33
  ******************************************/
@@ -168,6 +170,94 @@ static inline int hamming_dis(uint64_t a, uint64_t b) {
168
170
  return popcount64(a ^ b);
169
171
  }
170
172
 
173
+ static inline double sqr(double x) {
174
+ return x * x;
175
+ }
176
+
177
+ // Scalar (NONE) kernels for the two objectives. The dispatch boundary lives in
178
+ // the objective methods below, which route to these or to the AVX-512
179
+ // specializations (in polysemous_training/avx512.cpp) via
180
+ // with_selected_simd_levels.
181
+ namespace polysemous_training {
182
+
183
+ template <>
184
+ double hamming_compute_cost<SIMDLevel::NONE>(
185
+ int n,
186
+ const int* perm,
187
+ const double* target_dis,
188
+ const double* weights) {
189
+ double cost = 0;
190
+ for (int i = 0; i < n; i++) {
191
+ for (int j = 0; j < n; j++) {
192
+ double wanted = target_dis[i * n + j];
193
+ double w = weights[i * n + j];
194
+ double actual = hamming_dis(perm[i], perm[j]);
195
+ cost += w * sqr(wanted - actual);
196
+ }
197
+ }
198
+ return cost;
199
+ }
200
+
201
+ template <>
202
+ double hamming_cost_update<SIMDLevel::NONE>(
203
+ int n,
204
+ const int* perm,
205
+ int iw,
206
+ int jw,
207
+ const double* target_dis,
208
+ const double* weights) {
209
+ double delta_cost = 0;
210
+
211
+ for (int i = 0; i < n; i++) {
212
+ if (i == iw) {
213
+ for (int j = 0; j < n; j++) {
214
+ double wanted = target_dis[i * n + j], w = weights[i * n + j];
215
+ double actual = hamming_dis(perm[i], perm[j]);
216
+ delta_cost -= w * sqr(wanted - actual);
217
+ double new_actual = hamming_dis(
218
+ perm[jw],
219
+ perm[j == iw ? jw
220
+ : j == jw ? iw
221
+ : j]);
222
+ delta_cost += w * sqr(wanted - new_actual);
223
+ }
224
+ } else if (i == jw) {
225
+ for (int j = 0; j < n; j++) {
226
+ double wanted = target_dis[i * n + j], w = weights[i * n + j];
227
+ double actual = hamming_dis(perm[i], perm[j]);
228
+ delta_cost -= w * sqr(wanted - actual);
229
+ double new_actual = hamming_dis(
230
+ perm[iw],
231
+ perm[j == iw ? jw
232
+ : j == jw ? iw
233
+ : j]);
234
+ delta_cost += w * sqr(wanted - new_actual);
235
+ }
236
+ } else {
237
+ int j = iw;
238
+ {
239
+ double wanted = target_dis[i * n + j], w = weights[i * n + j];
240
+ double actual = hamming_dis(perm[i], perm[j]);
241
+ delta_cost -= w * sqr(wanted - actual);
242
+ double new_actual = hamming_dis(perm[i], perm[jw]);
243
+ delta_cost += w * sqr(wanted - new_actual);
244
+ }
245
+ j = jw;
246
+ {
247
+ double wanted = target_dis[i * n + j], w = weights[i * n + j];
248
+ double actual = hamming_dis(perm[i], perm[j]);
249
+ delta_cost -= w * sqr(wanted - actual);
250
+ double new_actual = hamming_dis(perm[i], perm[iw]);
251
+ delta_cost += w * sqr(wanted - new_actual);
252
+ }
253
+ }
254
+ }
255
+
256
+ return delta_cost;
257
+ }
258
+
259
+ } // namespace polysemous_training
260
+
171
261
  namespace {
172
262
 
173
263
  /// optimize permutation to reproduce a distance table with Hamming distances
@@ -175,10 +265,6 @@ struct ReproduceWithHammingObjective : PermutationObjective {
175
265
  int nbits;
176
266
  double dis_weight_factor;
177
267
 
178
- static double sqr(double x) {
179
- return x * x;
180
- }
181
-
182
268
  // weighting of distances: it is more important to reproduce small
183
269
  // distances well
184
270
  double dis_weight(double x) const {
@@ -190,73 +276,21 @@ struct ReproduceWithHammingObjective : PermutationObjective {
190
276
 
191
277
  // cost = quadratic difference between actual distance and Hamming distance
192
278
  double compute_cost(const int* perm) const override {
193
- double cost = 0;
194
- for (int i = 0; i < n; i++) {
195
- for (int j = 0; j < n; j++) {
196
- double wanted = target_dis[i * n + j];
197
- double w = weights[i * n + j];
198
- double actual = hamming_dis(perm[i], perm[j]);
199
- cost += w * sqr(wanted - actual);
200
- }
201
- }
202
- return cost;
279
+ return with_selected_simd_levels<polysemous_training::SIMD_LEVELS>(
280
+ [&]<SIMDLevel SL>() {
281
+ return polysemous_training::hamming_compute_cost<SL>(
282
+ n, perm, target_dis.data(), weights.data());
283
+ });
203
284
  }
204
285
 
205
286
  // what would the cost update be if iw and jw were swapped?
206
287
  // computed in O(n) instead of O(n^2) for the full re-computation
207
288
  double cost_update(const int* perm, int iw, int jw) const override {
208
- double delta_cost = 0;
209
-
210
- for (int i = 0; i < n; i++) {
211
- if (i == iw) {
212
- for (int j = 0; j < n; j++) {
213
- double wanted = target_dis[i * n + j],
214
- w = weights[i * n + j];
215
- double actual = hamming_dis(perm[i], perm[j]);
216
- delta_cost -= w * sqr(wanted - actual);
217
- double new_actual = hamming_dis(
218
- perm[jw],
219
- perm[j == iw ? jw
220
- : j == jw ? iw
221
- : j]);
222
- delta_cost += w * sqr(wanted - new_actual);
223
- }
224
- } else if (i == jw) {
225
- for (int j = 0; j < n; j++) {
226
- double wanted = target_dis[i * n + j],
227
- w = weights[i * n + j];
228
- double actual = hamming_dis(perm[i], perm[j]);
229
- delta_cost -= w * sqr(wanted - actual);
230
- double new_actual = hamming_dis(
231
- perm[iw],
232
- perm[j == iw ? jw
233
- : j == jw ? iw
234
- : j]);
235
- delta_cost += w * sqr(wanted - new_actual);
236
- }
237
- } else {
238
- int j = iw;
239
- {
240
- double wanted = target_dis[i * n + j],
241
- w = weights[i * n + j];
242
- double actual = hamming_dis(perm[i], perm[j]);
243
- delta_cost -= w * sqr(wanted - actual);
244
- double new_actual = hamming_dis(perm[i], perm[jw]);
245
- delta_cost += w * sqr(wanted - new_actual);
246
- }
247
- j = jw;
248
- {
249
- double wanted = target_dis[i * n + j],
250
- w = weights[i * n + j];
251
- double actual = hamming_dis(perm[i], perm[j]);
252
- delta_cost -= w * sqr(wanted - actual);
253
- double new_actual = hamming_dis(perm[i], perm[iw]);
254
- delta_cost += w * sqr(wanted - new_actual);
255
- }
256
- }
257
- }
258
-
259
- return delta_cost;
289
+ return with_selected_simd_levels<polysemous_training::SIMD_LEVELS>(
290
+ [&]<SIMDLevel SL>() {
291
+ return polysemous_training::hamming_cost_update<SL>(
292
+ n, perm, iw, jw, target_dis.data(), weights.data());
293
+ });
260
294
  }
261
295
 
262
296
  ReproduceWithHammingObjective(
@@ -306,14 +340,20 @@ double ReproduceDistancesObjective::get_source_dis(int i, int j) const {
306
340
  return source_dis[i * n + j];
307
341
  }
308
342
 
343
+ namespace polysemous_training {
344
+
309
345
  // cost = quadratic difference between actual distance and Hamming distance
310
- double ReproduceDistancesObjective::compute_cost(const int* perm) const {
346
+ template <>
347
+ double distances_compute_cost<SIMDLevel::NONE>(
348
+ const ReproduceDistancesObjective& obj,
349
+ const int* perm) {
350
+ const int n = obj.n;
311
351
  double cost = 0;
312
352
  for (int i = 0; i < n; i++) {
313
353
  for (int j = 0; j < n; j++) {
314
- double wanted = target_dis[i * n + j];
315
- double w = weights[i * n + j];
316
- double actual = get_source_dis(perm[i], perm[j]);
354
+ double wanted = obj.target_dis[i * n + j];
355
+ double w = obj.weights[i * n + j];
356
+ double actual = obj.get_source_dis(perm[i], perm[j]);
317
357
  cost += w * sqr(wanted - actual);
318
358
  }
319
359
  }
@@ -322,16 +362,22 @@ double ReproduceDistancesObjective::compute_cost(const int* perm) const {
322
362
 
323
363
  // what would the cost update be if iw and jw were swapped?
324
364
  // computed in O(n) instead of O(n^2) for the full re-computation
325
- double ReproduceDistancesObjective::cost_update(const int* perm, int iw, int jw)
326
- const {
365
+ template <>
366
+ double distances_cost_update<SIMDLevel::NONE>(
367
+ const ReproduceDistancesObjective& obj,
368
+ const int* perm,
369
+ int iw,
370
+ int jw) {
371
+ const int n = obj.n;
327
372
  double delta_cost = 0;
328
373
  for (int i = 0; i < n; i++) {
329
374
  if (i == iw) {
330
375
  for (int j = 0; j < n; j++) {
331
- double wanted = target_dis[i * n + j], w = weights[i * n + j];
332
- double actual = get_source_dis(perm[i], perm[j]);
376
+ double wanted = obj.target_dis[i * n + j],
377
+ w = obj.weights[i * n + j];
378
+ double actual = obj.get_source_dis(perm[i], perm[j]);
333
379
  delta_cost -= w * sqr(wanted - actual);
334
- double new_actual = get_source_dis(
380
+ double new_actual = obj.get_source_dis(
335
381
  perm[jw],
336
382
  perm[j == iw ? jw
337
383
  : j == jw ? iw
@@ -340,10 +386,11 @@ double ReproduceDistancesObjective::cost_update(const int* perm, int iw, int jw)
340
386
  }
341
387
  } else if (i == jw) {
342
388
  for (int j = 0; j < n; j++) {
343
- double wanted = target_dis[i * n + j], w = weights[i * n + j];
344
- double actual = get_source_dis(perm[i], perm[j]);
389
+ double wanted = obj.target_dis[i * n + j],
390
+ w = obj.weights[i * n + j];
391
+ double actual = obj.get_source_dis(perm[i], perm[j]);
345
392
  delta_cost -= w * sqr(wanted - actual);
346
- double new_actual = get_source_dis(
393
+ double new_actual = obj.get_source_dis(
347
394
  perm[iw],
348
395
  perm[j == iw ? jw
349
396
  : j == jw ? iw
@@ -353,18 +400,20 @@ double ReproduceDistancesObjective::cost_update(const int* perm, int iw, int jw)
353
400
  } else {
354
401
  int j = iw;
355
402
  {
356
- double wanted = target_dis[i * n + j], w = weights[i * n + j];
357
- double actual = get_source_dis(perm[i], perm[j]);
403
+ double wanted = obj.target_dis[i * n + j],
404
+ w = obj.weights[i * n + j];
405
+ double actual = obj.get_source_dis(perm[i], perm[j]);
358
406
  delta_cost -= w * sqr(wanted - actual);
359
- double new_actual = get_source_dis(perm[i], perm[jw]);
407
+ double new_actual = obj.get_source_dis(perm[i], perm[jw]);
360
408
  delta_cost += w * sqr(wanted - new_actual);
361
409
  }
362
410
  j = jw;
363
411
  {
364
- double wanted = target_dis[i * n + j], w = weights[i * n + j];
365
- double actual = get_source_dis(perm[i], perm[j]);
412
+ double wanted = obj.target_dis[i * n + j],
413
+ w = obj.weights[i * n + j];
414
+ double actual = obj.get_source_dis(perm[i], perm[j]);
366
415
  delta_cost -= w * sqr(wanted - actual);
367
- double new_actual = get_source_dis(perm[i], perm[iw]);
416
+ double new_actual = obj.get_source_dis(perm[i], perm[iw]);
368
417
  delta_cost += w * sqr(wanted - new_actual);
369
418
  }
370
419
  }
@@ -372,6 +421,25 @@ double ReproduceDistancesObjective::cost_update(const int* perm, int iw, int jw)
372
421
  return delta_cost;
373
422
  }
374
423
 
424
+ } // namespace polysemous_training
425
+
426
+ double ReproduceDistancesObjective::compute_cost(const int* perm) const {
427
+ return with_selected_simd_levels<polysemous_training::SIMD_LEVELS>(
428
+ [&]<SIMDLevel SL>() {
429
+ return polysemous_training::distances_compute_cost<SL>(
430
+ *this, perm);
431
+ });
432
+ }
433
+
434
+ double ReproduceDistancesObjective::cost_update(const int* perm, int iw, int jw)
435
+ const {
436
+ return with_selected_simd_levels<polysemous_training::SIMD_LEVELS>(
437
+ [&]<SIMDLevel SL>() {
438
+ return polysemous_training::distances_cost_update<SL>(
439
+ *this, perm, iw, jw);
440
+ });
441
+ }
442
+
375
443
  ReproduceDistancesObjective::ReproduceDistancesObjective(
376
444
  int n_in,
377
445
  const double* source_dis_in,