#include "topk-radix.hpp"
#include "common.hpp"
#include <algorithm>
static constexpr int SYCL_TOP_K_RADIX_BITS = 8;
static constexpr int SYCL_TOP_K_RADIX_BUCKETS = 1 << SYCL_TOP_K_RADIX_BITS;
static constexpr int SYCL_TOP_K_RADIX_HIST_COPIES = 8;
static constexpr int SYCL_TOP_K_RADIX_HIST_SIZE = SYCL_TOP_K_RADIX_BUCKETS * SYCL_TOP_K_RADIX_HIST_COPIES;
static constexpr int SYCL_TOP_K_RADIX_SLM_WORDS = SYCL_TOP_K_RADIX_HIST_SIZE + 5;
static inline uint32_t top_k_radix_key(float f) {
uint32_t u = sycl::bit_cast<uint32_t>(f);
if (u == 0x80000000u) {
u = 0u;
}
return (u & 0x80000000u) ? ~u : (u | 0x80000000u);
}
static void top_k_radix_select_f32(
const float * src,
int32_t * dst_idx,
const int ncols,
const int k,
uint32_t * slm,
const sycl::nd_item<1> & item_ct1
) {
using local_atomic = sycl::atomic_ref<uint32_t, sycl::memory_order::relaxed,
sycl::memory_scope::work_group,
sycl::access::address_space::local_space>;
const int tid = item_ct1.get_local_id(0);
const int block_size = item_ct1.get_local_range(0);
uint32_t * hist = slm;
uint32_t * s_digit = slm + SYCL_TOP_K_RADIX_HIST_SIZE;
uint32_t * s_bucket = slm + SYCL_TOP_K_RADIX_HIST_SIZE + 1;
uint32_t * s_need = slm + SYCL_TOP_K_RADIX_HIST_SIZE + 2;
uint32_t * s_cnt_gt = slm + SYCL_TOP_K_RADIX_HIST_SIZE + 3;
uint32_t * s_cnt_eq = slm + SYCL_TOP_K_RADIX_HIST_SIZE + 4;
if (tid == 0) {
*s_cnt_gt = 0;
*s_cnt_eq = 0;
}
const int copy = tid & (SYCL_TOP_K_RADIX_HIST_COPIES - 1);
uint32_t prefix = 0; uint32_t mask = 0; uint32_t need = (uint32_t) k;
for (int shift = 32 - SYCL_TOP_K_RADIX_BITS; shift >= 0; shift -= SYCL_TOP_K_RADIX_BITS) {
for (int i = tid; i < SYCL_TOP_K_RADIX_HIST_SIZE; i += block_size) {
hist[i] = 0;
}
item_ct1.barrier(sycl::access::fence_space::local_space);
for (int col = tid; col < ncols; col += block_size) {
const uint32_t key = top_k_radix_key(src[col]);
if ((key & mask) == prefix) {
const uint32_t bucket = (key >> shift) & (SYCL_TOP_K_RADIX_BUCKETS - 1);
local_atomic(hist[bucket * SYCL_TOP_K_RADIX_HIST_COPIES + copy]).fetch_add(1u);
}
}
item_ct1.barrier(sycl::access::fence_space::local_space);
uint32_t cnt = 0;
if (tid < SYCL_TOP_K_RADIX_BUCKETS) {
const uint32_t * h = hist + (SYCL_TOP_K_RADIX_BUCKETS - 1 - tid) * SYCL_TOP_K_RADIX_HIST_COPIES;
for (int c = 0; c < SYCL_TOP_K_RADIX_HIST_COPIES; c++) {
cnt += h[c];
}
}
const uint32_t incl = sycl::inclusive_scan_over_group(item_ct1.get_group(), cnt, sycl::plus<uint32_t>());
if (tid < SYCL_TOP_K_RADIX_BUCKETS && incl >= need && incl - cnt < need) {
*s_digit = (uint32_t) (SYCL_TOP_K_RADIX_BUCKETS - 1 - tid);
*s_bucket = cnt;
*s_need = need - (incl - cnt);
}
item_ct1.barrier(sycl::access::fence_space::local_space);
const uint32_t digit = *s_digit;
const uint32_t bucket_cnt = *s_bucket;
need = *s_need;
prefix |= digit << shift;
mask |= (uint32_t) (SYCL_TOP_K_RADIX_BUCKETS - 1) << shift;
if (bucket_cnt == need) {
break;
}
item_ct1.barrier(sycl::access::fence_space::local_space);
}
item_ct1.barrier(sycl::access::fence_space::local_space);
const uint32_t base_eq = (uint32_t) k - need;
for (int col = tid; col < ncols; col += block_size) {
const uint32_t kp = top_k_radix_key(src[col]) & mask;
if (kp > prefix) {
const uint32_t pos = local_atomic(*s_cnt_gt).fetch_add(1u);
dst_idx[pos] = col;
} else if (kp == prefix) {
const uint32_t pos = local_atomic(*s_cnt_eq).fetch_add(1u);
if (pos < need) {
dst_idx[base_eq + pos] = col;
}
}
}
}
static void top_k_radix_f32_sycl(
ggml_backend_sycl_context & ctx,
const float * src,
int32_t * dst_indices,
const int64_t ncols,
const int64_t nrows,
const int k,
dpct::queue_ptr main_stream
) {
GGML_ASSERT(ncols <= INT32_MAX);
const int block_size = ggml_sycl_info().max_work_group_sizes[ctx.device];
GGML_ASSERT(block_size >= SYCL_TOP_K_RADIX_BUCKETS);
const sycl::range<1> block_dims(block_size);
const sycl::range<1> grid_dims(nrows);
main_stream->submit([&](sycl::handler &cgh) {
sycl::local_accessor<uint32_t, 1> slm(sycl::range<1>(SYCL_TOP_K_RADIX_SLM_WORDS), cgh);
cgh.parallel_for(
sycl::nd_range<1>(grid_dims * block_dims, block_dims),
[=](sycl::nd_item<1> item_ct1) {
const int row = item_ct1.get_group(0);
top_k_radix_select_f32(
src + (int64_t) row * ncols, dst_indices + (int64_t) row * k,
(int) ncols, k,
slm.get_multi_ptr<sycl::access::decorated::no>().get(),
item_ct1);
});
});
}
static constexpr int SYCL_TOP_K_RADIX_ROW_DONE = SYCL_TOP_K_RADIX_BUCKETS + 0;
static constexpr int SYCL_TOP_K_RADIX_ROW_PREFIX = SYCL_TOP_K_RADIX_BUCKETS + 1;
static constexpr int SYCL_TOP_K_RADIX_ROW_MASK = SYCL_TOP_K_RADIX_BUCKETS + 2;
static constexpr int SYCL_TOP_K_RADIX_ROW_NEED = SYCL_TOP_K_RADIX_BUCKETS + 3;
static constexpr int SYCL_TOP_K_RADIX_ROW_CNT_GT = SYCL_TOP_K_RADIX_BUCKETS + 4;
static constexpr int SYCL_TOP_K_RADIX_ROW_CNT_EQ = SYCL_TOP_K_RADIX_BUCKETS + 5;
static constexpr int SYCL_TOP_K_RADIX_ROW_WORDS = SYCL_TOP_K_RADIX_BUCKETS + 6;
static constexpr int SYCL_TOP_K_RADIX_GROUPS_PER_NSM = 2;
static constexpr int SYCL_TOP_K_RADIX_MIN_SPLIT_COLS = 65536;
static constexpr int SYCL_TOP_K_RADIX_MIN_PART_COLS = 4096;
static int top_k_radix_split_groups(const int device, const int64_t ncols, const int64_t nrows) {
const int64_t target = (int64_t) SYCL_TOP_K_RADIX_GROUPS_PER_NSM * ggml_sycl_info().devices[device].nsm;
if (ncols < SYCL_TOP_K_RADIX_MIN_SPLIT_COLS || nrows >= target) {
return 1;
}
const int64_t by_rows = target / nrows; const int64_t by_cols = ncols / SYCL_TOP_K_RADIX_MIN_PART_COLS;
return (int) std::max<int64_t>(1, std::min(by_rows, by_cols));
}
using top_k_radix_gatomic = sycl::atomic_ref<uint32_t, sycl::memory_order::relaxed,
sycl::memory_scope::device,
sycl::access::address_space::global_space>;
static void top_k_radix_split_pass_f32(
const float * src,
uint32_t * state,
const int ncols,
const int k,
const int shift,
const bool first,
const int part,
const int nparts,
uint32_t * slm,
const sycl::nd_item<1> & item_ct1
) {
using local_atomic = sycl::atomic_ref<uint32_t, sycl::memory_order::relaxed,
sycl::memory_scope::work_group,
sycl::access::address_space::local_space>;
const int tid = item_ct1.get_local_id(0);
const int block_size = item_ct1.get_local_range(0);
uint32_t * hist = slm;
uint32_t * s_last = slm + SYCL_TOP_K_RADIX_HIST_SIZE;
uint32_t * s_row = slm + SYCL_TOP_K_RADIX_HIST_SIZE + 1;
if (tid == 0) {
s_row[0] = first ? 0u : state[SYCL_TOP_K_RADIX_ROW_PREFIX];
s_row[1] = first ? 0u : state[SYCL_TOP_K_RADIX_ROW_MASK];
s_row[2] = first ? (uint32_t) k : state[SYCL_TOP_K_RADIX_ROW_NEED];
}
for (int i = tid; i < SYCL_TOP_K_RADIX_HIST_SIZE; i += block_size) {
hist[i] = 0;
}
item_ct1.barrier(sycl::access::fence_space::local_space);
const uint32_t prefix = s_row[0];
const uint32_t mask = s_row[1];
const uint32_t need = s_row[2];
const int copy = tid & (SYCL_TOP_K_RADIX_HIST_COPIES - 1);
const int chunk = (ncols + nparts - 1) / nparts;
const int col0 = part * chunk;
const int col1 = std::min(ncols, col0 + chunk);
for (int col = col0 + tid; col < col1; col += block_size) {
const uint32_t key = top_k_radix_key(src[col]);
if ((key & mask) == prefix) {
const uint32_t bucket = (key >> shift) & (SYCL_TOP_K_RADIX_BUCKETS - 1);
local_atomic(hist[bucket * SYCL_TOP_K_RADIX_HIST_COPIES + copy]).fetch_add(1u);
}
}
item_ct1.barrier(sycl::access::fence_space::local_space);
for (int b = tid; b < SYCL_TOP_K_RADIX_BUCKETS; b += block_size) {
uint32_t sum = 0;
for (int c = 0; c < SYCL_TOP_K_RADIX_HIST_COPIES; c++) {
sum += hist[b * SYCL_TOP_K_RADIX_HIST_COPIES + c];
}
if (sum) {
top_k_radix_gatomic(state[b]).fetch_add(sum);
}
}
item_ct1.barrier(sycl::access::fence_space::global_and_local);
if (tid == 0) {
sycl::atomic_fence(sycl::memory_order::release, sycl::memory_scope::device);
sycl::atomic_ref<uint32_t, sycl::memory_order::acq_rel, sycl::memory_scope::device,
sycl::access::address_space::global_space> done(state[SYCL_TOP_K_RADIX_ROW_DONE]);
*s_last = (done.fetch_add(1u) == (uint32_t) (nparts - 1)) ? 1u : 0u;
}
item_ct1.barrier(sycl::access::fence_space::local_space);
if (*s_last == 0u) {
return;
}
sycl::atomic_fence(sycl::memory_order::acquire, sycl::memory_scope::device);
uint32_t cnt = 0;
if (tid < SYCL_TOP_K_RADIX_BUCKETS) {
cnt = top_k_radix_gatomic(state[SYCL_TOP_K_RADIX_BUCKETS - 1 - tid]).load();
}
const uint32_t incl = sycl::inclusive_scan_over_group(item_ct1.get_group(), cnt, sycl::plus<uint32_t>());
if (tid < SYCL_TOP_K_RADIX_BUCKETS && incl >= need && incl - cnt < need) {
const uint32_t digit = (uint32_t) (SYCL_TOP_K_RADIX_BUCKETS - 1 - tid);
top_k_radix_gatomic(state[SYCL_TOP_K_RADIX_ROW_PREFIX]).store(prefix | (digit << shift));
top_k_radix_gatomic(state[SYCL_TOP_K_RADIX_ROW_MASK]).store(
mask | ((uint32_t) (SYCL_TOP_K_RADIX_BUCKETS - 1) << shift));
top_k_radix_gatomic(state[SYCL_TOP_K_RADIX_ROW_NEED]).store(need - (incl - cnt));
}
item_ct1.barrier(sycl::access::fence_space::local_space);
for (int b = tid; b < SYCL_TOP_K_RADIX_BUCKETS; b += block_size) {
top_k_radix_gatomic(state[b]).store(0u);
}
if (tid == 0) {
top_k_radix_gatomic(state[SYCL_TOP_K_RADIX_ROW_DONE]).store(0u);
}
}
static void top_k_radix_split_emit_f32(
const float * src,
int32_t * dst_idx,
uint32_t * state,
const int ncols,
const int k,
const int part,
const int nparts,
uint32_t * slm,
const sycl::nd_item<1> & item_ct1
) {
using local_atomic = sycl::atomic_ref<uint32_t, sycl::memory_order::relaxed,
sycl::memory_scope::work_group,
sycl::access::address_space::local_space>;
const int tid = item_ct1.get_local_id(0);
const int block_size = item_ct1.get_local_range(0);
uint32_t * s_gt = slm;
uint32_t * s_eq = slm + 1;
uint32_t * s_base_gt = slm + 2;
uint32_t * s_base_eq = slm + 3;
uint32_t * s_row = slm + 4;
if (tid == 0) {
*s_gt = 0;
*s_eq = 0;
s_row[0] = state[SYCL_TOP_K_RADIX_ROW_PREFIX];
s_row[1] = state[SYCL_TOP_K_RADIX_ROW_MASK];
s_row[2] = state[SYCL_TOP_K_RADIX_ROW_NEED];
}
item_ct1.barrier(sycl::access::fence_space::local_space);
const uint32_t prefix = s_row[0];
const uint32_t mask = s_row[1];
const uint32_t need = s_row[2];
const uint32_t base_eq = (uint32_t) k - need;
const int chunk = (ncols + nparts - 1) / nparts;
const int col0 = part * chunk;
const int col1 = std::min(ncols, col0 + chunk);
for (int col = col0 + tid; col < col1; col += block_size) {
const uint32_t kp = top_k_radix_key(src[col]) & mask;
if (kp > prefix) {
local_atomic(*s_gt).fetch_add(1u);
} else if (kp == prefix) {
local_atomic(*s_eq).fetch_add(1u);
}
}
item_ct1.barrier(sycl::access::fence_space::local_space);
if (tid == 0) {
const uint32_t n_gt = *s_gt;
const uint32_t n_eq = *s_eq;
*s_base_gt = n_gt ? top_k_radix_gatomic(state[SYCL_TOP_K_RADIX_ROW_CNT_GT]).fetch_add(n_gt) : 0u;
*s_base_eq = n_eq ? top_k_radix_gatomic(state[SYCL_TOP_K_RADIX_ROW_CNT_EQ]).fetch_add(n_eq) : 0u;
*s_gt = 0;
*s_eq = 0;
}
item_ct1.barrier(sycl::access::fence_space::local_space);
const uint32_t base_gt_g = *s_base_gt;
const uint32_t base_eq_g = *s_base_eq;
for (int col = col0 + tid; col < col1; col += block_size) {
const uint32_t kp = top_k_radix_key(src[col]) & mask;
if (kp > prefix) {
dst_idx[base_gt_g + local_atomic(*s_gt).fetch_add(1u)] = col;
} else if (kp == prefix) {
const uint32_t pos = base_eq_g + local_atomic(*s_eq).fetch_add(1u);
if (pos < need) {
dst_idx[base_eq + pos] = col;
}
}
}
}
static void top_k_radix_split_f32_sycl(
ggml_backend_sycl_context & ctx,
const float * src,
int32_t * dst_indices,
const int64_t ncols,
const int64_t nrows,
const int k,
const int nparts,
dpct::queue_ptr main_stream
) {
GGML_ASSERT(ncols <= INT32_MAX);
GGML_ASSERT(nparts > 1);
const int block_size = ggml_sycl_info().max_work_group_sizes[ctx.device];
GGML_ASSERT(block_size >= SYCL_TOP_K_RADIX_BUCKETS);
const size_t state_words = (size_t) nrows * SYCL_TOP_K_RADIX_ROW_WORDS;
ggml_sycl_pool_alloc<uint32_t> state_alloc(ctx.pool(), state_words);
uint32_t * state = state_alloc.get();
SYCL_CHECK(CHECK_TRY_ERROR(main_stream->memset(state, 0, state_words * sizeof(uint32_t))));
const sycl::range<1> block_dims(block_size);
const sycl::range<1> grid_dims(nrows * nparts);
bool first = true;
for (int shift = 32 - SYCL_TOP_K_RADIX_BITS; shift >= 0; shift -= SYCL_TOP_K_RADIX_BITS) {
const bool is_first = first;
first = false;
main_stream->submit([&](sycl::handler &cgh) {
sycl::local_accessor<uint32_t, 1> slm(sycl::range<1>(SYCL_TOP_K_RADIX_HIST_SIZE + 4), cgh);
cgh.parallel_for(
sycl::nd_range<1>(grid_dims * block_dims, block_dims),
[=](sycl::nd_item<1> item_ct1) {
const int g = item_ct1.get_group(0);
const int row = g / nparts;
const int part = g % nparts;
top_k_radix_split_pass_f32(
src + (int64_t) row * ncols,
state + (int64_t) row * SYCL_TOP_K_RADIX_ROW_WORDS,
(int) ncols, k, shift, is_first, part, nparts,
slm.get_multi_ptr<sycl::access::decorated::no>().get(),
item_ct1);
});
});
}
main_stream->submit([&](sycl::handler &cgh) {
sycl::local_accessor<uint32_t, 1> slm(sycl::range<1>(8), cgh);
cgh.parallel_for(
sycl::nd_range<1>(grid_dims * block_dims, block_dims),
[=](sycl::nd_item<1> item_ct1) {
const int g = item_ct1.get_group(0);
const int row = g / nparts;
const int part = g % nparts;
top_k_radix_split_emit_f32(
src + (int64_t) row * ncols,
dst_indices + (int64_t) row * k,
state + (int64_t) row * SYCL_TOP_K_RADIX_ROW_WORDS,
(int) ncols, k, part, nparts,
slm.get_multi_ptr<sycl::access::decorated::no>().get(),
item_ct1);
});
});
}
void ggml_sycl_top_k_radix(
ggml_backend_sycl_context & ctx,
const float * src,
int32_t * dst_indices,
const int64_t ncols,
const int64_t nrows,
const int k,
dpct::queue_ptr main_stream
) {
const int nparts = top_k_radix_split_groups(ctx.device, ncols, nrows);
if (nparts > 1) {
top_k_radix_split_f32_sycl(ctx, src, dst_indices, ncols, nrows, k, nparts, main_stream);
} else {
top_k_radix_f32_sycl(ctx, src, dst_indices, ncols, nrows, k, main_stream);
}
}