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
@@ -59,68 +59,65 @@ namespace {
59
59
  struct TrainState {
60
60
  /// Orthogonal rotation. Train in rotated space (X_tilde = X * R);
61
61
  /// un-rotate centroids before return.
62
- faiss::RandomRotationMatrix R;
62
+ std::unique_ptr<faiss::VectorTransform> R;
63
63
 
64
64
  std::vector<float> X_tilde; // (n, d) row-major
65
65
  int n = 0;
66
66
  std::vector<float> Y_tilde; // (k, d) row-major
67
67
 
68
- std::vector<int> assignments; // size n
69
- std::vector<float> best_dists; // size n; tau per vector
70
-
71
- /// ||X_tilde[i, 0:d_prime]||^2; recomputed when d_prime changes.
72
- std::vector<float> x_norms_partial;
68
+ std::vector<int32_t> assignments; // size n
69
+ std::vector<float> best_dists; // size n; tau per vector
73
70
 
74
71
  int d_prime = 0;
75
72
 
76
73
  /// ADSampling threshold table; size d+1.
77
74
  std::vector<float> ad_coeff;
78
75
 
79
- /// PDX block layout for the trailing pruning sweep: block b covers
80
- /// original dims [true_block_end[b] - block_dim[b], true_block_end[b]).
81
- /// Recomputed when d_prime changes.
82
- std::vector<int> block_dim;
83
- std::vector<int> true_block_end;
84
-
85
76
  /// Counter for the verbose-mode "low pruning" warning.
86
77
  int low_pruning_streak = 0;
87
78
  bool low_pruning_warning_printed = false;
88
79
 
89
- explicit TrainState(int d) : R(d, d) {}
80
+ explicit TrainState(int d, bool spherical)
81
+ : R([d, spherical]() -> std::unique_ptr<faiss::VectorTransform> {
82
+ // Spherical (inner-product) clustering only: a power-of-two
83
+ // dimension can use the fast Hadamard rotation instead of
84
+ // the generic random rotation. L2 training keeps the
85
+ // original RandomRotationMatrix path unchanged.
86
+ if (spherical && d > 0 && (d & (d - 1)) == 0) {
87
+ return std::make_unique<faiss::HadamardRotation>(d);
88
+ }
89
+ return std::make_unique<faiss::RandomRotationMatrix>(d, d);
90
+ }()) {}
91
+ };
92
+
93
+ /// PDX block layout for the trailing pruning sweep: block b covers original
94
+ /// dims [true_block_end[b] - block_dim[b], true_block_end[b]).
95
+ struct LocalBlockLayout {
96
+ std::vector<int> block_dim;
97
+ std::vector<int> true_block_end;
90
98
  };
91
99
 
92
- /// Rebuild state.block_dim and state.true_block_end from the current
93
- /// state.d_prime and pdx_block_size. Call after any change to d_prime.
94
- void rebuild_pdx_block_layout(int d, int pdx_block_size, TrainState& state) {
95
- const int dp = state.d_prime;
96
- const int d_trail = d - dp;
100
+ LocalBlockLayout build_pdx_block_layout(
101
+ int d,
102
+ int d_prime,
103
+ int pdx_block_size) {
104
+ LocalBlockLayout layout;
105
+ const int d_trail = d - d_prime;
97
106
  const int n_full_blocks = d_trail / pdx_block_size;
98
107
  const int tail = d_trail % pdx_block_size;
99
108
  const int n_blocks = n_full_blocks + (tail > 0 ? 1 : 0);
100
- state.block_dim.assign(n_blocks, pdx_block_size);
101
- state.true_block_end.resize(n_blocks);
102
- if (n_blocks > 0) {
103
- assert(!state.block_dim.empty());
104
- assert(!state.true_block_end.empty());
105
- for (int b = 0; b < n_full_blocks; ++b) {
106
- state.true_block_end[b] = dp + (b + 1) * pdx_block_size;
107
- }
108
- if (tail > 0) {
109
- state.block_dim[n_full_blocks] = tail;
110
- state.true_block_end[n_full_blocks] = d;
111
- }
109
+ layout.block_dim.assign(n_blocks, pdx_block_size);
110
+ layout.true_block_end.resize(n_blocks);
111
+ for (int b = 0; b < n_full_blocks; ++b) {
112
+ layout.true_block_end[b] = d_prime + (b + 1) * pdx_block_size;
113
+ }
114
+ if (tail > 0) {
115
+ layout.block_dim[n_full_blocks] = tail;
116
+ layout.true_block_end[n_full_blocks] = d;
112
117
  }
118
+ return layout;
113
119
  }
114
120
 
115
- struct IterScratch {
116
- std::vector<float> partial_ip; // (bx_max, by_max) for the GEMM tile
117
- std::vector<float> Y_pdx; // PDX-laid-out trailing block
118
- std::vector<float> Y_trail; // row-major (k, d_trail) input to pdxify
119
- std::vector<float> y_norms_partial; // ||Y_tilde[j, 0:dp]||^2
120
- std::vector<int64_t> labels64; // size n; widened state.assignments
121
- int prev_d_trail = -1;
122
- };
123
-
124
121
  /// Iter 0: full GEMM via knn_L2sqr (vanilla Lloyd's). Fills
125
122
  /// state.assignments and state.best_dists. Returns objective.
126
123
  double run_iter0_full_gemm(int d, int k, TrainState& state) {
@@ -148,183 +145,56 @@ double run_iter0_full_gemm(int d, int k, TrainState& state) {
148
145
  return objective;
149
146
  }
150
147
 
151
- /// Iter 1+: partial GEMM over [0, d_prime) + ADSampling progressive
152
- /// pruning over the PDX-laid-out trailing block. Updates
153
- /// state.assignments and state.best_dists. Writes total_pairs and
154
- /// pruned_at_gemm. Returns objective.
148
+ /// Iter 1+: refresh tau, then run one shared super_kmeans_assign_iteration
149
+ /// pass (partial GEMM + ADSampling progressive pruning). Updates
150
+ /// state.assignments and state.best_dists, writes total_pairs and
151
+ /// pruned_at_gemm, and returns the objective.
155
152
  double run_iter_pruned(
156
153
  int d,
157
154
  int k,
158
155
  const SuperKMeansParameters& cp,
159
156
  TrainState& state,
160
- IterScratch& scratch,
157
+ SuperKMeansAssignScratch& assign_scratch,
161
158
  int64_t& total_pairs,
162
159
  int64_t& pruned_at_gemm) {
163
- const int dp = state.d_prime;
164
- assert(dp >= 1);
165
- assert(!state.ad_coeff.empty());
166
- assert(!scratch.partial_ip.empty());
167
- assert(!scratch.y_norms_partial.empty());
168
- const int d_trail = d - dp;
169
160
  const int n_train = state.n;
161
+ assert(state.d_prime >= 1);
162
+ assert(!state.ad_coeff.empty());
170
163
  assert(static_cast<int>(state.best_dists.size()) >= n_train);
171
- assert(static_cast<int>(state.x_norms_partial.size()) >= n_train);
172
164
  assert(static_cast<int>(state.assignments.size()) >= n_train);
173
165
 
174
- if (d_trail != scratch.prev_d_trail) {
175
- scratch.Y_pdx.resize(static_cast<size_t>(k) * d_trail);
176
- scratch.Y_trail.resize(static_cast<size_t>(k) * d_trail);
177
- scratch.prev_d_trail = d_trail;
178
- }
179
- for (int j = 0; j < k; ++j) {
180
- std::memcpy(
181
- scratch.Y_trail.data() + static_cast<size_t>(j) * d_trail,
182
- state.Y_tilde.data() + static_cast<size_t>(j) * d + dp,
183
- d_trail * sizeof(float));
184
- }
185
- detail::pdxify(
186
- scratch.Y_trail.data(),
187
- k,
188
- d_trail,
189
- cp.pdx_block_size,
190
- scratch.Y_pdx.data());
191
-
192
- detail::compute_partial_norms(
193
- state.Y_tilde.data(), k, d, dp, scratch.y_norms_partial.data());
194
-
195
- const int n_blocks = static_cast<int>(state.block_dim.size());
196
-
197
- for (int xi = 0; xi < n_train; xi += cp.x_batch) {
198
- const int bx = std::min(cp.x_batch, n_train - xi);
199
-
200
- // Refresh tau: recompute full-d L2 distance to the previously
201
- // assigned centroid. This is intentionally over all d dims (not
202
- // just d_prime) because tau must be an exact distance for the
203
- // chi-squared pruning bound to be valid. Cost is O(bx * d) per
204
- // x-batch, amortized across the y-batch tiles that follow.
166
+ // Refresh tau: exact full-d L2 distance to the currently assigned centroid.
167
+ // super_kmeans_assign_iteration requires an exact tau (the chi-squared
168
+ // bound assumes it), and centroids moved since the previous assignment.
205
169
  #pragma omp parallel for
206
- for (int i = 0; i < bx; ++i) {
207
- const int j_prev = state.assignments[xi + i];
208
- const float* xrow =
209
- state.X_tilde.data() + static_cast<size_t>(xi + i) * d;
210
- const float* yrow =
211
- state.Y_tilde.data() + static_cast<size_t>(j_prev) * d;
212
- float tau = 0.0f;
213
- for (int m = 0; m < d; ++m) {
214
- const float diff = xrow[m] - yrow[m];
215
- tau += diff * diff;
216
- }
217
- state.best_dists[xi + i] = tau;
218
- }
219
-
220
- for (int yj = 0; yj < k; yj += cp.y_batch) {
221
- const int by = std::min(cp.y_batch, k - yj);
222
-
223
- // GEMM phase: column-major sgemm computes
224
- // partial_ip[i*by + j] = <X[xi+i, 0:dp], Y[yj+j, 0:dp]>.
225
- {
226
- FINTEGER M = by;
227
- FINTEGER N_ = bx;
228
- FINTEGER K_ = dp;
229
- float alpha = 1.0f;
230
- float beta = 0.0f;
231
- FINTEGER lda_y = d;
232
- FINTEGER lda_x = d;
233
- FINTEGER ldc = by;
234
- sgemm_("Transpose",
235
- "Not transpose",
236
- &M,
237
- &N_,
238
- &K_,
239
- &alpha,
240
- state.Y_tilde.data() + static_cast<size_t>(yj) * d,
241
- &lda_y,
242
- state.X_tilde.data() + static_cast<size_t>(xi) * d,
243
- &lda_x,
244
- &beta,
245
- scratch.partial_ip.data(),
246
- &ldc);
247
- }
248
-
249
- // One SIMD dispatch per (xi, yj) tile — block_l2<SL> below is
250
- // a direct call (no per-call switch on SIMDConfig::level).
251
- with_simd_level([&]<SIMDLevel SL>() {
252
- [[maybe_unused]] const int omp_chunk_local = cp.omp_chunk;
253
- int64_t total_pairs_local = 0;
254
- int64_t pruned_at_gemm_local = 0;
255
- #pragma omp parallel for schedule(dynamic, omp_chunk_local) \
256
- reduction(+ : total_pairs_local) reduction(+ : pruned_at_gemm_local)
257
- for (int i = 0; i < bx; ++i) {
258
- // tau is the best full-d distance found so far for this
259
- // point; tightened as closer centroids are found.
260
- float tau = state.best_dists[xi + i];
261
- int best_j = state.assignments[xi + i];
262
- const float xnp_i = state.x_norms_partial[xi + i];
263
- const float* xrow = state.X_tilde.data() +
264
- static_cast<size_t>(xi + i) * d;
265
-
266
- for (int j = 0; j < by; ++j) {
267
- ++total_pairs_local;
268
-
269
- // L2-from-IP; clamp to handle catastrophic
270
- // cancellation when the true distance is ~0.
271
- float pd = xnp_i + scratch.y_norms_partial[yj + j] -
272
- 2.0f *
273
- scratch.partial_ip
274
- [static_cast<size_t>(i) * by +
275
- j];
276
- if (pd < 0.0f) {
277
- pd = 0.0f;
278
- }
279
-
280
- if (pd > state.ad_coeff[dp] * tau) {
281
- ++pruned_at_gemm_local;
282
- continue;
283
- }
284
-
285
- // double accumulator mitigates float drift over many
286
- // block additions.
287
- double dist = pd;
288
- bool keep = true;
289
-
290
- // Progressive pruning across PDX blocks. Per block:
291
- // stride = k * block_dim[b] floats, column-major
292
- // across centroids.
293
- size_t pdx_offset = 0;
294
- for (int b = 0; b < n_blocks; ++b) {
295
- const int n_in_block = state.block_dim.at(b);
296
- const int true_end = state.true_block_end.at(b);
297
- const float* xblk = xrow + (true_end - n_in_block);
298
- const float* yblk = scratch.Y_pdx.data() +
299
- pdx_offset +
300
- static_cast<size_t>(yj + j) * n_in_block;
301
- dist += faiss::detail::block_l2<SL>(
302
- xblk, yblk, n_in_block);
303
- pdx_offset += static_cast<size_t>(k) * n_in_block;
304
-
305
- if (dist >
306
- static_cast<double>(state.ad_coeff[true_end]) *
307
- tau) {
308
- keep = false;
309
- break;
310
- }
311
- }
312
-
313
- if (keep && dist < tau) {
314
- tau = static_cast<float>(dist);
315
- best_j = yj + j;
316
- }
317
- }
318
-
319
- state.best_dists[xi + i] = tau;
320
- state.assignments[xi + i] = best_j;
321
- }
322
- total_pairs += total_pairs_local;
323
- pruned_at_gemm += pruned_at_gemm_local;
324
- });
170
+ for (int i = 0; i < n_train; ++i) {
171
+ const int j_prev = state.assignments[i];
172
+ const float* xrow = state.X_tilde.data() + static_cast<size_t>(i) * d;
173
+ const float* yrow =
174
+ state.Y_tilde.data() + static_cast<size_t>(j_prev) * d;
175
+ float tau = 0.0f;
176
+ for (int m = 0; m < d; ++m) {
177
+ const float diff = xrow[m] - yrow[m];
178
+ tau += diff * diff;
325
179
  }
180
+ state.best_dists[i] = tau;
326
181
  }
327
182
 
183
+ super_kmeans_assign_iteration(
184
+ state.X_tilde.data(),
185
+ n_train,
186
+ d,
187
+ state.Y_tilde.data(),
188
+ k,
189
+ state.best_dists.data(),
190
+ state.assignments.data(),
191
+ state.d_prime,
192
+ state.ad_coeff.data(),
193
+ cp,
194
+ &total_pairs,
195
+ &pruned_at_gemm,
196
+ &assign_scratch);
197
+
328
198
  double objective = 0.0;
329
199
  for (int i = 0; i < n_train; ++i) {
330
200
  objective += state.best_dists[i];
@@ -337,13 +207,13 @@ int update_centroids_and_split(
337
207
  int d,
338
208
  int k,
339
209
  TrainState& state,
340
- IterScratch& scratch,
210
+ std::vector<int64_t>& labels64,
341
211
  std::vector<float>& hassign) {
342
212
  std::fill(hassign.begin(), hassign.end(), 0.0f);
343
- assert(!scratch.labels64.empty());
213
+ assert(!labels64.empty());
344
214
  assert(!state.assignments.empty());
345
215
  for (int i = 0; i < state.n; ++i) {
346
- scratch.labels64[i] = static_cast<int64_t>(state.assignments[i]);
216
+ labels64[i] = static_cast<int64_t>(state.assignments[i]);
347
217
  }
348
218
  detail::compute_centroids(
349
219
  d,
@@ -352,7 +222,7 @@ int update_centroids_and_split(
352
222
  /*k_frozen=*/0,
353
223
  reinterpret_cast<const uint8_t*>(state.X_tilde.data()),
354
224
  /*codec=*/nullptr,
355
- scratch.labels64.data(),
225
+ labels64.data(),
356
226
  /*weights=*/nullptr,
357
227
  hassign.data(),
358
228
  state.Y_tilde.data());
@@ -368,9 +238,8 @@ int update_centroids_and_split(
368
238
  state.Y_tilde.data());
369
239
  }
370
240
 
371
- /// Stay-in-band controller: nudge state.d_prime based on observed
372
- /// pruning rate. Recomputes x_norms_partial if d_prime changed. Returns
373
- /// the observed pruning rate (0 when there were no pairs).
241
+ /// Stay-in-band controller: nudge state.d_prime based on observed pruning
242
+ /// rate. Returns the observed pruning rate (0 when there were no pairs).
374
243
  float adapt_d_prime(
375
244
  int d,
376
245
  const SuperKMeansParameters& cp,
@@ -392,25 +261,16 @@ float adapt_d_prime(
392
261
  }
393
262
  new_dp = std::max(cp.d_prime_min, new_dp);
394
263
  new_dp = std::min(d / 2, new_dp);
395
- if (new_dp != state.d_prime) {
396
- state.d_prime = new_dp;
397
- detail::compute_partial_norms(
398
- state.X_tilde.data(),
399
- state.n,
400
- d,
401
- state.d_prime,
402
- state.x_norms_partial.data());
403
- rebuild_pdx_block_layout(d, cp.pdx_block_size, state);
404
- }
264
+ state.d_prime = new_dp;
405
265
  return pruning_rate;
406
266
  }
407
267
 
408
268
  /// Pre-loop setup: subsample, rotate, Forgy init, build ADSampling table,
409
- /// allocate scratch. Returned `sampled_x_owner` keeps the subsampled buffer
410
- /// alive when subsampling occurred (otherwise empty).
269
+ /// size the label scratch. Returned `sampled_x_owner` keeps the subsampled
270
+ /// buffer alive when subsampling occurred (otherwise empty).
411
271
  std::unique_ptr<uint8_t[]> setup_train_state(
412
272
  TrainState& state,
413
- IterScratch& scratch,
273
+ std::vector<int64_t>& labels64,
414
274
  std::vector<float>& hassign,
415
275
  const SuperKMeansParameters& cp,
416
276
  int d,
@@ -445,10 +305,17 @@ std::unique_ptr<uint8_t[]> setup_train_state(
445
305
  "SuperKMeans: training set size exceeds INT_MAX after sampling");
446
306
  state.n = static_cast<int>(nx);
447
307
 
448
- state.R.init(cp.seed);
308
+ if (auto* R = dynamic_cast<HadamardRotation*>(state.R.get())) {
309
+ R->init(cp.seed);
310
+ } else {
311
+ auto* dense_rotation =
312
+ dynamic_cast<RandomRotationMatrix*>(state.R.get());
313
+ FAISS_ASSERT(dense_rotation != nullptr);
314
+ dense_rotation->init(cp.seed);
315
+ }
449
316
 
450
317
  state.X_tilde.resize(static_cast<size_t>(state.n) * d);
451
- state.R.apply_noalloc(state.n, x_sampled, state.X_tilde.data());
318
+ state.R->apply_noalloc(state.n, x_sampled, state.X_tilde.data());
452
319
 
453
320
  // Forgy init: pick k random rows from the rotated pool as initial
454
321
  // centroids. These remain in rotated space; un-rotation happens
@@ -463,22 +330,14 @@ std::unique_ptr<uint8_t[]> setup_train_state(
463
330
  state.X_tilde.data() + static_cast<size_t>(perm[j]) * d,
464
331
  sizeof(float) * d);
465
332
  }
333
+ if (cp.spherical) {
334
+ fvec_renorm_L2(d, k, state.Y_tilde.data());
335
+ }
466
336
  }
467
337
 
468
338
  state.d_prime =
469
339
  std::max(cp.d_prime_min, static_cast<int>(d * cp.d_prime_fraction));
470
340
  state.d_prime = std::min(state.d_prime, d / 2);
471
- rebuild_pdx_block_layout(d, cp.pdx_block_size, state);
472
-
473
- // Iter 1+ uses L2-from-IP only over [0, d_prime), so full ||X[i]||^2 is
474
- // never read; iter 0 routes through knn_L2sqr which carries its own.
475
- state.x_norms_partial.resize(state.n);
476
- detail::compute_partial_norms(
477
- state.X_tilde.data(),
478
- state.n,
479
- d,
480
- state.d_prime,
481
- state.x_norms_partial.data());
482
341
 
483
342
  const double epsilon = static_cast<double>(cp.ad_epsilon_factor) / d;
484
343
  state.ad_coeff = detail::precompute_ad_thresholds(d, epsilon);
@@ -491,11 +350,7 @@ std::unique_ptr<uint8_t[]> setup_train_state(
491
350
 
492
351
  hassign.assign(k, 0.0f);
493
352
 
494
- const int by_max = std::min(cp.y_batch, k);
495
- const int bx_max = std::min(cp.x_batch, state.n);
496
- scratch.partial_ip.resize(static_cast<size_t>(bx_max) * by_max);
497
- scratch.y_norms_partial.resize(k);
498
- scratch.labels64.resize(state.n);
353
+ labels64.resize(state.n);
499
354
 
500
355
  return sampled_x_owner;
501
356
  }
@@ -504,7 +359,7 @@ std::unique_ptr<uint8_t[]> setup_train_state(
504
359
  /// reverse_transform applies R^T = R^-1.
505
360
  void untransform_centroids(
506
361
  std::vector<float>& centroids,
507
- const RandomRotationMatrix& R,
362
+ const VectorTransform& R,
508
363
  int d,
509
364
  int k,
510
365
  const float* Y_tilde) {
@@ -557,7 +412,7 @@ SuperKMeans::SuperKMeans(int d, int k, const SuperKMeansParameters& cp_in)
557
412
 
558
413
  void SuperKMeans::train(idx_t n, const float* x) {
559
414
  FAISS_THROW_IF_NOT_MSG(n > 0, "SuperKMeans: n must be positive");
560
- FAISS_THROW_IF_NOT_MSG(x != nullptr, "SuperKMeans: x must not be null");
415
+ FAISS_THROW_IF_MSG(x == nullptr, "SuperKMeans: x must not be null");
561
416
  FAISS_THROW_IF_NOT_MSG(
562
417
  n >= static_cast<idx_t>(k), "SuperKMeans: n must be >= k");
563
418
  if (cp.check_input_data_for_NaNs) {
@@ -576,11 +431,12 @@ void SuperKMeans::train(idx_t n, const float* x) {
576
431
  static_cast<idx_t>(k) * cp.min_points_per_centroid);
577
432
  }
578
433
 
579
- TrainState state(d);
580
- IterScratch scratch;
434
+ TrainState state(d, cp.spherical);
435
+ std::vector<int64_t> labels64;
436
+ SuperKMeansAssignScratch assign_scratch;
581
437
  std::vector<float> hassign;
582
438
  [[maybe_unused]] auto sampled_x_owner =
583
- setup_train_state(state, scratch, hassign, cp, d, k, n, x);
439
+ setup_train_state(state, labels64, hassign, cp, d, k, n, x);
584
440
 
585
441
  iteration_stats.clear();
586
442
  iteration_stats.reserve(cp.niter);
@@ -599,11 +455,20 @@ void SuperKMeans::train(idx_t n, const float* x) {
599
455
  objective = run_iter0_full_gemm(d, k, state);
600
456
  } else {
601
457
  objective = run_iter_pruned(
602
- d, k, cp, state, scratch, total_pairs, pruned_at_gemm);
458
+ d,
459
+ k,
460
+ cp,
461
+ state,
462
+ assign_scratch,
463
+ total_pairs,
464
+ pruned_at_gemm);
603
465
  }
604
466
 
605
467
  const int nsplit =
606
- update_centroids_and_split(d, k, state, scratch, hassign);
468
+ update_centroids_and_split(d, k, state, labels64, hassign);
469
+ if (cp.spherical) {
470
+ fvec_renorm_L2(d, k, state.Y_tilde.data());
471
+ }
607
472
  const float pruning_rate = (iter == 0)
608
473
  ? 0.0f
609
474
  : adapt_d_prime(d, cp, state, total_pairs, pruned_at_gemm);
@@ -650,7 +515,181 @@ void SuperKMeans::train(idx_t n, const float* x) {
650
515
  (getmillisecs() - t_train_start) / 1000.0);
651
516
  }
652
517
 
653
- untransform_centroids(centroids, state.R, d, k, state.Y_tilde.data());
518
+ untransform_centroids(centroids, *state.R, d, k, state.Y_tilde.data());
519
+ }
520
+
521
+ void super_kmeans_assign_iteration(
522
+ const float* X_tilde,
523
+ int n,
524
+ int d,
525
+ const float* Y_tilde,
526
+ int k,
527
+ float* tau,
528
+ int32_t* assignments,
529
+ int d_prime,
530
+ const float* ad_coeff,
531
+ const SuperKMeansParameters& cp,
532
+ int64_t* total_pairs_out,
533
+ int64_t* pruned_at_gemm_out,
534
+ SuperKMeansAssignScratch* scratch_in) {
535
+ FAISS_THROW_IF_NOT_MSG(
536
+ d_prime >= 1,
537
+ "super_kmeans_assign_iteration: d_prime must be >= 1");
538
+ FAISS_THROW_IF_NOT_MSG(
539
+ d_prime < d, "super_kmeans_assign_iteration: d_prime must be < d");
540
+ FAISS_THROW_IF_MSG(
541
+ ad_coeff == nullptr,
542
+ "super_kmeans_assign_iteration: ad_coeff must not be null");
543
+ FAISS_THROW_IF_MSG(
544
+ tau == nullptr,
545
+ "super_kmeans_assign_iteration: tau must not be null");
546
+ FAISS_THROW_IF_MSG(
547
+ assignments == nullptr,
548
+ "super_kmeans_assign_iteration: assignments must not be null");
549
+
550
+ const int d_trail = d - d_prime;
551
+ const auto layout = build_pdx_block_layout(d, d_prime, cp.pdx_block_size);
552
+ const int n_blocks = static_cast<int>(layout.block_dim.size());
553
+
554
+ // Reuse the caller's scratch when provided (grow-only buffers, so a caller
555
+ // looping over iterations allocates nothing in steady state); otherwise use
556
+ // a local instance for one-shot callers.
557
+ SuperKMeansAssignScratch local_scratch;
558
+ SuperKMeansAssignScratch& s = scratch_in ? *scratch_in : local_scratch;
559
+
560
+ // PDX-lay out the centroid trailing block once per call.
561
+ auto& Y_trail = s.Y_trail;
562
+ auto& Y_pdx = s.Y_pdx;
563
+ Y_trail.resize(static_cast<size_t>(k) * d_trail);
564
+ Y_pdx.resize(static_cast<size_t>(k) * d_trail);
565
+ for (int j = 0; j < k; ++j) {
566
+ std::memcpy(
567
+ Y_trail.data() + static_cast<size_t>(j) * d_trail,
568
+ Y_tilde + static_cast<size_t>(j) * d + d_prime,
569
+ d_trail * sizeof(float));
570
+ }
571
+ detail::pdxify(Y_trail.data(), k, d_trail, cp.pdx_block_size, Y_pdx.data());
572
+
573
+ auto& x_norms_partial = s.x_norms_partial;
574
+ auto& y_norms_partial = s.y_norms_partial;
575
+ x_norms_partial.resize(n);
576
+ detail::compute_partial_norms(
577
+ X_tilde, n, d, d_prime, x_norms_partial.data());
578
+ y_norms_partial.resize(k);
579
+ detail::compute_partial_norms(
580
+ Y_tilde, k, d, d_prime, y_norms_partial.data());
581
+
582
+ int64_t total_pairs = 0;
583
+ int64_t pruned_at_gemm = 0;
584
+
585
+ // Size the GEMM tile buffer to the actual max tile, not cp.x_batch *
586
+ // cp.y_batch, which over-allocates when n < x_batch or k < y_batch.
587
+ const int bx_max = std::min(cp.x_batch, n);
588
+ const int by_max = std::min(cp.y_batch, k);
589
+ auto& partial_ip = s.partial_ip;
590
+ partial_ip.resize(static_cast<size_t>(bx_max) * by_max);
591
+
592
+ for (int xi = 0; xi < n; xi += cp.x_batch) {
593
+ const int bx = std::min(cp.x_batch, n - xi);
594
+ for (int yj = 0; yj < k; yj += cp.y_batch) {
595
+ const int by = std::min(cp.y_batch, k - yj);
596
+
597
+ // Partial GEMM over [0, d_prime): partial_ip[i*by + j] =
598
+ // <X[xi+i, 0:d_prime], Y[yj+j, 0:d_prime]>.
599
+ {
600
+ FINTEGER M = by;
601
+ FINTEGER N_ = bx;
602
+ FINTEGER K_ = d_prime;
603
+ float alpha = 1.0f;
604
+ float beta = 0.0f;
605
+ FINTEGER lda_y = d;
606
+ FINTEGER lda_x = d;
607
+ FINTEGER ldc = by;
608
+ sgemm_("Transpose",
609
+ "Not transpose",
610
+ &M,
611
+ &N_,
612
+ &K_,
613
+ &alpha,
614
+ Y_tilde + static_cast<size_t>(yj) * d,
615
+ &lda_y,
616
+ X_tilde + static_cast<size_t>(xi) * d,
617
+ &lda_x,
618
+ &beta,
619
+ partial_ip.data(),
620
+ &ldc);
621
+ }
622
+
623
+ // One SIMD dispatch per (xi, yj) tile.
624
+ with_simd_level_with_sve([&]<SIMDLevel SL>() {
625
+ [[maybe_unused]] const int omp_chunk_local = cp.omp_chunk;
626
+ int64_t tile_total = 0;
627
+ int64_t tile_pruned = 0;
628
+ #pragma omp parallel for schedule(dynamic, omp_chunk_local) \
629
+ reduction(+ : tile_total) reduction(+ : tile_pruned)
630
+ for (int i = 0; i < bx; ++i) {
631
+ const float xnp_i = x_norms_partial[xi + i];
632
+ float tau_i = tau[xi + i];
633
+ int32_t best_j = assignments[xi + i];
634
+ const float* xrow =
635
+ X_tilde + static_cast<size_t>(xi + i) * d;
636
+ for (int j = 0; j < by; ++j) {
637
+ ++tile_total;
638
+ // L2-from-IP; clamp against catastrophic cancellation
639
+ // when the true distance is ~0.
640
+ float pd = xnp_i + y_norms_partial[yj + j] -
641
+ 2.0f *
642
+ partial_ip
643
+ [static_cast<size_t>(i) * by +
644
+ j];
645
+ if (pd < 0.0f) {
646
+ pd = 0.0f;
647
+ }
648
+ // ADSampling chi-squared bound at the d_prime boundary.
649
+ if (pd > ad_coeff[d_prime] * tau_i) {
650
+ ++tile_pruned;
651
+ continue;
652
+ }
653
+ // Progressive pruning across PDX blocks; double
654
+ // accumulator mitigates float drift over many block
655
+ // additions.
656
+ double dist = pd;
657
+ bool keep = true;
658
+ size_t pdx_offset = 0;
659
+ for (int b = 0; b < n_blocks; ++b) {
660
+ const int n_in_block = layout.block_dim[b];
661
+ const int true_end = layout.true_block_end[b];
662
+ const float* xblk = xrow + (true_end - n_in_block);
663
+ const float* yblk = Y_pdx.data() + pdx_offset +
664
+ static_cast<size_t>(yj + j) * n_in_block;
665
+ dist += faiss::detail::block_l2<SL>(
666
+ xblk, yblk, n_in_block);
667
+ pdx_offset += static_cast<size_t>(k) * n_in_block;
668
+ if (dist > static_cast<double>(ad_coeff[true_end]) *
669
+ tau_i) {
670
+ keep = false;
671
+ break;
672
+ }
673
+ }
674
+ if (keep && dist < tau_i) {
675
+ tau_i = static_cast<float>(dist);
676
+ best_j = static_cast<int32_t>(yj + j);
677
+ }
678
+ }
679
+ tau[xi + i] = tau_i;
680
+ assignments[xi + i] = best_j;
681
+ }
682
+ total_pairs += tile_total;
683
+ pruned_at_gemm += tile_pruned;
684
+ });
685
+ }
686
+ }
687
+ if (total_pairs_out) {
688
+ *total_pairs_out = total_pairs;
689
+ }
690
+ if (pruned_at_gemm_out) {
691
+ *pruned_at_gemm_out = pruned_at_gemm;
692
+ }
654
693
  }
655
694
 
656
695
  } // namespace faiss