75 static_assert(std::is_same_v<T, float> || std::is_same_v<T, double>,
76 "AfkMc2Seeder<T> requires T to be float or double");
78#ifdef CLUSTERING_KMEANS_AFKMC2_K_FLOOR
86 static constexpr std::size_t
kFloor = CLUSTERING_KMEANS_AFKMC2_K_FLOOR;
89 static constexpr std::size_t
kFloor = 100;
92#ifdef CLUSTERING_KMEANS_AFKMC2_CHAIN_LENGTH
100 static constexpr std::size_t
chainLengthDefault = CLUSTERING_KMEANS_AFKMC2_CHAIN_LENGTH;
107 : m_q({0}), m_aliasProb({0}), m_aliasIdx({0}), m_aliasSmall({0}), m_aliasLarge({0}),
108 m_yIdxBatch({0}), m_uBatch({0}), m_yQBatch({0}), m_yDistBatch({0}) {}
129 static constexpr std::size_t kCdfBlockElems = 8;
133 const std::size_t n = X.
dim(0);
134 const std::size_t d = X.
dim(1);
143 ensureShape(n, k, m);
148 const T *xData = X.
data();
149 T *centroidsData = outCentroids.
data();
150 T *qData = m_q.
data();
154 std::memcpy(centroidsData, xData + (first * d), d *
sizeof(T));
164 const T *firstRow = centroidsData;
165 const std::size_t qOps = n * d;
167 qPrecomputeParallel(xData, firstRow, n, d, qData, pool);
169 sqDistancesAosBlock<T>(firstRow, xData, n, d, qData);
174 sumD2 = sumReduceParallel(qData, n, pool);
176 sumD2 = sumReduceAvx2(qData, n);
179 const T invN = T{1} /
static_cast<T
>(n);
181 const T invSum = T{1} / sumD2;
182 affineInPlaceAvx2(qData, n, T{0.5} * invSum, T{0.5} * invN);
186 fillAvx2(qData, n, invN);
195 const std::size_t chainSamples = (k - 1) * (m + 1);
196 const bool useAlias = chainSamples * 5 > n;
197 const std::size_t qBlocks = (n + kCdfBlockElems - 1) / kCdfBlockElems;
199 buildAliasTable(qData, n);
201 T *blockPrefix = m_aliasProb.data();
202 bankWeightBlockSums(qData, n, kCdfBlockElems, blockPrefix);
204 for (std::size_t b = 0; b < qBlocks; ++b) {
205 running += blockPrefix[b];
206 blockPrefix[b] = running;
214 std::size_t *yIdxBatch = m_yIdxBatch.data();
215 T *uBatch = m_uBatch.data();
216 T *yQBatch = m_yQBatch.data();
217 T *yDistBatch = m_yDistBatch.data();
219 for (std::size_t c = 1; c < k; ++c) {
222 for (std::size_t t = 0; t <= m; ++t) {
223 yIdxBatch[t] = sampleFromAlias(rng, n);
226 sampleBatchFromBlocks(rng, qData, m_aliasProb.data(), n, qBlocks, m + 1, yIdxBatch);
228 for (std::size_t t = 0; t < m; ++t) {
235 minDistBatchedFromIdx(xData, d, yIdxBatch, m + 1, centroidsData, c, yDistBatch);
238 for (std::size_t t = 0; t <= m; ++t) {
239 yQBatch[t] = qData[yIdxBatch[t]];
246 std::size_t xIdx = yIdxBatch[0];
247 T xDist = yDistBatch[0];
249 for (std::size_t step = 0; step < m; ++step) {
250 const T yDist = yDistBatch[step + 1];
251 const T yQ = yQBatch[step + 1];
252 const T u = uBatch[step];
254 const T numer = yDist * xQ;
255 const T denom = xDist * yQ;
256 const bool accept = (denom <= T{0}) || ((u * denom) < numer);
259 xIdx = yIdxBatch[step + 1];
265 std::memcpy(centroidsData + (c * d), xData + (xIdx * d), d *
sizeof(T));
269 void ensureShape(std::size_t n, std::size_t k, std::size_t m) {
270 if (m_q.dim(0) != n) {
271 m_q = NDArray<T, 1>({n});
273 if (m_aliasProb.dim(0) != n) {
274 m_aliasProb = NDArray<T, 1>({n});
276 if (m_aliasIdx.dim(0) != n) {
277 m_aliasIdx = NDArray<std::size_t, 1>({n});
279 if (m_aliasSmall.dim(0) != n) {
280 m_aliasSmall = NDArray<std::size_t, 1>({n});
282 if (m_aliasLarge.dim(0) != n) {
283 m_aliasLarge = NDArray<std::size_t, 1>({n});
285 if (m_yIdxBatch.dim(0) != m + 1) {
286 m_yIdxBatch = NDArray<std::size_t, 1>({m + 1});
288 if (m_uBatch.dim(0) != m) {
289 m_uBatch = NDArray<T, 1>({m});
291 if (m_yQBatch.dim(0) != m + 1) {
292 m_yQBatch = NDArray<T, 1>({m + 1});
294 if (m_yDistBatch.dim(0) != m + 1) {
295 m_yDistBatch = NDArray<T, 1>({m + 1});
304 void buildAliasTable(
const T *qSrc, std::size_t n)
noexcept {
305 T *prob = m_aliasProb.data();
306 std::size_t *alias = m_aliasIdx.data();
307 std::size_t *smallStack = m_aliasSmall.data();
308 std::size_t *largeStack = m_aliasLarge.data();
312 const T total = sumReduceAvx2(qSrc, n);
314 const T scale =
static_cast<T
>(n) / total;
315 scaleAvx2(qSrc, n, scale, prob);
317 std::size_t numSmall = 0;
318 std::size_t numLarge = 0;
319 for (std::size_t i = 0; i < n; ++i) {
320 if (prob[i] < T{1}) {
321 smallStack[numSmall++] = i;
323 largeStack[numLarge++] = i;
327 while (numSmall > 0 && numLarge > 0) {
328 const std::size_t s = smallStack[--numSmall];
329 const std::size_t l = largeStack[--numLarge];
333 const T residual = prob[l] - (T{1} - prob[s]);
335 if (residual < T{1}) {
336 smallStack[numSmall++] = l;
338 largeStack[numLarge++] = l;
342 while (numLarge > 0) {
343 const std::size_t l = largeStack[--numLarge];
347 while (numSmall > 0) {
348 const std::size_t s = smallStack[--numSmall];
355 [[gnu::always_inline]] std::size_t sampleFromAlias(math::pcg64 &rng, std::size_t n)
noexcept {
357 const auto i =
static_cast<std::size_t
>(r %
static_cast<std::uint64_t
>(n));
359 return (u < m_aliasProb.data()[i]) ? i : m_aliasIdx.data()[i];
370 void sampleBatchFromBlocks(math::pcg64 &rng,
const T *q,
const T *blockPrefix, std::size_t n,
371 std::size_t qBlocks, std::size_t count, std::size_t *outIdx)
noexcept {
372 const T total = blockPrefix[qBlocks - 1];
373 T *u = m_yDistBatch.data();
374 for (std::size_t t = 0; t < count; ++t) {
377 for (std::size_t t = 0; t < count; ++t) {
382 std::size_t len = qBlocks + 1;
384 const std::size_t half = len / 2;
385 for (std::size_t t = 0; t < count; ++t) {
386 outIdx[t] += (blockPrefix[outIdx[t] + half - 1] <= u[t]) ? half : 0;
390 for (std::size_t t = 0; t < count; ++t) {
391 std::size_t b = outIdx[t];
395 const T rem = u[t] - ((b > 0) ? blockPrefix[b - 1] : T{0});
396 const std::size_t lo = b * kCdfBlockElems;
397 if (n - lo >= kCdfBlockElems) {
398#ifdef CLUSTERING_USE_AVX2
399 if constexpr (std::is_same_v<T, float>) {
400 outIdx[t] = lo + inverseCdfPickInBlock8F32(q + lo, rem);
404 outIdx[t] = inverseCdfPickInRange(q, lo, lo + kCdfBlockElems, rem);
406 outIdx[t] = inverseCdfPickInRange(q, lo, n, rem);
413 [[gnu::always_inline]]
void minDistBatchedFromIdx(
const T *xData, std::size_t d,
414 const std::size_t *yIdx, std::size_t qCount,
415 const T *centroids, std::size_t cCount,
417#ifdef CLUSTERING_USE_AVX2
418 if constexpr (std::is_same_v<T, float>) {
419 minDistBatchedAvx2F32(xData, d, yIdx, qCount, centroids, cCount, out);
423 for (std::size_t t = 0; t < qCount; ++t) {
424 const T *qrow = xData + (yIdx[t] * d);
425 T best = std::numeric_limits<T>::infinity();
427 alignas(16) std::array<T, 4> blockOut{};
429 for (; j + 4 <= cCount; j += 4) {
430 sqDistancesAosBlock<T>(qrow, centroids + (j * d), 4, d, blockOut.data());
431 for (std::size_t r = 0; r < 4; ++r) {
432 if (blockOut[r] < best) {
437 for (; j < cCount; ++j) {
438 const T dsq = sqEuclideanRowPtr(qrow, centroids + (j * d), d);
450 void qPrecomputeParallel(
const T *xData,
const T *firstRow, std::size_t n, std::size_t d,
451 T *qData, math::Pool pool)
noexcept {
453 [&](std::size_t startIdx, std::size_t endIdx)
noexcept {
454 const std::size_t cnt = endIdx - startIdx;
455 sqDistancesAosBlock<T>(firstRow, xData + (startIdx * d), cnt, d,
461 T sumReduceParallel(
const T *p, std::size_t n, math::Pool pool)
noexcept {
463 std::array<T, 64> partials{};
466 std::size_t{0}, n, workers,
467 [&, p](std::size_t startIdx, std::size_t endIdx, std::size_t slot)
noexcept {
468 partials[slot] = sumReduceAvx2(p + startIdx, endIdx - startIdx);
471 for (std::size_t w = 0; w < workers; ++w) {
478 NDArray<T, 1> m_aliasProb;
479 NDArray<std::size_t, 1> m_aliasIdx;
480 NDArray<std::size_t, 1> m_aliasSmall;
481 NDArray<std::size_t, 1> m_aliasLarge;
482 NDArray<std::size_t, 1> m_yIdxBatch;
483 NDArray<T, 1> m_uBatch;
484 NDArray<T, 1> m_yQBatch;
485 NDArray<T, 1> m_yDistBatch;