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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/lib/faiss/version.rb +1 -1
- data/vendor/faiss/faiss/AutoTune.cpp +3 -1
- data/vendor/faiss/faiss/Clustering.cpp +9 -1
- data/vendor/faiss/faiss/Clustering.h +8 -0
- data/vendor/faiss/faiss/IVFlib.cpp +14 -3
- data/vendor/faiss/faiss/Index.h +2 -2
- data/vendor/faiss/faiss/IndexAdditiveQuantizer.cpp +9 -10
- data/vendor/faiss/faiss/IndexAdditiveQuantizerFastScan.cpp +2 -3
- data/vendor/faiss/faiss/IndexBinaryFromFloat.cpp +24 -4
- data/vendor/faiss/faiss/IndexBinaryHNSW.cpp +16 -145
- data/vendor/faiss/faiss/IndexBinaryHNSW.h +0 -6
- data/vendor/faiss/faiss/IndexBinaryHash.cpp +5 -9
- data/vendor/faiss/faiss/IndexBinaryIVF.cpp +8 -18
- data/vendor/faiss/faiss/IndexBinaryIVF.h +8 -1
- data/vendor/faiss/faiss/IndexEDEN.cpp +273 -0
- data/vendor/faiss/faiss/IndexEDEN.h +57 -0
- data/vendor/faiss/faiss/IndexFastScan.cpp +15 -4
- data/vendor/faiss/faiss/IndexFlat.cpp +21 -54
- data/vendor/faiss/faiss/IndexFlat.h +2 -2
- data/vendor/faiss/faiss/IndexHNSW.cpp +311 -102
- data/vendor/faiss/faiss/IndexHNSW.h +31 -7
- data/vendor/faiss/faiss/IndexIDMap.cpp +26 -8
- data/vendor/faiss/faiss/IndexIDMap.h +2 -0
- data/vendor/faiss/faiss/IndexIVF.cpp +36 -10
- data/vendor/faiss/faiss/IndexIVFAdditiveQuantizer.cpp +1 -1
- data/vendor/faiss/faiss/IndexIVFAdditiveQuantizerFastScan.cpp +3 -4
- data/vendor/faiss/faiss/IndexIVFEDEN.cpp +302 -0
- data/vendor/faiss/faiss/IndexIVFEDEN.h +70 -0
- data/vendor/faiss/faiss/IndexIVFFastScan.cpp +5 -6
- data/vendor/faiss/faiss/IndexIVFFlat.cpp +3 -4
- data/vendor/faiss/faiss/IndexIVFIndependentQuantizer.cpp +1 -1
- data/vendor/faiss/faiss/IndexIVFPQ.cpp +49 -23
- data/vendor/faiss/faiss/IndexIVFPQ.h +11 -0
- data/vendor/faiss/faiss/IndexIVFPQFastScan.cpp +0 -1
- data/vendor/faiss/faiss/IndexIVFRaBitQ.cpp +19 -49
- data/vendor/faiss/faiss/IndexIVFRaBitQFastScan.cpp +180 -76
- data/vendor/faiss/faiss/IndexIVFRaBitQFastScan.h +5 -4
- data/vendor/faiss/faiss/IndexIVFSpectralHash.cpp +8 -6
- data/vendor/faiss/faiss/IndexLSH.cpp +2 -3
- data/vendor/faiss/faiss/IndexLattice.cpp +5 -0
- data/vendor/faiss/faiss/IndexNNDescent.cpp +9 -2
- data/vendor/faiss/faiss/IndexNSG.cpp +7 -2
- data/vendor/faiss/faiss/IndexPQ.cpp +6 -8
- data/vendor/faiss/faiss/IndexPreTransform.cpp +15 -0
- data/vendor/faiss/faiss/IndexRaBitQ.cpp +2 -2
- data/vendor/faiss/faiss/IndexRaBitQFastScan.cpp +1 -2
- data/vendor/faiss/faiss/IndexRaBitQFastScan.h +5 -1
- data/vendor/faiss/faiss/IndexRefine.cpp +30 -1
- data/vendor/faiss/faiss/IndexReplicas.cpp +1 -2
- data/vendor/faiss/faiss/IndexShards.cpp +5 -5
- data/vendor/faiss/faiss/IndexShardsIVF.cpp +6 -5
- data/vendor/faiss/faiss/MetaIndexes.cpp +2 -4
- data/vendor/faiss/faiss/SuperKMeans.cpp +286 -247
- data/vendor/faiss/faiss/SuperKMeans.h +33 -2
- data/vendor/faiss/faiss/VectorTransform.cpp +71 -2
- data/vendor/faiss/faiss/VectorTransform.h +3 -0
- data/vendor/faiss/faiss/clone_index.cpp +8 -0
- data/vendor/faiss/faiss/factory_tools.cpp +47 -4
- data/vendor/faiss/faiss/gpu/GpuCloner.cpp +11 -11
- data/vendor/faiss/faiss/gpu/GpuClonerOptions.h +1 -5
- data/vendor/faiss/faiss/gpu/GpuDistance.h +2 -5
- data/vendor/faiss/faiss/gpu/GpuIndex.h +38 -16
- data/vendor/faiss/faiss/gpu/GpuIndexCagra.h +71 -1
- data/vendor/faiss/faiss/gpu/GpuIndexIVF.h +17 -0
- data/vendor/faiss/faiss/gpu/GpuIndexIVFScalarQuantizer.h +16 -0
- data/vendor/faiss/faiss/gpu/perf/PerfClustering.cpp +1 -1
- data/vendor/faiss/faiss/gpu/perf/PerfIVFPQAdd.cpp +2 -2
- data/vendor/faiss/faiss/gpu/test/TestGpuIndexIVFScalarQuantizer.cpp +180 -0
- data/vendor/faiss/faiss/gpu_metal/MetalIndexIVFFlat.h +1 -5
- data/vendor/faiss/faiss/gpu_metal/MetalIndexIVFPQ.h +88 -0
- data/vendor/faiss/faiss/gpu_metal/impl/MetalIVFPQ.h +134 -0
- data/vendor/faiss/faiss/impl/AdditiveQuantizer.cpp +1 -1
- data/vendor/faiss/faiss/impl/ClusteringInitialization.cpp +7 -4
- data/vendor/faiss/faiss/impl/DistanceComputer.h +34 -0
- data/vendor/faiss/faiss/impl/EDENQuantizer.h +119 -0
- data/vendor/faiss/faiss/impl/HNSW.cpp +528 -267
- data/vendor/faiss/faiss/impl/HNSW.h +46 -7
- data/vendor/faiss/faiss/impl/IDSelector.h +44 -0
- data/vendor/faiss/faiss/impl/LocalSearchQuantizer.cpp +2 -2
- data/vendor/faiss/faiss/impl/NNDescent.cpp +10 -3
- data/vendor/faiss/faiss/impl/NSG.cpp +3 -1
- data/vendor/faiss/faiss/impl/Panorama.h +20 -9
- data/vendor/faiss/faiss/impl/PolysemousTraining.cpp +152 -84
- data/vendor/faiss/faiss/impl/ProductQuantizer.cpp +38 -26
- data/vendor/faiss/faiss/impl/RaBitQUtils.cpp +45 -37
- data/vendor/faiss/faiss/impl/RaBitQUtils.h +35 -0
- data/vendor/faiss/faiss/impl/RaBitQuantizer.cpp +239 -72
- data/vendor/faiss/faiss/impl/RaBitQuantizer.h +66 -4
- data/vendor/faiss/faiss/impl/RaBitQuantizerMultiBit.cpp +4 -13
- data/vendor/faiss/faiss/impl/ResultHandler.h +34 -34
- data/vendor/faiss/faiss/impl/ScalarQuantizer.cpp +287 -84
- data/vendor/faiss/faiss/impl/ScalarQuantizer.h +26 -10
- data/vendor/faiss/faiss/impl/ThreadedIndex-inl.h +2 -2
- data/vendor/faiss/faiss/impl/VisitedTable.cpp +22 -2
- data/vendor/faiss/faiss/impl/VisitedTable.h +20 -0
- data/vendor/faiss/faiss/impl/binary_hamming/IndexBinaryIVF_impl.h +90 -14
- data/vendor/faiss/faiss/impl/binary_hamming/avx2.cpp +4 -4
- data/vendor/faiss/faiss/impl/expanded_scanners.h +5 -1
- data/vendor/faiss/faiss/impl/fast_scan/decompose_qbs.h +1 -0
- data/vendor/faiss/faiss/impl/fast_scan/dispatching.h +35 -2
- data/vendor/faiss/faiss/impl/hnsw/LockVector.cpp +1 -1
- data/vendor/faiss/faiss/impl/index_read.cpp +491 -50
- data/vendor/faiss/faiss/impl/index_write.cpp +86 -30
- data/vendor/faiss/faiss/impl/lattice_Zn.cpp +8 -9
- data/vendor/faiss/faiss/impl/platform_macros.h +3 -1
- data/vendor/faiss/faiss/impl/polysemous_training/avx512.cpp +284 -0
- data/vendor/faiss/faiss/impl/polysemous_training/dispatch.h +115 -0
- data/vendor/faiss/faiss/impl/pq_code_distance/IVFPQScanner_impl.h +73 -39
- data/vendor/faiss/faiss/impl/pq_code_distance/IVFPQ_QueryTables.cpp +0 -1
- data/vendor/faiss/faiss/impl/pq_code_distance/PQDistanceComputer_impl.h +26 -15
- data/vendor/faiss/faiss/impl/pq_code_distance/avx2.cpp +4 -4
- data/vendor/faiss/faiss/impl/pq_code_distance/pq_code_distance-generic.cpp +4 -4
- data/vendor/faiss/faiss/impl/result_handler/ResultHandler.cpp +195 -0
- data/vendor/faiss/faiss/impl/result_handler/avx2.cpp +133 -0
- data/vendor/faiss/faiss/impl/result_handler/avx512.cpp +281 -0
- data/vendor/faiss/faiss/impl/scalar_quantizer/EDENQuantizer-avx2.cpp +72 -0
- data/vendor/faiss/faiss/impl/scalar_quantizer/EDENQuantizer-avx512.cpp +228 -0
- data/vendor/faiss/faiss/impl/scalar_quantizer/EDENQuantizer.cpp +887 -0
- data/vendor/faiss/faiss/impl/scalar_quantizer/distance_computers.h +2 -2
- data/vendor/faiss/faiss/impl/scalar_quantizer/quantizers.h +9 -8
- data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx2.cpp +90 -24
- data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512-impl.h +30 -30
- data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512-spr.cpp +4 -5
- data/vendor/faiss/faiss/impl/scalar_quantizer/sq-avx512.cpp +101 -34
- data/vendor/faiss/faiss/impl/scalar_quantizer/sq-dispatch.h +169 -13
- data/vendor/faiss/faiss/impl/scalar_quantizer/sq-neon.cpp +125 -26
- data/vendor/faiss/faiss/impl/simd_dispatch.h +70 -31
- data/vendor/faiss/faiss/index_factory.cpp +40 -7
- data/vendor/faiss/faiss/invlists/DirectMap.cpp +1 -1
- data/vendor/faiss/faiss/invlists/InvertedLists.cpp +9 -6
- data/vendor/faiss/faiss/invlists/OnDiskInvertedLists.cpp +29 -8
- data/vendor/faiss/faiss/python/python_callbacks.cpp +3 -1
- data/vendor/faiss/faiss/svs/IndexSVSFaissUtils.h +60 -0
- data/vendor/faiss/faiss/svs/IndexSVSFlat.cpp +26 -1
- data/vendor/faiss/faiss/svs/IndexSVSFlat.h +13 -0
- data/vendor/faiss/faiss/svs/IndexSVSIVF.cpp +1 -1
- data/vendor/faiss/faiss/svs/IndexSVSIVFLeanVec.cpp +1 -1
- data/vendor/faiss/faiss/svs/IndexSVSVamana.cpp +47 -5
- data/vendor/faiss/faiss/svs/IndexSVSVamana.h +23 -3
- data/vendor/faiss/faiss/svs/IndexSVSVamanaLVQ.cpp +4 -2
- data/vendor/faiss/faiss/svs/IndexSVSVamanaLVQ.h +2 -1
- data/vendor/faiss/faiss/svs/IndexSVSVamanaLeanVec.cpp +10 -4
- data/vendor/faiss/faiss/svs/IndexSVSVamanaLeanVec.h +2 -1
- data/vendor/faiss/faiss/utils/approx_topk_hamming/approx_topk_hamming.h +1 -1
- data/vendor/faiss/faiss/utils/distances.cpp +30 -11
- data/vendor/faiss/faiss/utils/distances_dispatch.h +30 -24
- data/vendor/faiss/faiss/utils/distances_fused/distances_fused.cpp +1 -1
- data/vendor/faiss/faiss/utils/distances_simd.cpp +4 -3
- data/vendor/faiss/faiss/utils/extra_distances.cpp +4 -14
- data/vendor/faiss/faiss/utils/extra_distances.h +1 -2
- data/vendor/faiss/faiss/utils/hamming.cpp +16 -10
- data/vendor/faiss/faiss/utils/hamming.h +10 -1
- data/vendor/faiss/faiss/utils/hamming_distance/common.h +14 -3
- data/vendor/faiss/faiss/utils/hamming_distance/hamming_avx512_vpopcnt.cpp +24 -0
- data/vendor/faiss/faiss/utils/hamming_distance/hamming_computer-avx512.h +1 -1
- data/vendor/faiss/faiss/utils/hamming_distance/{hamming_computer-avx512_spr.h → hamming_computer-avx512_vpopcnt.h} +85 -24
- data/vendor/faiss/faiss/utils/hamming_distance/hamming_impl.h +141 -0
- data/vendor/faiss/faiss/utils/quantize_lut.cpp +29 -8
- data/vendor/faiss/faiss/utils/rabitq_simd.h +202 -0
- data/vendor/faiss/faiss/utils/simd_impl/distances_arm_sve.cpp +194 -30
- data/vendor/faiss/faiss/utils/simd_impl/distances_avx2.cpp +0 -1
- data/vendor/faiss/faiss/utils/simd_impl/distances_avx512.cpp +263 -15
- data/vendor/faiss/faiss/utils/simd_impl/distances_rvv.cpp +198 -18
- data/vendor/faiss/faiss/utils/simd_impl/rabitq_avx2.cpp +245 -0
- data/vendor/faiss/faiss/utils/simd_impl/rabitq_avx512.cpp +330 -40
- data/vendor/faiss/faiss/utils/simd_impl/{rabitq_avx512_spr.cpp → rabitq_avx512_vpopcnt.cpp} +112 -23
- data/vendor/faiss/faiss/utils/simd_impl/rabitq_neon.cpp +11 -0
- data/vendor/faiss/faiss/utils/simd_impl/rabitq_rvv.cpp +143 -6
- data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_dispatch.h +2 -7
- data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_kernels.h +6 -1
- data/vendor/faiss/faiss/utils/simd_impl/super_kmeans_kernels_sve.cpp +34 -0
- data/vendor/faiss/faiss/utils/simd_levels.cpp +196 -47
- data/vendor/faiss/faiss/utils/simd_levels.h +33 -8
- data/vendor/faiss/faiss/utils/utils.cpp +9 -27
- metadata +21 -5
- 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::
|
|
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<
|
|
69
|
-
std::vector<float> best_dists;
|
|
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
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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+:
|
|
152
|
-
///
|
|
153
|
-
/// state.assignments and state.best_dists
|
|
154
|
-
/// pruned_at_gemm
|
|
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
|
-
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
210
|
+
std::vector<int64_t>& labels64,
|
|
341
211
|
std::vector<float>& hassign) {
|
|
342
212
|
std::fill(hassign.begin(), hassign.end(), 0.0f);
|
|
343
|
-
assert(!
|
|
213
|
+
assert(!labels64.empty());
|
|
344
214
|
assert(!state.assignments.empty());
|
|
345
215
|
for (int i = 0; i < state.n; ++i) {
|
|
346
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
///
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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
|