[init] baseline7 from project_6

This commit is contained in:
root
2026-08-25 07:05:49 +00:00
commit 0bb337bc00
1392 changed files with 321187 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
#!/bin/bash
# build_test_cutlass_batched.sh — Compile and test Cu10 TensorOp batched GEMM
set -eo pipefail
SAMPLES="/usr/local/corex-samples-3.2.3_x86_64/samples/cutlass"
SRC="ex_engine/xllm_kernels/cuda/moe_cutlass_batched.cu"
echo "=== Compile Cu10 TensorOp batched HGEMM ==="
/usr/local/corex/bin/clang++ \
--cuda-gpu-arch=ivcore10 --cuda-path=/usr/local/corex \
-I"${SAMPLES}/include" \
-I/usr/local/corex/include \
-L/usr/local/corex/lib64 -lcudart -lcutlass \
-DBUILD_STANDALONE_TEST \
-O2 -std=c++17 \
"$SRC" -o /tmp/test_cutlass_batched 2>&1
if [ -f /tmp/test_cutlass_batched ]; then
echo "Compile: SUCCESS"
echo ""
echo "=== Run ==="
/tmp/test_cutlass_batched
else
echo "Compile: FAILED"
fi

View File

@@ -0,0 +1,156 @@
#!/bin/bash
# build_test_hgemm.sh — Compile and test hgemm_blocktiling on BI-V100
#
# Usage: bash ex_engine/xllm_kernels/build_test_hgemm.sh
set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CUDA_DIR="${SCRIPT_DIR}/cuda"
echo "=== 1. Compile hgemm_blocktiling ==="
python3 -c "
import torch.utils.cpp_extension as ext
import os, shutil, glob
name = 'hgemm_blocktiling'
build_dir = '${SCRIPT_DIR}/build/tmp_' + name
os.makedirs(build_dir, exist_ok=True)
try:
mod = ext.load(
name=name,
sources=[
'${CUDA_DIR}/hgemm_blocktiling.cu',
'${CUDA_DIR}/bindings/hgemm_bind.cpp',
],
extra_include_paths=['${CUDA_DIR}/headers'],
extra_cflags=['-O2', '-std=c++17'],
extra_cuda_cflags=['-O2'],
build_directory=build_dir,
verbose=True,
)
built = glob.glob(build_dir + '/' + name + '*.so')
if built:
dst = '${SCRIPT_DIR}/build/' + name + '.so'
shutil.copy2(built[0], dst)
print(f'[build] SUCCESS: {dst} ({os.path.getsize(dst)} bytes)')
else:
print('[build] WARNING: .so not found')
except Exception as e:
print(f'[build] FAILED: {e}')
import traceback
traceback.print_exc()
"
echo ""
echo "=== 2. Functional test ==="
python3 << 'PYTEST'
import torch
import sys, os, glob
# Find and load the .so
build_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)) if '__file__' in dir() else '.',
'ex_engine/xllm_kernels/build')
sys.path.insert(0, build_dir)
try:
import hgemm_blocktiling as hg
print("Module loaded successfully")
except ImportError:
# Try loading from tmp build dir
import importlib.util
so_files = glob.glob('ex_engine/xllm_kernels/build/tmp_hgemm_blocktiling/hgemm_blocktiling*.so')
if not so_files:
print("SKIP: .so not found (need GPU machine)")
sys.exit(0)
spec = importlib.util.spec_from_file_location("hgemm_blocktiling", so_files[0])
hg = importlib.util.module_from_spec(spec)
spec.loader.exec_module(hg)
print(f"Module loaded from {so_files[0]}")
# Test 1: Small GEMM correctness
print("\n--- Test 1: Small GEMM (64x64 @ 64x64) ---")
M, N, K = 64, 64, 64
A = torch.randn(M, K, dtype=torch.float16, device='cuda')
B = torch.randn(K, N, dtype=torch.float16, device='cuda')
C_ref = torch.matmul(A.float(), B.float()).half()
C_our = hg.hgemm(A, B)
diff = (C_ref.float() - C_our.float()).abs().max().item()
print(f" Max abs diff: {diff:.6f}")
assert diff < 1.0, f"FAILED: diff={diff} too large"
print(f" PASS (diff < 1.0)")
# Test 2: Larger GEMM (typical MoE dimensions)
print("\n--- Test 2: MoE-sized GEMM (256x4096 @ 4096x11008) ---")
M, N, K = 256, 11008, 4096
A = torch.randn(M, K, dtype=torch.float16, device='cuda') * 0.01
B = torch.randn(K, N, dtype=torch.float16, device='cuda') * 0.01
C_ref = torch.matmul(A.float(), B.float()).half()
C_our = hg.hgemm(A, B)
diff = (C_ref.float() - C_our.float()).abs().max().item()
rel_diff = diff / (C_ref.float().abs().max().item() + 1e-8)
print(f" Max abs diff: {diff:.6f}, rel: {rel_diff:.6f}")
assert rel_diff < 0.05, f"FAILED: rel_diff={rel_diff} too large"
print(f" PASS")
# Test 3: MoE expert GEMM with variable counts
print("\n--- Test 3: MoE expert GEMM (8 experts, variable tokens) ---")
num_experts = 8
K_dim = 128
N_dim = 256
expert_counts = torch.tensor([32, 16, 0, 48, 8, 24, 4, 12], dtype=torch.int32)
total_tokens = expert_counts.sum().item()
input_tensor = torch.randn(total_tokens, K_dim, dtype=torch.float16, device='cuda') * 0.1
weights = torch.randn(num_experts, N_dim, K_dim, dtype=torch.float16, device='cuda') * 0.1
output = hg.moe_expert_gemm(input_tensor, weights, expert_counts.cuda())
# Verify against torch reference
offset = 0
for e in range(num_experts):
cnt = expert_counts[e].item()
if cnt == 0:
continue
inp_e = input_tensor[offset:offset+cnt]
w_e = weights[e] # (N, K)
ref_e = torch.matmul(inp_e.float(), w_e.float().t()).half()
out_e = output[offset:offset+cnt]
diff_e = (ref_e.float() - out_e.float()).abs().max().item()
print(f" Expert {e} (tokens={cnt}): max_diff={diff_e:.6f}")
offset += cnt
print(f" PASS")
# Test 4: Performance benchmark
print("\n--- Test 4: Performance (256x4096 @ 4096x11008, 100 iters) ---")
M, N, K = 256, 11008, 4096
A = torch.randn(M, K, dtype=torch.float16, device='cuda')
B = torch.randn(K, N, dtype=torch.float16, device='cuda')
# Warmup
for _ in range(10):
hg.hgemm(A, B)
torch.cuda.synchronize()
import time
start = time.time()
for _ in range(100):
hg.hgemm(A, B)
torch.cuda.synchronize()
elapsed = time.time() - start
print(f" Custom kernel: {elapsed*10:.2f} ms/iter")
start = time.time()
for _ in range(100):
torch.matmul(A, B)
torch.cuda.synchronize()
elapsed2 = time.time() - start
print(f" torch.matmul: {elapsed2*10:.2f} ms/iter")
print(f" Ratio: {elapsed/elapsed2:.2f}x")
print("\n=== ALL TESTS PASSED ===")
PYTEST

View File

@@ -0,0 +1,131 @@
#!/bin/bash
# build_test_hgemm_warp.sh — Compile and benchmark kernel 10 (warp tiling)
set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CUDA_DIR="${SCRIPT_DIR}/cuda"
echo "=== Compile hgemm_warptiling (kernel 10, WARPSIZE=64) ==="
python3 -c "
import torch.utils.cpp_extension as ext
import os, shutil, glob
name = 'hgemm_warptiling'
build_dir = '${SCRIPT_DIR}/build/tmp_' + name
os.makedirs(build_dir, exist_ok=True)
try:
mod = ext.load(
name=name,
sources=[
'${CUDA_DIR}/hgemm_warptiling.cu',
'${CUDA_DIR}/bindings/hgemm_warp_bind.cpp',
],
extra_include_paths=['${CUDA_DIR}/headers'],
extra_cflags=['-O2', '-std=c++17'],
extra_cuda_cflags=['-O2'],
build_directory=build_dir,
verbose=True,
)
built = glob.glob(build_dir + '/' + name + '*.so')
if built:
dst = '${SCRIPT_DIR}/build/' + name + '.so'
shutil.copy2(built[0], dst)
print(f'[build] SUCCESS: {dst} ({os.path.getsize(dst)} bytes)')
except Exception as e:
print(f'[build] FAILED: {e}')
import traceback; traceback.print_exc()
"
echo ""
echo "=== Test ==="
python3 << 'PYTEST'
import torch, sys, os, glob, time
build_dir = 'ex_engine/xllm_kernels/build'
sys.path.insert(0, build_dir)
# Load kernel 10
try:
so = glob.glob(f'{build_dir}/tmp_hgemm_warptiling/hgemm_warptiling*.so')
if so:
import importlib.util
spec = importlib.util.spec_from_file_location("hgemm_warptiling", so[0])
hw = importlib.util.module_from_spec(spec)
spec.loader.exec_module(hw)
print("kernel 10 (warp tiling) loaded")
else:
print("SKIP: kernel 10 .so not found")
sys.exit(0)
except Exception as e:
print(f"SKIP: {e}")
sys.exit(0)
# Load kernel 6 for comparison
try:
so6 = glob.glob(f'{build_dir}/tmp_hgemm_blocktiling/hgemm_blocktiling*.so')
if so6:
spec6 = importlib.util.spec_from_file_location("hgemm_blocktiling", so6[0])
hb = importlib.util.module_from_spec(spec6)
spec6.loader.exec_module(hb)
has_k6 = True
print("kernel 6 (block tiling) loaded")
else:
has_k6 = False
except:
has_k6 = False
# Correctness
print("\n--- Correctness (128x128 @ 128x128) ---")
M, N, K = 128, 128, 128
A = torch.randn(M, K, dtype=torch.float16, device='cuda')
B = torch.randn(K, N, dtype=torch.float16, device='cuda')
C_ref = torch.matmul(A.float(), B.float()).half()
C_k10 = hw.hgemm_warp(A, B)
diff = (C_ref.float() - C_k10.float()).abs().max().item()
print(f" Max abs diff: {diff:.6f}")
assert diff < 2.0, f"FAIL diff={diff}"
print(" PASS")
# Correctness on MoE size
print("\n--- Correctness (256x4096 @ 4096x11008) ---")
M, N, K = 256, 11008, 4096
A = torch.randn(M, K, dtype=torch.float16, device='cuda') * 0.01
B = torch.randn(K, N, dtype=torch.float16, device='cuda') * 0.01
C_ref = torch.matmul(A.float(), B.float()).half()
C_k10 = hw.hgemm_warp(A, B)
diff = (C_ref.float() - C_k10.float()).abs().max().item()
rel = diff / (C_ref.float().abs().max().item() + 1e-8)
print(f" Max abs diff: {diff:.6f}, rel: {rel:.6f}")
print(" PASS" if rel < 0.1 else " WARN: large relative diff")
# Performance benchmark
print("\n--- Performance (256x4096 @ 4096x11008, 100 iters) ---")
M, N, K = 256, 11008, 4096
A = torch.randn(M, K, dtype=torch.float16, device='cuda')
B = torch.randn(K, N, dtype=torch.float16, device='cuda')
def bench(fn, name, iters=100, warmup=10):
for _ in range(warmup):
fn()
torch.cuda.synchronize()
t0 = time.time()
for _ in range(iters):
fn()
torch.cuda.synchronize()
ms = (time.time() - t0) / iters * 1000
print(f" {name}: {ms:.2f} ms/iter")
return ms
t_torch = bench(lambda: torch.matmul(A, B), "torch.matmul")
t_k10 = bench(lambda: hw.hgemm_warp(A, B), "kernel 10 (warp)")
if has_k6:
t_k6 = bench(lambda: hb.hgemm(A, B), "kernel 6 (block)")
print(f"\n K10/torch = {t_k10/t_torch:.2f}x")
print(f" K6/torch = {t_k6/t_torch:.2f}x")
print(f" K10/K6 = {t_k10/t_k6:.2f}x (K10 should be faster)")
else:
print(f"\n K10/torch = {t_k10/t_torch:.2f}x")
print("\n=== DONE ===")
PYTEST

View File

@@ -0,0 +1,189 @@
/* Copyright 2025 The vLLM Authors and The xLLM Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <c10/cuda/CUDAGuard.h>
#include <torch/cuda.h>
#include <torch/extension.h>
#include <cstdint>
#include "device_utils.cuh"
// ref to:
// https://github.com/vllm-project/vllm/blob/main/csrc/activation_kernels.cu
namespace {
using ::xllm::kernel::cuda::xllm_ldg;
template <typename scalar_t,
scalar_t (*ACT_FN)(const scalar_t&),
bool act_first>
__device__ __forceinline__ scalar_t compute(const scalar_t& x,
const scalar_t& y) {
return act_first ? ACT_FN(x) * y : x * ACT_FN(y);
}
// Check if pointer is 16-byte aligned for int4 vectorized access
__device__ __forceinline__ bool is_16byte_aligned(const void* ptr) {
return (reinterpret_cast<uintptr_t>(ptr) & 15) == 0;
}
// Activation and gating kernel template with 128-bit vectorized access
// optimization.
template <typename scalar_t,
scalar_t (*ACT_FN)(const scalar_t&),
bool act_first>
__global__ void XLLM_KERNEL_ATTR(1024)
act_and_mul_kernel(scalar_t* __restrict__ out, // [..., d]
const scalar_t* __restrict__ input, // [..., 2, d]
const int d) {
constexpr int kVecSize = 16 / sizeof(scalar_t);
const int64_t token_idx = blockIdx.x;
const scalar_t* x_ptr = input + token_idx * 2 * d;
const scalar_t* y_ptr = x_ptr + d;
scalar_t* out_ptr = out + token_idx * d;
// Check alignment for 128-bit vectorized access.
// All three pointers must be 16-byte aligned for safe int4 operations.
const bool aligned = is_16byte_aligned(x_ptr) && is_16byte_aligned(y_ptr) &&
is_16byte_aligned(out_ptr);
if (aligned && d >= kVecSize) {
// Fast path: 128-bit vectorized loop
const int4* x_vec = reinterpret_cast<const int4*>(x_ptr);
const int4* y_vec = reinterpret_cast<const int4*>(y_ptr);
int4* out_vec = reinterpret_cast<int4*>(out_ptr);
const int num_vecs = d / kVecSize;
const int vec_end = num_vecs * kVecSize;
for (int i = threadIdx.x; i < num_vecs; i += blockDim.x) {
int4 x = xllm_ldg(&x_vec[i]), y = xllm_ldg(&y_vec[i]), r;
auto* xp = reinterpret_cast<scalar_t*>(&x);
auto* yp = reinterpret_cast<scalar_t*>(&y);
auto* rp = reinterpret_cast<scalar_t*>(&r);
#pragma unroll
for (int j = 0; j < kVecSize; j++) {
rp[j] = compute<scalar_t, ACT_FN, act_first>(xp[j], yp[j]);
}
out_vec[i] = r;
}
// Scalar cleanup for remaining elements
for (int i = vec_end + threadIdx.x; i < d; i += blockDim.x) {
out_ptr[i] = compute<scalar_t, ACT_FN, act_first>(xllm_ldg(&x_ptr[i]),
xllm_ldg(&y_ptr[i]));
}
} else {
// Scalar fallback for unaligned data or small d
for (int64_t idx = threadIdx.x; idx < d; idx += blockDim.x) {
const scalar_t x = xllm_ldg(&x_ptr[idx]);
const scalar_t y = xllm_ldg(&y_ptr[idx]);
out_ptr[idx] = compute<scalar_t, ACT_FN, act_first>(x, y);
}
}
}
template <typename T>
__device__ __forceinline__ T silu_kernel(const T& x) {
// x * sigmoid(x)
const float f = static_cast<float>(x);
return static_cast<T>(f / (1.0f + expf(-f)));
}
template <typename T>
__device__ __forceinline__ T gelu_kernel(const T& x) {
// Equivalent to PyTorch GELU with 'none' approximation.
// Refer to:
// https://github.com/pytorch/pytorch/blob/8ac9b20d4b090c213799e81acf48a55ea8d437d6/aten/src/ATen/native/cuda/ActivationGeluKernel.cu#L36-L38
const float f = static_cast<float>(x);
constexpr float kAlpha = M_SQRT1_2;
return static_cast<T>(f * 0.5f * (1.0f + ::erf(f * kAlpha)));
}
template <typename T>
__device__ __forceinline__ T gelu_tanh_kernel(const T& x) {
// Equivalent to PyTorch GELU with 'tanh' approximation.
// Refer to:
// https://github.com/pytorch/pytorch/blob/8ac9b20d4b090c213799e81acf48a55ea8d437d6/aten/src/ATen/native/cuda/ActivationGeluKernel.cu#L25-L30
const float f = static_cast<float>(x);
constexpr float kBeta = M_SQRT2 * M_2_SQRTPI * 0.5f;
constexpr float kKappa = 0.044715;
float x_cube = f * f * f;
float inner = kBeta * (f + kKappa * x_cube);
return static_cast<T>(0.5f * f * (1.0f + ::tanhf(inner)));
}
#define LAUNCH_ACTIVATION_GATE_KERNEL(KERNEL, ACT_FIRST) \
int d = input.size(-1) / 2; \
int64_t num_tokens = input.numel() / input.size(-1); \
dim3 grid(num_tokens); \
dim3 block(std::min(d, 1024)); \
if (num_tokens == 0) { \
return; \
} \
const at::cuda::OptionalCUDAGuard device_guard(device_of(input)); \
const cudaStream_t stream = at::cuda::getCurrentCUDAStream(); \
DISPATCH_FLOATING_TYPES(input.scalar_type(), "act_and_mul_kernel", [&] { \
act_and_mul_kernel<scalar_t, KERNEL<scalar_t>, ACT_FIRST> \
<<<grid, block, 0, stream>>>( \
out.data_ptr<scalar_t>(), input.data_ptr<scalar_t>(), d); \
});
void silu_and_mul(torch::Tensor out, // [..., d]
torch::Tensor input) // [..., 2 * d]
{
LAUNCH_ACTIVATION_GATE_KERNEL(silu_kernel, true);
}
void gelu_and_mul(torch::Tensor& out, // [..., d]
torch::Tensor& input) // [..., 2 * d]
{
LAUNCH_ACTIVATION_GATE_KERNEL(gelu_kernel, true);
}
void gelu_tanh_and_mul(torch::Tensor& out, // [..., d]
torch::Tensor& input) // [..., 2 * d]
{
LAUNCH_ACTIVATION_GATE_KERNEL(gelu_tanh_kernel, true);
}
} // namespace
namespace xllm::kernel::cuda {
void act_and_mul(torch::Tensor out,
torch::Tensor input,
const std::string& act_mode) {
if (act_mode != "silu" && act_mode != "gelu" && act_mode != "gelu_tanh" &&
act_mode != "gelu_pytorch_tanh") {
TORCH_CHECK(false, "Unsupported act mode: ", act_mode,
", only support silu, gelu, gelu_tanh, gelu_pytorch_tanh");
}
// flashinfer act_and_mul ops
// std::string uri = act_mode + "_and_mul";
// FunctionFactory::get_instance().act_and_mul(uri).call(
// out, input, support_pdl());
if (act_mode == "silu") {
silu_and_mul(out, input);
} else if (act_mode == "gelu") {
gelu_and_mul(out, input);
} else if (act_mode == "gelu_tanh" || act_mode == "gelu_pytorch_tanh") {
// gelu_tanh or gelu_pytorch_tanh (mathematically equivalent)
gelu_tanh_and_mul(out, input);
}
}
} // namespace xllm::kernel::cuda

View File

@@ -0,0 +1,129 @@
/*
* corex_batched_gemm_bind.cpp — pybind11 wrapper for CUTLASS batched GEMM
*
* Kernel uses RowMajor + OpClassTensorOp + Cu10 (verified 2.462ms).
* Source: ex_engine/xllm_kernels/cuda/moe_cutlass_batched.cu
*/
#include <torch/extension.h>
#include <cuda_runtime.h>
#include <cuda_fp16.h>
// Implemented in corex_batched_gemm_kernel.cu
// RowMajor, FP16 data, FP32 accumulation, TCU, Cu10
cudaError_t cutlass_batched_hgemm(
int m, int n, int k,
__half const *A, int lda, long long int batch_stride_A,
__half const *B, int ldb, long long int batch_stride_B,
__half *C, int ldc, long long int batch_stride_C,
int batch_count);
/*
* batched_gemm_fp16: C[i] = A[i] @ B[i]
* A: (batch, M, K) row-major
* B: (batch, K, N) row-major
* C: (batch, M, N) row-major
*
* Both A and B must be contiguous fp16 CUDA tensors.
*/
torch::Tensor batched_gemm_fp16(
torch::Tensor A, // (batch, M, K)
torch::Tensor B) // (batch, K, N)
{
TORCH_CHECK(A.is_cuda() && B.is_cuda(), "inputs must be CUDA tensors");
TORCH_CHECK(A.scalar_type() == torch::kFloat16 &&
B.scalar_type() == torch::kFloat16,
"inputs must be float16");
TORCH_CHECK(A.is_contiguous() && B.is_contiguous(),
"inputs must be contiguous");
TORCH_CHECK(A.dim() == 3 && B.dim() == 3,
"inputs must be 3D (batch, rows, cols)");
int batch = A.size(0);
int M = A.size(1);
int K = A.size(2);
int N = B.size(2);
TORCH_CHECK(B.size(0) == batch, "batch size mismatch");
TORCH_CHECK(B.size(1) == K, "K dimension mismatch");
auto C = torch::zeros({batch, M, N}, A.options());
// RowMajor: A is (M,K) with lda=K, B is (K,N) with ldb=N, C is (M,N) with ldc=N
auto status = cutlass_batched_hgemm(
M, N, K,
reinterpret_cast<const __half*>(A.data_ptr<at::Half>()),
K, (long long)M * K, // lda, strideA
reinterpret_cast<const __half*>(B.data_ptr<at::Half>()),
N, (long long)K * N, // ldb, strideB
reinterpret_cast<__half*>(C.data_ptr<at::Half>()),
N, (long long)M * N, // ldc, strideC
batch);
TORCH_CHECK(status == cudaSuccess,
"CUTLASS batched HGEMM failed: ", cudaGetErrorString(status));
return C;
}
/*
* moe_decode_fused: Full MoE decode using TCU batched GEMM.
*
* hidden_states: (1, H)
* w13_sel: (K, 2*I, H) — already gathered expert weights
* w2_sel: (K, H, I) — already gathered expert weights
* topk_weights: (K,)
*
* Pipeline:
* 1. gate_up = x @ w13^T via batched GEMM (K, 1, 2I)
* 2. act = silu(gate) * up
* 3. down = act @ w2^T via batched GEMM (K, 1, H)
* 4. out = weighted sum
*/
torch::Tensor moe_decode_fused(
torch::Tensor hidden_states, // (1, H)
torch::Tensor w13_sel, // (K, 2*I, H)
torch::Tensor w2_sel, // (K, H, I)
torch::Tensor topk_weights) // (K,)
{
int K_experts = w13_sel.size(0);
int two_I = w13_sel.size(1);
int H = w13_sel.size(2);
int I = two_I / 2;
// x: (1, H) → expand to (K, 1, H)
auto x = hidden_states.expand({K_experts, 1, H}).contiguous();
// w13^T: (K, 2I, H) → transpose last two dims → (K, H, 2I)
auto w13_t = w13_sel.transpose(1, 2).contiguous(); // (K, H, 2I)
// Step 1: gate_up = x @ w13^T → (K, 1, 2I)
auto gate_up = batched_gemm_fp16(x, w13_t);
gate_up = gate_up.squeeze(1); // (K, 2I)
// Step 2: silu activation
auto chunks = gate_up.chunk(2, /*dim=*/1);
auto act = torch::sigmoid(chunks[0]) * chunks[0] * chunks[1]; // silu(gate) * up
act = act.unsqueeze(1); // (K, 1, I)
// w2^T: (K, H, I) → transpose → (K, I, H)
auto w2_t = w2_sel.transpose(1, 2).contiguous(); // (K, I, H)
// Step 3: down = act @ w2^T → (K, 1, H)
auto down = batched_gemm_fp16(act, w2_t);
down = down.squeeze(1); // (K, H)
// Step 4: weighted sum
auto out = (down * topk_weights.unsqueeze(1)).sum(0, true);
return out.to(hidden_states.dtype());
}
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
m.doc() = "CUTLASS batched GEMM for MoE decode (BI-V100 TCU, Cu10 TensorOp)";
m.def("batched_gemm_fp16", &batched_gemm_fp16,
"Batched GEMM: (B,M,K) x (B,K,N) -> (B,M,N) in fp16 via TCU",
py::arg("A"), py::arg("B"));
m.def("moe_decode_fused", &moe_decode_fused,
"Full MoE decode via TCU batched GEMM",
py::arg("hidden_states"), py::arg("w13_sel"),
py::arg("w2_sel"), py::arg("topk_weights"));
}

View File

@@ -0,0 +1,135 @@
// hgemm_bind.cpp — pybind11 bindings for hgemm_blocktiling.cu
//
// Exports:
// hgemm(A, B, M, N, K) → C
// moe_expert_gemm(input, weights, expert_counts) → output
#include <torch/extension.h>
#include <cuda_runtime.h>
#include <cuda_fp16.h>
#include <ATen/cuda/CUDAContext.h>
#include <c10/cuda/CUDAStream.h>
#include <vector>
// Forward declarations from hgemm_blocktiling.cu
void launch_hgemm_blocktiling(
int M, int N, int K,
const __half* alpha, const __half* A, int lda,
const __half* B, int ldb,
const __half* beta, __half* C, int ldc,
cudaStream_t stream);
void launch_moe_expert_hgemm(
int num_experts,
const int* expert_counts,
const int* expert_offsets,
int N, int K,
const __half* input,
const __half* weights,
__half* output,
cudaStream_t stream);
// ============================================================================
// Python-facing wrappers
// ============================================================================
// Simple GEMM: C = A @ B
// A: (M, K) fp16, B: (K, N) fp16 → C: (M, N) fp16
torch::Tensor hgemm(torch::Tensor A, torch::Tensor B) {
TORCH_CHECK(A.is_cuda() && B.is_cuda(), "Inputs must be CUDA tensors");
TORCH_CHECK(A.scalar_type() == torch::kHalf, "A must be fp16");
TORCH_CHECK(B.scalar_type() == torch::kHalf, "B must be fp16");
TORCH_CHECK(A.dim() == 2 && B.dim() == 2, "A and B must be 2D");
TORCH_CHECK(A.size(1) == B.size(0), "Inner dimensions must match");
int M = A.size(0);
int K = A.size(1);
int N = B.size(1);
auto C = torch::zeros({M, N}, A.options());
__half alpha = __float2half(1.0f);
__half beta = __float2half(0.0f);
cudaStream_t stream = c10::cuda::getCurrentCUDAStream().stream();
launch_hgemm_blocktiling(
M, N, K, &alpha,
reinterpret_cast<const __half*>(A.data_ptr<at::Half>()),
A.size(1),
reinterpret_cast<const __half*>(B.data_ptr<at::Half>()),
B.size(1),
&beta,
reinterpret_cast<__half*>(C.data_ptr<at::Half>()),
C.size(1),
stream);
return C;
}
// MoE expert GEMM: for each expert e, compute
// output[offset_e : offset_e + count_e] = input[offset_e : offset_e + count_e] @ weights[e].T
//
// input: (total_tokens, K) fp16
// weights: (num_experts, N, K) fp16 — weight layout matches vllm w13/w2 convention
// expert_counts: (num_experts,) int32 — number of tokens per expert
//
// Returns: output (total_tokens, N) fp16
torch::Tensor moe_expert_gemm(
torch::Tensor input,
torch::Tensor weights,
torch::Tensor expert_counts
) {
TORCH_CHECK(input.is_cuda() && weights.is_cuda(), "Inputs must be CUDA");
TORCH_CHECK(input.scalar_type() == torch::kHalf, "input must be fp16");
TORCH_CHECK(weights.scalar_type() == torch::kHalf, "weights must be fp16");
TORCH_CHECK(expert_counts.scalar_type() == torch::kInt32 ||
expert_counts.scalar_type() == torch::kInt64,
"expert_counts must be int32 or int64");
int total_tokens = input.size(0);
int K = input.size(1);
int num_experts = weights.size(0);
int N = weights.size(1); // output dim
TORCH_CHECK(weights.size(2) == K, "weights K dim must match input");
auto output = torch::zeros({total_tokens, N}, input.options());
// Convert expert_counts to host int array
auto counts_cpu = expert_counts.to(torch::kCPU).to(torch::kInt32).contiguous();
std::vector<int> counts(num_experts);
std::vector<int> offsets(num_experts);
int cumsum = 0;
for (int i = 0; i < num_experts; i++) {
counts[i] = counts_cpu.data_ptr<int32_t>()[i];
offsets[i] = cumsum;
cumsum += counts[i];
}
cudaStream_t stream = c10::cuda::getCurrentCUDAStream().stream();
launch_moe_expert_hgemm(
num_experts,
counts.data(),
offsets.data(),
N, K,
reinterpret_cast<const __half*>(input.data_ptr<at::Half>()),
reinterpret_cast<const __half*>(weights.data_ptr<at::Half>()),
reinterpret_cast<__half*>(output.data_ptr<at::Half>()),
stream);
return output;
}
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
m.def("hgemm", &hgemm,
"FP16 GEMM: C = A @ B (adapted from siboehm kernel 6 for BI-V100)",
py::arg("A"), py::arg("B"));
m.def("moe_expert_gemm", &moe_expert_gemm,
"MoE expert GEMM: per-expert matmul with variable token counts",
py::arg("input"), py::arg("weights"), py::arg("expert_counts"));
}

View File

@@ -0,0 +1,36 @@
// hgemm_warp_bind.cpp — pybind11 for hgemm_warptiling (kernel 10, warp64)
#include <torch/extension.h>
#include <cuda_runtime.h>
#include <cuda_fp16.h>
#include <ATen/cuda/CUDAContext.h>
#include <c10/cuda/CUDAStream.h>
void launch_hgemm_warptiling(
int M, int N, int K, float alpha,
const __half* A, const __half* B,
float beta, __half* C, cudaStream_t stream);
torch::Tensor hgemm_warp(torch::Tensor A, torch::Tensor B) {
TORCH_CHECK(A.is_cuda() && B.is_cuda(), "Inputs must be CUDA tensors");
TORCH_CHECK(A.scalar_type() == torch::kHalf, "A must be fp16");
TORCH_CHECK(B.scalar_type() == torch::kHalf, "B must be fp16");
TORCH_CHECK(A.size(1) == B.size(0), "Inner dims must match");
int M = A.size(0), K = A.size(1), N = B.size(1);
auto C = torch::zeros({M, N}, A.options());
cudaStream_t stream = c10::cuda::getCurrentCUDAStream().stream();
launch_hgemm_warptiling(M, N, K, 1.0f,
reinterpret_cast<const __half*>(A.data_ptr<at::Half>()),
reinterpret_cast<const __half*>(B.data_ptr<at::Half>()),
0.0f,
reinterpret_cast<__half*>(C.data_ptr<at::Half>()),
stream);
return C;
}
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
m.def("hgemm_warp", &hgemm_warp,
"FP16 GEMM warp-tiling (siboehm K10, WARPSIZE=64 for BI-V100)");
}

View File

@@ -0,0 +1,18 @@
// xllm_activation_bind.cpp
#include <torch/extension.h>
namespace xllm::kernel::cuda {
void act_and_mul(torch::Tensor out, torch::Tensor input,
const std::string& act_mode);
}
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
m.def("silu_and_mul", [](torch::Tensor out, torch::Tensor input) {
xllm::kernel::cuda::act_and_mul(out, input, "silu");
}, "SiLU and Mul", py::arg("out"), py::arg("input"));
m.def("gelu_and_mul", [](torch::Tensor out, torch::Tensor input) {
xllm::kernel::cuda::act_and_mul(out, input, "gelu");
}, "GELU and Mul", py::arg("out"), py::arg("input"));
m.def("act_and_mul", &xllm::kernel::cuda::act_and_mul,
"Activation and Mul", py::arg("out"), py::arg("input"), py::arg("act_mode"));
}

View File

@@ -0,0 +1,19 @@
// xllm_cache_bind.cpp
#include <torch/extension.h>
namespace xllm::kernel::cuda {
void reshape_paged_cache(torch::Tensor slot_ids, torch::Tensor keys,
torch::Tensor values, torch::Tensor key_cache,
torch::Tensor value_cache);
void block_copy(torch::Tensor key_cache_ptrs, torch::Tensor value_cache_ptrs,
torch::Tensor src_block_indices, torch::Tensor dst_block_indices,
torch::Tensor cum_sum, int64_t numel_per_block,
torch::ScalarType cache_dtype);
}
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
m.def("reshape_paged_cache", &xllm::kernel::cuda::reshape_paged_cache,
"Reshape Paged KV Cache");
m.def("block_copy", &xllm::kernel::cuda::block_copy,
"Block Copy for KV Cache");
}

View File

@@ -0,0 +1,38 @@
// xllm_fused_qknorm_rope_bind.cpp — pybind11 for fused QK-Norm + RoPE kernel
// Source: upstream_ref/xllm/xllm/core/kernels/cuda/fused_qknorm_rope.cu
// Saves 4 kernel launches per layer (separate q_norm, k_norm, q_rope, k_rope)
// Qwen3.5 has 32 full-attention layers → saves 128 kernel launches per forward
#include <torch/extension.h>
namespace xllm::kernel::cuda {
void fused_qk_norm_rope(
torch::Tensor& qkv,
int64_t num_heads_q,
int64_t num_heads_k,
int64_t num_heads_v,
int64_t head_dim,
double eps,
const torch::Tensor& q_weight,
const torch::Tensor& k_weight,
const torch::Tensor& cos_sin_cache,
bool interleaved,
const torch::Tensor& position_ids);
}
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
m.def("fused_qk_norm_rope",
&xllm::kernel::cuda::fused_qk_norm_rope,
"Fused QK-Norm + RoPE (xllm CUDA kernel)",
py::arg("qkv"),
py::arg("num_heads_q"),
py::arg("num_heads_k"),
py::arg("num_heads_v"),
py::arg("head_dim"),
py::arg("eps") = 1e-6,
py::arg("q_weight"),
py::arg("k_weight"),
py::arg("cos_sin_cache"),
py::arg("interleaved") = false,
py::arg("position_ids"));
}

View File

@@ -0,0 +1,34 @@
// xllm_moe_bind.cpp — pybind11 for MoE CUDA kernels
#include <torch/extension.h>
#include <optional>
#include <tuple>
namespace xllm::kernel::cuda {
std::tuple<torch::Tensor, torch::Tensor> moe_fused_topk(
torch::Tensor& gating_output, int64_t topk, bool renormalize,
const std::optional<torch::Tensor>& correction_bias,
const std::string& scoring_func);
std::tuple<torch::Tensor, torch::Tensor, torch::Tensor> moe_compute_index(
const torch::Tensor& expert_id, int64_t num_experts);
torch::Tensor moe_combine_result(
const torch::Tensor& gemm2, const torch::Tensor& reduce_weight,
int64_t N, int32_t topk);
}
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
m.def("moe_fused_topk", &xllm::kernel::cuda::moe_fused_topk,
"MoE fused topk (softmax or sigmoid routing)",
py::arg("gating_output"), py::arg("topk"),
py::arg("renormalize") = true,
py::arg("correction_bias") = py::none(),
py::arg("scoring_func") = "softmax");
m.def("moe_compute_index", &xllm::kernel::cuda::moe_compute_index,
"MoE compute permutation index (histogram + prefix_sum + place)",
py::arg("expert_id"), py::arg("num_experts"));
m.def("moe_combine_result", &xllm::kernel::cuda::moe_combine_result,
"MoE combine (reorder + weighted sum)",
py::arg("gemm2"), py::arg("reduce_weight"),
py::arg("N"), py::arg("topk"));
}

View File

@@ -0,0 +1,24 @@
// xllm_norm_bind.cpp — pybind11 entry point for xllm norm kernels
// Compiled together with norm.cu to produce xllm_norm.so
//
// Exports: rms_norm, fused_add_rms_norm
#include <torch/extension.h>
namespace xllm::kernel::cuda {
void rms_norm(torch::Tensor output, torch::Tensor input,
torch::Tensor weight, double eps);
void fused_add_rms_norm(torch::Tensor& input, torch::Tensor& residual,
torch::Tensor& weight, double epsilon);
} // namespace xllm::kernel::cuda
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
m.def("rms_norm", &xllm::kernel::cuda::rms_norm,
"RMS Norm (xllm CUDA kernel)",
py::arg("output"), py::arg("input"),
py::arg("weight"), py::arg("eps") = 1e-6);
m.def("fused_add_rms_norm", &xllm::kernel::cuda::fused_add_rms_norm,
"Fused Add + RMS Norm (xllm CUDA kernel)",
py::arg("input"), py::arg("residual"),
py::arg("weight"), py::arg("epsilon") = 1e-6);
}

View File

@@ -0,0 +1,17 @@
// xllm_rope_bind.cpp
#include <torch/extension.h>
#include <optional>
namespace xllm::kernel::cuda {
void rotary_embedding(torch::Tensor& positions, torch::Tensor& query,
std::optional<torch::Tensor> key,
torch::Tensor& cos_sin_cache, bool is_neox);
}
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
m.def("rotary_embedding", &xllm::kernel::cuda::rotary_embedding,
"Rotary Position Embedding (xllm CUDA kernel)",
py::arg("positions"), py::arg("query"),
py::arg("key"), py::arg("cos_sin_cache"),
py::arg("is_neox") = true);
}

View File

@@ -0,0 +1,210 @@
/* Copyright 2025-2026 The xLLM Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <ATen/cuda/CUDAContext.h>
#include <c10/cuda/CUDAException.h>
#include <c10/cuda/CUDAGuard.h>
#include <c10/cuda/CUDAStream.h>
#include <cuda_runtime.h>
#include <torch/extension.h>
#include <cstdint>
#include <type_traits>
#include "device_utils.cuh"
namespace xllm::kernel::cuda {
namespace {
template <typename scalar_t>
struct VecType;
template <>
struct VecType<c10::Half> {
using type = uint4;
static constexpr int32_t vec_width = 8;
};
template <>
struct VecType<c10::BFloat16> {
using type = uint4;
static constexpr int32_t vec_width = 8;
};
template <>
struct VecType<float> {
using type = float4;
static constexpr int32_t vec_width = 4;
};
DEVICE_INLINE int32_t find_group_idx(const int32_t* __restrict__ cum_sum,
const int32_t num_groups,
const int32_t dst_idx) {
int32_t left = 0;
int32_t right = num_groups - 1;
while (left < right) {
const int32_t mid = left + ((right - left) >> 1);
const bool move_left = dst_idx < cum_sum[mid];
right = move_left ? mid : right;
left = move_left ? left : mid + 1;
}
return left;
}
template <typename scalar_t, bool kVectorized>
__global__ void block_copy_kernel(const int64_t* __restrict__ key_cache_ptrs,
const int64_t* __restrict__ value_cache_ptrs,
const int32_t* __restrict__ src_block_indices,
const int32_t* __restrict__ dst_block_indices,
const int32_t* __restrict__ cum_sum,
const int32_t num_groups,
const int64_t numel_per_block) {
const int64_t layer_idx = static_cast<int64_t>(blockIdx.x);
const int32_t dst_linear_idx = static_cast<int32_t>(blockIdx.y);
const int64_t tile_idx = static_cast<int64_t>(blockIdx.z);
scalar_t* __restrict__ key_cache = reinterpret_cast<scalar_t*>(
static_cast<uintptr_t>(key_cache_ptrs[layer_idx]));
scalar_t* __restrict__ value_cache = reinterpret_cast<scalar_t*>(
static_cast<uintptr_t>(value_cache_ptrs[layer_idx]));
const int32_t group_idx = find_group_idx(cum_sum, num_groups, dst_linear_idx);
const int32_t src_block = src_block_indices[group_idx];
const int32_t dst_block = dst_block_indices[dst_linear_idx];
const int64_t src_offset = static_cast<int64_t>(src_block) * numel_per_block;
const int64_t dst_offset = static_cast<int64_t>(dst_block) * numel_per_block;
if constexpr (kVectorized) {
using VecTypeT = typename VecType<scalar_t>::type;
constexpr int32_t kVecWidth = VecType<scalar_t>::vec_width;
const int64_t num_vecs_per_block = numel_per_block / kVecWidth;
const int64_t vec_idx = tile_idx * static_cast<int64_t>(blockDim.x) +
static_cast<int64_t>(threadIdx.x);
if (vec_idx >= num_vecs_per_block) {
return;
}
const int64_t elem_offset = vec_idx * kVecWidth;
const auto* key_src_vec =
reinterpret_cast<const VecTypeT*>(key_cache + src_offset + elem_offset);
const auto* value_src_vec = reinterpret_cast<const VecTypeT*>(
value_cache + src_offset + elem_offset);
auto* key_dst_vec =
reinterpret_cast<VecTypeT*>(key_cache + dst_offset + elem_offset);
auto* value_dst_vec =
reinterpret_cast<VecTypeT*>(value_cache + dst_offset + elem_offset);
*key_dst_vec = *key_src_vec;
*value_dst_vec = *value_src_vec;
} else {
const int64_t elem_idx = tile_idx * static_cast<int64_t>(blockDim.x) +
static_cast<int64_t>(threadIdx.x);
if (elem_idx >= numel_per_block) {
return;
}
key_cache[dst_offset + elem_idx] = key_cache[src_offset + elem_idx];
value_cache[dst_offset + elem_idx] = value_cache[src_offset + elem_idx];
}
}
} // namespace
void block_copy(torch::Tensor key_cache_ptrs,
torch::Tensor value_cache_ptrs,
torch::Tensor src_block_indices,
torch::Tensor dst_block_indices,
torch::Tensor cum_sum,
int64_t numel_per_block,
torch::ScalarType cache_dtype) {
if (src_block_indices.numel() == 0) {
return;
}
TORCH_CHECK(key_cache_ptrs.is_cuda());
TORCH_CHECK(value_cache_ptrs.is_cuda());
TORCH_CHECK(src_block_indices.is_cuda());
TORCH_CHECK(dst_block_indices.is_cuda());
TORCH_CHECK(cum_sum.is_cuda());
TORCH_CHECK(key_cache_ptrs.scalar_type() == torch::kInt64);
TORCH_CHECK(value_cache_ptrs.scalar_type() == torch::kInt64);
TORCH_CHECK(src_block_indices.scalar_type() == torch::kInt32);
TORCH_CHECK(dst_block_indices.scalar_type() == torch::kInt32);
TORCH_CHECK(cum_sum.scalar_type() == torch::kInt32);
TORCH_CHECK(key_cache_ptrs.dim() == 1);
TORCH_CHECK(value_cache_ptrs.dim() == 1);
TORCH_CHECK(src_block_indices.dim() == 1);
TORCH_CHECK(dst_block_indices.dim() == 1);
TORCH_CHECK(cum_sum.dim() == 1);
TORCH_CHECK(key_cache_ptrs.is_contiguous());
TORCH_CHECK(value_cache_ptrs.is_contiguous());
TORCH_CHECK(src_block_indices.is_contiguous());
TORCH_CHECK(dst_block_indices.is_contiguous());
TORCH_CHECK(cum_sum.is_contiguous());
TORCH_CHECK(key_cache_ptrs.size(0) == value_cache_ptrs.size(0));
TORCH_CHECK(src_block_indices.size(0) == cum_sum.size(0));
TORCH_CHECK(numel_per_block > 0);
const at::cuda::OptionalCUDAGuard device_guard(key_cache_ptrs.device());
constexpr int32_t kThreadsPerBlock = 256;
const int32_t num_layers = static_cast<int32_t>(key_cache_ptrs.size(0));
const int32_t num_groups = static_cast<int32_t>(src_block_indices.size(0));
const int32_t num_dst_blocks =
static_cast<int32_t>(dst_block_indices.size(0));
const cudaStream_t stream =
c10::cuda::getCurrentCUDAStream(key_cache_ptrs.get_device());
DISPATCH_FLOATING_TYPES(cache_dtype, "block_copy_kernel", [&] {
constexpr bool kHasVecType = std::is_same_v<scalar_t, float> ||
std::is_same_v<scalar_t, c10::Half> ||
std::is_same_v<scalar_t, c10::BFloat16>;
if constexpr (kHasVecType) {
constexpr int32_t kVecWidth = VecType<scalar_t>::vec_width;
if (numel_per_block % kVecWidth == 0) {
const int64_t tiles_per_block =
ceil_div<int64_t>(numel_per_block / kVecWidth, kThreadsPerBlock);
const dim3 grid(num_layers, num_dst_blocks, tiles_per_block);
block_copy_kernel<scalar_t, true>
<<<grid, kThreadsPerBlock, 0, stream>>>(
key_cache_ptrs.data_ptr<int64_t>(),
value_cache_ptrs.data_ptr<int64_t>(),
src_block_indices.data_ptr<int32_t>(),
dst_block_indices.data_ptr<int32_t>(),
cum_sum.data_ptr<int32_t>(),
num_groups,
numel_per_block);
C10_CUDA_KERNEL_LAUNCH_CHECK();
return;
}
}
const int64_t tiles_per_block =
ceil_div<int64_t>(numel_per_block, kThreadsPerBlock);
const dim3 grid(num_layers, num_dst_blocks, tiles_per_block);
block_copy_kernel<scalar_t, false><<<grid, kThreadsPerBlock, 0, stream>>>(
key_cache_ptrs.data_ptr<int64_t>(),
value_cache_ptrs.data_ptr<int64_t>(),
src_block_indices.data_ptr<int32_t>(),
dst_block_indices.data_ptr<int32_t>(),
cum_sum.data_ptr<int32_t>(),
num_groups,
numel_per_block);
C10_CUDA_KERNEL_LAUNCH_CHECK();
});
}
} // namespace xllm::kernel::cuda

View File

@@ -0,0 +1,67 @@
/*
* corex_batched_gemm_kernel.cu — FP16 Cu10 TensorOp batched GEMM
*
* Uses cutlass::gemm::device::GemmBatched with:
* - OpClassTensorOp (TCU, not SIMT)
* - arch::Cu10 (BI-V100)
* - float accumulation (FP32, not FP16)
*
* Source: ex_engine/xllm_kernels/cuda/moe_cutlass_batched.cu (verified 2.462ms)
*/
#include <cuda_runtime.h>
#include <cuda_fp16.h>
#include "cutlass/cutlass.h"
#include "cutlass/numeric_types.h"
#include "cutlass/layout/matrix.h"
#include "cutlass/gemm/device/gemm_batched.h"
cudaError_t cutlass_batched_hgemm(
int m, int n, int k,
__half const *A, int lda, long long int batch_stride_A,
__half const *B, int ldb, long long int batch_stride_B,
__half *C, int ldc, long long int batch_stride_C,
int batch_count)
{
using Gemm = cutlass::gemm::device::GemmBatched<
cutlass::half_t, // ElementA
cutlass::layout::RowMajor, // LayoutA
cutlass::half_t, // ElementB
cutlass::layout::RowMajor, // LayoutB
cutlass::half_t, // ElementC
cutlass::layout::RowMajor, // LayoutC
float, // ElementAccumulator — FP32!
cutlass::arch::OpClassTensorOp, // OperatorClass — TCU!
cutlass::arch::Cu10 // ArchTag — BI-V100!
// Defaults from DefaultGemmConfiguration<OpClassTensorOp, Cu10, half, half, half, float>:
// ThreadblockShape = <128, 128, 32>
// WarpShape = <32, 32, 32>
// InstructionShape = <16, 16, 16>
// Stages = 2
>;
float alpha = 1.0f;
float beta = 0.0f;
Gemm gemm_op;
cutlass::Status status = gemm_op({
{m, n, k},
{reinterpret_cast<cutlass::half_t const *>(A), lda},
batch_stride_A,
{reinterpret_cast<cutlass::half_t const *>(B), ldb},
batch_stride_B,
{reinterpret_cast<cutlass::half_t const *>(C), ldc},
batch_stride_C,
{reinterpret_cast<cutlass::half_t *>(C), ldc},
batch_stride_C,
{alpha, beta},
batch_count
});
if (status != cutlass::Status::kSuccess) {
return cudaErrorUnknown;
}
return cudaSuccess;
}

View File

@@ -0,0 +1,463 @@
/* Copyright 2026 The xLLM Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <ATen/cuda/CUDAContext.h>
#include <c10/cuda/CUDAGuard.h>
#include <cuda_runtime.h>
#include <torch/cuda.h>
#include <cmath>
#include <type_traits>
#include "cuda_ops_api.h"
#include "type_convert.cuh"
#include "utils.h"
using at::device_of;
// Borrowed from:
// https://github.com/vllm-project/vllm/blob/022f3cea5327cc720a325c50931e1edcfdf2d32b/csrc/fused_qknorm_rope_kernel.cu
constexpr uint32_t kFinalMask = 0xffffffffu;
namespace {
using namespace xllm::kernel::cuda;
template <typename T, int num>
struct packed_as;
// Specialization for packed_as used in this kernel.
template <>
struct packed_as<uint, 1> {
using type = uint;
};
template <>
struct packed_as<uint, 2> {
using type = uint2;
};
template <>
struct packed_as<uint, 4> {
using type = uint4;
};
template <typename T>
__inline__ __device__ T warp_reduce_sum(T val) {
#pragma unroll
for (int mask = 16; mask > 0; mask >>= 1)
val += __shfl_xor_sync(kFinalMask, val, mask, 32);
return val;
}
template <typename T>
inline __device__ __host__ T div_up(T m, T n) {
return (m + n - 1) / n;
}
// Perform per-head QK Norm and RoPE in a single kernel.
// scalar_t_in: data type of QKV and RMSNorm weights
// scalar_t_cache: data type of cos/sin cache
// head_dim: the dimension of each head
// interleave: interleave=!is_neox.
template <typename scalar_t_in,
typename scalar_t_cache,
int head_dim,
bool interleave>
__global__ void fused_qknorm_rope_kernel(
void* qkv_void, // Combined QKV tensor
int const num_heads_q, // Number of query heads
int const num_heads_k, // Number of key heads
int const num_heads_v, // Number of value heads
float const eps, // Epsilon for RMS normalization
void const* q_weight_void, // RMSNorm weights for query
void const* k_weight_void, // RMSNorm weights for key
void const* cos_sin_cache_void, // Pre-computed cos/sin cache
int64_t const* position_ids, // Position IDs for RoPE
int const num_tokens, // Number of tokens
int const rotary_dim // Dimension for RoPE
) {
#if !defined(__CUDA_ARCH__) || __CUDA_ARCH__ < 800
if constexpr ((std::is_same_v<scalar_t_in, c10::BFloat16>) ||
std::is_same_v<scalar_t_cache, c10::BFloat16>) {
return;
} else {
#endif
using Converter = _typeConvert<scalar_t_in>;
static_assert(Converter::exists,
"Input QKV data type is not supported for this CUDA "
"architecture or toolkit version.");
using T_in = typename Converter::hip_type;
using T2_in = typename Converter::packed_hip_type;
using CacheConverter = _typeConvert<scalar_t_cache>;
static_assert(CacheConverter::exists,
"Cache data type is not supported for this CUDA architecture "
"or toolkit version.");
using T_cache = typename CacheConverter::hip_type;
T_in* qkv = reinterpret_cast<T_in*>(qkv_void);
T_in const* q_weight = reinterpret_cast<T_in const*>(q_weight_void);
T_in const* k_weight = reinterpret_cast<T_in const*>(k_weight_void);
T_cache const* cos_sin_cache =
reinterpret_cast<T_cache const*>(cos_sin_cache_void);
int const warpsPerBlock = blockDim.x / 32;
int const warpId = threadIdx.x / 32;
int const laneId = threadIdx.x % 32;
// Calculate global warp index to determine which head/token this warp
// processes
int const globalWarpIdx = blockIdx.x * warpsPerBlock + warpId;
// Total number of attention heads (Q and K)
int const total_qk_heads = num_heads_q + num_heads_k;
// Determine which token and head type (Q or K) this warp processes
int const tokenIdx = globalWarpIdx / total_qk_heads;
int const localHeadIdx = globalWarpIdx % total_qk_heads;
// Skip if this warp is assigned beyond the number of tokens
if (tokenIdx >= num_tokens) return;
bool const isQ = localHeadIdx < num_heads_q;
int const headIdx = isQ ? localHeadIdx : localHeadIdx - num_heads_q;
int const num_heads = num_heads_q + num_heads_k + num_heads_v;
static_assert(head_dim % (32 * 2) == 0,
"head_dim must be divisible by 64 (each warp processes one "
"head, and each thread gets even number of "
"elements)");
constexpr int numElemsPerThread = head_dim / 32;
float elements[numElemsPerThread];
constexpr int elemSizeBytes = numElemsPerThread * sizeof(__nv_bfloat16);
static_assert(elemSizeBytes % 4 == 0,
"numSizeBytes must be a multiple of 4");
constexpr int vecSize =
elemSizeBytes /
4; // Use packed_as<uint, vecSize> to perform loading/saving.
using vec_T = typename packed_as<uint, vecSize>::type;
int offsetWarp; // Offset for the warp
if (isQ) {
// Q segment: token offset + head offset within Q segment
offsetWarp = tokenIdx * num_heads * head_dim + headIdx * head_dim;
} else {
// K segment: token offset + entire Q segment + head offset within K
// segment
offsetWarp = tokenIdx * num_heads * head_dim + num_heads_q * head_dim +
headIdx * head_dim;
}
int offsetThread = offsetWarp + laneId * numElemsPerThread;
// Sum of squares for RMSNorm
float sumOfSquares = 0.0f;
// Load.
{
vec_T vec = *reinterpret_cast<vec_T const*>(&qkv[offsetThread]);
constexpr int num_packed_elems = elemSizeBytes / sizeof(T2_in);
#pragma unroll
for (int i = 0; i < num_packed_elems; i++) {
// Interpret the generic vector chunk as the specific packed type
T2_in packed_val = *(reinterpret_cast<T2_in*>(&vec) + i);
// Convert to float2 for computation
float2 vals = Converter::convert(packed_val);
sumOfSquares += vals.x * vals.x;
sumOfSquares += vals.y * vals.y;
elements[2 * i] = vals.x;
elements[2 * i + 1] = vals.y;
}
}
// Reduce sum across warp using the utility function
sumOfSquares = warp_reduce_sum(sumOfSquares);
// Compute RMS normalization factor
float rms_rcp = rsqrtf(sumOfSquares / static_cast<float>(head_dim) + eps);
// Normalize elements
#pragma unroll
for (int i = 0; i < numElemsPerThread; i++) {
int dim = laneId * numElemsPerThread + i;
float weight = isQ ? Converter::convert(q_weight[dim])
: Converter::convert(k_weight[dim]);
elements[i] *= rms_rcp * weight;
}
// Apply RoPE to normalized elements
float elements2[numElemsPerThread]; // Additional buffer required for RoPE.
int64_t pos_id = position_ids[tokenIdx];
// Calculate cache pointer for this position - similar to
// pos_encoding_kernels.cu
T_cache const* cache_ptr = cos_sin_cache + pos_id * rotary_dim;
int const embed_dim = rotary_dim / 2;
T_cache const* cos_ptr = cache_ptr;
T_cache const* sin_ptr = cache_ptr + embed_dim;
int const rotary_lanes = rotary_dim / numElemsPerThread; // rotary range
if (laneId < rotary_lanes) {
if constexpr (interleave) {
// Perform interleaving. Use pre-computed cos/sin values.
#pragma unroll
for (int i = 0; i < numElemsPerThread / 2; ++i) {
int const idx0 = 2 * i;
int const idx1 = 2 * i + 1;
// Global dimension index in the head
int const dim_idx = laneId * numElemsPerThread + idx0;
float const val0 = elements[idx0];
float const val1 = elements[idx1];
int const half_dim = dim_idx / 2;
float const cos_val =
CacheConverter::convert(__ldg(cos_ptr + half_dim));
float const sin_val =
CacheConverter::convert(__ldg(sin_ptr + half_dim));
elements[idx0] = val0 * cos_val - val1 * sin_val;
elements[idx1] = val0 * sin_val + val1 * cos_val;
}
} else {
// Before data exchange with in warp, we need to sync.
__syncwarp();
int pairOffset = (rotary_dim / 2) / numElemsPerThread;
// Get the data from the other half of the warp. Use pre-computed
// cos/sin values.
#pragma unroll
for (int i = 0; i < numElemsPerThread; i++) {
elements2[i] = __shfl_xor_sync(kFinalMask, elements[i], pairOffset);
if (laneId < pairOffset) {
elements2[i] = -elements2[i];
}
int dim_idx = laneId * numElemsPerThread + i;
dim_idx = (dim_idx * 2) % rotary_dim;
int half_dim = dim_idx / 2;
float cos_val = CacheConverter::convert(__ldg(cos_ptr + half_dim));
float sin_val = CacheConverter::convert(__ldg(sin_ptr + half_dim));
elements[i] = elements[i] * cos_val + elements2[i] * sin_val;
}
// __shfl_xor_sync does not provide memfence. Need to sync again.
__syncwarp();
}
}
// Store.
{
vec_T vec;
constexpr int num_packed_elems = elemSizeBytes / sizeof(T2_in);
#pragma unroll
for (int i = 0; i < num_packed_elems; i++) {
// Convert from float2 back to the specific packed type
float2 vals = {elements[2 * i], elements[2 * i + 1]};
T2_in packed_val = Converter::convert(vals);
// Place it into the generic vector
*(reinterpret_cast<T2_in*>(&vec) + i) = packed_val;
}
*reinterpret_cast<vec_T*>(&qkv[offsetThread]) = vec;
}
#if !defined(__CUDA_ARCH__) || __CUDA_ARCH__ < 800
}
#endif
}
// Borrowed from
// https://github.com/flashinfer-ai/flashinfer/blob/8125d079a43e9a0ba463a4ed1b639cefd084cec9/include/flashinfer/pos_enc.cuh#L568
#define DISPATCH_INTERLEAVE(interleave, INTERLEAVE, ...) \
if (interleave) { \
const bool INTERLEAVE = true; \
__VA_ARGS__ \
} else { \
const bool INTERLEAVE = false; \
__VA_ARGS__ \
}
template <typename scalar_t_in, typename scalar_t_cache>
void launch_fused_qknorm_rope(void* qkv,
int const num_tokens,
int const num_heads_q,
int const num_heads_k,
int const num_heads_v,
int const head_dim,
int const rotary_dim,
float const eps,
void const* q_weight,
void const* k_weight,
void const* cos_sin_cache,
bool const interleave,
int64_t const* position_ids,
cudaStream_t stream) {
constexpr int blockSize = 256;
int const warpsPerBlock = blockSize / 32;
int const totalQKHeads = num_heads_q + num_heads_k;
int const totalWarps = num_tokens * totalQKHeads;
int const gridSize = div_up(totalWarps, warpsPerBlock);
dim3 gridDim(gridSize);
dim3 blockDim(blockSize);
switch (head_dim) {
case 64:
DISPATCH_INTERLEAVE(interleave, INTERLEAVE, {
fused_qknorm_rope_kernel<scalar_t_in, scalar_t_cache, 64, INTERLEAVE>
<<<gridDim, blockDim, 0, stream>>>(qkv,
num_heads_q,
num_heads_k,
num_heads_v,
eps,
q_weight,
k_weight,
cos_sin_cache,
position_ids,
num_tokens,
rotary_dim);
});
break;
case 128:
DISPATCH_INTERLEAVE(interleave, INTERLEAVE, {
fused_qknorm_rope_kernel<scalar_t_in, scalar_t_cache, 128, INTERLEAVE>
<<<gridDim, blockDim, 0, stream>>>(qkv,
num_heads_q,
num_heads_k,
num_heads_v,
eps,
q_weight,
k_weight,
cos_sin_cache,
position_ids,
num_tokens,
rotary_dim);
});
break;
case 256:
DISPATCH_INTERLEAVE(interleave, INTERLEAVE, {
fused_qknorm_rope_kernel<scalar_t_in, scalar_t_cache, 256, INTERLEAVE>
<<<gridDim, blockDim, 0, stream>>>(qkv,
num_heads_q,
num_heads_k,
num_heads_v,
eps,
q_weight,
k_weight,
cos_sin_cache,
position_ids,
num_tokens,
rotary_dim);
});
break;
default:
CHECK(false) << "Unsupported head dimension for fusedQKNormRope: "
<< head_dim;
}
}
} // namespace
namespace xllm::kernel::cuda {
void fused_qk_norm_rope(
torch::Tensor& qkv, // Combined QKV tensor [num_tokens,
// (num_heads_q+num_heads_k+num_heads_v)*head_dim]
int64_t num_heads_q, // Number of query heads
int64_t num_heads_k, // Number of key heads
int64_t num_heads_v, // Number of value heads
int64_t head_dim, // Dimension per head
double eps, // Epsilon for RMS normalization
const torch::Tensor& q_weight, // RMSNorm weights for query [head_dim]
const torch::Tensor& k_weight, // RMSNorm weights for key [head_dim]
const torch::Tensor&
cos_sin_cache, // Cos/sin cache [max_position, rotary_dim]
bool interleaved, // Whether RoPE is applied in interleaved style
const torch::Tensor& position_ids // Position IDs for RoPE [num_tokens]
) {
// Input validation
CHECK(qkv.is_cuda()) << "qkv must be a CUDA tensor";
CHECK(qkv.is_contiguous()) << "qkv must be contiguous";
CHECK(position_ids.is_cuda()) << "position_ids must be a CUDA tensor";
CHECK(position_ids.is_contiguous()) << "position_ids must be contiguous";
CHECK(q_weight.is_cuda()) << "q_weight must be a CUDA tensor";
CHECK(q_weight.is_contiguous()) << "q_weight must be contiguous";
CHECK(k_weight.is_cuda()) << "k_weight must be a CUDA tensor";
CHECK(k_weight.is_contiguous()) << "k_weight must be contiguous";
CHECK(cos_sin_cache.is_cuda()) << "cos_sin_cache must be a CUDA tensor";
CHECK(cos_sin_cache.is_contiguous()) << "cos_sin_cache must be contiguous";
CHECK(position_ids.scalar_type() == torch::kInt64)
<< "position_ids dtype is " << position_ids.scalar_type()
<< ", while Int64 is expected";
CHECK(qkv.dim() == 2) << "QKV tensor must be 2D: [num_tokens, "
<< "(num_heads_q+num_heads_k+num_heads_v)*head_dim]";
CHECK(position_ids.dim() == 1) << "Position IDs must be 1D: [num_tokens]";
CHECK(q_weight.dim() == 1) << "Query weights must be 1D: [head_dim]";
CHECK(k_weight.dim() == 1) << "Key weights must be 1D: [head_dim]";
CHECK(cos_sin_cache.dim() == 2)
<< "Cos/sin cache must be 2D: [max_position, rotary_dim]";
CHECK(q_weight.size(0) == head_dim)
<< "Query weights size must match head dimension";
CHECK(k_weight.size(0) == head_dim)
<< "Key weights size must match head dimension";
CHECK(cos_sin_cache.size(1) % 2 == 0) << "rotary_dim must be even";
CHECK(cos_sin_cache.size(1) <= head_dim)
<< "rotary_dim must be less than or equal to head_dim";
CHECK(qkv.scalar_type() == q_weight.scalar_type() &&
qkv.scalar_type() == k_weight.scalar_type())
<< "qkv, q_weight and k_weight must have the same dtype";
int64_t num_tokens = qkv.size(0);
CHECK(position_ids.size(0) == num_tokens)
<< "Number of tokens in position_ids must match QKV";
int64_t total_heads = num_heads_q + num_heads_k + num_heads_v;
CHECK(qkv.size(1) == total_heads * head_dim)
<< "QKV tensor size must match total number of heads and head dimension";
const at::cuda::OptionalCUDAGuard device_guard(device_of(qkv));
const cudaStream_t stream = at::cuda::getCurrentCUDAStream();
DISPATCH_HALF_TYPES(qkv.scalar_type(), "fused_qk_norm_rope_kernel", [&] {
using qkv_scalar_t = scalar_t;
DISPATCH_FLOATING_TYPES(
cos_sin_cache.scalar_type(), "fused_qk_norm_rope_kernel", [&] {
using cache_scalar_t = scalar_t;
launch_fused_qknorm_rope<qkv_scalar_t, cache_scalar_t>(
qkv.data_ptr(),
static_cast<int>(num_tokens),
static_cast<int>(num_heads_q),
static_cast<int>(num_heads_k),
static_cast<int>(num_heads_v),
static_cast<int>(head_dim),
static_cast<int>(cos_sin_cache.size(1)),
static_cast<float>(eps),
q_weight.data_ptr(),
k_weight.data_ptr(),
cos_sin_cache.data_ptr(),
interleaved,
reinterpret_cast<int64_t const*>(position_ids.data_ptr()),
stream);
});
});
}
} // namespace xllm::kernel::cuda

View File

@@ -0,0 +1,108 @@
/*
* Copyright (c) 2022-2025, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// refers to
// https://github.com/NVIDIA/TensorRT-LLM/blob/main/cpp/include/tensorrt_llm/kernels/archCondition.h
#pragma once
namespace xllm::kernel::cuda {
namespace detail {
#ifdef __CUDA_ARCH__
// __CUDA_ARCH_SPECIFIC__ is only available starting from CUDA 12.9
#if (__CUDACC_VER_MAJOR__ > 12 || \
(__CUDACC_VER_MAJOR__ == 12 && __CUDACC_VER_MINOR__ >= 9))
#define HAS_CUDA_SPECIFIC_MACRO 1
#if __CUDA_ARCH__ >= 900
#if !defined(__CUDA_ARCH_SPECIFIC__) && !defined(__CUDA_ARCH_FAMILY_SPECIFIC__)
#error \
"Compiling for SM90 or newer architectures must use Arch specific or Arch Family specific target"
#endif
#endif
#else
#define HAS_CUDA_SPECIFIC_MACRO 0
#endif
// For CUDA < 12.9, we assume that sm90 or newer architectures are always built
// with arch specific.
#if defined(__CUDA_ARCH_SPECIFIC__) || \
(!HAS_CUDA_SPECIFIC_MACRO && __CUDA_ARCH__ >= 900)
static constexpr bool isArchSpecific = true;
#else
static constexpr bool isArchSpecific = false;
#endif
struct arch_info {
static constexpr bool mIsDevice = true;
static constexpr bool mArchSpecific = isArchSpecific;
static constexpr int mMajor = __CUDA_ARCH__ / 100;
static constexpr int mMinor = __CUDA_ARCH__ / 10 % 10;
static constexpr int mArch = __CUDA_ARCH__ / 10;
};
#else
struct arch_info {
static constexpr bool mIsDevice = false;
static constexpr bool mArchSpecific = false;
static constexpr int mMajor = 0;
static constexpr int mMinor = 0;
static constexpr int mArch = 0;
};
#endif
} // namespace detail
namespace arch {
struct is_device : std::bool_constant<detail::arch_info::mIsDevice> {};
struct is_arch_specific : std::bool_constant<detail::arch_info::mArchSpecific> {
};
template <int Arch>
struct is_match
: std::bool_constant<is_device::value && detail::arch_info::mArch == Arch> {
};
template <int Major>
struct is_major : std::bool_constant<is_device::value &&
detail::arch_info::mMajor == Major> {};
template <int Arch>
struct is_compatible : std::bool_constant<is_major<Arch>::value &&
detail::arch_info::mArch >= Arch> {};
inline constexpr bool is_device_v = is_device::value;
inline constexpr bool is_arch_specific_v = is_arch_specific::value;
template <int Arch>
inline constexpr bool is_match_v = is_match<Arch>::value;
template <int Major>
inline constexpr bool is_major_v = is_major<Major>::value;
template <int Arch>
inline constexpr bool is_compatible_v = is_compatible<Arch>::value;
} // namespace arch
} // namespace xllm::kernel::cuda

View File

@@ -0,0 +1,37 @@
// corex_compat_utils.h — Lightweight replacement for xllm's utils.h
// Removes glog/tvm dependencies for BI-V100 corex compilation
// Provides CHECK macro via TORCH_CHECK and DISPATCH macros from device_utils.cuh
#pragma once
#include <torch/torch.h>
#include <c10/cuda/CUDAGuard.h>
// Replace glog CHECK with TORCH_CHECK
#ifndef CHECK
#define CHECK(cond) TORCH_CHECK(cond)
#endif
#ifndef CHECK_EQ
#define CHECK_EQ(a, b) TORCH_CHECK((a) == (b))
#endif
#ifndef CHECK_GE
#define CHECK_GE(a, b) TORCH_CHECK((a) >= (b))
#endif
// Include device_utils for DISPATCH_HALF_TYPES etc
#include "device_utils.cuh"
// ffi namespace stub (some headers reference it)
namespace ffi {
template <typename T>
using Array = std::vector<T>;
}
// HOST_DEVICE_INLINE
#if defined(__CUDACC__) || defined(_NVHPC_CUDA)
#define HOST_DEVICE_INLINE __host__ __device__ __forceinline__
#else
#define HOST_DEVICE_INLINE inline
#endif

View File

@@ -0,0 +1,306 @@
/* Copyright 2025-2026 The xLLM Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#pragma once
#include <ATen/DynamicLibrary.h>
#include <ATen/core/dispatch/Dispatcher.h>
#include <glog/logging.h>
#include <optional>
#include <tuple>
#include <vector>
#include "utils.h"
namespace xllm::kernel::cuda {
// TODO: add head_size parameter
void rotary_embedding(torch::Tensor& positions,
torch::Tensor& query,
std::optional<torch::Tensor> key,
torch::Tensor& cos_sin_cache,
// int64_t head_size,
bool is_neox);
// act_mode only support silu, gelu, gelu_tanh
void act_and_mul(torch::Tensor out,
torch::Tensor input,
const std::string& act_mode);
void reshape_paged_cache(
torch::Tensor slot_ids, // [n_tokens]
torch::Tensor keys, // [n_tokens, n_kv_heads, head_dim]
torch::Tensor values, // [n_tokens, n_kv_heads, head_dim]
torch::Tensor key_cache, // [n_blocks, block_size, n_heads, head_dim]
torch::Tensor value_cache);
void block_copy(torch::Tensor key_cache_ptrs,
torch::Tensor value_cache_ptrs,
torch::Tensor src_block_indices,
torch::Tensor dst_block_indices,
torch::Tensor cum_sum,
int64_t numel_per_block,
torch::ScalarType cache_dtype);
#if !defined(USE_DCU)
void batch_prefill(const std::string& uri,
ffi::Array<int64_t> plan_info,
torch::Tensor float_workspace_buffer,
torch::Tensor int_workspace_buffer,
torch::Tensor page_locked_int_workspace_buffer,
torch::Tensor query,
torch::Tensor key,
torch::Tensor value,
torch::Tensor q_cu_seq_lens,
torch::Tensor kv_cu_seq_lens,
int64_t window_left,
double sm_scale,
torch::Tensor output,
std::optional<torch::Tensor>& output_lse,
const std::optional<torch::Tensor>& mask = std::nullopt);
// Wrapper function for batch_prefill that conditionally uses AttentionRunner
// for piecewise CUDA Graph capture
void batch_prefill_with_optional_piecewise_capture(
const std::string& uri,
ffi::Array<int64_t> plan_info,
torch::Tensor float_workspace_buffer,
torch::Tensor int_workspace_buffer,
torch::Tensor page_locked_int_workspace_buffer,
torch::Tensor query,
torch::Tensor key,
torch::Tensor value,
torch::Tensor q_cu_seq_lens,
torch::Tensor kv_cu_seq_lens,
int64_t window_left,
double sm_scale,
torch::Tensor output,
std::optional<torch::Tensor>& output_lse);
void batch_prefill_non_causal(
const std::string& uri,
ffi::Array<int64_t> plan_info,
torch::Tensor float_workspace_buffer,
torch::Tensor int_workspace_buffer,
torch::Tensor page_locked_int_workspace_buffer,
torch::Tensor query,
torch::Tensor key,
torch::Tensor value,
torch::Tensor q_cu_seq_lens,
torch::Tensor kv_cu_seq_lens,
int64_t window_left,
double sm_scale,
torch::Tensor output,
std::optional<torch::Tensor>& output_lse,
const std::optional<torch::Tensor>& mask = std::nullopt);
void batch_chunked_prefill(
const std::string& uri,
ffi::Array<int64_t> plan_info,
torch::Tensor float_workspace_buffer,
torch::Tensor int_workspace_buffer,
torch::Tensor page_locked_int_workspace_buffer,
torch::Tensor query,
torch::Tensor k_cache,
torch::Tensor v_cache,
torch::Tensor paged_kv_indptr,
torch::Tensor paged_kv_indices,
torch::Tensor paged_kv_last_page_len,
int64_t window_left,
double sm_scale,
torch::Tensor output,
std::optional<torch::Tensor>& output_lse,
std::optional<torch::Tensor> qo_indptr = std::nullopt,
bool causal = true);
void batch_decode(const std::string& uri,
ffi::Array<int64_t> plan_info,
torch::Tensor float_workspace_buffer,
torch::Tensor int_workspace_buffer,
torch::Tensor page_locked_int_workspace_buffer,
torch::Tensor query,
torch::Tensor k_cache,
torch::Tensor v_cache,
torch::Tensor paged_kv_indptr,
torch::Tensor paged_kv_indices,
torch::Tensor paged_kv_last_page_len,
int64_t window_left,
double sm_scale,
torch::Tensor output,
std::optional<torch::Tensor>& output_lse,
bool use_tensor_core,
std::optional<torch::Tensor> qo_indptr = std::nullopt);
#endif // !defined(USE_DCU)
void rms_norm(torch::Tensor output,
torch::Tensor input,
torch::Tensor weight,
double eps);
void fused_add_rms_norm(torch::Tensor& input, // [..., hidden_size]
torch::Tensor& residual, // [..., hidden_size]
torch::Tensor& weight, // [hidden_size]
double epsilon);
torch::Tensor matmul(torch::Tensor a,
torch::Tensor b,
std::optional<torch::Tensor> bias);
void cutlass_scaled_mm(torch::Tensor& c,
torch::Tensor const& a,
torch::Tensor const& b,
torch::Tensor const& a_scales,
torch::Tensor const& b_scales,
std::optional<torch::Tensor> const& bias);
// Static scaled FP8 quantization
// Quantizes input tensor to FP8 using a pre-computed scale factor
void static_scaled_fp8_quant(torch::Tensor& out, // [..., d]
torch::Tensor const& input, // [..., d]
torch::Tensor const& scale); // [1]
// FP8 scaled quantize: quantizes input tensor to FP8 e4m3 format
// Returns: (quantized_output, scale)
std::tuple<torch::Tensor, torch::Tensor> fp8_scaled_quantize(
const torch::Tensor& input,
const std::optional<torch::Tensor>& output = std::nullopt,
const std::optional<torch::Tensor>& scale = std::nullopt);
// ============================================================================
// Fused RMSNorm + Static FP8 Quantization
// ============================================================================
// These functions combine RMSNorm and FP8 quantization to reduce memory
// bandwidth by avoiding the intermediate write-back to global memory.
// Fused RMSNorm + Static FP8 Quantization (without residual)
// Combines RMSNorm normalization and FP8 quantization in a single kernel.
// This is optimal for the first layer where no residual connection exists.
void rms_norm_static_fp8_quant(
torch::Tensor& out, // [..., hidden_size], FP8 output
torch::Tensor& input, // [..., hidden_size], input tensor
torch::Tensor& weight, // [hidden_size], RMSNorm weight
torch::Tensor& scale, // [1], FP8 quantization scale
double epsilon); // RMSNorm epsilon
// Fused Add + RMSNorm + Static FP8 Quantization (with residual)
// Combines residual addition, RMSNorm, and FP8 quantization in a single kernel.
// The residual tensor is updated in-place with the sum of input and residual.
void fused_add_rms_norm_static_fp8_quant(
torch::Tensor& out, // [..., hidden_size], FP8 output
torch::Tensor& input, // [..., hidden_size], input tensor
torch::Tensor& residual, // [..., hidden_size], residual (updated in-place)
torch::Tensor& weight, // [hidden_size], RMSNorm weight
torch::Tensor& scale, // [1], FP8 quantization scale
double epsilon); // RMSNorm epsilon
// FP8 scaled matmul for W8A8 quantization using CUTLASS kernels
// Performs: c = (a @ b.T) with scales applied
torch::Tensor fp8_scaled_matmul(
const torch::Tensor& a,
const torch::Tensor& b,
const torch::Tensor& a_scale,
const torch::Tensor& b_scale,
torch::ScalarType output_dtype,
const std::optional<torch::Tensor>& bias = std::nullopt,
const std::optional<torch::Tensor>& output = std::nullopt);
std::pair<torch::Tensor, torch::Tensor> compute_topk_for_beam_search(
torch::Tensor combined_probs,
uint32_t batch_size,
uint32_t beam_size,
uint32_t top_k,
torch::Device device);
std::pair<torch::Tensor, torch::Tensor> compute_topk_general(
torch::Tensor input,
uint32_t batch_size,
uint32_t input_length,
uint32_t k,
torch::Device device);
torch::Tensor air_log_softmax_last_dim(const torch::Tensor& input,
const torch::Tensor& temperatures);
void fused_qk_norm_rope(
torch::Tensor& qkv, // Combined QKV tensor [num_tokens,
// (num_heads_q+num_heads_k+num_heads_v)*head_dim]
int64_t num_heads_q, // Number of query heads
int64_t num_heads_k, // Number of key heads
int64_t num_heads_v, // Number of value heads
int64_t head_dim, // Dimension per head
double eps, // Epsilon for RMS normalization
const torch::Tensor& q_weight, // RMSNorm weights for query [head_dim]
const torch::Tensor& k_weight, // RMSNorm weights for key [head_dim]
const torch::Tensor&
cos_sin_cache, // Cos/sin cache [max_position, rotary_dim]
bool interleaved, // Whether RoPE is applied in interleaved style
const torch::Tensor& position_ids // Position IDs for RoPE [num_tokens]
);
std::tuple<torch::Tensor, torch::Tensor> moe_fused_topk(
torch::Tensor& gating_output,
int64_t topk,
bool renormalize,
const std::optional<torch::Tensor>& correction_bias,
const std::string& scoring_func);
torch::Tensor random_sample(const torch::Tensor& probs);
torch::Tensor cutlass_fused_moe(
const torch::Tensor& input, // [num_tokens, hidden]
const torch::Tensor& token_selected_experts, // [num_tokens, top_k]
const torch::Tensor& token_final_scales, // [num_tokens, top_k]
const torch::Tensor&
fc1_expert_weights, // [num_experts, inter_dim, hidden]
const torch::Tensor&
fc2_expert_weights, // [num_experts, hidden, inter_dim]
torch::ScalarType output_dtype,
const std::vector<torch::Tensor>& quant_scales,
int32_t tp_size,
int32_t tp_rank,
int32_t ep_size,
int32_t ep_rank,
int32_t cluster_size,
int32_t cluster_rank,
const std::optional<torch::Tensor>& fc1_expert_biases = std::nullopt,
const std::optional<torch::Tensor>& fc2_expert_biases = std::nullopt,
const std::optional<torch::Tensor>& input_sf = std::nullopt,
const std::optional<torch::Tensor>& swiglu_alpha = std::nullopt,
const std::optional<torch::Tensor>& swiglu_beta = std::nullopt,
const std::optional<torch::Tensor>& swiglu_limit = std::nullopt,
const std::optional<torch::Tensor>& output = std::nullopt,
bool enable_alltoall = false,
bool use_deepseek_fp8_block_scale = false,
bool use_w4_group_scaling = false,
bool use_mxfp8_act_scaling = false,
bool min_latency_mode = false,
bool use_packed_weights = false,
int32_t tune_max_num_tokens = 8192,
ActivationType activation_type = ActivationType::SWIGLU);
// ---- moe_compute_index (moe_compute_index.cu) ----
// Fused routing index: bincount + argsort replacement.
// Returns {src_dst, dst_src, expert_sizes}.
std::tuple<torch::Tensor, torch::Tensor, torch::Tensor> moe_compute_index(
const torch::Tensor& expert_id,
int64_t num_experts);
// ---- moe_combine_result (moe_combine.cu) ----
// Fused combine: reorder + weighted sum in one pass.
torch::Tensor moe_combine_result(const torch::Tensor& gemm2,
const torch::Tensor& reduce_weight,
int64_t N,
int32_t topk);
} // namespace xllm::kernel::cuda

View File

@@ -0,0 +1,150 @@
/* Copyright 2025-2026 The xLLM Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#pragma once
#if defined(USE_DCU)
#include <hip/amd_detail/amd_hip_bf16.h>
#include <hipcub/hipcub.hpp>
namespace cub = hipcub;
#else
#include <cub/cub.cuh>
#if CUB_VERSION >= 200800
#include <cuda/functional>
#endif
#endif
namespace xllm::kernel::cuda {
#if !defined(USE_DCU)
using BFloat16Type = __nv_bfloat16;
#define WARP_SIZE 32
#define XLLM_KERNEL_ATTR(MAX_THREADS)
#else
using BFloat16Type = hip_bfloat16;
#define WARP_SIZE 64
#define XLLM_KERNEL_ATTR(MAX_THREADS) __launch_bounds__(MAX_THREADS, 1)
#endif
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
// Aligned array type
template <typename T,
// Number of elements in the array
int N,
// Alignment requirement in bytes
int Alignment = sizeof(T) * N>
class alignas(Alignment) AlignedArray {
T data[N];
};
#define XLLM_SHFL_XOR_SYNC(mask, var, lane_mask) \
__shfl_xor_sync((mask), (var), (lane_mask))
#define XLLM_SHFL_XOR_SYNC_WIDTH(mask, var, lane_mask, width) \
__shfl_xor_sync((mask), (var), (lane_mask), (width))
template <typename T>
__device__ __forceinline__ T xllm_ldg(const T* ptr) {
#if defined(USE_DCU)
return *ptr;
#else
return __ldg(ptr);
#endif
}
// Define reduction operators based on CUB version.
#if defined(USE_DCU)
using MaxReduceOp = hipcub::Max;
using MinReduceOp = hipcub::Min;
#elif CUB_VERSION >= 200800
using MaxReduceOp = ::cuda::maximum<>;
using MinReduceOp = ::cuda::minimum<>;
#else
using MaxReduceOp = cub::Max;
using MinReduceOp = cub::Min;
#endif
template <typename T>
__device__ float convert_to_float(T x) {
if constexpr (std::is_same_v<T, __half>) {
return __half2float(x);
#if defined(USE_DCU)
} else if constexpr (std::is_same_v<T, hip_bfloat16>) {
return __bfloat162float(reinterpret_cast<const __hip_bfloat16&>(x));
#else
} else if constexpr (std::is_same_v<T, __nv_bfloat16>) {
return __bfloat162float(x);
#endif
} else if constexpr (std::is_same_v<T, float>) {
return x;
} else {
return static_cast<float>(x);
}
}
// Constructs some constants needed to partition the work across threads at
// compile time.
template <typename T, int EXPERTS, int BYTES_PER_LDG>
struct TopkConstants {
static constexpr int ELTS_PER_LDG = BYTES_PER_LDG / sizeof(T);
static_assert(EXPERTS / (ELTS_PER_LDG * WARP_SIZE) == 0 ||
EXPERTS % (ELTS_PER_LDG * WARP_SIZE) == 0,
"");
static constexpr int VECs_PER_THREAD =
MAX(1, EXPERTS / (ELTS_PER_LDG * WARP_SIZE));
static constexpr int VPT = VECs_PER_THREAD * ELTS_PER_LDG;
static constexpr int THREADS_PER_ROW = EXPERTS / VPT;
static constexpr int ROWS_PER_WARP = WARP_SIZE / THREADS_PER_ROW;
};
} // namespace xllm::kernel::cuda
// ============================================================================
// Portable macros and utilities (from xllm/core/kernels/cuda/utils.h)
// ============================================================================
#ifndef DEVICE_INLINE
#define DEVICE_INLINE __device__ __forceinline__
#define HOST_DEVICE_INLINE __host__ __device__ __forceinline__
#endif
template <typename T>
HOST_DEVICE_INLINE constexpr std::enable_if_t<std::is_integral_v<T>, T>
ceil_div(T a, T b) {
return (a + b - 1) / b;
}
// ============================================================================
// Dispatch macros (from xllm/core/kernels/cuda/utils.h)
// These wrap AT_DISPATCH_SWITCH for float16/bfloat16/float32 dispatch.
// Placed here because cuda_ops_api.h → utils.h is not available on corex
// (glog/logging.h dependency).
// ============================================================================
#ifndef DISPATCH_FLOATING_TYPES
#define DISPATCH_CASE_FLOATING_TYPES(...) \
AT_DISPATCH_CASE(at::ScalarType::Float, __VA_ARGS__) \
AT_DISPATCH_CASE(at::ScalarType::Half, __VA_ARGS__) \
AT_DISPATCH_CASE(at::ScalarType::BFloat16, __VA_ARGS__)
#define DISPATCH_FLOATING_TYPES(TYPE, NAME, ...) \
AT_DISPATCH_SWITCH(TYPE, NAME, DISPATCH_CASE_FLOATING_TYPES(__VA_ARGS__))
#define DISPATCH_CASE_HALF_TYPES(...) \
AT_DISPATCH_CASE(at::ScalarType::Half, __VA_ARGS__) \
AT_DISPATCH_CASE(at::ScalarType::BFloat16, __VA_ARGS__)
#define DISPATCH_HALF_TYPES(TYPE, NAME, ...) \
AT_DISPATCH_SWITCH(TYPE, NAME, DISPATCH_CASE_HALF_TYPES(__VA_ARGS__))
#endif

View File

@@ -0,0 +1,239 @@
/* Copyright 2025-2026 The xLLM Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/jd-opensource/xllm/blob/main/LICENSE
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ===========================================================================*/
#pragma once
// clang-format off
#include <c10/util/Float8_e4m3fn.h>
#include <cmath>
#include <torch/types.h>
// clang-format on
namespace xllm {
namespace kernel {
namespace cuda {
// FP8 type max value definitions
template <typename T,
typename = std::enable_if_t<std::is_same_v<T, c10::Float8_e4m3fn> ||
std::is_same_v<T, int8_t>>>
struct quant_type_max {
static constexpr T val() { return std::numeric_limits<T>::max(); }
};
template <typename T>
__host__ __device__ static constexpr T quant_type_max_v =
quant_type_max<T>::val();
// Minimum scaling factor for quantization types
template <typename T,
typename = std::enable_if_t<std::is_same_v<T, c10::Float8_e4m3fn> ||
std::is_same_v<T, int8_t>>>
struct min_scaling_factor {
__device__ __host__ static inline float val() {
return 1.0f / (quant_type_max_v<T> * 512.0f);
}
};
template <>
struct min_scaling_factor<int8_t> {
__device__ __host__ static inline float val() {
return std::numeric_limits<float>::epsilon();
}
};
// Vectorization containers
template <typename scalar_t, size_t vec_size>
struct __align__(vec_size * sizeof(scalar_t)) vec_n_t {
scalar_t val[vec_size];
};
template <typename quant_type_t, size_t vec_size>
struct __align__(vec_size * sizeof(quant_type_t)) q8_n_t {
static_assert(std::is_same_v<quant_type_t, int8_t> ||
std::is_same_v<quant_type_t, c10::Float8_e4m3fn>);
quant_type_t val[vec_size];
};
// Atomic max for float
__device__ __forceinline__ float atomicMaxFloat(float* addr, float value) {
float old;
old = (value >= 0)
? __int_as_float(atomicMax((int*)addr, __float_as_int(value)))
: __uint_as_float(
atomicMin((unsigned int*)addr, __float_as_uint(value)));
return old;
}
// FP8 conversion functions
namespace fp8 {
#ifdef ENABLE_FP8
#include <cuda_fp8.h>
// float -> c10::Float8_e4m3fn conversion
template <typename Tout, typename Tin>
__inline__ __device__ Tout
vec_conversion(const Tin& x,
const __nv_fp8_interpretation_t fp8_type = __NV_E4M3) {
return x;
}
template <>
__inline__ __device__ c10::Float8_e4m3fn
vec_conversion<c10::Float8_e4m3fn, float>(
const float& a,
const __nv_fp8_interpretation_t fp8_type) {
#if defined(__CUDA_ARCH__) && __CUDA_ARCH__ < 800
return static_cast<c10::Float8_e4m3fn>(a);
#else
return c10::Float8_e4m3fn(__nv_cvt_float_to_fp8(a, __NV_SATFINITE, fp8_type),
c10::Float8_e4m3fn::from_bits());
#endif
}
#endif // ENABLE_FP8
} // namespace fp8
// Scaled FP8 conversion with saturation
template <bool is_scale_inverted, typename fp8_type>
__device__ __forceinline__ fp8_type scaled_fp8_conversion(float const val,
float const scale) {
float x = 0.0f;
if constexpr (is_scale_inverted) {
x = val * scale;
} else {
x = val / scale;
}
float r =
fmaxf(-quant_type_max_v<fp8_type>, fminf(x, quant_type_max_v<fp8_type>));
#ifdef ENABLE_FP8
// Use hardware cvt instruction for fp8 on nvidia
return fp8::vec_conversion<fp8_type, float>(r);
#else
return static_cast<fp8_type>(r);
#endif
}
// Vectorization utilities
template <int VEC_SIZE, typename InT, typename OutT, typename ScaOp>
struct DefaultVecOp {
ScaOp scalar_op;
__device__ __forceinline__ void operator()(
vec_n_t<OutT, VEC_SIZE>& dst,
const vec_n_t<InT, VEC_SIZE>& src) const {
#pragma unroll
for (int i = 0; i < VEC_SIZE; ++i) {
scalar_op(dst.val[i], src.val[i]);
}
}
};
template <int VEC_SIZE,
typename InT,
typename OutT,
typename VecOp,
typename ScaOp>
__device__ inline void vectorize_with_alignment(
const InT* in,
OutT* out,
int len,
int tid,
int stride,
VecOp&& vec_op, // vec_n_t<InT,16> -> vec_n_t<OutT,16>
ScaOp&& scalar_op) { // InT -> OutT
static_assert(VEC_SIZE > 0 && (VEC_SIZE & (VEC_SIZE - 1)) == 0,
"VEC_SIZE must be a positive power-of-two");
constexpr int WIDTH = VEC_SIZE * sizeof(InT);
uintptr_t addr = reinterpret_cast<uintptr_t>(in);
// Fast path when the whole region is already aligned
bool can_vec = ((addr & (WIDTH - 1)) == 0) && ((len & (VEC_SIZE - 1)) == 0);
if (can_vec) {
int num_vec = len / VEC_SIZE;
using vin_t = vec_n_t<InT, VEC_SIZE>;
using vout_t = vec_n_t<OutT, VEC_SIZE>;
auto* v_in = reinterpret_cast<const vin_t*>(in);
auto* v_out = reinterpret_cast<vout_t*>(out);
for (int i = tid; i < num_vec; i += stride) {
vout_t tmp;
vin_t src = v_in[i];
vec_op(tmp, src);
v_out[i] = tmp;
}
return;
}
int misalignment_offset = addr & (WIDTH - 1);
int alignment_bytes = WIDTH - misalignment_offset;
int prefix_elems = alignment_bytes & (WIDTH - 1);
prefix_elems /= sizeof(InT);
prefix_elems = min(prefix_elems, len);
// Prefix handling
for (int i = tid; i < prefix_elems; i += stride) {
scalar_op(out[i], in[i]);
}
in += prefix_elems;
out += prefix_elems;
len -= prefix_elems;
int num_vec = len / VEC_SIZE;
using vin_t = vec_n_t<InT, VEC_SIZE>;
using vout_t = vec_n_t<OutT, VEC_SIZE>;
auto* v_in = reinterpret_cast<const vin_t*>(in);
auto* v_out = reinterpret_cast<vout_t*>(out);
// Vectorized main part
for (int i = tid; i < num_vec; i += stride) {
vout_t tmp;
vin_t src = v_in[i];
vec_op(tmp, src);
v_out[i] = tmp;
}
// Tail handling
int tail_start = num_vec * VEC_SIZE;
for (int i = tid + tail_start; i < len; i += stride) {
scalar_op(out[i], in[i]);
}
}
template <int VEC_SIZE, typename InT, typename OutT, typename ScaOp>
__device__ __forceinline__ void vectorize_with_alignment(const InT* in,
OutT* out,
int len,
int tid,
int stride,
ScaOp&& scalar_op) {
using Vec = DefaultVecOp<VEC_SIZE, InT, OutT, std::decay_t<ScaOp>>;
vectorize_with_alignment<VEC_SIZE>(in,
out,
len,
tid,
stride,
Vec{scalar_op},
std::forward<ScaOp>(scalar_op));
}
} // namespace cuda
} // namespace kernel
} // namespace xllm

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,231 @@
/* Copyright 2025 The vLLM Authors and The xLLM Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#pragma once
#include <cuda_bf16.h>
#include <cuda_fp16.h>
#include <torch/all.h>
// ref to:
// https://github.com/vllm-project/vllm/blob/main/csrc/type_convert.cuh
/* Converter helpers for the conversion from torch types to HIP/CUDA types,
and the associated type conversions within HIP/CUDA. These helpers need
to be implemented for now because the relevant type conversion
operators/constructors are not consistently implemented by HIP/CUDA, so
a generic conversion via type casts cannot be implemented.
Each helper should have the member static constexpr bool `exists`:
If false, the optimized kernel is not used for the corresponding torch type.
If true, the helper should be fully defined as shown in the examples below.
*/
namespace xllm::kernel::cuda {
template <typename torch_type>
class _typeConvert {
public:
static constexpr bool exists = false;
};
template <>
class _typeConvert<float> {
public:
static constexpr bool exists = true;
using hip_type = float;
using packed_hip_type = float2;
using packed_hip_type4 = float4; // For 128-bit vectorization
__device__ static __forceinline__ float convert(hip_type x) { return x; }
__device__ static __forceinline__ float2 convert(packed_hip_type x) {
return x;
}
__device__ static __forceinline__ float4 convert(packed_hip_type4 x) {
return x;
}
};
#if defined(USE_DCU) || (defined(CUDA_VERSION) && (CUDA_VERSION >= 12000)) || \
defined(USE_MACA)
// CUDA < 12.0 runs into issues with packed type conversion
template <>
class _typeConvert<c10::Half> {
public:
static constexpr bool exists = true;
using hip_type = __half;
using packed_hip_type = __half2;
__device__ static __forceinline__ float convert(hip_type x) {
return __half2float(x);
}
__device__ static __forceinline__ float2 convert(packed_hip_type x) {
return __half22float2(x);
}
__device__ static __forceinline__ hip_type convert(float x) {
return __float2half_rn(x);
}
__device__ static __forceinline__ packed_hip_type convert(float2 x) {
return __float22half2_rn(x);
}
};
#endif // defined(USE_DCU) || CUDA_VERSION >= 12000
#if defined(USE_DCU)
template <>
class _typeConvert<c10::BFloat16> {
public:
static constexpr bool exists = true;
using hip_type = __hip_bfloat16;
using packed_hip_type = __hip_bfloat162;
__device__ static __forceinline__ float convert(hip_type x) {
return __bfloat162float(x);
}
__device__ static __forceinline__ float2 convert(packed_hip_type x) {
return __bfloat1622float2(x);
}
__device__ static __forceinline__ hip_type convert(float x) {
return __float2bfloat16(x);
}
__device__ static __forceinline__ packed_hip_type convert(float2 x) {
return __float22bfloat162_rn(x);
}
};
#elif defined(CUDA_VERSION) && (CUDA_VERSION >= 12000) && \
defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 800) || \
defined(USE_MACA)
// CUDA_ARCH < 800 does not have BF16 support.
template <>
class _typeConvert<c10::BFloat16> {
public:
static constexpr bool exists = true;
using hip_type = __nv_bfloat16;
using packed_hip_type = __nv_bfloat162;
__device__ static __forceinline__ float convert(hip_type x) {
return __bfloat162float(x);
}
__device__ static __forceinline__ float2 convert(packed_hip_type x) {
return __bfloat1622float2(x);
}
__device__ static __forceinline__ hip_type convert(float x) {
return __float2bfloat16(x);
}
__device__ static __forceinline__ packed_hip_type convert(float2 x) {
return __float22bfloat162_rn(x);
}
};
#endif
/* Vector helper to generate vectorized and packed FP16/BF16 ops
for appropriate specializations of fused_add_rms_norm_kernel.
Only functions that are necessary in that kernel are implemented.
Alignment to 16 bytes is required to use 128-bit global memory ops.
*/
template <typename scalar_t, int width>
class alignas(16) _f16Vec {
public:
/* Not theoretically necessary that width is a power of 2 but should
almost always be the case for optimization purposes */
static_assert(width > 0 && (width & (width - 1)) == 0,
"Width is not a positive power of 2!");
using Converter = _typeConvert<scalar_t>;
using T1 = typename Converter::hip_type;
using T2 = typename Converter::packed_hip_type;
T1 data[width];
__device__ _f16Vec& operator+=(const _f16Vec<scalar_t, width>& other) {
if constexpr (width % 2 == 0) {
#pragma unroll
for (int i = 0; i < width; i += 2) {
if constexpr (std::is_same_v<T2, float2>) {
data[i] += other.data[i];
data[i + 1] += other.data[i + 1];
} else {
T2 temp{data[i], data[i + 1]};
temp += T2{other.data[i], other.data[i + 1]};
data[i] = temp.x;
data[i + 1] = temp.y;
}
}
} else {
#pragma unroll
for (int i = 0; i < width; ++i) data[i] += other.data[i];
}
return *this;
}
__device__ _f16Vec& operator*=(const _f16Vec<scalar_t, width>& other) {
if constexpr (width % 2 == 0) {
#pragma unroll
for (int i = 0; i < width; i += 2) {
if constexpr (std::is_same_v<T2, float2>) {
data[i] *= other.data[i];
data[i + 1] *= other.data[i + 1];
} else {
T2 temp{data[i], data[i + 1]};
temp *= T2{other.data[i], other.data[i + 1]};
data[i] = temp.x;
data[i + 1] = temp.y;
}
}
} else {
#pragma unroll
for (int i = 0; i < width; ++i) data[i] *= other.data[i];
}
return *this;
}
__device__ _f16Vec& operator*=(const float scale) {
if constexpr (width % 2 == 0) {
#pragma unroll
for (int i = 0; i < width; i += 2) {
float2 temp_f = Converter::convert(T2{data[i], data[i + 1]});
temp_f.x *= scale;
temp_f.y *= scale;
T2 temp = Converter::convert(temp_f);
data[i] = temp.x;
data[i + 1] = temp.y;
}
} else {
#pragma unroll
for (int i = 0; i < width; ++i) {
float temp = Converter::convert(data[i]) * scale;
data[i] = Converter::convert(temp);
}
}
return *this;
}
__device__ float sum_squares() const {
float result = 0.0f;
if constexpr (width % 2 == 0) {
#pragma unroll
for (int i = 0; i < width; i += 2) {
float2 z = Converter::convert(T2{data[i], data[i + 1]});
result += z.x * z.x + z.y * z.y;
}
} else {
#pragma unroll
for (int i = 0; i < width; ++i) {
float x = Converter::convert(data[i]);
result += x * x;
}
}
return result;
}
};
} // namespace xllm::kernel::cuda

View File

@@ -0,0 +1,163 @@
/* Copyright 2025-2026 The xLLM Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#pragma once
#include <ATen/DynamicLibrary.h>
#if defined(USE_DCU)
#include <c10/hip/HIPGuard.h>
#else
#include <c10/cuda/CUDAGuard.h>
#endif
#include <glog/logging.h>
#include <torch/torch.h>
#if !defined(USE_DCU)
#include <tvm/ffi/container/array.h>
#include <tvm/ffi/container/tensor.h>
#include <tvm/ffi/extra/c_env_api.h>
#include <tvm/ffi/extra/module.h>
#include <tvm/ffi/optional.h>
#endif
#include <string>
#include <tuple>
#include <type_traits>
#include <unordered_map>
#if defined(__CUDACC__) || defined(_NVHPC_CUDA) || defined(__HIPCC__)
#define HOST_DEVICE_INLINE __host__ __device__ __forceinline__
#define DEVICE_INLINE __device__ __forceinline__
#define HOST_INLINE __host__ __forceinline__
#else
#define HOST_DEVICE_INLINE inline
#define DEVICE_INLINE inline
#define HOST_INLINE inline
#endif
#if !defined(USE_DCU)
namespace ffi = tvm::ffi;
#endif
namespace xllm::kernel::cuda {
template <typename T>
HOST_DEVICE_INLINE constexpr std::enable_if_t<std::is_integral_v<T>, T>
ceil_div(T a, T b) {
return (a + b - 1) / b;
}
enum class ActivationType : int8_t {
GELU = 0,
RELU = 1,
SILU = 2,
SWIGLU = 3,
GEGLU = 4,
SWIGLU_BIAS = 5,
RELU2 = 6,
IDENTITY = 7,
INVALID_TYPE = 8
};
// torch tensor is only on cpu
torch::Tensor get_cache_buffer(const int32_t seq_len,
const torch::Device& device);
// NOLINTBEGIN(cppcoreguidelines-macro-usage)
#define DISPATCH_CASE_FLOATING_TYPES(...) \
AT_DISPATCH_CASE(at::ScalarType::Float, __VA_ARGS__) \
AT_DISPATCH_CASE(at::ScalarType::Half, __VA_ARGS__) \
AT_DISPATCH_CASE(at::ScalarType::BFloat16, __VA_ARGS__)
#define DISPATCH_FLOATING_TYPES(TYPE, NAME, ...) \
AT_DISPATCH_SWITCH(TYPE, NAME, DISPATCH_CASE_FLOATING_TYPES(__VA_ARGS__))
#define DISPATCH_CASE_HALF_TYPES(...) \
AT_DISPATCH_CASE(at::ScalarType::Half, __VA_ARGS__) \
AT_DISPATCH_CASE(at::ScalarType::BFloat16, __VA_ARGS__)
#define DISPATCH_HALF_TYPES(TYPE, NAME, ...) \
AT_DISPATCH_SWITCH(TYPE, NAME, DISPATCH_CASE_HALF_TYPES(__VA_ARGS__))
// NOLINTEND(cppcoreguidelines-macro-usage)
bool should_use_tensor_core(torch::ScalarType kv_cache_dtype,
int64_t num_attention_heads,
int64_t num_kv_heads);
bool support_pdl();
std::string path_to_uri_so_lib(const std::string& uri);
std::string determine_attention_backend(int64_t pos_encoding_mode,
bool use_fp16_qk_reduction,
bool use_custom_mask);
std::string get_batch_prefill_uri(const std::string& backend,
torch::ScalarType dtype_q,
torch::ScalarType dtype_kv,
torch::ScalarType dtype_o,
torch::ScalarType dtype_idx,
int64_t head_dim_qk,
int64_t head_dim_vo,
int64_t pos_encoding_mode,
bool use_sliding_window,
bool use_logits_soft_cap,
bool use_fp16_qk_reduction);
std::string get_batch_decode_uri(torch::ScalarType dtype_q,
torch::ScalarType dtype_kv,
torch::ScalarType dtype_o,
torch::ScalarType dtype_idx,
int64_t head_dim_qk,
int64_t head_dim_vo,
int64_t pos_encoding_mode,
bool use_sliding_window,
bool use_logits_soft_cap);
std::tuple<torch::Tensor, double> split_scale_param(const torch::Tensor& scale);
#if !defined(USE_DCU)
DLDataType to_dl_data_type(torch::ScalarType scalar_type);
// below are tvm-ffi related functions
ffi::Tensor to_ffi_tensor(const torch::Tensor& torch_tensor);
ffi::Optional<ffi::Tensor> to_ffi_optional_tensor(
const std::optional<torch::Tensor>& optional);
ffi::Array<ffi::Tensor> to_ffi_array_tensors(
const std::vector<torch::Tensor>& torch_tensors);
ffi::Optional<ffi::Array<ffi::Tensor>> to_ffi_optional_array_tensors(
const std::optional<std::vector<torch::Tensor>>& optional);
ffi::Module get_module(const std::string& uri);
ffi::Function get_function(const std::string& uri,
const std::string& func_name);
inline void bind_tvmffi_stream_to_current_torch_stream(
const torch::Device& device) {
const auto cur = c10::cuda::getCurrentCUDAStream(device.index());
// DLPack device type for CUDA is 2 (kDLCUDA).
void* original_stream = nullptr;
const int rc = TVMFFIEnvSetStream(
/*device_type=*/2,
/*device_id=*/device.index(),
reinterpret_cast<void*>(cur.stream()),
&original_stream);
if (rc != 0) {
LOG(WARNING) << "[tvmffi.stream] failed to set stream, rc=" << rc
<< " dev=" << device.index();
}
}
#endif // !defined(USE_DCU)
} // namespace xllm::kernel::cuda

View File

@@ -0,0 +1,167 @@
// hgemm_blocktiling.cu — FP16 GEMM for BI-V100
//
// 1:1 from siboehm/SGEMM_CUDA kernel 6 (sgemmVectorize).
// Changes: float→__half, float4→load 4 halfs, FP32 accumulator.
// No WARPSIZE usage. No cooperative_groups. CUDA 10.2 safe.
#include <cuda_fp16.h>
#include <cuda_runtime.h>
#define CEIL_DIV(M, N) (((M) + (N)-1) / (N))
template <const int BM, const int BN, const int BK, const int TM, const int TN>
__global__ void hgemmVectorize(int M, int N, int K, float alpha,
const __half *A, const __half *B,
float beta, __half *C) {
const uint cRow = blockIdx.y;
const uint cCol = blockIdx.x;
// BN/TN are the number of threads to span a column
const int threadCol = threadIdx.x % (BN / TN);
const int threadRow = threadIdx.x / (BN / TN);
// allocate space for the current blocktile in smem
// A stored transposed: As[BK][BM], B normal: Bs[BK][BN]
__shared__ __half As[BM * BK];
__shared__ __half Bs[BK * BN];
// Move blocktile to beginning of A's row and B's column
A += cRow * BM * K;
B += cCol * BN;
C += cRow * BM * N + cCol * BN;
// calculating the indices that this thread will load into SMEM
// FP16: load 4 halfs (8 bytes) per step. 4 halfs per thread.
// siboehm: float4 = 4 floats = 128bit. We do 4 halfs = 64bit.
const uint innerRowA = threadIdx.x / (BK / 4);
const uint innerColA = threadIdx.x % (BK / 4);
const uint innerRowB = threadIdx.x / (BN / 4);
const uint innerColB = threadIdx.x % (BN / 4);
// allocate thread-local cache for results in registerfile
// FP32 accumulation to avoid FP16 precision loss
float threadResults[TM * TN] = {0.0f};
__half regM[TM];
__half regN[TN];
// outer-most loop over block tiles
for (uint bkIdx = 0; bkIdx < K; bkIdx += BK) {
// populate the SMEM caches
// transpose A while loading it (same as siboehm)
// Load 4 halfs from A
__half a0 = A[innerRowA * K + innerColA * 4 + 0];
__half a1 = A[innerRowA * K + innerColA * 4 + 1];
__half a2 = A[innerRowA * K + innerColA * 4 + 2];
__half a3 = A[innerRowA * K + innerColA * 4 + 3];
As[(innerColA * 4 + 0) * BM + innerRowA] = a0;
As[(innerColA * 4 + 1) * BM + innerRowA] = a1;
As[(innerColA * 4 + 2) * BM + innerRowA] = a2;
As[(innerColA * 4 + 3) * BM + innerRowA] = a3;
// Load 4 halfs from B (no transpose)
Bs[innerRowB * BN + innerColB * 4 + 0] = B[innerRowB * N + innerColB * 4 + 0];
Bs[innerRowB * BN + innerColB * 4 + 1] = B[innerRowB * N + innerColB * 4 + 1];
Bs[innerRowB * BN + innerColB * 4 + 2] = B[innerRowB * N + innerColB * 4 + 2];
Bs[innerRowB * BN + innerColB * 4 + 3] = B[innerRowB * N + innerColB * 4 + 3];
__syncthreads();
// advance blocktile
A += BK; // move BK columns to right
B += BK * N; // move BK rows down
// calculate per-thread results
for (uint dotIdx = 0; dotIdx < BK; ++dotIdx) {
// block into registers
for (uint i = 0; i < TM; ++i) {
regM[i] = As[dotIdx * BM + threadRow * TM + i];
}
for (uint i = 0; i < TN; ++i) {
regN[i] = Bs[dotIdx * BN + threadCol * TN + i];
}
// FP32 accumulation
for (uint resIdxM = 0; resIdxM < TM; ++resIdxM) {
float aVal = __half2float(regM[resIdxM]);
for (uint resIdxN = 0; resIdxN < TN; ++resIdxN) {
threadResults[resIdxM * TN + resIdxN] +=
aVal * __half2float(regN[resIdxN]);
}
}
}
__syncthreads();
}
// write out the results
for (uint resIdxM = 0; resIdxM < TM; resIdxM += 1) {
for (uint resIdxN = 0; resIdxN < TN; resIdxN += 1) {
uint row = cRow * BM + threadRow * TM + resIdxM;
uint col = cCol * BN + threadCol * TN + resIdxN;
if (row < M && col < N) {
float c_old = __half2float(C[(threadRow * TM + resIdxM) * N +
threadCol * TN + resIdxN]);
C[(threadRow * TM + resIdxM) * N + threadCol * TN + resIdxN] =
__float2half(alpha * threadResults[resIdxM * TN + resIdxN] +
beta * c_old);
}
}
}
}
// ============================================================================
// Launch wrapper — matches siboehm runSgemmVectorize
// ============================================================================
void launch_hgemm_blocktiling(
int M, int N, int K,
const __half* alpha_ptr,
const __half* A, int lda,
const __half* B, int ldb,
const __half* beta_ptr,
__half* C, int ldc,
cudaStream_t stream)
{
constexpr int BM = 128;
constexpr int BN = 128;
constexpr int BK = 8;
constexpr int TM = 8;
constexpr int TN = 8;
// 256 threads — same as siboehm
constexpr int NUM_THREADS = (BM * BN) / (TM * TN);
dim3 grid(CEIL_DIV(N, BN), CEIL_DIV(M, BM));
dim3 block(NUM_THREADS);
float alpha = 1.0f, beta = 0.0f;
if (alpha_ptr) alpha = __half2float(*alpha_ptr);
if (beta_ptr) beta = __half2float(*beta_ptr);
hgemmVectorize<BM, BN, BK, TM, TN>
<<<grid, block, 0, stream>>>(M, N, K, alpha, A, B, beta, C);
}
// ============================================================================
// MoE expert GEMM — C++ loop over experts (replaces Python for-loop)
// ============================================================================
void launch_moe_expert_hgemm(
int num_experts,
const int* expert_counts, // host, [num_experts]
const int* expert_offsets, // host, [num_experts]
int N, int K,
const __half* input, // (total_tokens, K)
const __half* weights, // (num_experts, N, K)
__half* output, // (total_tokens, N)
cudaStream_t stream)
{
for (int e = 0; e < num_experts; e++) {
int M_e = expert_counts[e];
if (M_e == 0) continue;
int off = expert_offsets[e];
const __half* A = input + off * K;
const __half* B = weights + (long long)e * N * K;
__half* C_e = output + off * N;
launch_hgemm_blocktiling(M_e, N, K,
nullptr, A, K, B, N, nullptr, C_e, N, stream);
}
}

View File

@@ -0,0 +1,199 @@
// hgemm_warptiling.cu — FP16 warp-tiling GEMM for BI-V100 (warp_size=64)
//
// 1:1 from siboehm/SGEMM_CUDA kernel 10 (sgemmWarptiling).
// Changes from original:
// 1. WARPSIZE = 32 → 64 (BI-V100 confirmed)
// 2. float → __half for A/B/C data and shared memory
// 3. float4 vectorized load → 4 scalar __half loads
// 4. threadResults accumulator stays float (FP32 accumulation)
// 5. C writeback: scalar instead of float4
#include <cuda_fp16.h>
#include <cuda_runtime.h>
#define CEIL_DIV(M, N) (((M) + (N)-1) / (N))
const int WARPSIZE = 64; // BI-V100 confirmed
namespace wt {
template <const int BM, const int BN, const int BK, const int rowStrideA,
const int rowStrideB>
__device__ void loadFromGmem(int N, int K, const __half *A, const __half *B,
__half *As, __half *Bs, int innerRowA, int innerColA,
int innerRowB, int innerColB) {
for (uint offset = 0; offset + rowStrideA <= BM; offset += rowStrideA) {
// Load 4 halfs from A, transpose while storing
__half a0 = A[(innerRowA + offset) * K + innerColA * 4 + 0];
__half a1 = A[(innerRowA + offset) * K + innerColA * 4 + 1];
__half a2 = A[(innerRowA + offset) * K + innerColA * 4 + 2];
__half a3 = A[(innerRowA + offset) * K + innerColA * 4 + 3];
As[(innerColA * 4 + 0) * BM + innerRowA + offset] = a0;
As[(innerColA * 4 + 1) * BM + innerRowA + offset] = a1;
As[(innerColA * 4 + 2) * BM + innerRowA + offset] = a2;
As[(innerColA * 4 + 3) * BM + innerRowA + offset] = a3;
}
for (uint offset = 0; offset + rowStrideB <= BK; offset += rowStrideB) {
// Load 4 halfs from B, no transpose
Bs[(innerRowB + offset) * BN + innerColB * 4 + 0] =
B[(innerRowB + offset) * N + innerColB * 4 + 0];
Bs[(innerRowB + offset) * BN + innerColB * 4 + 1] =
B[(innerRowB + offset) * N + innerColB * 4 + 1];
Bs[(innerRowB + offset) * BN + innerColB * 4 + 2] =
B[(innerRowB + offset) * N + innerColB * 4 + 2];
Bs[(innerRowB + offset) * BN + innerColB * 4 + 3] =
B[(innerRowB + offset) * N + innerColB * 4 + 3];
}
}
template <const int BM, const int BN, const int BK, const int WM, const int WN,
const int WMITER, const int WNITER, const int WSUBM, const int WSUBN,
const int TM, const int TN>
__device__ void
processFromSmem(float *regM, float *regN, float *threadResults, const __half *As,
const __half *Bs, const uint warpRow, const uint warpCol,
const uint threadRowInWarp, const uint threadColInWarp) {
for (uint dotIdx = 0; dotIdx < BK; ++dotIdx) {
// populate registers for whole warptile
for (uint wSubRowIdx = 0; wSubRowIdx < WMITER; ++wSubRowIdx) {
for (uint i = 0; i < TM; ++i) {
regM[wSubRowIdx * TM + i] = __half2float(
As[(dotIdx * BM) + warpRow * WM + wSubRowIdx * WSUBM +
threadRowInWarp * TM + i]);
}
}
for (uint wSubColIdx = 0; wSubColIdx < WNITER; ++wSubColIdx) {
for (uint i = 0; i < TN; ++i) {
regN[wSubColIdx * TN + i] = __half2float(
Bs[(dotIdx * BN) + warpCol * WN + wSubColIdx * WSUBN +
threadColInWarp * TN + i]);
}
}
// execute warptile matmul — FP32 accumulation
for (uint wSubRowIdx = 0; wSubRowIdx < WMITER; ++wSubRowIdx) {
for (uint wSubColIdx = 0; wSubColIdx < WNITER; ++wSubColIdx) {
for (uint resIdxM = 0; resIdxM < TM; ++resIdxM) {
for (uint resIdxN = 0; resIdxN < TN; ++resIdxN) {
threadResults[(wSubRowIdx * TM + resIdxM) * (WNITER * TN) +
(wSubColIdx * TN) + resIdxN] +=
regM[wSubRowIdx * TM + resIdxM] *
regN[wSubColIdx * TN + resIdxN];
}
}
}
}
}
}
} // namespace wt
template <const int BM, const int BN, const int BK, const int WM, const int WN,
const int WNITER, const int TM, const int TN, const int NUM_THREADS>
__global__ void __launch_bounds__(NUM_THREADS)
hgemmWarptiling(int M, int N, int K, float alpha, const __half *A,
const __half *B, float beta, __half *C) {
const uint cRow = blockIdx.y;
const uint cCol = blockIdx.x;
// Placement of the warp in the threadblock tile
const uint warpIdx = threadIdx.x / WARPSIZE; // the warp this thread is in
const uint warpCol = warpIdx % (BN / WN);
const uint warpRow = warpIdx / (BN / WN);
// size of the warp subtile
constexpr uint WMITER = (WM * WN) / (WARPSIZE * TM * TN * WNITER);
constexpr uint WSUBM = WM / WMITER;
constexpr uint WSUBN = WN / WNITER;
// Placement of the thread in the warp subtile
const uint threadIdxInWarp = threadIdx.x % WARPSIZE; // [0, 63]
const uint threadColInWarp = threadIdxInWarp % (WSUBN / TN);
const uint threadRowInWarp = threadIdxInWarp / (WSUBN / TN);
// allocate space for the current blocktile in SMEM
__shared__ __half As[BM * BK];
__shared__ __half Bs[BK * BN];
// Move blocktile to beginning of A's row and B's column
A += cRow * BM * K;
B += cCol * BN;
// Move C_ptr to warp's output tile
C += (cRow * BM + warpRow * WM) * N + cCol * BN + warpCol * WN;
// calculating the indices that this thread will load into SMEM
// FP16: 4 halfs per thread per step
const uint innerRowA = threadIdx.x / (BK / 4);
const uint innerColA = threadIdx.x % (BK / 4);
constexpr uint rowStrideA = (NUM_THREADS * 4) / BK;
const uint innerRowB = threadIdx.x / (BN / 4);
const uint innerColB = threadIdx.x % (BN / 4);
constexpr uint rowStrideB = NUM_THREADS / (BN / 4);
// allocate thread-local cache for results in registerfile
float threadResults[WMITER * TM * WNITER * TN] = {0.0f};
// we cache into registers on the warptile level
float regM[WMITER * TM] = {0.0f};
float regN[WNITER * TN] = {0.0f};
// outer-most loop over block tiles
for (uint bkIdx = 0; bkIdx < K; bkIdx += BK) {
wt::loadFromGmem<BM, BN, BK, rowStrideA, rowStrideB>(
N, K, A, B, As, Bs, innerRowA, innerColA, innerRowB, innerColB);
__syncthreads();
wt::processFromSmem<BM, BN, BK, WM, WN, WMITER, WNITER, WSUBM, WSUBN, TM,
TN>(regM, regN, threadResults, As, Bs, warpRow, warpCol,
threadRowInWarp, threadColInWarp);
A += BK; // move BK columns to right
B += BK * N; // move BK rows down
__syncthreads();
}
// write out the results — scalar writeback (no float4 for __half)
for (uint wSubRowIdx = 0; wSubRowIdx < WMITER; ++wSubRowIdx) {
for (uint wSubColIdx = 0; wSubColIdx < WNITER; ++wSubColIdx) {
__half *C_interim = C + (wSubRowIdx * WSUBM) * N + wSubColIdx * WSUBN;
for (uint resIdxM = 0; resIdxM < TM; resIdxM += 1) {
for (uint resIdxN = 0; resIdxN < TN; resIdxN += 1) {
uint idx = (threadRowInWarp * TM + resIdxM) * N +
threadColInWarp * TN + resIdxN;
float c_old = __half2float(C_interim[idx]);
const int i = (wSubRowIdx * TM + resIdxM) * (WNITER * TN) +
wSubColIdx * TN + resIdxN;
C_interim[idx] = __float2half(alpha * threadResults[i] + beta * c_old);
}
}
}
}
}
// ============================================================================
// Launch wrapper
// ============================================================================
void launch_hgemm_warptiling(
int M, int N, int K,
float alpha,
const __half* A,
const __half* B,
float beta,
__half* C,
cudaStream_t stream)
{
// Config B — best on BI-V100 (beats cublas 0.7x on 256x4096@4096x11008):
// probe_k10_configs.sh confirmed: 7.6ms vs cublas 10.5ms
// 128 threads = 2 warps of 64
// WMITER = (64*64)/(64*8*4*2) = 4096/4096 = 1
// WSUBM = 64/1 = 64, WSUBN = 64/2 = 32
// threads_per_warp = (64/8)*(32/4) = 8*8 = 64 ✓
constexpr int NUM_THREADS = 128;
constexpr int BM = 128, BN = 128, BK = 16;
constexpr int WM = 64, WN = 64;
constexpr int WNITER = 2;
constexpr int TM = 8, TN = 4;
dim3 grid(CEIL_DIV(N, BN), CEIL_DIV(M, BM));
dim3 block(NUM_THREADS);
hgemmWarptiling<BM, BN, BK, WM, WN, WNITER, TM, TN, NUM_THREADS>
<<<grid, block, 0, stream>>>(M, N, K, alpha, A, B, beta, C);
}

View File

@@ -0,0 +1,124 @@
/* Copyright 2025-2026 The xLLM Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "kernels/cuda/cuda_ops_api.h"
#include "kernels/cuda/utils.h"
#include "platform/device.h"
#include "platform/platform.h"
namespace xllm::kernel::cuda {
torch::Tensor cutlass_fused_moe(
const torch::Tensor& input, // [num_tokens, hidden]
const torch::Tensor& token_selected_experts, // [num_tokens, top_k]
const torch::Tensor& token_final_scales, // [num_tokens, top_k]
const torch::Tensor&
fc1_expert_weights, // [num_experts, inter_dim, hidden]
const torch::Tensor&
fc2_expert_weights, // [num_experts, hidden, inter_dim]
torch::ScalarType output_dtype,
const std::vector<torch::Tensor>& quant_scales,
int32_t tp_size,
int32_t tp_rank,
int32_t ep_size,
int32_t ep_rank,
int32_t cluster_size,
int32_t cluster_rank,
const std::optional<torch::Tensor>& fc1_expert_biases,
const std::optional<torch::Tensor>& fc2_expert_biases,
const std::optional<torch::Tensor>& input_sf,
const std::optional<torch::Tensor>& swiglu_alpha,
const std::optional<torch::Tensor>& swiglu_beta,
const std::optional<torch::Tensor>& swiglu_limit,
const std::optional<torch::Tensor>& output,
bool enable_alltoall,
bool use_deepseek_fp8_block_scale,
bool use_w4_group_scaling,
bool use_mxfp8_act_scaling,
bool min_latency_mode,
bool use_packed_weights,
int32_t tune_max_num_tokens,
ActivationType activation_type) {
int64_t num_rows = input.size(0);
int64_t hidden_size = fc2_expert_weights.size(1);
if (min_latency_mode) {
num_rows *= fc2_expert_weights.size(0);
}
std::vector<int64_t> output_shape = {num_rows, hidden_size};
torch::Tensor result_output;
if (output.has_value() && output.value().defined()) {
result_output = output.value();
} else {
torch::TensorOptions options = input.options().dtype(output_dtype);
result_output = torch::empty(output_shape, options);
}
std::string fused_moe_uri = "fused_moe";
if (Platform::is_support_sm90a()) {
fused_moe_uri += "_90";
} else if (Platform::is_support_sm100a() || Platform::is_support_sm100f()) {
fused_moe_uri += "_100";
} else if (Platform::is_support_sm120a()) {
fused_moe_uri += "_120";
} else {
LOG(FATAL) << "FusedMoE is only supported on sm90, sm100, sm120.";
}
bind_tvmffi_stream_to_current_torch_stream(input.device());
ffi::Module fused_moe_runner =
get_function(fused_moe_uri, "init")(
to_dl_data_type(input.scalar_type()),
to_dl_data_type(fc1_expert_weights.scalar_type()),
to_dl_data_type(output_dtype),
use_deepseek_fp8_block_scale,
use_w4_group_scaling,
use_mxfp8_act_scaling,
use_packed_weights)
.cast<ffi::Module>();
fused_moe_runner->GetFunction("run_moe").value()(
to_ffi_tensor(result_output),
to_ffi_tensor(input),
to_ffi_tensor(token_selected_experts),
to_ffi_optional_tensor(token_final_scales),
to_ffi_tensor(fc1_expert_weights),
to_ffi_optional_tensor(fc1_expert_biases),
to_ffi_tensor(fc2_expert_weights),
to_ffi_optional_tensor(fc2_expert_biases),
to_ffi_optional_array_tensors(quant_scales),
to_ffi_optional_tensor(input_sf),
to_ffi_optional_tensor(swiglu_alpha),
to_ffi_optional_tensor(swiglu_beta),
to_ffi_optional_tensor(swiglu_limit),
tp_size,
tp_rank,
ep_size,
ep_rank,
cluster_size,
cluster_rank,
enable_alltoall,
min_latency_mode,
/*profile_ids=*/ffi::Optional<ffi::Array<int64_t>>(), // TODO: support
// auto tuning
// profile ids
support_pdl(),
activation_type);
return result_output;
}
} // namespace xllm::kernel::cuda

View File

@@ -0,0 +1,105 @@
/* Copyright 2025-2026 The xLLM Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
// Fused MoE combine kernel — reorder + weighted sum in one pass.
// Replaces: torch::zeros + index_copy_ + view + multiply + sum
//
// Algorithm per token (each block handles one token):
// 1. For each of its topk experts, read gemm2 at flat_idx directly
// (gemm2 is flat-index-ordered after scatter via index_copy_ with dst_src)
// 2. Multiply by router weight
// 3. Accumulate into output[token]
//
// Grid: num_tokens (N) blocks
// Block: HIDDEN_DIM / HIDDEN_TILE threads
#include <c10/cuda/CUDAGuard.h>
#include "device_utils.cuh"
#include <torch/extension.h>
namespace xllm::kernel::cuda {
constexpr int32_t kCombineBlockSize = 256;
template <typename scalar_t>
__global__ void XLLM_KERNEL_ATTR(kCombineBlockSize) moe_combine_kernel(
const scalar_t* __restrict__ gemm2, // [N*topk, H] flat-index-ordered
const float* __restrict__ reduce_weight, // [N, topk]
scalar_t* __restrict__ output, // [N, H]
int64_t N,
int32_t topk,
int64_t H) {
int64_t token_id = blockIdx.x; // 0 .. N-1
if (token_id >= N) return;
int32_t tid = threadIdx.x;
int32_t stride = kCombineBlockSize;
// Accumulate over topk experts for this token
for (int64_t h = tid; h < H; h += stride) {
float acc = 0.0f;
for (int32_t k = 0; k < topk; ++k) {
int64_t flat_idx = token_id * topk + k;
float w = reduce_weight[flat_idx];
acc += w * static_cast<float>(gemm2[flat_idx * H + h]);
}
output[token_id * H + h] = static_cast<scalar_t>(acc);
}
}
// ---- Host-side orchestrator ----
torch::Tensor moe_combine_result(
const torch::Tensor& gemm2, // [N*topk, H] flat-index-ordered
const torch::Tensor& reduce_weight, // [N, topk] float or same as gemm2
int64_t N,
int32_t topk) {
auto stream = at::cuda::getCurrentCUDAStream();
int64_t H = gemm2.size(1);
auto dtype = gemm2.scalar_type();
auto output = torch::empty({N, H}, gemm2.options());
auto rw = reduce_weight.to(gemm2.device(), torch::kFloat32).contiguous();
if (dtype == torch::kFloat16) {
moe_combine_kernel<c10::Half>
<<<N, kCombineBlockSize, 0, stream>>>(gemm2.data_ptr<c10::Half>(),
rw.data_ptr<float>(),
output.data_ptr<c10::Half>(),
N,
topk,
H);
} else if (dtype == torch::kBFloat16) {
moe_combine_kernel<c10::BFloat16>
<<<N, kCombineBlockSize, 0, stream>>>(gemm2.data_ptr<c10::BFloat16>(),
rw.data_ptr<float>(),
output.data_ptr<c10::BFloat16>(),
N,
topk,
H);
} else {
moe_combine_kernel<float>
<<<N, kCombineBlockSize, 0, stream>>>(gemm2.data_ptr<float>(),
rw.data_ptr<float>(),
output.data_ptr<float>(),
N,
topk,
H);
}
return output;
}
} // namespace xllm::kernel::cuda

View File

@@ -0,0 +1,156 @@
/* Copyright 2025-2026 The xLLM Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
// Fused MoE token index computation — 3 kernels replacing:
// torch::bincount + 2 × torch::argsort + torch::cumsum + CPU sync
//
// Phase 1 histogram: atomicAdd per-expert token counts
// Phase 2 prefix_sum: 1 block, exclusive scan → expert_offsets
// Phase 3 place_indices: atomicAdd on offsets, write dst_src + src_dst
//
// expert_sizes = per-expert token count [num_experts] (preserved)
// expert_offsets = exclusive prefix sum of counts (scratch, reused)
#include <c10/cuda/CUDAGuard.h>
#include <torch/extension.h>
#include <cub/block/block_scan.cuh>
#include "device_utils.cuh"
namespace xllm::kernel::cuda {
constexpr int32_t kMoeIndexBlock = 256;
// ---- Phase 1: histogram ----
__global__ void
#ifdef USE_DCU
__launch_bounds__(kMoeIndexBlock, 1)
#endif
moe_histogram_kernel(const int32_t* __restrict__ expert_id,
int32_t* __restrict__ expert_sizes,
int64_t num_elements,
int32_t num_experts) {
int64_t tid = int64_t(blockIdx.x) * kMoeIndexBlock + threadIdx.x;
if (tid < num_elements) {
int32_t eid = expert_id[tid];
if (eid >= 0 && eid < num_experts) {
atomicAdd(&expert_sizes[eid], 1);
}
}
}
// ---- Phase 2: exclusive prefix sum (1 block) ----
// input: expert_sizes (per-expert counts)
// output: expert_offsets (exclusive scan of counts)
// total_out (total number of tokens, scalar)
__global__ void
#ifdef USE_DCU
__launch_bounds__(kMoeIndexBlock, 1)
#endif
moe_prefix_sum_kernel(const int32_t* __restrict__ expert_sizes,
int32_t* __restrict__ expert_offsets,
int32_t num_experts,
int64_t* __restrict__ total_out) {
using BlockScan = cub::BlockScan<int32_t, kMoeIndexBlock>;
__shared__ typename BlockScan::TempStorage s_scan;
int32_t val = (threadIdx.x < num_experts) ? expert_sizes[threadIdx.x] : 0;
int32_t offset;
BlockScan(s_scan).ExclusiveSum(val, offset);
__syncthreads();
// total = all elements sum = last thread's exclusive output + its input
int32_t total = offset + val;
if (threadIdx.x < num_experts) {
expert_offsets[threadIdx.x] = offset;
}
if (threadIdx.x == 0 && total_out != nullptr) {
*total_out = total;
}
}
// ---- Phase 3: place indices ----
// atomicAdd on expert_offsets to assign a unique position within
// [start(e), start(e)+count(e)), then write both direction mappings.
__global__ void
#ifdef USE_DCU
__launch_bounds__(kMoeIndexBlock, 1)
#endif
moe_place_indices_kernel(const int32_t* __restrict__ expert_id,
int32_t* __restrict__ expert_offsets,
int32_t* __restrict__ dst_src,
int32_t* __restrict__ src_dst,
int64_t num_elements,
int32_t num_experts) {
int64_t flat_idx = int64_t(blockIdx.x) * kMoeIndexBlock + threadIdx.x;
if (flat_idx >= num_elements) return;
int32_t eid = expert_id[flat_idx];
if (eid < 0 || eid >= num_experts) return;
int32_t pos = atomicAdd(&expert_offsets[eid], 1);
dst_src[pos] = static_cast<int32_t>(flat_idx);
src_dst[flat_idx] = pos;
}
// ---- Host-side orchestrator ----
// Returns {src_dst, dst_src, expert_sizes}
std::tuple<torch::Tensor, torch::Tensor, torch::Tensor> moe_compute_index(
const torch::Tensor& expert_id,
int64_t num_experts) {
auto device = expert_id.device();
auto stream = at::cuda::getCurrentCUDAStream();
int64_t N = expert_id.numel();
int32_t E = static_cast<int32_t>(num_experts);
TORCH_CHECK(E <= kMoeIndexBlock, "num_experts cannot exceed ", kMoeIndexBlock);
auto expert_id_i32 = expert_id.to(torch::kInt32).contiguous();
auto opt_i32 = expert_id_i32.options();
auto expert_sizes = torch::zeros({num_experts}, opt_i32);
auto expert_offsets = torch::empty({num_experts}, opt_i32);
auto dst_src = torch::empty({N}, opt_i32);
auto src_dst = torch::empty({N}, opt_i32);
int64_t grid = (N + kMoeIndexBlock - 1) / kMoeIndexBlock;
// Phase 1: histogram
moe_histogram_kernel<<<grid, kMoeIndexBlock, 0, stream>>>(
expert_id_i32.data_ptr<int32_t>(),
expert_sizes.data_ptr<int32_t>(),
N,
E);
// Phase 2: prefix sum (1 block)
moe_prefix_sum_kernel<<<1, kMoeIndexBlock, 0, stream>>>(
expert_sizes.data_ptr<int32_t>(),
expert_offsets.data_ptr<int32_t>(),
E,
nullptr);
// Phase 3: place indices
moe_place_indices_kernel<<<grid, kMoeIndexBlock, 0, stream>>>(
expert_id_i32.data_ptr<int32_t>(),
expert_offsets.data_ptr<int32_t>(),
dst_src.data_ptr<int32_t>(),
src_dst.data_ptr<int32_t>(),
N,
E);
return std::make_tuple(src_dst, dst_src, expert_sizes);
}
} // namespace xllm::kernel::cuda

View File

@@ -0,0 +1,59 @@
/* Copyright 2025-2026 The xLLM Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#if defined(USE_DCU)
#include "kernels/dcu/dcu_ops_api.h"
#else
#include "device_utils.cuh"
#include <torch/extension.h>
#endif
#include "moe_topk_sigmoid_kernels.cuh"
#include "moe_topk_softmax_kernels.cuh"
namespace xllm::kernel::cuda {
std::tuple<torch::Tensor, torch::Tensor> moe_fused_topk(
torch::Tensor& gating_output,
int64_t topk,
bool renormalize,
const std::optional<torch::Tensor>& correction_bias,
const std::string& scoring_func) {
int64_t num_tokens = gating_output.size(0);
torch::Tensor topk_weights = torch::empty(
{num_tokens, topk},
torch::dtype(torch::kFloat32).device(gating_output.device()));
torch::Tensor topk_ids =
torch::empty({num_tokens, topk},
torch::dtype(torch::kInt32).device(gating_output.device()));
if (scoring_func == "softmax") {
std::optional<torch::Tensor> none_correction_bias = std::nullopt;
topk_softmax(topk_weights,
topk_ids,
gating_output,
renormalize,
/*moe_softcapping=*/0.0,
none_correction_bias);
} else if (scoring_func == "sigmoid") {
topk_sigmoid(
topk_weights, topk_ids, gating_output, renormalize, correction_bias);
} else {
TORCH_CHECK(false, "Unsupported scoring function: ", scoring_func);
}
return std::make_tuple(topk_weights, topk_ids);
}
} // namespace xllm::kernel::cuda

View File

@@ -0,0 +1,345 @@
/*
* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// refers to
// https://github.com/NVIDIA/TensorRT-LLM/blob/main/cpp/tensorrt_llm/kernels/moeTopKFuncs.cuh
#pragma once
#include <cooperative_groups.h>
#if !defined(USE_DCU)
#include <cooperative_groups/reduce.h>
#endif
#if defined(USE_MACA)
#include <cuda_bf16.h>
#endif
#if !defined(USE_DCU)
#include <cub/cub.cuh>
#else
#include <hipcub/hipcub.hpp>
#endif
#include "arch_condition.h"
#if defined(USE_DCU)
#include <hip/hip_bfloat16.h>
#include <hip/hip_fp16.h>
#endif
#include "device_utils.cuh"
namespace xllm::kernel::cuda {
namespace reduce_topk {
namespace cg = cooperative_groups;
static constexpr int kWarpSize = 32;
#if !defined(USE_DCU)
static constexpr bool kTllmGenHasFastRedux = arch::is_major_v<10>;
#else
static constexpr bool kTllmGenHasFastRedux = false;
#endif
template <typename T_>
struct TopKRedType {
using T = T_;
static_assert(
std::is_same_v<T, float> || std::is_same_v<T, half> ||
std::is_same_v<T, BFloat16Type> || std::is_same_v<T, int>,
"Top K reduction only implemented for int, float, float16 and bfloat16");
using TypeCmp = std::conditional_t<sizeof(T) == 4, uint64_t, uint32_t>;
using IdxT = std::conditional_t<sizeof(T) == 4, int32_t, int16_t>;
#if defined(USE_DCU)
using UnsignedBits = std::conditional_t<sizeof(T) == 4, uint32_t, uint16_t>;
#endif
static constexpr int kMoveBits = (sizeof(T) == 4) ? 32 : 16;
static constexpr int kMaxIdx = 65535;
TypeCmp compValIdx;
static __host__ __device__ inline TypeCmp makeCmpVal(T val, int32_t idx = 0) {
#if !defined(USE_DCU)
auto valueBits = cub::Traits<T>::TwiddleIn(
reinterpret_cast<typename cub::Traits<T>::UnsignedBits&>(val));
#else
UnsignedBits valueBits = reinterpret_cast<UnsignedBits&>(val);
constexpr UnsignedBits kSignMask =
static_cast<UnsignedBits>(UnsignedBits{1} << (sizeof(T) * 8 - 1));
if constexpr (std::is_same_v<T, int>) {
valueBits = static_cast<UnsignedBits>(valueBits ^ kSignMask);
} else {
valueBits = (valueBits & kSignMask)
? static_cast<UnsignedBits>(~valueBits)
: static_cast<UnsignedBits>(valueBits ^ kSignMask);
}
#endif
TypeCmp compactTmp = valueBits;
compactTmp = (compactTmp << kMoveBits) | (0xFFFF & (kMaxIdx - idx));
// Use 65535 minus idx to give higher priority to elements with smaller
// indices.
return compactTmp;
}
static __host__ __device__ void unpack(T& value,
int32_t& index,
TypeCmp cmp) {
// Since "65535-idx" is always smaller than 65536 and positive, we can
// directly use it as the lower 16 bits
index = kMaxIdx - static_cast<int32_t>((cmp & 0xFFFF));
auto compactTmp = cmp >> kMoveBits;
#if !defined(USE_DCU)
auto valueBits = cub::Traits<T>::TwiddleOut(
reinterpret_cast<typename cub::Traits<T>::UnsignedBits&>(compactTmp));
#else
UnsignedBits valueBits = static_cast<UnsignedBits>(compactTmp);
constexpr UnsignedBits kSignMask =
static_cast<UnsignedBits>(UnsignedBits{1} << (sizeof(T) * 8 - 1));
if constexpr (std::is_same_v<T, int>) {
valueBits = static_cast<UnsignedBits>(valueBits ^ kSignMask);
} else {
valueBits = (valueBits & kSignMask)
? static_cast<UnsignedBits>(valueBits ^ kSignMask)
: static_cast<UnsignedBits>(~valueBits);
}
#endif
value = reinterpret_cast<T&>(valueBits);
}
__host__ __device__ TopKRedType() = default;
__host__ __device__ TopKRedType(T val, int32_t idx)
: compValIdx(makeCmpVal(val, idx)) {}
__host__ __device__ operator TypeCmp() const noexcept { return compValIdx; }
__device__ inline TypeCmp reduce(
cg::thread_block_tile<kWarpSize> const& warp) {
#if defined(USE_DCU)
TypeCmp result = compValIdx;
#pragma unroll
for (int offset = kWarpSize / 2; offset > 0; offset >>= 1) {
TypeCmp other = warp.shfl_down(result, offset);
result = other > result ? other : result;
}
return warp.shfl(result, 0);
#else
if constexpr (!kTllmGenHasFastRedux || sizeof(TypeCmp) == 8) {
return cg::reduce(warp, compValIdx, cg::greater<TypeCmp>{});
} else {
TypeCmp result;
asm("redux.sync.max.u32 %0, %1, 0xffffffff;\n"
: "=r"(result)
: "r"(compValIdx));
return result;
}
#endif
}
};
////////////////////////////////////////////////////////////////////////////////////////////////////
template <int K_, bool Enable_>
struct TopKIdx {
// by default, empty
};
template <int K_>
struct TopKIdx<K_, true> {
static constexpr int K = K_;
int32_t val[K];
};
////////////////////////////////////////////////////////////////////////////////////////////////////
#define TOPK_SWAP(I, J) \
{ \
auto pairMin = min(topK[I].compValIdx, topK[J].compValIdx); \
auto pairMax = max(topK[I].compValIdx, topK[J].compValIdx); \
topK[I].compValIdx = pairMax; \
topK[J].compValIdx = pairMin; \
}
template <int N, typename RedType>
struct Sort;
template <typename RedType>
struct Sort<1, RedType> {
static __device__ void run(RedType* topK) {}
};
template <typename RedType>
struct Sort<2, RedType> {
static __device__ void run(RedType* topK) { TOPK_SWAP(0, 1); }
};
template <typename RedType>
struct Sort<3, RedType> {
static __device__ void run(RedType* topK) {
TOPK_SWAP(0, 1);
TOPK_SWAP(1, 2);
TOPK_SWAP(0, 1);
}
};
template <typename RedType>
struct Sort<4, RedType> {
static __device__ void run(RedType* topK) {
TOPK_SWAP(0, 2);
TOPK_SWAP(1, 3);
TOPK_SWAP(0, 1);
TOPK_SWAP(2, 3);
TOPK_SWAP(1, 2);
}
};
template <int K, typename Type>
__forceinline__ __device__ void reduceTopK(
cg::thread_block_tile<kWarpSize> const& warp,
Type (&out)[K],
int32_t (&outIdx)[K],
Type value,
int32_t idx,
Type const minValue,
int actualK = K) {
static_assert(K > 0, "Top K must have K > 0");
static_assert(K < kWarpSize, "Top K must have K < kWarpSize");
using RedType = TopKRedType<Type>;
RedType topK{value, idx};
typename RedType::TypeCmp packedMax{};
#pragma unroll
for (int kk = 0; kk < actualK; ++kk) //@todo: check if actualK is correct
{
topK =
kk > 0 && packedMax == topK.compValIdx ? RedType{minValue, idx} : topK;
// get the next largest value
packedMax = topK.reduce(warp);
RedType::unpack(out[kk], outIdx[kk], packedMax);
}
};
template <int K, typename Type, int N, bool IsSorted = false>
__device__ void reduceTopKFunc(cg::thread_block_tile<kWarpSize> const& warp,
Type (&out)[K],
int32_t (&outIdx)[K],
Type (&value)[N],
int32_t (&idx)[N],
Type minValue,
int actualK = K) {
static_assert(K > 0, "Top K must have K > 0");
static_assert(K < kWarpSize, "Top K must have K < kWarpSize");
static_assert(N > 0, "Top K must have N > 0");
static_assert(N < 5,
"Only support candidates number less than or equal to 128");
using RedType = TopKRedType<Type>;
RedType topK[N];
#pragma unroll
for (int nn = 0; nn < N; ++nn) {
topK[nn] = RedType{value[nn], idx[nn]};
}
if constexpr (!IsSorted) {
Sort<N, RedType>::run(topK);
}
typename RedType::TypeCmp packedMax{};
#pragma unroll
for (int kk = 0; kk < actualK; ++kk) {
bool update = kk > 0 && packedMax == topK[0].compValIdx;
#pragma unroll
for (int nn = 0; nn < N; ++nn) {
topK[nn] = update && nn == N - 1 ? RedType{minValue, idx[nn]}
: update ? topK[nn + 1]
: topK[nn];
}
// get the next largest value
packedMax = topK[0].reduce(warp);
RedType::unpack(out[kk], outIdx[kk], packedMax);
}
};
template <int K, typename Type, int N>
__forceinline__ __device__ void reduceTopK(
cg::thread_block_tile<kWarpSize> const& warp,
Type (&out)[K],
int32_t (&outIdx)[K],
Type (&value)[N],
int32_t (&idx)[N],
Type const minValue,
int actualK = K) {
static_assert(K > 0, "Top K must have K > 0");
static_assert(K < kWarpSize, "Top K must have K < kWarpSize");
static_assert(N > 0, "Top K must have N > 0");
static_assert(
N <= 16,
"Only support candidates number less than or equal to 16*32=512");
static_assert(N <= 4 || N % 4 == 0,
"Only support candidates number is a multiple of 4*32=128 or "
"less than or equal to 4");
using RedType = TopKRedType<Type>;
if constexpr (N <= 4) {
reduceTopKFunc<K, Type, N>(
warp, out, outIdx, value, idx, minValue, actualK);
} else {
constexpr int kNumLoops = N / 4;
constexpr int kNumResults = (kNumLoops * K - 1) / kWarpSize + 1;
Type topKBufferValue[kNumResults];
int32_t topKBufferIdx[kNumResults];
int32_t laneIdx = threadIdx.x % kWarpSize;
// Sentinel index must be in [0, kMaxIdx] to survive makeCmpVal pack/unpack
// (kMaxIdx - idx is stored in 16 bits; -1 would become 0 and unpack to
// 65535). Use kMaxIdx so sentinel slots have smallest compValIdx for
// minValue and lose to any real candidate.
for (int ii = 0; ii < kNumResults; ++ii) {
topKBufferValue[ii] = minValue;
topKBufferIdx[ii] = RedType::kMaxIdx;
}
for (int loop = 0; loop < kNumLoops; ++loop) {
int start = loop * 4;
Type topKValue[K];
int32_t topKIdx[K];
Type inValue[4];
int32_t inIdx[4];
for (int i = 0; i < 4; ++i) {
inValue[i] = value[start + i];
inIdx[i] = idx[start + i];
}
reduceTopKFunc<K, Type, 4>(
warp, topKValue, topKIdx, inValue, inIdx, minValue, actualK);
int inOffset = laneIdx % K;
if (laneIdx >= loop * K && laneIdx < (loop + 1) * K) {
topKBufferValue[0] = topKValue[inOffset];
topKBufferIdx[0] = topKIdx[inOffset];
}
if (loop == kNumLoops - 1 && (laneIdx < (kNumLoops * K - kWarpSize))) {
topKBufferValue[1] = topKValue[inOffset];
topKBufferIdx[1] = topKIdx[inOffset];
}
}
reduceTopKFunc<K, Type, kNumResults>(
warp, out, outIdx, topKBufferValue, topKBufferIdx, minValue, actualK);
}
};
#undef TOPK_SWAP
} // namespace reduce_topk
} // namespace xllm::kernel::cuda

View File

@@ -0,0 +1,608 @@
// Adapt from
// https://github.com/vllm-project/vllm/blob/v0.7.3/csrc/moe/topk_softmax_kernels.cu
// which is originally adapted from
// https://github.com/NVIDIA/TensorRT-LLM/blob/v0.7.1/cpp/tensorrt_llm/kernels/mixtureOfExperts/moe_kernels.cu
/* Copyright 2025 SGLang Team. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <ATen/cuda/CUDAContext.h>
#include <c10/cuda/CUDAGuard.h>
#include <torch/all.h>
#include <cub/util_type.cuh>
#if !defined(USE_DCU) && !defined(USE_MACA)
#endif
#include "device_utils.cuh"
namespace {
using namespace xllm::kernel::cuda;
#if defined(USE_DCU)
static constexpr unsigned long long kSigmoidFullMask = 0xffffffffffffffffULL;
#else
static constexpr unsigned int kSigmoidFullMask = 0xffffffffU;
#endif
// ====================== Sigmoid things ===============================
// We have our own implementation of sigmoid here so we can support transposing
// the output in the sigmoid kernel when we extend this module to support
// expert-choice routing.
template <typename T, int TPB>
__launch_bounds__(TPB) __global__
void moe_sigmoid(const T* input,
const bool* finished,
float* output,
const int num_cols,
const float* correction_bias) {
const int thread_row_offset = blockIdx.x * num_cols;
// Don't touch finished rows.
if ((finished != nullptr) && finished[blockIdx.x]) {
return;
}
// First pass: Apply transformation, find max, and write transformed values to
// output
for (int ii = threadIdx.x; ii < num_cols; ii += TPB) {
const int idx = thread_row_offset + ii;
float val = convert_to_float<T>(input[idx]);
val = 1.0f / (1.0f + expf(-val));
// Apply correction bias if provided
if (correction_bias != nullptr) {
val = val + correction_bias[ii];
}
output[idx] = val; // Store transformed value
}
}
template <int TPB>
__launch_bounds__(TPB) __global__
void moe_topK(const float* inputs_after_sigmoid,
const bool* finished,
float* output,
int* indices,
const int num_experts,
const int k,
const int start_expert,
const int end_expert,
const bool renormalize,
const float* correction_bias) {
using cub_kvp = cub::KeyValuePair<int, float>;
using BlockReduce = cub::BlockReduce<cub_kvp, TPB>;
__shared__ typename BlockReduce::TempStorage tmpStorage;
cub_kvp thread_kvp;
cub::ArgMax arg_max;
const int block_row = blockIdx.x;
const bool row_is_active = finished ? !finished[block_row] : true;
const int thread_read_offset = blockIdx.x * num_experts;
float row_sum_for_renormalize = 0;
for (int k_idx = 0; k_idx < k; ++k_idx) {
thread_kvp.key = 0;
thread_kvp.value = -1.f; // This is OK because inputs are probabilities
cub_kvp inp_kvp;
for (int expert = threadIdx.x; expert < num_experts; expert += TPB) {
const int idx = thread_read_offset + expert;
inp_kvp.key = expert;
inp_kvp.value = inputs_after_sigmoid[idx];
for (int prior_k = 0; prior_k < k_idx; ++prior_k) {
const int prior_winning_expert = indices[k * block_row + prior_k];
if (prior_winning_expert == expert) {
inp_kvp = thread_kvp;
}
}
thread_kvp = arg_max(inp_kvp, thread_kvp);
}
const cub_kvp result_kvp =
BlockReduce(tmpStorage).Reduce(thread_kvp, arg_max);
if (threadIdx.x == 0) {
// Ignore experts the node isn't responsible for with expert parallelism
const int expert = result_kvp.key;
const bool node_uses_expert =
expert >= start_expert && expert < end_expert;
const bool should_process_row = row_is_active && node_uses_expert;
const int idx = k * block_row + k_idx;
float val = result_kvp.value;
if (correction_bias != nullptr) {
val -= correction_bias[expert];
}
output[idx] = val;
indices[idx] = should_process_row ? (expert - start_expert) : num_experts;
assert(indices[idx] >= 0);
row_sum_for_renormalize += val;
}
__syncthreads();
}
if (renormalize && threadIdx.x == 0) {
float row_sum_for_renormalize_inv = 1.f / row_sum_for_renormalize;
for (int k_idx = 0; k_idx < k; ++k_idx) {
const int idx = k * block_row + k_idx;
output[idx] = output[idx] * row_sum_for_renormalize_inv;
}
}
}
// ====================== TopK sigmoid things ===============================
/*
A Top-K gating sigmoid written to exploit when the number of experts in the
MoE layers are a small power of 2. This allows us to cleanly share the rows
among the threads in a single warp and eliminate communication between warps
(so no need to use shared mem).
It fuses the sigmoid, max and argmax into a single kernel.
Limitations:
1) This implementation is intended for when the number of experts is a small
power of 2. 2) This implementation assumes k is small, but will work for any
k.
*/
template <typename T,
int VPT,
int NUM_EXPERTS,
int WARPS_PER_CTA,
int BYTES_PER_LDG>
__launch_bounds__(WARPS_PER_CTA* WARP_SIZE) __global__
void topk_gating_sigmoid(const T* input,
const bool* finished,
float* output,
const int num_rows,
int* indices,
const int k,
const int start_expert,
const int end_expert,
const bool renormalize,
const float* correction_bias) {
// We begin by enforcing compile time assertions and setting up compile time
// constants.
static_assert(VPT == (VPT & -VPT), "VPT must be power of 2");
static_assert(NUM_EXPERTS == (NUM_EXPERTS & -NUM_EXPERTS),
"NUM_EXPERTS must be power of 2");
static_assert(BYTES_PER_LDG == (BYTES_PER_LDG & -BYTES_PER_LDG),
"BYTES_PER_LDG must be power of 2");
static_assert(BYTES_PER_LDG <= 16, "BYTES_PER_LDG must be leq 16");
// Number of bytes each thread pulls in per load
static constexpr int kEltsPerLdg = BYTES_PER_LDG / sizeof(T);
static constexpr int kEltsPerRow = NUM_EXPERTS;
static constexpr int kThreadsPerRow = kEltsPerRow / VPT;
static constexpr int kLdgPerThread = VPT / kEltsPerLdg;
// Restrictions based on previous section.
static_assert(
VPT % kEltsPerLdg == 0,
"The elements per thread must be a multiple of the elements per ldg");
static_assert(WARP_SIZE % kThreadsPerRow == 0,
"The threads per row must cleanly divide the threads per warp");
static_assert(kThreadsPerRow == (kThreadsPerRow & -kThreadsPerRow),
"THREADS_PER_ROW must be power of 2");
static_assert(kThreadsPerRow <= WARP_SIZE,
"THREADS_PER_ROW can be at most warp size");
// We have NUM_EXPERTS elements per row. We specialize for small #experts
static constexpr int kEltsPerWarp = WARP_SIZE * VPT;
static constexpr int kRowsPerWarp = kEltsPerWarp / kEltsPerRow;
static constexpr int kRowsPerCta = WARPS_PER_CTA * kRowsPerWarp;
// Restrictions for previous section.
static_assert(kEltsPerWarp % kEltsPerRow == 0,
"The elts per row must cleanly divide the total elt per warp");
// ===================== From this point, we finally start computing run-time
// variables. ========================
// Compute CTA and warp rows. We pack multiple rows into a single warp, and a
// block contains WARPS_PER_CTA warps. This, each block processes a chunk of
// rows. We start by computing the start row for each block.
const int cta_base_row = blockIdx.x * kRowsPerCta;
// Now, using the base row per thread block, we compute the base row per warp.
const int warp_base_row = cta_base_row + threadIdx.y * kRowsPerWarp;
// The threads in a warp are split into sub-groups that will work on a row.
// We compute row offset for each thread sub-group
const int thread_row_in_warp = threadIdx.x / kThreadsPerRow;
const int thread_row = warp_base_row + thread_row_in_warp;
// Threads with indices out of bounds should early exit here.
if (thread_row >= num_rows) {
return;
}
const bool row_is_active = finished ? !finished[thread_row] : true;
// We finally start setting up the read pointers for each thread. First, each
// thread jumps to the start of the row it will read.
const T* thread_row_ptr = input + thread_row * kEltsPerRow;
// Now, we compute the group each thread belong to in order to determine the
// first column to start loads.
const int thread_group_idx = threadIdx.x % kThreadsPerRow;
const int first_elt_read_by_thread = thread_group_idx * kEltsPerLdg;
const T* thread_read_ptr = thread_row_ptr + first_elt_read_by_thread;
// Determine the pointer type to use to read in the data depending on the
// BYTES_PER_LDG template param. In theory, this can support all powers of 2
// up to 16. NOTE(woosuk): The original implementation uses CUTLASS aligned
// array here. We defined our own aligned array and use it here to avoid the
// dependency on CUTLASS.
using AccessType = AlignedArray<T, kEltsPerLdg>;
// Finally, we pull in the data from global mem
T row_chunk_temp[VPT];
AccessType* row_chunk_vec_ptr =
reinterpret_cast<AccessType*>(&row_chunk_temp);
const AccessType* vec_thread_read_ptr =
reinterpret_cast<const AccessType*>(thread_read_ptr);
#pragma unroll
// Note(Byron): interleaved loads to achieve better memory coalescing
// | thread[0] | thread[1] | thread[2] | thread[3] | thread[0] | thread[1] |
// thread[2] | thread[3] | ...
for (int ii = 0; ii < kLdgPerThread; ++ii) {
row_chunk_vec_ptr[ii] = vec_thread_read_ptr[ii * kThreadsPerRow];
}
float row_chunk[VPT];
#pragma unroll
// Note(Byron): upcast logits to float32
for (int ii = 0; ii < VPT; ++ii) {
float val = convert_to_float<T>(row_chunk_temp[ii]);
val = 1.0f / (1.0f + expf(-val));
// Apply correction bias if provided
if (correction_bias != nullptr) {
/*
LDG is interleaved
|thread0 LDG| |thread1 LDG| |thread0 LDG| |thread1 LDG|
|--------- group0 --------| |----------group1 --------|
^ local2
*/
const int group_id = ii / kEltsPerLdg;
const int local_id = ii % kEltsPerLdg;
const int expert_idx = first_elt_read_by_thread +
group_id * kThreadsPerRow * kEltsPerLdg + local_id;
val = val + correction_bias[expert_idx];
}
row_chunk[ii] = val;
}
// Now, row_chunk contains the sigmoid of the row chunk. Now, I want to find
// the topk elements in each row, along with the max index.
int start_col = first_elt_read_by_thread;
static constexpr int kColsPerGroupLdg = kEltsPerLdg * kThreadsPerRow;
float row_sum_for_renormalize = 0;
for (int k_idx = 0; k_idx < k; ++k_idx) {
// First, each thread does the local argmax
float max_val = row_chunk[0];
int expert = start_col;
#pragma unroll
for (int ldg = 0, col = start_col; ldg < kLdgPerThread;
++ldg, col += kColsPerGroupLdg) {
#pragma unroll
for (int ii = 0; ii < kEltsPerLdg; ++ii) {
float val = row_chunk[ldg * kEltsPerLdg + ii];
// No check on the experts here since columns with the smallest index
// are processed first and only updated if > (not >=)
if (val > max_val) {
max_val = val;
expert = col + ii;
}
}
}
// Now, we perform the argmax reduce. We use the butterfly pattern so threads
// reach consensus about the max. This will be useful for K > 1 so that the
// threads can agree on "who" had the max value. That thread can then blank out
// their max with -inf and the warp can run more iterations...
#pragma unroll
for (int mask = kThreadsPerRow / 2; mask > 0; mask /= 2) {
float other_max = XLLM_SHFL_XOR_SYNC_WIDTH(
kSigmoidFullMask, max_val, mask, kThreadsPerRow);
int other_expert = XLLM_SHFL_XOR_SYNC_WIDTH(
kSigmoidFullMask, expert, mask, kThreadsPerRow);
// We want lower indices to "win" in every thread so we break ties this
// way
if (other_max > max_val ||
(other_max == max_val && other_expert < expert)) {
max_val = other_max;
expert = other_expert;
}
}
// Write the max for this k iteration to global memory.
if (thread_group_idx == 0) {
// Add a guard to ignore experts not included by this node
const bool node_uses_expert =
expert >= start_expert && expert < end_expert;
const bool should_process_row = row_is_active && node_uses_expert;
// The lead thread from each sub-group will write out the final results to
// global memory. (This will be a single) thread per row of the
// input/output matrices.
const int idx = k * thread_row + k_idx;
if (correction_bias != nullptr) {
max_val -= correction_bias[expert];
}
output[idx] = max_val;
indices[idx] = should_process_row ? (expert - start_expert) : NUM_EXPERTS;
row_sum_for_renormalize += max_val;
}
// Finally, we clear the value in the thread with the current max if there
// is another iteration to run.
if (k_idx + 1 < k) {
const int ldg_group_for_expert = expert / kColsPerGroupLdg;
const int thread_to_clear_in_group =
(expert / kEltsPerLdg) % kThreadsPerRow;
// Only the thread in the group which produced the max will reset the
// "winning" value to -inf.
if (thread_group_idx == thread_to_clear_in_group) {
const int offset_for_expert = expert % kEltsPerLdg;
// Safe to set to any negative value since row_chunk values must be
// between 0 and 1.
row_chunk[ldg_group_for_expert * kEltsPerLdg + offset_for_expert] =
-10000.f;
}
}
}
// Fuse renormalization of topk_weights into this kernel
if (renormalize && thread_group_idx == 0) {
float row_sum_for_renormalize_inv = 1.f / row_sum_for_renormalize;
#pragma unroll
for (int k_idx = 0; k_idx < k; ++k_idx) {
const int idx = k * thread_row + k_idx;
output[idx] = output[idx] * row_sum_for_renormalize_inv;
}
}
}
template <typename T, int EXPERTS, int WARPS_PER_TB>
void topk_gating_sigmoid_launcher_helper(const T* input,
const bool* finished,
float* output,
int* indices,
const int num_rows,
const int k,
const int start_expert,
const int end_expert,
const bool renormalize,
const float* correction_bias,
cudaStream_t stream) {
static constexpr std::size_t kMaxBytesPerLdg = 16;
static constexpr int kBytesPerLdg = MIN(kMaxBytesPerLdg, sizeof(T) * EXPERTS);
using Constants = TopkConstants<T, EXPERTS, kBytesPerLdg>;
static constexpr int kVpt = Constants::VPT;
static constexpr int kRowsPerWarp = Constants::ROWS_PER_WARP;
const int num_warps = (num_rows + kRowsPerWarp - 1) / kRowsPerWarp;
const int num_blocks = (num_warps + WARPS_PER_TB - 1) / WARPS_PER_TB;
dim3 block_dim(WARP_SIZE, WARPS_PER_TB);
topk_gating_sigmoid<T, kVpt, EXPERTS, WARPS_PER_TB, kBytesPerLdg>
<<<num_blocks, block_dim, 0, stream>>>(input,
finished,
output,
num_rows,
indices,
k,
start_expert,
end_expert,
renormalize,
correction_bias);
}
#define LAUNCH_SIGMOID(TYPE, NUM_EXPERTS, WARPS_PER_TB) \
topk_gating_sigmoid_launcher_helper<TYPE, NUM_EXPERTS, WARPS_PER_TB>( \
gating_output, \
nullptr, \
topk_weights, \
topk_indices, \
num_tokens, \
topk, \
0, \
num_experts, \
renormalize, \
correction_bias, \
stream);
template <typename T>
void topk_gating_sigmoid_kernel_launcher(const T* gating_output,
float* topk_weights,
int* topk_indices,
float* sigmoid_workspace,
const int num_tokens,
const int num_experts,
const int topk,
const bool renormalize,
const float* correction_bias,
cudaStream_t stream) {
static constexpr int kWarpsPerTb = 4;
switch (num_experts) {
case 1:
LAUNCH_SIGMOID(T, 1, kWarpsPerTb);
break;
case 2:
LAUNCH_SIGMOID(T, 2, kWarpsPerTb);
break;
case 4:
LAUNCH_SIGMOID(T, 4, kWarpsPerTb);
break;
case 8:
LAUNCH_SIGMOID(T, 8, kWarpsPerTb);
break;
case 16:
LAUNCH_SIGMOID(T, 16, kWarpsPerTb);
break;
case 32:
LAUNCH_SIGMOID(T, 32, kWarpsPerTb);
break;
case 64:
LAUNCH_SIGMOID(T, 64, kWarpsPerTb);
break;
case 128:
LAUNCH_SIGMOID(T, 128, kWarpsPerTb);
break;
case 256:
LAUNCH_SIGMOID(T, 256, kWarpsPerTb);
break;
default: {
TORCH_CHECK(sigmoid_workspace != nullptr,
"sigmoid_workspace must be provided for num_experts that are "
"not a power of 2.");
static constexpr int kTpb = 256;
moe_sigmoid<T, kTpb><<<num_tokens, kTpb, 0, stream>>>(gating_output,
nullptr,
sigmoid_workspace,
num_experts,
correction_bias);
moe_topK<kTpb><<<num_tokens, kTpb, 0, stream>>>(sigmoid_workspace,
nullptr,
topk_weights,
topk_indices,
num_experts,
topk,
0,
num_experts,
renormalize,
correction_bias);
}
}
}
} // namespace
namespace xllm::kernel::cuda {
void topk_sigmoid(torch::Tensor& topk_weights, // [num_tokens, topk]
torch::Tensor& topk_indices, // [num_tokens, topk]
torch::Tensor& gating_output, // [num_tokens, num_experts]
const bool renormalize,
const std::optional<torch::Tensor>& correction_bias) {
// Check data type
CHECK(gating_output.scalar_type() == at::ScalarType::Float ||
gating_output.scalar_type() == at::ScalarType::Half ||
gating_output.scalar_type() == at::ScalarType::BFloat16)
<< "gating_output must be float32, float16, or bfloat16";
// Check dimensions
CHECK(gating_output.dim() == 2)
<< "gating_output must be 2D tensor [num_tokens, num_experts]";
CHECK(topk_weights.dim() == 2)
<< "topk_weights must be 2D tensor [num_tokens, topk]";
CHECK(topk_indices.dim() == 2)
<< "topk_indices must be 2D tensor [num_tokens, topk]";
// Check shapes
CHECK(gating_output.size(0) == topk_weights.size(0))
<< "First dimension of topk_weights must match num_tokens in "
"gating_output";
CHECK(gating_output.size(0) == topk_indices.size(0))
<< "First dimension of topk_indices must match num_tokens in "
"gating_output";
CHECK(topk_weights.size(-1) == topk_indices.size(-1))
<< "Second dimension of topk_indices must match topk in topk_weights";
CHECK(topk_weights.size(-1) <= gating_output.size(-1))
<< "topk must be less than or equal to num_experts";
const int num_experts = static_cast<int>(gating_output.size(-1));
const int num_tokens = static_cast<int>(gating_output.size(0));
const int topk = static_cast<int>(topk_weights.size(-1));
const bool is_pow_2 =
(num_experts != 0) && ((num_experts & (num_experts - 1)) == 0);
const bool needs_workspace = !is_pow_2 || num_experts > 256;
const int64_t workspace_size = needs_workspace ? num_tokens * num_experts : 0;
const at::cuda::OptionalCUDAGuard device_guard(device_of(gating_output));
const cudaStream_t stream = at::cuda::getCurrentCUDAStream();
torch::Tensor sigmoid_workspace = torch::empty(
{workspace_size}, gating_output.options().dtype(at::ScalarType::Float));
const at::ScalarType dtype = gating_output.scalar_type();
// Validate correction_bias if provided - must always be float32
const float* bias_ptr = nullptr;
if (correction_bias.has_value()) {
const torch::Tensor& bias_tensor = correction_bias.value();
CHECK(bias_tensor.dim() == 1)
<< "correction_bias must be 1D tensor [num_experts]";
CHECK(bias_tensor.size(0) == num_experts)
<< "correction_bias size must match num_experts";
CHECK(bias_tensor.scalar_type() == at::ScalarType::Float)
<< "correction_bias must be float32, got " << bias_tensor.scalar_type();
bias_ptr = bias_tensor.data_ptr<float>();
}
if (dtype == at::ScalarType::Float) {
topk_gating_sigmoid_kernel_launcher<float>(
gating_output.data_ptr<float>(),
topk_weights.data_ptr<float>(),
topk_indices.data_ptr<int>(),
sigmoid_workspace.data_ptr<float>(),
num_tokens,
num_experts,
topk,
renormalize,
bias_ptr,
stream);
} else if (dtype == at::ScalarType::Half) {
topk_gating_sigmoid_kernel_launcher<__half>(
reinterpret_cast<const __half*>(gating_output.data_ptr<at::Half>()),
topk_weights.data_ptr<float>(),
topk_indices.data_ptr<int>(),
sigmoid_workspace.data_ptr<float>(),
num_tokens,
num_experts,
topk,
renormalize,
bias_ptr,
stream);
} else if (dtype == at::ScalarType::BFloat16) {
topk_gating_sigmoid_kernel_launcher<BFloat16Type>(
reinterpret_cast<const BFloat16Type*>(
gating_output.data_ptr<at::BFloat16>()),
topk_weights.data_ptr<float>(),
topk_indices.data_ptr<int>(),
sigmoid_workspace.data_ptr<float>(),
num_tokens,
num_experts,
topk,
renormalize,
bias_ptr,
stream);
} else {
LOG(FATAL) << "Unsupported gating_output dtype: " << dtype;
}
}
} // namespace xllm::kernel::cuda

View File

@@ -0,0 +1,866 @@
// Adapt from
// https://github.com/vllm-project/vllm/blob/v0.7.3/csrc/moe/topk_softmax_kernels.cu
// which is originally adapted from
// https://github.com/NVIDIA/TensorRT-LLM/blob/v0.7.1/cpp/tensorrt_llm/kernels/mixtureOfExperts/moe_kernels.cu
/* Copyright 2025 SGLang Team. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <ATen/cuda/CUDAContext.h>
#include <c10/cuda/CUDAGuard.h>
#include <torch/all.h>
#include <cub/util_type.cuh>
#if !defined(USE_DCU) && !defined(USE_MACA)
#endif
#include "device_utils.cuh"
using cub_kvp = cub::KeyValuePair<int, float>;
namespace {
using namespace xllm::kernel::cuda;
#if defined(USE_DCU)
static constexpr unsigned long long kSoftmaxFullMask = 0xffffffffffffffffULL;
#else
static constexpr unsigned int kSoftmaxFullMask = 0xffffffffU;
#endif
// ====================== Softmax things ===============================
// We have our own implementation of softmax here so we can support transposing
// the output in the softmax kernel when we extend this module to support
// expert-choice routing.
template <typename T, int TPB>
__launch_bounds__(TPB) __global__
void moe_softmax(const T* input,
const bool* finished,
float* output,
const int num_cols,
const float moe_softcapping,
const float* correction_bias) {
using BlockReduce = cub::BlockReduce<float, TPB>;
__shared__ typename BlockReduce::TempStorage tmpStorage;
__shared__ float normalizing_factor;
__shared__ float float_max;
const int thread_row_offset = blockIdx.x * num_cols;
float threadData(-FLT_MAX);
// Don't touch finished rows.
if ((finished != nullptr) && finished[blockIdx.x]) {
return;
}
// First pass: Apply transformation, find max, and write transformed values to
// output
for (int ii = threadIdx.x; ii < num_cols; ii += TPB) {
const int idx = thread_row_offset + ii;
float val = convert_to_float<T>(input[idx]);
// Apply tanh softcapping if enabled
if (moe_softcapping != 0.0f) {
val = tanhf(val / moe_softcapping) * moe_softcapping;
}
// Apply correction bias if provided
if (correction_bias != nullptr) {
val = val + correction_bias[ii];
}
output[idx] = val; // Store transformed value
threadData = max(val, threadData);
}
const float maxElem =
BlockReduce(tmpStorage).Reduce(threadData, MaxReduceOp());
if (threadIdx.x == 0) {
float_max = maxElem;
}
__syncthreads();
// Second pass: Compute sum using transformed values from output
threadData = 0;
for (int ii = threadIdx.x; ii < num_cols; ii += TPB) {
const int idx = thread_row_offset + ii;
threadData += exp((output[idx] - float_max));
}
const auto Z = BlockReduce(tmpStorage).Sum(threadData);
if (threadIdx.x == 0) {
normalizing_factor = 1.f / Z;
}
__syncthreads();
// Third pass: Compute final softmax using transformed values from output
for (int ii = threadIdx.x; ii < num_cols; ii += TPB) {
const int idx = thread_row_offset + ii;
const float softmax_val =
exp((output[idx] - float_max)) * normalizing_factor;
output[idx] = softmax_val;
}
}
namespace moe {
class TopKPair {
public:
static constexpr int kPair = 2;
static constexpr int kMaxIndex = 0;
cub_kvp max;
cub_kvp secondMax;
__device__ TopKPair() {}
__device__ TopKPair(cub_kvp max, cub_kvp secondMax)
: max(max), secondMax(secondMax) {}
};
class TopKPairArgMax {
public:
__device__ TopKPairArgMax() {}
__device__ __forceinline__ TopKPair
operator()(const TopKPair& candidate1, const TopKPair& candidate2) const {
cub_kvp globalMax, globalSecondMax;
// Determine the global maximum
if (candidate1.max.value > candidate2.max.value) {
globalMax = candidate1.max;
} else {
globalMax = candidate2.max;
}
// Determine the global second maximum
if (globalMax.key == candidate1.max.key) {
// If candidate1 contributed the max, compare its secondMax with
// candidate2's max
globalSecondMax = (candidate1.secondMax.value > candidate2.max.value)
? candidate1.secondMax
: candidate2.max;
} else {
// If candidate2 contributed the max, compare its secondMax with
// candidate1's max
globalSecondMax = (candidate2.secondMax.value > candidate1.max.value)
? candidate2.secondMax
: candidate1.max;
}
return TopKPair(globalMax, globalSecondMax);
}
};
} // namespace moe
template <int TPB>
__launch_bounds__(TPB) __global__
void moe_topk_fast(float* inputs_after_softmax,
const bool* finished,
float* output,
int* indices,
const int num_experts,
const int k,
const int start_expert,
const int end_expert,
const bool renormalize) {
using namespace moe;
using BlockReduce = cub::BlockReduce<TopKPair, TPB>;
__shared__ typename BlockReduce::TempStorage tmpStorage;
TopKPair thread_pair;
const int block_row = blockIdx.x;
const bool row_is_active = finished ? !finished[block_row] : true;
const int thread_read_offset = blockIdx.x * num_experts;
float row_sum_for_renormalize = 0;
// Each loop finds the top 2 elements,
// thus requiring only ceil(k / 2) loops (calculated as (k + 1) / 2).
for (int k_idx = 0; k_idx < (k + TopKPair::kPair - 1) / TopKPair::kPair;
++k_idx) {
// Initializing the top 2 elements by the minimum value.
thread_pair.max.key = 0;
thread_pair.max.value = -1.f;
thread_pair.secondMax.key = 0;
thread_pair.secondMax.value = -1.f;
cub_kvp inp_kvp;
for (int expert = threadIdx.x; expert < num_experts; expert += TPB) {
const int idx = thread_read_offset + expert;
inp_kvp.key = expert;
inp_kvp.value = inputs_after_softmax[idx];
// updating the thread_pair according to inp_kvp's value
if (inp_kvp.value > thread_pair.max.value) {
thread_pair.secondMax = thread_pair.max;
thread_pair.max = inp_kvp;
} else if (inp_kvp.value > thread_pair.secondMax.value) {
thread_pair.secondMax = inp_kvp;
}
}
TopKPairArgMax reducer;
const TopKPair result_pair =
BlockReduce(tmpStorage).Reduce(thread_pair, reducer);
if (threadIdx.x == 0) {
#pragma unroll
// updating 2 elements to the result.
for (int i = 0; i < TopKPair::kPair; i++) {
if (k_idx * 2 + i >= k) {
break;
}
cub_kvp result = (i == TopKPair::kMaxIndex) ? result_pair.max
: result_pair.secondMax;
int expert = result.key;
bool node_uses_expert = expert >= start_expert && expert < end_expert;
bool should_process_row = row_is_active && node_uses_expert;
// The inputs_after_softmax is modified in-place to avoid unnecessary
// loops for finding the top k-1 value. 1.f represents the minimum
// value.
inputs_after_softmax[thread_read_offset + expert] = -1.f;
int idx = k * block_row + k_idx * 2 + i;
output[idx] = result.value;
indices[idx] =
should_process_row ? (expert - start_expert) : num_experts;
assert(indices[idx] >= 0);
row_sum_for_renormalize += result.value;
}
}
__syncthreads();
}
if (renormalize && threadIdx.x == 0) {
float row_sum_for_renormalize_inv = 1.f / row_sum_for_renormalize;
for (int k_idx = 0; k_idx < k; ++k_idx) {
const int idx = k * block_row + k_idx;
output[idx] = output[idx] * row_sum_for_renormalize_inv;
}
}
}
template <int TPB>
__launch_bounds__(TPB) __global__ void moe_topK(float* inputs_after_softmax,
const bool* finished,
float* output,
int* indices,
const int num_experts,
const int k,
const int start_expert,
const int end_expert,
const bool renormalize) {
using cub_kvp = cub::KeyValuePair<int, float>;
using BlockReduce = cub::BlockReduce<cub_kvp, TPB>;
__shared__ typename BlockReduce::TempStorage tmpStorage;
cub_kvp thread_kvp;
cub::ArgMax arg_max;
const int block_row = blockIdx.x;
const bool row_is_active = finished ? !finished[block_row] : true;
const int thread_read_offset = blockIdx.x * num_experts;
float row_sum_for_renormalize = 0;
for (int k_idx = 0; k_idx < k; ++k_idx) {
thread_kvp.key = 0;
thread_kvp.value = -1.f; // This is OK because inputs are probabilities
cub_kvp inp_kvp;
for (int expert = threadIdx.x; expert < num_experts; expert += TPB) {
const int idx = thread_read_offset + expert;
inp_kvp.key = expert;
inp_kvp.value = inputs_after_softmax[idx];
thread_kvp = arg_max(inp_kvp, thread_kvp);
}
const cub_kvp result_kvp =
BlockReduce(tmpStorage).Reduce(thread_kvp, arg_max);
if (threadIdx.x == 0) {
// Ignore experts the node isn't responsible for with expert parallelism
const int expert = result_kvp.key;
const bool node_uses_expert =
expert >= start_expert && expert < end_expert;
const bool should_process_row = row_is_active && node_uses_expert;
const int idx = k * block_row + k_idx;
output[idx] = result_kvp.value;
indices[idx] = should_process_row ? (expert - start_expert) : num_experts;
assert(indices[idx] >= 0);
row_sum_for_renormalize += result_kvp.value;
// The inputs_after_softmax is modified in-place to avoid unnecessary
// loops for finding the top k-1 value. 1.f represents the minimum value.
inputs_after_softmax[thread_read_offset + expert] = -1.f;
}
__syncthreads();
}
if (renormalize && threadIdx.x == 0) {
float row_sum_for_renormalize_inv = 1.f / row_sum_for_renormalize;
for (int k_idx = 0; k_idx < k; ++k_idx) {
const int idx = k * block_row + k_idx;
output[idx] = output[idx] * row_sum_for_renormalize_inv;
}
}
}
// ====================== TopK softmax things ===============================
/*
A Top-K gating softmax written to exploit when the number of experts in the
MoE layers are a small power of 2. This allows us to cleanly share the rows
among the threads in a single warp and eliminate communication between warps
(so no need to use shared mem).
It fuses the softmax, max and argmax into a single kernel.
Limitations:
1) This implementation is intended for when the number of experts is a small
power of 2. 2) This implementation assumes k is small, but will work for any
k.
*/
template <typename T,
int VPT,
int NUM_EXPERTS,
int WARPS_PER_CTA,
int BYTES_PER_LDG>
__launch_bounds__(WARPS_PER_CTA* WARP_SIZE) __global__
void topk_gating_softmax(const T* input,
const bool* finished,
float* output,
const int num_rows,
int* indices,
const int k,
const int start_expert,
const int end_expert,
const bool renormalize,
const float moe_softcapping,
const float* correction_bias) {
// We begin by enforcing compile time assertions and setting up compile time
// constants.
static_assert(VPT == (VPT & -VPT), "VPT must be power of 2");
static_assert(NUM_EXPERTS == (NUM_EXPERTS & -NUM_EXPERTS),
"NUM_EXPERTS must be power of 2");
static_assert(BYTES_PER_LDG == (BYTES_PER_LDG & -BYTES_PER_LDG),
"BYTES_PER_LDG must be power of 2");
static_assert(BYTES_PER_LDG <= 16, "BYTES_PER_LDG must be leq 16");
// Number of bytes each thread pulls in per load
static constexpr int kEltsPerLdg = BYTES_PER_LDG / sizeof(T);
static constexpr int kEltsPerRow = NUM_EXPERTS;
static constexpr int kThreadsPerRow = kEltsPerRow / VPT;
static constexpr int kLdgPerThread = VPT / kEltsPerLdg;
// Restrictions based on previous section.
static_assert(
VPT % kEltsPerLdg == 0,
"The elements per thread must be a multiple of the elements per ldg");
static_assert(WARP_SIZE % kThreadsPerRow == 0,
"The threads per row must cleanly divide the threads per warp");
static_assert(kThreadsPerRow == (kThreadsPerRow & -kThreadsPerRow),
"THREADS_PER_ROW must be power of 2");
static_assert(kThreadsPerRow <= WARP_SIZE,
"THREADS_PER_ROW can be at most warp size");
// We have NUM_EXPERTS elements per row. We specialize for small #experts
static constexpr int kEltsPerWarp = WARP_SIZE * VPT;
static constexpr int kRowsPerWarp = kEltsPerWarp / kEltsPerRow;
static constexpr int kRowsPerCta = WARPS_PER_CTA * kRowsPerWarp;
// Restrictions for previous section.
static_assert(kEltsPerWarp % kEltsPerRow == 0,
"The elts per row must cleanly divide the total elt per warp");
// ===================== From this point, we finally start computing run-time
// variables. ========================
// Compute CTA and warp rows. We pack multiple rows into a single warp, and a
// block contains WARPS_PER_CTA warps. This, each block processes a chunk of
// rows. We start by computing the start row for each block.
const int cta_base_row = blockIdx.x * kRowsPerCta;
// Now, using the base row per thread block, we compute the base row per warp.
const int warp_base_row = cta_base_row + threadIdx.y * kRowsPerWarp;
// The threads in a warp are split into sub-groups that will work on a row.
// We compute row offset for each thread sub-group
const int thread_row_in_warp = threadIdx.x / kThreadsPerRow;
const int thread_row = warp_base_row + thread_row_in_warp;
// Threads with indices out of bounds should early exit here.
if (thread_row >= num_rows) {
return;
}
const bool row_is_active = finished ? !finished[thread_row] : true;
// We finally start setting up the read pointers for each thread. First, each
// thread jumps to the start of the row it will read.
const T* thread_row_ptr = input + thread_row * kEltsPerRow;
// Now, we compute the group each thread belong to in order to determine the
// first column to start loads.
const int thread_group_idx = threadIdx.x % kThreadsPerRow;
const int first_elt_read_by_thread = thread_group_idx * kEltsPerLdg;
const T* thread_read_ptr = thread_row_ptr + first_elt_read_by_thread;
// Determine the pointer type to use to read in the data depending on the
// BYTES_PER_LDG template param. In theory, this can support all powers of 2
// up to 16. NOTE(woosuk): The original implementation uses CUTLASS aligned
// array here. We defined our own aligned array and use it here to avoid the
// dependency on CUTLASS.
using AccessType = AlignedArray<T, kEltsPerLdg>;
// Finally, we pull in the data from global mem
T row_chunk_temp[VPT];
AccessType* row_chunk_vec_ptr =
reinterpret_cast<AccessType*>(&row_chunk_temp);
const AccessType* vec_thread_read_ptr =
reinterpret_cast<const AccessType*>(thread_read_ptr);
#pragma unroll
// Note(Byron): interleaved loads to achieve better memory coalescing
// | thread[0] | thread[1] | thread[2] | thread[3] | thread[0] | thread[1] |
// thread[2] | thread[3] | ...
for (int ii = 0; ii < kLdgPerThread; ++ii) {
row_chunk_vec_ptr[ii] = vec_thread_read_ptr[ii * kThreadsPerRow];
}
float row_chunk[VPT];
#pragma unroll
// Note(Byron): upcast logits to float32
for (int ii = 0; ii < VPT; ++ii) {
row_chunk[ii] = convert_to_float<T>(row_chunk_temp[ii]);
}
// Apply tanh softcapping and correction bias
if (moe_softcapping != 0.0f || correction_bias != nullptr) {
#pragma unroll
for (int ii = 0; ii < VPT; ++ii) {
float val = row_chunk[ii];
// Apply tanh softcapping if enabled
if (moe_softcapping != 0.0f) {
val = tanhf(val / moe_softcapping) * moe_softcapping;
}
// Apply correction bias if provided
if (correction_bias != nullptr) {
/*
LDG is interleaved
|thread0 LDG| |thread1 LDG| |thread0 LDG| |thread1 LDG|
|--------- group0 --------| |----------group1 --------|
^ local2
*/
const int group_id = ii / kEltsPerLdg;
const int local_id = ii % kEltsPerLdg;
const int expert_idx = first_elt_read_by_thread +
group_id * kThreadsPerRow * kEltsPerLdg +
local_id;
val = val + correction_bias[expert_idx];
}
row_chunk[ii] = val;
}
}
// First, we perform a max reduce within the thread. We can do the max in fp16
// safely (I think) and just convert to float afterwards for the exp + sum
// reduction.
float thread_max = row_chunk[0];
#pragma unroll
for (int ii = 1; ii < VPT; ++ii) {
thread_max = max(thread_max, row_chunk[ii]);
}
/*********************************/
/********* Softmax Begin *********/
/*********************************/
// Now, we find the max within the thread group and distribute among the
// threads. We use a butterfly reduce. lane id: 0-31 within a warp
#pragma unroll
for (int mask = kThreadsPerRow / 2; mask > 0; mask /= 2) {
// butterfly reduce with (lane id ^ mask)
thread_max = max(thread_max,
XLLM_SHFL_XOR_SYNC_WIDTH(
kSoftmaxFullMask, thread_max, mask, kThreadsPerRow));
}
// From this point, thread max in all the threads have the max within the row.
// Now, we subtract the max from each element in the thread and take the exp.
// We also compute the thread local sum.
float row_sum = 0;
#pragma unroll
for (int ii = 0; ii < VPT; ++ii) {
row_chunk[ii] = expf(row_chunk[ii] - thread_max);
row_sum += row_chunk[ii];
}
// Now, we perform the sum reduce within each thread group. Similar to the max
// reduce, we use a bufferfly pattern.
#pragma unroll
for (int mask = kThreadsPerRow / 2; mask > 0; mask /= 2) {
row_sum += XLLM_SHFL_XOR_SYNC_WIDTH(
kSoftmaxFullMask, row_sum, mask, kThreadsPerRow);
}
// From this point, all threads have the max and the sum for their rows in the
// thread_max and thread_sum variables respectively. Finally, we can scale the
// rows for the softmax. Technically, for top-k gating we don't need to
// compute the entire softmax row. We can likely look at the maxes and only
// compute for the top-k values in the row. However, this kernel will likely
// not be a bottle neck and it seems better to closer match torch and find the
// argmax after computing the softmax.
const float reciprocal_row_sum = 1.f / row_sum;
#pragma unroll
for (int ii = 0; ii < VPT; ++ii) {
row_chunk[ii] = row_chunk[ii] * reciprocal_row_sum;
}
/*******************************/
/********* Softmax End *********/
/*******************************/
// Now, softmax_res contains the softmax of the row chunk. Now, I want to find
// the topk elements in each row, along with the max index.
int start_col = first_elt_read_by_thread;
static constexpr int kColsPerGroupLdg = kEltsPerLdg * kThreadsPerRow;
float row_sum_for_renormalize = 0;
for (int k_idx = 0; k_idx < k; ++k_idx) {
// First, each thread does the local argmax
float max_val = row_chunk[0];
int expert = start_col;
#pragma unroll
for (int ldg = 0, col = start_col; ldg < kLdgPerThread;
++ldg, col += kColsPerGroupLdg) {
#pragma unroll
for (int ii = 0; ii < kEltsPerLdg; ++ii) {
float val = row_chunk[ldg * kEltsPerLdg + ii];
// No check on the experts here since columns with the smallest index
// are processed first and only updated if > (not >=)
if (val > max_val) {
max_val = val;
expert = col + ii;
}
}
}
// Now, we perform the argmax reduce. We use the butterfly pattern so threads
// reach consensus about the max. This will be useful for K > 1 so that the
// threads can agree on "who" had the max value. That thread can then blank out
// their max with -inf and the warp can run more iterations...
#pragma unroll
for (int mask = kThreadsPerRow / 2; mask > 0; mask /= 2) {
float other_max = XLLM_SHFL_XOR_SYNC_WIDTH(
kSoftmaxFullMask, max_val, mask, kThreadsPerRow);
int other_expert = XLLM_SHFL_XOR_SYNC_WIDTH(
kSoftmaxFullMask, expert, mask, kThreadsPerRow);
// We want lower indices to "win" in every thread so we break ties this
// way
if (other_max > max_val ||
(other_max == max_val && other_expert < expert)) {
max_val = other_max;
expert = other_expert;
}
}
// Write the max for this k iteration to global memory.
if (thread_group_idx == 0) {
// Add a guard to ignore experts not included by this node
const bool node_uses_expert =
expert >= start_expert && expert < end_expert;
const bool should_process_row = row_is_active && node_uses_expert;
// The lead thread from each sub-group will write out the final results to
// global memory. (This will be a single) thread per row of the
// input/output matrices.
const int idx = k * thread_row + k_idx;
output[idx] = max_val;
indices[idx] = should_process_row ? (expert - start_expert) : NUM_EXPERTS;
row_sum_for_renormalize += max_val;
}
// Finally, we clear the value in the thread with the current max if there
// is another iteration to run.
if (k_idx + 1 < k) {
const int ldg_group_for_expert = expert / kColsPerGroupLdg;
const int thread_to_clear_in_group =
(expert / kEltsPerLdg) % kThreadsPerRow;
// Only the thread in the group which produced the max will reset the
// "winning" value to -inf.
if (thread_group_idx == thread_to_clear_in_group) {
const int offset_for_expert = expert % kEltsPerLdg;
// Safe to set to any negative value since row_chunk values must be
// between 0 and 1.
row_chunk[ldg_group_for_expert * kEltsPerLdg + offset_for_expert] =
-10000.f;
}
}
}
// Fuse renormalization of topk_weights into this kernel
if (renormalize && thread_group_idx == 0) {
float row_sum_for_renormalize_inv = 1.f / row_sum_for_renormalize;
#pragma unroll
for (int k_idx = 0; k_idx < k; ++k_idx) {
const int idx = k * thread_row + k_idx;
output[idx] = output[idx] * row_sum_for_renormalize_inv;
}
}
}
template <typename T, int EXPERTS, int WARPS_PER_TB>
void topk_gating_softmax_launcher_helper(const T* input,
const bool* finished,
float* output,
int* indices,
const int num_rows,
const int k,
const int start_expert,
const int end_expert,
const bool renormalize,
const float moe_softcapping,
const float* correction_bias,
cudaStream_t stream) {
static constexpr std::size_t kMaxBytesPerLdg = 16;
static constexpr int kBytesPerLdg = MIN(kMaxBytesPerLdg, sizeof(T) * EXPERTS);
using Constants = TopkConstants<T, EXPERTS, kBytesPerLdg>;
static constexpr int kVpt = Constants::VPT;
static constexpr int kRowsPerWarp = Constants::ROWS_PER_WARP;
const int num_warps = (num_rows + kRowsPerWarp - 1) / kRowsPerWarp;
const int num_blocks = (num_warps + WARPS_PER_TB - 1) / WARPS_PER_TB;
dim3 block_dim(WARP_SIZE, WARPS_PER_TB);
topk_gating_softmax<T, kVpt, EXPERTS, WARPS_PER_TB, kBytesPerLdg>
<<<num_blocks, block_dim, 0, stream>>>(input,
finished,
output,
num_rows,
indices,
k,
start_expert,
end_expert,
renormalize,
moe_softcapping,
correction_bias);
}
#define LAUNCH_SOFTMAX(TYPE, NUM_EXPERTS, WARPS_PER_TB) \
topk_gating_softmax_launcher_helper<TYPE, NUM_EXPERTS, WARPS_PER_TB>( \
gating_output, \
nullptr, \
topk_weights, \
topk_indices, \
num_tokens, \
topk, \
0, \
num_experts, \
renormalize, \
moe_softcapping, \
correction_bias, \
stream);
template <typename T>
void topk_gating_softmax_kernel_launcher(const T* gating_output,
float* topk_weights,
int* topk_indices,
float* softmax_workspace,
const int num_tokens,
const int num_experts,
const int topk,
const bool renormalize,
const float moe_softcapping,
const float* correction_bias,
cudaStream_t stream) {
static constexpr int kWarpsPerTb = 4;
switch (num_experts) {
case 1:
LAUNCH_SOFTMAX(T, 1, kWarpsPerTb);
break;
case 2:
LAUNCH_SOFTMAX(T, 2, kWarpsPerTb);
break;
case 4:
LAUNCH_SOFTMAX(T, 4, kWarpsPerTb);
break;
case 8:
LAUNCH_SOFTMAX(T, 8, kWarpsPerTb);
break;
case 16:
LAUNCH_SOFTMAX(T, 16, kWarpsPerTb);
break;
case 32:
LAUNCH_SOFTMAX(T, 32, kWarpsPerTb);
break;
case 64:
LAUNCH_SOFTMAX(T, 64, kWarpsPerTb);
break;
case 128:
LAUNCH_SOFTMAX(T, 128, kWarpsPerTb);
break;
case 256:
LAUNCH_SOFTMAX(T, 256, kWarpsPerTb);
break;
default: {
CHECK(softmax_workspace != nullptr)
<< "softmax_workspace must be provided for num_experts that are "
"not a power of 2.";
static constexpr int kTpb = 256;
moe_softmax<T, kTpb><<<num_tokens, kTpb, 0, stream>>>(gating_output,
nullptr,
softmax_workspace,
num_experts,
moe_softcapping,
correction_bias);
if (topk == 1) {
// Note: As an optimization for better performance,
// the softmax_workspace is overwritten in-place by both moeTopK and
// moe_topk_fast.
moe_topK<kTpb><<<num_tokens, kTpb, 0, stream>>>(softmax_workspace,
nullptr,
topk_weights,
topk_indices,
num_experts,
topk,
0,
num_experts,
renormalize);
} else {
moe_topk_fast<kTpb><<<num_tokens, kTpb, 0, stream>>>(softmax_workspace,
nullptr,
topk_weights,
topk_indices,
num_experts,
topk,
0,
num_experts,
renormalize);
}
}
}
}
} // namespace
namespace xllm::kernel::cuda {
void topk_softmax(torch::Tensor& topk_weights, // [num_tokens, topk]
torch::Tensor& topk_indices, // [num_tokens, topk]
torch::Tensor& gating_output, // [num_tokens, num_experts]
const bool renormalize,
const double moe_softcapping,
const std::optional<torch::Tensor>& correction_bias) {
// Check data type
CHECK(gating_output.scalar_type() == at::ScalarType::Float ||
gating_output.scalar_type() == at::ScalarType::Half ||
gating_output.scalar_type() == at::ScalarType::BFloat16)
<< "gating_output must be float32, float16, or bfloat16";
// Check dimensions
CHECK(gating_output.dim() == 2)
<< "gating_output must be 2D tensor [num_tokens, num_experts]";
CHECK(topk_weights.dim() == 2)
<< "topk_weights must be 2D tensor [num_tokens, topk]";
CHECK(topk_indices.dim() == 2)
<< "topk_indices must be 2D tensor [num_tokens, topk]";
// Check shapes
CHECK(gating_output.size(0) == topk_weights.size(0))
<< "First dimension of topk_weights must match num_tokens in "
"gating_output"
<< "First dimension of topk_indices must match num_tokens in "
"gating_output";
CHECK(topk_weights.size(-1) == topk_indices.size(-1))
<< "Second dimension of topk_indices must match topk in topk_weights"
<< "topk must be less than or equal to num_experts";
const int num_experts = static_cast<int>(gating_output.size(-1));
const int num_tokens = static_cast<int>(gating_output.size(0));
const int topk = static_cast<int>(topk_weights.size(-1));
const bool is_pow_2 =
(num_experts != 0) && ((num_experts & (num_experts - 1)) == 0);
const bool needs_workspace = !is_pow_2 || num_experts > 256;
const int64_t workspace_size = needs_workspace ? num_tokens * num_experts : 0;
const at::cuda::OptionalCUDAGuard device_guard(device_of(gating_output));
const cudaStream_t stream = at::cuda::getCurrentCUDAStream();
torch::Tensor softmax_workspace = torch::empty(
{workspace_size}, gating_output.options().dtype(at::ScalarType::Float));
const at::ScalarType dtype = gating_output.scalar_type();
// Validate correction_bias if provided - must always be float32
const float* bias_ptr = nullptr;
if (correction_bias.has_value()) {
const torch::Tensor& bias_tensor = correction_bias.value();
CHECK(bias_tensor.dim() == 1)
<< "correction_bias must be 1D tensor [num_experts]";
CHECK(bias_tensor.size(0) == num_experts)
<< "correction_bias size must match num_experts";
CHECK(bias_tensor.scalar_type() == at::ScalarType::Float)
<< "correction_bias must be float32, got " << bias_tensor.scalar_type();
bias_ptr = bias_tensor.data_ptr<float>();
}
// Cast moe_softcapping from double to float for CUDA kernels
const float moe_softcapping_f = static_cast<float>(moe_softcapping);
if (dtype == at::ScalarType::Float) {
topk_gating_softmax_kernel_launcher<float>(
gating_output.data_ptr<float>(),
topk_weights.data_ptr<float>(),
topk_indices.data_ptr<int>(),
softmax_workspace.data_ptr<float>(),
num_tokens,
num_experts,
topk,
renormalize,
moe_softcapping_f,
bias_ptr,
stream);
} else if (dtype == at::ScalarType::Half) {
topk_gating_softmax_kernel_launcher<__half>(
reinterpret_cast<const __half*>(gating_output.data_ptr<at::Half>()),
topk_weights.data_ptr<float>(),
topk_indices.data_ptr<int>(),
softmax_workspace.data_ptr<float>(),
num_tokens,
num_experts,
topk,
renormalize,
moe_softcapping_f,
bias_ptr,
stream);
} else if (dtype == at::ScalarType::BFloat16) {
topk_gating_softmax_kernel_launcher<BFloat16Type>(
reinterpret_cast<const BFloat16Type*>(
gating_output.data_ptr<at::BFloat16>()),
topk_weights.data_ptr<float>(),
topk_indices.data_ptr<int>(),
softmax_workspace.data_ptr<float>(),
num_tokens,
num_experts,
topk,
renormalize,
moe_softcapping_f,
bias_ptr,
stream);
} else {
LOG(FATAL) << "Unsupported gating_output dtype: " << dtype;
}
}
} // namespace xllm::kernel::cuda

View File

@@ -0,0 +1,233 @@
// moe_cutlass_batched.cu — FP16 Cu10 TensorOp batched GEMM for MoE on BI-V100
//
// Adapted from corex-samples cutlass/examples/05_batched_gemm/batched_gemm.cu
// Changes from original:
// 1. float → cutlass::half_t (FP16 data)
// 2. arch::OpClassSimt → arch::OpClassTensorOp (use TCU)
// 3. arch::Sm61 → arch::Cu10 (BI-V100 arch)
// 4. ElementAccumulator = float (FP32 accumulation)
// 5. Row-major layout (PyTorch convention) instead of column-major
//
// Default Cu10 FP16 TensorOp config from default_gemm_configuration.h:
// ThreadblockShape = GemmShape<128, 128, 32>
// WarpShape = GemmShape<32, 32, 32>
// InstructionShape = GemmShape<16, 16, 16>
// kStages = 2
//
// This uses __ivcorex_matrix_mad_f32x4_f16x4 under the hood (via mma_cu10.h).
#include <iostream>
#include <vector>
#include "cutlass/cutlass.h"
#include "cutlass/numeric_types.h"
#include "cutlass/layout/matrix.h"
#include "cutlass/gemm/device/gemm_batched.h"
// FP16 batched GEMM using Cu10 TensorOp
// C[i] = alpha * A[i] @ B[i] + beta * C[i]
// All matrices row-major, FP16 in/out, FP32 accumulation.
cudaError_t cutlass_batched_hgemm_tensorop(
int m, int n, int k,
float alpha,
cutlass::half_t const *A, int lda, long long int batch_stride_A,
cutlass::half_t const *B, int ldb, long long int batch_stride_B,
cutlass::half_t *C, int ldc, long long int batch_stride_C,
float beta,
int batch_count)
{
using Gemm = cutlass::gemm::device::GemmBatched<
cutlass::half_t, // ElementA
cutlass::layout::RowMajor, // LayoutA
cutlass::half_t, // ElementB
cutlass::layout::RowMajor, // LayoutB
cutlass::half_t, // ElementC
cutlass::layout::RowMajor, // LayoutC
float, // ElementAccumulator
cutlass::arch::OpClassTensorOp, // OperatorClass — use TCU
cutlass::arch::Cu10 // ArchTag — BI-V100
// Remaining params use defaults from DefaultGemmConfiguration:
// ThreadblockShape = <128, 128, 32>
// WarpShape = <32, 32, 32>
// InstructionShape = <16, 16, 16>
// Stages = 2
>;
Gemm gemm_op;
cutlass::Status status = gemm_op({
{m, n, k},
{A, lda},
batch_stride_A,
{B, ldb},
batch_stride_B,
{C, ldc},
batch_stride_C,
{C, ldc},
batch_stride_C,
{alpha, beta},
batch_count
});
if (status != cutlass::Status::kSuccess) {
return cudaErrorUnknown;
}
return cudaSuccess;
}
// ============================================================================
// Standalone test
// ============================================================================
#ifdef BUILD_STANDALONE_TEST
#include <cuda_fp16.h>
#include <cstdio>
#include <cstdlib>
#include <cmath>
int main() {
// Test: 8 batches of (1, 256) @ (256, 128) — simulates decode MoE
int m = 1, n = 128, k = 256;
int batch_count = 8;
float alpha = 1.0f, beta = 0.0f;
int lda = k; // row-major: (m, k), stride = k
int ldb = n; // row-major: (k, n), stride = n
int ldc = n; // row-major: (m, n), stride = n
long long int stride_A = (long long)m * k;
long long int stride_B = (long long)k * n;
long long int stride_C = (long long)m * n;
size_t size_A = batch_count * stride_A * sizeof(cutlass::half_t);
size_t size_B = batch_count * stride_B * sizeof(cutlass::half_t);
size_t size_C = batch_count * stride_C * sizeof(cutlass::half_t);
// Allocate host
std::vector<cutlass::half_t> h_A(batch_count * stride_A);
std::vector<cutlass::half_t> h_B(batch_count * stride_B);
std::vector<cutlass::half_t> h_C(batch_count * stride_C, cutlass::half_t(0.0f));
// Fill with small values
for (auto &v : h_A) v = cutlass::half_t(0.01f * (rand() % 100 - 50));
for (auto &v : h_B) v = cutlass::half_t(0.01f * (rand() % 100 - 50));
// Allocate device
cutlass::half_t *d_A, *d_B, *d_C;
cudaMalloc(&d_A, size_A);
cudaMalloc(&d_B, size_B);
cudaMalloc(&d_C, size_C);
cudaMemcpy(d_A, h_A.data(), size_A, cudaMemcpyHostToDevice);
cudaMemcpy(d_B, h_B.data(), size_B, cudaMemcpyHostToDevice);
cudaMemcpy(d_C, h_C.data(), size_C, cudaMemcpyHostToDevice);
// Run CUTLASS batched GEMM
cudaError_t result = cutlass_batched_hgemm_tensorop(
m, n, k, alpha,
d_A, lda, stride_A,
d_B, ldb, stride_B,
d_C, ldc, stride_C,
beta, batch_count);
cudaDeviceSynchronize();
if (result != cudaSuccess) {
printf("CUTLASS batched GEMM FAILED: %s\n", cudaGetErrorString(result));
cudaError_t last = cudaGetLastError();
if (last != cudaSuccess)
printf("Last CUDA error: %s\n", cudaGetErrorString(last));
cudaFree(d_A); cudaFree(d_B); cudaFree(d_C);
return -1;
}
// Copy back
cudaMemcpy(h_C.data(), d_C, size_C, cudaMemcpyDeviceToHost);
// Verify against CPU reference
bool pass = true;
for (int b = 0; b < batch_count; b++) {
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
float ref = 0.0f;
for (int p = 0; p < k; p++) {
float a_val = float(h_A[b * stride_A + i * k + p]);
float b_val = float(h_B[b * stride_B + p * n + j]);
ref += a_val * b_val;
}
float got = float(h_C[b * stride_C + i * n + j]);
if (fabs(ref - got) > 1.0f) {
printf("MISMATCH batch=%d [%d,%d]: ref=%.4f got=%.4f\n",
b, i, j, ref, got);
pass = false;
}
}
}
}
if (pass) {
printf("CUTLASS Cu10 TensorOp batched HGEMM: PASSED (%d batches of %dx%d@%dx%d)\n",
batch_count, m, k, k, n);
}
// Benchmark
cudaEvent_t t0, t1;
cudaEventCreate(&t0);
cudaEventCreate(&t1);
// Warmup
for (int i = 0; i < 5; i++)
cutlass_batched_hgemm_tensorop(m, n, k, alpha,
d_A, lda, stride_A, d_B, ldb, stride_B,
d_C, ldc, stride_C, beta, batch_count);
cudaDeviceSynchronize();
cudaEventRecord(t0);
for (int i = 0; i < 100; i++)
cutlass_batched_hgemm_tensorop(m, n, k, alpha,
d_A, lda, stride_A, d_B, ldb, stride_B,
d_C, ldc, stride_C, beta, batch_count);
cudaEventRecord(t1);
cudaEventSynchronize(t1);
float ms;
cudaEventElapsedTime(&ms, t0, t1);
printf("Perf: %.3f ms/iter (8 batches of 1x256 @ 256x128)\n", ms / 100.0f);
// Also test MoE-sized: 8 batches of (1, 4096) @ (4096, 11008)
int m2 = 1, n2 = 11008, k2 = 4096;
long long stride_A2 = (long long)m2 * k2;
long long stride_B2 = (long long)k2 * n2;
long long stride_C2 = (long long)m2 * n2;
cutlass::half_t *d_A2, *d_B2, *d_C2;
cudaMalloc(&d_A2, batch_count * stride_A2 * sizeof(cutlass::half_t));
cudaMalloc(&d_B2, batch_count * stride_B2 * sizeof(cutlass::half_t));
cudaMalloc(&d_C2, batch_count * stride_C2 * sizeof(cutlass::half_t));
for (int i = 0; i < 5; i++)
cutlass_batched_hgemm_tensorop(m2, n2, k2, alpha,
d_A2, k2, stride_A2, d_B2, n2, stride_B2,
d_C2, n2, stride_C2, beta, batch_count);
cudaDeviceSynchronize();
cudaEventRecord(t0);
for (int i = 0; i < 20; i++)
cutlass_batched_hgemm_tensorop(m2, n2, k2, alpha,
d_A2, k2, stride_A2, d_B2, n2, stride_B2,
d_C2, n2, stride_C2, beta, batch_count);
cudaEventRecord(t1);
cudaEventSynchronize(t1);
cudaEventElapsedTime(&ms, t0, t1);
printf("Perf: %.3f ms/iter (8 batches of 1x4096 @ 4096x11008 — MoE decode)\n", ms / 20.0f);
cudaFree(d_A); cudaFree(d_B); cudaFree(d_C);
cudaFree(d_A2); cudaFree(d_B2); cudaFree(d_C2);
cudaEventDestroy(t0);
cudaEventDestroy(t1);
return pass ? 0 : -1;
}
#endif // BUILD_STANDALONE_TEST

View File

@@ -0,0 +1,595 @@
/* Copyright 2025 The vLLM Authors and The xLLM Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <c10/cuda/CUDAGuard.h>
#include <torch/cuda.h>
#include <cstdint>
#include <cub/cub.cuh>
#include "device_utils.cuh"
#include "fp8_quant_utils.cuh"
#include "type_convert.cuh"
// ref to:
// https://github.com/vllm-project/vllm/blob/main/csrc/layernorm_kernels.cu
// corex CUB (CUDA 10.2) — use old-style CUB operators
using CubAddOp = cub::Sum;
using CubMaxOp = cub::Max;
namespace {
using namespace xllm::kernel::cuda;
template <typename scalar_t>
__global__ void XLLM_KERNEL_ATTR(1024)
rms_norm_kernel(scalar_t* __restrict__ out, // [..., hidden_size]
const scalar_t* __restrict__ input, // [..., hidden_size]
const int64_t input_stride,
const scalar_t* __restrict__ weight, // [hidden_size]
const float epsilon,
const int num_tokens,
const int hidden_size) {
__shared__ float s_variance;
float variance = 0.0f;
for (int idx = threadIdx.x; idx < hidden_size; idx += blockDim.x) {
const float x = static_cast<float>(input[blockIdx.x * input_stride + idx]);
variance += x * x;
}
using BlockReduce = cub::BlockReduce<float, 1024>;
__shared__ typename BlockReduce::TempStorage reduceStore;
variance = BlockReduce(reduceStore).Reduce(variance, CubAddOp{}, blockDim.x);
if (threadIdx.x == 0) {
s_variance = rsqrtf(variance / hidden_size + epsilon);
}
__syncthreads();
for (int idx = threadIdx.x; idx < hidden_size; idx += blockDim.x) {
float x = static_cast<float>(input[blockIdx.x * input_stride + idx]);
out[blockIdx.x * hidden_size + idx] =
(static_cast<scalar_t>(x * s_variance)) * weight[idx];
}
}
/* Function specialization in the case of FP16/BF16 tensors.
Additional optimizations we can make in this case are
packed and vectorized operations, which help with the
memory latency bottleneck. */
template <typename scalar_t, int width>
__global__ std::enable_if_t<(width > 0) && _typeConvert<scalar_t>::exists>
XLLM_KERNEL_ATTR(1024) fused_add_rms_norm_kernel(
scalar_t* __restrict__ input, // [..., hidden_size]
const int64_t input_stride,
scalar_t* __restrict__ residual, // [..., hidden_size]
const scalar_t* __restrict__ weight, // [hidden_size]
const float epsilon,
const int num_tokens,
const int hidden_size) {
// Sanity checks on our vector struct and type-punned pointer arithmetic
static_assert(std::is_pod_v<_f16Vec<scalar_t, width>>);
static_assert(sizeof(_f16Vec<scalar_t, width>) == sizeof(scalar_t) * width);
const int vec_hidden_size = hidden_size / width;
const int64_t vec_input_stride = input_stride / width;
__shared__ float s_variance;
float variance = 0.0f;
/* These and the argument pointers are all declared `restrict` as they are
not aliased in practice. Argument pointers should not be dereferenced
in this kernel as that would be undefined behavior */
auto* __restrict__ input_v =
reinterpret_cast<_f16Vec<scalar_t, width>*>(input);
auto* __restrict__ residual_v =
reinterpret_cast<_f16Vec<scalar_t, width>*>(residual);
auto* __restrict__ weight_v =
reinterpret_cast<const _f16Vec<scalar_t, width>*>(weight);
for (int idx = threadIdx.x; idx < vec_hidden_size; idx += blockDim.x) {
int id = blockIdx.x * vec_hidden_size + idx;
int64_t strided_id = blockIdx.x * vec_input_stride + idx;
_f16Vec<scalar_t, width> temp = input_v[strided_id];
temp += residual_v[id];
variance += temp.sum_squares();
residual_v[id] = temp;
}
using BlockReduce = cub::BlockReduce<float, 1024>;
__shared__ typename BlockReduce::TempStorage reduceStore;
variance = BlockReduce(reduceStore).Reduce(variance, CubAddOp{}, blockDim.x);
if (threadIdx.x == 0) {
s_variance = rsqrtf(variance / hidden_size + epsilon);
}
__syncthreads();
for (int idx = threadIdx.x; idx < vec_hidden_size; idx += blockDim.x) {
int id = blockIdx.x * vec_hidden_size + idx;
int64_t strided_id = blockIdx.x * vec_input_stride + idx;
_f16Vec<scalar_t, width> temp = residual_v[id];
temp *= s_variance;
temp *= weight_v[idx];
input_v[strided_id] = temp;
}
}
/* Generic fused_add_rms_norm_kernel
The width field is not used here but necessary for other specializations.
*/
template <typename scalar_t, int width>
__global__ std::enable_if_t<(width == 0) || !_typeConvert<scalar_t>::exists>
XLLM_KERNEL_ATTR(1024) fused_add_rms_norm_kernel(
scalar_t* __restrict__ input, // [..., hidden_size]
const int64_t input_stride,
scalar_t* __restrict__ residual, // [..., hidden_size]
const scalar_t* __restrict__ weight, // [hidden_size]
const float epsilon,
const int num_tokens,
const int hidden_size) {
__shared__ float s_variance;
float variance = 0.0f;
for (int idx = threadIdx.x; idx < hidden_size; idx += blockDim.x) {
scalar_t z = input[blockIdx.x * input_stride + idx];
z += residual[blockIdx.x * hidden_size + idx];
float x = static_cast<float>(z);
variance += x * x;
residual[blockIdx.x * hidden_size + idx] = z;
}
using BlockReduce = cub::BlockReduce<float, 1024>;
__shared__ typename BlockReduce::TempStorage reduceStore;
variance = BlockReduce(reduceStore).Reduce(variance, CubAddOp{}, blockDim.x);
if (threadIdx.x == 0) {
s_variance = rsqrtf(variance / hidden_size + epsilon);
}
__syncthreads();
for (int idx = threadIdx.x; idx < hidden_size; idx += blockDim.x) {
float x = static_cast<float>(residual[blockIdx.x * hidden_size + idx]);
input[blockIdx.x * input_stride + idx] =
(static_cast<scalar_t>(x * s_variance)) * weight[idx];
}
}
#define LAUNCH_FUSED_ADD_RMS_NORM(width) \
DISPATCH_FLOATING_TYPES( \
input.scalar_type(), "fused_add_rms_norm_kernel", [&] { \
fused_add_rms_norm_kernel<scalar_t, width> \
<<<grid, block, 0, stream>>>(input.data_ptr<scalar_t>(), \
input_stride, \
residual.data_ptr<scalar_t>(), \
weight.data_ptr<scalar_t>(), \
epsilon, \
num_tokens, \
hidden_size); \
});
// ============================================================================
// Fused RMSNorm + Static FP8 Quantization Kernels
// ============================================================================
// These kernels combine RMSNorm and FP8 quantization to reduce memory
// bandwidth by avoiding the intermediate write-back to global memory.
// Dispatch macro for FP8 types
#define DISPATCH_FP8_TYPES(TYPE, NAME, ...) \
[&] { \
const auto& the_type = TYPE; \
switch (the_type) { \
case at::ScalarType::Float8_e4m3fn: { \
using fp8_t = c10::Float8_e4m3fn; \
return __VA_ARGS__(); \
} \
default: \
AT_ERROR(#NAME, \
" not implemented for FP8 type '", \
toString(the_type), \
"'"); \
} \
}()
/**
* Fused RMSNorm + Static FP8 Quantization kernel (without residual)
* Combines RMSNorm and FP8 quantization in a single kernel to reduce
* memory bandwidth by avoiding intermediate write-back.
*
* @tparam scalar_t Input data type (float, half, bfloat16)
* @tparam fp8_type Output FP8 type (c10::Float8_e4m3fn)
* @param out Output FP8 tensor [num_tokens, hidden_size]
* @param input Input tensor [num_tokens, hidden_size]
* @param input_stride Stride of input tensor in the token dimension
* @param weight RMSNorm weight tensor [hidden_size]
* @param scale FP8 quantization scale (scalar)
* @param epsilon RMSNorm epsilon
* @param num_tokens Number of tokens
* @param hidden_size Hidden dimension size
*/
template <typename scalar_t, typename fp8_type>
__global__ void rms_norm_static_fp8_quant_kernel(
fp8_type* __restrict__ out, // [num_tokens, hidden_size]
const scalar_t* __restrict__ input, // [num_tokens, hidden_size]
const int64_t input_stride,
const scalar_t* __restrict__ weight, // [hidden_size]
const float* __restrict__ scale, // [1]
const float epsilon,
const int num_tokens,
const int hidden_size) {
__shared__ float s_variance;
float variance = 0.0f;
const scalar_t* input_row = input + blockIdx.x * input_stride;
// Step 1: Compute variance for RMSNorm
for (int idx = threadIdx.x; idx < hidden_size; idx += blockDim.x) {
const float x = static_cast<float>(input_row[idx]);
variance += x * x;
}
using BlockReduce = cub::BlockReduce<float, 1024>;
__shared__ typename BlockReduce::TempStorage reduceStore;
variance = BlockReduce(reduceStore).Reduce(variance, CubAddOp{}, blockDim.x);
if (threadIdx.x == 0) {
s_variance = rsqrtf(variance / hidden_size + epsilon);
}
__syncthreads();
// Step 2: Precompute scale inverse to avoid division
const float scale_inv = 1.0f / (*scale);
// Step 3: Fused RMSNorm + FP8 quantization
for (int idx = threadIdx.x; idx < hidden_size; idx += blockDim.x) {
float x = static_cast<float>(input_row[idx]);
float out_norm = (static_cast<scalar_t>(x * s_variance)) *
static_cast<float>(weight[idx]);
out[blockIdx.x * hidden_size + idx] =
xllm::kernel::cuda::scaled_fp8_conversion<true, fp8_type>(out_norm,
scale_inv);
}
}
/**
* Fused Add + RMSNorm + Static FP8 Quantization kernel (with residual)
* Optimized version with packed + vectorized operations for FP16/BF16.
*
* @tparam scalar_t Input data type (float, half, bfloat16)
* @tparam width Vector width for optimization (0, 8)
* @tparam fp8_type Output FP8 type (c10::Float8_e4m3fn)
*/
template <typename scalar_t, int width, typename fp8_type>
__global__ std::enable_if_t<(width > 0) && _typeConvert<scalar_t>::exists>
fused_add_rms_norm_static_fp8_quant_kernel(
fp8_type* __restrict__ out, // [num_tokens, hidden_size]
scalar_t* __restrict__ input, // [num_tokens, hidden_size]
const int64_t input_stride,
scalar_t* __restrict__ residual, // [num_tokens, hidden_size]
const scalar_t* __restrict__ weight, // [hidden_size]
const float* __restrict__ scale, // [1]
const float epsilon,
const int num_tokens,
const int hidden_size) {
static_assert(std::is_pod_v<_f16Vec<scalar_t, width>>);
static_assert(sizeof(_f16Vec<scalar_t, width>) == sizeof(scalar_t) * width);
const int vec_hidden_size = hidden_size / width;
const int64_t vec_input_stride = input_stride / width;
__shared__ float s_variance;
float variance = 0.0f;
auto* __restrict__ input_v =
reinterpret_cast<_f16Vec<scalar_t, width>*>(input);
auto* __restrict__ residual_v =
reinterpret_cast<_f16Vec<scalar_t, width>*>(residual);
auto* __restrict__ weight_v =
reinterpret_cast<const _f16Vec<scalar_t, width>*>(weight);
// Step 1: Fused add and compute variance
for (int idx = threadIdx.x; idx < vec_hidden_size; idx += blockDim.x) {
int id = blockIdx.x * vec_hidden_size + idx;
int64_t strided_id = blockIdx.x * vec_input_stride + idx;
_f16Vec<scalar_t, width> temp = input_v[strided_id];
temp += residual_v[id];
variance += temp.sum_squares();
residual_v[id] = temp; // Store updated residual
}
using BlockReduce = cub::BlockReduce<float, 1024>;
__shared__ typename BlockReduce::TempStorage reduceStore;
variance = BlockReduce(reduceStore).Reduce(variance, CubAddOp{}, blockDim.x);
if (threadIdx.x == 0) {
s_variance = rsqrtf(variance / hidden_size + epsilon);
}
__syncthreads();
// Step 2: Precompute scale inverse
const float scale_inv = 1.0f / (*scale);
// Step 3: Fused RMSNorm + FP8 quantization
for (int idx = threadIdx.x; idx < vec_hidden_size; idx += blockDim.x) {
int id = blockIdx.x * vec_hidden_size + idx;
_f16Vec<scalar_t, width> temp = residual_v[id];
temp *= s_variance;
temp *= weight_v[idx];
// Convert each element to FP8
#pragma unroll
for (int i = 0; i < width; ++i) {
float val = _typeConvert<scalar_t>::convert(temp.data[i]);
out[id * width + i] =
xllm::kernel::cuda::scaled_fp8_conversion<true, fp8_type>(val,
scale_inv);
}
}
}
/**
* Generic fused add + RMSNorm + FP8 quant kernel (fallback for unaligned data)
*/
template <typename scalar_t, int width, typename fp8_type>
__global__ std::enable_if_t<(width == 0) || !_typeConvert<scalar_t>::exists>
fused_add_rms_norm_static_fp8_quant_kernel(
fp8_type* __restrict__ out, // [num_tokens, hidden_size]
scalar_t* __restrict__ input, // [num_tokens, hidden_size]
const int64_t input_stride,
scalar_t* __restrict__ residual, // [num_tokens, hidden_size]
const scalar_t* __restrict__ weight, // [hidden_size]
const float* __restrict__ scale, // [1]
const float epsilon,
const int num_tokens,
const int hidden_size) {
__shared__ float s_variance;
float variance = 0.0f;
// Step 1: Fused add and compute variance
for (int idx = threadIdx.x; idx < hidden_size; idx += blockDim.x) {
scalar_t z = input[blockIdx.x * input_stride + idx];
z += residual[blockIdx.x * hidden_size + idx];
float x = static_cast<float>(z);
variance += x * x;
residual[blockIdx.x * hidden_size + idx] = z; // Store updated residual
}
using BlockReduce = cub::BlockReduce<float, 1024>;
__shared__ typename BlockReduce::TempStorage reduceStore;
variance = BlockReduce(reduceStore).Reduce(variance, CubAddOp{}, blockDim.x);
if (threadIdx.x == 0) {
s_variance = rsqrtf(variance / hidden_size + epsilon);
}
__syncthreads();
// Step 2: Precompute scale inverse
const float scale_inv = 1.0f / (*scale);
// Step 3: Fused RMSNorm + FP8 quantization
for (int idx = threadIdx.x; idx < hidden_size; idx += blockDim.x) {
float x = static_cast<float>(residual[blockIdx.x * hidden_size + idx]);
float out_norm = (static_cast<scalar_t>(x * s_variance)) *
static_cast<float>(weight[idx]);
out[blockIdx.x * hidden_size + idx] =
xllm::kernel::cuda::scaled_fp8_conversion<true, fp8_type>(out_norm,
scale_inv);
}
}
#define LAUNCH_FUSED_ADD_RMS_NORM_STATIC_FP8_QUANT(width) \
DISPATCH_FLOATING_TYPES( \
input.scalar_type(), "fused_add_rms_norm_static_fp8_quant", [&] { \
DISPATCH_FP8_TYPES( \
out.scalar_type(), "fused_add_rms_norm_static_fp8_quant", [&] { \
fused_add_rms_norm_static_fp8_quant_kernel<scalar_t, \
width, \
fp8_t> \
<<<grid, block, 0, stream>>>(out.data_ptr<fp8_t>(), \
input.data_ptr<scalar_t>(), \
input_stride, \
residual.data_ptr<scalar_t>(), \
weight.data_ptr<scalar_t>(), \
scale.data_ptr<float>(), \
epsilon, \
num_tokens, \
hidden_size); \
}); \
});
} // namespace
namespace xllm::kernel::cuda {
// flashinfer rmsnorm ops
// void rmsnorm(torch::Tensor output,
// torch::Tensor input,
// torch::Tensor weight,
// double eps) {
// FunctionFactory::get_instance().rmsnorm_func("norm").call(
// output, input, weight, eps, support_pdl());
// }
void rms_norm(torch::Tensor output, // [..., hidden_size]
torch::Tensor input, // [..., hidden_size]
torch::Tensor weight, // [hidden_size]
double eps) {
CHECK(output.is_contiguous());
CHECK(weight.is_contiguous());
// The kernel addresses tokens as `blockIdx.x * input_stride + idx`, which
// can only represent contiguous inputs or simple 2D strided rows. Flux q/k
// tensors reach this path as high-dimensional transposed views, so make that
// layout explicit before flattening tokens for the kernel.
if (input.dim() > 2 && !input.is_contiguous()) {
input = input.contiguous();
}
CHECK(input.stride(-1) == 1);
int hidden_size = input.size(-1);
int num_tokens = input.numel() / hidden_size;
int64_t input_stride = input.stride(-2);
dim3 grid(num_tokens);
dim3 block(std::min(hidden_size, 1024));
const at::cuda::OptionalCUDAGuard device_guard(device_of(input));
const cudaStream_t stream = at::cuda::getCurrentCUDAStream();
DISPATCH_FLOATING_TYPES(input.scalar_type(), "rms_norm_kernel", [&] {
rms_norm_kernel<scalar_t>
<<<grid, block, 0, stream>>>(output.data_ptr<scalar_t>(),
input.data_ptr<scalar_t>(),
input_stride,
weight.data_ptr<scalar_t>(),
eps,
num_tokens,
hidden_size);
});
}
void fused_add_rms_norm(torch::Tensor& input, // [..., hidden_size]
torch::Tensor& residual, // [..., hidden_size]
torch::Tensor& weight, // [hidden_size]
double epsilon) {
CHECK(weight.scalar_type() == input.scalar_type());
CHECK(input.scalar_type() == residual.scalar_type());
CHECK(residual.is_contiguous());
CHECK(weight.is_contiguous());
int hidden_size = input.size(-1);
int64_t input_stride = input.stride(-2);
int num_tokens = input.numel() / hidden_size;
dim3 grid(num_tokens);
/* This kernel is memory-latency bound in many scenarios.
When num_tokens is large, a smaller block size allows
for increased block occupancy on CUs and better latency
hiding on global mem ops. */
const int max_block_size = (num_tokens < 256) ? 1024 : 256;
dim3 block(std::min(hidden_size, max_block_size));
const at::cuda::OptionalCUDAGuard device_guard(device_of(input));
const cudaStream_t stream = at::cuda::getCurrentCUDAStream();
/*If the tensor types are FP16/BF16, try to use the optimized kernel
with packed + vectorized ops.
Max optimization is achieved with a width-8 vector of FP16/BF16s
since we can load at most 128 bits at once in a global memory op.
However, this requires each tensor's data to be aligned to 16
bytes.
*/
auto inp_ptr = reinterpret_cast<std::uintptr_t>(input.data_ptr());
auto res_ptr = reinterpret_cast<std::uintptr_t>(residual.data_ptr());
auto wt_ptr = reinterpret_cast<std::uintptr_t>(weight.data_ptr());
constexpr int kVectorWidth = 8;
constexpr int kReqAlignmentBytes =
kVectorWidth * 2; // kVectorWidth * sizeof(bfloat16 or float16) (float32
// falls back to non-vectorized version anyway)
bool ptrs_are_aligned = inp_ptr % kReqAlignmentBytes == 0 &&
res_ptr % kReqAlignmentBytes == 0 &&
wt_ptr % kReqAlignmentBytes == 0;
bool offsets_are_multiple_of_vector_width =
hidden_size % kVectorWidth == 0 && input_stride % kVectorWidth == 0;
if (ptrs_are_aligned && offsets_are_multiple_of_vector_width) {
LAUNCH_FUSED_ADD_RMS_NORM(8);
} else {
LAUNCH_FUSED_ADD_RMS_NORM(0);
}
}
// ============================================================================
// Fused RMSNorm + Static FP8 Quantization Host Functions
// ============================================================================
void rms_norm_static_fp8_quant(torch::Tensor& out, // [..., hidden_size], FP8
torch::Tensor& input, // [..., hidden_size]
torch::Tensor& weight, // [hidden_size]
torch::Tensor& scale, // [1]
double epsilon) {
CHECK(out.is_contiguous());
CHECK(input.stride(-1) == 1);
CHECK(weight.is_contiguous());
CHECK(scale.is_contiguous());
int hidden_size = input.size(-1);
int64_t input_stride = input.stride(-2);
int num_tokens = input.numel() / hidden_size;
// For large num_tokens, use smaller blocks to increase SM concurrency
const int max_block_size = (num_tokens < 256) ? 1024 : 256;
dim3 grid(num_tokens);
dim3 block(std::min(hidden_size, max_block_size));
const at::cuda::OptionalCUDAGuard device_guard(device_of(input));
const cudaStream_t stream = at::cuda::getCurrentCUDAStream();
DISPATCH_FLOATING_TYPES(
input.scalar_type(), "rms_norm_static_fp8_quant", [&] {
DISPATCH_FP8_TYPES(out.scalar_type(), "rms_norm_static_fp8_quant", [&] {
rms_norm_static_fp8_quant_kernel<scalar_t, fp8_t>
<<<grid, block, 0, stream>>>(out.data_ptr<fp8_t>(),
input.data_ptr<scalar_t>(),
input_stride,
weight.data_ptr<scalar_t>(),
scale.data_ptr<float>(),
epsilon,
num_tokens,
hidden_size);
});
});
}
void fused_add_rms_norm_static_fp8_quant(
torch::Tensor& out, // [..., hidden_size], FP8
torch::Tensor& input, // [..., hidden_size]
torch::Tensor& residual, // [..., hidden_size]
torch::Tensor& weight, // [hidden_size]
torch::Tensor& scale, // [1]
double epsilon) {
CHECK(out.is_contiguous());
CHECK(residual.is_contiguous());
CHECK(weight.is_contiguous());
CHECK(scale.is_contiguous());
CHECK(residual.scalar_type() == input.scalar_type());
CHECK(weight.scalar_type() == input.scalar_type());
int hidden_size = input.size(-1);
int64_t input_stride = input.stride(-2);
int num_tokens = input.numel() / hidden_size;
dim3 grid(num_tokens);
const int max_block_size = (num_tokens < 256) ? 1024 : 256;
dim3 block(std::min(hidden_size, max_block_size));
const at::cuda::OptionalCUDAGuard device_guard(device_of(input));
const cudaStream_t stream = at::cuda::getCurrentCUDAStream();
// Check alignment for vectorized kernel
auto inp_ptr = reinterpret_cast<std::uintptr_t>(input.data_ptr());
auto res_ptr = reinterpret_cast<std::uintptr_t>(residual.data_ptr());
auto wt_ptr = reinterpret_cast<std::uintptr_t>(weight.data_ptr());
constexpr int kVectorWidth = 8;
constexpr int kReqAlignmentBytes = kVectorWidth * 2;
bool ptrs_are_aligned = inp_ptr % kReqAlignmentBytes == 0 &&
res_ptr % kReqAlignmentBytes == 0 &&
wt_ptr % kReqAlignmentBytes == 0;
bool offsets_are_multiple_of_vector_width =
hidden_size % kVectorWidth == 0 && input_stride % kVectorWidth == 0;
if (ptrs_are_aligned && offsets_are_multiple_of_vector_width) {
LAUNCH_FUSED_ADD_RMS_NORM_STATIC_FP8_QUANT(8);
} else {
LAUNCH_FUSED_ADD_RMS_NORM_STATIC_FP8_QUANT(0);
}
}
} // namespace xllm::kernel::cuda

View File

@@ -0,0 +1,600 @@
/* Copyright 2025 The vLLM Authors and The xLLM Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <c10/cuda/CUDAGuard.h>
#include <torch/cuda.h>
#include <cstdint>
#include <cub/cub.cuh>
#include "cuda_ops_api.h"
#include "device_utils.cuh"
#include "fp8_quant_utils.cuh"
#include "type_convert.cuh"
// ref to:
// https://github.com/vllm-project/vllm/blob/main/csrc/layernorm_kernels.cu
#if CUB_VERSION >= 200800
#include <cuda/std/functional>
using CubAddOp = ::cuda::std::plus<>;
using CubMaxOp = ::cuda::maximum<>;
#else // if CUB_VERSION < 200800
using CubAddOp = cub::Sum;
using CubMaxOp = cub::Max;
#endif // CUB_VERSION
namespace {
using namespace xllm::kernel::cuda;
template <typename scalar_t>
__global__ void XLLM_KERNEL_ATTR(1024)
rms_norm_kernel(scalar_t* __restrict__ out, // [..., hidden_size]
const scalar_t* __restrict__ input, // [..., hidden_size]
const int64_t input_stride,
const scalar_t* __restrict__ weight, // [hidden_size]
const float epsilon,
const int num_tokens,
const int hidden_size) {
__shared__ float s_variance;
float variance = 0.0f;
for (int idx = threadIdx.x; idx < hidden_size; idx += blockDim.x) {
const float x = static_cast<float>(input[blockIdx.x * input_stride + idx]);
variance += x * x;
}
using BlockReduce = cub::BlockReduce<float, 1024>;
__shared__ typename BlockReduce::TempStorage reduceStore;
variance = BlockReduce(reduceStore).Reduce(variance, CubAddOp{}, blockDim.x);
if (threadIdx.x == 0) {
s_variance = rsqrtf(variance / hidden_size + epsilon);
}
__syncthreads();
for (int idx = threadIdx.x; idx < hidden_size; idx += blockDim.x) {
float x = static_cast<float>(input[blockIdx.x * input_stride + idx]);
out[blockIdx.x * hidden_size + idx] =
(static_cast<scalar_t>(x * s_variance)) * weight[idx];
}
}
/* Function specialization in the case of FP16/BF16 tensors.
Additional optimizations we can make in this case are
packed and vectorized operations, which help with the
memory latency bottleneck. */
template <typename scalar_t, int width>
__global__ std::enable_if_t<(width > 0) && _typeConvert<scalar_t>::exists>
XLLM_KERNEL_ATTR(1024) fused_add_rms_norm_kernel(
scalar_t* __restrict__ input, // [..., hidden_size]
const int64_t input_stride,
scalar_t* __restrict__ residual, // [..., hidden_size]
const scalar_t* __restrict__ weight, // [hidden_size]
const float epsilon,
const int num_tokens,
const int hidden_size) {
// Sanity checks on our vector struct and type-punned pointer arithmetic
static_assert(std::is_pod_v<_f16Vec<scalar_t, width>>);
static_assert(sizeof(_f16Vec<scalar_t, width>) == sizeof(scalar_t) * width);
const int vec_hidden_size = hidden_size / width;
const int64_t vec_input_stride = input_stride / width;
__shared__ float s_variance;
float variance = 0.0f;
/* These and the argument pointers are all declared `restrict` as they are
not aliased in practice. Argument pointers should not be dereferenced
in this kernel as that would be undefined behavior */
auto* __restrict__ input_v =
reinterpret_cast<_f16Vec<scalar_t, width>*>(input);
auto* __restrict__ residual_v =
reinterpret_cast<_f16Vec<scalar_t, width>*>(residual);
auto* __restrict__ weight_v =
reinterpret_cast<const _f16Vec<scalar_t, width>*>(weight);
for (int idx = threadIdx.x; idx < vec_hidden_size; idx += blockDim.x) {
int id = blockIdx.x * vec_hidden_size + idx;
int64_t strided_id = blockIdx.x * vec_input_stride + idx;
_f16Vec<scalar_t, width> temp = input_v[strided_id];
temp += residual_v[id];
variance += temp.sum_squares();
residual_v[id] = temp;
}
using BlockReduce = cub::BlockReduce<float, 1024>;
__shared__ typename BlockReduce::TempStorage reduceStore;
variance = BlockReduce(reduceStore).Reduce(variance, CubAddOp{}, blockDim.x);
if (threadIdx.x == 0) {
s_variance = rsqrtf(variance / hidden_size + epsilon);
}
__syncthreads();
for (int idx = threadIdx.x; idx < vec_hidden_size; idx += blockDim.x) {
int id = blockIdx.x * vec_hidden_size + idx;
int64_t strided_id = blockIdx.x * vec_input_stride + idx;
_f16Vec<scalar_t, width> temp = residual_v[id];
temp *= s_variance;
temp *= weight_v[idx];
input_v[strided_id] = temp;
}
}
/* Generic fused_add_rms_norm_kernel
The width field is not used here but necessary for other specializations.
*/
template <typename scalar_t, int width>
__global__ std::enable_if_t<(width == 0) || !_typeConvert<scalar_t>::exists>
XLLM_KERNEL_ATTR(1024) fused_add_rms_norm_kernel(
scalar_t* __restrict__ input, // [..., hidden_size]
const int64_t input_stride,
scalar_t* __restrict__ residual, // [..., hidden_size]
const scalar_t* __restrict__ weight, // [hidden_size]
const float epsilon,
const int num_tokens,
const int hidden_size) {
__shared__ float s_variance;
float variance = 0.0f;
for (int idx = threadIdx.x; idx < hidden_size; idx += blockDim.x) {
scalar_t z = input[blockIdx.x * input_stride + idx];
z += residual[blockIdx.x * hidden_size + idx];
float x = static_cast<float>(z);
variance += x * x;
residual[blockIdx.x * hidden_size + idx] = z;
}
using BlockReduce = cub::BlockReduce<float, 1024>;
__shared__ typename BlockReduce::TempStorage reduceStore;
variance = BlockReduce(reduceStore).Reduce(variance, CubAddOp{}, blockDim.x);
if (threadIdx.x == 0) {
s_variance = rsqrtf(variance / hidden_size + epsilon);
}
__syncthreads();
for (int idx = threadIdx.x; idx < hidden_size; idx += blockDim.x) {
float x = static_cast<float>(residual[blockIdx.x * hidden_size + idx]);
input[blockIdx.x * input_stride + idx] =
(static_cast<scalar_t>(x * s_variance)) * weight[idx];
}
}
#define LAUNCH_FUSED_ADD_RMS_NORM(width) \
DISPATCH_FLOATING_TYPES( \
input.scalar_type(), "fused_add_rms_norm_kernel", [&] { \
fused_add_rms_norm_kernel<scalar_t, width> \
<<<grid, block, 0, stream>>>(input.data_ptr<scalar_t>(), \
input_stride, \
residual.data_ptr<scalar_t>(), \
weight.data_ptr<scalar_t>(), \
epsilon, \
num_tokens, \
hidden_size); \
});
// ============================================================================
// Fused RMSNorm + Static FP8 Quantization Kernels
// ============================================================================
// These kernels combine RMSNorm and FP8 quantization to reduce memory
// bandwidth by avoiding the intermediate write-back to global memory.
// Dispatch macro for FP8 types
#define DISPATCH_FP8_TYPES(TYPE, NAME, ...) \
[&] { \
const auto& the_type = TYPE; \
switch (the_type) { \
case at::ScalarType::Float8_e4m3fn: { \
using fp8_t = c10::Float8_e4m3fn; \
return __VA_ARGS__(); \
} \
default: \
AT_ERROR(#NAME, \
" not implemented for FP8 type '", \
toString(the_type), \
"'"); \
} \
}()
/**
* Fused RMSNorm + Static FP8 Quantization kernel (without residual)
* Combines RMSNorm and FP8 quantization in a single kernel to reduce
* memory bandwidth by avoiding intermediate write-back.
*
* @tparam scalar_t Input data type (float, half, bfloat16)
* @tparam fp8_type Output FP8 type (c10::Float8_e4m3fn)
* @param out Output FP8 tensor [num_tokens, hidden_size]
* @param input Input tensor [num_tokens, hidden_size]
* @param input_stride Stride of input tensor in the token dimension
* @param weight RMSNorm weight tensor [hidden_size]
* @param scale FP8 quantization scale (scalar)
* @param epsilon RMSNorm epsilon
* @param num_tokens Number of tokens
* @param hidden_size Hidden dimension size
*/
template <typename scalar_t, typename fp8_type>
__global__ void rms_norm_static_fp8_quant_kernel(
fp8_type* __restrict__ out, // [num_tokens, hidden_size]
const scalar_t* __restrict__ input, // [num_tokens, hidden_size]
const int64_t input_stride,
const scalar_t* __restrict__ weight, // [hidden_size]
const float* __restrict__ scale, // [1]
const float epsilon,
const int num_tokens,
const int hidden_size) {
__shared__ float s_variance;
float variance = 0.0f;
const scalar_t* input_row = input + blockIdx.x * input_stride;
// Step 1: Compute variance for RMSNorm
for (int idx = threadIdx.x; idx < hidden_size; idx += blockDim.x) {
const float x = static_cast<float>(input_row[idx]);
variance += x * x;
}
using BlockReduce = cub::BlockReduce<float, 1024>;
__shared__ typename BlockReduce::TempStorage reduceStore;
variance = BlockReduce(reduceStore).Reduce(variance, CubAddOp{}, blockDim.x);
if (threadIdx.x == 0) {
s_variance = rsqrtf(variance / hidden_size + epsilon);
}
__syncthreads();
// Step 2: Precompute scale inverse to avoid division
const float scale_inv = 1.0f / (*scale);
// Step 3: Fused RMSNorm + FP8 quantization
for (int idx = threadIdx.x; idx < hidden_size; idx += blockDim.x) {
float x = static_cast<float>(input_row[idx]);
float out_norm = (static_cast<scalar_t>(x * s_variance)) *
static_cast<float>(weight[idx]);
out[blockIdx.x * hidden_size + idx] =
xllm::kernel::cuda::scaled_fp8_conversion<true, fp8_type>(out_norm,
scale_inv);
}
}
/**
* Fused Add + RMSNorm + Static FP8 Quantization kernel (with residual)
* Optimized version with packed + vectorized operations for FP16/BF16.
*
* @tparam scalar_t Input data type (float, half, bfloat16)
* @tparam width Vector width for optimization (0, 8)
* @tparam fp8_type Output FP8 type (c10::Float8_e4m3fn)
*/
template <typename scalar_t, int width, typename fp8_type>
__global__ std::enable_if_t<(width > 0) && _typeConvert<scalar_t>::exists>
fused_add_rms_norm_static_fp8_quant_kernel(
fp8_type* __restrict__ out, // [num_tokens, hidden_size]
scalar_t* __restrict__ input, // [num_tokens, hidden_size]
const int64_t input_stride,
scalar_t* __restrict__ residual, // [num_tokens, hidden_size]
const scalar_t* __restrict__ weight, // [hidden_size]
const float* __restrict__ scale, // [1]
const float epsilon,
const int num_tokens,
const int hidden_size) {
static_assert(std::is_pod_v<_f16Vec<scalar_t, width>>);
static_assert(sizeof(_f16Vec<scalar_t, width>) == sizeof(scalar_t) * width);
const int vec_hidden_size = hidden_size / width;
const int64_t vec_input_stride = input_stride / width;
__shared__ float s_variance;
float variance = 0.0f;
auto* __restrict__ input_v =
reinterpret_cast<_f16Vec<scalar_t, width>*>(input);
auto* __restrict__ residual_v =
reinterpret_cast<_f16Vec<scalar_t, width>*>(residual);
auto* __restrict__ weight_v =
reinterpret_cast<const _f16Vec<scalar_t, width>*>(weight);
// Step 1: Fused add and compute variance
for (int idx = threadIdx.x; idx < vec_hidden_size; idx += blockDim.x) {
int id = blockIdx.x * vec_hidden_size + idx;
int64_t strided_id = blockIdx.x * vec_input_stride + idx;
_f16Vec<scalar_t, width> temp = input_v[strided_id];
temp += residual_v[id];
variance += temp.sum_squares();
residual_v[id] = temp; // Store updated residual
}
using BlockReduce = cub::BlockReduce<float, 1024>;
__shared__ typename BlockReduce::TempStorage reduceStore;
variance = BlockReduce(reduceStore).Reduce(variance, CubAddOp{}, blockDim.x);
if (threadIdx.x == 0) {
s_variance = rsqrtf(variance / hidden_size + epsilon);
}
__syncthreads();
// Step 2: Precompute scale inverse
const float scale_inv = 1.0f / (*scale);
// Step 3: Fused RMSNorm + FP8 quantization
for (int idx = threadIdx.x; idx < vec_hidden_size; idx += blockDim.x) {
int id = blockIdx.x * vec_hidden_size + idx;
_f16Vec<scalar_t, width> temp = residual_v[id];
temp *= s_variance;
temp *= weight_v[idx];
// Convert each element to FP8
#pragma unroll
for (int i = 0; i < width; ++i) {
float val = _typeConvert<scalar_t>::convert(temp.data[i]);
out[id * width + i] =
xllm::kernel::cuda::scaled_fp8_conversion<true, fp8_type>(val,
scale_inv);
}
}
}
/**
* Generic fused add + RMSNorm + FP8 quant kernel (fallback for unaligned data)
*/
template <typename scalar_t, int width, typename fp8_type>
__global__ std::enable_if_t<(width == 0) || !_typeConvert<scalar_t>::exists>
fused_add_rms_norm_static_fp8_quant_kernel(
fp8_type* __restrict__ out, // [num_tokens, hidden_size]
scalar_t* __restrict__ input, // [num_tokens, hidden_size]
const int64_t input_stride,
scalar_t* __restrict__ residual, // [num_tokens, hidden_size]
const scalar_t* __restrict__ weight, // [hidden_size]
const float* __restrict__ scale, // [1]
const float epsilon,
const int num_tokens,
const int hidden_size) {
__shared__ float s_variance;
float variance = 0.0f;
// Step 1: Fused add and compute variance
for (int idx = threadIdx.x; idx < hidden_size; idx += blockDim.x) {
scalar_t z = input[blockIdx.x * input_stride + idx];
z += residual[blockIdx.x * hidden_size + idx];
float x = static_cast<float>(z);
variance += x * x;
residual[blockIdx.x * hidden_size + idx] = z; // Store updated residual
}
using BlockReduce = cub::BlockReduce<float, 1024>;
__shared__ typename BlockReduce::TempStorage reduceStore;
variance = BlockReduce(reduceStore).Reduce(variance, CubAddOp{}, blockDim.x);
if (threadIdx.x == 0) {
s_variance = rsqrtf(variance / hidden_size + epsilon);
}
__syncthreads();
// Step 2: Precompute scale inverse
const float scale_inv = 1.0f / (*scale);
// Step 3: Fused RMSNorm + FP8 quantization
for (int idx = threadIdx.x; idx < hidden_size; idx += blockDim.x) {
float x = static_cast<float>(residual[blockIdx.x * hidden_size + idx]);
float out_norm = (static_cast<scalar_t>(x * s_variance)) *
static_cast<float>(weight[idx]);
out[blockIdx.x * hidden_size + idx] =
xllm::kernel::cuda::scaled_fp8_conversion<true, fp8_type>(out_norm,
scale_inv);
}
}
#define LAUNCH_FUSED_ADD_RMS_NORM_STATIC_FP8_QUANT(width) \
DISPATCH_FLOATING_TYPES( \
input.scalar_type(), "fused_add_rms_norm_static_fp8_quant", [&] { \
DISPATCH_FP8_TYPES( \
out.scalar_type(), "fused_add_rms_norm_static_fp8_quant", [&] { \
fused_add_rms_norm_static_fp8_quant_kernel<scalar_t, \
width, \
fp8_t> \
<<<grid, block, 0, stream>>>(out.data_ptr<fp8_t>(), \
input.data_ptr<scalar_t>(), \
input_stride, \
residual.data_ptr<scalar_t>(), \
weight.data_ptr<scalar_t>(), \
scale.data_ptr<float>(), \
epsilon, \
num_tokens, \
hidden_size); \
}); \
});
} // namespace
namespace xllm::kernel::cuda {
// flashinfer rmsnorm ops
// void rmsnorm(torch::Tensor output,
// torch::Tensor input,
// torch::Tensor weight,
// double eps) {
// FunctionFactory::get_instance().rmsnorm_func("norm").call(
// output, input, weight, eps, support_pdl());
// }
void rms_norm(torch::Tensor output, // [..., hidden_size]
torch::Tensor input, // [..., hidden_size]
torch::Tensor weight, // [hidden_size]
double eps) {
CHECK(output.is_contiguous());
CHECK(weight.is_contiguous());
// The kernel addresses tokens as `blockIdx.x * input_stride + idx`, which
// can only represent contiguous inputs or simple 2D strided rows. Flux q/k
// tensors reach this path as high-dimensional transposed views, so make that
// layout explicit before flattening tokens for the kernel.
if (input.dim() > 2 && !input.is_contiguous()) {
input = input.contiguous();
}
CHECK(input.stride(-1) == 1);
int hidden_size = input.size(-1);
int num_tokens = input.numel() / hidden_size;
int64_t input_stride = input.stride(-2);
dim3 grid(num_tokens);
dim3 block(std::min(hidden_size, 1024));
const at::cuda::OptionalCUDAGuard device_guard(device_of(input));
const cudaStream_t stream = at::cuda::getCurrentCUDAStream();
DISPATCH_FLOATING_TYPES(input.scalar_type(), "rms_norm_kernel", [&] {
rms_norm_kernel<scalar_t>
<<<grid, block, 0, stream>>>(output.data_ptr<scalar_t>(),
input.data_ptr<scalar_t>(),
input_stride,
weight.data_ptr<scalar_t>(),
eps,
num_tokens,
hidden_size);
});
}
void fused_add_rms_norm(torch::Tensor& input, // [..., hidden_size]
torch::Tensor& residual, // [..., hidden_size]
torch::Tensor& weight, // [hidden_size]
double epsilon) {
CHECK(weight.scalar_type() == input.scalar_type());
CHECK(input.scalar_type() == residual.scalar_type());
CHECK(residual.is_contiguous());
CHECK(weight.is_contiguous());
int hidden_size = input.size(-1);
int64_t input_stride = input.stride(-2);
int num_tokens = input.numel() / hidden_size;
dim3 grid(num_tokens);
/* This kernel is memory-latency bound in many scenarios.
When num_tokens is large, a smaller block size allows
for increased block occupancy on CUs and better latency
hiding on global mem ops. */
const int max_block_size = (num_tokens < 256) ? 1024 : 256;
dim3 block(std::min(hidden_size, max_block_size));
const at::cuda::OptionalCUDAGuard device_guard(device_of(input));
const cudaStream_t stream = at::cuda::getCurrentCUDAStream();
/*If the tensor types are FP16/BF16, try to use the optimized kernel
with packed + vectorized ops.
Max optimization is achieved with a width-8 vector of FP16/BF16s
since we can load at most 128 bits at once in a global memory op.
However, this requires each tensor's data to be aligned to 16
bytes.
*/
auto inp_ptr = reinterpret_cast<std::uintptr_t>(input.data_ptr());
auto res_ptr = reinterpret_cast<std::uintptr_t>(residual.data_ptr());
auto wt_ptr = reinterpret_cast<std::uintptr_t>(weight.data_ptr());
constexpr int kVectorWidth = 8;
constexpr int kReqAlignmentBytes =
kVectorWidth * 2; // kVectorWidth * sizeof(bfloat16 or float16) (float32
// falls back to non-vectorized version anyway)
bool ptrs_are_aligned = inp_ptr % kReqAlignmentBytes == 0 &&
res_ptr % kReqAlignmentBytes == 0 &&
wt_ptr % kReqAlignmentBytes == 0;
bool offsets_are_multiple_of_vector_width =
hidden_size % kVectorWidth == 0 && input_stride % kVectorWidth == 0;
if (ptrs_are_aligned && offsets_are_multiple_of_vector_width) {
LAUNCH_FUSED_ADD_RMS_NORM(8);
} else {
LAUNCH_FUSED_ADD_RMS_NORM(0);
}
}
// ============================================================================
// Fused RMSNorm + Static FP8 Quantization Host Functions
// ============================================================================
void rms_norm_static_fp8_quant(torch::Tensor& out, // [..., hidden_size], FP8
torch::Tensor& input, // [..., hidden_size]
torch::Tensor& weight, // [hidden_size]
torch::Tensor& scale, // [1]
double epsilon) {
CHECK(out.is_contiguous());
CHECK(input.stride(-1) == 1);
CHECK(weight.is_contiguous());
CHECK(scale.is_contiguous());
int hidden_size = input.size(-1);
int64_t input_stride = input.stride(-2);
int num_tokens = input.numel() / hidden_size;
// For large num_tokens, use smaller blocks to increase SM concurrency
const int max_block_size = (num_tokens < 256) ? 1024 : 256;
dim3 grid(num_tokens);
dim3 block(std::min(hidden_size, max_block_size));
const at::cuda::OptionalCUDAGuard device_guard(device_of(input));
const cudaStream_t stream = at::cuda::getCurrentCUDAStream();
DISPATCH_FLOATING_TYPES(
input.scalar_type(), "rms_norm_static_fp8_quant", [&] {
DISPATCH_FP8_TYPES(out.scalar_type(), "rms_norm_static_fp8_quant", [&] {
rms_norm_static_fp8_quant_kernel<scalar_t, fp8_t>
<<<grid, block, 0, stream>>>(out.data_ptr<fp8_t>(),
input.data_ptr<scalar_t>(),
input_stride,
weight.data_ptr<scalar_t>(),
scale.data_ptr<float>(),
epsilon,
num_tokens,
hidden_size);
});
});
}
void fused_add_rms_norm_static_fp8_quant(
torch::Tensor& out, // [..., hidden_size], FP8
torch::Tensor& input, // [..., hidden_size]
torch::Tensor& residual, // [..., hidden_size]
torch::Tensor& weight, // [hidden_size]
torch::Tensor& scale, // [1]
double epsilon) {
CHECK(out.is_contiguous());
CHECK(residual.is_contiguous());
CHECK(weight.is_contiguous());
CHECK(scale.is_contiguous());
CHECK(residual.scalar_type() == input.scalar_type());
CHECK(weight.scalar_type() == input.scalar_type());
int hidden_size = input.size(-1);
int64_t input_stride = input.stride(-2);
int num_tokens = input.numel() / hidden_size;
dim3 grid(num_tokens);
const int max_block_size = (num_tokens < 256) ? 1024 : 256;
dim3 block(std::min(hidden_size, max_block_size));
const at::cuda::OptionalCUDAGuard device_guard(device_of(input));
const cudaStream_t stream = at::cuda::getCurrentCUDAStream();
// Check alignment for vectorized kernel
auto inp_ptr = reinterpret_cast<std::uintptr_t>(input.data_ptr());
auto res_ptr = reinterpret_cast<std::uintptr_t>(residual.data_ptr());
auto wt_ptr = reinterpret_cast<std::uintptr_t>(weight.data_ptr());
constexpr int kVectorWidth = 8;
constexpr int kReqAlignmentBytes = kVectorWidth * 2;
bool ptrs_are_aligned = inp_ptr % kReqAlignmentBytes == 0 &&
res_ptr % kReqAlignmentBytes == 0 &&
wt_ptr % kReqAlignmentBytes == 0;
bool offsets_are_multiple_of_vector_width =
hidden_size % kVectorWidth == 0 && input_stride % kVectorWidth == 0;
if (ptrs_are_aligned && offsets_are_multiple_of_vector_width) {
LAUNCH_FUSED_ADD_RMS_NORM_STATIC_FP8_QUANT(8);
} else {
LAUNCH_FUSED_ADD_RMS_NORM_STATIC_FP8_QUANT(0);
}
}
} // namespace xllm::kernel::cuda

View File

@@ -0,0 +1,102 @@
/* Copyright 2025-2026 The xLLM Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <c10/cuda/CUDAStream.h>
#include <torch/extension.h>
#include "device_utils.cuh"
namespace xllm::kernel::cuda {
template <typename T>
__global__ void XLLM_KERNEL_ATTR(1024) reshape_paged_cache_kernel(
const int* __restrict__ slot_ids, // [n_tokens]
const T* __restrict__ keys, // [n_tokens, n_heads, head_dim]
const T* __restrict__ values, // [n_tokens, n_heads, head_dim]
T* __restrict__ key_cache,
T* __restrict__ value_cache,
int64_t k_stride,
int64_t v_stride,
int64_t n_kv_heads,
int64_t head_dim,
int64_t block_size) {
// block/token index
const int64_t bid = blockIdx.x;
// which slot to write to
const int64_t slot_id = slot_ids[bid];
if (slot_id < 0) {
return;
}
// block index
const int64_t block_idx = slot_id / block_size;
// offset within block
const int64_t block_offset = slot_id % block_size;
// base index for the block in cache
const int64_t block_base_idx = block_idx * block_size * n_kv_heads * head_dim;
// copy value one by one for the token
for (int64_t i = threadIdx.x; i < n_kv_heads * head_dim; i += blockDim.x) {
const int64_t k_src_idx = bid * k_stride + i;
const int64_t v_src_idx = bid * v_stride + i;
// cache: [n_blocks, block_size, n_heads, head_dim]
const int64_t head_base_idx =
block_base_idx + block_offset * n_kv_heads * head_dim;
// which head to write to
const int head_idx = i / head_dim;
// which dim within head to write to
const int head_offset = i % head_dim;
const int64_t dst_idx = head_base_idx + head_idx * head_dim + head_offset;
key_cache[dst_idx] = keys[k_src_idx];
value_cache[dst_idx] = values[v_src_idx];
}
}
void reshape_paged_cache(
torch::Tensor slot_ids, // [n_tokens]
torch::Tensor keys, // [n_tokens, n_kv_heads, head_dim]
torch::Tensor values, // [n_tokens, n_kv_heads, head_dim]
torch::Tensor key_cache, // [n_blocks, block_size, n_heads, head_dim]
torch::Tensor value_cache) {
// keys and values should be continuous at n_kv_heads and head_dim dims
CHECK(keys.stride(-1) == 1 && keys.stride(-2) == keys.size(-1));
CHECK(values.stride(-1) == 1 && values.stride(-2) == values.size(-1));
const int64_t n_tokens = keys.size(-3);
const int64_t n_kv_heads = keys.size(-2);
const int64_t head_dim = keys.size(-1);
const int64_t block_size = key_cache.size(-3);
// it is possible that keys and values have different strides
const int64_t k_stride = keys.stride(-3);
const int64_t v_stride = values.stride(-3);
const int64_t n = n_kv_heads * head_dim;
dim3 grid(n_tokens);
dim3 block(std::min<int>(n, 1024));
DISPATCH_FLOATING_TYPES(
keys.scalar_type(), "reshape_paged_cache_kernel", [&] {
reshape_paged_cache_kernel<scalar_t>
<<<grid, block, 0, c10::cuda::getCurrentCUDAStream()>>>(
slot_ids.data_ptr<int>(),
keys.data_ptr<scalar_t>(),
values.data_ptr<scalar_t>(),
key_cache.data_ptr<scalar_t>(),
value_cache.data_ptr<scalar_t>(),
k_stride,
v_stride,
n_kv_heads,
head_dim,
block_size);
});
}
} // namespace xllm::kernel::cuda

View File

@@ -0,0 +1,258 @@
/* Copyright 2025 The vLLM Authors and The xLLM Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <ATen/cuda/CUDAContext.h>
#include <c10/cuda/CUDAGuard.h>
#include <torch/all.h>
#include "device_utils.cuh"
// ref to:
// https://github.com/vllm-project/vllm/blob/main/csrc/pos_encoding_kernels.cu
namespace {
template <typename scalar_t, bool IS_NEOX>
inline __device__ void apply_token_rotary_embedding(
scalar_t* __restrict__ arr,
const scalar_t* __restrict__ cos_ptr,
const scalar_t* __restrict__ sin_ptr,
int rot_offset,
int embed_dim) {
int x_index, y_index;
scalar_t cos, sin;
if (IS_NEOX) {
// GPT-NeoX style rotary embedding.
x_index = rot_offset;
y_index = embed_dim + rot_offset;
cos = *(cos_ptr + x_index);
sin = *(sin_ptr + x_index);
} else {
// GPT-J style rotary embedding.
x_index = 2 * rot_offset;
y_index = 2 * rot_offset + 1;
cos = *(cos_ptr + x_index / 2);
sin = *(sin_ptr + x_index / 2);
}
const scalar_t x = arr[x_index];
const scalar_t y = arr[y_index];
arr[x_index] = x * cos - y * sin;
arr[y_index] = y * cos + x * sin;
}
template <typename scalar_t, bool IS_NEOX>
inline __device__ void apply_rotary_embedding(
scalar_t* __restrict__ query, // [batch_size, seq_len, num_heads,
// head_size] or [num_tokens, num_heads,
// head_size]
scalar_t* __restrict__ key, // nullptr or
// [batch_size, seq_len, num_kv_heads,
// head_size] or [num_tokens, num_kv_heads,
// head_size]
const scalar_t* cache_ptr,
const int head_size,
const int num_heads,
const int num_kv_heads,
const int rot_dim,
const int token_idx,
const int64_t query_stride,
const int64_t key_stride,
const int64_t head_stride) {
const int embed_dim = rot_dim / 2;
const scalar_t* cos_ptr = cache_ptr;
const scalar_t* sin_ptr = cache_ptr + embed_dim;
const int nq = num_heads * embed_dim;
for (int i = threadIdx.x; i < nq; i += blockDim.x) {
const int head_idx = i / embed_dim;
const int64_t token_head =
token_idx * query_stride + head_idx * head_stride;
const int rot_offset = i % embed_dim;
apply_token_rotary_embedding<scalar_t, IS_NEOX>(
query + token_head, cos_ptr, sin_ptr, rot_offset, embed_dim);
}
if (key != nullptr) {
const int nk = num_kv_heads * embed_dim;
for (int i = threadIdx.x; i < nk; i += blockDim.x) {
const int head_idx = i / embed_dim;
const int64_t token_head =
token_idx * key_stride + head_idx * head_stride;
const int rot_offset = i % embed_dim;
apply_token_rotary_embedding<scalar_t, IS_NEOX>(
key + token_head, cos_ptr, sin_ptr, rot_offset, embed_dim);
}
}
}
template <typename scalar_t, bool IS_NEOX>
__global__ void XLLM_KERNEL_ATTR(512) rotary_embedding_kernel(
const int64_t* __restrict__ positions, // [batch_size, seq_len] or
// [num_tokens]
scalar_t* __restrict__ query, // [batch_size, seq_len, num_heads,
// head_size] or [num_tokens, num_heads,
// head_size]
scalar_t* __restrict__ key, // nullptr or
// [batch_size, seq_len, num_kv_heads,
// head_size] or [num_tokens, num_kv_heads,
// head_size]
const scalar_t* __restrict__ cos_sin_cache, // [max_position, 2,
// rot_dim // 2]
const int rot_dim,
const int64_t query_stride,
const int64_t key_stride,
const int64_t head_stride,
const int num_heads,
const int num_kv_heads,
const int head_size) {
// Each thread block is responsible for one token.
const int token_idx = blockIdx.x;
int64_t pos = positions[token_idx];
const scalar_t* cache_ptr = cos_sin_cache + pos * rot_dim;
apply_rotary_embedding<scalar_t, IS_NEOX>(query,
key,
cache_ptr,
head_size,
num_heads,
num_kv_heads,
rot_dim,
token_idx,
query_stride,
key_stride,
head_stride);
}
} // namespace
namespace xllm::kernel::cuda {
// flashinfer rope ops
// void apply_rope_pos_ids_cos_sin_cache(torch::Tensor q,
// torch::Tensor k,
// torch::Tensor cos_sin_cache,
// torch::Tensor pos_ids,
// bool interleave) {
// const int64_t head_dim = cos_sin_cache.size(-1) / 2;
// q = q.view({q.size(0), -1, head_dim});
// k = k.view({k.size(0), -1, head_dim});
// FunctionFactory::get_instance().rope_func("rope").call(
// q, k, q, k, cos_sin_cache, pos_ids, interleave);
// }
void rotary_embedding(
torch::Tensor& positions, // [batch_size, seq_len] or [num_tokens]
torch::Tensor& query, // [batch_size, seq_len, num_heads * head_size] or
// [num_tokens, num_heads * head_size] or
// [batch_size, seq_len, num_heads, head_size] or
// [num_tokens, num_heads, head_size]
std::optional<torch::Tensor> key,
// null or
// [batch_size, seq_len, num_kv_heads * head_size] or
// [num_tokens, num_kv_heads * head_size] or
// [batch_size, seq_len, num_heads, head_size] or
// [num_tokens, num_heads, head_size]
// int64_t head_size,
torch::Tensor& cos_sin_cache, // [max_position, rot_dim]
bool is_neox) {
// num_tokens = batch_size * seq_len
const int positions_ndim = positions.dim();
const int query_ndim = query.dim();
// For partial rotary models, e.g. MiniMax-M2 with head_dim=128 and
// rotary_dim=64, the cache width is the rotary dimension rather than the
// physical per-head stride. When query is already shaped as
// [*, num_heads, head_size], infer the real head_size from query itself.
int64_t head_size = (query_ndim == positions_ndim + 2)
? query.size(-1)
: cos_sin_cache.size(-1);
int64_t num_tokens = positions.numel();
// Make sure num_tokens dim is consistent across positions, query, and key
CHECK(positions_ndim == 1 || positions_ndim == 2)
<< "positions must have shape [num_tokens] or [batch_size, seq_len]";
if (positions_ndim == 1) {
CHECK(query.size(0) == positions.size(0) &&
(!key.has_value() || key->size(0) == positions.size(0)))
<< "query, key and positions must have the same number of tokens";
}
if (positions_ndim == 2) {
CHECK(query.size(0) == positions.size(0) &&
(!key.has_value() || key->size(0) == positions.size(0)) &&
query.size(1) == positions.size(1) &&
(!key.has_value() || key->size(1) == positions.size(1)))
<< "query, key and positions must have the same batch_size and seq_len";
}
// Make sure head_size is valid for query and key
// hidden_size = num_heads * head_size
int query_hidden_size = query.numel() / num_tokens;
int key_hidden_size = key.has_value() ? key->numel() / num_tokens : 0;
CHECK(query_hidden_size % head_size == 0);
CHECK(key_hidden_size % head_size == 0);
// Make sure query and key have consistent number of heads
int num_heads = query_hidden_size / head_size;
int num_kv_heads = key.has_value() ? key_hidden_size / head_size : num_heads;
CHECK(num_heads % num_kv_heads == 0);
int rot_dim = cos_sin_cache.size(1);
int seq_dim_idx = positions_ndim - 1;
int64_t query_stride = query.stride(seq_dim_idx);
int64_t key_stride = key.has_value() ? key->stride(seq_dim_idx) : 0;
// Determine head stride: for [*, heads, head_size] use stride of last dim;
// for flat [*, heads*head_size], heads blocks are contiguous of size
// head_size
int64_t head_stride =
(query_ndim == positions_ndim + 2) ? query.stride(-2) : head_size;
dim3 grid(num_tokens);
dim3 block(std::min<int64_t>(num_heads * rot_dim / 2, 512));
const at::cuda::OptionalCUDAGuard device_guard(device_of(query));
const cudaStream_t stream = at::cuda::getCurrentCUDAStream();
DISPATCH_FLOATING_TYPES(
query.scalar_type(), "apply_rope_pos_ids_cos_sin_cache", [&] {
if (is_neox) {
rotary_embedding_kernel<scalar_t, true><<<grid, block, 0, stream>>>(
positions.data_ptr<int64_t>(),
query.data_ptr<scalar_t>(),
key.has_value() ? key->data_ptr<scalar_t>() : nullptr,
cos_sin_cache.data_ptr<scalar_t>(),
rot_dim,
query_stride,
key_stride,
head_stride,
num_heads,
num_kv_heads,
head_size);
} else {
rotary_embedding_kernel<scalar_t, false><<<grid, block, 0, stream>>>(
positions.data_ptr<int64_t>(),
query.data_ptr<scalar_t>(),
key.has_value() ? key->data_ptr<scalar_t>() : nullptr,
cos_sin_cache.data_ptr<scalar_t>(),
rot_dim,
query_stride,
key_stride,
head_stride,
num_heads,
num_kv_heads,
head_size);
}
});
}
} // namespace xllm::kernel::cuda

View File

@@ -0,0 +1,129 @@
/* Copyright 2026 The xLLM Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <ATen/cuda/CUDAContext.h>
#include <cuda_runtime.h>
#include <torch/script.h>
#include <torch/torch.h>
#include "cuda.h"
namespace xllm::kernel::cuda {
void beam_search(torch::Tensor acc_logprob,
torch::Tensor in_sequence_group,
torch::Tensor top_tokens,
torch::Tensor top_logprobs,
torch::Tensor out_acc_logprob,
torch::Tensor out_token_ids,
torch::Tensor out_token_index,
torch::Tensor out_beam_count_prefix_sums,
torch::Tensor out_sequence_group,
uint32_t batch_size,
uint32_t current_step) {
torch::Device device = acc_logprob.device();
uint32_t beam_size = in_sequence_group.size(1);
uint32_t top_k = top_tokens.size(1);
uint32_t total_rounds = in_sequence_group.size(2);
CHECK_EQ(beam_size, top_k) << "beam_size must be equal with top_k.";
if (current_step == 0) {
auto tokens_view =
top_tokens.view({batch_size, top_k}).slice(1, 0, beam_size);
auto init_probs_view =
top_logprobs.view({batch_size, top_k}).slice(1, 0, beam_size);
out_token_ids.view({batch_size, beam_size}).copy_(tokens_view);
out_acc_logprob.view({batch_size, beam_size}).copy_(init_probs_view);
auto indices =
torch::arange(
beam_size,
torch::TensorOptions().dtype(torch::kInt32).device(device))
.unsqueeze(0)
.expand({batch_size, -1})
.reshape({-1, 1});
out_token_index.copy_(indices);
auto sequence_view =
out_sequence_group.view({batch_size, beam_size, total_rounds});
sequence_view.slice(2, 0, 1).squeeze(2).copy_(tokens_view);
} else {
auto combined_probs =
(acc_logprob + top_logprobs).view({batch_size, beam_size * top_k});
auto topk_result = torch::topk(combined_probs, beam_size, -1);
auto new_probs = std::get<0>(topk_result); // [batch_size, beam_size]
auto new_indices = std::get<1>(topk_result); // [batch_size, beam_size]
auto ordered_indices = new_indices.argsort(static_cast<int64_t>(1), false);
// Reorder new_probs (and corresponding new_indices) by ordered_indices to
// keep alignment.
if (current_step < total_rounds - 1) {
new_probs = new_probs.gather(1, ordered_indices);
new_indices = new_indices.gather(1, ordered_indices);
}
auto parent_beam = (new_indices / top_k).to(torch::kLong);
auto token_in_beam = (new_indices % top_k).to(torch::kLong);
auto top_tokens_reshaped = top_tokens.view({batch_size, beam_size, top_k});
auto batch_idx =
torch::arange(batch_size,
torch::TensorOptions().dtype(torch::kLong).device(device))
.unsqueeze(1)
.expand_as(parent_beam);
using torch::indexing::TensorIndex;
auto new_tokens = top_tokens_reshaped.index({TensorIndex(batch_idx),
TensorIndex(parent_beam),
TensorIndex(token_in_beam)});
out_acc_logprob.view({batch_size, beam_size}).copy_(new_probs);
out_token_index.view({batch_size, beam_size})
.copy_(new_indices.to(torch::kInt32));
out_token_ids.view({batch_size, beam_size}).copy_(new_tokens);
auto batch_range =
torch::arange(
batch_size,
torch::TensorOptions().dtype(torch::kInt32).device(device))
.unsqueeze(1)
.expand({-1, beam_size});
auto beam_range =
torch::arange(
beam_size,
torch::TensorOptions().dtype(torch::kInt32).device(device))
.unsqueeze(0)
.expand({batch_size, -1});
using torch::indexing::Slice;
using torch::indexing::TensorIndex;
out_sequence_group.slice(2, 0, current_step) =
in_sequence_group.index({TensorIndex(batch_range),
TensorIndex(parent_beam.to(torch::kInt32)),
Slice(0, current_step)});
out_sequence_group.slice(2, current_step, current_step + 1) =
new_tokens.unsqueeze(2);
}
}
} // namespace xllm::kernel::cuda

View File

@@ -0,0 +1,312 @@
/* Copyright 2026 The xLLM Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <ATen/cuda/CUDAContext.h>
#include <c10/cuda/CUDAException.h>
#include <c10/cuda/CUDAGuard.h>
#include <cuda_runtime.h>
#include <glog/logging.h>
#include <torch/extension.h>
#include <cstdint>
#include <vector>
#include "xattention_ops_api.h"
namespace {
// In-place cache selection kernel for Xattention.
// Reorders KV cache entries based on beam search results. After beam search,
// the beam indices may have changed, and this kernel copies KV cache data from
// old beam positions to new beam positions to maintain consistency.
// Inputs:
// k_ptrs_i64 : [Layer] - pointers to K cache tensors for each layer
// v_ptrs_i64 : [Layer] - pointers to V cache tensors for each layer
// beam_index : [B*Beam] - mapping from new beam index to old beam index
// block_table : [B] - request ID per batch item (extracted from [B*Beam,
// 1]) B : batch size (actual batch size, not batch_size *
// beam_size) Beam : beam width Kv : number of KV
// heads MaxStep : maximum decode steps D : head
// dimension MaxReq : maximum number of requests Layer :
// number of transformer layers decode_step : current decode step
// (0-indexed)
// Cache layout: [MaxReq, Beam, MaxStep, Kv, D]
// The kernel performs two passes to avoid overwriting data:
// pass-1: copy from old_beam > new_beam (increasing new_beam)
// pass-2: copy from old_beam < new_beam (decreasing new_beam)
template <typename scalar_t>
__global__ void cache_select_inplace_ptrs_kernel(
const int64_t* __restrict__ k_ptrs_i64, // [Layer]
const int64_t* __restrict__ v_ptrs_i64, // [Layer]
const int32_t* __restrict__ beam_index, // [B*Beam]
const int32_t* __restrict__ block_table, // [B]
int32_t B,
int32_t Beam,
int32_t Kv,
int32_t MaxStep,
int32_t D,
int32_t MaxReq,
int32_t Layer,
int32_t decode_step) {
const int32_t b = static_cast<int32_t>(blockIdx.x);
const int32_t kv = static_cast<int32_t>(blockIdx.y);
const int32_t layer = static_cast<int32_t>(blockIdx.z);
if (b >= B || kv >= Kv || layer >= Layer) {
return;
}
const int32_t step_end =
decode_step < (MaxStep - 1) ? decode_step : (MaxStep - 1);
const int32_t req = block_table[b];
if (req < 0 || req >= MaxReq) {
return;
}
scalar_t* __restrict__ k_cache =
reinterpret_cast<scalar_t*>(static_cast<uintptr_t>(k_ptrs_i64[layer]));
scalar_t* __restrict__ v_cache =
reinterpret_cast<scalar_t*>(static_cast<uintptr_t>(v_ptrs_i64[layer]));
// base(req, beam, s, kv, d) = ((((req*Beam + beam)*MaxStep + s)*Kv + kv) * D
// + d)
const int64_t req_base = static_cast<int64_t>(req) * Beam;
const int64_t step_kv_stride = static_cast<int64_t>(Kv) * D;
const int64_t kv_d_base = static_cast<int64_t>(kv) * D;
// grid_step is typically small; loop over s in-kernel to reduce launch
// blocks.
for (int32_t s = 0; s <= step_end; ++s) {
// pass-1: new_beam increasing, copy if old_beam > new_beam
for (int32_t new_beam = 0; new_beam < Beam; ++new_beam) {
const int32_t old_beam = beam_index[b * Beam + new_beam] / Beam;
if (old_beam >= 0 && old_beam < Beam && old_beam > new_beam) {
const int64_t dst_base =
((req_base + new_beam) * MaxStep + s) * step_kv_stride + kv_d_base;
const int64_t src_base =
((req_base + old_beam) * MaxStep + s) * step_kv_stride + kv_d_base;
for (int32_t d = static_cast<int32_t>(threadIdx.x); d < D;
d += static_cast<int32_t>(blockDim.x)) {
k_cache[dst_base + d] = k_cache[src_base + d];
v_cache[dst_base + d] = v_cache[src_base + d];
}
}
}
// pass-2: new_beam decreasing, copy if old_beam < new_beam
for (int32_t new_beam = Beam - 1; new_beam >= 0; --new_beam) {
const int32_t old_beam = beam_index[b * Beam + new_beam] / Beam;
if (old_beam >= 0 && old_beam < Beam && old_beam < new_beam) {
const int64_t dst_base =
((req_base + new_beam) * MaxStep + s) * step_kv_stride + kv_d_base;
const int64_t src_base =
((req_base + old_beam) * MaxStep + s) * step_kv_stride + kv_d_base;
for (int32_t d = static_cast<int32_t>(threadIdx.x); d < D;
d += static_cast<int32_t>(blockDim.x)) {
k_cache[dst_base + d] = k_cache[src_base + d];
v_cache[dst_base + d] = v_cache[src_base + d];
}
}
}
}
}
void cache_select_cuda_launch_ptrs(
torch::Tensor k0,
torch::Tensor v0,
torch::Tensor k_ptrs_i64, // [Layer] int64 (CUDA)
torch::Tensor v_ptrs_i64, // [Layer] int64 (CUDA)
torch::Tensor beam_index_i32, // [B*Beam, 1] int32
torch::Tensor block_table_i32, // [B] int32
int64_t decode_step,
int64_t layer_num) {
CHECK(k_ptrs_i64.is_cuda() && v_ptrs_i64.is_cuda())
<< "k_ptrs_i64/v_ptrs_i64 must be CUDA";
CHECK_EQ(k_ptrs_i64.scalar_type(), torch::kInt64)
<< "k_ptrs_i64/v_ptrs_i64 must be int64";
CHECK_EQ(v_ptrs_i64.scalar_type(), torch::kInt64)
<< "k_ptrs_i64/v_ptrs_i64 must be int64";
CHECK(k_ptrs_i64.is_contiguous() && v_ptrs_i64.is_contiguous())
<< "k_ptrs_i64/v_ptrs_i64 must be contiguous";
const int64_t B64 = block_table_i32.size(0);
const int64_t Beam64 = k0.size(1);
const int64_t MaxStep64 = k0.size(2);
const int64_t Kv64 = k0.size(3);
const int64_t D64 = k0.size(4);
const int64_t MaxReq64 = k0.size(0);
const int64_t Layer64 = layer_num;
const int32_t B = static_cast<int32_t>(B64);
const int32_t Beam = static_cast<int32_t>(Beam64);
const int32_t Kv = static_cast<int32_t>(Kv64);
const int32_t MaxStep = static_cast<int32_t>(MaxStep64);
const int32_t D = static_cast<int32_t>(D64);
const int32_t MaxReq = static_cast<int32_t>(MaxReq64);
const int32_t Layer = static_cast<int32_t>(Layer64);
const int32_t decode_step_i32 = static_cast<int32_t>(decode_step);
// Warp-aligned threads, capped to keep occupancy reasonable.
int threads_per_block = ((D + 31) / 32) * 32;
if (threads_per_block < 32) {
threads_per_block = 32;
}
if (threads_per_block > 256) {
threads_per_block = 256;
}
dim3 block_dim(static_cast<unsigned int>(threads_per_block), 1, 1);
CHECK_LE(Kv64, static_cast<int64_t>(UINT32_MAX)) << "Kv too large for grid.y";
CHECK_LE(Layer64, 65535) << "layer_num too large for grid.z";
dim3 grid_dim(static_cast<unsigned int>(B),
static_cast<unsigned int>(Kv),
static_cast<unsigned int>(Layer));
cudaStream_t stream = at::cuda::getCurrentCUDAStream();
AT_DISPATCH_FLOATING_TYPES_AND2(torch::ScalarType::Half,
torch::ScalarType::BFloat16,
k0.scalar_type(),
"cache_select_inplace_ptrs_kernel",
[&] {
cache_select_inplace_ptrs_kernel<scalar_t>
<<<grid_dim, block_dim, 0, stream>>>(
k_ptrs_i64.data_ptr<int64_t>(),
v_ptrs_i64.data_ptr<int64_t>(),
beam_index_i32.data_ptr<int32_t>(),
block_table_i32.data_ptr<int32_t>(),
B,
Beam,
Kv,
MaxStep,
D,
MaxReq,
Layer,
decode_step_i32);
});
C10_CUDA_KERNEL_LAUNCH_CHECK();
}
} // namespace
namespace xllm::kernel::cuda {
void cache_select(const torch::Tensor& beam_index, // [B*Beam, 1]
std::vector<torch::Tensor>& unshared_k_cache,
std::vector<torch::Tensor>& unshared_v_cache,
const torch::Tensor& block_table, // [B*Beam, 1]
int64_t decode_step,
int64_t beam_size,
int64_t layer_num) {
CHECK_GE(layer_num, 0) << "layer_num must be >= 0";
if (layer_num == 0) {
return;
}
CHECK_EQ(static_cast<int64_t>(unshared_k_cache.size()), layer_num)
<< "unshared_k_cache length mismatch";
CHECK_EQ(static_cast<int64_t>(unshared_v_cache.size()), layer_num)
<< "unshared_v_cache length mismatch";
CHECK(beam_index.is_cuda()) << "beam_index must be CUDA";
CHECK(block_table.is_cuda()) << "block_table must be CUDA";
CHECK_EQ(block_table.dim(), 2) << "block_table must be [B*Beam, 1]";
CHECK_EQ(block_table.size(1), 1) << "block_table must be [B*Beam, 1]";
CHECK_EQ(beam_index.dim(), 2) << "beam_index must be [B*Beam, 1]";
CHECK_EQ(beam_index.size(1), 1) << "beam_index must be [B*Beam, 1]";
CHECK_GE(decode_step, 0) << "decode_step must be >= 0";
CHECK_GT(beam_size, 0) << "beam_size must be > 0";
// block_table is [B*Beam, 1] with sequential values [0,1,2,3,...]
// Infer actual batch_size
CHECK_EQ(block_table.size(0) % beam_size, 0)
<< "block_table.size(0) must be divisible by beam_size";
const int64_t B = block_table.size(0) / beam_size;
CHECK_EQ(beam_index.size(0), B * beam_size)
<< "beam_index size mismatch with B*beam_size";
// Prepare indices (int32, contiguous).
auto beam_index_i32 = beam_index.to(torch::kInt32).contiguous();
auto block_table_i32 = torch::arange(
0,
B,
torch::TensorOptions().dtype(torch::kInt32).device(block_table.device()));
// Validate shapes/dtypes against layer 0.
const auto& k0 = unshared_k_cache[0];
const auto& v0 = unshared_v_cache[0];
CHECK(k0.is_cuda() && v0.is_cuda()) << "cache must be CUDA";
CHECK(k0.is_contiguous() && v0.is_contiguous()) << "cache must be contiguous";
CHECK_EQ(k0.dim(), 5) << "cache must be 5D [MaxReq, Beam, MaxStep, Kv, D]";
CHECK_EQ(v0.sizes(), k0.sizes()) << "k/v cache shapes must match";
CHECK_EQ(k0.size(1), beam_size) << "beam_size mismatch with cache";
CHECK_LT(decode_step, k0.size(2)) << "decode_step must be < max_decode_step";
// Pack layer pointers into CUDA int64 tensors so we can launch once.
// Note: pointer values are produced on host (data_ptr()), then copied to GPU.
c10::cuda::CUDAGuard device_guard(k0.device());
auto ptr_cuda_opts =
torch::TensorOptions().dtype(torch::kInt64).device(k0.device());
auto k_ptrs_i64 = torch::empty({layer_num}, ptr_cuda_opts);
auto v_ptrs_i64 = torch::empty({layer_num}, ptr_cuda_opts);
std::vector<int64_t> k_ptrs_host(static_cast<size_t>(layer_num));
std::vector<int64_t> v_ptrs_host(static_cast<size_t>(layer_num));
for (int64_t layer = 0; layer < layer_num; ++layer) {
auto k = unshared_k_cache[static_cast<size_t>(layer)];
auto v = unshared_v_cache[static_cast<size_t>(layer)];
CHECK(k.is_cuda() && v.is_cuda()) << "cache must be CUDA";
CHECK(k.is_contiguous() && v.is_contiguous()) << "cache must be contiguous";
CHECK_EQ(k.sizes(), k0.sizes()) << "all layers must have same cache shape";
CHECK_EQ(v.sizes(), k0.sizes()) << "all layers must have same cache shape";
CHECK_EQ(k.scalar_type(), k0.scalar_type())
<< "all layers must have same dtype";
CHECK_EQ(v.scalar_type(), k0.scalar_type())
<< "all layers must have same dtype";
CHECK_EQ(k.get_device(), k0.get_device())
<< "all layers must be on the same CUDA device";
CHECK_EQ(v.get_device(), k0.get_device())
<< "all layers must be on the same CUDA device";
k_ptrs_host[static_cast<size_t>(layer)] =
static_cast<int64_t>(reinterpret_cast<uintptr_t>(k.data_ptr()));
v_ptrs_host[static_cast<size_t>(layer)] =
static_cast<int64_t>(reinterpret_cast<uintptr_t>(v.data_ptr()));
}
cudaStream_t stream = at::cuda::getCurrentCUDAStream();
C10_CUDA_CHECK(
cudaMemcpyAsync(k_ptrs_i64.data_ptr<int64_t>(),
k_ptrs_host.data(),
static_cast<size_t>(layer_num) * sizeof(int64_t),
cudaMemcpyHostToDevice,
stream));
C10_CUDA_CHECK(
cudaMemcpyAsync(v_ptrs_i64.data_ptr<int64_t>(),
v_ptrs_host.data(),
static_cast<size_t>(layer_num) * sizeof(int64_t),
cudaMemcpyHostToDevice,
stream));
cache_select_cuda_launch_ptrs(k0,
v0,
k_ptrs_i64,
v_ptrs_i64,
beam_index_i32,
block_table_i32,
decode_step,
layer_num);
}
} // namespace xllm::kernel::cuda

View File

@@ -0,0 +1,298 @@
/* Copyright 2026 The xLLM Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <c10/cuda/CUDAGuard.h>
#include <cuda_runtime.h>
#include <torch/cuda.h>
#include <cstdint>
#include <type_traits>
#include "kernels/cuda/utils.h"
#include "xattention_ops_api.h"
namespace {
template <typename scalar_t>
struct VecType;
template <>
struct VecType<c10::Half> {
using type = uint4; // 8 elements * 2 bytes = 16 bytes
static constexpr int32_t vec_width = 8;
};
template <>
struct VecType<c10::BFloat16> {
using type = uint4; // 8 elements * 2 bytes = 16 bytes
static constexpr int32_t vec_width = 8;
};
template <>
struct VecType<float> {
using type = float4; // 4 elements * 4 bytes = 16 bytes
static constexpr int32_t vec_width = 4;
};
// decoder reshape and cache kernel.
// Copies proj_k and proj_v into unshared_k_cache / unshared_v_cache.
// Inputs:
// proj_k : [batch_size, beam_size, kv_heads, head_dim]
// proj_v : [batch_size, beam_size, kv_heads, head_dim]
// step : [1] - current decode step
// batch_size : batch size
// beam_size : beam size
// kv_heads : number of kv heads
// head_dim : head dimension
// k_stride0 : proj_k.stride(0)
// k_stride1 : proj_k.stride(1)
// v_stride0 : proj_v.stride(0)
// v_stride1 : proj_v.stride(1)
// cache_stride0 : unshared_k_cache.stride(0)
// cache_stride1 : unshared_k_cache.stride(1)
// cache_stride2 : unshared_k_cache.stride(2)
// cache_stride3 : unshared_k_cache.stride(3)
// Outputs:
// unshared_k_cache : [max_batch_size, beam_size, max_step, kv_heads,
// head_dim]
// unshared_v_cache : [max_batch_size, beam_size, max_step, kv_heads,
// head_dim]
template <typename scalar_t>
__global__ void decoder_reshape_and_cache_kernel(
const scalar_t* __restrict__ proj_k,
const scalar_t* __restrict__ proj_v,
scalar_t* __restrict__ unshared_k_cache,
scalar_t* __restrict__ unshared_v_cache,
const int32_t* __restrict__ step,
const int64_t batch_size,
const int64_t beam_size,
const int64_t kv_heads,
const int64_t head_dim,
const int64_t k_stride0,
const int64_t k_stride1,
const int64_t v_stride0,
const int64_t v_stride1,
const int64_t cache_stride0,
const int64_t cache_stride1,
const int64_t cache_stride2,
const int64_t cache_stride3) {
using VecTypeT = typename VecType<scalar_t>::type;
constexpr int32_t VEC_WIDTH = VecType<scalar_t>::vec_width;
const int64_t token_idx = static_cast<int64_t>(blockIdx.y);
const int64_t total_tokens = batch_size * beam_size;
if (token_idx >= total_tokens) {
return;
}
const int64_t batch_idx = token_idx / beam_size;
const int64_t beam_idx = token_idx - batch_idx * beam_size;
__shared__ int32_t current_step_s;
if (threadIdx.x == 0) {
current_step_s = __ldg(step);
}
__syncthreads();
const int64_t current_step = static_cast<int64_t>(current_step_s);
const int64_t vecs_per_head = head_dim / VEC_WIDTH;
const int64_t total_vecs = kv_heads * vecs_per_head;
const int64_t k_token_base = batch_idx * k_stride0 + beam_idx * k_stride1;
const int64_t v_token_base = batch_idx * v_stride0 + beam_idx * v_stride1;
const int64_t dst_token_base = batch_idx * cache_stride0 +
beam_idx * cache_stride1 +
current_step * cache_stride2;
for (int64_t linear_idx = static_cast<int64_t>(threadIdx.x);
linear_idx < total_vecs;
linear_idx += static_cast<int64_t>(blockDim.x)) {
const int64_t head_idx = linear_idx / vecs_per_head;
const int64_t vec_idx = linear_idx - head_idx * vecs_per_head;
const int64_t vec_offset = vec_idx * VEC_WIDTH;
const auto* k_src_vec = reinterpret_cast<const VecTypeT*>(
proj_k + k_token_base + head_idx * head_dim + vec_offset);
const auto* v_src_vec = reinterpret_cast<const VecTypeT*>(
proj_v + v_token_base + head_idx * head_dim + vec_offset);
auto* k_dst_vec =
reinterpret_cast<VecTypeT*>(unshared_k_cache + dst_token_base +
head_idx * cache_stride3 + vec_offset);
auto* v_dst_vec =
reinterpret_cast<VecTypeT*>(unshared_v_cache + dst_token_base +
head_idx * cache_stride3 + vec_offset);
*k_dst_vec = *k_src_vec;
*v_dst_vec = *v_src_vec;
}
}
} // namespace
namespace xllm::kernel::cuda {
void decoder_reshape_and_cache(torch::Tensor proj_k,
torch::Tensor proj_v,
torch::Tensor unshared_k_cache,
torch::Tensor unshared_v_cache,
torch::Tensor step) {
CHECK_EQ(proj_k.dim(), 4) << "proj_k must be 4-dimensional";
CHECK_EQ(proj_v.dim(), 4) << "proj_v must be 4-dimensional";
CHECK_EQ(unshared_k_cache.dim(), 5)
<< "unshared_k_cache must be 5-dimensional";
CHECK_EQ(unshared_v_cache.dim(), 5)
<< "unshared_v_cache must be 5-dimensional";
CHECK(proj_k.is_cuda() && proj_v.is_cuda() && unshared_k_cache.is_cuda() &&
unshared_v_cache.is_cuda() && step.is_cuda())
<< "all tensors must be CUDA tensors";
CHECK_EQ(step.dim(), 1) << "step must be 1-dimensional";
CHECK_EQ(step.size(0), 1) << "step must have shape [1]";
CHECK_EQ(step.scalar_type(), at::ScalarType::Int)
<< "step must be int32 (torch::kInt32)";
const int64_t batch_size = proj_k.size(0);
const int64_t beam_size = proj_k.size(1);
const int64_t kv_heads = proj_k.size(2);
const int64_t head_dim = proj_k.size(3);
CHECK_EQ(proj_v.sizes(), proj_k.sizes())
<< "proj_v and proj_k must have same shape";
CHECK_EQ(unshared_k_cache.size(3), kv_heads)
<< "unshared_k_cache kv_heads mismatch";
CHECK_EQ(unshared_k_cache.size(4), head_dim)
<< "unshared_k_cache head_dim mismatch";
CHECK(unshared_v_cache.sizes() == unshared_k_cache.sizes())
<< "unshared_v_cache and unshared_k_cache must have same shape";
// This kernel is specialized for qkv-slice layouts:
// last dim contiguous and kv head stride tightly packed by head_dim.
CHECK_EQ(proj_k.stride(3), 1) << "proj_k must satisfy stride(3)=1";
CHECK_EQ(proj_v.stride(3), 1) << "proj_v must satisfy stride(3)=1";
CHECK_EQ(proj_k.stride(2), head_dim)
<< "proj_k must satisfy stride(2)=head_dim";
CHECK_EQ(proj_v.stride(2), head_dim)
<< "proj_v must satisfy stride(2)=head_dim";
CHECK_EQ(unshared_k_cache.stride(4), 1)
<< "unshared_k_cache must satisfy stride(4)=1";
CHECK_EQ(unshared_v_cache.stride(4), 1)
<< "unshared_v_cache must satisfy stride(4)=1";
CHECK_EQ(unshared_k_cache.stride(3), head_dim)
<< "unshared_k_cache must satisfy stride(3)=head_dim";
CHECK_EQ(unshared_v_cache.stride(3), head_dim)
<< "unshared_v_cache must satisfy stride(3)=head_dim";
const at::cuda::OptionalCUDAGuard device_guard(device_of(proj_k));
const cudaStream_t stream = at::cuda::getCurrentCUDAStream();
const int64_t k_stride0 = proj_k.stride(0);
const int64_t k_stride1 = proj_k.stride(1);
const int64_t v_stride0 = proj_v.stride(0);
const int64_t v_stride1 = proj_v.stride(1);
const int64_t cache_stride0 = unshared_k_cache.stride(0);
const int64_t cache_stride1 = unshared_k_cache.stride(1);
const int64_t cache_stride2 = unshared_k_cache.stride(2);
const int64_t cache_stride3 = unshared_k_cache.stride(3);
// Launch kernel: one block per (batch, beam), threads cover
// kv_heads*head_dim.
const int64_t total_tokens = batch_size * beam_size;
dim3 grid_dim(1, static_cast<unsigned int>(total_tokens), 1);
DISPATCH_FLOATING_TYPES(
proj_k.scalar_type(), "decoder_reshape_and_cache_kernel", [&] {
constexpr int32_t VEC_WIDTH = (std::is_same_v<scalar_t, c10::Half> ||
std::is_same_v<scalar_t, c10::BFloat16>)
? 8
: 4; // FP16/BF16: 8, Float: 4
constexpr int32_t kWarpSize = 32;
constexpr int32_t kMaxThreadsPerBlock = 256;
constexpr int32_t kAlignmentBytes = 16; // 128-bit alignment
CHECK(head_dim % VEC_WIDTH == 0)
<< "head_dim must be divisible by vector width: " << VEC_WIDTH;
const int64_t vecs_per_head = head_dim / VEC_WIDTH;
const int64_t total_vecs = kv_heads * vecs_per_head;
CHECK(total_vecs > 0) << "total_vecs must be > 0";
int32_t threads_per_block = static_cast<int32_t>(
total_vecs > kMaxThreadsPerBlock ? kMaxThreadsPerBlock
: total_vecs);
threads_per_block =
((threads_per_block + kWarpSize - 1) / kWarpSize) * kWarpSize;
if (threads_per_block < kWarpSize) {
threads_per_block = kWarpSize;
}
dim3 block_dim(threads_per_block, 1, 1);
const auto proj_k_ptr =
reinterpret_cast<std::uintptr_t>(proj_k.data_ptr<scalar_t>());
const auto proj_v_ptr =
reinterpret_cast<std::uintptr_t>(proj_v.data_ptr<scalar_t>());
const auto k_cache_ptr = reinterpret_cast<std::uintptr_t>(
unshared_k_cache.data_ptr<scalar_t>());
const auto v_cache_ptr = reinterpret_cast<std::uintptr_t>(
unshared_v_cache.data_ptr<scalar_t>());
CHECK(proj_k_ptr % kAlignmentBytes == 0)
<< "proj_k data_ptr must be 16-byte aligned";
CHECK(proj_v_ptr % kAlignmentBytes == 0)
<< "proj_v data_ptr must be 16-byte aligned";
CHECK(k_cache_ptr % kAlignmentBytes == 0)
<< "unshared_k_cache data_ptr must be 16-byte aligned";
CHECK(v_cache_ptr % kAlignmentBytes == 0)
<< "unshared_v_cache data_ptr must be 16-byte aligned";
const int64_t scalar_bytes = static_cast<int64_t>(sizeof(scalar_t));
CHECK((k_stride0 * scalar_bytes) % kAlignmentBytes == 0)
<< "proj_k stride(0) bytes must be 16-byte aligned";
CHECK((k_stride1 * scalar_bytes) % kAlignmentBytes == 0)
<< "proj_k stride(1) bytes must be 16-byte aligned";
CHECK((v_stride0 * scalar_bytes) % kAlignmentBytes == 0)
<< "proj_v stride(0) bytes must be 16-byte aligned";
CHECK((v_stride1 * scalar_bytes) % kAlignmentBytes == 0)
<< "proj_v stride(1) bytes must be 16-byte aligned";
CHECK((cache_stride0 * scalar_bytes) % kAlignmentBytes == 0)
<< "cache stride(0) bytes must be 16-byte aligned";
CHECK((cache_stride1 * scalar_bytes) % kAlignmentBytes == 0)
<< "cache stride(1) bytes must be 16-byte aligned";
CHECK((cache_stride2 * scalar_bytes) % kAlignmentBytes == 0)
<< "cache stride(2) bytes must be 16-byte aligned";
CHECK((cache_stride3 * scalar_bytes) % kAlignmentBytes == 0)
<< "cache stride(3) bytes must be 16-byte aligned";
decoder_reshape_and_cache_kernel<scalar_t>
<<<grid_dim, block_dim, 0, stream>>>(
proj_k.data_ptr<scalar_t>(),
proj_v.data_ptr<scalar_t>(),
unshared_k_cache.data_ptr<scalar_t>(),
unshared_v_cache.data_ptr<scalar_t>(),
step.data_ptr<int32_t>(),
batch_size,
beam_size,
kv_heads,
head_dim,
k_stride0,
k_stride1,
v_stride0,
v_stride1,
cache_stride0,
cache_stride1,
cache_stride2,
cache_stride3);
});
C10_CUDA_KERNEL_LAUNCH_CHECK();
}
} // namespace xllm::kernel::cuda

View File

@@ -0,0 +1,168 @@
/* Copyright 2026 The xLLM Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <c10/cuda/CUDAGuard.h>
#include <cuda_runtime.h>
#include <torch/cuda.h>
#include <cmath>
#include "kernels/cuda/utils.h"
#include "xattention_ops_api.h"
namespace {
// Fused log-sum-exp combine kernel.
//
// Layout and strategy (aligned with the TileLang version):
// - Each block is responsible for one (batch_idx, head_idx) pair, i.e. one
// row in the flattened [B * H, D] layout.
// - Threads within a block parallelize along the head_dim (D) dimension to
// ensure coalesced global memory access.
//
// Tensors:
// shared_o : [B, H, D] - shared attention output
// shared_lse : [B, H, 1] - shared log-sum-exp (FP32)
// unshared_o : [B, H, D] - unshared attention output
// unshared_lse: [B, H, 1] - unshared log-sum-exp (FP32)
// output : [B, H, D] - combined output
template <typename scalar_t, typename out_scalar_t>
__global__ void lse_combine_kernel(
out_scalar_t* __restrict__ output, // [B, H, D]
const scalar_t* __restrict__ shared_o, // [B, H, D]
const float* __restrict__ shared_lse, // [B, H, 1], always FP32
const scalar_t* __restrict__ unshared_o, // [B, H, D]
const float* __restrict__ unshared_lse, // [B, H, 1], always FP32
const int64_t B, // batch_size * beam_size
const int64_t H, // num_heads
const int64_t D) { // head_dim
const int64_t total_elements = B * H;
const int64_t idx = static_cast<int64_t>(blockIdx.y);
if (idx >= total_elements) {
return;
}
// Load LSE scalars for this (batch, head) pair.
const float shared_lse_val = shared_lse[idx];
const float unshared_lse_val = unshared_lse[idx];
// 1. Compute element-wise max LSE.
const float lse_max = fmaxf(shared_lse_val, unshared_lse_val);
// 2. Compute base-2 exponentials relative to max.
const float exp_shared = exp2f(shared_lse_val - lse_max);
const float exp_unshared = exp2f(unshared_lse_val - lse_max);
// 3. Compute merged LSE.
const float lse_new = lse_max + log2f(exp_shared + exp_unshared);
// 4. Compute normalized weights.
const float w_shared = exp2f(shared_lse_val - lse_new);
const float w_unshared = exp2f(unshared_lse_val - lse_new);
// 5. Weighted combine along the head_dim.
const int64_t base_idx = idx * D;
// Threads in the block parallelize along D with stride blockDim.x for
// coalesced global memory access.
for (int64_t d = threadIdx.x; d < D; d += blockDim.x) {
const float shared_val = static_cast<float>(shared_o[base_idx + d]);
const float unshared_val = static_cast<float>(unshared_o[base_idx + d]);
const float combined = w_shared * shared_val + w_unshared * unshared_val;
output[base_idx + d] = static_cast<out_scalar_t>(combined);
}
}
} // namespace
namespace xllm::kernel::cuda {
// Host wrapper for the fused LSE combine kernel.
//
// All inputs are expected to be on the same CUDA device:
// shared_o : [B, H, D], floating type (including Half/BFloat16)
// shared_lse : [B, H, 1], float32
// unshared_o : [B, H, D], same type/shape as shared_o
// unshared_lse: [B, H, 1], float32
// output : [B, H, D], will be resized/allocated as needed.
void lse_combine(torch::Tensor output,
torch::Tensor shared_o,
torch::Tensor shared_lse,
torch::Tensor unshared_o,
torch::Tensor unshared_lse) {
CHECK_EQ(shared_o.dim(), 3) << "shared_o must be 3D [B, H, D]";
CHECK_EQ(unshared_o.dim(), 3) << "unshared_o must be 3D [B, H, D]";
CHECK_EQ(shared_lse.dim(), 3) << "shared_lse must be 3D [B, H, 1]";
CHECK_EQ(unshared_lse.dim(), 3) << "unshared_lse must be 3D [B, H, 1]";
const int64_t B = shared_o.size(0);
const int64_t H = shared_o.size(1);
const int64_t D = shared_o.size(2);
CHECK_EQ(shared_o.sizes(), unshared_o.sizes())
<< "shared_o and unshared_o must have same shape";
CHECK_EQ(shared_lse.scalar_type(), torch::kFloat32)
<< "shared_lse must be float32";
CHECK_EQ(unshared_lse.scalar_type(), torch::kFloat32)
<< "unshared_lse must be float32";
CHECK_EQ(shared_lse.size(0), B)
<< "shared_lse shape mismatch, expected [B, H, 1]";
CHECK_EQ(shared_lse.size(1), H)
<< "shared_lse shape mismatch, expected [B, H, 1]";
CHECK_EQ(shared_lse.size(2), 1)
<< "shared_lse shape mismatch, expected [B, H, 1]";
CHECK_EQ(unshared_lse.size(0), B)
<< "unshared_lse shape mismatch, expected [B, H, 1]";
CHECK_EQ(unshared_lse.size(1), H)
<< "unshared_lse shape mismatch, expected [B, H, 1]";
CHECK_EQ(unshared_lse.size(2), 1)
<< "unshared_lse shape mismatch, expected [B, H, 1]";
// Ensure output has the correct shape and dtype.
if (!output.defined() || output.sizes() != shared_o.sizes()) {
output = torch::empty_like(shared_o);
}
const at::cuda::OptionalCUDAGuard device_guard(device_of(shared_o));
const cudaStream_t stream = at::cuda::getCurrentCUDAStream();
// Launch kernel: one block per (batch, head) pair, threads along D.
const int64_t total_elements = B * H;
const int threads_per_block = 128;
dim3 block_dim(threads_per_block, 1, 1);
dim3 grid_dim(1, static_cast<unsigned int>(total_elements), 1);
DISPATCH_FLOATING_TYPES(
shared_o.scalar_type(), "lse_combine_kernel_input", [&] {
using in_t = scalar_t;
DISPATCH_FLOATING_TYPES(
output.scalar_type(), "lse_combine_kernel_output", [&] {
using out_t = scalar_t;
lse_combine_kernel<in_t, out_t>
<<<grid_dim, block_dim, 0, stream>>>(
output.data_ptr<out_t>(),
shared_o.data_ptr<in_t>(),
shared_lse.data_ptr<float>(),
unshared_o.data_ptr<in_t>(),
unshared_lse.data_ptr<float>(),
B,
H,
D);
});
});
C10_CUDA_KERNEL_LAUNCH_CHECK();
}
} // namespace xllm::kernel::cuda

View File

@@ -0,0 +1,220 @@
/* Copyright 2026 The xLLM Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <c10/cuda/CUDAGuard.h>
#include <cuda_runtime.h>
#include <glog/logging.h>
#include <torch/cuda.h>
#include <cstdint>
#include <type_traits>
#include "kernels/cuda/cuda_ops_api.h"
#include "kernels/cuda/utils.h"
using at::device_of;
namespace {
template <typename scalar_t>
struct VecType;
template <>
struct VecType<c10::Half> {
using type = uint4; // 8 elements * 2 bytes = 16 bytes
static constexpr int32_t vec_width = 8;
};
template <>
struct VecType<c10::BFloat16> {
using type = uint4; // 8 elements * 2 bytes = 16 bytes
static constexpr int32_t vec_width = 8;
};
template <>
struct VecType<float> {
using type = float4; // 4 elements * 4 bytes = 16 bytes
static constexpr int32_t vec_width = 4;
};
template <typename scalar_t>
__global__ void prefill_reshape_and_cache_kernel(
const scalar_t* __restrict__ proj_k, // [shared_len, kv_heads, head_dim]
const scalar_t* __restrict__ proj_v, // [shared_len, kv_heads, head_dim]
scalar_t* __restrict__ shared_k_cache, // [shared_len, kv_heads, head_dim]
scalar_t* __restrict__ shared_v_cache, // [shared_len, kv_heads, head_dim]
const int64_t shared_len,
const int64_t kv_heads,
const int64_t head_dim,
const int64_t k_stride0, // proj_k.stride(0)
const int64_t v_stride0, // proj_v.stride(0)
const int64_t v_stride1) { // proj_v.stride(1), same as head_dim
using VecTypeT = typename VecType<scalar_t>::type;
constexpr int32_t VEC_WIDTH = VecType<scalar_t>::vec_width;
const int64_t token_idx = static_cast<int64_t>(blockIdx.y);
if (token_idx >= shared_len) {
return;
}
const int64_t vecs_per_head = head_dim / VEC_WIDTH;
const int64_t total_vecs = kv_heads * vecs_per_head;
const int64_t k_token_base = token_idx * k_stride0;
const int64_t v_token_base = token_idx * v_stride0;
const int64_t dst_token_base = token_idx * kv_heads * head_dim;
for (int64_t linear_idx = threadIdx.x; linear_idx < total_vecs;
linear_idx += blockDim.x) {
const int64_t head_idx = linear_idx / vecs_per_head;
const int64_t vec_idx = linear_idx - head_idx * vecs_per_head;
const int64_t head_offset = head_idx * head_dim;
const int64_t vec_offset = vec_idx * VEC_WIDTH;
const auto* k_src_vec = reinterpret_cast<const VecTypeT*>(
proj_k + k_token_base + head_offset + vec_offset);
const auto* v_src_vec = reinterpret_cast<const VecTypeT*>(
proj_v + v_token_base + head_idx * v_stride1 + vec_offset);
auto* k_dst_vec = reinterpret_cast<VecTypeT*>(
shared_k_cache + dst_token_base + head_offset + vec_offset);
auto* v_dst_vec = reinterpret_cast<VecTypeT*>(
shared_v_cache + dst_token_base + head_offset + vec_offset);
*k_dst_vec = *k_src_vec;
*v_dst_vec = *v_src_vec;
}
}
} // namespace
namespace xllm::kernel::cuda {
void prefill_reshape_and_cache(
torch::Tensor proj_k, // [shared_len, kv_heads, head_dim]
torch::Tensor proj_v, // [shared_len, kv_heads, head_dim]
torch::Tensor
shared_k_cache, // [num_shared_kv_seq_len, kv_heads, head_dim]
torch::Tensor shared_v_cache) {
CHECK(proj_k.dim() == 3) << "proj_k must be 3-dimensional";
CHECK(proj_v.dim() == 3) << "proj_v must be 3-dimensional";
CHECK(shared_k_cache.dim() == 3) << "shared_k_cache must be 3-dimensional";
CHECK(shared_v_cache.dim() == 3) << "shared_v_cache must be 3-dimensional";
CHECK(proj_k.is_cuda() && proj_v.is_cuda() && shared_k_cache.is_cuda() &&
shared_v_cache.is_cuda())
<< "all tensors must be CUDA tensors";
const int64_t shared_len = proj_k.size(0);
const int64_t kv_heads = proj_k.size(1);
const int64_t head_dim = proj_k.size(2);
CHECK(proj_v.sizes() == proj_k.sizes())
<< "proj_v and proj_k must have same shape";
CHECK(shared_k_cache.size(0) >= shared_len &&
shared_k_cache.size(1) == kv_heads &&
shared_k_cache.size(2) == head_dim)
<< "shared_k_cache shape mismatch";
CHECK(shared_v_cache.size(0) >= shared_len &&
shared_v_cache.size(1) == kv_heads &&
shared_v_cache.size(2) == head_dim)
<< "shared_v_cache shape mismatch";
shared_k_cache = shared_k_cache.slice(0, 0, shared_len);
shared_v_cache = shared_v_cache.slice(0, 0, shared_len);
// This kernel is specialized for qkv-slice layouts:
// last dim contiguous and head stride tightly packed by head_dim.
CHECK(proj_k.stride(2) == 1 && proj_v.stride(2) == 1)
<< "proj_k/proj_v must be contiguous on head_dim (stride(2)=1)";
CHECK(proj_k.stride(1) == head_dim && proj_v.stride(1) == head_dim)
<< "proj_k/proj_v must satisfy stride(1)=head_dim for qkv-slice layout";
CHECK(shared_k_cache.stride(2) == 1 && shared_v_cache.stride(2) == 1)
<< "shared caches must be contiguous on head_dim (stride(2)=1)";
CHECK(shared_k_cache.stride(1) == head_dim &&
shared_v_cache.stride(1) == head_dim)
<< "shared caches must satisfy stride(1)=head_dim";
CHECK(shared_k_cache.stride(0) == kv_heads * head_dim &&
shared_v_cache.stride(0) == kv_heads * head_dim)
<< "shared caches must be contiguous on token stride";
const at::cuda::OptionalCUDAGuard device_guard(device_of(proj_k));
const cudaStream_t stream = at::cuda::getCurrentCUDAStream();
const int64_t k_stride0 = proj_k.stride(0);
const int64_t v_stride0 = proj_v.stride(0);
const int64_t v_stride1 = proj_v.stride(1);
dim3 grid_dim(1, static_cast<unsigned int>(shared_len), 1);
DISPATCH_FLOATING_TYPES(
proj_k.scalar_type(), "prefill_reshape_and_cache_kernel", [&] {
constexpr int32_t VEC_WIDTH = (std::is_same_v<scalar_t, c10::Half> ||
std::is_same_v<scalar_t, c10::BFloat16>)
? 8
: 4; // FP16/BF16: 8, Float: 4
constexpr int32_t kWarpSize = 32;
constexpr int32_t kMaxThreadsPerBlock = 256;
CHECK(head_dim % VEC_WIDTH == 0)
<< "head_dim must be divisible by vector width: " << VEC_WIDTH;
const int64_t vecs_per_head = head_dim / VEC_WIDTH;
const int64_t total_vecs = kv_heads * vecs_per_head;
CHECK(total_vecs > 0) << "total_vecs must be > 0";
int32_t threads_per_block = static_cast<int32_t>(
total_vecs > kMaxThreadsPerBlock ? kMaxThreadsPerBlock
: total_vecs);
threads_per_block =
((threads_per_block + kWarpSize - 1) / kWarpSize) * kWarpSize;
if (threads_per_block < kWarpSize) {
threads_per_block = kWarpSize;
}
dim3 block_dim(threads_per_block, 1, 1);
const auto proj_k_ptr =
reinterpret_cast<std::uintptr_t>(proj_k.data_ptr<scalar_t>());
const auto proj_v_ptr =
reinterpret_cast<std::uintptr_t>(proj_v.data_ptr<scalar_t>());
const auto k_cache_ptr = reinterpret_cast<std::uintptr_t>(
shared_k_cache.data_ptr<scalar_t>());
const auto v_cache_ptr = reinterpret_cast<std::uintptr_t>(
shared_v_cache.data_ptr<scalar_t>());
constexpr int32_t alignment_bytes = 16; // 128-bit alignment
CHECK(proj_k_ptr % alignment_bytes == 0)
<< "proj_k data_ptr must be 16-byte aligned";
CHECK(proj_v_ptr % alignment_bytes == 0)
<< "proj_v data_ptr must be 16-byte aligned";
CHECK(k_cache_ptr % alignment_bytes == 0)
<< "shared_k_cache data_ptr must be 16-byte aligned";
CHECK(v_cache_ptr % alignment_bytes == 0)
<< "shared_v_cache data_ptr must be 16-byte aligned";
const int64_t scalar_bytes = static_cast<int64_t>(sizeof(scalar_t));
CHECK((k_stride0 * scalar_bytes) % alignment_bytes == 0)
<< "proj_k stride(0) bytes must be 16-byte aligned";
CHECK((v_stride0 * scalar_bytes) % alignment_bytes == 0)
<< "proj_v stride(0) bytes must be 16-byte aligned";
CHECK((v_stride1 * scalar_bytes) % alignment_bytes == 0)
<< "proj_v stride(1) bytes must be 16-byte aligned";
prefill_reshape_and_cache_kernel<scalar_t>
<<<grid_dim, block_dim, 0, stream>>>(
proj_k.data_ptr<scalar_t>(),
proj_v.data_ptr<scalar_t>(),
shared_k_cache.data_ptr<scalar_t>(),
shared_v_cache.data_ptr<scalar_t>(),
shared_len,
kv_heads,
head_dim,
k_stride0,
v_stride0,
v_stride1);
});
C10_CUDA_KERNEL_LAUNCH_CHECK();
}
} // namespace xllm::kernel::cuda

View File

@@ -0,0 +1,63 @@
/* Copyright 2026 The xLLM Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#pragma once
#include <torch/torch.h>
#include <vector>
namespace xllm::kernel::cuda {
void decoder_reshape_and_cache(torch::Tensor proj_k,
torch::Tensor proj_v,
torch::Tensor unshared_k_cache,
torch::Tensor unshared_v_cache,
torch::Tensor step);
void cache_select(const torch::Tensor& beam_index,
std::vector<torch::Tensor>& unshared_k_cache,
std::vector<torch::Tensor>& unshared_v_cache,
const torch::Tensor& block_table,
int64_t decode_step,
int64_t beam_size,
int64_t layer_num);
void lse_combine(torch::Tensor output,
torch::Tensor shared_o,
torch::Tensor shared_lse,
torch::Tensor unshared_o,
torch::Tensor unshared_lse);
void prefill_reshape_and_cache(
torch::Tensor proj_k, // [shared_len, kv_heads, head_dim]
torch::Tensor proj_v, // [shared_len, kv_heads, head_dim]
torch::Tensor
shared_k_cache, // [num_shared_kv_seq_len, kv_heads, head_dim]
torch::Tensor shared_v_cache);
void beam_search(torch::Tensor acc_logprob,
torch::Tensor in_sequence_group,
torch::Tensor top_tokens,
torch::Tensor top_logprobs,
torch::Tensor out_acc_logprob,
torch::Tensor out_token_ids,
torch::Tensor out_token_index,
torch::Tensor out_beam_count_prefix_sums,
torch::Tensor out_sequence_group,
uint32_t batch_size,
uint32_t current_step);
} // namespace xllm::kernel::cuda

View File

@@ -0,0 +1,28 @@
include(cc_library)
set(CMAKE_CUDA_ARCHITECTURES ivcore11)
file(GLOB_RECURSE ILU_HEADER_FILES
"${CMAKE_CURRENT_LIST_DIR}/*.h"
)
file(GLOB_RECURSE ILU_SOURCE_FILES
"${CMAKE_CURRENT_LIST_DIR}/*.cpp"
"${CMAKE_CURRENT_LIST_DIR}/*.cu"
)
find_package(Python3 REQUIRED COMPONENTS Interpreter Development)
cc_library(
NAME
ilu_kernels
HDRS
${ILU_HEADER_FILES}
SRCS
${ILU_SOURCE_FILES}
DEPS
torch
:util
ixformer_kernels
ixformer
${Python3_LIBRARIES}
cuinfer
)

View File

@@ -0,0 +1,32 @@
/* Copyright 2025 The xLLM Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "ilu_ops_api.h"
using namespace ixformer;
namespace xllm::kernel::ilu {
void act_and_mul(torch::Tensor out,
torch::Tensor input,
const std::string& act_mode) {
if (act_mode == "silu") {
infer::silu_and_mul(input, out);
} else {
LOG(FATAL) << "Unsupported act mode: " << act_mode
<< ", only support silu, gelu, gelu_tanh";
}
}
} // namespace xllm::kernel::ilu

View File

@@ -0,0 +1,163 @@
/* Copyright 2025 The xLLM Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "ilu_ops_api.h"
#include "ixinfer.h"
#include "utils.h"
using namespace ixformer;
namespace xllm::kernel::ilu {
void reshape_paged_cache(torch::Tensor& key,
std::optional<torch::Tensor>& value,
torch::Tensor& key_cache,
std::optional<torch::Tensor>& value_cache,
torch::Tensor& slot_mapping) {
auto value_ = value.value_or(torch::Tensor());
auto value_cache_ = value_cache.value_or(torch::Tensor());
int64_t key_token_stride = key.stride(0);
int64_t value_token_stride = 0;
if (value_.defined()) {
value_token_stride = value_.stride(0);
}
slot_mapping = slot_mapping.to(at::kLong);
infer::xllm_reshape_and_cache(key,
value_,
key_cache,
value_cache_,
slot_mapping,
key_token_stride,
value_token_stride);
}
void batch_prefill(torch::Tensor& query,
const torch::Tensor& key,
const std::optional<torch::Tensor>& value,
torch::Tensor& output,
std::optional<torch::Tensor>& output_lse,
const std::optional<torch::Tensor>& q_cu_seq_lens,
const std::optional<torch::Tensor>& kv_cu_seq_lens,
const std::optional<torch::Tensor>& alibi_slope,
const std::optional<torch::Tensor>& attn_bias,
const std::optional<torch::Tensor>& q_quant_scale,
const std::optional<torch::Tensor>& k_quant_scale,
const std::optional<torch::Tensor>& v_quant_scale,
const torch::Tensor& block_tables,
int64_t max_query_len,
int64_t max_seq_len,
float scale,
bool is_causal,
int64_t window_size_left,
int64_t window_size_right,
const std::string& compute_dtype,
bool return_lse) {
double softcap = 0.0;
bool sqrt_alibi = false;
auto q_cu_seq_lens_ = q_cu_seq_lens.value_or(torch::Tensor());
auto kv_cu_seq_lens_ = kv_cu_seq_lens.value_or(torch::Tensor());
auto q_quant_scale_ = q_quant_scale.value_or(torch::Tensor());
auto k_quant_scale_ = k_quant_scale.value_or(torch::Tensor());
auto v_quant_scale_ = v_quant_scale.value_or(torch::Tensor());
auto block_tables_ = block_tables;
auto key_ = key;
auto value_ = value.value();
infer::ixinfer_flash_attn_unpad_with_block_tables(query,
key_,
value_,
output,
block_tables_,
q_cu_seq_lens_,
kv_cu_seq_lens_,
max_query_len,
max_seq_len,
is_causal,
window_size_left,
window_size_right,
static_cast<double>(scale),
softcap,
sqrt_alibi,
alibi_slope,
c10::nullopt,
output_lse);
}
void batch_decode(torch::Tensor& query,
const torch::Tensor& k_cache,
torch::Tensor& output,
const torch::Tensor& block_table,
const torch::Tensor& seq_lens,
const std::optional<torch::Tensor>& v_cache,
std::optional<torch::Tensor>& output_lse,
const std::optional<torch::Tensor>& q_quant_scale,
const std::optional<torch::Tensor>& k_cache_quant_scale,
const std::optional<torch::Tensor>& v_cache_quant_scale,
const std::optional<torch::Tensor>& out_quant_scale,
const std::optional<torch::Tensor>& alibi_slope,
const std::optional<torch::Tensor>& mask,
const std::string& compute_dtype,
int64_t max_seq_len,
int64_t window_size_left,
int64_t window_size_right,
float scale,
bool return_lse,
bool is_causal,
int64_t kv_cache_quant_bit_size) {
if (query.dim() == 4) {
query =
query
.view({query.size(0) * query.size(1), query.size(2), query.size(3)})
.contiguous();
}
if (output.dim() == 4) {
output = output
.view({output.size(0) * output.size(1),
output.size(2),
output.size(3)})
.contiguous();
;
}
auto v_cache_ = v_cache.value_or(torch::Tensor());
int64_t num_kv_heads = k_cache.size(1);
int64_t page_block_size = k_cache.size(2);
double softcap = 0.0;
bool enable_cuda_graph = false;
bool use_sqrt_alibi = false;
auto block_table_ = block_table;
auto k_cache_ = k_cache;
auto seq_lens_ = seq_lens;
infer::xllm_paged_attention(output,
query,
k_cache_,
v_cache_,
num_kv_heads,
scale,
block_table_,
seq_lens_,
page_block_size,
max_seq_len,
alibi_slope,
is_causal,
(int32_t)window_size_left,
(int32_t)window_size_right,
softcap,
enable_cuda_graph,
use_sqrt_alibi,
c10::nullopt);
}
} // namespace xllm::kernel::ilu

View File

@@ -0,0 +1,99 @@
/* Copyright 2025 The xLLM Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <glog/logging.h>
#include "ilu_ops_api.h"
namespace xllm::kernel::ilu {
std::tuple<torch::Tensor, torch::Tensor> moe_active_topk(
const torch::Tensor& input,
int64_t topk,
int64_t num_expert_group,
int64_t topk_group,
bool normalize,
const std::optional<torch::Tensor>& mask,
const std::string& normed_by,
const std::string& scoring_func,
double route_scale,
const std::optional<torch::Tensor>& e_score_correction_bias) {
torch::Tensor input_ = input.to(torch::kFloat32);
auto reduce_weight =
torch::empty({input.size(0), topk},
torch::dtype(torch::kFloat).device(input.device()));
auto topk_indices =
torch::empty({input.size(0), topk},
torch::dtype(torch::kInt32).device(input.device()));
auto token_expert_indices =
torch::empty({input.size(0), topk},
torch::dtype(torch::kInt32).device(input.device()));
infer::topk_softmax(
reduce_weight, topk_indices, token_expert_indices, input_, false);
auto tt = reduce_weight.sum(-1);
if (normalize) {
reduce_weight = reduce_weight / reduce_weight.sum(-1).unsqueeze(-1);
}
return std::make_tuple(reduce_weight, topk_indices);
}
std::vector<torch::Tensor> moe_gen_idx(torch::Tensor& expert_id,
int64_t expert_num) {
auto src_dst = expert_id.new_empty({expert_id.numel()});
auto dst_src = torch::empty_like(src_dst);
auto expert_sizes_gpu = expert_id.new_empty({expert_num});
auto expert_sizes_gpu_cumsum = expert_id.new_zeros({expert_id.numel() + 1});
infer::moe_compute_token_index_api(expert_id,
src_dst,
dst_src,
expert_sizes_gpu,
/*expert_mask=*/std::nullopt,
/*expert_sizes_cpu*/ std::nullopt,
/*expert_sizes_gpu*/ std::nullopt,
0,
expert_num,
expert_num);
expert_sizes_gpu_cumsum = expert_sizes_gpu.cumsum(-1);
return {src_dst, dst_src, expert_sizes_gpu, expert_sizes_gpu_cumsum};
}
torch::Tensor moe_expand_input(const torch::Tensor& input,
const torch::Tensor& gather_index,
const torch::Tensor& combine_idx,
int64_t topk) {
int64_t dst_tokens = input.size(0) * topk;
auto output = input.new_empty({dst_tokens, input.size(1)});
infer::moe_expand_input(
output, input, combine_idx, gather_index, dst_tokens, topk);
return output;
}
torch::Tensor moe_combine_result(torch::Tensor& input, torch::Tensor& weight) {
input = input.view({-1, weight.size(1), input.size(1)});
auto output = input.new_empty({input.size(0), input.size(2)});
infer::moe_output_reduce_sum(output,
input,
weight,
/*mask=*/std::nullopt,
/*extra_residual*/ std::nullopt,
/*scaling_factor=*/1.0);
return output;
}
} // namespace xllm::kernel::ilu

View File

@@ -0,0 +1,39 @@
/* Copyright 2026 The xLLM Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "ilu_ops_api.h"
namespace xllm::kernel::ilu {
torch::Tensor group_gemm(torch::Tensor& input,
torch::Tensor& weight,
torch::Tensor& tokens_per_experts,
const std::optional<torch::Tensor>& dst_to_src,
torch::Tensor& output) {
infer::moe_w16a16_group_gemm(
output,
input,
weight,
tokens_per_experts,
dst_to_src,
/*bias=*/std::nullopt,
/*format=*/"TN",
/*persistent=*/0,
/*output_n=*/tokens_per_experts.sum().item<int64_t>());
return output;
}
} // namespace xllm::kernel::ilu

View File

@@ -0,0 +1,153 @@
/* Copyright 2025 The xLLM Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#pragma once
#include <ATen/DynamicLibrary.h>
#include <ATen/core/dispatch/Dispatcher.h>
#include <cuda_runtime.h>
#include <glog/logging.h>
#include <torch/all.h>
#include <optional>
#include "ATen/Tensor.h"
#include "ATen/cuda/CUDAEvent.h"
#include "c10/core/Device.h"
#include "c10/core/DeviceGuard.h"
#include "c10/core/GradMode.h"
#include "c10/core/InferenceMode.h"
#include "c10/core/MemoryFormat.h"
#include "c10/core/ScalarType.h"
#include "c10/core/TensorOptions.h"
#include "c10/cuda/CUDAFunctions.h"
#include "c10/cuda/CUDAGuard.h"
#include "c10/cuda/CUDAStream.h"
#include "ixformer.h"
#include "kernels/kernels.h"
// #include "utils.h"
using namespace ixformer;
namespace xllm::kernel::ilu {
void apply_rope_pos_ids_cos_sin_cache(torch::Tensor& query,
torch::Tensor& key,
torch::Tensor& cos_sin_cache,
torch::Tensor& positions,
bool interleave);
// act_mode only support silu, gelu, gelu_tanh
void act_and_mul(torch::Tensor out,
torch::Tensor input,
const std::string& act_mode);
void reshape_paged_cache(
torch::Tensor& key, // (num_tokens, num_heads, head_size)
std::optional<torch::Tensor>& value, // (num_tokens, num_heads, head_size)
torch::Tensor& key_cache, // (num_blocks, num_heads, block_size, head_size)
std::optional<torch::Tensor>&
value_cache, // (num_blocks, num_heads, block_size, head_size)
torch::Tensor& slot_mapping); //(num_tokens)
void batch_prefill(torch::Tensor& query,
const torch::Tensor& key,
const std::optional<torch::Tensor>& value,
torch::Tensor& output,
std::optional<torch::Tensor>& output_lse,
const std::optional<torch::Tensor>& q_cu_seq_lens,
const std::optional<torch::Tensor>& kv_cu_seq_lens,
const std::optional<torch::Tensor>& alibi_slope,
const std::optional<torch::Tensor>& attn_bias,
const std::optional<torch::Tensor>& q_quant_scale,
const std::optional<torch::Tensor>& k_quant_scale,
const std::optional<torch::Tensor>& v_quant_scale,
const torch::Tensor& block_tables,
int64_t max_query_len,
int64_t max_seq_len,
float scale,
bool is_causal,
int64_t window_size_left,
int64_t window_size_right,
const std::string& compute_dtype,
bool return_lse);
void batch_decode(torch::Tensor& query,
const torch::Tensor& k_cache,
torch::Tensor& output,
const torch::Tensor& block_table,
const torch::Tensor& seq_lens,
const std::optional<torch::Tensor>& v_cache,
std::optional<torch::Tensor>& output_lse,
const std::optional<torch::Tensor>& q_quant_scale,
const std::optional<torch::Tensor>& k_cache_quant_scale,
const std::optional<torch::Tensor>& v_cache_quant_scale,
const std::optional<torch::Tensor>& out_quant_scale,
const std::optional<torch::Tensor>& alibi_slope,
const std::optional<torch::Tensor>& mask,
const std::string& compute_dtype,
int64_t max_seq_len,
int64_t window_size_left,
int64_t window_size_right,
float scale,
bool return_lse,
bool is_causal,
int64_t kv_cache_quant_bit_size);
void residual_layer_norm(torch::Tensor& input,
torch::Tensor& output,
std::optional<torch::Tensor>& residual,
torch::Tensor& weight,
std::optional<torch::Tensor>& bias,
std::optional<torch::Tensor>& residual_out,
double eps);
void rms_norm(torch::Tensor& output,
torch::Tensor& input,
torch::Tensor& weight,
double eps);
torch::Tensor matmul(torch::Tensor a,
torch::Tensor b,
std::optional<torch::Tensor> bias);
std::tuple<torch::Tensor, torch::Tensor> moe_active_topk(
const torch::Tensor& input,
int64_t topk,
int64_t num_expert_group,
int64_t topk_group,
bool normalize,
const std::optional<torch::Tensor>& mask,
const std::string& normed_by,
const std::string& scoring_func,
double route_scale,
const std::optional<torch::Tensor>& e_score_correction_bias);
std::vector<torch::Tensor> moe_gen_idx(torch::Tensor& expert_id,
int64_t expert_num);
torch::Tensor moe_expand_input(const torch::Tensor& input,
const torch::Tensor& gather_index,
const torch::Tensor& combine_idx,
int64_t topk);
torch::Tensor group_gemm(torch::Tensor& input,
torch::Tensor& weight,
torch::Tensor& tokens_per_experts,
const std::optional<torch::Tensor>& dst_to_src,
torch::Tensor& output);
torch::Tensor moe_combine_result(torch::Tensor& input, torch::Tensor& weight);
} // namespace xllm::kernel::ilu

View File

@@ -0,0 +1,147 @@
/* Copyright 2025 The xLLM Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <torch/all.h>
#include "ATen/Tensor.h"
#include "utils.h"
namespace ixformer::infer {
torch::Tensor ixinfer_flash_attn_unpad_with_block_tables(
torch::Tensor& query,
torch::Tensor& key_cache,
torch::Tensor& value_cache,
torch::Tensor& out,
torch::Tensor& block_tables,
torch::Tensor& cu_seq_q,
torch::Tensor& cu_seq_k,
int64_t max_seq_q,
int64_t max_seq_k,
bool is_causal,
int64_t window_left,
int64_t window_right,
double scale,
double softcap,
bool sqrt_alibi,
const std::optional<torch::Tensor>& alibi_slopes,
const std::optional<torch::Tensor>& sinks,
std::optional<torch::Tensor>& lse);
void silu_and_mul(torch::Tensor& input, torch::Tensor& output);
torch::Tensor xllm_paged_attention(
torch::Tensor& out,
torch::Tensor& query,
torch::Tensor& key_cache,
torch::Tensor& value_cache,
int64_t num_kv_heads,
double scale,
torch::Tensor& block_tables,
torch::Tensor& context_lens,
int64_t block_size,
int64_t max_context_len,
const std::optional<torch::Tensor>& alibi_slopes,
bool causal,
int32_t window_left,
int32_t window_right,
double softcap,
bool enable_cuda_graph,
bool use_sqrt_alibi,
const std::optional<torch::Tensor>& sinks);
torch::Tensor ixformer_linear(torch::Tensor& input,
torch::Tensor& weight,
int64_t act_type,
const std::optional<torch::Tensor>& bias,
const std::optional<torch::Tensor>& out,
const std::optional<bool> persistent);
torch::Tensor ixformer_linear_ex(torch::Tensor& input,
torch::Tensor& weight,
const c10::optional<torch::Tensor>& bias,
const c10::optional<torch::Tensor>& out);
void xllm_reshape_and_cache(torch::Tensor& key,
torch::Tensor& value,
torch::Tensor& key_cache,
torch::Tensor& value_cache,
torch::Tensor& slot_mapping,
int64_t key_token_stride,
int64_t value_token_stride);
void xllm_rotary_embedding(torch::Tensor& positions,
torch::Tensor& query,
torch::Tensor& key,
int64_t head_size,
torch::Tensor& cos_sin_cache,
bool is_neox);
void residual_rms_norm(torch::Tensor& input,
torch::Tensor& residual,
torch::Tensor& weight,
torch::Tensor& output,
torch::Tensor& residual_output,
const std::optional<torch::Tensor>& fused_bias,
double alpha,
double eps,
bool is_post);
void rms_norm(torch::Tensor& input,
torch::Tensor& weight,
torch::Tensor& output,
const std::optional<torch::Tensor>& fused_bias,
double eps);
void topk_softmax(torch::Tensor& topk_weights,
torch::Tensor& topk_indices,
torch::Tensor& token_expert_indices,
torch::Tensor& gating_output,
bool renormalize);
void moe_compute_token_index_api(
torch::Tensor& topk_ids,
torch::Tensor& src_dst,
torch::Tensor& dst_src,
torch::Tensor& expert_sizes_gpu,
const c10::optional<torch::Tensor>& expert_mask,
const c10::optional<torch::Tensor>& expert_sizes_cpu,
const c10::optional<torch::Tensor>& expand_tokens_gpu,
int64_t start_expert_id,
int64_t end_expert_id,
int64_t num_experts);
void moe_expand_input(torch::Tensor outputs,
torch::Tensor inputs,
torch::Tensor dst_to_src,
const c10::optional<torch::Tensor>& src_to_dst,
int64_t dst_tokens,
int64_t expand_factor);
void moe_w16a16_group_gemm(torch::Tensor output,
torch::Tensor inputs,
torch::Tensor weights,
torch::Tensor tokens_per_experts,
const c10::optional<torch::Tensor>& dst_to_src,
const c10::optional<torch::Tensor>& bias,
std::string format,
int64_t persistent,
int64_t output_n);
void moe_output_reduce_sum(torch::Tensor outputs,
torch::Tensor inputs,
const c10::optional<torch::Tensor>& mul_weight,
const c10::optional<torch::Tensor>& mask,
const c10::optional<torch::Tensor>& extra_residual,
double scaling_factor);
} // namespace ixformer::infer

View File

@@ -0,0 +1,73 @@
/* Copyright 2025 The xLLM Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "ilu_ops_api.h"
#include "util/env_var.h"
namespace xllm::kernel::ilu {
bool gemv_conditions(const torch::Tensor& input,
const torch::Tensor& weight,
const torch::Tensor& bias,
int64_t gemv_max_batch) {
// gemv input:[m,k] weight:[n,k]
// 1. m <= gemv_max_batch
// 2. k % 32 == 0 && n % 2 == 0
// 3. bias is None
torch::Tensor input_view = input.view({-1, input.size(-1)});
torch::Tensor weight_view = weight.view({-1, weight.size(-1)});
int64_t m = input_view.size(0);
int64_t k = input_view.size(1);
int64_t n = weight_view.size(0);
if (bias.defined() == false && m <= gemv_max_batch && k % 32 == 0 &&
n % 2 == 0) {
return true;
}
return false;
}
torch::Tensor matmul(torch::Tensor a,
torch::Tensor b,
std::optional<torch::Tensor> bias) {
int64_t act_type = -1;
bool persistent = false;
std::vector<int64_t> output_shape = a.sizes().vec();
if (!output_shape.empty()) {
output_shape[output_shape.size() - 1] = b.size(0);
}
torch::Tensor output = a.new_empty(output_shape);
bool use_gemv = true;
const int64_t gemv_max_batch = 1;
const bool disable_infer_gemm_ex =
xllm::util::get_bool_env("DISABLE_INFER_GEMM_EX", false);
use_gemv =
use_gemv &&
gemv_conditions(a, b, bias.value_or(at::Tensor()), gemv_max_batch) &&
!disable_infer_gemm_ex && (act_type == -1);
if (use_gemv) {
output = infer::ixformer_linear_ex(a, b, bias, output);
} else {
output = infer::ixformer_linear(a, b, act_type, bias, output, persistent);
}
return output;
}
} // namespace xllm::kernel::ilu

View File

@@ -0,0 +1,51 @@
/* Copyright 2025 The xLLM Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "ilu_ops_api.h"
#include "utils.h"
using namespace ixformer;
namespace xllm::kernel::ilu {
void residual_layer_norm(torch::Tensor& input,
torch::Tensor& output,
std::optional<torch::Tensor>& residual,
torch::Tensor& weight,
std::optional<torch::Tensor>& bias,
std::optional<torch::Tensor>& residual_out,
double eps) {
auto residual_ = residual.value_or(torch::zeros_like(input));
torch::Tensor residual_out_ = residual_out.value_or(torch::zeros_like(input));
infer::residual_rms_norm(input,
residual_,
weight,
output,
residual_out_,
bias,
/*alpha=*/1.0,
eps,
false);
}
void rms_norm(torch::Tensor& output,
torch::Tensor& input,
torch::Tensor& weight,
double eps) {
std::optional<torch::Tensor> fused_bias = std::nullopt;
infer::rms_norm(input, weight, output, fused_bias, eps);
}
} // namespace xllm::kernel::ilu

View File

@@ -0,0 +1,31 @@
/* Copyright 2025 The xLLM Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "ilu_ops_api.h"
#include "utils.h"
namespace xllm::kernel::ilu {
void apply_rope_pos_ids_cos_sin_cache(torch::Tensor& query,
torch::Tensor& key,
torch::Tensor& cos_sin_cache,
torch::Tensor& positions,
bool interleave) {
const int64_t head_size = cos_sin_cache.size(-1);
infer::xllm_rotary_embedding(
positions, query, key, head_size, cos_sin_cache, !interleave);
}
} // namespace xllm::kernel::ilu

View File

@@ -0,0 +1,63 @@
/* Copyright 2025 The xLLM Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#pragma once
namespace xllm::kernel::ilu {
#undef check_tensor_contiguous
#define check_tensor_contiguous(x, type) \
TORCH_CHECK(x.scalar_type() == type); \
TORCH_CHECK(x.is_cuda()); \
TORCH_CHECK(x.is_contiguous());
#undef check_tensor_half_bf_float
#define check_tensor_half_bf_float(x) \
TORCH_CHECK(x.scalar_type() == at::ScalarType::Half || \
x.scalar_type() == at::ScalarType::Float || \
x.scalar_type() == at::ScalarType::BFloat16); \
TORCH_CHECK(x.is_cuda());
// from torchCheckMsgImpl
inline const char* ixformer_check_msg_impl(const char* msg) { return msg; }
// // If there is just 1 user-provided C-string argument, use it.
#define IXFORMER_CHECK_MSG(cond, type, ...) \
(ixformer_check_msg_impl( \
"Expected " #cond \
" to be true, but got false. " \
"(Could this error message be improved? If so, " \
"please report an enhancement request to ixformer.)", \
##__VA_ARGS__))
#define IXFORMER_CHECK(cond, ...) \
{ \
if (!(cond)) { \
std::cerr << __FILE__ << " (" << __LINE__ << ")" \
<< "-" << __FUNCTION__ << " : " \
<< IXFORMER_CHECK_MSG(cond, "", ##__VA_ARGS__) << std::endl; \
throw std::runtime_error("IXFORMER_CHECK ERROR"); \
} \
}
#undef CUINFER_CHECK
#define CUINFER_CHECK(func) \
do { \
cuinferStatus_t status = (func); \
if (status != CUINFER_STATUS_SUCCESS) { \
std::cerr << "Error in file " << __FILE__ << " on line " << __LINE__ \
<< ": " << cuinferGetErrorString(status) << std::endl; \
throw std::runtime_error("CUINFER_CHECK ERROR"); \
} \
} while (0)
} // namespace xllm::kernel::ilu

View File

@@ -0,0 +1,11 @@
/* Auto-generated aggregation header for xllm::kernel namespace.
* Equivalent to CMake cc_library(NAME kernels HDRS param.h ops_api.h).
*
* AST Layer 3: kernel dispatch interface
* Called by: xllm_layers/ (Layer 2)
* Calls: xllm_kernels/ilu/ (Layer 4)
*/
#pragma once
#include "param.h"
#include "ops_api.h"

View File

@@ -0,0 +1,59 @@
/* Copyright 2026 The xLLM Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "core/kernels/npu/aclnn/pytorch_npu_helper.hpp"
#include "core/kernels/npu/utils.h"
#include "core/kernels/npu/xllm_ops/xllm_ops_api.h"
namespace xllm::kernel::npu {
torch::Tensor causal_conv1d(const torch::Tensor& x,
const torch::Tensor& weight,
const torch::Tensor& conv_state,
const std::optional<torch::Tensor>& bias_opt,
const torch::IntArrayRef query_start_loc_opt,
const torch::IntArrayRef cache_indices_opt,
const torch::IntArrayRef initial_state_mode_opt,
const torch::IntArrayRef num_accepted_tokens_opt,
int64_t activation_mode,
int64_t pad_slot_id,
int64_t run_mode) {
check_tensor(x, "x", "causal_conv1d");
check_tensor(weight, "weight", "causal_conv1d");
check_tensor(conv_state, "conv_state", "causal_conv1d");
c10::optional<torch::Tensor> bias_tensor = c10::nullopt;
if (bias_opt.has_value() && bias_opt.value().defined()) {
bias_tensor = bias_opt.value();
}
torch::Tensor output = torch::empty(x.sizes(), x.options());
EXEC_NPU_CMD(aclnnCausalConv1d,
x,
weight,
bias_tensor,
conv_state,
query_start_loc_opt,
cache_indices_opt,
initial_state_mode_opt,
num_accepted_tokens_opt,
activation_mode,
pad_slot_id,
run_mode,
output);
return output;
}
} // namespace xllm::kernel::npu

View File

@@ -0,0 +1,83 @@
/* Copyright 2026 The xLLM Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <glog/logging.h>
#include "core/kernels/npu/aclnn/pytorch_npu_helper.hpp"
#include "core/kernels/npu/npu_ops_api.h"
#include "core/kernels/npu/utils.h"
namespace {
c10::optional<torch::Tensor> to_c10_optional_tensor(
const std::optional<torch::Tensor>& tensor_opt) {
if (tensor_opt.has_value() && tensor_opt.value().defined()) {
return tensor_opt.value();
}
return c10::nullopt;
}
} // namespace
namespace xllm::kernel::npu {
torch::Tensor npu_recurrent_gated_delta_rule(
const torch::Tensor& query,
const torch::Tensor& key,
const torch::Tensor& value,
torch::Tensor& state,
const std::optional<torch::Tensor>& beta,
const std::optional<double> scale,
const std::optional<torch::Tensor>& actual_seq_lengths,
const std::optional<torch::Tensor>& ssm_state_indices,
const std::optional<torch::Tensor>& num_accepted_tokens,
const std::optional<torch::Tensor>& g,
const std::optional<torch::Tensor>& gk) {
check_tensor(query, "query", "recurrent_gated_delta_rule");
check_tensor(key, "key", "recurrent_gated_delta_rule");
check_tensor(value, "value", "recurrent_gated_delta_rule");
check_tensor(state, "state", "recurrent_gated_delta_rule");
CHECK(scale.has_value())
<< "recurrent_gated_delta_rule requires a valid scale value";
c10::optional<torch::Tensor> beta_tensor = to_c10_optional_tensor(beta);
c10::optional<torch::Tensor> actual_seq_lengths_tensor =
to_c10_optional_tensor(actual_seq_lengths);
c10::optional<torch::Tensor> ssm_state_indices_tensor =
to_c10_optional_tensor(ssm_state_indices);
c10::optional<torch::Tensor> num_accepted_tokens_tensor =
to_c10_optional_tensor(num_accepted_tokens);
c10::optional<torch::Tensor> g_tensor = to_c10_optional_tensor(g);
c10::optional<torch::Tensor> gk_tensor = to_c10_optional_tensor(gk);
float scale_value = static_cast<float>(scale.value());
torch::Tensor output = torch::empty_like(value);
EXEC_NPU_CMD(aclnnRecurrentGatedDeltaRule,
query,
key,
value,
beta_tensor,
state,
actual_seq_lengths_tensor,
ssm_state_indices_tensor,
g_tensor,
gk_tensor,
num_accepted_tokens_tensor,
scale_value,
output);
return output;
}
} // namespace xllm::kernel::npu

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,177 @@
/* Copyright 2025 The xLLM Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://github.com/jd-opensource/xllm/blob/main/LICENSE
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#pragma once
#include "param.h"
namespace xllm::kernel {
static const std::string kActModeSilu = "silu";
static const std::string kActModeGelu = "gelu";
static const std::string kActModeQuickGelu = "quick_gelu";
static const std::string kActModeSwish = "swish";
void apply_rotary(RotaryParams& params);
void active(ActivationParams& params);
void reshape_paged_cache(ReshapePagedCacheParams& params);
void reshape_from_cache(ReshapeFromCacheParams& params);
// Quantize and store KV cache to paged cache (INT8 quantization)
// Only supported on MLU backend
void quant_to_paged_cache(ReshapePagedCacheParams& params);
// Dequantize KV cache from paged cache (INT8 to FP16/BF16)
// Only supported on MLU backend
void dequant_from_paged_cache(ReshapeFromCacheParams& params);
void fused_layernorm(FusedLayerNormParams& params);
torch::Tensor matmul(MatmulParams& params);
torch::Tensor group_gemm(GroupGemmParams& params);
std::tuple<torch::Tensor, torch::Tensor> moe_active_topk(
MoeFusedTopkParams& params);
std::vector<torch::Tensor> moe_gen_idx(MoeGenIdxParams& params);
torch::Tensor moe_expand_input(MoeExpandInputParams& params);
torch::Tensor moe_combine_result(MoeCombineResultParams& params);
torch::Tensor moe_all2all_gen_send_layout(
MoeAll2AllGenSendLayoutParams& params);
std::vector<torch::Tensor> moe_all2all_gen_gather_index(
MoeAll2AllGenGatherIndexParams& params);
std::vector<torch::Tensor> moe_all2all_create(MoeAll2AllCreateParams& params);
void moe_all2all_init(MoeAll2AllInitParams& params);
void moe_all2all_dispatch(MoeAll2AllDispatchParams& params);
void moe_all2all_combine(MoeAll2AllCombineParams& params);
void moe_all2all_destroy(MoeAll2AllDestroyParams& params);
std::tuple<torch::Tensor, torch::Tensor> scaled_quantize(
ScaledQuantizeParams& params);
torch::Tensor scaled_matmul(ScaledMatmulParams& params);
torch::Tensor apply_top_k_top_p(TopKPParams& params);
torch::Tensor random_sample(RandomSampleParams& params);
torch::Tensor rejection_sample(RejectionSampleParams& params);
void masked_indexer_select_paged_kv(MaskedIndexerSelectPagedKVParams& params);
void gather_split(GatherSplitParams& params);
void fused_mla_q(FusedMlaQParams& params);
void fused_mla_kv(FusedMlaKVParams& params);
void fused_indexer_q(FusedIndexerQParams& params);
void fused_indexer_k(FusedIndexerKParams& params);
// L2 normalization along the last dimension
torch::Tensor l2_norm(torch::Tensor& x, double eps = 1e-6);
// TODO: NPU moe_init_routing_v2 is equivalent to moe_gen_idx + moe_expand_input
// (and token_count/cusum outputs) on other backends.
std::tuple<torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor>
moe_init_routing_v2(MoeInitRoutingV2Params& params);
// FP8 scaled quantize: quantizes input tensor to FP8 e4m3 format
// Returns: (quantized_output, scale)
std::tuple<torch::Tensor, torch::Tensor> fp8_scaled_quantize(
Fp8ScaledQuantizeParams& params);
// FP8 scaled matmul for W8A8 quantization using CUTLASS kernels
// Performs: c = (a @ b.T) with scales applied
torch::Tensor fp8_scaled_matmul(Fp8ScaledMatmulParams& params);
// Static scaled FP8 quantization helper
// Quantizes input tensor to FP8 using a pre-computed scale factor
void static_scaled_fp8_quant(StaticScaledFp8QuantParams& params);
// Fused RMSNorm + Static FP8 Quantization
// These fused operations combine RMSNorm and FP8 quantization to reduce memory
// bandwidth by avoiding the intermediate write-back to global memory.
// Fused RMSNorm + Static FP8 Quantization
// Returns: FP8 quantized output tensor
torch::Tensor rms_norm_static_fp8_quant(RmsNormStaticFp8QuantParams& params);
// Fused Add + RMSNorm + Static FP8 Quantization (with residual)
// Returns: tuple of (FP8 quantized output, updated residual)
std::tuple<torch::Tensor, torch::Tensor> fused_add_rms_norm_static_fp8_quant(
FusedAddRmsNormStaticFp8QuantParams& params);
std::pair<torch::Tensor, torch::Tensor> fused_gdn_gating(
FusedGdnGatingParams& params);
std::pair<torch::Tensor, torch::Tensor> fused_recurrent_gated_delta_rule(
FusedRecurrentGatedDeltaRuleParams& params);
torch::Tensor causal_conv1d_update(CausalConv1dUpdateParams& params);
torch::Tensor gated_layer_norm(GatedLayerNormParams& params);
std::pair<torch::Tensor, torch::Tensor> partial_rotary_embedding(
PartialRotaryEmbeddingParams& params);
std::tuple<torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor>
fused_qkvzba_split_reshape_cat(FusedQkvzbaSplitReshapeParams& params);
void gemma_rms_norm(GemmaRMSNormParams& params);
std::tuple<torch::Tensor, torch::Tensor, torch::Tensor, torch::Tensor>
split_qkv_rmsnorm_mrope(SplitQkvRmsnormMropeParams& params);
bool has_split_qkv_rmsnorm_mrope_specialization(int64_t num_q_heads,
int64_t num_kv_heads,
int64_t head_size);
torch::Tensor build_split_qkv_rmsnorm_mrope_gather_pattern(
int64_t rope_dim,
const std::vector<int64_t>& mrope_section,
bool is_interleaved,
const torch::Device& device);
std::pair<torch::Tensor, torch::Tensor> chunk_gated_delta_rule(
ChunkGatedDeltaRuleParams& params);
torch::Tensor recurrent_gated_delta_rule(
const torch::Tensor& query,
const torch::Tensor& key,
const torch::Tensor& value,
torch::Tensor& state,
const std::optional<torch::Tensor>& beta,
const std::optional<double> scale,
const std::optional<torch::Tensor>& actual_seq_lengths,
const std::optional<torch::Tensor>& ssm_state_indices,
const std::optional<torch::Tensor>& num_accepted_tokens,
const std::optional<torch::Tensor>& g,
const std::optional<torch::Tensor>& gk);
} // namespace xllm::kernel

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,122 @@
#!/bin/bash
# rebuild_test_k10.sh — Clean rebuild and test kernel 10 Config B
set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CUDA_DIR="${SCRIPT_DIR}/cuda"
echo "=== Clean old builds ==="
rm -rf "${SCRIPT_DIR}/build/tmp_hgemm_warptiling"
rm -f "${SCRIPT_DIR}/build/hgemm_warptiling.so"
echo "=== Compile ==="
python3 -c "
import torch.utils.cpp_extension as ext
import os, shutil, glob
name = 'hgemm_warptiling'
build_dir = '${SCRIPT_DIR}/build/tmp_' + name
os.makedirs(build_dir, exist_ok=True)
mod = ext.load(
name=name,
sources=[
'${CUDA_DIR}/hgemm_warptiling.cu',
'${CUDA_DIR}/bindings/hgemm_warp_bind.cpp',
],
extra_include_paths=['${CUDA_DIR}/headers'],
extra_cflags=['-O2', '-std=c++17'],
extra_cuda_cflags=['-O2'],
build_directory=build_dir,
verbose=True,
)
built = glob.glob(build_dir + '/' + name + '*.so')
if built:
dst = '${SCRIPT_DIR}/build/' + name + '.so'
shutil.copy2(built[0], dst)
print(f'[build] SUCCESS: {dst}')
"
echo ""
echo "=== Test ==="
python3 << 'PYTEST'
import torch, sys, os, glob, time, importlib.util
build_dir = 'ex_engine/xllm_kernels/build'
so = glob.glob(f'{build_dir}/tmp_hgemm_warptiling/hgemm_warptiling*.so')
if not so:
print("SKIP: .so not found")
sys.exit(0)
spec = importlib.util.spec_from_file_location("hgemm_warptiling", so[0])
hw = importlib.util.module_from_spec(spec)
spec.loader.exec_module(hw)
print(f"Loaded: {so[0]}")
# Test 1: tiny
print("\n--- 16x16 @ 16x16 ---")
A = torch.eye(16, dtype=torch.float16, device='cuda')
B = torch.ones(16, 16, dtype=torch.float16, device='cuda')
C = hw.hgemm_warp(A, B)
diff = (C.float() - B.float()).abs().max().item()
print(f" I @ ones = ones? diff={diff:.6f}")
# Test 2: 128x128
print("\n--- 128x128 @ 128x128 ---")
A = torch.randn(128, 128, dtype=torch.float16, device='cuda') * 0.1
B = torch.randn(128, 128, dtype=torch.float16, device='cuda') * 0.1
C_ref = torch.matmul(A.float(), B.float()).half()
C_k10 = hw.hgemm_warp(A, B)
diff = (C_ref.float() - C_k10.float()).abs().max().item()
print(f" max_diff={diff:.6f}")
if diff > 2.0:
# Debug: print a few values
print(f" C_ref[0,:5] = {C_ref[0,:5].tolist()}")
print(f" C_k10[0,:5] = {C_k10[0,:5].tolist()}")
print(f" C_ref[-1,-5:] = {C_ref[-1,-5:].tolist()}")
print(f" C_k10[-1,-5:] = {C_k10[-1,-5:].tolist()}")
print(" FAIL")
else:
print(" PASS")
# Test 3: MoE size
print("\n--- 256x4096 @ 4096x11008 ---")
A = torch.randn(256, 4096, dtype=torch.float16, device='cuda') * 0.01
B = torch.randn(4096, 11008, dtype=torch.float16, device='cuda') * 0.01
C_ref = torch.matmul(A.float(), B.float()).half()
C_k10 = hw.hgemm_warp(A, B)
diff = (C_ref.float() - C_k10.float()).abs().max().item()
rel = diff / (C_ref.float().abs().max().item() + 1e-8)
print(f" max_diff={diff:.6f}, rel={rel:.6f}")
if diff > 2.0:
print(f" C_ref[0,:5] = {C_ref[0,:5].tolist()}")
print(f" C_k10[0,:5] = {C_k10[0,:5].tolist()}")
print(" FAIL")
else:
print(" PASS")
# Test 4: Performance
print("\n--- Performance 256x4096 @ 4096x11008 ---")
for _ in range(10):
hw.hgemm_warp(A, B)
torch.cuda.synchronize()
t0 = time.time()
for _ in range(100):
hw.hgemm_warp(A, B)
torch.cuda.synchronize()
ms_k10 = (time.time() - t0) / 100 * 1000
for _ in range(10):
torch.matmul(A, B)
torch.cuda.synchronize()
t0 = time.time()
for _ in range(100):
torch.matmul(A, B)
torch.cuda.synchronize()
ms_torch = (time.time() - t0) / 100 * 1000
print(f" kernel 10: {ms_k10:.2f} ms")
print(f" torch.matmul: {ms_torch:.2f} ms")
print(f" ratio: {ms_k10/ms_torch:.2f}x")
PYTEST