faiss 0.6.2 → 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.
Files changed (144) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/lib/faiss/version.rb +1 -1
  4. data/vendor/faiss/faiss/AutoTune.cpp +3 -1
  5. data/vendor/faiss/faiss/Clustering.cpp +9 -1
  6. data/vendor/faiss/faiss/IVFlib.cpp +14 -3
  7. data/vendor/faiss/faiss/Index.h +2 -2
  8. data/vendor/faiss/faiss/IndexAdditiveQuantizer.cpp +9 -10
  9. data/vendor/faiss/faiss/IndexAdditiveQuantizerFastScan.cpp +2 -3
  10. data/vendor/faiss/faiss/IndexBinaryFromFloat.cpp +1 -2
  11. data/vendor/faiss/faiss/IndexBinaryHNSW.cpp +4 -5
  12. data/vendor/faiss/faiss/IndexBinaryHash.cpp +5 -9
  13. data/vendor/faiss/faiss/IndexBinaryIVF.cpp +2 -4
  14. data/vendor/faiss/faiss/IndexEDEN.cpp +273 -0
  15. data/vendor/faiss/faiss/IndexEDEN.h +57 -0
  16. data/vendor/faiss/faiss/IndexFastScan.cpp +15 -4
  17. data/vendor/faiss/faiss/IndexFlat.cpp +13 -50
  18. data/vendor/faiss/faiss/IndexHNSW.cpp +10 -11
  19. data/vendor/faiss/faiss/IndexIDMap.cpp +16 -3
  20. data/vendor/faiss/faiss/IndexIDMap.h +2 -0
  21. data/vendor/faiss/faiss/IndexIVF.cpp +17 -6
  22. data/vendor/faiss/faiss/IndexIVFAdditiveQuantizer.cpp +1 -1
  23. data/vendor/faiss/faiss/IndexIVFAdditiveQuantizerFastScan.cpp +3 -4
  24. data/vendor/faiss/faiss/IndexIVFEDEN.cpp +302 -0
  25. data/vendor/faiss/faiss/IndexIVFEDEN.h +70 -0
  26. data/vendor/faiss/faiss/IndexIVFFastScan.cpp +5 -6
  27. data/vendor/faiss/faiss/IndexIVFFlat.cpp +3 -4
  28. data/vendor/faiss/faiss/IndexIVFIndependentQuantizer.cpp +1 -1
  29. data/vendor/faiss/faiss/IndexIVFPQ.cpp +40 -22
  30. data/vendor/faiss/faiss/IndexIVFPQFastScan.cpp +0 -1
  31. data/vendor/faiss/faiss/IndexIVFRaBitQ.cpp +19 -49
  32. data/vendor/faiss/faiss/IndexIVFRaBitQFastScan.cpp +180 -76
  33. data/vendor/faiss/faiss/IndexIVFRaBitQFastScan.h +5 -4
  34. data/vendor/faiss/faiss/IndexIVFSpectralHash.cpp +8 -6
  35. data/vendor/faiss/faiss/IndexLSH.cpp +2 -3
  36. data/vendor/faiss/faiss/IndexLattice.cpp +5 -0
  37. data/vendor/faiss/faiss/IndexNNDescent.cpp +9 -2
  38. data/vendor/faiss/faiss/IndexNSG.cpp +7 -2
  39. data/vendor/faiss/faiss/IndexPQ.cpp +6 -8
  40. data/vendor/faiss/faiss/IndexPreTransform.cpp +15 -0
  41. data/vendor/faiss/faiss/IndexRaBitQ.cpp +2 -2
  42. data/vendor/faiss/faiss/IndexRaBitQFastScan.cpp +1 -2
  43. data/vendor/faiss/faiss/IndexRaBitQFastScan.h +5 -1
  44. data/vendor/faiss/faiss/IndexRefine.cpp +30 -1
  45. data/vendor/faiss/faiss/IndexReplicas.cpp +1 -2
  46. data/vendor/faiss/faiss/IndexShards.cpp +2 -2
  47. data/vendor/faiss/faiss/IndexShardsIVF.cpp +2 -2
  48. data/vendor/faiss/faiss/MetaIndexes.cpp +2 -4
  49. data/vendor/faiss/faiss/SuperKMeans.cpp +256 -240
  50. data/vendor/faiss/faiss/SuperKMeans.h +30 -0
  51. data/vendor/faiss/faiss/VectorTransform.cpp +33 -2
  52. data/vendor/faiss/faiss/clone_index.cpp +5 -0
  53. data/vendor/faiss/faiss/factory_tools.cpp +47 -4
  54. data/vendor/faiss/faiss/gpu/GpuCloner.cpp +11 -11
  55. data/vendor/faiss/faiss/gpu/GpuIndex.h +34 -11
  56. data/vendor/faiss/faiss/gpu/GpuIndexCagra.h +47 -0
  57. data/vendor/faiss/faiss/gpu/GpuIndexIVF.h +17 -0
  58. data/vendor/faiss/faiss/gpu/GpuIndexIVFScalarQuantizer.h +16 -0
  59. data/vendor/faiss/faiss/gpu/perf/PerfClustering.cpp +1 -1
  60. data/vendor/faiss/faiss/gpu/perf/PerfIVFPQAdd.cpp +2 -2
  61. data/vendor/faiss/faiss/gpu/test/TestGpuIndexIVFScalarQuantizer.cpp +180 -0
  62. data/vendor/faiss/faiss/gpu_metal/MetalIndexIVFFlat.h +1 -5
  63. data/vendor/faiss/faiss/gpu_metal/MetalIndexIVFPQ.h +88 -0
  64. data/vendor/faiss/faiss/gpu_metal/impl/MetalIVFPQ.h +134 -0
  65. data/vendor/faiss/faiss/impl/ClusteringInitialization.cpp +2 -2
  66. data/vendor/faiss/faiss/impl/DistanceComputer.h +34 -0
  67. data/vendor/faiss/faiss/impl/EDENQuantizer.h +119 -0
  68. data/vendor/faiss/faiss/impl/HNSW.cpp +109 -152
  69. data/vendor/faiss/faiss/impl/LocalSearchQuantizer.cpp +2 -2
  70. data/vendor/faiss/faiss/impl/NSG.cpp +3 -1
  71. data/vendor/faiss/faiss/impl/Panorama.h +9 -7
  72. data/vendor/faiss/faiss/impl/PolysemousTraining.cpp +152 -84
  73. data/vendor/faiss/faiss/impl/ProductQuantizer.cpp +34 -22
  74. data/vendor/faiss/faiss/impl/RaBitQUtils.cpp +44 -36
  75. data/vendor/faiss/faiss/impl/RaBitQUtils.h +35 -0
  76. data/vendor/faiss/faiss/impl/RaBitQuantizer.cpp +168 -67
  77. data/vendor/faiss/faiss/impl/RaBitQuantizer.h +19 -0
  78. data/vendor/faiss/faiss/impl/RaBitQuantizerMultiBit.cpp +2 -11
  79. data/vendor/faiss/faiss/impl/ResultHandler.h +25 -31
  80. data/vendor/faiss/faiss/impl/ScalarQuantizer.cpp +258 -57
  81. data/vendor/faiss/faiss/impl/ScalarQuantizer.h +20 -0
  82. data/vendor/faiss/faiss/impl/ThreadedIndex-inl.h +2 -2
  83. data/vendor/faiss/faiss/impl/VisitedTable.cpp +22 -2
  84. data/vendor/faiss/faiss/impl/VisitedTable.h +20 -0
  85. data/vendor/faiss/faiss/impl/binary_hamming/IndexBinaryIVF_impl.h +1 -1
  86. data/vendor/faiss/faiss/impl/binary_hamming/avx2.cpp +4 -4
  87. data/vendor/faiss/faiss/impl/fast_scan/dispatching.h +35 -2
  88. data/vendor/faiss/faiss/impl/hnsw/LockVector.cpp +1 -1
  89. data/vendor/faiss/faiss/impl/index_read.cpp +376 -36
  90. data/vendor/faiss/faiss/impl/index_write.cpp +55 -4
  91. data/vendor/faiss/faiss/impl/lattice_Zn.cpp +8 -9
  92. data/vendor/faiss/faiss/impl/platform_macros.h +3 -1
  93. data/vendor/faiss/faiss/impl/polysemous_training/avx512.cpp +284 -0
  94. data/vendor/faiss/faiss/impl/polysemous_training/dispatch.h +115 -0
  95. data/vendor/faiss/faiss/impl/pq_code_distance/IVFPQ_QueryTables.cpp +0 -1
  96. data/vendor/faiss/faiss/impl/pq_code_distance/PQDistanceComputer_impl.h +26 -15
  97. data/vendor/faiss/faiss/impl/pq_code_distance/avx2.cpp +4 -4
  98. data/vendor/faiss/faiss/impl/result_handler/ResultHandler.cpp +195 -0
  99. data/vendor/faiss/faiss/impl/result_handler/avx2.cpp +133 -0
  100. data/vendor/faiss/faiss/impl/result_handler/avx512.cpp +281 -0
  101. data/vendor/faiss/faiss/impl/scalar_quantizer/EDENQuantizer-avx2.cpp +72 -0
  102. data/vendor/faiss/faiss/impl/scalar_quantizer/EDENQuantizer-avx512.cpp +228 -0
  103. data/vendor/faiss/faiss/impl/scalar_quantizer/EDENQuantizer.cpp +882 -0
  104. data/vendor/faiss/faiss/impl/scalar_quantizer/quantizers.h +9 -8
  105. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx2.cpp +85 -23
  106. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512-impl.h +30 -30
  107. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512-spr.cpp +4 -5
  108. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512.cpp +101 -34
  109. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-dispatch.h +136 -0
  110. data/vendor/faiss/faiss/impl/scalar_quantizer/sq-neon.cpp +16 -16
  111. data/vendor/faiss/faiss/impl/simd_dispatch.h +30 -9
  112. data/vendor/faiss/faiss/index_factory.cpp +32 -6
  113. data/vendor/faiss/faiss/invlists/DirectMap.cpp +1 -1
  114. data/vendor/faiss/faiss/invlists/InvertedLists.cpp +2 -2
  115. data/vendor/faiss/faiss/invlists/OnDiskInvertedLists.cpp +19 -4
  116. data/vendor/faiss/faiss/python/python_callbacks.cpp +3 -1
  117. data/vendor/faiss/faiss/svs/IndexSVSFaissUtils.h +60 -0
  118. data/vendor/faiss/faiss/svs/IndexSVSFlat.cpp +26 -1
  119. data/vendor/faiss/faiss/svs/IndexSVSFlat.h +13 -0
  120. data/vendor/faiss/faiss/svs/IndexSVSIVF.cpp +1 -1
  121. data/vendor/faiss/faiss/svs/IndexSVSIVFLeanVec.cpp +1 -1
  122. data/vendor/faiss/faiss/svs/IndexSVSVamana.cpp +31 -1
  123. data/vendor/faiss/faiss/svs/IndexSVSVamana.h +15 -2
  124. data/vendor/faiss/faiss/svs/IndexSVSVamanaLeanVec.cpp +1 -2
  125. data/vendor/faiss/faiss/utils/approx_topk_hamming/approx_topk_hamming.h +1 -1
  126. data/vendor/faiss/faiss/utils/distances.cpp +14 -2
  127. data/vendor/faiss/faiss/utils/distances_simd.cpp +4 -3
  128. data/vendor/faiss/faiss/utils/extra_distances.cpp +4 -14
  129. data/vendor/faiss/faiss/utils/extra_distances.h +1 -2
  130. data/vendor/faiss/faiss/utils/hamming.cpp +1 -1
  131. data/vendor/faiss/faiss/utils/quantize_lut.cpp +29 -8
  132. data/vendor/faiss/faiss/utils/rabitq_simd.h +202 -0
  133. data/vendor/faiss/faiss/utils/simd_impl/distances_avx2.cpp +0 -1
  134. data/vendor/faiss/faiss/utils/simd_impl/distances_avx512.cpp +263 -15
  135. data/vendor/faiss/faiss/utils/simd_impl/distances_rvv.cpp +160 -18
  136. data/vendor/faiss/faiss/utils/simd_impl/rabitq_avx2.cpp +245 -0
  137. data/vendor/faiss/faiss/utils/simd_impl/rabitq_avx512.cpp +273 -0
  138. data/vendor/faiss/faiss/utils/simd_impl/rabitq_avx512_spr.cpp +92 -0
  139. data/vendor/faiss/faiss/utils/simd_impl/rabitq_neon.cpp +11 -0
  140. data/vendor/faiss/faiss/utils/simd_impl/rabitq_rvv.cpp +143 -6
  141. data/vendor/faiss/faiss/utils/simd_levels.cpp +44 -0
  142. data/vendor/faiss/faiss/utils/simd_levels.h +14 -0
  143. data/vendor/faiss/faiss/utils/utils.cpp +9 -27
  144. metadata +16 -1
@@ -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,
@@ -862,32 +862,44 @@ void ProductQuantizer::search_sdc(
862
862
  size_t k = res->k;
863
863
  int64_t nq_signed = nq;
864
864
 
865
- #pragma omp parallel for
866
- for (int64_t i = 0; i < nq_signed; i++) {
867
- /* Compute distances and keep smallest values */
868
- idx_t* heap_ids = res->ids + i * k;
869
- float* heap_dis = res->val + i * k;
870
- const uint8_t* qcode = qcodes + i * code_size;
871
-
872
- if (init_finalize_heap)
873
- maxheap_heapify(k, heap_dis, heap_ids);
874
-
875
- const uint8_t* bcode = bcodes;
876
- for (size_t j = 0; j < nb; j++) {
877
- float dis = 0;
878
- const float* tab = sdc_table.data();
865
+ #pragma omp parallel
866
+ {
867
+ // One allocation per OMP thread instead of one per query.
868
+ std::vector<const float*> q_row(M);
869
+ #pragma omp for
870
+ for (int64_t i = 0; i < nq_signed; i++) {
871
+ idx_t* heap_ids = res->ids + i * k;
872
+ float* heap_dis = res->val + i * k;
873
+ const uint8_t* qcode = qcodes + i * code_size;
874
+
875
+ if (init_finalize_heap)
876
+ maxheap_heapify(k, heap_dis, heap_ids);
877
+
878
+ // Precompute per-subquantizer row pointers: q_row[m] points to
879
+ // sdc_table[m*ksub^2 + qcode[m]*ksub], eliminating M
880
+ // multiplications and M pointer advances per database vector in the
881
+ // j-loop.
882
+ const float* sdc = sdc_table.data();
879
883
  for (size_t m = 0; m < M; m++) {
880
- dis += tab[bcode[m] + qcode[m] * ksub];
881
- tab += ksub * ksub;
884
+ q_row[m] = sdc + m * (size_t)(ksub * ksub) +
885
+ (size_t)qcode[m] * ksub;
882
886
  }
883
- if (dis < heap_dis[0]) {
884
- maxheap_replace_top(k, heap_dis, heap_ids, dis, j);
887
+
888
+ const uint8_t* bcode = bcodes;
889
+ for (size_t j = 0; j < nb; j++) {
890
+ float dis = 0;
891
+ for (size_t m = 0; m < M; m++) {
892
+ dis += q_row[m][bcode[m]];
893
+ }
894
+ if (dis < heap_dis[0]) {
895
+ maxheap_replace_top(k, heap_dis, heap_ids, dis, j);
896
+ }
897
+ bcode += code_size;
885
898
  }
886
- bcode += code_size;
887
- }
888
899
 
889
- if (init_finalize_heap)
890
- maxheap_reorder(k, heap_dis, heap_ids);
900
+ if (init_finalize_heap)
901
+ maxheap_reorder(k, heap_dis, heap_ids);
902
+ }
891
903
  }
892
904
  }
893
905
 
@@ -188,46 +188,54 @@ QueryFactorsData compute_query_factors(
188
188
 
189
189
  const float inv_d_sqrt = 1.0f / std::sqrt(static_cast<float>(d));
190
190
 
191
- // Compute quantization range
192
- float v_min = std::numeric_limits<float>::max();
193
- float v_max = std::numeric_limits<float>::lowest();
194
-
195
191
  const float* rq = rotated_q.data();
196
- if (centered) {
197
- float z_max = Z_MAX_BY_QB[qb - 1];
198
- float v_radius = z_max * std::sqrt(query_factors.qr_to_c_L2sqr / d);
199
- v_min = -v_radius;
200
- v_max = v_radius;
201
- } else {
202
- for (size_t i = 0; i < d; i++) {
203
- const float v_q = rq[i];
204
- v_min = std::min(v_min, v_q);
205
- v_max = std::max(v_max, v_q);
206
- }
207
- }
208
-
209
- // Quantize the query
210
- const uint8_t max_code = (1 << qb) - 1;
211
- const float delta = (v_max - v_min) / max_code;
212
- const float inv_delta = 1.0f / delta;
213
-
214
- rotated_qq.resize(d);
192
+ float v_min;
193
+ float v_max;
194
+ float delta;
215
195
  size_t sum_qq = 0;
216
196
  int64_t sum2_signed_odd_int = 0;
217
-
197
+ const uint8_t max_code = (1 << qb) - 1;
198
+ rotated_qq.resize(d);
218
199
  uint8_t* rqq = rotated_qq.data();
219
- for (size_t i = 0; i < d; i++) {
220
- const float v_q = rq[i];
221
- const uint8_t v_qq = std::clamp<float>(
222
- std::round((v_q - v_min) * inv_delta), 0, max_code);
223
- rqq[i] = v_qq;
224
- sum_qq += v_qq;
225
-
226
- if (centered) {
227
- int64_t signed_odd_int = int64_t(v_qq) * 2 - max_code;
228
- sum2_signed_odd_int += signed_odd_int * signed_odd_int;
229
- }
230
- }
200
+
201
+ // Select the SIMD implementation once for both range computation and
202
+ // quantization. This function runs once per query/probe pair.
203
+ with_selected_simd_levels<rabitq::RABITQ_QUANTIZATION_SIMD_LEVELS>(
204
+ [&]<SIMDLevel SL>() {
205
+ if (centered) {
206
+ const float z_max = Z_MAX_BY_QB[qb - 1];
207
+ const float v_radius =
208
+ z_max * std::sqrt(query_factors.qr_to_c_L2sqr / d);
209
+ v_min = -v_radius;
210
+ v_max = v_radius;
211
+ } else {
212
+ v_min = std::numeric_limits<float>::max();
213
+ v_max = std::numeric_limits<float>::lowest();
214
+ rabitq::minmax_values<SL>(rq, d, v_min, v_max);
215
+ }
216
+
217
+ delta = (v_max - v_min) / max_code;
218
+ // A constant (or zero-norm) query has delta == 0. Preserve the
219
+ // scalar path's centered correction terms while avoiding
220
+ // 0 * inf during quantization.
221
+ if (delta <= 0.0f) {
222
+ memset(rqq, 0, d * sizeof(uint8_t));
223
+ if (centered) {
224
+ sum2_signed_odd_int = int64_t(d) * max_code * max_code;
225
+ }
226
+ } else {
227
+ rabitq::quantize_query_values<SL>(
228
+ rq,
229
+ d,
230
+ v_min,
231
+ 1.0f / delta,
232
+ max_code,
233
+ centered,
234
+ rqq,
235
+ sum_qq,
236
+ sum2_signed_odd_int);
237
+ }
238
+ });
231
239
 
232
240
  // Compute query factors
233
241
  query_factors.c1 = 2 * delta * inv_d_sqrt;
@@ -13,6 +13,7 @@
13
13
  #include <faiss/utils/rabitq_simd.h>
14
14
  #include <faiss/utils/simd_levels.h>
15
15
  #include <algorithm>
16
+ #include <cassert>
16
17
  #include <cstddef>
17
18
  #include <cstdint>
18
19
  #include <cstring>
@@ -86,6 +87,40 @@ struct QueryFactorsData {
86
87
  */
87
88
  FAISS_API extern const float Z_MAX_BY_QB[8];
88
89
 
90
+ /** Fast half-away rounding for non-negative RaBitQ quantization values.
91
+ *
92
+ * Contract: x must be finite, non-NaN, and in [0, 255.5). This is not a
93
+ * bit-exact replacement for roundf within a few ulps below k + 0.5, where the
94
+ * addition may round across the tie. It is intended only for RaBitQ LUT
95
+ * quantization paths where a +/- 1 code difference at those boundaries is
96
+ * acceptable.
97
+ */
98
+ inline uint8_t round_nonnegative_to_uint8(float x) {
99
+ assert(x == x);
100
+ assert(x >= 0.0f);
101
+ assert(x < 255.5f);
102
+ return rabitq::round_nonnegative_byte_scalar(x);
103
+ }
104
+
105
+ /** Same as round_nonnegative_to_uint8 for uint16 RaBitQ bias values. */
106
+ inline uint16_t round_nonnegative_to_uint16(float x) {
107
+ assert(x == x);
108
+ assert(x >= 0.0f);
109
+ assert(x < 65535.5f);
110
+ return static_cast<uint16_t>(static_cast<int>(x + 0.5f));
111
+ }
112
+
113
+ /** Fast clamped rounding for query-byte quantization.
114
+ *
115
+ * Contract: x must be non-NaN. Values outside [0, max_code] are clamped before
116
+ * applying the same non-bit-exact rounding used by
117
+ * round_nonnegative_to_uint8().
118
+ */
119
+ inline uint8_t round_clamped_to_uint8(float x, uint8_t max_code) {
120
+ assert(x == x);
121
+ return rabitq::round_clamped_byte_scalar(x, max_code);
122
+ }
123
+
89
124
  /** Compute factors for a single database vector using RaBitQ algorithm.
90
125
  * This function consolidates the mathematical logic that was duplicated
91
126
  * between IndexRaBitQ and IndexRaBitQFastScan.