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
258 lines
9.8 KiB
Plaintext
258 lines
9.8 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/utils.cuh>
|
|
|
|
#include <sgl_kernel/impl/norm.cuh>
|
|
|
|
#include <dlpack/dlpack.h>
|
|
#include <tvm/ffi/container/tensor.h>
|
|
|
|
#include <cstdint>
|
|
#include <cuda_bf16.h>
|
|
#include <cuda_fp16.h>
|
|
#include <type_traits>
|
|
|
|
namespace {
|
|
|
|
struct QKNormParams {
|
|
void* __restrict__ q;
|
|
void* __restrict__ k; // k is offset by (-num_qo_heads * head_dim) elements
|
|
int64_t q_stride;
|
|
int64_t k_stride;
|
|
uint32_t num_qo_heads;
|
|
uint32_t num_kv_heads;
|
|
float eps;
|
|
const void* __restrict__ q_weight;
|
|
const void* __restrict__ k_weight;
|
|
uint32_t num_tokens;
|
|
};
|
|
|
|
constexpr uint32_t kWarpsPerBlock = 4;
|
|
constexpr uint32_t kThreadsPerBlock = kWarpsPerBlock * device::kWarpThreads;
|
|
|
|
// Warp-level kernel for head_dim <= 256
|
|
template <int64_t kHeadDim, bool kUsePDL, typename Float>
|
|
__global__ void fused_qknorm_warp(const QKNormParams __grid_constant__ params) {
|
|
using namespace device;
|
|
using Storage = norm::StorageType<Float, kHeadDim>;
|
|
|
|
static_assert(sizeof(Float) == 2, "Only support FP16/BF16");
|
|
const auto& [q, k, q_stride, k_stride, num_qo_heads, num_kv_heads, eps, q_weight, k_weight, num_tokens] = params;
|
|
|
|
const auto num_blks = gridDim.x;
|
|
const auto num_workers = num_blks * kWarpsPerBlock;
|
|
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 * kWarpsPerBlock + threadIdx.x / kWarpThreads;
|
|
const auto gmem = tile::Memory<Storage>::warp();
|
|
|
|
PDLWaitPrimary<kUsePDL>(); // wait for primary kernel
|
|
|
|
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 load_q = head_id < num_qo_heads;
|
|
const auto input = load_q ? pointer::offset(q, 2 * (token_id * q_stride + head_id * kHeadDim))
|
|
: pointer::offset(k, 2 * (token_id * k_stride + head_id * kHeadDim));
|
|
const auto weight = load_q ? q_weight : k_weight;
|
|
const auto input_vec = gmem.load(input);
|
|
const auto weight_vec = gmem.load(weight);
|
|
const auto output_vec = norm::apply_norm_warp<kHeadDim>(input_vec, weight_vec, eps);
|
|
gmem.store(input, output_vec);
|
|
}
|
|
|
|
PDLTriggerSecondary<kUsePDL>(); // launch secondary kernel
|
|
}
|
|
|
|
// For CTA level, used for head_dim > 256 (512,1024)
|
|
template <int64_t kHeadDim, bool kUsePDL, typename Float>
|
|
__global__ void fused_qknorm_cta(const QKNormParams __grid_constant__ params) {
|
|
using namespace device;
|
|
using Storage = norm::StorageType<Float, kHeadDim>;
|
|
|
|
constexpr auto kNumThreads = host::norm::get_cta_threads<Float, kHeadDim>();
|
|
constexpr auto kNumWarps = kNumThreads / kWarpThreads;
|
|
|
|
static_assert(sizeof(Float) == 2, "Only support FP16/BF16");
|
|
const auto& [q, k, q_stride, k_stride, num_qo_heads, num_kv_heads, eps, q_weight, k_weight, num_tokens] = params;
|
|
|
|
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 gmem = tile::Memory<Storage>::cta(kNumThreads);
|
|
__shared__ float smem[norm::kSmemBufferSize];
|
|
|
|
PDLWaitPrimary<kUsePDL>(); // wait for primary kernel
|
|
|
|
for (auto idx = blockIdx.x; idx < num_works; idx += gridDim.x) {
|
|
const int64_t token_id = idx / num_q_and_k_heads;
|
|
const int64_t head_id = idx % num_q_and_k_heads;
|
|
const auto load_q = head_id < num_qo_heads;
|
|
const auto input = load_q ? pointer::offset(q, 2 * (token_id * q_stride + head_id * kHeadDim))
|
|
: pointer::offset(k, 2 * (token_id * k_stride + head_id * kHeadDim));
|
|
const auto weight = load_q ? q_weight : k_weight;
|
|
const auto input_vec = gmem.load(input);
|
|
const auto weight_vec = gmem.load(weight);
|
|
const auto output_vec = norm::apply_norm_cta<kHeadDim>(input_vec, weight_vec, eps, smem, kNumWarps);
|
|
gmem.store(input, output_vec);
|
|
}
|
|
|
|
PDLTriggerSecondary<kUsePDL>(); // launch secondary kernel
|
|
}
|
|
|
|
// Warp-level kernel struct for head_dim <= 256
|
|
template <int64_t kHeadDim, bool kUsePDL, typename DType>
|
|
struct QKNormKernelWarp {
|
|
static_assert(std::is_same_v<DType, fp16_t> || std::is_same_v<DType, bf16_t>);
|
|
static_assert(!host::norm::should_use_cta<DType, kHeadDim>(), "Use QKNormKernelCTA for head_dim > 256");
|
|
static constexpr auto kernel = fused_qknorm_warp<kHeadDim, kUsePDL, DType>;
|
|
|
|
static void
|
|
run(const tvm::ffi::TensorView q,
|
|
const tvm::ffi::TensorView k,
|
|
const tvm::ffi::TensorView q_weight,
|
|
const tvm::ffi::TensorView k_weight,
|
|
float eps) {
|
|
using namespace host;
|
|
|
|
auto N = SymbolicSize{"num_tokens"};
|
|
auto Q = SymbolicSize{"num_qo_heads"};
|
|
auto K = SymbolicSize{"num_kv_heads"};
|
|
auto D = SymbolicSize{"head_dim"};
|
|
auto Sq = SymbolicSize{"q_stride"};
|
|
auto Sk = SymbolicSize{"k_stride"};
|
|
auto device = SymbolicDevice{};
|
|
D.set_value(kHeadDim);
|
|
device.set_options<kDLCUDA>();
|
|
|
|
TensorMatcher({N, Q, D}) // q input
|
|
.with_strides({Sq, D, 1})
|
|
.with_dtype<DType>()
|
|
.with_device(device)
|
|
.verify(q);
|
|
TensorMatcher({N, K, D}) // k input
|
|
.with_strides({Sk, D, 1})
|
|
.with_dtype<DType>()
|
|
.with_device(device)
|
|
.verify(k);
|
|
TensorMatcher({D}) // weight
|
|
.with_dtype<DType>()
|
|
.with_device(device)
|
|
.verify(q_weight)
|
|
.verify(k_weight);
|
|
|
|
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());
|
|
|
|
// NOTE: we offset the k here to reduce computation cost in the kernel
|
|
const auto params = QKNormParams{
|
|
.q = q.data_ptr(),
|
|
.k = pointer::offset(k.data_ptr(), -2 * static_cast<int64_t>(num_qo_heads) * kHeadDim),
|
|
.q_stride = static_cast<int64_t>(Sq.unwrap()),
|
|
.k_stride = static_cast<int64_t>(Sk.unwrap()),
|
|
.num_qo_heads = num_qo_heads,
|
|
.num_kv_heads = num_kv_heads,
|
|
.eps = eps,
|
|
.q_weight = q_weight.data_ptr(),
|
|
.k_weight = k_weight.data_ptr(),
|
|
.num_tokens = num_tokens,
|
|
};
|
|
|
|
static const uint32_t max_occupancy = runtime::get_blocks_per_sm(kernel, kThreadsPerBlock);
|
|
static const uint32_t kNumSM = runtime::get_sm_count(device.unwrap().device_id);
|
|
|
|
// choose kernel based on dtype
|
|
const auto num_works = (num_qo_heads + num_kv_heads) * num_tokens;
|
|
const auto needed_blocks = div_ceil(num_works, kWarpsPerBlock);
|
|
|
|
// we use persistent kernel, which limit the number of blocks to reduce overhead
|
|
const auto num_blocks = std::min(kNumSM * max_occupancy, needed_blocks);
|
|
LaunchKernel(num_blocks, kThreadsPerBlock, device.unwrap()) //
|
|
.enable_pdl(kUsePDL)(kernel, params);
|
|
}
|
|
};
|
|
|
|
// This goes with fused_qknorm_cta
|
|
template <int64_t kHeadDim, bool kUsePDL, typename DType>
|
|
struct QKNormKernelCTA {
|
|
static_assert(std::is_same_v<DType, fp16_t> || std::is_same_v<DType, bf16_t>);
|
|
static_assert(host::norm::should_use_cta<DType, kHeadDim>(), "Use QKNormKernelWarp for head_dim <= 256");
|
|
static constexpr auto kernel = fused_qknorm_cta<kHeadDim, kUsePDL, DType>;
|
|
static constexpr auto kNumThreads = host::norm::get_cta_threads<DType, kHeadDim>();
|
|
|
|
static void
|
|
run(const tvm::ffi::TensorView q,
|
|
const tvm::ffi::TensorView k,
|
|
const tvm::ffi::TensorView q_weight,
|
|
const tvm::ffi::TensorView k_weight,
|
|
float eps) {
|
|
using namespace host;
|
|
|
|
auto N = SymbolicSize{"num_tokens"};
|
|
auto Q = SymbolicSize{"num_qo_heads"};
|
|
auto K = SymbolicSize{"num_kv_heads"};
|
|
auto D = SymbolicSize{"head_dim"};
|
|
auto Sq = SymbolicSize{"q_stride"};
|
|
auto Sk = SymbolicSize{"k_stride"};
|
|
auto device = SymbolicDevice{};
|
|
D.set_value(kHeadDim);
|
|
device.set_options<kDLCUDA>();
|
|
|
|
TensorMatcher({N, Q, D}) // q input
|
|
.with_strides({Sq, D, 1})
|
|
.with_dtype<DType>()
|
|
.with_device(device)
|
|
.verify(q);
|
|
TensorMatcher({N, K, D}) // k input
|
|
.with_strides({Sk, D, 1})
|
|
.with_dtype<DType>()
|
|
.with_device(device)
|
|
.verify(k);
|
|
TensorMatcher({D}) // weight
|
|
.with_dtype<DType>()
|
|
.with_device(device)
|
|
.verify(q_weight)
|
|
.verify(k_weight);
|
|
|
|
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());
|
|
|
|
// NOTE: we offset the k here to reduce computation cost in the kernel
|
|
const auto params = QKNormParams{
|
|
.q = q.data_ptr(),
|
|
.k = pointer::offset(k.data_ptr(), -2 * static_cast<int64_t>(num_qo_heads) * kHeadDim),
|
|
.q_stride = static_cast<int64_t>(Sq.unwrap()),
|
|
.k_stride = static_cast<int64_t>(Sk.unwrap()),
|
|
.num_qo_heads = num_qo_heads,
|
|
.num_kv_heads = num_kv_heads,
|
|
.eps = eps,
|
|
.q_weight = q_weight.data_ptr(),
|
|
.k_weight = k_weight.data_ptr(),
|
|
.num_tokens = num_tokens,
|
|
};
|
|
|
|
static const uint32_t max_occupancy = runtime::get_blocks_per_sm(kernel, kNumThreads);
|
|
static const uint32_t kNumSM = runtime::get_sm_count(device.unwrap().device_id);
|
|
|
|
const auto num_works = (num_qo_heads + num_kv_heads) * num_tokens;
|
|
|
|
// we use persistent kernel, which limit the number of blocks to reduce overhead
|
|
const auto num_blocks = std::min<uint32_t>(num_works, max_occupancy * kNumSM);
|
|
LaunchKernel(num_blocks, kNumThreads, device.unwrap()) //
|
|
.enable_pdl(kUsePDL)(kernel, params);
|
|
}
|
|
};
|
|
|
|
// Unified dispatch: select warp or CTA kernel based on head_dim
|
|
template <int64_t kHeadDim, bool kUsePDL, typename DType>
|
|
using QKNormKernel = std::conditional_t<
|
|
host::norm::should_use_cta<DType, kHeadDim>(),
|
|
QKNormKernelCTA<kHeadDim, kUsePDL, DType>,
|
|
QKNormKernelWarp<kHeadDim, kUsePDL, DType>>;
|
|
|
|
} // namespace
|