94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
471 lines
20 KiB
Plaintext
471 lines
20 KiB
Plaintext
#include <sgl_kernel/tensor.h>
|
|
#include <sgl_kernel/utils.h>
|
|
|
|
#include <sgl_kernel/runtime.cuh>
|
|
#include <sgl_kernel/tile.cuh>
|
|
#include <sgl_kernel/type.cuh>
|
|
#include <sgl_kernel/utils.cuh>
|
|
#include <sgl_kernel/vec.cuh>
|
|
|
|
#include <dlpack/dlpack.h>
|
|
|
|
#include <numeric>
|
|
|
|
namespace {
|
|
|
|
struct FusedRopeParams {
|
|
void* __restrict__ q_ptr;
|
|
void* __restrict__ k_ptr; // NOTE: this k is pre-offset in host code to reduce computation in kernel
|
|
const void* __restrict__ cos_sin_cache_ptr;
|
|
const void* __restrict__ positions;
|
|
int64_t q_stride_bytes;
|
|
int64_t k_stride_bytes;
|
|
int64_t head_stride_bytes;
|
|
uint32_t num_qo_heads;
|
|
uint32_t num_kv_heads;
|
|
uint32_t num_tokens;
|
|
};
|
|
|
|
struct FusedRopeStoreParams {
|
|
FusedRopeParams base_params;
|
|
void* v_ptr;
|
|
void* __restrict__ k_cache;
|
|
void* __restrict__ v_cache;
|
|
const void* __restrict__ out_loc;
|
|
int64_t v_stride_bytes;
|
|
int64_t cache_stride_bytes;
|
|
};
|
|
|
|
constexpr uint32_t kBlockSize = 128;
|
|
|
|
[[maybe_unused]]
|
|
constexpr auto next_pow2(uint32_t target, uint32_t factor = 1) {
|
|
uint32_t power = 1;
|
|
while (power * factor < target)
|
|
power *= 2;
|
|
return power;
|
|
}
|
|
|
|
template <bool kIsNeox, int64_t kRopeDim, bool kUsePDL, typename DType, typename IdType, uint32_t kWorkThreads>
|
|
__global__ void fused_rope_kernel(const __grid_constant__ FusedRopeParams params) {
|
|
using namespace device;
|
|
|
|
constexpr int64_t kCosSinStrideBytes = kRopeDim * sizeof(float);
|
|
constexpr int64_t kVecSize = next_pow2(kRopeDim, (2 * kWorkThreads * (1 + kIsNeox)));
|
|
using DType2 = packed_t<DType>;
|
|
using InputStorage = AlignedVector<DType2, kVecSize>;
|
|
constexpr int64_t kDimPerThread = kVecSize * 2 * (1 + kIsNeox);
|
|
constexpr uint32_t kLaneCount = kRopeDim / kDimPerThread;
|
|
static_assert(kRopeDim % kDimPerThread == 0 && kLaneCount <= kWorkThreads);
|
|
|
|
const auto &[
|
|
q, k, cos_sin_cache_ptr, positions, // pointers
|
|
q_stride_bytes, k_stride_bytes, head_stride_bytes, // strides
|
|
num_qo_heads, num_kv_heads, num_tokens // dimensions
|
|
] = params;
|
|
|
|
const auto num_blks = gridDim.x;
|
|
constexpr auto kWorkersPerBlock = kBlockSize / kWorkThreads;
|
|
const auto num_workers = num_blks * kWorkersPerBlock;
|
|
const auto num_q_and_k_heads = num_qo_heads + num_kv_heads;
|
|
const auto num_works = num_q_and_k_heads * num_tokens;
|
|
const auto start_worker_id = (blockIdx.x * kBlockSize + threadIdx.x) / kWorkThreads;
|
|
const auto cos_cache_ptr = cos_sin_cache_ptr;
|
|
const auto sin_cache_ptr = pointer::offset(cos_sin_cache_ptr, kCosSinStrideBytes / 2);
|
|
|
|
uint32_t lane_id = threadIdx.x % kWorkThreads;
|
|
if constexpr (kLaneCount < kWorkThreads) {
|
|
if (lane_id >= kLaneCount) return;
|
|
}
|
|
|
|
PDLWaitPrimary<kUsePDL>();
|
|
|
|
for (auto idx = start_worker_id; idx < num_works; idx += num_workers) {
|
|
const int64_t token_id = idx / num_q_and_k_heads;
|
|
const int64_t head_id = idx % num_q_and_k_heads;
|
|
const auto pos = static_cast<const IdType*>(positions)[token_id];
|
|
const auto load_q = head_id < num_qo_heads;
|
|
const auto input_ = load_q ? pointer::offset(q, token_id * q_stride_bytes) //
|
|
: pointer::offset(k, token_id * k_stride_bytes);
|
|
const auto input = pointer::offset(input_, head_id * head_stride_bytes);
|
|
const auto cos_ptr = pointer::offset(cos_cache_ptr, pos * kCosSinStrideBytes);
|
|
const auto sin_ptr = pointer::offset(sin_cache_ptr, pos * kCosSinStrideBytes);
|
|
if constexpr (kIsNeox) {
|
|
using CacheStorage = AlignedVector<fp32x2_t, kVecSize>;
|
|
const auto input_x = input;
|
|
const auto input_y = pointer::offset(input, (kRopeDim / 2) * sizeof(DType));
|
|
auto input_vec_x = load_as<InputStorage>(input_x, lane_id);
|
|
auto input_vec_y = load_as<InputStorage>(input_y, lane_id);
|
|
const auto cos_pair = load_as<CacheStorage>(cos_ptr, lane_id);
|
|
const auto sin_pair = load_as<CacheStorage>(sin_ptr, lane_id);
|
|
#pragma unroll
|
|
for (int64_t j = 0; j < kVecSize; ++j) {
|
|
const auto [x0, x1] = cast<fp32x2_t>(input_vec_x[j]);
|
|
const auto [y0, y1] = cast<fp32x2_t>(input_vec_y[j]);
|
|
const auto [cos_0, cos_1] = cos_pair[j];
|
|
const auto [sin_0, sin_1] = sin_pair[j];
|
|
const auto out_x0 = x0 * cos_0 - y0 * sin_0;
|
|
const auto out_y0 = x0 * sin_0 + y0 * cos_0;
|
|
const auto out_x1 = x1 * cos_1 - y1 * sin_1;
|
|
const auto out_y1 = x1 * sin_1 + y1 * cos_1;
|
|
input_vec_x[j] = cast<DType2, fp32x2_t>({out_x0, out_x1});
|
|
input_vec_y[j] = cast<DType2, fp32x2_t>({out_y0, out_y1});
|
|
}
|
|
store_as<InputStorage>(input_x, input_vec_x, lane_id);
|
|
store_as<InputStorage>(input_y, input_vec_y, lane_id);
|
|
} else {
|
|
using CacheStorage = AlignedVector<float, kVecSize>;
|
|
auto input_vec = load_as<InputStorage>(input, lane_id);
|
|
const auto cos_vec = load_as<CacheStorage>(cos_ptr, lane_id);
|
|
const auto sin_vec = load_as<CacheStorage>(sin_ptr, lane_id);
|
|
#pragma unroll
|
|
for (int64_t j = 0; j < kVecSize; ++j) {
|
|
const auto [x, y] = cast<fp32x2_t>(input_vec[j]);
|
|
const auto cos = cos_vec[j];
|
|
const auto sin = sin_vec[j];
|
|
const auto out_x = x * cos - y * sin;
|
|
const auto out_y = x * sin + y * cos;
|
|
input_vec[j] = cast<DType2, fp32x2_t>({out_x, out_y});
|
|
}
|
|
store_as<InputStorage>(input, input_vec, lane_id);
|
|
}
|
|
}
|
|
|
|
PDLTriggerSecondary<kUsePDL>();
|
|
}
|
|
|
|
template <bool kIsNeox, int64_t kRopeDim, bool kUsePDL, typename DType, typename IdType, uint32_t kWorkThreads>
|
|
__global__ void fused_rope_store_kernel(const __grid_constant__ FusedRopeStoreParams params) {
|
|
using namespace device;
|
|
|
|
constexpr int64_t kCosSinStrideBytes = kRopeDim * sizeof(float);
|
|
constexpr int64_t kVecSize = kRopeDim / (2 * kWorkThreads * (1 + kIsNeox));
|
|
using DType2 = packed_t<DType>;
|
|
using InputStorage = AlignedVector<DType2, kVecSize>;
|
|
constexpr int64_t kDimPerThread = kVecSize * 2 * (1 + kIsNeox);
|
|
static_assert(kRopeDim == kDimPerThread * kWorkThreads);
|
|
|
|
const auto& [base_params, v_ptr, k_cache, v_cache, out_loc, v_stride_bytes, cache_stride_bytes] = params;
|
|
const auto &[
|
|
q, k, cos_sin_cache_ptr, positions, // pointers
|
|
q_stride_bytes, k_stride_bytes, head_stride_bytes, // strides
|
|
num_qo_heads, num_kv_heads, num_tokens // dimensions
|
|
] = base_params;
|
|
|
|
const auto num_blks = gridDim.x;
|
|
constexpr auto kWorkersPerBlock = kBlockSize / kWorkThreads;
|
|
const auto num_workers = num_blks * kWorkersPerBlock;
|
|
const auto num_q_and_k_heads = num_qo_heads + num_kv_heads;
|
|
const auto num_works = num_q_and_k_heads * num_tokens;
|
|
const auto num_extra_works = num_kv_heads * num_tokens; // rope works + v store works
|
|
const auto start_worker_id = (blockIdx.x * kBlockSize + threadIdx.x) / kWorkThreads;
|
|
const auto lane_id = threadIdx.x % kWorkThreads;
|
|
const auto cos_cache_ptr = cos_sin_cache_ptr;
|
|
const auto sin_cache_ptr = pointer::offset(cos_sin_cache_ptr, kCosSinStrideBytes / 2);
|
|
|
|
auto idx = start_worker_id;
|
|
|
|
PDLWaitPrimary<kUsePDL>();
|
|
// in this case, head_dim = rope_dim must be true
|
|
__builtin_assume(head_stride_bytes == kRopeDim * sizeof(DType));
|
|
|
|
for (; idx < num_works; idx += num_workers) {
|
|
const int64_t token_id = idx / num_q_and_k_heads;
|
|
const int64_t head_id = idx % num_q_and_k_heads;
|
|
const auto pos = static_cast<const IdType*>(positions)[token_id];
|
|
const auto loc = static_cast<const IdType*>(out_loc)[token_id];
|
|
const auto load_q = head_id < num_qo_heads;
|
|
const auto input_ = load_q ? pointer::offset(q, token_id * q_stride_bytes) //
|
|
: pointer::offset(k, token_id * k_stride_bytes);
|
|
const auto input = pointer::offset(input_, head_id * head_stride_bytes);
|
|
const auto cos_ptr = pointer::offset(cos_cache_ptr, pos * kCosSinStrideBytes);
|
|
const auto sin_ptr = pointer::offset(sin_cache_ptr, pos * kCosSinStrideBytes);
|
|
if constexpr (kIsNeox) {
|
|
using CacheStorage = AlignedVector<fp32x2_t, kVecSize>;
|
|
const auto input_x = input;
|
|
const auto input_y = pointer::offset(input, (kRopeDim / 2) * sizeof(DType));
|
|
auto input_vec_x = load_as<InputStorage>(input_x, lane_id);
|
|
auto input_vec_y = load_as<InputStorage>(input_y, lane_id);
|
|
const auto cos_pair = load_as<CacheStorage>(cos_ptr, lane_id);
|
|
const auto sin_pair = load_as<CacheStorage>(sin_ptr, lane_id);
|
|
#pragma unroll
|
|
for (int64_t j = 0; j < kVecSize; ++j) {
|
|
const auto [x0, x1] = cast<fp32x2_t>(input_vec_x[j]);
|
|
const auto [y0, y1] = cast<fp32x2_t>(input_vec_y[j]);
|
|
const auto [cos_0, cos_1] = cos_pair[j];
|
|
const auto [sin_0, sin_1] = sin_pair[j];
|
|
const auto out_x0 = x0 * cos_0 - y0 * sin_0;
|
|
const auto out_y0 = x0 * sin_0 + y0 * cos_0;
|
|
const auto out_x1 = x1 * cos_1 - y1 * sin_1;
|
|
const auto out_y1 = x1 * sin_1 + y1 * cos_1;
|
|
input_vec_x[j] = cast<DType2, fp32x2_t>({out_x0, out_x1});
|
|
input_vec_y[j] = cast<DType2, fp32x2_t>({out_y0, out_y1});
|
|
}
|
|
store_as<InputStorage>(input, input_vec_x, lane_id);
|
|
const auto input_y_out = pointer::offset(input, (kRopeDim / 2) * sizeof(DType));
|
|
store_as<InputStorage>(input_y_out, input_vec_y, lane_id);
|
|
if (!load_q) {
|
|
const auto k_out = pointer::offset(k_cache, loc * cache_stride_bytes, head_id * head_stride_bytes);
|
|
store_as<InputStorage>(k_out, input_vec_x, lane_id);
|
|
const auto k_out_y = pointer::offset(k_out, (kRopeDim / 2) * sizeof(DType));
|
|
store_as<InputStorage>(k_out_y, input_vec_y, lane_id);
|
|
}
|
|
} else {
|
|
using CacheStorage = AlignedVector<float, kVecSize>;
|
|
auto input_vec = load_as<InputStorage>(input, lane_id);
|
|
const auto cos_vec = load_as<CacheStorage>(cos_ptr, lane_id);
|
|
const auto sin_vec = load_as<CacheStorage>(sin_ptr, lane_id);
|
|
#pragma unroll
|
|
for (int64_t j = 0; j < kVecSize; ++j) {
|
|
const auto [x, y] = cast<fp32x2_t>(input_vec[j]);
|
|
const auto cos = cos_vec[j];
|
|
const auto sin = sin_vec[j];
|
|
const auto out_x = x * cos - y * sin;
|
|
const auto out_y = x * sin + y * cos;
|
|
input_vec[j] = cast<DType2, fp32x2_t>({out_x, out_y});
|
|
}
|
|
store_as<InputStorage>(input, input_vec, lane_id);
|
|
if (!load_q) {
|
|
const auto k_out = pointer::offset(k_cache, loc * cache_stride_bytes, head_id * head_stride_bytes);
|
|
store_as<InputStorage>(k_out, input_vec, lane_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
__syncwarp(); // to avoid warp divergence
|
|
idx -= num_works;
|
|
for (; idx < num_extra_works; idx += num_workers) {
|
|
using VStorage = AlignedVector<DType, kRopeDim / kWorkThreads>;
|
|
const int64_t token_id = idx / num_kv_heads;
|
|
const int64_t head_id = idx % num_kv_heads;
|
|
const auto loc = static_cast<const IdType*>(out_loc)[token_id];
|
|
const auto input = pointer::offset(v_ptr, token_id * v_stride_bytes, head_id * head_stride_bytes);
|
|
const auto input_vec = load_as<VStorage>(input, lane_id);
|
|
const auto output = pointer::offset(v_cache, loc * cache_stride_bytes, head_id * head_stride_bytes);
|
|
store_as<VStorage>(output, input_vec, lane_id);
|
|
}
|
|
PDLTriggerSecondary<kUsePDL>();
|
|
}
|
|
|
|
template <bool kIsNeox, int64_t kRopeDim, bool kUsePDL, typename DType>
|
|
struct FusedRopeKernel {
|
|
static constexpr uint32_t kDimPerThread = std::gcd(16 / sizeof(DType), kRopeDim);
|
|
static constexpr uint32_t kWorkThreads = next_pow2(kRopeDim, kDimPerThread);
|
|
static constexpr bool kSupportFused = kWorkThreads * kDimPerThread == kRopeDim;
|
|
static_assert(kRopeDim % kDimPerThread == 0);
|
|
static_assert(kBlockSize % kWorkThreads == 0);
|
|
|
|
template <typename IdType>
|
|
static constexpr auto _kernel_0 = fused_rope_kernel<kIsNeox, kRopeDim, kUsePDL, DType, IdType, kWorkThreads>;
|
|
template <typename IdType>
|
|
static constexpr auto _kernel_1 = fused_rope_store_kernel<kIsNeox, kRopeDim, kUsePDL, DType, IdType, kWorkThreads>;
|
|
|
|
static auto get_num_sm(DLDevice device) {
|
|
static const auto kNumSM = host::runtime::get_sm_count(device.device_id);
|
|
return kNumSM;
|
|
}
|
|
|
|
static void
|
|
run(const tvm::ffi::TensorView q,
|
|
const tvm::ffi::TensorView k,
|
|
const tvm::ffi::TensorView cos_sin_cache,
|
|
const tvm::ffi::TensorView positions) {
|
|
using namespace host;
|
|
auto N = SymbolicSize{"num_tokens"};
|
|
auto Q = SymbolicSize{"num_qo_heads"};
|
|
auto K = SymbolicSize{"num_kv_heads"};
|
|
auto D = SymbolicSize{"rope_dim"};
|
|
auto Dq = SymbolicSize{"q_stride"};
|
|
auto Dk = SymbolicSize{"k_stride"};
|
|
auto Dd = SymbolicSize{"head_stride"};
|
|
auto device = SymbolicDevice{};
|
|
auto id_type = SymbolicDType{};
|
|
D.set_value(kRopeDim);
|
|
device.set_options<kDLCUDA>();
|
|
TensorMatcher({N, Q, D}) // q input
|
|
.with_strides({Dq, Dd, 1})
|
|
.with_dtype<DType>()
|
|
.with_device(device)
|
|
.verify(q);
|
|
TensorMatcher({N, K, D}) // k input
|
|
.with_strides({Dk, Dd, 1})
|
|
.with_dtype<DType>()
|
|
.with_device(device)
|
|
.verify(k);
|
|
TensorMatcher({-1, D}) // cos_sin_cache
|
|
.with_dtype<float>()
|
|
.with_device(device)
|
|
.verify(cos_sin_cache);
|
|
TensorMatcher({N}) // positions
|
|
.with_dtype<int32_t, int64_t>(id_type)
|
|
.with_device(device)
|
|
.verify(positions);
|
|
|
|
const auto num_tokens = static_cast<uint32_t>(N.unwrap());
|
|
const auto num_qo_heads = static_cast<uint32_t>(Q.unwrap());
|
|
const auto num_kv_heads = static_cast<uint32_t>(K.unwrap());
|
|
const auto q_stride_bytes = static_cast<int64_t>(Dq.unwrap() * sizeof(DType));
|
|
const auto k_stride_bytes = static_cast<int64_t>(Dk.unwrap() * sizeof(DType));
|
|
const auto head_stride_bytes = static_cast<int64_t>(Dd.unwrap() * sizeof(DType));
|
|
|
|
// NOTE: we offset the k here to reduce computation cost in the kernel
|
|
const int64_t k_offset = static_cast<int64_t>(num_qo_heads) * head_stride_bytes;
|
|
const auto params = FusedRopeParams{
|
|
.q_ptr = q.data_ptr(),
|
|
.k_ptr = pointer::offset(k.data_ptr(), -k_offset),
|
|
.cos_sin_cache_ptr = cos_sin_cache.data_ptr(),
|
|
.positions = positions.data_ptr(),
|
|
.q_stride_bytes = q_stride_bytes,
|
|
.k_stride_bytes = k_stride_bytes,
|
|
.head_stride_bytes = head_stride_bytes,
|
|
.num_qo_heads = num_qo_heads,
|
|
.num_kv_heads = num_kv_heads,
|
|
.num_tokens = num_tokens,
|
|
};
|
|
|
|
const auto is_int32 = id_type.is_type<int32_t>();
|
|
const auto kernel = is_int32 ? _kernel_0<int32_t> : _kernel_0<int64_t>;
|
|
const uint32_t kNumSM = get_num_sm(device.unwrap());
|
|
static const uint32_t kOccupancyTable[2] = {
|
|
runtime::get_blocks_per_sm(_kernel_0<int32_t>, kBlockSize),
|
|
runtime::get_blocks_per_sm(_kernel_0<int64_t>, kBlockSize),
|
|
};
|
|
const auto max_blocks = kOccupancyTable[is_int32 ? 0 : 1] * kNumSM;
|
|
const auto num_works = (num_qo_heads + num_kv_heads) * num_tokens;
|
|
const auto needed_blocks = div_ceil(num_works, (kBlockSize / kWorkThreads));
|
|
const auto num_blocks = std::min(max_blocks, needed_blocks);
|
|
LaunchKernel(num_blocks, kBlockSize, device.unwrap()) //
|
|
.enable_pdl(kUsePDL)(kernel, params);
|
|
}
|
|
|
|
static void run_fused(
|
|
const tvm::ffi::TensorView q,
|
|
const tvm::ffi::TensorView k,
|
|
const tvm::ffi::TensorView v,
|
|
const tvm::ffi::TensorView k_cache,
|
|
const tvm::ffi::TensorView v_cache,
|
|
const tvm::ffi::TensorView cos_sin_cache,
|
|
const tvm::ffi::TensorView positions,
|
|
const tvm::ffi::TensorView out_loc) {
|
|
if constexpr (kSupportFused) {
|
|
return _run_fused_impl(q, k, v, k_cache, v_cache, cos_sin_cache, positions, out_loc);
|
|
} else {
|
|
host::Panic("Fused rope + store is not supported for rope_dim ", kRopeDim);
|
|
}
|
|
}
|
|
|
|
static void _run_fused_impl(
|
|
const tvm::ffi::TensorView q,
|
|
const tvm::ffi::TensorView k,
|
|
const tvm::ffi::TensorView v,
|
|
const tvm::ffi::TensorView k_cache,
|
|
const tvm::ffi::TensorView v_cache,
|
|
const tvm::ffi::TensorView cos_sin_cache,
|
|
const tvm::ffi::TensorView positions,
|
|
const tvm::ffi::TensorView out_loc) {
|
|
using namespace host;
|
|
|
|
auto N = SymbolicSize{"num_tokens"};
|
|
auto Q = SymbolicSize{"num_qo_heads"};
|
|
auto K = SymbolicSize{"num_kv_heads"};
|
|
auto D = SymbolicSize{"rope_dim"};
|
|
auto R = SymbolicSize{"row_size"};
|
|
auto Dq = SymbolicSize{"q_stride"};
|
|
auto Dk = SymbolicSize{"k_stride"};
|
|
auto Dv = SymbolicSize{"v_stride"};
|
|
auto Dd = SymbolicSize{"head_stride"};
|
|
auto Dc = SymbolicSize{"cache_stride"};
|
|
auto device = SymbolicDevice{};
|
|
auto id_type = SymbolicDType{};
|
|
D.set_value(kRopeDim);
|
|
device.set_options<kDLCUDA>();
|
|
|
|
TensorMatcher({N, Q, D}) // q input
|
|
.with_strides({Dq, Dd, 1})
|
|
.with_dtype<DType>()
|
|
.with_device(device)
|
|
.verify(q);
|
|
TensorMatcher({N, K, D}) // k input
|
|
.with_strides({Dk, Dd, 1})
|
|
.with_dtype<DType>()
|
|
.with_device(device)
|
|
.verify(k);
|
|
TensorMatcher({N, K, D}) // v input
|
|
.with_strides({Dv, Dd, 1})
|
|
.with_dtype<DType>()
|
|
.with_device(device)
|
|
.verify(v);
|
|
TensorMatcher({-1, D}) // cos_sin_cache
|
|
.with_dtype<float>()
|
|
.with_device(device)
|
|
.verify(cos_sin_cache);
|
|
TensorMatcher({N}) // positions, out_loc
|
|
.with_dtype<int32_t, int64_t>(id_type)
|
|
.with_device(device)
|
|
.verify(positions)
|
|
.verify(out_loc);
|
|
TensorMatcher({-1, R}) // k_cache
|
|
.with_strides({Dc, 1})
|
|
.with_dtype<DType>()
|
|
.with_device(device)
|
|
.verify(k_cache)
|
|
.verify(v_cache);
|
|
|
|
const auto num_tokens = static_cast<uint32_t>(N.unwrap());
|
|
const auto num_qo_heads = static_cast<uint32_t>(Q.unwrap());
|
|
const auto num_kv_heads = static_cast<uint32_t>(K.unwrap());
|
|
const auto q_stride_bytes = static_cast<int64_t>(Dq.unwrap() * sizeof(DType));
|
|
const auto k_stride_bytes = static_cast<int64_t>(Dk.unwrap() * sizeof(DType));
|
|
const auto head_stride = Dd.unwrap();
|
|
const auto row_dim = R.unwrap();
|
|
const auto head_stride_bytes = static_cast<int64_t>(Dd.unwrap() * sizeof(DType));
|
|
|
|
RuntimeCheck(kRopeDim == head_stride, "rope_dim ", kRopeDim, " should = head_stride ", head_stride);
|
|
RuntimeCheck(num_kv_heads * kRopeDim == row_dim, "invalid kvcache");
|
|
|
|
// NOTE: we offset the k here to reduce computation cost in the kernel
|
|
const int64_t k_offset = static_cast<int64_t>(num_qo_heads) * head_stride_bytes;
|
|
const auto params = FusedRopeParams{
|
|
.q_ptr = q.data_ptr(),
|
|
.k_ptr = pointer::offset(k.data_ptr(), -k_offset),
|
|
.cos_sin_cache_ptr = cos_sin_cache.data_ptr(),
|
|
.positions = positions.data_ptr(),
|
|
.q_stride_bytes = q_stride_bytes,
|
|
.k_stride_bytes = k_stride_bytes,
|
|
.head_stride_bytes = head_stride_bytes,
|
|
.num_qo_heads = num_qo_heads,
|
|
.num_kv_heads = num_kv_heads,
|
|
.num_tokens = num_tokens,
|
|
};
|
|
|
|
const auto v_stride_bytes = static_cast<int64_t>(Dv.unwrap() * sizeof(DType));
|
|
const auto cache_stride_bytes = static_cast<int64_t>(Dc.unwrap() * sizeof(DType));
|
|
const auto store_params = FusedRopeStoreParams{
|
|
.base_params = params,
|
|
.v_ptr = v.data_ptr(),
|
|
.k_cache = pointer::offset(k_cache.data_ptr(), -k_offset),
|
|
.v_cache = v_cache.data_ptr(),
|
|
.out_loc = out_loc.data_ptr(),
|
|
.v_stride_bytes = v_stride_bytes,
|
|
.cache_stride_bytes = cache_stride_bytes,
|
|
};
|
|
|
|
const auto is_int32 = id_type.is_type<int32_t>();
|
|
const auto kernel = is_int32 ? _kernel_1<int32_t> : _kernel_1<int64_t>;
|
|
const uint32_t kNumSM = get_num_sm(device.unwrap());
|
|
static const uint32_t kOccupancyTable[2] = {
|
|
runtime::get_blocks_per_sm(_kernel_1<int32_t>, kBlockSize),
|
|
runtime::get_blocks_per_sm(_kernel_1<int64_t>, kBlockSize),
|
|
};
|
|
const auto max_blocks = kOccupancyTable[is_int32 ? 0 : 1] * kNumSM;
|
|
// rope works for q+k heads, plus v store works for kv heads
|
|
const auto num_total_works = (num_qo_heads + 2 * num_kv_heads) * num_tokens;
|
|
const auto needed_blocks = div_ceil(num_total_works, (kBlockSize / kWorkThreads));
|
|
const auto num_blocks = std::min(max_blocks, needed_blocks);
|
|
LaunchKernel(num_blocks, kBlockSize, device.unwrap()) //
|
|
.enable_pdl(kUsePDL)(kernel, store_params);
|
|
}
|
|
};
|
|
|
|
} // namespace
|