Files
2026-07-13 12:24:33 +08:00

217 lines
8.3 KiB
C++

// SPDX-License-Identifier: Apache-2.0
//
// SYCL/XPU implementation of calculate_cdf, hand-tuned for the Intel Xe
// architecture (PVC / DG2 / BMG / Arc).
//
// Algorithm
// ---------
// For each (layer_id, channel_id) we build a histogram of the uint8
// input values along the `ntokens` axis, prefix-sum it into a CDF,
// normalise to a uint16 range, and add the bin index so values are
// strictly monotonic (see normalize_cdf_value()).
//
// Intel XPU mapping
// -----------------
// - 2-D nd_range: dim 0 = layer, dim 1 = channel (BLOCK_SIZE per WG).
// - Each work-item owns one channel (no atomic contention).
// - Histogram lives in Shared Local Memory (SLM) as a 2-D tile of
// [MAX_BINS_SUPPORTED+1][BLOCK_SIZE] uint16s -- one column per
// channel. All traffic stays local; no cross work-item sync in the
// inner increment loop.
// - sub_group_size locked to 16 (native SIMD on Intel discrete GPUs).
// - BLOCK_SIZE is a template parameter so IGC can fully unroll and
// statically size the SLM allocation.
//
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#include <sycl/sycl.hpp>
#pragma GCC diagnostic pop
#include <torch/all.h>
#include <ATen/ATen.h>
#include <c10/core/DeviceGuard.h>
#include <c10/xpu/XPUStream.h>
#include "cachegen_kernels_sycl.h"
#include <cstdint>
#include <stdexcept>
namespace {
constexpr int MAX_BINS_SUPPORTED = 64;
constexpr int INTEL_SUB_GROUP_SIZE = 16;
// Linear remap into [0, 0xFFFF - max_bins].
inline uint16_t normalize_cdf_value(uint16_t cdf_value, uint16_t total_count,
int max_bins) {
const uint32_t MAX_UINT16_VALUE = 0xFFFFu - static_cast<uint32_t>(max_bins);
return static_cast<uint16_t>(MAX_UINT16_VALUE *
static_cast<uint32_t>(cdf_value) /
static_cast<uint32_t>(total_count));
}
template <int BLOCK_SIZE>
void launch_calculate_cdf(sycl::queue& queue, const uint8_t* input,
int16_t* output, int nlayers, int ntokens,
int nchannels, int max_bins) {
const int channel_blocks = (nchannels + BLOCK_SIZE - 1) / BLOCK_SIZE;
const int lp = max_bins + 1;
sycl::range<2> global_range(static_cast<size_t>(nlayers),
static_cast<size_t>(channel_blocks) * BLOCK_SIZE);
sycl::range<2> local_range(1, BLOCK_SIZE);
queue.submit([&](sycl::handler& cgh) {
// SLM histogram: [MAX_BINS_SUPPORTED + 1][BLOCK_SIZE] uint16.
sycl::local_accessor<uint16_t, 2> hist(
sycl::range<2>(MAX_BINS_SUPPORTED + 1, BLOCK_SIZE), cgh);
cgh.parallel_for(
sycl::nd_range<2>(global_range, local_range),
[=](sycl::nd_item<2> item)
[[sycl::reqd_sub_group_size(INTEL_SUB_GROUP_SIZE)]] {
const int layer_id = static_cast<int>(item.get_group(0));
const int tx = static_cast<int>(item.get_local_id(1));
const int channel_block = static_cast<int>(item.get_group(1));
const int start_channel = channel_block * BLOCK_SIZE;
const int channel_id = start_channel + tx;
const bool in_range = (channel_id < nchannels);
// Zero this column of the SLM histogram. No barrier
// needed: every work-item only ever touches column `tx`,
// so there is zero cross-thread sharing in SLM. Profiler
// (unitrace --stall-sampling) confirmed that the previous
// barriers caused ~67% SyncStall — removing them was the
// single largest win on this kernel.
for (int i = 0; i <= max_bins; ++i) {
hist[i][tx] = 0;
}
if (in_range) {
// Histogram pass: counts at hist[v+1][tx]. hist[0][tx] stays
// 0.
for (int i = 0; i < ntokens; ++i) {
const uint8_t v =
input[(static_cast<int64_t>(layer_id) * ntokens + i) *
nchannels +
channel_id];
hist[v + 1][tx] += 1;
}
// Inclusive prefix sum so hist[i] = sum of counts of
// bins [0..i-1].
uint16_t total = 0;
for (int i = 0; i < max_bins; ++i) {
uint16_t value = hist[i + 1][tx];
hist[i + 1][tx] = total + value;
total += value;
}
// Normalise + add bin index for strict monotonicity.
if (total > 0) {
for (int i = 0; i <= max_bins; ++i) {
hist[i][tx] =
normalize_cdf_value(hist[i][tx], total, max_bins) +
static_cast<uint16_t>(i);
}
} else {
for (int i = 0; i <= max_bins; ++i) {
hist[i][tx] = static_cast<uint16_t>(i);
}
}
}
if (in_range) {
const int64_t base =
(static_cast<int64_t>(layer_id) * nchannels + channel_id) *
lp;
for (int i = 0; i <= max_bins; ++i) {
output[base + i] = static_cast<int16_t>(hist[i][tx]);
}
}
});
});
}
// Largest power-of-two divisor of nchannels, capped at 128.
int get_block_size_xpu(int nchannels) {
static const int kCandidates[] = {128, 64, 32, 16, 8, 4, 2, 1};
for (int bs : kCandidates) {
if (nchannels % bs == 0) return bs;
}
return 1;
}
} // namespace
at::Tensor calculate_cdf_xpu(const at::Tensor& input, int64_t max_bins) {
if (!input.device().is_xpu()) {
throw std::runtime_error("calculate_cdf_xpu: input must be an XPU tensor");
}
if (max_bins >= MAX_BINS_SUPPORTED) {
throw std::runtime_error("calculate_cdf_xpu: max_bins must be < 64");
}
if (input.dim() != 3) {
throw std::runtime_error(
"calculate_cdf_xpu: input must be 3-D [nlayers, ntokens, nchannels]");
}
const auto sizes = input.sizes();
const int nlayers = static_cast<int>(sizes[0]);
const int ntokens = static_cast<int>(sizes[1]);
const int nchannels = static_cast<int>(sizes[2]);
auto contiguous = input.is_contiguous() ? input : input.contiguous();
auto output = torch::zeros({nlayers, nchannels, max_bins + 1},
input.options().dtype(at::kShort));
sycl::queue& queue =
c10::xpu::getCurrentXPUStream(input.device().index()).queue();
// Accept both Byte (uint8) and Char (int8) inputs: values are bin
// indices in [0, max_bins), so byte-pattern is identical either way.
const uint8_t* in_ptr =
reinterpret_cast<const uint8_t*>(contiguous.data_ptr());
int16_t* out_ptr = output.data_ptr<int16_t>();
const int block_size = get_block_size_xpu(nchannels);
switch (block_size) {
case 1:
launch_calculate_cdf<1>(queue, in_ptr, out_ptr, nlayers, ntokens,
nchannels, static_cast<int>(max_bins));
break;
case 2:
launch_calculate_cdf<2>(queue, in_ptr, out_ptr, nlayers, ntokens,
nchannels, static_cast<int>(max_bins));
break;
case 4:
launch_calculate_cdf<4>(queue, in_ptr, out_ptr, nlayers, ntokens,
nchannels, static_cast<int>(max_bins));
break;
case 8:
launch_calculate_cdf<8>(queue, in_ptr, out_ptr, nlayers, ntokens,
nchannels, static_cast<int>(max_bins));
break;
case 16:
launch_calculate_cdf<16>(queue, in_ptr, out_ptr, nlayers, ntokens,
nchannels, static_cast<int>(max_bins));
break;
case 32:
launch_calculate_cdf<32>(queue, in_ptr, out_ptr, nlayers, ntokens,
nchannels, static_cast<int>(max_bins));
break;
case 64:
launch_calculate_cdf<64>(queue, in_ptr, out_ptr, nlayers, ntokens,
nchannels, static_cast<int>(max_bins));
break;
case 128:
launch_calculate_cdf<128>(queue, in_ptr, out_ptr, nlayers, ntokens,
nchannels, static_cast<int>(max_bins));
break;
default:
throw std::runtime_error("calculate_cdf_xpu: unsupported block size");
}
return output;
}