faiss 0.4.2 → 0.5.0

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 (153) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/ext/faiss/index.cpp +36 -10
  4. data/ext/faiss/index_binary.cpp +19 -6
  5. data/ext/faiss/kmeans.cpp +6 -6
  6. data/ext/faiss/numo.hpp +273 -123
  7. data/lib/faiss/version.rb +1 -1
  8. data/vendor/faiss/faiss/AutoTune.cpp +2 -3
  9. data/vendor/faiss/faiss/AutoTune.h +1 -1
  10. data/vendor/faiss/faiss/Clustering.cpp +2 -2
  11. data/vendor/faiss/faiss/Clustering.h +2 -2
  12. data/vendor/faiss/faiss/IVFlib.cpp +1 -2
  13. data/vendor/faiss/faiss/IVFlib.h +1 -1
  14. data/vendor/faiss/faiss/Index.h +10 -10
  15. data/vendor/faiss/faiss/Index2Layer.cpp +1 -1
  16. data/vendor/faiss/faiss/Index2Layer.h +2 -2
  17. data/vendor/faiss/faiss/IndexAdditiveQuantizerFastScan.cpp +9 -4
  18. data/vendor/faiss/faiss/IndexAdditiveQuantizerFastScan.h +5 -1
  19. data/vendor/faiss/faiss/IndexBinary.h +7 -7
  20. data/vendor/faiss/faiss/IndexBinaryFromFloat.h +1 -1
  21. data/vendor/faiss/faiss/IndexBinaryHNSW.cpp +3 -1
  22. data/vendor/faiss/faiss/IndexBinaryHNSW.h +1 -1
  23. data/vendor/faiss/faiss/IndexBinaryHash.cpp +3 -3
  24. data/vendor/faiss/faiss/IndexBinaryHash.h +5 -5
  25. data/vendor/faiss/faiss/IndexBinaryIVF.cpp +7 -6
  26. data/vendor/faiss/faiss/IndexFastScan.cpp +125 -49
  27. data/vendor/faiss/faiss/IndexFastScan.h +107 -7
  28. data/vendor/faiss/faiss/IndexFlat.h +1 -1
  29. data/vendor/faiss/faiss/IndexHNSW.cpp +3 -1
  30. data/vendor/faiss/faiss/IndexHNSW.h +1 -1
  31. data/vendor/faiss/faiss/IndexIDMap.cpp +14 -13
  32. data/vendor/faiss/faiss/IndexIDMap.h +6 -6
  33. data/vendor/faiss/faiss/IndexIVF.cpp +1 -1
  34. data/vendor/faiss/faiss/IndexIVF.h +5 -5
  35. data/vendor/faiss/faiss/IndexIVFAdditiveQuantizer.cpp +1 -1
  36. data/vendor/faiss/faiss/IndexIVFAdditiveQuantizerFastScan.cpp +9 -3
  37. data/vendor/faiss/faiss/IndexIVFAdditiveQuantizerFastScan.h +3 -1
  38. data/vendor/faiss/faiss/IndexIVFFastScan.cpp +176 -90
  39. data/vendor/faiss/faiss/IndexIVFFastScan.h +173 -18
  40. data/vendor/faiss/faiss/IndexIVFFlat.cpp +1 -0
  41. data/vendor/faiss/faiss/IndexIVFFlatPanorama.cpp +366 -0
  42. data/vendor/faiss/faiss/IndexIVFFlatPanorama.h +64 -0
  43. data/vendor/faiss/faiss/IndexIVFPQ.cpp +3 -1
  44. data/vendor/faiss/faiss/IndexIVFPQ.h +1 -1
  45. data/vendor/faiss/faiss/IndexIVFPQFastScan.cpp +134 -2
  46. data/vendor/faiss/faiss/IndexIVFPQFastScan.h +7 -1
  47. data/vendor/faiss/faiss/IndexIVFRaBitQ.cpp +13 -6
  48. data/vendor/faiss/faiss/IndexIVFRaBitQ.h +1 -0
  49. data/vendor/faiss/faiss/IndexIVFRaBitQFastScan.cpp +650 -0
  50. data/vendor/faiss/faiss/IndexIVFRaBitQFastScan.h +216 -0
  51. data/vendor/faiss/faiss/IndexIVFSpectralHash.cpp +1 -1
  52. data/vendor/faiss/faiss/IndexIVFSpectralHash.h +1 -1
  53. data/vendor/faiss/faiss/IndexNNDescent.cpp +1 -1
  54. data/vendor/faiss/faiss/IndexNSG.cpp +1 -1
  55. data/vendor/faiss/faiss/IndexNeuralNetCodec.h +1 -1
  56. data/vendor/faiss/faiss/IndexPQ.h +1 -1
  57. data/vendor/faiss/faiss/IndexPQFastScan.cpp +6 -2
  58. data/vendor/faiss/faiss/IndexPQFastScan.h +5 -1
  59. data/vendor/faiss/faiss/IndexRaBitQ.cpp +13 -10
  60. data/vendor/faiss/faiss/IndexRaBitQ.h +7 -2
  61. data/vendor/faiss/faiss/IndexRaBitQFastScan.cpp +586 -0
  62. data/vendor/faiss/faiss/IndexRaBitQFastScan.h +149 -0
  63. data/vendor/faiss/faiss/IndexShards.cpp +1 -1
  64. data/vendor/faiss/faiss/MatrixStats.cpp +3 -3
  65. data/vendor/faiss/faiss/MetricType.h +1 -1
  66. data/vendor/faiss/faiss/VectorTransform.h +2 -2
  67. data/vendor/faiss/faiss/clone_index.cpp +3 -1
  68. data/vendor/faiss/faiss/gpu/GpuCloner.cpp +1 -1
  69. data/vendor/faiss/faiss/gpu/GpuIndex.h +11 -11
  70. data/vendor/faiss/faiss/gpu/GpuIndexBinaryCagra.h +1 -1
  71. data/vendor/faiss/faiss/gpu/GpuIndexBinaryFlat.h +1 -1
  72. data/vendor/faiss/faiss/gpu/GpuIndexCagra.h +10 -6
  73. data/vendor/faiss/faiss/gpu/perf/IndexWrapper-inl.h +2 -0
  74. data/vendor/faiss/faiss/gpu/test/TestGpuIcmEncoder.cpp +7 -0
  75. data/vendor/faiss/faiss/gpu/test/TestGpuIndexIVFFlat.cpp +1 -1
  76. data/vendor/faiss/faiss/impl/AdditiveQuantizer.cpp +1 -1
  77. data/vendor/faiss/faiss/impl/AdditiveQuantizer.h +1 -1
  78. data/vendor/faiss/faiss/impl/AuxIndexStructures.cpp +2 -2
  79. data/vendor/faiss/faiss/impl/AuxIndexStructures.h +1 -1
  80. data/vendor/faiss/faiss/impl/CodePacker.h +2 -2
  81. data/vendor/faiss/faiss/impl/DistanceComputer.h +3 -3
  82. data/vendor/faiss/faiss/impl/FastScanDistancePostProcessing.h +53 -0
  83. data/vendor/faiss/faiss/impl/HNSW.cpp +1 -1
  84. data/vendor/faiss/faiss/impl/HNSW.h +4 -4
  85. data/vendor/faiss/faiss/impl/IDSelector.cpp +2 -2
  86. data/vendor/faiss/faiss/impl/IDSelector.h +1 -1
  87. data/vendor/faiss/faiss/impl/LocalSearchQuantizer.cpp +4 -4
  88. data/vendor/faiss/faiss/impl/LocalSearchQuantizer.h +1 -1
  89. data/vendor/faiss/faiss/impl/LookupTableScaler.h +1 -1
  90. data/vendor/faiss/faiss/impl/NNDescent.cpp +1 -1
  91. data/vendor/faiss/faiss/impl/NNDescent.h +2 -2
  92. data/vendor/faiss/faiss/impl/NSG.cpp +1 -1
  93. data/vendor/faiss/faiss/impl/PanoramaStats.cpp +33 -0
  94. data/vendor/faiss/faiss/impl/PanoramaStats.h +38 -0
  95. data/vendor/faiss/faiss/impl/PolysemousTraining.cpp +5 -5
  96. data/vendor/faiss/faiss/impl/ProductAdditiveQuantizer.cpp +1 -1
  97. data/vendor/faiss/faiss/impl/ProductAdditiveQuantizer.h +1 -1
  98. data/vendor/faiss/faiss/impl/ProductQuantizer-inl.h +2 -0
  99. data/vendor/faiss/faiss/impl/ProductQuantizer.h +1 -1
  100. data/vendor/faiss/faiss/impl/RaBitQUtils.cpp +246 -0
  101. data/vendor/faiss/faiss/impl/RaBitQUtils.h +153 -0
  102. data/vendor/faiss/faiss/impl/RaBitQuantizer.cpp +54 -158
  103. data/vendor/faiss/faiss/impl/RaBitQuantizer.h +2 -1
  104. data/vendor/faiss/faiss/impl/ResidualQuantizer.h +1 -1
  105. data/vendor/faiss/faiss/impl/ResultHandler.h +4 -4
  106. data/vendor/faiss/faiss/impl/ScalarQuantizer.cpp +1 -1
  107. data/vendor/faiss/faiss/impl/ScalarQuantizer.h +1 -1
  108. data/vendor/faiss/faiss/impl/ThreadedIndex-inl.h +7 -4
  109. data/vendor/faiss/faiss/impl/index_read.cpp +87 -3
  110. data/vendor/faiss/faiss/impl/index_write.cpp +73 -3
  111. data/vendor/faiss/faiss/impl/io.cpp +2 -2
  112. data/vendor/faiss/faiss/impl/io.h +4 -4
  113. data/vendor/faiss/faiss/impl/kmeans1d.cpp +1 -1
  114. data/vendor/faiss/faiss/impl/kmeans1d.h +1 -1
  115. data/vendor/faiss/faiss/impl/lattice_Zn.h +2 -2
  116. data/vendor/faiss/faiss/impl/mapped_io.cpp +2 -2
  117. data/vendor/faiss/faiss/impl/mapped_io.h +4 -3
  118. data/vendor/faiss/faiss/impl/maybe_owned_vector.h +8 -1
  119. data/vendor/faiss/faiss/impl/pq4_fast_scan.cpp +30 -4
  120. data/vendor/faiss/faiss/impl/pq4_fast_scan.h +14 -8
  121. data/vendor/faiss/faiss/impl/pq4_fast_scan_search_qbs.cpp +5 -6
  122. data/vendor/faiss/faiss/impl/simd_result_handlers.h +55 -11
  123. data/vendor/faiss/faiss/impl/zerocopy_io.h +1 -1
  124. data/vendor/faiss/faiss/index_factory.cpp +43 -1
  125. data/vendor/faiss/faiss/index_factory.h +1 -1
  126. data/vendor/faiss/faiss/index_io.h +1 -1
  127. data/vendor/faiss/faiss/invlists/InvertedLists.cpp +205 -0
  128. data/vendor/faiss/faiss/invlists/InvertedLists.h +62 -0
  129. data/vendor/faiss/faiss/utils/AlignedTable.h +1 -1
  130. data/vendor/faiss/faiss/utils/Heap.cpp +2 -2
  131. data/vendor/faiss/faiss/utils/Heap.h +3 -3
  132. data/vendor/faiss/faiss/utils/NeuralNet.cpp +1 -1
  133. data/vendor/faiss/faiss/utils/NeuralNet.h +3 -3
  134. data/vendor/faiss/faiss/utils/approx_topk/approx_topk.h +2 -2
  135. data/vendor/faiss/faiss/utils/approx_topk/avx2-inl.h +2 -2
  136. data/vendor/faiss/faiss/utils/approx_topk/mode.h +1 -1
  137. data/vendor/faiss/faiss/utils/distances.h +2 -2
  138. data/vendor/faiss/faiss/utils/extra_distances-inl.h +3 -1
  139. data/vendor/faiss/faiss/utils/hamming-inl.h +2 -0
  140. data/vendor/faiss/faiss/utils/hamming.cpp +7 -6
  141. data/vendor/faiss/faiss/utils/hamming.h +1 -1
  142. data/vendor/faiss/faiss/utils/hamming_distance/common.h +1 -2
  143. data/vendor/faiss/faiss/utils/partitioning.cpp +5 -5
  144. data/vendor/faiss/faiss/utils/partitioning.h +2 -2
  145. data/vendor/faiss/faiss/utils/rabitq_simd.h +222 -336
  146. data/vendor/faiss/faiss/utils/random.cpp +1 -1
  147. data/vendor/faiss/faiss/utils/simdlib_avx2.h +1 -1
  148. data/vendor/faiss/faiss/utils/simdlib_avx512.h +1 -1
  149. data/vendor/faiss/faiss/utils/simdlib_neon.h +2 -2
  150. data/vendor/faiss/faiss/utils/transpose/transpose-avx512-inl.h +1 -1
  151. data/vendor/faiss/faiss/utils/utils.cpp +5 -2
  152. data/vendor/faiss/faiss/utils/utils.h +2 -2
  153. metadata +14 -3
@@ -7,9 +7,8 @@
7
7
 
8
8
  #pragma once
9
9
 
10
- #include <memory>
11
-
12
10
  #include <faiss/IndexIVF.h>
11
+ #include <faiss/impl/FastScanDistancePostProcessing.h>
13
12
  #include <faiss/utils/AlignedTable.h>
14
13
 
15
14
  namespace faiss {
@@ -63,6 +62,15 @@ struct IndexIVFFastScan : IndexIVF {
63
62
  // quantizer used to pack the codes
64
63
  Quantizer* fine_quantizer = nullptr;
65
64
 
65
+ /** Constructor for IndexIVFFastScan
66
+ *
67
+ * @param quantizer coarse quantizer for IVF clustering
68
+ * @param d dimensionality of vectors
69
+ * @param nlist number of inverted lists
70
+ * @param code_size size of each code in bytes
71
+ * @param metric distance metric to use
72
+ * @param own_invlists whether to own the inverted lists
73
+ */
66
74
  IndexIVFFastScan(
67
75
  Index* quantizer,
68
76
  size_t d,
@@ -73,7 +81,16 @@ struct IndexIVFFastScan : IndexIVF {
73
81
 
74
82
  IndexIVFFastScan();
75
83
 
76
- /// called by implementations
84
+ /** Initialize the fast scan functionality (called by implementations)
85
+ *
86
+ * @param fine_quantizer fine quantizer for encoding
87
+ * @param M number of subquantizers
88
+ * @param nbits number of bits per subquantizer
89
+ * @param nlist number of inverted lists
90
+ * @param metric distance metric to use
91
+ * @param bbs block size for SIMD processing
92
+ * @param own_invlists whether to own the inverted lists
93
+ */
77
94
  void init_fastscan(
78
95
  Quantizer* fine_quantizer,
79
96
  size_t M,
@@ -91,34 +108,72 @@ struct IndexIVFFastScan : IndexIVF {
91
108
  /// orig's inverted lists (for debugging)
92
109
  InvertedLists* orig_invlists = nullptr;
93
110
 
111
+ /** Add vectors with specific IDs to the index
112
+ *
113
+ * @param n number of vectors to add
114
+ * @param x vectors to add (n * d)
115
+ * @param xids IDs for the vectors (n)
116
+ */
94
117
  void add_with_ids(idx_t n, const float* x, const idx_t* xids) override;
95
-
96
118
  // prepare look-up tables
97
119
 
98
120
  virtual bool lookup_table_is_3d() const = 0;
99
121
 
100
122
  // compact way of conveying coarse quantization results
101
123
  struct CoarseQuantized {
102
- size_t nprobe;
124
+ size_t nprobe = 0;
103
125
  const float* dis = nullptr;
104
126
  const idx_t* ids = nullptr;
105
127
  };
106
128
 
129
+ /* Compute distance table for query set, given a list of coarse
130
+ * quantizers.
131
+ *
132
+ * @param n number of queries
133
+ * @param x query vectors (n, d)
134
+ * @param cq coarse quantization results
135
+ * @param dis_tables output distance tables
136
+ * @param biases output bias values
137
+ * @param context processing context containing query factors
138
+ processor
139
+ */
107
140
  virtual void compute_LUT(
108
141
  size_t n,
109
142
  const float* x,
110
143
  const CoarseQuantized& cq,
111
144
  AlignedTable<float>& dis_tables,
112
- AlignedTable<float>& biases) const = 0;
145
+ AlignedTable<float>& biases,
146
+ const FastScanDistancePostProcessing& context) const = 0;
113
147
 
148
+ /** Compute quantized lookup tables for distance computation
149
+ *
150
+ * @param n number of query vectors
151
+ * @param x query vectors (n * d)
152
+ * @param cq coarse quantization results
153
+ * @param dis_tables output quantized distance tables
154
+ * @param biases output quantized bias values
155
+ * @param normalizers output normalization factors
156
+ * @param context processing context containing query factors
157
+ * processor
158
+ */
114
159
  void compute_LUT_uint8(
115
160
  size_t n,
116
161
  const float* x,
117
162
  const CoarseQuantized& cq,
118
163
  AlignedTable<uint8_t>& dis_tables,
119
164
  AlignedTable<uint16_t>& biases,
120
- float* normalizers) const;
165
+ float* normalizers,
166
+ const FastScanDistancePostProcessing& context) const;
121
167
 
168
+ /** Search for k nearest neighbors
169
+ *
170
+ * @param n number of query vectors
171
+ * @param x query vectors (n * d)
172
+ * @param k number of nearest neighbors to find
173
+ * @param distances output distances (n * k)
174
+ * @param labels output labels/indices (n * k)
175
+ * @param params optional search parameters
176
+ */
122
177
  void search(
123
178
  idx_t n,
124
179
  const float* x,
@@ -127,6 +182,19 @@ struct IndexIVFFastScan : IndexIVF {
127
182
  idx_t* labels,
128
183
  const SearchParameters* params = nullptr) const override;
129
184
 
185
+ /** Search with pre-assigned coarse quantization
186
+ *
187
+ * @param n number of query vectors
188
+ * @param x query vectors (n * d)
189
+ * @param k number of nearest neighbors to find
190
+ * @param assign coarse cluster assignments (n * nprobe)
191
+ * @param centroid_dis distances to centroids (n * nprobe)
192
+ * @param distances output distances (n * k)
193
+ * @param labels output labels/indices (n * k)
194
+ * @param store_pairs whether to store cluster-relative pairs
195
+ * @param params optional IVF search parameters
196
+ * @param stats optional search statistics
197
+ */
130
198
  void search_preassigned(
131
199
  idx_t n,
132
200
  const float* x,
@@ -139,6 +207,14 @@ struct IndexIVFFastScan : IndexIVF {
139
207
  const IVFSearchParameters* params = nullptr,
140
208
  IndexIVFStats* stats = nullptr) const override;
141
209
 
210
+ /** Range search for all neighbors within radius
211
+ *
212
+ * @param n number of query vectors
213
+ * @param x query vectors (n * d)
214
+ * @param radius search radius
215
+ * @param result output range search results
216
+ * @param params optional search parameters
217
+ */
142
218
  void range_search(
143
219
  idx_t n,
144
220
  const float* x,
@@ -146,7 +222,45 @@ struct IndexIVFFastScan : IndexIVF {
146
222
  RangeSearchResult* result,
147
223
  const SearchParameters* params = nullptr) const override;
148
224
 
149
- // internal search funcs
225
+ /** Create a KNN handler for this index type
226
+ *
227
+ * This method can be overridden by derived classes to provide
228
+ * specialized handlers (e.g., IVFRaBitQHeapHandler for RaBitQ indexes).
229
+ * Base implementation creates standard handlers based on k and impl.
230
+ *
231
+ * @param is_max true for max-heap (inner product), false for
232
+ * min-heap (L2 distance)
233
+ * @param impl implementation number:
234
+ * - even (10, 12, 14): use heap for top-k
235
+ * - odd (11, 13, 15): use reservoir sampling
236
+ * @param n number of queries
237
+ * @param k number of neighbors to find per query
238
+ * @param distances output array for distances (n * k), will be
239
+ * populated by handler
240
+ * @param labels output array for result IDs (n * k), will be
241
+ * populated by handler
242
+ * @param sel optional ID selector to filter results (nullptr =
243
+ * no filtering)
244
+ * @param context processing context containing additional data
245
+ * @param normalizers optional array of size 2*n for converting quantized
246
+ * uint16 distances to float.
247
+ *
248
+ * @return Allocated result handler (caller owns and must delete).
249
+ * Handler processes SIMD batches and populates distances/labels.
250
+ *
251
+ * @note The returned handler must be deleted by caller after use.
252
+ * Typical usage: handler->begin() → process batches → handler->end()
253
+ */
254
+ virtual SIMDResultHandlerToFloat* make_knn_handler(
255
+ bool is_max,
256
+ int impl,
257
+ idx_t n,
258
+ idx_t k,
259
+ float* distances,
260
+ idx_t* labels,
261
+ const IDSelector* sel,
262
+ const FastScanDistancePostProcessing& context,
263
+ const float* normalizers = nullptr) const;
150
264
 
151
265
  // dispatch to implementations and parallelize
152
266
  void search_dispatch_implem(
@@ -156,7 +270,7 @@ struct IndexIVFFastScan : IndexIVF {
156
270
  float* distances,
157
271
  idx_t* labels,
158
272
  const CoarseQuantized& cq,
159
- const NormTableScaler* scaler,
273
+ const FastScanDistancePostProcessing& context,
160
274
  const IVFSearchParameters* params = nullptr) const;
161
275
 
162
276
  void range_search_dispatch_implem(
@@ -165,7 +279,7 @@ struct IndexIVFFastScan : IndexIVF {
165
279
  float radius,
166
280
  RangeSearchResult& rres,
167
281
  const CoarseQuantized& cq_in,
168
- const NormTableScaler* scaler,
282
+ const FastScanDistancePostProcessing& context,
169
283
  const IVFSearchParameters* params = nullptr) const;
170
284
 
171
285
  // impl 1 and 2 are just for verification
@@ -177,7 +291,7 @@ struct IndexIVFFastScan : IndexIVF {
177
291
  float* distances,
178
292
  idx_t* labels,
179
293
  const CoarseQuantized& cq,
180
- const NormTableScaler* scaler,
294
+ const FastScanDistancePostProcessing& context,
181
295
  const IVFSearchParameters* params = nullptr) const;
182
296
 
183
297
  template <class C>
@@ -188,7 +302,7 @@ struct IndexIVFFastScan : IndexIVF {
188
302
  float* distances,
189
303
  idx_t* labels,
190
304
  const CoarseQuantized& cq,
191
- const NormTableScaler* scaler,
305
+ const FastScanDistancePostProcessing& context,
192
306
  const IVFSearchParameters* params = nullptr) const;
193
307
 
194
308
  // implem 10 and 12 are not multithreaded internally, so
@@ -200,7 +314,7 @@ struct IndexIVFFastScan : IndexIVF {
200
314
  const CoarseQuantized& cq,
201
315
  size_t* ndis_out,
202
316
  size_t* nlist_out,
203
- const NormTableScaler* scaler,
317
+ const FastScanDistancePostProcessing& context,
204
318
  const IVFSearchParameters* params = nullptr) const;
205
319
 
206
320
  void search_implem_12(
@@ -210,7 +324,7 @@ struct IndexIVFFastScan : IndexIVF {
210
324
  const CoarseQuantized& cq,
211
325
  size_t* ndis_out,
212
326
  size_t* nlist_out,
213
- const NormTableScaler* scaler,
327
+ const FastScanDistancePostProcessing& context,
214
328
  const IVFSearchParameters* params = nullptr) const;
215
329
 
216
330
  // implem 14 is multithreaded internally across nprobes and queries
@@ -222,7 +336,7 @@ struct IndexIVFFastScan : IndexIVF {
222
336
  idx_t* labels,
223
337
  const CoarseQuantized& cq,
224
338
  int impl,
225
- const NormTableScaler* scaler,
339
+ const FastScanDistancePostProcessing& context,
226
340
  const IVFSearchParameters* params = nullptr) const;
227
341
 
228
342
  // reconstruct vectors from packed invlists
@@ -234,16 +348,57 @@ struct IndexIVFFastScan : IndexIVF {
234
348
  // reconstruct orig invlists (for debugging)
235
349
  void reconstruct_orig_invlists();
236
350
 
237
- /** Decode a set of vectors.
351
+ /** Decode a set of vectors
238
352
  *
239
- * NOTE: The codes in the IndexFastScan object are non-contiguous.
240
- * But this method requires a contiguous representation.
353
+ * NOTE: The codes in the IndexFastScan object are non-contiguous.
354
+ * But this method requires a contiguous representation.
241
355
  *
242
356
  * @param n number of vectors
243
357
  * @param bytes input encoded vectors, size n * code_size
244
358
  * @param x output vectors, size n * d
245
359
  */
246
360
  void sa_decode(idx_t n, const uint8_t* bytes, float* x) const override;
361
+
362
+ protected:
363
+ /** Preprocess metadata from encoded vectors before packing.
364
+ *
365
+ * Called during add_with_ids after encode_vectors but before codes
366
+ * are packed into SIMD-friendly blocks. Subclasses can override to
367
+ * extract and store metadata embedded in codes or perform other
368
+ * pre-packing operations.
369
+ *
370
+ * Default implementation: no-op
371
+ *
372
+ * Example use case:
373
+ * - IndexIVFRaBitQFastScan extracts factor data from codes for use
374
+ * during search-time distance corrections
375
+ *
376
+ * @param n number of vectors encoded
377
+ * @param flat_codes encoded vectors (n * code_size bytes)
378
+ * @param start_global_idx starting global index (ntotal before add)
379
+ */
380
+ virtual void preprocess_code_metadata(
381
+ idx_t n,
382
+ const uint8_t* flat_codes,
383
+ idx_t start_global_idx);
384
+
385
+ /** Get stride for interpreting codes during SIMD packing.
386
+ *
387
+ * The stride determines how to read codes when packing them into
388
+ * SIMD-friendly block format. This is needed when codes contain
389
+ * embedded metadata that should be skipped during packing.
390
+ *
391
+ * Default implementation: returns 0 (use standard M-byte stride)
392
+ *
393
+ * Example use case:
394
+ * - IndexIVFRaBitQFastScan returns code_size because codes contain
395
+ * embedded factor data after the quantized bits
396
+ *
397
+ * @return stride in bytes:
398
+ * - 0: use default stride (M bytes, standard PQ/AQ codes)
399
+ * - >0: use custom stride (e.g., code_size for embedded metadata)
400
+ */
401
+ virtual size_t code_packing_stride() const;
247
402
  };
248
403
 
249
404
  struct IVFFastScanStats {
@@ -13,6 +13,7 @@
13
13
 
14
14
  #include <cinttypes>
15
15
  #include <cstdio>
16
+ #include <numeric>
16
17
 
17
18
  #include <faiss/IndexFlat.h>
18
19
 
@@ -0,0 +1,366 @@
1
+ /*
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ // -*- c++ -*-
9
+
10
+ #include <faiss/IndexIVFFlatPanorama.h>
11
+
12
+ #include <cstdio>
13
+
14
+ #include <faiss/IndexFlat.h>
15
+
16
+ #include <faiss/impl/AuxIndexStructures.h>
17
+ #include <faiss/impl/IDSelector.h>
18
+ #include <faiss/impl/PanoramaStats.h>
19
+
20
+ #include <faiss/impl/FaissAssert.h>
21
+ #include <faiss/utils/distances.h>
22
+ #include <faiss/utils/extra_distances.h>
23
+ #include <faiss/utils/utils.h>
24
+
25
+ namespace faiss {
26
+
27
+ IndexIVFFlatPanorama::IndexIVFFlatPanorama(
28
+ Index* quantizer,
29
+ size_t d,
30
+ size_t nlist,
31
+ int n_levels,
32
+ MetricType metric,
33
+ bool own_invlists)
34
+ : IndexIVFFlat(quantizer, d, nlist, metric, false), n_levels(n_levels) {
35
+ // For now, we only support L2 distance.
36
+ // Supporting dot product and cosine distance is a trivial addition
37
+ // left for future work.
38
+ FAISS_THROW_IF_NOT(metric == METRIC_L2);
39
+
40
+ // We construct the inverted lists here so that we can use the
41
+ // level-oriented storage. This does not cause a leak as we constructed
42
+ // IndexIVF first, with own_invlists set to false.
43
+ this->invlists = new ArrayInvertedListsPanorama(nlist, code_size, n_levels);
44
+ this->own_invlists = own_invlists;
45
+ }
46
+
47
+ IndexIVFFlatPanorama::IndexIVFFlatPanorama() : n_levels(0) {}
48
+
49
+ namespace {
50
+
51
+ template <typename VectorDistance, bool use_sel>
52
+ struct IVFFlatScannerPanorama : InvertedListScanner {
53
+ VectorDistance vd;
54
+ const ArrayInvertedListsPanorama* storage;
55
+ using C = typename VectorDistance::C;
56
+
57
+ IVFFlatScannerPanorama(
58
+ const VectorDistance& vd,
59
+ const ArrayInvertedListsPanorama* storage,
60
+ bool store_pairs,
61
+ const IDSelector* sel)
62
+ : InvertedListScanner(store_pairs, sel), vd(vd), storage(storage) {
63
+ keep_max = vd.is_similarity;
64
+ code_size = vd.d * sizeof(float);
65
+ cum_sums.resize(storage->n_levels + 1);
66
+ }
67
+
68
+ const float* xi = nullptr;
69
+ std::vector<float> cum_sums;
70
+ float q_norm = 0.0f;
71
+ void set_query(const float* query) override {
72
+ this->xi = query;
73
+
74
+ const size_t d = vd.d;
75
+ const size_t level_width_floats = storage->level_width / sizeof(float);
76
+
77
+ std::vector<float> suffix_sums(d + 1);
78
+ suffix_sums[d] = 0.0f;
79
+
80
+ for (int j = d - 1; j >= 0; j--) {
81
+ float squared_val = query[j] * query[j];
82
+ suffix_sums[j] = suffix_sums[j + 1] + squared_val;
83
+ }
84
+
85
+ for (size_t level = 0; level < storage->n_levels; level++) {
86
+ size_t start_idx = level * level_width_floats;
87
+ if (start_idx < d) {
88
+ cum_sums[level] = sqrt(suffix_sums[start_idx]);
89
+ } else {
90
+ cum_sums[level] = 0.0f;
91
+ }
92
+ }
93
+
94
+ cum_sums[storage->n_levels] = 0.0f;
95
+ q_norm = suffix_sums[0];
96
+ }
97
+
98
+ void set_list(idx_t list_no, float /* coarse_dis */) override {
99
+ this->list_no = list_no;
100
+ }
101
+
102
+ /// This function is unreachable as `IndexIVF` only calls this within
103
+ /// iterators, which are not supported by `IndexIVFFlatPanorama`.
104
+ /// To avoid undefined behavior, we throw an error here.
105
+ float distance_to_code(const uint8_t* /* code */) const override {
106
+ FAISS_THROW_MSG(
107
+ "IndexIVFFlatPanorama does not support distance_to_code");
108
+ }
109
+
110
+ /// Helper function for progressive filtering that both scan_codes and
111
+ /// scan_codes_range use. Processes a batch of vectors through all levels,
112
+ /// computing exact distances and pruning based on a threshold.
113
+ /// Returns the number of active survivors after all levels.
114
+ size_t progressive_filter_batch(
115
+ size_t batch_no,
116
+ size_t list_size,
117
+ const uint8_t* codes_base,
118
+ const float* cum_sums_data,
119
+ float threshold,
120
+ std::vector<float>& exact_distances,
121
+ std::vector<uint32_t>& active_indices,
122
+ const idx_t* ids,
123
+ PanoramaStats& local_stats) const {
124
+ const size_t d = vd.d;
125
+ const size_t level_width_floats = storage->level_width / sizeof(float);
126
+
127
+ size_t batch_start = batch_no * storage->kBatchSize;
128
+ size_t curr_batch_size =
129
+ std::min(list_size - batch_start, storage->kBatchSize);
130
+
131
+ size_t cumsum_batch_offset =
132
+ batch_no * storage->kBatchSize * (storage->n_levels + 1);
133
+ const float* batch_cum_sums = cum_sums_data + cumsum_batch_offset;
134
+
135
+ size_t batch_offset = batch_no * storage->kBatchSize * code_size;
136
+ const uint8_t* storage_base = codes_base + batch_offset;
137
+
138
+ // Initialize active set with ID-filtered vectors.
139
+ size_t num_active = 0;
140
+ for (size_t i = 0; i < curr_batch_size; i++) {
141
+ size_t global_idx = batch_start + i;
142
+ bool include = !use_sel || sel->is_member(ids[global_idx]);
143
+
144
+ active_indices[num_active] = i;
145
+ float cum_sum = batch_cum_sums[i];
146
+ exact_distances[i] = cum_sum * cum_sum + q_norm;
147
+
148
+ num_active += include;
149
+ }
150
+
151
+ if (num_active == 0) {
152
+ return 0;
153
+ }
154
+
155
+ size_t total_active = num_active;
156
+
157
+ const float* level_cum_sums = batch_cum_sums + storage->kBatchSize;
158
+
159
+ // Progressive filtering through levels.
160
+ for (size_t level = 0; level < storage->n_levels; level++) {
161
+ local_stats.total_dims_scanned += num_active;
162
+ local_stats.total_dims += total_active;
163
+
164
+ float query_cum_norm = cum_sums[level + 1];
165
+
166
+ size_t level_offset =
167
+ level * storage->level_width * storage->kBatchSize;
168
+ const float* level_storage =
169
+ (const float*)(storage_base + level_offset);
170
+
171
+ size_t next_active = 0;
172
+ for (size_t i = 0; i < num_active; i++) {
173
+ uint32_t idx = active_indices[i];
174
+ const float* yj = level_storage + idx * level_width_floats;
175
+ const float* query_level = xi + level * level_width_floats;
176
+
177
+ size_t actual_level_width = std::min(
178
+ level_width_floats, d - level * level_width_floats);
179
+ float dot_product =
180
+ fvec_inner_product(query_level, yj, actual_level_width);
181
+
182
+ exact_distances[idx] -= 2.0f * dot_product;
183
+
184
+ float cum_sum = level_cum_sums[idx];
185
+ float cauchy_schwarz_bound = 2.0f * cum_sum * query_cum_norm;
186
+ float lower_bound = exact_distances[idx] - cauchy_schwarz_bound;
187
+
188
+ active_indices[next_active] = idx;
189
+ next_active += C::cmp(threshold, lower_bound) ? 1 : 0;
190
+ }
191
+
192
+ num_active = next_active;
193
+ level_cum_sums += storage->kBatchSize;
194
+ }
195
+
196
+ return num_active;
197
+ }
198
+
199
+ size_t scan_codes(
200
+ size_t list_size,
201
+ const uint8_t* codes,
202
+ const idx_t* ids,
203
+ float* simi,
204
+ idx_t* idxi,
205
+ size_t k) const override {
206
+ size_t nup = 0;
207
+
208
+ const size_t n_batches =
209
+ (list_size + storage->kBatchSize - 1) / storage->kBatchSize;
210
+
211
+ const uint8_t* codes_base = codes;
212
+ const float* cum_sums_data = storage->get_cum_sums(list_no);
213
+
214
+ std::vector<float> exact_distances(storage->kBatchSize);
215
+ std::vector<uint32_t> active_indices(storage->kBatchSize);
216
+
217
+ PanoramaStats local_stats;
218
+ local_stats.reset();
219
+
220
+ // Panorama's IVFFlat core progressive filtering algorithm:
221
+ // Process vectors in batches for cache efficiency. For each batch:
222
+ // 1. Apply ID selection filter and initialize distances
223
+ // (||y||^2 + ||x||^2).
224
+ // 2. Maintain an "active set" of candidate indices that haven't been
225
+ // pruned yet.
226
+ // 3. For each level, refine distances incrementally and compact the
227
+ // active set:
228
+ // - Compute dot product for current level: exact_dist -= 2*<x,y>.
229
+ // - Use Cauchy-Schwarz bound on remaining levels to get lower bound
230
+ // - Prune candidates whose lower bound exceeds k-th best distance.
231
+ // - Compact active_indices to remove pruned candidates (branchless)
232
+ // 4. After all levels, survivors are exact distances; update heap.
233
+ // This achieves early termination while maintaining SIMD-friendly
234
+ // sequential access patterns in the level-oriented storage layout.
235
+ for (size_t batch_no = 0; batch_no < n_batches; batch_no++) {
236
+ size_t batch_start = batch_no * storage->kBatchSize;
237
+
238
+ size_t num_active = progressive_filter_batch(
239
+ batch_no,
240
+ list_size,
241
+ codes_base,
242
+ cum_sums_data,
243
+ simi[0],
244
+ exact_distances,
245
+ active_indices,
246
+ ids,
247
+ local_stats);
248
+
249
+ // Add batch survivors to heap.
250
+ for (size_t i = 0; i < num_active; i++) {
251
+ uint32_t idx = active_indices[i];
252
+ size_t global_idx = batch_start + idx;
253
+ float dis = exact_distances[idx];
254
+
255
+ if (C::cmp(simi[0], dis)) {
256
+ int64_t id = store_pairs ? lo_build(list_no, global_idx)
257
+ : ids[global_idx];
258
+ heap_replace_top<C>(k, simi, idxi, dis, id);
259
+ nup++;
260
+ }
261
+ }
262
+ }
263
+
264
+ indexPanorama_stats.add(local_stats);
265
+ return nup;
266
+ }
267
+
268
+ void scan_codes_range(
269
+ size_t list_size,
270
+ const uint8_t* codes,
271
+ const idx_t* ids,
272
+ float radius,
273
+ RangeQueryResult& res) const override {
274
+ const size_t n_batches =
275
+ (list_size + storage->kBatchSize - 1) / storage->kBatchSize;
276
+
277
+ const uint8_t* codes_base = codes;
278
+ const float* cum_sums_data = storage->get_cum_sums(list_no);
279
+
280
+ std::vector<float> exact_distances(storage->kBatchSize);
281
+ std::vector<uint32_t> active_indices(storage->kBatchSize);
282
+
283
+ PanoramaStats local_stats;
284
+ local_stats.reset();
285
+
286
+ // Same progressive filtering as scan_codes, but with fixed radius
287
+ // threshold instead of dynamic heap threshold.
288
+ for (size_t batch_no = 0; batch_no < n_batches; batch_no++) {
289
+ size_t batch_start = batch_no * storage->kBatchSize;
290
+
291
+ size_t num_active = progressive_filter_batch(
292
+ batch_no,
293
+ list_size,
294
+ codes_base,
295
+ cum_sums_data,
296
+ radius,
297
+ exact_distances,
298
+ active_indices,
299
+ ids,
300
+ local_stats);
301
+
302
+ // Add batch survivors to range result.
303
+ for (size_t i = 0; i < num_active; i++) {
304
+ uint32_t idx = active_indices[i];
305
+ size_t global_idx = batch_start + idx;
306
+ float dis = exact_distances[idx];
307
+
308
+ if (C::cmp(radius, dis)) {
309
+ int64_t id = store_pairs ? lo_build(list_no, global_idx)
310
+ : ids[global_idx];
311
+ res.add(dis, id);
312
+ }
313
+ }
314
+ }
315
+
316
+ indexPanorama_stats.add(local_stats);
317
+ }
318
+ };
319
+
320
+ struct Run_get_InvertedListScanner {
321
+ using T = InvertedListScanner*;
322
+
323
+ template <class VD>
324
+ InvertedListScanner* f(
325
+ VD& vd,
326
+ const IndexIVFFlatPanorama* ivf,
327
+ bool store_pairs,
328
+ const IDSelector* sel) {
329
+ // Safely cast to ArrayInvertedListsPanorama to access cumulative sums.
330
+ const ArrayInvertedListsPanorama* storage =
331
+ dynamic_cast<const ArrayInvertedListsPanorama*>(ivf->invlists);
332
+ FAISS_THROW_IF_NOT_MSG(
333
+ storage,
334
+ "IndexIVFFlatPanorama requires ArrayInvertedListsPanorama");
335
+
336
+ if (sel) {
337
+ return new IVFFlatScannerPanorama<VD, true>(
338
+ vd, storage, store_pairs, sel);
339
+ } else {
340
+ return new IVFFlatScannerPanorama<VD, false>(
341
+ vd, storage, store_pairs, sel);
342
+ }
343
+ }
344
+ };
345
+
346
+ } // anonymous namespace
347
+
348
+ InvertedListScanner* IndexIVFFlatPanorama::get_InvertedListScanner(
349
+ bool store_pairs,
350
+ const IDSelector* sel,
351
+ const IVFSearchParameters*) const {
352
+ Run_get_InvertedListScanner run;
353
+ return dispatch_VectorDistance(
354
+ d, metric_type, metric_arg, run, this, store_pairs, sel);
355
+ }
356
+
357
+ void IndexIVFFlatPanorama::reconstruct_from_offset(
358
+ int64_t list_no,
359
+ int64_t offset,
360
+ float* recons) const {
361
+ const uint8_t* code = invlists->get_single_code(list_no, offset);
362
+ memcpy(recons, code, code_size);
363
+ invlists->release_codes(list_no, code);
364
+ }
365
+
366
+ } // namespace faiss