chore: vendor sglang v0.5.10 snapshot
This commit is contained in:
100
third_party/sglang/sgl-kernel/csrc/cpu/CMakeLists.txt
vendored
Executable file
100
third_party/sglang/sgl-kernel/csrc/cpu/CMakeLists.txt
vendored
Executable file
@@ -0,0 +1,100 @@
|
||||
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
|
||||
project(sgl_kernel)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
find_package(Python COMPONENTS Interpreter Development.Module ${SKBUILD_SABI_COMPONENT} REQUIRED)
|
||||
|
||||
execute_process(
|
||||
COMMAND ${Python_EXECUTABLE}
|
||||
-c "import torch; print(torch.utils.cmake_prefix_path)"
|
||||
OUTPUT_VARIABLE TORCH_PY_PREFIX
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
|
||||
message(STATUS ${TORCH_PY_PREFIX})
|
||||
list(APPEND CMAKE_PREFIX_PATH ${TORCH_PY_PREFIX}/Torch)
|
||||
find_package(Torch REQUIRED)
|
||||
|
||||
include_directories(
|
||||
${TORCH_INCLUDE_DIRS}
|
||||
${TORCH_INSTALL_PREFIX}/include
|
||||
${Python_INCLUDE_DIRS}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../csrc
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
# Platform-specific library directory
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64")
|
||||
set(PLAT_LIB_DIR "/usr/lib/x86_64-linux-gnu")
|
||||
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64")
|
||||
set(PLAT_LIB_DIR "/usr/lib/aarch64-linux-gnu")
|
||||
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "ppc64le|ppc64")
|
||||
set(PLAT_LIB_DIR "/usr/lib/powerpc64le-linux-gnu")
|
||||
else()
|
||||
set(PLAT_LIB_DIR "/usr/lib/${CMAKE_SYSTEM_PROCESSOR}-linux-gnu")
|
||||
endif()
|
||||
link_directories(${PLAT_LIB_DIR})
|
||||
|
||||
# Conda library path support
|
||||
if(DEFINED ENV{CONDA_PREFIX})
|
||||
set(CONDA_LIB_DIR "$ENV{CONDA_PREFIX}/lib")
|
||||
message(STATUS "Using Conda lib dir: ${CONDA_LIB_DIR}")
|
||||
link_directories(${CONDA_LIB_DIR})
|
||||
set(CONDA_INCLUDE_DIR "$ENV{CONDA_PREFIX}/include")
|
||||
include_directories(${CONDA_INCLUDE_DIR})
|
||||
|
||||
# Look for libnuma in Conda's lib directory
|
||||
find_library(NUMA_LIB numa HINTS "${CONDA_LIB_DIR}")
|
||||
if(NUMA_LIB)
|
||||
message(STATUS "Found libnuma: ${NUMA_LIB}")
|
||||
else()
|
||||
message(FATAL_ERROR "libnuma not found in Conda environment at ${CONDA_LIB_DIR}\n"
|
||||
"Please install it using: conda install libnuma numactl\n")
|
||||
endif()
|
||||
else()
|
||||
if(DEFINED ENV{VIRTUAL_ENV})
|
||||
set(VENV_LIB_DIR "$ENV{VIRTUAL_ENV}/lib")
|
||||
message(STATUS "Using venv lib dir: ${VENV_LIB_DIR}")
|
||||
link_directories(${VENV_LIB_DIR})
|
||||
set(VENV_INCLUDE_DIR "$ENV{VIRTUAL_ENV}/include")
|
||||
include_directories(${VENV_INCLUDE_DIR})
|
||||
endif()
|
||||
# Look for libnuma in system env paths
|
||||
find_library(NUMA_LIB numa)
|
||||
if(NUMA_LIB)
|
||||
message(STATUS "Found libnuma: ${NUMA_LIB}")
|
||||
else()
|
||||
message(FATAL_ERROR "libnuma not found in system environment\n"
|
||||
"Please install it using: apt-get install libnuma numactl\n")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
file(GLOB_RECURSE SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp")
|
||||
|
||||
if(NOT DEFINED ENV{SGLANG_CPU_FP8_CVT_FTZ})
|
||||
set(ENV{SGLANG_CPU_FP8_CVT_FTZ} "1")
|
||||
endif()
|
||||
|
||||
if("$ENV{SGLANG_CPU_FP8_CVT_FTZ}" STREQUAL "1")
|
||||
message(STATUS "Enabling macro: SGLANG_CPU_FP8_CVT_FTZ")
|
||||
add_compile_definitions(SGLANG_CPU_FP8_CVT_FTZ)
|
||||
endif()
|
||||
|
||||
add_compile_options(
|
||||
-O3
|
||||
-Wno-unknown-pragmas
|
||||
-march=native
|
||||
-fopenmp
|
||||
)
|
||||
|
||||
Python_add_library(common_ops MODULE USE_SABI ${SKBUILD_SABI_VERSION} WITH_SOABI ${SOURCES})
|
||||
target_link_libraries(common_ops PRIVATE ${TORCH_LIBRARIES} ${NUMA_LIB})
|
||||
target_include_directories(common_ops PRIVATE ${TORCH_INCLUDE_DIRS})
|
||||
|
||||
install(TARGETS common_ops
|
||||
LIBRARY DESTINATION sgl_kernel
|
||||
)
|
||||
127
third_party/sglang/sgl-kernel/csrc/cpu/aarch64/shm.h
vendored
Normal file
127
third_party/sglang/sgl-kernel/csrc/cpu/aarch64/shm.h
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
#pragma once
|
||||
#include <arm_neon.h>
|
||||
|
||||
#define VECTOR_LENGTH_IN_BYTES 16
|
||||
|
||||
__attribute__((target("+bf16"))) inline float32x4x2_t cvt_bf16_to_fp32(const bfloat16x8_t src) {
|
||||
float32x4x2_t y;
|
||||
y.val[0] = vcvtq_low_f32_bf16(src);
|
||||
y.val[1] = vcvtq_high_f32_bf16(src);
|
||||
return y;
|
||||
}
|
||||
|
||||
__attribute__((target("+bf16"))) inline bfloat16x8_t cvt_fp32_to_bf16(const float32x4x2_t src) {
|
||||
return vcvtq_high_bf16_f32(vcvtq_low_bf16_f32(src.val[0]), src.val[1]);
|
||||
}
|
||||
|
||||
__attribute__((target("+bf16"))) inline void
|
||||
reduce_bf16_buffers(int start_elements, int num_elements, char* to_buffer, char** buffers, int world_size) {
|
||||
const int element_size = 2;
|
||||
const int vector_length = VECTOR_LENGTH_IN_BYTES / element_size;
|
||||
int main_elements = num_elements - (num_elements % vector_length);
|
||||
int remain_elements = num_elements % vector_length;
|
||||
|
||||
// process aligned part
|
||||
#pragma omp parallel for
|
||||
for (int i = start_elements * element_size; i < (start_elements + main_elements) * element_size;
|
||||
i += VECTOR_LENGTH_IN_BYTES) {
|
||||
float32x4x2_t inout_val = cvt_bf16_to_fp32(vld1q_bf16((const bfloat16_t*)(buffers[0] + i)));
|
||||
for (int j = 1; j < world_size; j++) {
|
||||
const float32x4x2_t in_val = cvt_bf16_to_fp32(vld1q_bf16((const bfloat16_t*)(buffers[j] + i)));
|
||||
inout_val.val[0] = vaddq_f32(inout_val.val[0], in_val.val[0]);
|
||||
inout_val.val[1] = vaddq_f32(inout_val.val[1], in_val.val[1]);
|
||||
}
|
||||
vst1q_bf16((bfloat16_t*)(to_buffer + i), cvt_fp32_to_bf16(inout_val));
|
||||
}
|
||||
|
||||
// process remaining part
|
||||
int i = (start_elements + main_elements) * element_size;
|
||||
while (remain_elements > 0) {
|
||||
float val = 0.0f;
|
||||
for (int j = 0; j < world_size; j++) {
|
||||
val += vcvtah_f32_bf16(*(bfloat16_t*)(buffers[j] + i));
|
||||
}
|
||||
*(bfloat16_t*)(to_buffer + i) = vcvth_bf16_f32(val);
|
||||
remain_elements--;
|
||||
i += element_size;
|
||||
}
|
||||
}
|
||||
|
||||
inline void reduce_fp16_buffers(int start_elements, int num_elements, char* to_buffer, char** buffers, int world_size) {
|
||||
const int element_size = 2;
|
||||
const int vector_length = VECTOR_LENGTH_IN_BYTES / element_size;
|
||||
int main_elements = num_elements - (num_elements % vector_length);
|
||||
int remain_elements = num_elements % vector_length;
|
||||
|
||||
// process aligned part
|
||||
#pragma omp parallel for
|
||||
for (int i = start_elements * element_size; i < (start_elements + main_elements) * element_size;
|
||||
i += VECTOR_LENGTH_IN_BYTES) {
|
||||
float16x8_t inout_val = vld1q_f16((const float16_t*)(buffers[0] + i));
|
||||
for (int j = 1; j < world_size; j++) {
|
||||
const float16x8_t in_val = vld1q_f16((const float16_t*)(buffers[j] + i));
|
||||
inout_val = vaddq_f16(inout_val, in_val);
|
||||
}
|
||||
vst1q_f16((float16_t*)(to_buffer + i), inout_val);
|
||||
}
|
||||
|
||||
// process remaining part
|
||||
int i = (start_elements + main_elements) * element_size;
|
||||
while (remain_elements > 0) {
|
||||
float16_t val = 0.0f;
|
||||
for (int j = 0; j < world_size; j++) {
|
||||
val = vaddh_f16(val, *(float16_t*)(buffers[j] + i));
|
||||
}
|
||||
*(float16_t*)(to_buffer + i) = val;
|
||||
remain_elements--;
|
||||
i += element_size;
|
||||
}
|
||||
}
|
||||
|
||||
inline void reduce_fp32_buffers(int start_elements, int num_elements, char* to_buffer, char** buffers, int world_size) {
|
||||
const int element_size = 4;
|
||||
const int vector_length = VECTOR_LENGTH_IN_BYTES / element_size;
|
||||
int main_elements = num_elements - (num_elements % vector_length);
|
||||
int remain_elements = num_elements % vector_length;
|
||||
|
||||
// process aligned part
|
||||
#pragma omp parallel for
|
||||
for (int i = start_elements * element_size; i < (start_elements + main_elements) * element_size;
|
||||
i += VECTOR_LENGTH_IN_BYTES) {
|
||||
float32x4_t inout_val = vld1q_f32((const float*)(buffers[0] + i));
|
||||
for (int j = 1; j < world_size; j++) {
|
||||
const float32x4_t in_val = vld1q_f32((const float*)(buffers[j] + i));
|
||||
inout_val = vaddq_f32(inout_val, in_val);
|
||||
}
|
||||
vst1q_f32((float32_t*)(to_buffer + i), inout_val);
|
||||
}
|
||||
|
||||
// process remaining part
|
||||
int i = (start_elements + main_elements) * element_size;
|
||||
while (remain_elements > 0) {
|
||||
float val = 0.0f;
|
||||
for (int j = 0; j < world_size; j++) {
|
||||
val += *(float*)(buffers[j] + i);
|
||||
}
|
||||
*(float*)(to_buffer + i) = val;
|
||||
remain_elements--;
|
||||
i += element_size;
|
||||
}
|
||||
}
|
||||
|
||||
inline void parallel_memcpy(void* to, void* from, size_t n_bytes) {
|
||||
auto aligned_bytes = n_bytes - (n_bytes % VECTOR_LENGTH_IN_BYTES);
|
||||
// process aligned part
|
||||
#pragma omp parallel for
|
||||
for (size_t i = 0; i < aligned_bytes; i += VECTOR_LENGTH_IN_BYTES) {
|
||||
const uint8x16_t val = vld1q_u8((uint8_t*)from + i);
|
||||
vst1q_u8((uint8_t*)to + i, val);
|
||||
}
|
||||
|
||||
// process remaining part
|
||||
for (size_t i = aligned_bytes; i < n_bytes; i++) {
|
||||
*((uint8_t*)to + i) = *((uint8_t*)from + i);
|
||||
}
|
||||
}
|
||||
|
||||
#undef VECTOR_LENGTH_IN_BYTES
|
||||
135
third_party/sglang/sgl-kernel/csrc/cpu/activation.cpp
vendored
Normal file
135
third_party/sglang/sgl-kernel/csrc/cpu/activation.cpp
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
#include "common.h"
|
||||
#include "vec.h"
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename scalar_t, typename func_t, typename vec_func_t>
|
||||
void act_and_mul_kernel_impl(
|
||||
scalar_t* __restrict__ output,
|
||||
const scalar_t* __restrict__ input,
|
||||
int64_t num_tokens,
|
||||
int64_t dim,
|
||||
const func_t& f,
|
||||
const vec_func_t& vf) {
|
||||
using bVec = at::vec::Vectorized<scalar_t>;
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
|
||||
constexpr int64_t kVecSize = bVec::size();
|
||||
at::parallel_for(0, num_tokens, 0, [&](int64_t begin, int64_t end) {
|
||||
for (int64_t i = begin; i < end; ++i) {
|
||||
// local ptrs
|
||||
const scalar_t* __restrict__ input_ptr = input + i * 2 * dim;
|
||||
const scalar_t* __restrict__ input_other_ptr = input_ptr + dim;
|
||||
scalar_t* __restrict__ output_ptr = output + i * dim;
|
||||
|
||||
int64_t d;
|
||||
#pragma GCC unroll 4
|
||||
for (d = 0; d <= dim - kVecSize; d += kVecSize) {
|
||||
bVec x_bvec = bVec::loadu(input_ptr + d);
|
||||
fVec x_fvec0, x_fvec1;
|
||||
std::tie(x_fvec0, x_fvec1) = at::vec::convert_to_float(x_bvec);
|
||||
|
||||
bVec y_bvec = bVec::loadu(input_other_ptr + d);
|
||||
fVec y_fvec0, y_fvec1;
|
||||
std::tie(y_fvec0, y_fvec1) = at::vec::convert_to_float(y_bvec);
|
||||
|
||||
x_fvec0 = vf(x_fvec0);
|
||||
x_fvec1 = vf(x_fvec1);
|
||||
|
||||
x_fvec0 = x_fvec0 * y_fvec0;
|
||||
x_fvec1 = x_fvec1 * y_fvec1;
|
||||
|
||||
x_bvec = convert_from_float_ext<scalar_t>(x_fvec0, x_fvec1);
|
||||
x_bvec.store(output_ptr + d);
|
||||
}
|
||||
#pragma GCC unroll 4
|
||||
for (; d < dim; ++d) {
|
||||
float x_val = static_cast<float>(input_ptr[d]);
|
||||
float y_val = static_cast<float>(input_other_ptr[d]);
|
||||
output_ptr[d] = f(x_val) * y_val;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// input : {num_tokens, 2 * d}
|
||||
// output : {num_tokens, d}
|
||||
at::Tensor silu_and_mul_cpu(at::Tensor& input) {
|
||||
RECORD_FUNCTION("sgl-kernel::silu_and_mul_cpu", std::vector<c10::IValue>({input}));
|
||||
auto sizes = input.sizes().vec();
|
||||
int64_t last_dim = input.ndimension() - 1;
|
||||
int64_t d = sizes[last_dim] / 2;
|
||||
sizes[last_dim] = d;
|
||||
int64_t num_tokens = input.numel() / input.size(-1);
|
||||
at::Tensor out = at::empty(sizes, input.options());
|
||||
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(input.scalar_type(), "silu_and_mul", [&] {
|
||||
using Vec = at::vec::Vectorized<float>;
|
||||
act_and_mul_kernel_impl(
|
||||
out.data_ptr<scalar_t>(),
|
||||
input.data_ptr<scalar_t>(),
|
||||
num_tokens,
|
||||
d,
|
||||
[](float x) { return x / (1.f + std::exp(-x)); },
|
||||
[](Vec x) { return x / (Vec(1.f) + x.neg().exp()); });
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
at::Tensor gelu_tanh_and_mul_cpu(const at::Tensor& input) {
|
||||
RECORD_FUNCTION("sgl-kernel::gelu_tanh_and_mul_cpu", std::vector<c10::IValue>({input}));
|
||||
auto sizes = input.sizes().vec();
|
||||
int64_t last_dim = input.ndimension() - 1;
|
||||
int64_t d = sizes[last_dim] / 2;
|
||||
sizes[last_dim] = d;
|
||||
int64_t num_tokens = input.numel() / input.size(-1);
|
||||
at::Tensor out = at::empty(sizes, input.options());
|
||||
const float sqrt_2_div_pi = std::sqrt(2.f / M_PI);
|
||||
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(input.scalar_type(), "gelu_tanh_and_mul", [&] {
|
||||
using Vec = at::vec::Vectorized<float>;
|
||||
act_and_mul_kernel_impl(
|
||||
out.data_ptr<scalar_t>(),
|
||||
input.data_ptr<scalar_t>(),
|
||||
num_tokens,
|
||||
d,
|
||||
[sqrt_2_div_pi](float x) {
|
||||
float x3 = x * x * x;
|
||||
float tanh_arg = sqrt_2_div_pi * (x + 0.044715f * x3);
|
||||
return 0.5f * x * (1.f + std::tanh(tanh_arg));
|
||||
},
|
||||
[sqrt_2_div_pi](Vec x) {
|
||||
Vec x3 = x * x * x;
|
||||
Vec tanh_arg = Vec(sqrt_2_div_pi) * (x + Vec(0.044715f) * x3);
|
||||
return Vec(0.5f) * x * (Vec(1.f) + tanh_arg.tanh());
|
||||
});
|
||||
});
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
at::Tensor gelu_and_mul_cpu(const at::Tensor& input) {
|
||||
RECORD_FUNCTION("sgl-kernel::gelu_and_mul_cpu", std::vector<c10::IValue>({input}));
|
||||
auto sizes = input.sizes().vec();
|
||||
int64_t last_dim = input.ndimension() - 1;
|
||||
int64_t d = sizes[last_dim] / 2;
|
||||
sizes[last_dim] = d;
|
||||
int64_t num_tokens = input.numel() / input.size(-1);
|
||||
at::Tensor out = at::empty(sizes, input.options());
|
||||
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(input.scalar_type(), "gelu_and_mul", [&] {
|
||||
using Vec = at::vec::Vectorized<float>;
|
||||
const float inv_sqrt2 = 1.0f / std::sqrt(2.0f);
|
||||
act_and_mul_kernel_impl(
|
||||
out.data_ptr<scalar_t>(),
|
||||
input.data_ptr<scalar_t>(),
|
||||
num_tokens,
|
||||
d,
|
||||
[inv_sqrt2](float x) { return 0.5f * x * (1.f + std::erf(x * inv_sqrt2)); },
|
||||
[inv_sqrt2](Vec x) { return Vec(0.5f) * x * (Vec(1.f) + (x * Vec(inv_sqrt2)).erf()); });
|
||||
});
|
||||
|
||||
return out;
|
||||
}
|
||||
205
third_party/sglang/sgl-kernel/csrc/cpu/bmm.cpp
vendored
Normal file
205
third_party/sglang/sgl-kernel/csrc/cpu/bmm.cpp
vendored
Normal file
@@ -0,0 +1,205 @@
|
||||
#include "common.h"
|
||||
#include "gemm.h"
|
||||
#include "vec.h"
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename scalar_t, typename packed_t>
|
||||
void bmm_kernel_impl(
|
||||
scalar_t* __restrict__ out,
|
||||
const scalar_t* __restrict__ mat1,
|
||||
const packed_t* __restrict__ mat2,
|
||||
int64_t B,
|
||||
int64_t M,
|
||||
int64_t N,
|
||||
int64_t K,
|
||||
int64_t mat1_strideB,
|
||||
int64_t mat1_strideM,
|
||||
int64_t out_strideB,
|
||||
int64_t out_strideM,
|
||||
float scale = 0.f) {
|
||||
constexpr int64_t BLOCK_M = block_size_m();
|
||||
constexpr int64_t BLOCK_N = block_size_n();
|
||||
const int64_t MB = div_up(M, BLOCK_M);
|
||||
const int64_t NB = div_up(N, BLOCK_N);
|
||||
|
||||
// mat2 contiguous in [B, N, K]
|
||||
int64_t mat2_strideB = N * K;
|
||||
int64_t mat2_strideN = K;
|
||||
|
||||
const bool use_brgemm = can_use_brgemm<scalar_t>(M);
|
||||
|
||||
// parallel on [B, MB, NB]
|
||||
at::parallel_for(0, B * MB * NB, 0, [&](int64_t begin, int64_t end) {
|
||||
int64_t bs{0}, mb{0}, nb{0};
|
||||
data_index_init(begin, bs, B, mb, MB, nb, NB);
|
||||
|
||||
// for brgemm, use float32 for accumulate
|
||||
alignas(64) float Ctmp[BLOCK_M * BLOCK_N];
|
||||
|
||||
for (int i = begin; i < end; ++i) {
|
||||
UNUSED(i);
|
||||
int mb_start = mb * BLOCK_M;
|
||||
int mb_size = std::min(M - mb_start, BLOCK_M);
|
||||
int nb_start = nb * BLOCK_N;
|
||||
int nb_size = std::min(N - nb_start, BLOCK_N);
|
||||
|
||||
tinygemm_kernel<scalar_t>(
|
||||
/* A */ mat1 + bs * mat1_strideB + mb_start * mat1_strideM,
|
||||
/* B */ mat2 + bs * mat2_strideB + nb_start * mat2_strideN /* nb * BLOCK_N * K */,
|
||||
/* C */ out + bs * out_strideB + mb_start * out_strideM + nb_start,
|
||||
/* Ctmp*/ Ctmp,
|
||||
/* M */ mb_size,
|
||||
/* N */ nb_size,
|
||||
/* K */ K,
|
||||
/* lda */ mat1_strideM,
|
||||
/* ldb */ nb_size,
|
||||
/* ldc */ out_strideM,
|
||||
/* brg */ use_brgemm);
|
||||
|
||||
// move to the next index
|
||||
data_index_step(bs, B, mb, MB, nb, NB);
|
||||
}
|
||||
|
||||
if (use_brgemm) {
|
||||
at::native::cpublas::brgemm_release();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <>
|
||||
void bmm_kernel_impl(
|
||||
at::BFloat16* __restrict__ out,
|
||||
const at::BFloat16* __restrict__ mat1,
|
||||
const at::Float8_e4m3fn* __restrict__ mat2,
|
||||
int64_t B,
|
||||
int64_t M,
|
||||
int64_t N,
|
||||
int64_t K,
|
||||
int64_t mat1_strideB,
|
||||
int64_t mat1_strideM,
|
||||
int64_t out_strideB,
|
||||
int64_t out_strideM,
|
||||
float scale) {
|
||||
constexpr int64_t BLOCK_M = block_size_m();
|
||||
constexpr int64_t BLOCK_N = block_size_n();
|
||||
const int64_t MB = div_up(M, BLOCK_M);
|
||||
const int64_t NB = div_up(N, BLOCK_N);
|
||||
|
||||
// mat2 contiguous in [B, N, K]
|
||||
int64_t mat2_strideB = N * K;
|
||||
int64_t mat2_strideN = K;
|
||||
|
||||
const bool use_brgemm = can_use_brgemm<at::BFloat16>(M);
|
||||
|
||||
// parallel on [B, MB, NB]
|
||||
parallel_2d(B * MB, NB, [&](int64_t mb0, int64_t mb1, int64_t nb0, int64_t nb1) {
|
||||
// for brgemm, use float32 for accumulate
|
||||
alignas(64) float Ctmp[BLOCK_M * BLOCK_N];
|
||||
// for brgemm when mat2 is float8_e4m3
|
||||
alignas(64) at::BFloat16 Btmp[BLOCK_N * BLOCK_K];
|
||||
|
||||
loop_2d<at::Float8_e4m3fn>(mb0, mb1, nb0, nb1, BLOCK_N * K, [&](int64_t mb, int64_t nb, int64_t nb_offset) {
|
||||
int64_t bs = mb / MB;
|
||||
int64_t mb_start = (mb % MB) * BLOCK_M;
|
||||
int64_t mb_size = std::min(M - mb_start, BLOCK_M);
|
||||
int64_t nb_start = nb * BLOCK_N;
|
||||
int64_t nb_size = std::min(N - nb_start, BLOCK_N);
|
||||
|
||||
tinygemm_kernel(
|
||||
/* A */ mat1 + bs * mat1_strideB + mb_start * mat1_strideM,
|
||||
/* B */ mat2 + bs * mat2_strideB + nb_start * mat2_strideN /* nb * BLOCK_N * K */,
|
||||
/* C */ out + bs * out_strideB + mb_start * out_strideM + nb_start,
|
||||
/* Btmp*/ Btmp,
|
||||
/* Ctmp*/ Ctmp,
|
||||
/*scale*/ scale,
|
||||
/* M */ mb_size,
|
||||
/* N */ nb_size,
|
||||
/* K */ K,
|
||||
/* lda */ mat1_strideM,
|
||||
/* ldb */ nb_size,
|
||||
/* ldc */ out_strideM,
|
||||
/* brg */ use_brgemm);
|
||||
});
|
||||
|
||||
if (use_brgemm) {
|
||||
at::native::cpublas::brgemm_release();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// mat1 : [B, M, K]
|
||||
// mat2 : [B, N, K] or [B, OC, IC]
|
||||
// out : [B, M, N]
|
||||
// scale: [] 0-dim tensor for per tensor quant
|
||||
//
|
||||
void bmm_cpu(
|
||||
at::Tensor& out, at::Tensor& mat1, at::Tensor& mat2, bool is_vnni, const std::optional<at::Tensor>& scale) {
|
||||
RECORD_FUNCTION("sgl-kernel::bmm_cpu", std::vector<c10::IValue>({out, mat1, mat2}));
|
||||
|
||||
auto packed_w = is_vnni ? mat2 : convert_weight_packed(mat2);
|
||||
|
||||
// input and out could be non-contiguous
|
||||
// weight needs to be contiguous in [OC, IC] order
|
||||
CHECK_LAST_DIM_CONTIGUOUS_INPUT(mat1);
|
||||
CHECK_LAST_DIM_CONTIGUOUS_INPUT(out);
|
||||
CHECK_INPUT(mat2);
|
||||
CHECK_DIM(3, out);
|
||||
CHECK_DIM(3, mat1);
|
||||
CHECK_DIM(3, mat2);
|
||||
|
||||
int64_t B = mat1.size(0);
|
||||
int64_t M = mat1.size(1);
|
||||
int64_t N = mat2.size(1);
|
||||
int64_t K = mat1.size(2);
|
||||
|
||||
const bool use_fp8_w8a16 = scale.has_value();
|
||||
TORCH_CHECK(N % 32 == 0, "tinygemm requires N to be 32x.");
|
||||
|
||||
int64_t mat1_strideB = mat1.stride(0);
|
||||
int64_t mat1_strideM = mat1.stride(1);
|
||||
int64_t out_strideB = out.stride(0);
|
||||
int64_t out_strideM = out.stride(1);
|
||||
|
||||
// check shapes
|
||||
TORCH_CHECK(mat2.size(0) == B && mat2.size(2) == K, "bmm: mat2 shape mismatch!");
|
||||
TORCH_CHECK(out.size(0) == B && out.size(1) == M, "bmm: out shape mismatch!");
|
||||
if (!use_fp8_w8a16) {
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(mat1.scalar_type(), "bmm_kernel_impl", [&] {
|
||||
bmm_kernel_impl<scalar_t, scalar_t>(
|
||||
out.data_ptr<scalar_t>(),
|
||||
mat1.data_ptr<scalar_t>(),
|
||||
packed_w.data_ptr<scalar_t>(),
|
||||
B,
|
||||
M,
|
||||
N,
|
||||
K,
|
||||
mat1_strideB,
|
||||
mat1_strideM,
|
||||
out_strideB,
|
||||
out_strideM);
|
||||
});
|
||||
} else { // fp8 bmm
|
||||
float scale_val = 0.f;
|
||||
|
||||
auto scale_tensor = scale.value();
|
||||
TORCH_CHECK(scale_tensor.ndimension() == 0, "bmm: expect scale to be 0-dim tensor.");
|
||||
scale_val = scale_tensor.item<float>();
|
||||
|
||||
bmm_kernel_impl<at::BFloat16, at::Float8_e4m3fn>(
|
||||
out.data_ptr<at::BFloat16>(),
|
||||
mat1.data_ptr<at::BFloat16>(),
|
||||
packed_w.data_ptr<at::Float8_e4m3fn>(),
|
||||
B,
|
||||
M,
|
||||
N,
|
||||
K,
|
||||
mat1_strideB,
|
||||
mat1_strideM,
|
||||
out_strideB,
|
||||
out_strideM,
|
||||
scale_val);
|
||||
}
|
||||
}
|
||||
370
third_party/sglang/sgl-kernel/csrc/cpu/common.h
vendored
Normal file
370
third_party/sglang/sgl-kernel/csrc/cpu/common.h
vendored
Normal file
@@ -0,0 +1,370 @@
|
||||
#pragma once
|
||||
|
||||
#include <ATen/ATen.h>
|
||||
#include <ATen/Parallel.h>
|
||||
#include <ATen/record_function.h>
|
||||
|
||||
#if defined(_OPENMP)
|
||||
#include <omp.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
// dispatch bool
|
||||
#define AT_DISPATCH_BOOL(BOOL_V, BOOL_NAME, ...) \
|
||||
[&] { \
|
||||
if (BOOL_V) { \
|
||||
constexpr bool BOOL_NAME = true; \
|
||||
return __VA_ARGS__(); \
|
||||
} else { \
|
||||
constexpr bool BOOL_NAME = false; \
|
||||
return __VA_ARGS__(); \
|
||||
} \
|
||||
}()
|
||||
|
||||
#define AT_DISPATCH_BOOL2(BOOL_V1, BOOL_NAME1, BOOL_V2, BOOL_NAME2, ...) \
|
||||
[&] { \
|
||||
if (BOOL_V1) { \
|
||||
constexpr bool BOOL_NAME1 = true; \
|
||||
if (BOOL_V2) { \
|
||||
constexpr bool BOOL_NAME2 = true; \
|
||||
return __VA_ARGS__(); \
|
||||
} else { \
|
||||
constexpr bool BOOL_NAME2 = false; \
|
||||
return __VA_ARGS__(); \
|
||||
} \
|
||||
} else { \
|
||||
constexpr bool BOOL_NAME1 = false; \
|
||||
if (BOOL_V2) { \
|
||||
constexpr bool BOOL_NAME2 = true; \
|
||||
return __VA_ARGS__(); \
|
||||
} else { \
|
||||
constexpr bool BOOL_NAME2 = false; \
|
||||
return __VA_ARGS__(); \
|
||||
} \
|
||||
} \
|
||||
}()
|
||||
|
||||
// dispatch: bfloat16, float16, int8_t, fp8_e4m3, uint8_t(mxfp4/int4)
|
||||
#define CPU_DISPATCH_PACKED_TYPES(TYPE, ...) \
|
||||
[&] { \
|
||||
switch (TYPE) { \
|
||||
case at::ScalarType::BFloat16: { \
|
||||
using packed_t = at::BFloat16; \
|
||||
return __VA_ARGS__(); \
|
||||
} \
|
||||
case at::ScalarType::Half: { \
|
||||
using packed_t = at::Half; \
|
||||
return __VA_ARGS__(); \
|
||||
} \
|
||||
case at::ScalarType::Char: { \
|
||||
using packed_t = int8_t; \
|
||||
return __VA_ARGS__(); \
|
||||
} \
|
||||
case at::ScalarType::Float8_e4m3fn: { \
|
||||
using packed_t = at::Float8_e4m3fn; \
|
||||
return __VA_ARGS__(); \
|
||||
} \
|
||||
case at::ScalarType::Byte: { \
|
||||
using packed_t = uint8_t; \
|
||||
return __VA_ARGS__(); \
|
||||
} \
|
||||
default: \
|
||||
TORCH_CHECK(false, "Unsupported floating data type.\n"); \
|
||||
} \
|
||||
}()
|
||||
|
||||
// dispatch with mixed dtypes (TYPE1, TYPE2):
|
||||
// TYPE1: the primary dtype (input, output, weight);
|
||||
// TYPE2: the secondary dtype (bias, etc.).
|
||||
#define CPU_DISPATCH_REDUCED_FLOATING_TYPES_EXT(TYPE1, TYPE2, ...) \
|
||||
[&] { \
|
||||
if (TYPE2 == at::kFloat) { \
|
||||
switch (TYPE1) { \
|
||||
case at::ScalarType::BFloat16: { \
|
||||
using scalar_t = at::BFloat16; \
|
||||
using param_t = float; \
|
||||
return __VA_ARGS__(); \
|
||||
} \
|
||||
case at::ScalarType::Half: { \
|
||||
using scalar_t = at::Half; \
|
||||
using param_t = float; \
|
||||
return __VA_ARGS__(); \
|
||||
} \
|
||||
default: \
|
||||
TORCH_CHECK(false, "Unsupported floating data type.\n"); \
|
||||
} \
|
||||
} else { \
|
||||
TORCH_CHECK(TYPE1 == TYPE2); \
|
||||
switch (TYPE1) { \
|
||||
case at::ScalarType::BFloat16: { \
|
||||
using scalar_t = at::BFloat16; \
|
||||
using param_t = at::BFloat16; \
|
||||
return __VA_ARGS__(); \
|
||||
} \
|
||||
case at::ScalarType::Half: { \
|
||||
using scalar_t = at::Half; \
|
||||
using param_t = at::Half; \
|
||||
return __VA_ARGS__(); \
|
||||
} \
|
||||
default: \
|
||||
TORCH_CHECK(false, "Unsupported floating data type.\n"); \
|
||||
} \
|
||||
} \
|
||||
}()
|
||||
|
||||
#define UNUSED(x) (void)(x)
|
||||
|
||||
#define CHECK_CPU(x) TORCH_CHECK(x.device().type() == at::kCPU, #x " must be a CPU tensor")
|
||||
|
||||
#define CHECK_CONTIGUOUS(x) TORCH_CHECK(x.is_contiguous(), #x " must be contiguous")
|
||||
#define CHECK_LAST_DIM_CONTIGUOUS(x) \
|
||||
TORCH_CHECK(x.strides()[x.strides().size() - 1] == 1, #x "must be contiguous at last dimension")
|
||||
|
||||
#define CHECK_INPUT(x) \
|
||||
CHECK_CPU(x); \
|
||||
CHECK_CONTIGUOUS(x)
|
||||
#define CHECK_LAST_DIM_CONTIGUOUS_INPUT(x) \
|
||||
CHECK_CPU(x); \
|
||||
CHECK_LAST_DIM_CONTIGUOUS(x)
|
||||
|
||||
#define CHECK_DIM(d, x) TORCH_CHECK(x.dim() == d, #x " must be a " #d "D tensor")
|
||||
|
||||
#define CHECK_EQ(a, b) TORCH_CHECK((a) == (b), "CHECK_EQ(" #a ", " #b ") failed. ", a, " vs ", b)
|
||||
#define CHECK_GT(a, b) TORCH_CHECK((a) > (b), "CHECK_GT(" #a ", " #b ") failed. ", a, " vs ", b)
|
||||
#define CHECK_GE(a, b) TORCH_CHECK((a) >= (b), "CHECK_GE(" #a ", " #b ") failed. ", a, " vs ", b)
|
||||
|
||||
template <bool is_only_lastdim_contiguous>
|
||||
static inline void CHECK_INPUT_SHAPE_DTYPE(const at::Tensor& tensor, const at::IntArrayRef sizes, at::ScalarType st) {
|
||||
TORCH_CHECK(tensor.sizes() == sizes, "Input tensor shape mismatch: expected ", sizes, ", got ", tensor.sizes());
|
||||
TORCH_CHECK(tensor.scalar_type() == st, "Input tensor dtype mismatch");
|
||||
if constexpr (is_only_lastdim_contiguous) {
|
||||
CHECK_LAST_DIM_CONTIGUOUS_INPUT(tensor);
|
||||
} else {
|
||||
CHECK_INPUT(tensor);
|
||||
}
|
||||
}
|
||||
|
||||
// [NB] Parallel Routines
|
||||
//
|
||||
// * at::parallel_for - applies for most of generic use cases, this will be compiled
|
||||
// against openmp in default torch release.
|
||||
//
|
||||
// * parallel_for - same function as above, can choose payload partition scheme in
|
||||
// balance211.
|
||||
//
|
||||
// * parallel_2d - parallel for 2 dimensions, used in GEMM, etc.
|
||||
// this one will do payload balance across 2 dimensions.
|
||||
//
|
||||
|
||||
// grain size for each thread
|
||||
constexpr int GRAIN_SIZE = 1024;
|
||||
|
||||
template <typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
|
||||
inline T div_up(T x, T y) {
|
||||
return (x + y - 1) / y;
|
||||
}
|
||||
|
||||
// you can only use at::get_thread_num() with at::parallel_for()
|
||||
// as it is lazy initialized, otherwise it will always return 0.
|
||||
inline int get_thread_num() {
|
||||
#if defined(_OPENMP)
|
||||
return omp_get_thread_num();
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
// balance payload across each thread
|
||||
template <typename T>
|
||||
inline void balance211(T n, T nth, T ith, T& n_start, T& n_end) {
|
||||
#if 0
|
||||
// onednn partition pattern
|
||||
T& n_my = n_end;
|
||||
if (nth <= 1 || n == 0) {
|
||||
n_start = 0;
|
||||
n_my = n;
|
||||
} else {
|
||||
T n1 = div_up(n, nth);
|
||||
T n2 = n1 - 1;
|
||||
T T1 = n - n2 * nth;
|
||||
n_my = ith < T1 ? n1 : n2;
|
||||
n_start = ith <= T1 ? ith*n1 : T1 * n1 + (ith - T1) * n2;
|
||||
}
|
||||
n_end += n_start;
|
||||
#else
|
||||
// pytorch aten partition pattern
|
||||
T n_my = div_up(n, nth);
|
||||
n_start = ith * n_my;
|
||||
n_end = std::min(n_start + n_my, n);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename func_t>
|
||||
inline void parallel_for(int n, const func_t& f) {
|
||||
#if defined(_OPENMP)
|
||||
#pragma omp parallel
|
||||
{
|
||||
int nth = omp_get_num_threads();
|
||||
int ith = omp_get_thread_num();
|
||||
int tbegin, tend;
|
||||
balance211(n, nth, ith, tbegin, tend);
|
||||
f(tbegin, tend);
|
||||
}
|
||||
#else
|
||||
f(0, n);
|
||||
#endif
|
||||
}
|
||||
|
||||
// for 1d parallel, use `actual_nth`
|
||||
// for 2d parallel, use even nths, e.g. 43->42
|
||||
int inline adjust_num_threads(int m) {
|
||||
int actual_nth = at::get_num_threads();
|
||||
if (m == 1) {
|
||||
return actual_nth;
|
||||
}
|
||||
return std::max(1, (actual_nth >> 1) * 2);
|
||||
}
|
||||
|
||||
template <typename func_t>
|
||||
inline void parallel_2d(int m, int n, const func_t& f) {
|
||||
// make sure we have even num_threads
|
||||
int nth = adjust_num_threads(m);
|
||||
|
||||
// [NOTE] thread blocking:
|
||||
//
|
||||
// 1) prefer square block per thread
|
||||
// 2) use even number of CPU cores
|
||||
// 3) use all `num_threads` cores
|
||||
//
|
||||
// we have:
|
||||
// TM * TN = T
|
||||
// BM / TM = BN / TN
|
||||
// then:
|
||||
// TM = ((BM / BN) * T) ^ 0.5
|
||||
//
|
||||
float r = float(m) / n;
|
||||
int nth_m = std::ceil(std::sqrt(r * nth));
|
||||
int nth_n = 1;
|
||||
for (; nth_m > 0; --nth_m) {
|
||||
nth_n = nth / nth_m;
|
||||
if (nth_m * nth_n == nth) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(_OPENMP)
|
||||
#pragma omp parallel num_threads(nth)
|
||||
{
|
||||
int ith = omp_get_thread_num();
|
||||
int ith_m = ith / nth_n;
|
||||
int ith_n = ith % nth_n;
|
||||
|
||||
int thread_block_m = div_up(m, nth_m);
|
||||
int thread_block_n = div_up(n, nth_n);
|
||||
|
||||
int begin_m = ith_m * thread_block_m;
|
||||
int end_m = std::min(m, begin_m + thread_block_m);
|
||||
int begin_n = ith_n * thread_block_n;
|
||||
int end_n = std::min(n, begin_n + thread_block_n);
|
||||
|
||||
f(begin_m, end_m, begin_n, end_n);
|
||||
}
|
||||
#else
|
||||
f(0, m, 0, n);
|
||||
#endif
|
||||
}
|
||||
|
||||
// limit max cache blocks
|
||||
// when we need to do pre-unpack for weights, e.g. fp8
|
||||
#define MAX_CACHE_BLOCK_SIZE 4
|
||||
|
||||
template <typename T>
|
||||
inline int get_cache_blocks(int chunk_size) {
|
||||
// L2 2MB and ratio of 50%
|
||||
const int L2_size = 2048 * 1024 >> 1;
|
||||
return std::max(1, int(L2_size / (chunk_size * sizeof(T))));
|
||||
}
|
||||
|
||||
template <>
|
||||
inline int get_cache_blocks<at::Float8_e4m3fn>(int chunk_size) {
|
||||
// fp8 uses bf16 as accumulate type
|
||||
int cache_block_size = get_cache_blocks<at::BFloat16>(chunk_size);
|
||||
return std::min(MAX_CACHE_BLOCK_SIZE, cache_block_size);
|
||||
}
|
||||
|
||||
// 2d sequential loop in range : [mb0, mb1), [nb0, nb1)
|
||||
template <typename T, typename func_t>
|
||||
inline void loop_2d(int64_t mb0, int64_t mb1, int64_t nb0, int64_t nb1, int64_t chunk_size, const func_t& f) {
|
||||
// get number of blocks for L2 in most inner loop
|
||||
int64_t cache_blocks_nb = get_cache_blocks<T>(chunk_size);
|
||||
|
||||
// loop order: [NB / cache_blocks_nb, MB, cache_blocks_nb]
|
||||
// TODO: implement reverse order of [MB / cache_blocks_mb, NB, cache_blocks_mb]
|
||||
for (int64_t nbb = nb0; nbb < nb1; nbb += cache_blocks_nb) {
|
||||
for (int64_t mb = mb0; mb < mb1; ++mb) {
|
||||
for (int64_t nb = nbb; nb < std::min(nbb + cache_blocks_nb, nb1); ++nb) {
|
||||
f(mb, nb, nb - nbb);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// data indexing for dimension collapse
|
||||
template <typename T>
|
||||
inline T data_index_init(T offset) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
inline T data_index_init(T offset, T& x, const T& X, Args&&... args) {
|
||||
offset = data_index_init(offset, std::forward<Args>(args)...);
|
||||
x = offset % X;
|
||||
return offset / X;
|
||||
}
|
||||
|
||||
inline bool data_index_step() {
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
inline bool data_index_step(T& x, const T& X, Args&&... args) {
|
||||
if (data_index_step(std::forward<Args>(args)...)) {
|
||||
x = ((x + 1) == X) ? 0 : (x + 1);
|
||||
return x == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// forced unroll for perf critical path
|
||||
|
||||
#if __has_attribute(always_inline)
|
||||
#define ALWAYS_INLINE __attribute__((__always_inline__)) inline
|
||||
#else
|
||||
#define ALWAYS_INLINE inline
|
||||
#endif
|
||||
|
||||
template <int n>
|
||||
struct Unroll {
|
||||
template <typename Func, typename... Args>
|
||||
ALWAYS_INLINE void operator()(const Func& f, Args... args) const {
|
||||
Unroll<n - 1>{}(f, args...);
|
||||
f(std::integral_constant<int, n - 1>{}, args...);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Unroll<1> {
|
||||
template <typename Func, typename... Args>
|
||||
ALWAYS_INLINE void operator()(const Func& f, Args... args) const {
|
||||
f(std::integral_constant<int, 0>{}, args...);
|
||||
}
|
||||
};
|
||||
|
||||
// conditional data ptr for optional tensor
|
||||
template <typename T>
|
||||
inline T* conditional_data_ptr(const std::optional<at::Tensor>& opt) {
|
||||
return opt.has_value() ? opt.value().data_ptr<T>() : nullptr;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
235
third_party/sglang/sgl-kernel/csrc/cpu/conv3d.cpp
vendored
Normal file
235
third_party/sglang/sgl-kernel/csrc/cpu/conv3d.cpp
vendored
Normal file
@@ -0,0 +1,235 @@
|
||||
#include "common.h"
|
||||
#include "gemm.h"
|
||||
#include "vec.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// convert to vnni format
|
||||
// from [N, K] to [K/2, N, 2] for bfloat16 and float16
|
||||
template <typename scalar_t>
|
||||
inline void
|
||||
pack_vnni(scalar_t* __restrict__ packed, const scalar_t* __restrict__ weight, int64_t N, int64_t K, int64_t lda) {
|
||||
const int64_t VNNI_BLK = 2;
|
||||
for (int64_t n = 0; n < N; ++n) {
|
||||
for (int64_t k = 0; k < K / VNNI_BLK; ++k) {
|
||||
for (int64_t d = 0; d < VNNI_BLK; ++d) {
|
||||
packed[k * N * VNNI_BLK + n * VNNI_BLK + d] = weight[n * lda + k * VNNI_BLK + d];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(CPU_CAPABILITY_AVX512)
|
||||
template <>
|
||||
inline void pack_vnni(
|
||||
at::BFloat16* __restrict__ packed, const at::BFloat16* __restrict__ weight, int64_t N, int64_t K, int64_t lda) {
|
||||
const float* src = reinterpret_cast<const float*>(weight);
|
||||
float* dst = reinterpret_cast<float*>(packed);
|
||||
int64_t K2 = K >> 1;
|
||||
int64_t lda2 = lda >> 1;
|
||||
int64_t ldb2 = N * 2 >> 1;
|
||||
|
||||
__m512i vinputs[16];
|
||||
|
||||
for (int64_t n = 0; n < N; n += 16) {
|
||||
for (int64_t k2 = 0; k2 < K2; k2 += 16) {
|
||||
for (int64_t d = 0; d < 16; ++d) {
|
||||
vinputs[d] = _mm512_loadu_si512(src + (n + d) * lda2 + k2);
|
||||
}
|
||||
transpose_16x16_32bit(vinputs);
|
||||
for (int64_t d = 0; d < 16; ++d) {
|
||||
_mm512_storeu_si512(dst + (k2 + d) * ldb2 + n, vinputs[d]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// apply bias: C [M, N] ldc, Ctmp: [M, N]
|
||||
template <typename scalar_t>
|
||||
inline void copy_add_stub(
|
||||
scalar_t* __restrict__ C,
|
||||
const float* __restrict__ Ctmp,
|
||||
const scalar_t* __restrict__ bias,
|
||||
int64_t M,
|
||||
int64_t N,
|
||||
int64_t ldc) {
|
||||
using bVec = at::vec::Vectorized<scalar_t>;
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
constexpr int kVecSize = bVec::size();
|
||||
|
||||
for (int64_t d = 0; d < N; d += kVecSize) {
|
||||
fVec bias0, bias1;
|
||||
bVec bias_vec = bVec::loadu(bias + d);
|
||||
std::tie(bias0, bias1) = at::vec::convert_to_float(bias_vec);
|
||||
|
||||
for (int64_t m = 0; m < M; ++m) {
|
||||
fVec data0 = fVec::loadu(Ctmp + m * N + d) + bias0;
|
||||
fVec data1 = fVec::loadu(Ctmp + m * N + d + fVec::size()) + bias1;
|
||||
bVec out_vec = convert_from_float_ext<scalar_t>(data0, data1);
|
||||
out_vec.store(C + m * ldc + d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
void conv3d_embed_kernel_impl(
|
||||
scalar_t* __restrict__ out,
|
||||
const scalar_t* __restrict__ input,
|
||||
const scalar_t* __restrict__ weight,
|
||||
const scalar_t* __restrict__ bias,
|
||||
int64_t N,
|
||||
int64_t IC,
|
||||
int64_t OC,
|
||||
int64_t D,
|
||||
int64_t H,
|
||||
int64_t W) {
|
||||
constexpr int64_t BLOCK_M = block_size_m();
|
||||
constexpr int64_t BLOCK_N = block_size_n();
|
||||
const int64_t MB = div_up(N, BLOCK_M);
|
||||
const int64_t NB = div_up(OC, BLOCK_N);
|
||||
|
||||
// K in gemm
|
||||
const int64_t K = IC * D * H * W;
|
||||
|
||||
// input : [ N/BLOCK_M, BLOCK_M, IC, D, H, W]
|
||||
// weight: [OC/BLOCK_N, IC, D, H*W/2, BLOCK_N, 2]
|
||||
// out : [N/BLOCK_M, BLOCK_M, OC/BLOCK_N, BLOCK_N]
|
||||
parallel_2d(MB, NB, [&](int64_t mb0, int64_t mb1, int64_t nb0, int64_t nb1) {
|
||||
alignas(64) float Ctmp[BLOCK_M * BLOCK_N];
|
||||
|
||||
loop_2d<scalar_t>(mb0, mb1, nb0, nb1, BLOCK_N * K, [&](int64_t mb, int64_t nb, int64_t nb_offset) {
|
||||
int64_t mb_start = mb * BLOCK_M;
|
||||
int64_t mb_size = std::min(N - mb_start, BLOCK_M);
|
||||
int64_t nb_start = nb * BLOCK_N;
|
||||
int64_t nb_size = std::min(OC - nb_start, BLOCK_N);
|
||||
|
||||
const scalar_t* __restrict__ A = input + mb_start * K;
|
||||
const scalar_t* __restrict__ B = weight + nb_start * K;
|
||||
#if 0
|
||||
// only access 1st index of D dimension
|
||||
for (int64_t ic = 0; ic < IC; ++ic) {
|
||||
for (int64_t d = 0; d < D; ++d) {
|
||||
at::native::cpublas::brgemm(
|
||||
mb_size,
|
||||
nb_size,
|
||||
H * W,
|
||||
K,
|
||||
BLOCK_N,
|
||||
BLOCK_N,
|
||||
/* add_C */ ic > 0 || d > 0,
|
||||
A + ic * (D * H * W) + /* d */ 0 * (H * W), // dimension D for input is repeated
|
||||
B + ic * (D * BLOCK_N * H * W) + d * (BLOCK_N * H * W),
|
||||
Ctmp);
|
||||
}
|
||||
#else
|
||||
// accumulates K normally, this is still marginally faster than above
|
||||
at::native::cpublas::brgemm(mb_size, nb_size, K, K, BLOCK_N, BLOCK_N, false, A, B, Ctmp);
|
||||
#endif
|
||||
// update bias
|
||||
copy_add_stub(out + mb_start * OC + nb_start, Ctmp, bias + nb_start, mb_size, nb_size, OC);
|
||||
});
|
||||
|
||||
at::native::cpublas::brgemm_release();
|
||||
});
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// [NB]: use blocked format for weight of OIDHW
|
||||
//
|
||||
// from [OC, Cin, D, H, W]
|
||||
// view [OC / BLOCK_N, BLOCK_N, Cin, D, H * W]
|
||||
// view [OC / BLOCK_N, IC, D, BLOCK_N, H * W]
|
||||
// to [OC / BLOCK_N][IC, D][H * W / 2, BLOCK_N, 2]
|
||||
// +- parallel -+- seq -+------ mma ----------+
|
||||
//
|
||||
at::Tensor conv3d_embed_weight_pack(const at::Tensor& weight) {
|
||||
CHECK_INPUT(weight);
|
||||
|
||||
int64_t OC = weight.size(0);
|
||||
int64_t IC = weight.size(1);
|
||||
int64_t D = weight.size(2);
|
||||
int64_t H = weight.size(3);
|
||||
int64_t W = weight.size(4);
|
||||
|
||||
constexpr int64_t BLOCK_N = block_size_n();
|
||||
TORCH_CHECK(OC % BLOCK_N == 0, "conv3d_embed_weight_pack: expect OC dividable by ", BLOCK_N);
|
||||
TORCH_CHECK((H * W) % TILE_K == 0, "conv3d_embed_weight_pack: expect IC dividable by ", TILE_K);
|
||||
|
||||
// strides
|
||||
int64_t stride_nb = BLOCK_N * IC * D * H * W;
|
||||
int64_t stride_ic = D * H * W;
|
||||
int64_t stride_d = H * W;
|
||||
|
||||
const int64_t NB = div_up(OC, BLOCK_N);
|
||||
at::Tensor packed_weight = at::empty_like(weight);
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(weight.scalar_type(), "conv3d_embed_weight_pack", [&] {
|
||||
// parallel {NB, IC, D}
|
||||
at::parallel_for(0, NB * IC * D, 0, [&](int64_t begin, int64_t end) {
|
||||
int64_t nb{0}, ic{0}, d{0};
|
||||
data_index_init(begin, nb, NB, ic, IC, d, D);
|
||||
|
||||
const scalar_t* w_data = weight.data_ptr<scalar_t>();
|
||||
scalar_t* packed_data = packed_weight.data_ptr<scalar_t>();
|
||||
|
||||
for (int64_t i = begin; i < end; ++i) {
|
||||
int64_t n = nb * BLOCK_N;
|
||||
int64_t n_size = std::min(BLOCK_N, OC - n); // BLOCK_N
|
||||
|
||||
pack_vnni<scalar_t>(
|
||||
packed_data + i * (BLOCK_N * H * W),
|
||||
w_data + nb * stride_nb + ic * stride_ic + d * stride_d,
|
||||
n_size,
|
||||
H * W,
|
||||
IC * D * H * W);
|
||||
|
||||
// move to the next index
|
||||
data_index_step(nb, NB, ic, IC, d, D);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return packed_weight;
|
||||
}
|
||||
|
||||
// conv3d mapped to gemm in embedding
|
||||
at::Tensor conv3d_embed_cpu(const at::Tensor& input, const at::Tensor& weight, const at::Tensor& bias, bool is_vnni) {
|
||||
RECORD_FUNCTION("sgl_kernel::conv3d_embed_cpu", std::vector<c10::IValue>({input, weight, bias}));
|
||||
|
||||
auto packed_w = is_vnni ? weight : conv3d_embed_weight_pack(weight);
|
||||
|
||||
CHECK_CONTIGUOUS(input);
|
||||
CHECK_CONTIGUOUS(weight);
|
||||
CHECK_DIM(5, input);
|
||||
CHECK_DIM(5, weight);
|
||||
|
||||
const int64_t N = input.size(0);
|
||||
const int64_t IC = input.size(1);
|
||||
const int64_t OC = weight.size(0);
|
||||
const int64_t D = input.size(2);
|
||||
const int64_t H = input.size(3);
|
||||
const int64_t W = input.size(4);
|
||||
|
||||
const auto st = input.scalar_type();
|
||||
CHECK_INPUT_SHAPE_DTYPE<false>(weight, {OC, IC, D, H, W}, st);
|
||||
CHECK_INPUT_SHAPE_DTYPE<false>(bias, {OC}, st);
|
||||
|
||||
// allocate {D, H, W} for out is 1
|
||||
at::Tensor out = at::empty({N, OC}, input.options());
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(st, "conv3d_embed_kernel_impl", [&] {
|
||||
conv3d_embed_kernel_impl<scalar_t>(
|
||||
out.data_ptr<scalar_t>(),
|
||||
input.data_ptr<scalar_t>(),
|
||||
packed_w.data_ptr<scalar_t>(),
|
||||
bias.data_ptr<scalar_t>(),
|
||||
N,
|
||||
IC,
|
||||
OC,
|
||||
D,
|
||||
H,
|
||||
W);
|
||||
});
|
||||
|
||||
return out;
|
||||
}
|
||||
1744
third_party/sglang/sgl-kernel/csrc/cpu/decode.cpp
vendored
Normal file
1744
third_party/sglang/sgl-kernel/csrc/cpu/decode.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
432
third_party/sglang/sgl-kernel/csrc/cpu/extend.cpp
vendored
Normal file
432
third_party/sglang/sgl-kernel/csrc/cpu/extend.cpp
vendored
Normal file
@@ -0,0 +1,432 @@
|
||||
#include "common.h"
|
||||
#include "flash_attn.h"
|
||||
#include "gemm.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// [NOTE]: extend attention for CPU
|
||||
// 1. BLOCK_M and BLOCK_N tuned for various seq lengths
|
||||
// 2. can handle non-contiguous k_extend and v_extend
|
||||
// 3. computes attention for prefix and extend separately
|
||||
// 4. TODO: apply head dimension blocking to optimize GQA
|
||||
//
|
||||
|
||||
template <typename scalar_t, typename index_t, int BLOCK_M, int BLOCK_N>
|
||||
void extend_attention_kernel_impl(
|
||||
scalar_t* __restrict__ o_extend,
|
||||
const scalar_t* __restrict__ q_extend,
|
||||
const scalar_t* __restrict__ k_extend,
|
||||
const scalar_t* __restrict__ v_extend,
|
||||
const scalar_t* __restrict__ k_buffer,
|
||||
const scalar_t* __restrict__ v_buffer,
|
||||
const index_t* __restrict__ req_to_token,
|
||||
const int64_t* __restrict__ req_pool_indices,
|
||||
const int64_t* __restrict__ seq_lens,
|
||||
const index_t* __restrict__ extend_seq_lens,
|
||||
const index_t* __restrict__ extend_start_loc,
|
||||
const void* __restrict__ buffer,
|
||||
int batches,
|
||||
int num_heads,
|
||||
int num_heads_kv,
|
||||
int head_size,
|
||||
int head_size_v,
|
||||
int q_strideM,
|
||||
int q_strideH,
|
||||
int ke_strideN,
|
||||
int ke_strideH,
|
||||
int ve_strideN,
|
||||
int ve_strideH,
|
||||
int k_strideN,
|
||||
int k_strideH,
|
||||
int v_strideN,
|
||||
int v_strideH,
|
||||
float sm_scale,
|
||||
int max_num_reqs,
|
||||
int max_context_len,
|
||||
int max_total_num_tokens,
|
||||
int max_len_extend,
|
||||
int buffer_size_per_thread,
|
||||
bool is_prefix_skipped) {
|
||||
// strides
|
||||
const int o_strideM = num_heads * head_size_v;
|
||||
const int o_strideH = head_size_v;
|
||||
|
||||
// we use same buffer for packed key and value
|
||||
const int ldb_tmp = std::max(head_size, head_size_v);
|
||||
|
||||
const int num_groups = num_heads / num_heads_kv;
|
||||
TORCH_CHECK(num_groups * num_heads_kv == num_heads);
|
||||
|
||||
// number of blocks along M
|
||||
int MB = div_up(max_len_extend, BLOCK_M);
|
||||
|
||||
// parallel on [batches, num_heads, BM]
|
||||
at::parallel_for(0, batches * num_heads * MB, 0, [&](int begin, int end) {
|
||||
int bs{0}, head_id{0}, mb{0};
|
||||
data_index_init(begin, bs, batches, head_id, num_heads, mb, MB);
|
||||
|
||||
int tid = at::get_thread_num();
|
||||
// s_i and s_delta: [BLOCK_M, BLOCK_N]
|
||||
float* __restrict__ s_i = reinterpret_cast<float*>((char*)(buffer) + tid * buffer_size_per_thread);
|
||||
scalar_t* __restrict__ s_delta = reinterpret_cast<scalar_t*>(s_i);
|
||||
|
||||
// v_prime: [BLOCK_M, head_size_v]
|
||||
float* __restrict__ v_prime = s_i + BLOCK_M * BLOCK_N;
|
||||
|
||||
// Btmp: [BLOCK_N, max(head_size, head_size_v)]
|
||||
scalar_t* __restrict__ Btmp = reinterpret_cast<scalar_t*>(v_prime + BLOCK_M * head_size_v);
|
||||
|
||||
// init Btmp just once for each thread to prevent NaN
|
||||
fill_stub(Btmp, 0.f, BLOCK_N * ldb_tmp);
|
||||
|
||||
alignas(64) float s_prime[BLOCK_M];
|
||||
alignas(64) float m_prime[BLOCK_M];
|
||||
|
||||
for (int i = begin; i < end; ++i) {
|
||||
// seq_len = prefix + extend
|
||||
int head_kv_id = head_id / num_groups;
|
||||
int seq_len = seq_lens[bs];
|
||||
int seq_len_extend = extend_seq_lens[bs];
|
||||
int seq_len_prefix = seq_len - seq_len_extend;
|
||||
int seq_extend_start_loc = extend_start_loc[bs];
|
||||
|
||||
int req_pool_id = req_pool_indices[bs];
|
||||
TORCH_CHECK(seq_len_prefix >= 0, "prefix len < 0!");
|
||||
TORCH_CHECK(seq_len <= max_context_len, "seq_len out of scope!");
|
||||
TORCH_CHECK(req_pool_id < max_num_reqs, "req_pool_id out of scope!");
|
||||
|
||||
if (is_prefix_skipped) {
|
||||
TORCH_CHECK(seq_len_prefix == 0, "extend attention: expect seq_len_prefix to be 0, got ", seq_len_prefix);
|
||||
}
|
||||
|
||||
// offset and size in MB
|
||||
int m = mb * BLOCK_M;
|
||||
int m_size = std::min(BLOCK_M, seq_len_extend - m);
|
||||
|
||||
if (m_size <= 0) {
|
||||
data_index_step(bs, batches, head_id, num_heads, mb, MB);
|
||||
continue;
|
||||
}
|
||||
|
||||
// get query
|
||||
const scalar_t* __restrict__ q_ptr = q_extend + (seq_extend_start_loc + m) * q_strideM + head_id * q_strideH;
|
||||
|
||||
// init v', s' and m'
|
||||
fill_stub(v_prime, 0.f, m_size * head_size_v);
|
||||
fill_stub(s_prime, 0.f, m_size);
|
||||
fill_stub(m_prime, -std::numeric_limits<scalar_t>::infinity(), m_size);
|
||||
|
||||
// stage 1: compute scores with prefix
|
||||
for (int n = 0; n < seq_len_prefix; n += BLOCK_N) {
|
||||
int n_size = std::min(BLOCK_N, seq_len_prefix - n);
|
||||
|
||||
// `n_size` is K in 2nd gemm, pad to TILE_K;
|
||||
const int padded_n_size = div_up(n_size, TILE_K) * TILE_K;
|
||||
|
||||
// get key and pack
|
||||
pack_vnni<scalar_t, index_t>(
|
||||
/* dst */ Btmp,
|
||||
/* src */ k_buffer + head_kv_id * k_strideH,
|
||||
/* ind */ req_to_token + req_pool_id * max_context_len + n,
|
||||
/* N */ n_size,
|
||||
/* K */ head_size,
|
||||
/* ld_src */ k_strideN,
|
||||
/* ld_dst */ BLOCK_N);
|
||||
|
||||
// calculate s_i <- Q @ K
|
||||
at::native::cpublas::brgemm(
|
||||
/* M */ m_size,
|
||||
/* N */ n_size,
|
||||
/* K */ head_size,
|
||||
/* lda */ q_strideM,
|
||||
/* ldb */ BLOCK_N,
|
||||
/* ldc */ BLOCK_N,
|
||||
/* add_C */ false,
|
||||
/* A */ q_ptr,
|
||||
/* B */ Btmp,
|
||||
/* C */ s_i);
|
||||
|
||||
flash_attn_softmax<scalar_t, BLOCK_M, BLOCK_N>::apply(
|
||||
s_i, s_delta, v_prime, s_prime, m_prime, m_size, n_size, padded_n_size, head_size_v, sm_scale);
|
||||
|
||||
// get value and pack
|
||||
pack_vnni2<scalar_t, index_t>(
|
||||
/* dst */ Btmp,
|
||||
/* src */ v_buffer + head_kv_id * v_strideH,
|
||||
/* ind */ req_to_token + req_pool_id * max_context_len + n,
|
||||
/* K */ n_size,
|
||||
/* N */ head_size_v,
|
||||
/* ld_src */ v_strideN,
|
||||
/* ld_dst */ head_size_v);
|
||||
|
||||
// calculate V' <- s_delta @ V + V'
|
||||
at::native::cpublas::brgemm(
|
||||
/* M */ m_size,
|
||||
/* N */ head_size_v,
|
||||
/* K */ padded_n_size, // n_size
|
||||
/* lda */ BLOCK_N,
|
||||
/* ldb */ head_size_v,
|
||||
/* ldc */ head_size_v,
|
||||
/* add_C */ true,
|
||||
/* A */ s_delta,
|
||||
/* B */ Btmp,
|
||||
/* C */ v_prime);
|
||||
} // loop with seq_len_prefix
|
||||
|
||||
// stage 2: compute the triangle part
|
||||
int num_keys = std::min(seq_len_extend, m + BLOCK_M);
|
||||
for (int n = 0; n < num_keys; n += BLOCK_N) {
|
||||
int n_size = std::min(BLOCK_N, num_keys - n);
|
||||
|
||||
// `n_size` is K in 2nd gemm, pad to TILE_K;
|
||||
const int padded_n_size = div_up(n_size, TILE_K) * TILE_K;
|
||||
|
||||
// get key and pack
|
||||
pack_vnni<scalar_t>(
|
||||
/* dst */ Btmp,
|
||||
/* src */ k_extend + (seq_extend_start_loc + n) * ke_strideN + head_kv_id * ke_strideH,
|
||||
/* N */ n_size,
|
||||
/* K */ head_size,
|
||||
/* ld_src */ ke_strideN,
|
||||
/* ld_dst */ BLOCK_N);
|
||||
|
||||
// calculate s_i <- Q @ K
|
||||
at::native::cpublas::brgemm(
|
||||
/* M */ m_size,
|
||||
/* N */ n_size,
|
||||
/* K */ head_size,
|
||||
/* lda */ q_strideM,
|
||||
/* ldb */ BLOCK_N,
|
||||
/* ldc */ BLOCK_N,
|
||||
/* add_C */ false,
|
||||
/* A */ q_ptr,
|
||||
/* B */ Btmp,
|
||||
/* C */ s_i);
|
||||
|
||||
// apply causal mask
|
||||
if (num_keys - n <= BLOCK_N) {
|
||||
for (int row = 0; row < m_size; ++row) {
|
||||
int last_col = m + row - n;
|
||||
// fill [last_col + 1, n_size) to -inf
|
||||
float* row_ptr = s_i + row * BLOCK_N;
|
||||
fill_stub(row_ptr + last_col + 1, -std::numeric_limits<float>::infinity(), n_size - last_col - 1);
|
||||
}
|
||||
}
|
||||
|
||||
flash_attn_softmax<scalar_t, BLOCK_M, BLOCK_N>::apply(
|
||||
s_i, s_delta, v_prime, s_prime, m_prime, m_size, n_size, padded_n_size, head_size_v, sm_scale);
|
||||
|
||||
// get value and pack
|
||||
pack_vnni2<scalar_t>(
|
||||
/* dst */ Btmp,
|
||||
/* src */ v_extend + (seq_extend_start_loc + n) * ve_strideN + head_kv_id * ve_strideH,
|
||||
/* K */ n_size,
|
||||
/* N */ head_size_v,
|
||||
/* ld_src */ ve_strideN,
|
||||
/* ld_dst */ head_size_v);
|
||||
|
||||
// calculate V' <- s_delta @ V + V'
|
||||
at::native::cpublas::brgemm(
|
||||
/* M */ m_size,
|
||||
/* N */ head_size_v,
|
||||
/* K */ padded_n_size, // n_size
|
||||
/* lda */ BLOCK_N,
|
||||
/* ldb */ head_size_v,
|
||||
/* ldc */ head_size_v,
|
||||
/* add_C */ true,
|
||||
/* A */ s_delta,
|
||||
/* B */ Btmp,
|
||||
/* C */ v_prime);
|
||||
} // loop with seq_len_extend
|
||||
|
||||
scalar_t* __restrict__ out_ptr = o_extend + (seq_extend_start_loc + m) * o_strideM + head_id * o_strideH;
|
||||
for (int row = 0; row < m_size; ++row) {
|
||||
float s = 1 / s_prime[row];
|
||||
copy_stub<scalar_t>(out_ptr + row * o_strideM, v_prime + row * head_size_v, s, head_size_v);
|
||||
}
|
||||
|
||||
// move to the next index
|
||||
data_index_step(bs, batches, head_id, num_heads, mb, MB);
|
||||
}
|
||||
at::native::cpublas::brgemm_release();
|
||||
});
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
template <int BLOCK_M, int BLOCK_N>
|
||||
inline int resize_buffer(at::Tensor& buffer, int num_threads, int head_size, int head_size_v) {
|
||||
static_assert(BLOCK_M <= BLOCK_N, "Make sure BLOCK_M <= BLOCK_N to prevent buffer overflows during causal masking");
|
||||
const int size_per_thread =
|
||||
/* s_i */ BLOCK_M * BLOCK_N * sizeof(float) +
|
||||
/* v_prime */ BLOCK_M * head_size_v * sizeof(float) +
|
||||
/* Btmp */ BLOCK_N * std::max(head_size, head_size_v) * sizeof(uint16_t);
|
||||
|
||||
buffer.resize_({num_threads, size_per_thread});
|
||||
return size_per_thread;
|
||||
}
|
||||
|
||||
#define LAUNCH_EXTEND_ATTENTION_KERNEL(BLOCK_M, BLOCK_N) \
|
||||
do { \
|
||||
int sz = resize_buffer<BLOCK_M, BLOCK_N>(buffer, num_threads, head_size, head_size_v); \
|
||||
\
|
||||
extend_attention_kernel_impl<scalar_t, index_t, BLOCK_M, BLOCK_N>( \
|
||||
o_extend.data_ptr<scalar_t>(), \
|
||||
q_extend.data_ptr<scalar_t>(), \
|
||||
k_extend.data_ptr<scalar_t>(), \
|
||||
v_extend.data_ptr<scalar_t>(), \
|
||||
k_buffer.data_ptr<scalar_t>(), \
|
||||
v_buffer.data_ptr<scalar_t>(), \
|
||||
req_to_token.data_ptr<index_t>(), \
|
||||
req_pool_indices.data_ptr<int64_t>(), \
|
||||
seq_lens.data_ptr<int64_t>(), \
|
||||
extend_seq_lens.data_ptr<index_t>(), \
|
||||
extend_start_loc.data_ptr<index_t>(), \
|
||||
buffer.data_ptr(), \
|
||||
num_seqs, \
|
||||
num_heads, \
|
||||
num_heads_kv, \
|
||||
head_size, \
|
||||
head_size_v, \
|
||||
q_strideM, \
|
||||
q_strideH, \
|
||||
ke_strideN, \
|
||||
ke_strideH, \
|
||||
ve_strideN, \
|
||||
ve_strideH, \
|
||||
k_strideN, \
|
||||
k_strideH, \
|
||||
v_strideN, \
|
||||
v_strideH, \
|
||||
sm_scale, \
|
||||
max_num_reqs, \
|
||||
max_context_len, \
|
||||
max_total_num_tokens, \
|
||||
max_len_extend, \
|
||||
sz, \
|
||||
is_prefix_skipped); \
|
||||
} while (0)
|
||||
|
||||
// q_extend, k_extend, v_extend, o_extend: contiguous tensors
|
||||
// k_buffer, v_buffer: (prefix + extend) tensors in mem_manager
|
||||
//
|
||||
// q_extend: [num_tokens, num_heads, head_size]
|
||||
// k_extend: [num_extend_tokens, num_heads, head_size]
|
||||
// v_extend: [num_extend_tokens, num_heads, head_size]
|
||||
// o_extend: [num_tokens, num_heads, head_size]
|
||||
// k_buffer: [max_total_num_tokens, num_heads, head_size]
|
||||
// v_buffer: [max_total_num_tokens, num_heads, head_size]
|
||||
// req_to_token: [max_num_reqs, max_context_len] int32 or int64
|
||||
// req_pool_indices: [num_seqs] int64
|
||||
// seq_lens: [num_seqs] int64
|
||||
// extend_seq_lens: [num_seqs]
|
||||
// extend_start_loc: [num_seqs]
|
||||
//
|
||||
void extend_attention_cpu(
|
||||
at::Tensor& q_extend,
|
||||
at::Tensor& k_extend,
|
||||
at::Tensor& v_extend,
|
||||
at::Tensor& o_extend,
|
||||
at::Tensor& k_buffer,
|
||||
at::Tensor& v_buffer,
|
||||
at::Tensor& req_to_token,
|
||||
at::Tensor& req_pool_indices,
|
||||
at::Tensor& seq_lens,
|
||||
at::Tensor& extend_seq_lens,
|
||||
at::Tensor& extend_start_loc,
|
||||
int64_t max_len_extend,
|
||||
double sm_scale,
|
||||
double logit_cap) {
|
||||
RECORD_FUNCTION(
|
||||
"sgl-kernel::extend_attention_cpu",
|
||||
std::vector<c10::IValue>(
|
||||
{q_extend,
|
||||
k_extend,
|
||||
v_extend,
|
||||
o_extend,
|
||||
k_buffer,
|
||||
v_buffer,
|
||||
req_to_token,
|
||||
req_pool_indices,
|
||||
seq_lens,
|
||||
extend_seq_lens,
|
||||
extend_start_loc,
|
||||
max_len_extend}));
|
||||
|
||||
CHECK_LAST_DIM_CONTIGUOUS_INPUT(q_extend);
|
||||
CHECK_INPUT(o_extend);
|
||||
CHECK_LAST_DIM_CONTIGUOUS_INPUT(k_extend);
|
||||
CHECK_LAST_DIM_CONTIGUOUS_INPUT(v_extend);
|
||||
CHECK_LAST_DIM_CONTIGUOUS_INPUT(k_buffer);
|
||||
CHECK_LAST_DIM_CONTIGUOUS_INPUT(v_buffer);
|
||||
|
||||
int num_seqs = seq_lens.size(0);
|
||||
int max_num_reqs = req_to_token.size(0);
|
||||
int max_context_len = req_to_token.size(1);
|
||||
int max_total_num_tokens = k_buffer.size(0);
|
||||
|
||||
int num_heads = q_extend.size(1);
|
||||
int num_heads_kv = k_extend.size(1);
|
||||
int head_size = q_extend.size(2);
|
||||
int head_size_v = v_extend.size(2);
|
||||
|
||||
// strides for q_extend, k_extend and v_extend
|
||||
int q_strideM = q_extend.stride(0);
|
||||
int q_strideH = q_extend.stride(1);
|
||||
int ke_strideN = k_extend.stride(0);
|
||||
int ke_strideH = k_extend.stride(1);
|
||||
int ve_strideN = v_extend.stride(0);
|
||||
int ve_strideH = v_extend.stride(1);
|
||||
|
||||
// strides for k_buffer and v_buffer
|
||||
int k_strideN = k_buffer.stride(0);
|
||||
int k_strideH = k_buffer.stride(1);
|
||||
int v_strideN = v_buffer.stride(0);
|
||||
int v_strideH = v_buffer.stride(1);
|
||||
|
||||
// check sizes
|
||||
CHECK_EQ(req_pool_indices.size(0), num_seqs);
|
||||
CHECK_EQ(extend_seq_lens.size(0), num_seqs);
|
||||
CHECK_EQ(extend_start_loc.size(0), num_seqs);
|
||||
CHECK_EQ(v_extend.size(1), num_heads_kv);
|
||||
CHECK_EQ(k_buffer.size(1), v_buffer.size(1));
|
||||
|
||||
// MLA will skip prefix part
|
||||
const bool is_prefix_skipped = k_buffer.size(1) != num_heads_kv;
|
||||
|
||||
// check index data types
|
||||
const auto index_dtype = req_to_token.scalar_type();
|
||||
TORCH_CHECK(
|
||||
index_dtype == at::kInt || index_dtype == at::kLong,
|
||||
"extend: expect req_to_token to be int32 or int64, got ",
|
||||
index_dtype);
|
||||
TORCH_CHECK(seq_lens.scalar_type() == at::kLong, "extend: expect req_lens to be int64, got ", seq_lens.scalar_type());
|
||||
TORCH_CHECK(
|
||||
req_pool_indices.scalar_type() == at::kLong,
|
||||
"extend: expect req_pool_indices to be int64, got ",
|
||||
req_pool_indices.scalar_type());
|
||||
TORCH_CHECK(
|
||||
extend_seq_lens.scalar_type() == index_dtype && extend_start_loc.scalar_type() == index_dtype,
|
||||
"extend: expect extend_seq_lens and extend_start_loc to have same dtype as req_to_token.");
|
||||
|
||||
// D and DV need to be 32x as we transpose by 512-bit
|
||||
TORCH_CHECK(head_size % 32 == 0, "invalid head_size ", head_size);
|
||||
TORCH_CHECK(head_size_v % 32 == 0, "invalid head_size_v ", head_size_v);
|
||||
|
||||
int num_threads = at::get_num_threads();
|
||||
auto buffer = at::empty({}, q_extend.options().dtype(at::kChar));
|
||||
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(q_extend.scalar_type(), "extend_attention_kernel", [&] {
|
||||
AT_DISPATCH_INDEX_TYPES(index_dtype, "extend_attention_indices", [&] {
|
||||
if (max_len_extend <= 256) {
|
||||
LAUNCH_EXTEND_ATTENTION_KERNEL(32, 64);
|
||||
} else if (max_len_extend <= 1024) {
|
||||
LAUNCH_EXTEND_ATTENTION_KERNEL(128, 256);
|
||||
} else if (max_len_extend <= 4096) {
|
||||
LAUNCH_EXTEND_ATTENTION_KERNEL(256, 768);
|
||||
} else { // max_len_extend > 4096
|
||||
LAUNCH_EXTEND_ATTENTION_KERNEL(512, 768);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
550
third_party/sglang/sgl-kernel/csrc/cpu/flash_attn.cpp
vendored
Normal file
550
third_party/sglang/sgl-kernel/csrc/cpu/flash_attn.cpp
vendored
Normal file
@@ -0,0 +1,550 @@
|
||||
/*****************************************************************************************
|
||||
* Copyright (c) 2025 - 2025 Codeplay Software Ltd. All rights reserved.
|
||||
* Copyright (C) 2025 Intel Corporation, All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
****************************************************************************************/
|
||||
#include "flash_attn.h"
|
||||
|
||||
#include "common.h"
|
||||
#include "gemm.h"
|
||||
|
||||
// [NOTE]: flash attention interface for CPU
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename scalar_t, int BLOCK_M, int BLOCK_N>
|
||||
void flash_attn_kernel_impl(
|
||||
scalar_t* __restrict__ out,
|
||||
const scalar_t* __restrict__ q,
|
||||
const scalar_t* __restrict__ k,
|
||||
const scalar_t* __restrict__ v,
|
||||
void* __restrict__ buffer,
|
||||
int seqlen_q,
|
||||
int seqlen_k,
|
||||
int batches,
|
||||
int num_heads,
|
||||
int num_heads_kv,
|
||||
int head_size,
|
||||
int head_size_v,
|
||||
int q_strideM,
|
||||
int q_strideH,
|
||||
int k_strideN,
|
||||
int k_strideH,
|
||||
int v_strideN,
|
||||
int v_strideH,
|
||||
float sm_scale,
|
||||
int buffer_size_per_thread,
|
||||
bool causal) {
|
||||
// strides
|
||||
const int o_strideM = num_heads * head_size_v;
|
||||
const int o_strideH = head_size_v;
|
||||
|
||||
// we use same buffer for packed key and value
|
||||
const int ldb_tmp = std::max(head_size, head_size_v);
|
||||
|
||||
const int num_groups = num_heads / num_heads_kv;
|
||||
TORCH_CHECK(num_groups * num_heads_kv == num_heads);
|
||||
|
||||
// number of super locks along M
|
||||
int MB = div_up(seqlen_q, BLOCK_M);
|
||||
|
||||
// parallel on [batches, num_heads, MB]
|
||||
parallel_for(batches * num_heads * MB, [&](int begin, int end) {
|
||||
int bs{0}, head_id{0}, mb{0};
|
||||
data_index_init(begin, bs, batches, head_id, num_heads, mb, MB);
|
||||
|
||||
int tid = get_thread_num();
|
||||
// s_i and s_delta: [BLOCK_M, BLOCK_N]
|
||||
float* __restrict__ s_i = reinterpret_cast<float*>((char*)(buffer) + tid * buffer_size_per_thread);
|
||||
scalar_t* __restrict__ s_delta = reinterpret_cast<scalar_t*>(s_i);
|
||||
|
||||
// v_prime: [BLOCK_M, head_size_v]
|
||||
float* __restrict__ v_prime = s_i + BLOCK_M * BLOCK_N;
|
||||
|
||||
// Btmp: [BLOCK_N, max(head_size, head_size_v)]
|
||||
scalar_t* __restrict__ Btmp = reinterpret_cast<scalar_t*>(v_prime + BLOCK_M * head_size_v);
|
||||
|
||||
// init Btmp and Btmp2 just once for each thread to prevent NaN
|
||||
fill_stub(Btmp, 0.f, BLOCK_N * ldb_tmp);
|
||||
|
||||
alignas(64) float s_prime[BLOCK_M];
|
||||
alignas(64) float m_prime[BLOCK_M];
|
||||
|
||||
for (int i = begin; i < end; ++i) {
|
||||
// [Note] use int64_t to avoid overflow
|
||||
// For large inputs, for example bs = 4096, seqlen_q = 4097, m = 0, q_strideM = 128:
|
||||
// The index calculated below: (seq_q_start_loc + m) * q_strideM = 4096 * 4097 * 128 will overflow int
|
||||
int64_t seq_q_start_loc = bs * seqlen_q;
|
||||
int64_t seq_k_start_loc = bs * seqlen_k;
|
||||
|
||||
// offset and size in MB
|
||||
int m = mb * BLOCK_M;
|
||||
int m_size = std::min(BLOCK_M, seqlen_q - m);
|
||||
|
||||
assert(m_size > 0);
|
||||
|
||||
int head_kv_id = head_id / num_groups;
|
||||
|
||||
// get query
|
||||
const scalar_t* __restrict__ q_ptr = q + (seq_q_start_loc + m) * q_strideM + head_id * q_strideH;
|
||||
|
||||
// init v', s' and m'
|
||||
fill_stub(v_prime, 0.f, m_size * head_size_v);
|
||||
fill_stub(s_prime, 0.f, m_size);
|
||||
fill_stub(m_prime, -std::numeric_limits<scalar_t>::infinity(), m_size);
|
||||
|
||||
int num_keys = causal ? std::min(m + m_size, seqlen_k) : seqlen_k;
|
||||
for (int n = 0; n < num_keys; n += BLOCK_N) {
|
||||
int n_size = std::min(BLOCK_N, num_keys - n);
|
||||
|
||||
// `n_size` is K in 2nd gemm, pad to TILE_K;
|
||||
const int padded_n_size = div_up(n_size, TILE_K) * TILE_K;
|
||||
|
||||
// get key and pack
|
||||
pack_vnni<scalar_t>(
|
||||
/* dst */ Btmp,
|
||||
/* src */ k + (seq_k_start_loc + n) * k_strideN + head_kv_id * k_strideH,
|
||||
/* N */ n_size,
|
||||
/* K */ head_size,
|
||||
/* ld_src */ k_strideN,
|
||||
/* ld_dst */ BLOCK_N);
|
||||
|
||||
// calculate s_i <- Q @ K
|
||||
at::native::cpublas::brgemm(
|
||||
/* M */ m_size,
|
||||
/* N */ n_size,
|
||||
/* K */ head_size,
|
||||
/* lda */ q_strideM,
|
||||
/* ldb */ BLOCK_N,
|
||||
/* ldc */ BLOCK_N,
|
||||
/* add_C */ false,
|
||||
/* A */ q_ptr,
|
||||
/* B */ Btmp,
|
||||
/* C */ s_i);
|
||||
|
||||
// apply causal mask
|
||||
if (causal && num_keys - n <= BLOCK_N) {
|
||||
for (int row = 0; row < m_size; ++row) {
|
||||
int last_col = m + row - n;
|
||||
// fill [last_col + 1, n_size) to -inf
|
||||
float* row_ptr = s_i + row * BLOCK_N;
|
||||
fill_stub(row_ptr + last_col + 1, -std::numeric_limits<float>::infinity(), n_size - last_col - 1);
|
||||
}
|
||||
}
|
||||
|
||||
flash_attn_softmax<scalar_t, BLOCK_M, BLOCK_N>::apply(
|
||||
s_i, s_delta, v_prime, s_prime, m_prime, m_size, n_size, padded_n_size, head_size_v, sm_scale);
|
||||
|
||||
// get value and pack
|
||||
pack_vnni2<scalar_t>(
|
||||
/* dst */ Btmp,
|
||||
/* src */ v + (seq_k_start_loc + n) * v_strideN + head_kv_id * v_strideH,
|
||||
/* K */ n_size,
|
||||
/* N */ head_size_v,
|
||||
/* ld_src */ v_strideN,
|
||||
/* ld_dst */ head_size_v);
|
||||
|
||||
// calculate V' <- s_delta @ V + V'
|
||||
at::native::cpublas::brgemm(
|
||||
/* M */ m_size,
|
||||
/* N */ head_size_v,
|
||||
/* K */ padded_n_size, // n_size
|
||||
/* lda */ BLOCK_N,
|
||||
/* ldb */ head_size_v,
|
||||
/* ldc */ head_size_v,
|
||||
/* add_C */ true,
|
||||
/* A */ s_delta,
|
||||
/* B */ Btmp,
|
||||
/* C */ v_prime);
|
||||
} // loop with seqlen_k
|
||||
|
||||
scalar_t* __restrict__ out_ptr = out + (seq_q_start_loc + m) * o_strideM + head_id * o_strideH;
|
||||
for (int row = 0; row < m_size; ++row) {
|
||||
float s = 1 / s_prime[row];
|
||||
copy_stub<scalar_t>(out_ptr + row * o_strideM, v_prime + row * head_size_v, s, head_size_v);
|
||||
}
|
||||
|
||||
// move to the next index
|
||||
data_index_step(bs, batches, head_id, num_heads, mb, MB);
|
||||
}
|
||||
at::native::cpublas::brgemm_release();
|
||||
});
|
||||
}
|
||||
|
||||
template <typename scalar_t, int BLOCK_M, int BLOCK_N>
|
||||
void flash_attn_varlen_kernel_impl(
|
||||
scalar_t* __restrict__ out,
|
||||
const scalar_t* __restrict__ q,
|
||||
const scalar_t* __restrict__ k,
|
||||
const scalar_t* __restrict__ v,
|
||||
const int32_t* __restrict__ cu_seqlens_q,
|
||||
const int32_t* __restrict__ cu_seqlens_k,
|
||||
void* __restrict__ buffer,
|
||||
int32_t* __restrict__ indices,
|
||||
int max_seqlen_q,
|
||||
int max_seqlen_k,
|
||||
int batches,
|
||||
int num_heads,
|
||||
int num_heads_kv,
|
||||
int head_size,
|
||||
int head_size_v,
|
||||
int q_strideM,
|
||||
int q_strideH,
|
||||
int k_strideN,
|
||||
int k_strideH,
|
||||
int v_strideN,
|
||||
int v_strideH,
|
||||
float sm_scale,
|
||||
int buffer_size_per_thread,
|
||||
bool causal) {
|
||||
// strides
|
||||
const int o_strideM = num_heads * head_size_v;
|
||||
const int o_strideH = head_size_v;
|
||||
|
||||
// compute index (bs, mb_offset) for Query blocks
|
||||
// do this sequentially as usually problem size won't be big
|
||||
int idx = 0;
|
||||
for (int32_t bs = 0; bs < batches; ++bs) {
|
||||
int32_t seqlen_q = cu_seqlens_q[bs + 1] - cu_seqlens_q[bs];
|
||||
int32_t seqlen_k = cu_seqlens_k[bs + 1] - cu_seqlens_k[bs];
|
||||
TORCH_CHECK(seqlen_q <= max_seqlen_q && seqlen_k <= max_seqlen_k);
|
||||
|
||||
int32_t blocks = div_up(seqlen_q, BLOCK_M);
|
||||
for (int32_t offset = 0; offset < blocks; ++offset) {
|
||||
indices[idx * 2 + 0] = bs;
|
||||
indices[idx * 2 + 1] = offset;
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
// number of query blocks
|
||||
int MB = idx;
|
||||
|
||||
// we use same buffer for packed key and value
|
||||
const int ldb_tmp = std::max(head_size, head_size_v);
|
||||
|
||||
const int num_groups = num_heads / num_heads_kv;
|
||||
TORCH_CHECK(num_groups * num_heads_kv == num_heads);
|
||||
|
||||
// parallel on [MB, num_heads]
|
||||
parallel_for(num_heads * MB, [&](int begin, int end) {
|
||||
int head_id{0}, mb{0};
|
||||
data_index_init(begin, head_id, num_heads, mb, MB);
|
||||
|
||||
int tid = get_thread_num();
|
||||
// s_i and s_delta: [BLOCK_M, BLOCK_N]
|
||||
float* __restrict__ s_i = reinterpret_cast<float*>((char*)(buffer) + tid * buffer_size_per_thread);
|
||||
scalar_t* __restrict__ s_delta = reinterpret_cast<scalar_t*>(s_i);
|
||||
|
||||
// v_prime: [BLOCK_M, head_size_v]
|
||||
float* __restrict__ v_prime = s_i + BLOCK_M * BLOCK_N;
|
||||
|
||||
// Btmp: [BLOCK_N, max(head_size, head_size_v)]
|
||||
scalar_t* __restrict__ Btmp = reinterpret_cast<scalar_t*>(v_prime + BLOCK_M * head_size_v);
|
||||
|
||||
// init Btmp just once for each thread to prevent NaN
|
||||
fill_stub(Btmp, 0.f, BLOCK_N * ldb_tmp);
|
||||
|
||||
alignas(64) float s_prime[BLOCK_M];
|
||||
alignas(64) float m_prime[BLOCK_M];
|
||||
|
||||
for (int i = begin; i < end; ++i) {
|
||||
int32_t bs = indices[mb * 2 + 0];
|
||||
|
||||
// See [Note] use int64_t to avoid overflow
|
||||
int64_t seq_q_start_loc = cu_seqlens_q[bs];
|
||||
int64_t seq_k_start_loc = cu_seqlens_k[bs];
|
||||
|
||||
int32_t seqlen_q = cu_seqlens_q[bs + 1] - cu_seqlens_q[bs];
|
||||
|
||||
// offset and size in MB
|
||||
int m = indices[mb * 2 + 1] * BLOCK_M;
|
||||
int m_size = std::min(BLOCK_M, seqlen_q - m);
|
||||
|
||||
assert(m_size > 0);
|
||||
|
||||
int head_kv_id = head_id / num_groups;
|
||||
|
||||
// get query
|
||||
const scalar_t* __restrict__ q_ptr = q + (seq_q_start_loc + m) * q_strideM + head_id * q_strideH;
|
||||
|
||||
// init v', s' and m'
|
||||
fill_stub(v_prime, 0.f, m_size * head_size_v);
|
||||
fill_stub(s_prime, 0.f, m_size);
|
||||
fill_stub(m_prime, -std::numeric_limits<scalar_t>::infinity(), m_size);
|
||||
|
||||
int seqlen_k = cu_seqlens_k[bs + 1] - cu_seqlens_k[bs];
|
||||
int num_keys = causal ? std::min(m + m_size, seqlen_k) : seqlen_k;
|
||||
for (int n = 0; n < num_keys; n += BLOCK_N) {
|
||||
int n_size = std::min(BLOCK_N, num_keys - n);
|
||||
|
||||
// `n_size` is K in 2nd gemm, pad to TILE_K;
|
||||
const int padded_n_size = div_up(n_size, TILE_K) * TILE_K;
|
||||
|
||||
// get key and pack
|
||||
pack_vnni<scalar_t>(
|
||||
/* dst */ Btmp,
|
||||
/* src */ k + (seq_k_start_loc + n) * k_strideN + head_kv_id * k_strideH,
|
||||
/* N */ n_size,
|
||||
/* K */ head_size,
|
||||
/* ld_src */ k_strideN,
|
||||
/* ld_dst */ BLOCK_N);
|
||||
|
||||
// calculate s_i <- Q @ K
|
||||
at::native::cpublas::brgemm(
|
||||
/* M */ m_size,
|
||||
/* N */ n_size,
|
||||
/* K */ head_size,
|
||||
/* lda */ q_strideM,
|
||||
/* ldb */ BLOCK_N,
|
||||
/* ldc */ BLOCK_N,
|
||||
/* add_C */ false,
|
||||
/* A */ q_ptr,
|
||||
/* B */ Btmp,
|
||||
/* C */ s_i);
|
||||
|
||||
// apply causal mask
|
||||
if (causal && num_keys - n <= BLOCK_N) {
|
||||
for (int row = 0; row < m_size; ++row) {
|
||||
int last_col = m + row - n;
|
||||
// fill [last_col + 1, n_size) to -inf
|
||||
float* row_ptr = s_i + row * BLOCK_N;
|
||||
fill_stub(row_ptr + last_col + 1, -std::numeric_limits<float>::infinity(), n_size - last_col - 1);
|
||||
}
|
||||
}
|
||||
|
||||
flash_attn_softmax<scalar_t, BLOCK_M, BLOCK_N>::apply(
|
||||
s_i, s_delta, v_prime, s_prime, m_prime, m_size, n_size, padded_n_size, head_size_v, sm_scale);
|
||||
|
||||
// get value and pack
|
||||
pack_vnni2<scalar_t>(
|
||||
/* dst */ Btmp,
|
||||
/* src */ v + (seq_k_start_loc + n) * v_strideN + head_kv_id * v_strideH,
|
||||
/* K */ n_size,
|
||||
/* N */ head_size_v,
|
||||
/* ld_src */ v_strideN,
|
||||
/* ld_dst */ head_size_v);
|
||||
|
||||
// calculate V' <- s_delta @ V + V'
|
||||
at::native::cpublas::brgemm(
|
||||
/* M */ m_size,
|
||||
/* N */ head_size_v,
|
||||
/* K */ padded_n_size, // n_size
|
||||
/* lda */ BLOCK_N,
|
||||
/* ldb */ head_size_v,
|
||||
/* ldc */ head_size_v,
|
||||
/* add_C */ true,
|
||||
/* A */ s_delta,
|
||||
/* B */ Btmp,
|
||||
/* C */ v_prime);
|
||||
} // loop with seqlen_k
|
||||
|
||||
scalar_t* __restrict__ out_ptr = out + (seq_q_start_loc + m) * o_strideM + head_id * o_strideH;
|
||||
for (int row = 0; row < m_size; ++row) {
|
||||
float s = 1 / s_prime[row];
|
||||
copy_stub<scalar_t>(out_ptr + row * o_strideM, v_prime + row * head_size_v, s, head_size_v);
|
||||
}
|
||||
|
||||
// move to the next index
|
||||
data_index_step(head_id, num_heads, mb, MB);
|
||||
}
|
||||
at::native::cpublas::brgemm_release();
|
||||
});
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
template <typename index_t>
|
||||
inline bool has_varlen_sequences(
|
||||
const at::Tensor& cu_seqlens_q,
|
||||
const at::Tensor& cu_seqlens_k,
|
||||
int batches,
|
||||
index_t max_seqlen_q,
|
||||
index_t max_seqlen_k) {
|
||||
const index_t* cu_seqlens_q_data = cu_seqlens_q.data_ptr<index_t>();
|
||||
const index_t* cu_seqlens_k_data = cu_seqlens_k.data_ptr<index_t>();
|
||||
|
||||
for (int bs = 0; bs < batches; ++bs) {
|
||||
index_t seqlen_q = cu_seqlens_q_data[bs + 1] - cu_seqlens_q_data[bs];
|
||||
index_t seqlen_k = cu_seqlens_k_data[bs + 1] - cu_seqlens_k_data[bs];
|
||||
if (seqlen_q != max_seqlen_q || seqlen_k != max_seqlen_k) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <int BLOCK_M, int BLOCK_N>
|
||||
inline int resize_buffer(at::Tensor& buffer, int num_threads, int head_size, int head_size_v) {
|
||||
static_assert(BLOCK_M <= BLOCK_N, "Make sure BLOCK_M <= BLOCK_N to prevent buffer overflows during causal masking");
|
||||
const int size_per_thread =
|
||||
/* s_i */ BLOCK_M * BLOCK_N * sizeof(float) +
|
||||
/* v_prime */ BLOCK_M * head_size_v * sizeof(float) +
|
||||
/* Btmp */ BLOCK_N * std::max(head_size, head_size_v) * sizeof(uint16_t);
|
||||
|
||||
buffer.resize_({num_threads, size_per_thread});
|
||||
return size_per_thread;
|
||||
}
|
||||
|
||||
template <int BLOCK_M>
|
||||
inline void resize_indices(at::Tensor& indices, int num_seqs, int max_seqlen_q) {
|
||||
// we allocate memory based on max seqlen
|
||||
indices.resize_({num_seqs, div_up(max_seqlen_q, BLOCK_M), 2});
|
||||
}
|
||||
|
||||
// [NOTE]: `flash_attn_varlen_func` AMX kernel
|
||||
//
|
||||
// q: [num_tokens, num_heads, head_size]
|
||||
// k: [num_tokens, num_heads_kv, head_size]
|
||||
// v: [num_tokens, num_heads_kv, head_size_v]
|
||||
// cu_seqlens_q: [num_seqs + 1]
|
||||
// cu_seqlens_k: [num_seqs + 1]
|
||||
// out: [num_tokens, num_heads, head_size_v]
|
||||
//
|
||||
at::Tensor flash_attn_varlen_func(
|
||||
const at::Tensor& q,
|
||||
const at::Tensor& k,
|
||||
const at::Tensor& v,
|
||||
const at::Tensor& cu_seqlens_q,
|
||||
const at::Tensor& cu_seqlens_k,
|
||||
int64_t max_seqlen_q,
|
||||
int64_t max_seqlen_k,
|
||||
bool causal) {
|
||||
RECORD_FUNCTION(
|
||||
"sgl_kernel::flash_attn_varlen_func",
|
||||
std::vector<c10::IValue>({q, k, v, cu_seqlens_q, cu_seqlens_k, max_seqlen_q, max_seqlen_k, causal}));
|
||||
|
||||
CHECK_LAST_DIM_CONTIGUOUS_INPUT(q);
|
||||
CHECK_LAST_DIM_CONTIGUOUS_INPUT(k);
|
||||
CHECK_LAST_DIM_CONTIGUOUS_INPUT(v);
|
||||
CHECK_DIM(3, q);
|
||||
CHECK_DIM(3, k);
|
||||
CHECK_DIM(3, v);
|
||||
CHECK_INPUT(cu_seqlens_q);
|
||||
CHECK_INPUT(cu_seqlens_k);
|
||||
CHECK_EQ(cu_seqlens_q.scalar_type(), at::kInt);
|
||||
CHECK_EQ(cu_seqlens_k.scalar_type(), at::kInt);
|
||||
|
||||
int num_seqs = cu_seqlens_q.size(0) - 1;
|
||||
int num_tokens = q.size(0);
|
||||
int num_heads = q.size(1);
|
||||
int num_heads_kv = k.size(1);
|
||||
int head_size = q.size(2);
|
||||
int head_size_v = v.size(2);
|
||||
|
||||
// strides for q, k and v
|
||||
int q_strideM = q.stride(0);
|
||||
int q_strideH = q.stride(1);
|
||||
int k_strideN = k.stride(0);
|
||||
int k_strideH = k.stride(1);
|
||||
int v_strideN = v.stride(0);
|
||||
int v_strideH = v.stride(1);
|
||||
|
||||
// check sizes
|
||||
CHECK_EQ(k.size(2), head_size);
|
||||
CHECK_EQ(v.size(1), num_heads_kv);
|
||||
CHECK_EQ(cu_seqlens_k.size(0), num_seqs + 1);
|
||||
|
||||
// D and DV need to be even as we transpose by 512-bit
|
||||
TORCH_CHECK(head_size % 2 == 0, "invalid head_size ", head_size);
|
||||
TORCH_CHECK(head_size_v % 2 == 0, "invalid head_size_v ", head_size_v);
|
||||
|
||||
// softmax scale
|
||||
double sm_scale = 1.0 / std::sqrt(static_cast<double>(head_size));
|
||||
|
||||
// check whether the batch has variant lengths
|
||||
const bool is_varlen =
|
||||
has_varlen_sequences<int32_t>(cu_seqlens_q, cu_seqlens_k, num_seqs, max_seqlen_q, max_seqlen_k);
|
||||
|
||||
int num_threads = at::get_num_threads();
|
||||
at::Tensor buffer = at::empty({}, q.options().dtype(at::kChar));
|
||||
at::Tensor indices = at::empty({}, q.options().dtype(at::kInt));
|
||||
at::Tensor out = at::empty({num_tokens, num_heads, head_size_v}, q.options());
|
||||
|
||||
// TODO: tune the block size
|
||||
constexpr int BLOCK_M = 512;
|
||||
constexpr int BLOCK_N = 768;
|
||||
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(q.scalar_type(), "flash_attn_varlen_func", [&] {
|
||||
int sz = resize_buffer<BLOCK_M, BLOCK_N>(buffer, num_threads, head_size, head_size_v);
|
||||
|
||||
if (is_varlen) {
|
||||
resize_indices<BLOCK_M>(indices, num_seqs, max_seqlen_q);
|
||||
flash_attn_varlen_kernel_impl<scalar_t, BLOCK_M, BLOCK_N>(
|
||||
out.data_ptr<scalar_t>(),
|
||||
q.data_ptr<scalar_t>(),
|
||||
k.data_ptr<scalar_t>(),
|
||||
v.data_ptr<scalar_t>(),
|
||||
cu_seqlens_q.data_ptr<int32_t>(),
|
||||
cu_seqlens_k.data_ptr<int32_t>(),
|
||||
buffer.data_ptr(),
|
||||
indices.data_ptr<int32_t>(),
|
||||
max_seqlen_q,
|
||||
max_seqlen_k,
|
||||
num_seqs,
|
||||
num_heads,
|
||||
num_heads_kv,
|
||||
head_size,
|
||||
head_size_v,
|
||||
q_strideM,
|
||||
q_strideH,
|
||||
k_strideN,
|
||||
k_strideH,
|
||||
v_strideN,
|
||||
v_strideH,
|
||||
sm_scale,
|
||||
sz,
|
||||
causal);
|
||||
} else {
|
||||
flash_attn_kernel_impl<scalar_t, BLOCK_M, BLOCK_N>(
|
||||
out.data_ptr<scalar_t>(),
|
||||
q.data_ptr<scalar_t>(),
|
||||
k.data_ptr<scalar_t>(),
|
||||
v.data_ptr<scalar_t>(),
|
||||
buffer.data_ptr(),
|
||||
max_seqlen_q,
|
||||
max_seqlen_k,
|
||||
num_seqs,
|
||||
num_heads,
|
||||
num_heads_kv,
|
||||
head_size,
|
||||
head_size_v,
|
||||
q_strideM,
|
||||
q_strideH,
|
||||
k_strideN,
|
||||
k_strideH,
|
||||
v_strideN,
|
||||
v_strideH,
|
||||
sm_scale,
|
||||
sz,
|
||||
causal);
|
||||
}
|
||||
});
|
||||
|
||||
return out;
|
||||
}
|
||||
246
third_party/sglang/sgl-kernel/csrc/cpu/flash_attn.h
vendored
Normal file
246
third_party/sglang/sgl-kernel/csrc/cpu/flash_attn.h
vendored
Normal file
@@ -0,0 +1,246 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
#include "vec.h"
|
||||
#include "vec_pack.h"
|
||||
|
||||
template <typename scalar_t>
|
||||
inline void fill_stub(scalar_t* __restrict__ out, float val, int size) {
|
||||
using Vec = at::vec::Vectorized<scalar_t>;
|
||||
constexpr int kVecSize = Vec::size();
|
||||
const Vec data_vec = Vec(static_cast<scalar_t>(val));
|
||||
int d = 0;
|
||||
#pragma GCC unroll 4
|
||||
for (; d <= size - kVecSize; d += kVecSize) {
|
||||
data_vec.store(out + d);
|
||||
}
|
||||
if (size - d > 0) {
|
||||
data_vec.store(out + d, size - d);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename scalar_t, int BLOCK_N>
|
||||
inline void copy_stub(scalar_t* __restrict__ out, const float* __restrict__ input) {
|
||||
static_assert(BLOCK_N % 32 == 0);
|
||||
using bVec = at::vec::Vectorized<scalar_t>;
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
|
||||
constexpr int COLS = BLOCK_N / 16;
|
||||
auto store = [&](auto i) {
|
||||
constexpr int col = i % COLS;
|
||||
// for COLS = 2, 4 use 512bit store
|
||||
if constexpr (col % 2 == 0) {
|
||||
fVec a_fvec0 = fVec::loadu(input + col * 16);
|
||||
fVec a_fvec1 = fVec::loadu(input + col * 16 + 16);
|
||||
bVec out_bvec = convert_from_float_ext<scalar_t>(a_fvec0, a_fvec1);
|
||||
out_bvec.store(out + col * 16);
|
||||
}
|
||||
};
|
||||
Unroll<COLS>{}(store);
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
inline void copy_stub(scalar_t* __restrict__ out, const float* __restrict__ acc, float s, int size) {
|
||||
using bVec = at::vec::Vectorized<scalar_t>;
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
constexpr int kVecSize = bVec::size();
|
||||
const fVec s_fvec = fVec(s);
|
||||
int d = 0;
|
||||
#pragma GCC unroll 4
|
||||
for (; d <= size - kVecSize; d += kVecSize) {
|
||||
fVec a_fvec0 = fVec::loadu(acc + d) * s_fvec;
|
||||
fVec a_fvec1 = fVec::loadu(acc + d + fVec::size()) * s_fvec;
|
||||
bVec out_bvec = convert_from_float_ext<scalar_t>(a_fvec0, a_fvec1);
|
||||
out_bvec.store(out + d);
|
||||
}
|
||||
for (; d < size; ++d) {
|
||||
out[d] = static_cast<scalar_t>(acc[d] * s);
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(CPU_CAPABILITY_AVX512)
|
||||
template <>
|
||||
inline void copy_stub<at::BFloat16>(at::BFloat16* __restrict__ out, const float* __restrict__ acc, float s, int size) {
|
||||
const __m512 vscale = _mm512_set1_ps(s);
|
||||
int d = 0;
|
||||
#pragma GCC unroll 4
|
||||
for (; d <= size - 32; d += 32) {
|
||||
__m512 va0 = _mm512_mul_ps(_mm512_loadu_ps(acc + d), vscale);
|
||||
__m512 va1 = _mm512_mul_ps(_mm512_loadu_ps(acc + d + 16), vscale);
|
||||
__m512i vb = (__m512i)(_mm512_cvtne2ps_pbh(va1, va0));
|
||||
_mm512_storeu_si512(out + d, vb);
|
||||
}
|
||||
int remainder = size - d;
|
||||
if (remainder > 0) {
|
||||
if (remainder <= 16) {
|
||||
const __mmask16 vmask = (1ULL << remainder) - 1;
|
||||
__m512 va = _mm512_mul_ps(_mm512_maskz_loadu_ps(vmask, acc + d), vscale);
|
||||
__m256i vb = (__m256i)(_mm512_cvtneps_pbh(va));
|
||||
_mm256_mask_storeu_epi16(reinterpret_cast<__m256i*>(out + d), vmask, vb);
|
||||
} else { // remainder > 16
|
||||
const __mmask16 vmask = (1ULL << (remainder - 16)) - 1;
|
||||
__m512 va0 = _mm512_mul_ps(_mm512_loadu_ps(acc + d), vscale);
|
||||
__m512 va1 = _mm512_mul_ps(_mm512_maskz_loadu_ps(vmask, acc + d + 16), vscale);
|
||||
__m512i vb = (__m512i)(_mm512_cvtne2ps_pbh(va1, va0));
|
||||
const __mmask32 vmask2 = (1ULL << remainder) - 1;
|
||||
_mm512_mask_storeu_epi16(reinterpret_cast<__m512i*>(out + d), vmask2, vb);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
template <typename scalar_t, int BLOCK_M, int BLOCK_N>
|
||||
struct flash_attn_softmax {
|
||||
static inline void apply(
|
||||
float* __restrict__ s_i,
|
||||
scalar_t* __restrict__ s_delta2,
|
||||
float* __restrict__ v_prime,
|
||||
float* __restrict__ s_prime,
|
||||
float* __restrict__ m_prime,
|
||||
int m_size,
|
||||
int n_size,
|
||||
int padded_n_size,
|
||||
int head_size_v,
|
||||
const float sm_scale) {
|
||||
using Vec = at::vec::Vectorized<float>;
|
||||
const Vec scale_vec = Vec(sm_scale);
|
||||
float* s_delta = s_i;
|
||||
for (int row = 0; row < m_size; ++row) {
|
||||
// s_i <- s_i * scale
|
||||
at::vec::map<float>(
|
||||
[scale_vec](Vec x) { return x * scale_vec; }, s_i + row * BLOCK_N, s_i + row * BLOCK_N, n_size);
|
||||
|
||||
// m_i: max value per row
|
||||
float m_i = at::vec::reduce_all<float>(
|
||||
[](Vec& x, Vec& y) { return at::vec::maximum(x, y); }, s_i + row * BLOCK_N, n_size);
|
||||
m_i = std::max(m_i, m_prime[row]);
|
||||
|
||||
// m_delta <- exp(m' - m_i)
|
||||
float m_delta = std::exp(m_prime[row] - m_i);
|
||||
|
||||
// s_delta <- exp(s_i - m_i)
|
||||
at::vec::map<float>(
|
||||
[m_i](Vec x) { return (x - Vec(m_i)).fexp_u20(); }, s_delta + row * BLOCK_N, s_i + row * BLOCK_N, n_size);
|
||||
|
||||
// s' <- s' * m_delta + sum(s_delta)
|
||||
s_prime[row] *= m_delta;
|
||||
s_prime[row] += at::vec::reduce_all<float>([](Vec& x, Vec& y) { return x + y; }, s_delta + row * BLOCK_N, n_size);
|
||||
|
||||
m_prime[row] = m_i;
|
||||
|
||||
// v' <- v' * m_delta
|
||||
at::vec::map<float>(
|
||||
[m_delta](Vec x) { return x * Vec(m_delta); },
|
||||
v_prime + row * head_size_v,
|
||||
v_prime + row * head_size_v,
|
||||
head_size_v);
|
||||
|
||||
// pad s_delta with 0 first and then convert to scalar_t
|
||||
fill_stub(s_delta + row * BLOCK_N + n_size, 0.f, padded_n_size - n_size);
|
||||
copy_stub<scalar_t, BLOCK_N>(s_delta2 + row * BLOCK_N, s_delta + row * BLOCK_N);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#if defined(CPU_CAPABILITY_AVX512)
|
||||
template <int BLOCK_M, int BLOCK_N>
|
||||
struct flash_attn_softmax<at::BFloat16, BLOCK_M, BLOCK_N> {
|
||||
static inline void apply(
|
||||
float* __restrict__ s_i,
|
||||
at::BFloat16* __restrict__ s_delta2,
|
||||
float* __restrict__ v_prime,
|
||||
float* __restrict__ s_prime,
|
||||
float* __restrict__ m_prime,
|
||||
int m_size,
|
||||
int n_size,
|
||||
int padded_n_size,
|
||||
int head_size_v,
|
||||
const float sm_scale) {
|
||||
float* s_delta = s_i;
|
||||
const __m512 vscale = _mm512_set1_ps(sm_scale);
|
||||
|
||||
int n_remainder = n_size & 15; // 0xF
|
||||
const __mmask16 vmask = (1ULL << n_remainder) - 1;
|
||||
|
||||
int v_remainder = head_size_v & 15; // 0xF
|
||||
const __mmask16 vmask1 = (1ULL << v_remainder) - 1;
|
||||
|
||||
constexpr float NEG_INF = -std::numeric_limits<float>::infinity();
|
||||
|
||||
__m512 va;
|
||||
__m256i vb;
|
||||
__m512 vmax;
|
||||
__m512 vsum;
|
||||
__m512 vmdelta;
|
||||
|
||||
const __m512 vneg_inf = _mm512_set1_ps(NEG_INF);
|
||||
|
||||
for (int m = 0; m < m_size; ++m) {
|
||||
vmax = vneg_inf;
|
||||
|
||||
// s_i <- s_i * scale
|
||||
int n = 0;
|
||||
for (; n <= n_size - 16; n += 16) {
|
||||
va = _mm512_mul_ps(_mm512_loadu_ps(s_i + m * BLOCK_N + n), vscale);
|
||||
vmax = _mm512_max_ps(va, vmax);
|
||||
}
|
||||
if (n_remainder > 0) {
|
||||
va = _mm512_mul_ps(_mm512_mask_loadu_ps(vneg_inf, vmask, s_i + m * BLOCK_N + n), vscale);
|
||||
vmax = _mm512_max_ps(va, vmax);
|
||||
}
|
||||
|
||||
// m_i: max value per row
|
||||
float m_i = _mm512_reduce_max_ps(vmax);
|
||||
m_i = std::max(m_i, m_prime[m]);
|
||||
vmax = _mm512_set1_ps(m_i);
|
||||
|
||||
// m_delta <- exp(m' - m_i)
|
||||
float m_delta = std::exp(m_prime[m] - m_i);
|
||||
|
||||
// s_delta <- exp(s_i - m_i)
|
||||
vsum = _mm512_setzero_ps();
|
||||
for (n = 0; n <= n_size - 16; n += 16) {
|
||||
va = _mm512_mul_ps(_mm512_loadu_ps(s_i + m * BLOCK_N + n), vscale);
|
||||
va = _mm512_fexp_u20_ps(_mm512_sub_ps(va, vmax));
|
||||
vsum = _mm512_add_ps(vsum, va);
|
||||
|
||||
vb = (__m256i)(_mm512_cvtneps_pbh(va));
|
||||
_mm256_storeu_si256(reinterpret_cast<__m256i*>(s_delta2 + m * BLOCK_N + n), vb);
|
||||
}
|
||||
if (n_remainder > 0) {
|
||||
va = _mm512_mul_ps(_mm512_mask_loadu_ps(vneg_inf, vmask, s_i + m * BLOCK_N + n), vscale);
|
||||
va = _mm512_fexp_u20_ps(_mm512_sub_ps(va, vmax));
|
||||
vsum = _mm512_add_ps(vsum, va);
|
||||
|
||||
vb = (__m256i)(_mm512_cvtneps_pbh(va));
|
||||
_mm256_mask_storeu_epi16(reinterpret_cast<__m256i*>(s_delta2 + m * BLOCK_N + n), vmask, vb);
|
||||
}
|
||||
|
||||
// s' <- s' * m_delta + sum(s_delta)
|
||||
s_prime[m] *= m_delta;
|
||||
s_prime[m] += _mm512_reduce_add_ps(vsum);
|
||||
|
||||
m_prime[m] = m_i;
|
||||
|
||||
// pad s_delta with 0, pad_size range from [0, 32)
|
||||
int pad_size = padded_n_size - n_size;
|
||||
if (pad_size > 0) {
|
||||
const __m512i vzero = _mm512_setzero_si512();
|
||||
__mmask32 vmask2 = (1ULL << pad_size) - 1;
|
||||
_mm512_mask_storeu_epi16(reinterpret_cast<__m512i*>(s_delta2 + m * BLOCK_N + n_size), vmask2, vzero);
|
||||
}
|
||||
|
||||
// v' <- v' * m_delta
|
||||
vmdelta = _mm512_set1_ps(m_delta);
|
||||
int k = 0;
|
||||
for (; k <= head_size_v - 16; k += 16) {
|
||||
va = _mm512_mul_ps(_mm512_loadu_ps(v_prime + m * head_size_v + k), vmdelta);
|
||||
_mm512_storeu_ps(reinterpret_cast<__m512*>(v_prime + m * head_size_v + k), va);
|
||||
}
|
||||
if (v_remainder > 0) {
|
||||
va = _mm512_mul_ps(_mm512_maskz_loadu_ps(vmask1, v_prime + m * head_size_v + k), vmdelta);
|
||||
_mm512_mask_storeu_ps(reinterpret_cast<__m512*>(v_prime + m * head_size_v + k), vmask1, va);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
#endif
|
||||
852
third_party/sglang/sgl-kernel/csrc/cpu/gemm.cpp
vendored
Normal file
852
third_party/sglang/sgl-kernel/csrc/cpu/gemm.cpp
vendored
Normal file
@@ -0,0 +1,852 @@
|
||||
#include "gemm.h"
|
||||
|
||||
#include "common.h"
|
||||
#include "vec.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// packed layout:
|
||||
// quants {N, K} int8_t
|
||||
// comp {N} int32_t
|
||||
template <int BLOCK_N>
|
||||
inline void s8s8_compensation(int8_t* __restrict__ packed, int K) {
|
||||
#if defined(CPU_CAPABILITY_AVX512)
|
||||
constexpr int COLS = BLOCK_N / 16;
|
||||
__m512i vcomp[COLS];
|
||||
|
||||
for (int col = 0; col < COLS; ++col) {
|
||||
vcomp[col] = _mm512_setzero_si512();
|
||||
}
|
||||
|
||||
const int64_t offset = BLOCK_N * K;
|
||||
const __m512i off = _mm512_set1_epi8(static_cast<char>(0x80));
|
||||
for (int k = 0; k < K / 4; ++k) {
|
||||
for (int col = 0; col < COLS; ++col) {
|
||||
__m512i vb = _mm512_loadu_si512((const __m512i*)(packed + k * BLOCK_N * 4 + col * 64));
|
||||
vcomp[col] = _mm512_dpbusd_epi32(vcomp[col], off, vb);
|
||||
}
|
||||
}
|
||||
|
||||
for (int col = 0; col < COLS; ++col) {
|
||||
_mm512_storeu_si512((__m512i*)(packed + offset + col * 64), vcomp[col]);
|
||||
}
|
||||
#else
|
||||
TORCH_CHECK(false, "s8s8_compensation not implemented!");
|
||||
#endif
|
||||
}
|
||||
|
||||
// convert to vnni format
|
||||
// from [N, K] to [K/2, N, 2] for bfloat16 and float16
|
||||
template <typename packed_t>
|
||||
inline void pack_vnni(packed_t* __restrict__ packed, const packed_t* __restrict__ weight, int N, int K) {
|
||||
const int VNNI_BLK = 2;
|
||||
for (int n = 0; n < N; ++n) {
|
||||
for (int k = 0; k < K / VNNI_BLK; ++k) {
|
||||
for (int d = 0; d < VNNI_BLK; ++d) {
|
||||
packed[k * N * VNNI_BLK + n * VNNI_BLK + d] = weight[n * K + k * VNNI_BLK + d];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void pack_vnni<int8_t>(int8_t* __restrict__ packed, const int8_t* __restrict__ weight, int N, int K) {
|
||||
constexpr int BLOCK_N = block_size_n();
|
||||
TORCH_CHECK(N == BLOCK_N);
|
||||
|
||||
const int VNNI_BLK = 4;
|
||||
for (int n = 0; n < N; ++n) {
|
||||
for (int k = 0; k < K / VNNI_BLK; ++k) {
|
||||
for (int d = 0; d < VNNI_BLK; ++d) {
|
||||
packed[k * N * VNNI_BLK + n * VNNI_BLK + d] = weight[n * K + k * VNNI_BLK + d];
|
||||
}
|
||||
}
|
||||
}
|
||||
s8s8_compensation<BLOCK_N>(packed, K);
|
||||
}
|
||||
|
||||
// uint8_t: mxfp4 or int4
|
||||
// pack to vnni2 format as they are computed with bfloat16
|
||||
//
|
||||
// from [N, K'/2, 2] to [K'/2, N, 2], view 2x int4 as unit8:
|
||||
// from [N, K ] to [K, N ] where K = K'/2
|
||||
//
|
||||
template <>
|
||||
inline void pack_vnni<uint8_t>(uint8_t* __restrict__ packed, const uint8_t* __restrict__ weight, int N, int K) {
|
||||
constexpr int BLOCK_N = block_size_n();
|
||||
|
||||
uint8_t unpacked[2 * BLOCK_N];
|
||||
|
||||
// 32-way pack (align with BLOCK_N), faster for avx512 unpacking
|
||||
//
|
||||
// for a range of (64):
|
||||
// {0, 1, 2, ..., 63}
|
||||
//
|
||||
// original format:
|
||||
// { 1|0, 3|2, ..., 63|62}
|
||||
//
|
||||
// packed format:
|
||||
// {32|0, 31|1, ..., 63|31}
|
||||
//
|
||||
for (int k = 0; k < K; ++k) {
|
||||
// unpack first
|
||||
for (int n = 0; n < N; ++n) {
|
||||
uint8_t value = weight[n * K + k];
|
||||
unpacked[n * 2 + 0] = value & 0xF; // lower 4 bits
|
||||
unpacked[n * 2 + 1] = value >> 4; // higher 4 bits
|
||||
}
|
||||
// re-pack to 32-way
|
||||
for (int n = 0; n < N; ++n) {
|
||||
packed[k * N + n] = (unpacked[n + BLOCK_N] << 4) | unpacked[n];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
inline void copy_stub(scalar_t* __restrict__ out, const float* __restrict__ input, int64_t size) {
|
||||
using bVec = at::vec::Vectorized<scalar_t>;
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
constexpr int kVecSize = bVec::size();
|
||||
|
||||
int64_t d;
|
||||
#pragma GCC unroll 4
|
||||
for (d = 0; d <= size - kVecSize; d += kVecSize) {
|
||||
fVec data0 = fVec::loadu(input + d);
|
||||
fVec data1 = fVec::loadu(input + d + fVec::size());
|
||||
bVec out_vec = convert_from_float_ext<scalar_t>(data0, data1);
|
||||
out_vec.store(out + d);
|
||||
}
|
||||
for (; d < size; ++d) {
|
||||
out[d] = static_cast<scalar_t>(input[d]);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
inline void copy_stub(float* __restrict__ out, const scalar_t* __restrict__ input, int64_t size) {
|
||||
using bVec = at::vec::Vectorized<scalar_t>;
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
constexpr int kVecSize = bVec::size();
|
||||
|
||||
int64_t d;
|
||||
#pragma GCC unroll 4
|
||||
for (d = 0; d <= size - kVecSize; d += kVecSize) {
|
||||
fVec data0, data1;
|
||||
bVec b_vec = bVec::loadu(input + d);
|
||||
std::tie(data0, data1) = at::vec::convert_to_float(b_vec);
|
||||
data0.store(out + d);
|
||||
data1.store(out + d + fVec::size());
|
||||
}
|
||||
for (; d < size; ++d) {
|
||||
out[d] = static_cast<float>(input[d]);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
inline void copy_add_stub(
|
||||
scalar_t* __restrict__ out, const float* __restrict__ input, const float* __restrict__ bias, int64_t size) {
|
||||
using bVec = at::vec::Vectorized<scalar_t>;
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
constexpr int kVecSize = bVec::size();
|
||||
|
||||
int64_t d;
|
||||
#pragma GCC unroll 4
|
||||
for (d = 0; d <= size - kVecSize; d += kVecSize) {
|
||||
fVec data0 = fVec::loadu(input + d) + fVec::loadu(bias + d);
|
||||
fVec data1 = fVec::loadu(input + d + fVec::size()) + fVec::loadu(bias + d + fVec::size());
|
||||
bVec out_vec = convert_from_float_ext<scalar_t>(data0, data1);
|
||||
out_vec.store(out + d);
|
||||
}
|
||||
for (; d < size; ++d) {
|
||||
out[d] = static_cast<scalar_t>(input[d] + bias[d]);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename scalar_t, bool has_bias>
|
||||
inline void scalar_sigmoid_and_mul(
|
||||
scalar_t* __restrict__ out,
|
||||
const float* __restrict__ input,
|
||||
const float* __restrict__ bias,
|
||||
const scalar_t* __restrict__ mul,
|
||||
int SIZE) {
|
||||
using bVec = at::vec::Vectorized<scalar_t>;
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
// scalar sigmoid
|
||||
const fVec one = fVec(1.f);
|
||||
fVec X;
|
||||
if constexpr (has_bias) {
|
||||
assert(bias != nullptr);
|
||||
X = fVec(input[0] + bias[0]);
|
||||
} else {
|
||||
X = fVec(input[0]);
|
||||
}
|
||||
X = one / (one + X.neg().exp_u20());
|
||||
|
||||
// vec mul
|
||||
constexpr int kVecSize = bVec::size();
|
||||
for (int d = 0; d < SIZE; d += kVecSize) {
|
||||
bVec m_bvec = bVec::loadu(mul + d);
|
||||
fVec m_fvec0, m_fvec1;
|
||||
std::tie(m_fvec0, m_fvec1) = at::vec::convert_to_float(m_bvec);
|
||||
m_fvec0 = m_fvec0 * X;
|
||||
m_fvec1 = m_fvec1 * X;
|
||||
|
||||
bVec out_vec = convert_from_float_ext<scalar_t>(m_fvec0, m_fvec1);
|
||||
out_vec.store(out + d);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename scalar_t, bool has_bias, int BLOCK_M, int BLOCK_N>
|
||||
struct tinygemm_kernel_nn {
|
||||
static inline void apply(
|
||||
const scalar_t* __restrict__ A,
|
||||
const scalar_t* __restrict__ B,
|
||||
scalar_t* __restrict__ C,
|
||||
const float* __restrict__ bias,
|
||||
int64_t K,
|
||||
int64_t lda,
|
||||
int64_t ldb,
|
||||
int64_t ldc) {
|
||||
TORCH_CHECK(false, "tinygemm_kernel_nn: scalar path not implemented!");
|
||||
}
|
||||
};
|
||||
|
||||
#if defined(CPU_CAPABILITY_AVX512)
|
||||
template <bool has_bias, int BLOCK_M, int BLOCK_N>
|
||||
struct tinygemm_kernel_nn<at::BFloat16, has_bias, BLOCK_M, BLOCK_N> {
|
||||
static inline void apply(
|
||||
const at::BFloat16* __restrict__ A,
|
||||
const at::BFloat16* __restrict__ B,
|
||||
at::BFloat16* __restrict__ C,
|
||||
const float* __restrict__ bias,
|
||||
int64_t K,
|
||||
int64_t lda,
|
||||
int64_t ldb,
|
||||
int64_t ldc) {
|
||||
constexpr int ROWS = BLOCK_M;
|
||||
constexpr int COLS = BLOCK_N / 16;
|
||||
|
||||
// prefetch distance
|
||||
constexpr int PREFETCH_SIZE_K = 0;
|
||||
|
||||
__m512bh va;
|
||||
__m512bh vb[COLS];
|
||||
__m512 vc[ROWS * COLS];
|
||||
|
||||
auto loadc = [&](auto i) {
|
||||
constexpr int col = i % COLS;
|
||||
if constexpr (has_bias) {
|
||||
vc[i] = _mm512_loadu_ps(bias + col * 16);
|
||||
} else {
|
||||
vc[i] = _mm512_set1_ps(0.f);
|
||||
}
|
||||
};
|
||||
Unroll<ROWS * COLS>{}(loadc);
|
||||
|
||||
const int64_t K2 = K >> 1;
|
||||
const int64_t lda2 = lda >> 1;
|
||||
const int64_t ldb2 = ldb; // ldb * 2 >> 1;
|
||||
const float* a_ptr = reinterpret_cast<const float*>(A);
|
||||
const float* b_ptr = reinterpret_cast<const float*>(B);
|
||||
|
||||
auto compute = [&](auto i, int64_t k) {
|
||||
constexpr int row = i / COLS;
|
||||
constexpr int col = i % COLS;
|
||||
|
||||
if constexpr (col == 0) {
|
||||
va = (__m512bh)(_mm512_set1_ps(a_ptr[row * lda2 + k]));
|
||||
}
|
||||
if constexpr (row == 0) {
|
||||
vb[col] = (__m512bh)(_mm512_loadu_si512(b_ptr + k * ldb2 + col * 16));
|
||||
if constexpr (PREFETCH_SIZE_K > 0) {
|
||||
_mm_prefetch(b_ptr + (k + PREFETCH_SIZE_K) * ldb2 + col * 16, _MM_HINT_T0);
|
||||
}
|
||||
}
|
||||
vc[i] = _mm512_dpbf16_ps(vc[i], va, vb[col]);
|
||||
};
|
||||
for (int64_t k = 0; k < K2; ++k) {
|
||||
Unroll<ROWS * COLS>{}(compute, k);
|
||||
}
|
||||
|
||||
auto storec = [&](auto i) {
|
||||
constexpr int row = i / COLS;
|
||||
constexpr int col = i % COLS;
|
||||
// for COLS = 2, 4 use 512bit store
|
||||
// for COLS = 1, 3 use 256bit store
|
||||
if constexpr (COLS % 2 == 0) {
|
||||
if constexpr (col % 2 == 0) {
|
||||
_mm512_storeu_si512(
|
||||
reinterpret_cast<__m512i*>((C + row * ldc + col * 16)),
|
||||
(__m512i)(_mm512_cvtne2ps_pbh(vc[row * COLS + col + 1], vc[row * COLS + col])));
|
||||
}
|
||||
} else {
|
||||
_mm256_storeu_si256(reinterpret_cast<__m256i*>(C + row * ldc + col * 16), (__m256i)(_mm512_cvtneps_pbh(vc[i])));
|
||||
}
|
||||
};
|
||||
Unroll<ROWS * COLS>{}(storec);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
#define LAUNCH_TINYGEMM_KERNEL_NN(MB_SIZE, NB_SIZE) \
|
||||
tinygemm_kernel_nn<scalar_t, has_bias, MB_SIZE, NB_SIZE>::apply( \
|
||||
A + mb_start * lda, \
|
||||
B + nb_start * 2, \
|
||||
C + mb_start * ldc + nb_start, \
|
||||
has_bias ? bias + nb_start : nullptr, \
|
||||
K, \
|
||||
lda, \
|
||||
ldb, \
|
||||
ldc);
|
||||
|
||||
template <typename scalar_t, bool has_bias>
|
||||
struct brgemm {
|
||||
static inline void apply(
|
||||
const scalar_t* __restrict__ A,
|
||||
const scalar_t* __restrict__ B,
|
||||
scalar_t* __restrict__ C,
|
||||
float* __restrict__ Ctmp,
|
||||
const float* __restrict__ bias,
|
||||
int64_t M,
|
||||
int64_t N,
|
||||
int64_t K,
|
||||
int64_t lda,
|
||||
int64_t ldb,
|
||||
int64_t ldc) {
|
||||
constexpr int BLOCK_N = block_size_n();
|
||||
at::native::cpublas::brgemm(M, N, K, lda, ldb, BLOCK_N, /* add_C */ false, A, B, Ctmp);
|
||||
|
||||
// copy from Ctmp to C
|
||||
for (int64_t m = 0; m < M; ++m) {
|
||||
if constexpr (has_bias) {
|
||||
copy_add_stub(C + m * ldc, Ctmp + m * BLOCK_N, bias, N);
|
||||
} else {
|
||||
copy_stub(C + m * ldc, Ctmp + m * BLOCK_N, N);
|
||||
}
|
||||
}
|
||||
}
|
||||
static inline void apply(
|
||||
const float* __restrict__ A,
|
||||
const float* __restrict__ B,
|
||||
scalar_t* __restrict__ C,
|
||||
float* __restrict__ Ctmp,
|
||||
const float* __restrict__ bias,
|
||||
int64_t M,
|
||||
int64_t N,
|
||||
int64_t K,
|
||||
int64_t lda,
|
||||
int64_t ldb,
|
||||
int64_t ldc) {
|
||||
constexpr int BLOCK_N = block_size_n();
|
||||
at::native::cpublas::brgemm(M, N, K, lda, ldb, BLOCK_N, /* add_C */ false, A, B, Ctmp);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename scalar_t, bool has_bias>
|
||||
void tinygemm_kernel(
|
||||
const scalar_t* __restrict__ A,
|
||||
const scalar_t* __restrict__ B,
|
||||
scalar_t* __restrict__ C,
|
||||
float* __restrict__ Ctmp,
|
||||
const float* __restrict__ bias,
|
||||
int64_t M,
|
||||
int64_t N,
|
||||
int64_t K,
|
||||
int64_t lda,
|
||||
int64_t ldb,
|
||||
int64_t ldc,
|
||||
bool brg) {
|
||||
if (brg) {
|
||||
brgemm<scalar_t, has_bias>::apply(A, B, C, Ctmp, bias, M, N, K, lda, ldb, ldc);
|
||||
return;
|
||||
}
|
||||
|
||||
// pattern: 1-4-16, N = 16, 32, 48, 64
|
||||
constexpr int64_t BLOCK_M = 4;
|
||||
constexpr int64_t BLOCK_N = 64;
|
||||
const int64_t MB = div_up(M, BLOCK_M);
|
||||
const int64_t NB = div_up(N, BLOCK_N);
|
||||
for (int mb = 0; mb < MB; ++mb) {
|
||||
int64_t mb_start = mb * BLOCK_M;
|
||||
int64_t mb_size = std::min(BLOCK_M, M - mb_start);
|
||||
for (int64_t nb = 0; nb < NB; ++nb) {
|
||||
int64_t nb_start = nb * BLOCK_N;
|
||||
int64_t nb_size = std::min(BLOCK_N, N - nb_start);
|
||||
|
||||
switch (mb_size << 4 | nb_size >> 4) {
|
||||
// mb_size = 1
|
||||
case 0x11:
|
||||
LAUNCH_TINYGEMM_KERNEL_NN(1, 16);
|
||||
break;
|
||||
case 0x12:
|
||||
LAUNCH_TINYGEMM_KERNEL_NN(1, 32);
|
||||
break;
|
||||
case 0x13:
|
||||
LAUNCH_TINYGEMM_KERNEL_NN(1, 48);
|
||||
break;
|
||||
case 0x14:
|
||||
LAUNCH_TINYGEMM_KERNEL_NN(1, 64);
|
||||
break;
|
||||
// mb_size = 2
|
||||
case 0x21:
|
||||
LAUNCH_TINYGEMM_KERNEL_NN(2, 16);
|
||||
break;
|
||||
case 0x22:
|
||||
LAUNCH_TINYGEMM_KERNEL_NN(2, 32);
|
||||
break;
|
||||
case 0x23:
|
||||
LAUNCH_TINYGEMM_KERNEL_NN(2, 48);
|
||||
break;
|
||||
case 0x24:
|
||||
LAUNCH_TINYGEMM_KERNEL_NN(2, 64);
|
||||
break;
|
||||
// mb_size = 3
|
||||
case 0x31:
|
||||
LAUNCH_TINYGEMM_KERNEL_NN(3, 16);
|
||||
break;
|
||||
case 0x32:
|
||||
LAUNCH_TINYGEMM_KERNEL_NN(3, 32);
|
||||
break;
|
||||
case 0x33:
|
||||
LAUNCH_TINYGEMM_KERNEL_NN(3, 48);
|
||||
break;
|
||||
case 0x34:
|
||||
LAUNCH_TINYGEMM_KERNEL_NN(3, 64);
|
||||
break;
|
||||
// mb_size = 4
|
||||
case 0x41:
|
||||
LAUNCH_TINYGEMM_KERNEL_NN(4, 16);
|
||||
break;
|
||||
case 0x42:
|
||||
LAUNCH_TINYGEMM_KERNEL_NN(4, 32);
|
||||
break;
|
||||
case 0x43:
|
||||
LAUNCH_TINYGEMM_KERNEL_NN(4, 48);
|
||||
break;
|
||||
case 0x44:
|
||||
LAUNCH_TINYGEMM_KERNEL_NN(4, 64);
|
||||
break;
|
||||
default:
|
||||
TORCH_CHECK(false, "Unexpected block size, ", mb_size, " x ", nb_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename scalar_t, bool has_bias>
|
||||
void tinygemm_kernel(
|
||||
const float* __restrict__ A,
|
||||
const float* __restrict__ B,
|
||||
scalar_t* __restrict__ C,
|
||||
float* __restrict__ Ctmp,
|
||||
const float* __restrict__ bias,
|
||||
int64_t M,
|
||||
int64_t N,
|
||||
int64_t K,
|
||||
int64_t lda,
|
||||
int64_t ldb,
|
||||
int64_t ldc,
|
||||
bool brg) {
|
||||
TORCH_CHECK(brg, "Expected to use fp32 brgemm for small N GEMM");
|
||||
if (brg) {
|
||||
brgemm<scalar_t, has_bias>::apply(A, B, C, Ctmp, bias, M, N, K, lda, ldb, ldc);
|
||||
return;
|
||||
}
|
||||
// TODO : add intrinsic path
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
void weight_packed_linear_kernel_impl(
|
||||
scalar_t* __restrict__ out,
|
||||
const scalar_t* __restrict__ mat1,
|
||||
const scalar_t* __restrict__ mat2,
|
||||
const float* __restrict__ bias,
|
||||
int64_t M,
|
||||
int64_t N,
|
||||
int64_t K,
|
||||
int64_t mat1_strideM,
|
||||
int64_t out_strideM) {
|
||||
constexpr int64_t BLOCK_M = block_size_m();
|
||||
constexpr int64_t BLOCK_N = block_size_n();
|
||||
const int64_t MB = div_up(M, BLOCK_M);
|
||||
const int64_t NB = div_up(N, BLOCK_N);
|
||||
|
||||
const bool use_brgemm = can_use_brgemm<scalar_t>(M);
|
||||
|
||||
// parallel on [MB, NB]
|
||||
AT_DISPATCH_BOOL(bias != nullptr, has_bias, [&] {
|
||||
parallel_2d(MB, NB, [&](int64_t mb0, int64_t mb1, int64_t nb0, int64_t nb1) {
|
||||
// for brgemm, use float32 for accumulate
|
||||
alignas(64) float Ctmp[BLOCK_M * BLOCK_N];
|
||||
|
||||
loop_2d<scalar_t>(mb0, mb1, nb0, nb1, BLOCK_N * K, [&](int64_t mb, int64_t nb, int64_t nb_offset) {
|
||||
int64_t mb_start = mb * BLOCK_M;
|
||||
int64_t mb_size = std::min(M - mb_start, BLOCK_M);
|
||||
int64_t nb_start = nb * BLOCK_N;
|
||||
int64_t nb_size = std::min(N - nb_start, BLOCK_N);
|
||||
|
||||
tinygemm_kernel<scalar_t, has_bias>(
|
||||
/* A */ mat1 + mb_start * mat1_strideM,
|
||||
/* B */ mat2 + nb_start * K /* nb * BLOCK_N * K */,
|
||||
/* C */ out + mb_start * out_strideM + nb_start,
|
||||
/* Ctmp*/ Ctmp,
|
||||
/* bias*/ bias + nb_start,
|
||||
/* M */ mb_size,
|
||||
/* N */ nb_size,
|
||||
/* K */ K,
|
||||
/* lda */ mat1_strideM,
|
||||
/* ldb */ nb_size,
|
||||
/* ldc */ out_strideM,
|
||||
/* brg */ use_brgemm);
|
||||
});
|
||||
|
||||
if (use_brgemm) {
|
||||
at::native::cpublas::brgemm_release();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
void weight_packed_linear_kernel_impl(
|
||||
scalar_t* __restrict__ out,
|
||||
const scalar_t* __restrict__ mat1,
|
||||
const float* __restrict__ mat2,
|
||||
const float* __restrict__ bias,
|
||||
const scalar_t* __restrict__ post_mul_mat,
|
||||
int64_t M,
|
||||
int64_t N,
|
||||
int64_t K,
|
||||
int64_t mat1_strideM,
|
||||
int64_t out_strideM) {
|
||||
constexpr int64_t BLOCK_M = block_size_m();
|
||||
constexpr int64_t BLOCK_N = block_size_n();
|
||||
const int64_t MB = div_up(M, BLOCK_M);
|
||||
const int64_t NB = div_up(N, BLOCK_N);
|
||||
|
||||
const bool use_brgemm = true; // TODO: add intrinsic path
|
||||
// parallel on [MB, NB]
|
||||
AT_DISPATCH_BOOL(bias != nullptr, has_bias, [&] {
|
||||
parallel_2d(MB, NB, [&](int64_t mb0, int64_t mb1, int64_t nb0, int64_t nb1) {
|
||||
// for brgemm, use float32 for accumulate
|
||||
alignas(64) float Atmp[BLOCK_M * K];
|
||||
alignas(64) float Ctmp[BLOCK_M * BLOCK_N];
|
||||
|
||||
loop_2d<float>(mb0, mb1, nb0, nb1, BLOCK_N * K, [&](int64_t mb, int64_t nb, int64_t nb_offset) {
|
||||
int64_t mb_start = mb * BLOCK_M;
|
||||
int64_t mb_size = std::min(M - mb_start, BLOCK_M);
|
||||
int64_t nb_start = nb * BLOCK_N;
|
||||
int64_t nb_size = std::min(N - nb_start, BLOCK_N);
|
||||
for (int64_t m = 0; m < mb_size; ++m) {
|
||||
copy_stub<scalar_t>(Atmp + m * K, mat1 + mb_start * mat1_strideM + m * K, K);
|
||||
}
|
||||
tinygemm_kernel<scalar_t, has_bias>(
|
||||
/* A */ Atmp,
|
||||
/* B */ mat2 + nb_start * K /* nb * BLOCK_N * K */,
|
||||
/* C */ out + mb_start * out_strideM + nb_start,
|
||||
/* Ctmp*/ Ctmp,
|
||||
/* bias*/ bias + nb_start,
|
||||
/* M */ mb_size,
|
||||
/* N */ nb_size,
|
||||
/* K */ K,
|
||||
/* lda */ mat1_strideM,
|
||||
/* ldb */ nb_size,
|
||||
/* ldc */ out_strideM,
|
||||
/* brg */ use_brgemm);
|
||||
|
||||
if (post_mul_mat != nullptr) {
|
||||
for (int64_t m = 0; m < mb_size; ++m) {
|
||||
scalar_sigmoid_and_mul<scalar_t, has_bias>(
|
||||
out + mb_start * out_strideM + nb_start + m * out_strideM,
|
||||
Ctmp + m * BLOCK_N,
|
||||
bias + nb_start,
|
||||
post_mul_mat + mb_start * out_strideM + m * out_strideM,
|
||||
out_strideM);
|
||||
}
|
||||
} else {
|
||||
for (int64_t m = 0; m < mb_size; ++m) {
|
||||
if constexpr (has_bias) {
|
||||
copy_add_stub(
|
||||
out + mb_start * out_strideM + nb_start + m * out_strideM, Ctmp + m * BLOCK_N, bias + nb_start, N);
|
||||
} else {
|
||||
copy_stub(out + mb_start * out_strideM + nb_start + m * out_strideM, Ctmp + m * BLOCK_N, N);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (use_brgemm) {
|
||||
at::native::cpublas::brgemm_release();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// tinygemm interface
|
||||
template <typename scalar_t>
|
||||
void tinygemm_kernel(
|
||||
const scalar_t* __restrict__ A,
|
||||
const scalar_t* __restrict__ B,
|
||||
scalar_t* __restrict__ C,
|
||||
float* __restrict__ Ctmp,
|
||||
int64_t M,
|
||||
int64_t N,
|
||||
int64_t K,
|
||||
int64_t lda,
|
||||
int64_t ldb,
|
||||
int64_t ldc,
|
||||
bool brg) {
|
||||
tinygemm_kernel<scalar_t, false>(A, B, C, Ctmp, nullptr, M, N, K, lda, ldb, ldc, brg);
|
||||
}
|
||||
|
||||
#define INSTANTIATE_TINYGEMM_TEMPLATE(TYPE) \
|
||||
template void tinygemm_kernel<TYPE>( \
|
||||
const TYPE* __restrict__ A, \
|
||||
const TYPE* __restrict__ B, \
|
||||
TYPE* __restrict__ C, \
|
||||
float* __restrict__ Ctmp, \
|
||||
int64_t M, \
|
||||
int64_t N, \
|
||||
int64_t K, \
|
||||
int64_t lda, \
|
||||
int64_t ldb, \
|
||||
int64_t ldc, \
|
||||
bool brg)
|
||||
|
||||
INSTANTIATE_TINYGEMM_TEMPLATE(at::BFloat16);
|
||||
INSTANTIATE_TINYGEMM_TEMPLATE(at::Half);
|
||||
|
||||
at::Tensor convert_weight_packed(at::Tensor& weight) {
|
||||
// for 3d moe weights
|
||||
// weight : [E, OC, IC]
|
||||
// w1 : [E, 2N, K]
|
||||
// w2 : [E, K, N]
|
||||
CHECK_INPUT(weight);
|
||||
|
||||
const int64_t ndim = weight.ndimension();
|
||||
TORCH_CHECK(ndim == 2 || ndim == 3, "expect weight to be 2d or 3d, got ", ndim, "d tensor.");
|
||||
|
||||
if (ndim == 2 && weight.size(0) < TILE_N) {
|
||||
// for 2D weight and small OC shape, we use fma linear path, which needs transpose not pack
|
||||
return weight.to(at::kFloat).t().contiguous();
|
||||
}
|
||||
|
||||
const auto st = weight.scalar_type();
|
||||
const int64_t E = ndim == 3 ? weight.size(0) : 1;
|
||||
const int64_t OC = ndim == 3 ? weight.size(1) : weight.size(0);
|
||||
const int64_t IC = ndim == 3 ? weight.size(2) : weight.size(1);
|
||||
|
||||
// mxfp4 or int4 are packed with uint8
|
||||
const int64_t actual_IC = st == at::kByte ? IC * 2 : IC;
|
||||
|
||||
// we handle 2 TILE_N at a time.
|
||||
TORCH_CHECK(OC % TILE_N == 0, "invalid weight out features ", OC);
|
||||
TORCH_CHECK(actual_IC % TILE_K == 0, "invalid weight input features ", actual_IC);
|
||||
|
||||
constexpr int64_t BLOCK_N = block_size_n();
|
||||
const int64_t NB = div_up(OC, BLOCK_N);
|
||||
|
||||
// use phony sizes here [E, OC, IC], for each [E], [OC, IC] -> [IC / 2, OC, 2]
|
||||
auto packed_weight = at::empty({}, weight.options());
|
||||
const int64_t stride = OC * IC;
|
||||
|
||||
// Note: for `kByte` (uint8), it represents either `mxfp4` or `int4`.
|
||||
TORCH_CHECK(
|
||||
st == at::kBFloat16 || st == at::kHalf || st == at::kChar || st == at::kFloat8_e4m3fn || st == at::kByte,
|
||||
"expect weight to be bfloat16, float16, int8, fp8_e4m3 or uint8(mxfp4 or int4).");
|
||||
|
||||
CPU_DISPATCH_PACKED_TYPES(st, [&] {
|
||||
// adjust most inner dimension size
|
||||
const int packed_row_size = get_row_size<packed_t>(actual_IC);
|
||||
auto sizes = weight.sizes().vec();
|
||||
sizes[ndim - 1] = packed_row_size;
|
||||
packed_weight.resize_(sizes);
|
||||
|
||||
const packed_t* w_data = weight.data_ptr<packed_t>();
|
||||
packed_t* packed_data = packed_weight.data_ptr<packed_t>();
|
||||
|
||||
// parallel on {E, NB}
|
||||
at::parallel_for(0, E * NB, 0, [&](int64_t begin, int64_t end) {
|
||||
int64_t e{0}, nb{0};
|
||||
data_index_init(begin, e, E, nb, NB);
|
||||
|
||||
for (int64_t i = begin; i < end; ++i) {
|
||||
UNUSED(i);
|
||||
|
||||
int64_t n = nb * BLOCK_N;
|
||||
int64_t n_size = std::min(BLOCK_N, OC - n);
|
||||
pack_vnni<packed_t>(
|
||||
packed_data + e * OC * packed_row_size + n * packed_row_size, w_data + e * stride + n * IC, n_size, IC);
|
||||
|
||||
// move to the next index
|
||||
data_index_step(e, E, nb, NB);
|
||||
}
|
||||
});
|
||||
});
|
||||
return packed_weight;
|
||||
}
|
||||
|
||||
at::Tensor convert_scale_packed(at::Tensor& scale) {
|
||||
CHECK_INPUT(scale);
|
||||
|
||||
const int64_t ndim = scale.ndimension();
|
||||
TORCH_CHECK(ndim == 2 || ndim == 3, "expect scale to be 2d or 3d, got ", ndim, "d tensor.");
|
||||
const auto st = scale.scalar_type();
|
||||
const int64_t E = ndim == 3 ? scale.size(0) : 1;
|
||||
const int64_t N = ndim == 3 ? scale.size(1) : scale.size(0);
|
||||
// number of groups, e.g. K/32
|
||||
const int64_t G = ndim == 3 ? scale.size(2) : scale.size(1);
|
||||
|
||||
constexpr int64_t BLOCK_N = block_size_n();
|
||||
TORCH_CHECK(N % BLOCK_N == 0, "invalid weight out features ", N);
|
||||
const int64_t NB = N / BLOCK_N;
|
||||
|
||||
auto packed_scale = at::empty_like(scale);
|
||||
TORCH_CHECK(st == at::kByte, "expect scale to be uint8.");
|
||||
|
||||
const uint8_t* s_data = scale.data_ptr<uint8_t>();
|
||||
uint8_t* packed_data = packed_scale.data_ptr<uint8_t>();
|
||||
|
||||
// parallel on src {E, NB, BLOCK_N, G}, dst {E, NB, G, BLOCK_N}
|
||||
at::parallel_for(0, E * NB * BLOCK_N * G, 0, [&](int64_t begin, int64_t end) {
|
||||
int64_t e{0}, nb{0}, n{0}, g{0};
|
||||
data_index_init(begin, e, E, nb, NB, n, BLOCK_N, g, G);
|
||||
|
||||
for (int64_t i = begin; i < end; ++i) {
|
||||
packed_data[e * N * G + nb * G * BLOCK_N + g * BLOCK_N + n] = s_data[i];
|
||||
// move to the next index
|
||||
data_index_step(e, E, nb, NB, n, BLOCK_N, g, G);
|
||||
}
|
||||
});
|
||||
return packed_scale;
|
||||
}
|
||||
|
||||
// mat1 : [M, K]
|
||||
// mat2 : [N, K] ([K, N] if use_fma_gemm)
|
||||
// bias : [N]
|
||||
// out : [M, N]
|
||||
//
|
||||
at::Tensor
|
||||
weight_packed_linear(at::Tensor& mat1, at::Tensor& mat2, const std::optional<at::Tensor>& bias, bool is_vnni) {
|
||||
RECORD_FUNCTION("sgl-kernel::weight_packed_linear", std::vector<c10::IValue>({mat1, mat2, bias}));
|
||||
|
||||
auto packed_w = is_vnni ? mat2 : convert_weight_packed(mat2);
|
||||
bool use_fma_gemm = false;
|
||||
if (packed_w.scalar_type() == at::kFloat) {
|
||||
use_fma_gemm = true;
|
||||
}
|
||||
|
||||
int64_t M = mat1.size(0);
|
||||
int64_t K = mat1.size(1);
|
||||
int64_t N = use_fma_gemm ? mat2.size(1) : mat2.size(0);
|
||||
|
||||
CHECK_LAST_DIM_CONTIGUOUS_INPUT(mat1);
|
||||
CHECK_INPUT(mat2);
|
||||
CHECK_DIM(2, mat1);
|
||||
CHECK_DIM(2, mat2);
|
||||
if (!use_fma_gemm) {
|
||||
CHECK_EQ(mat1.size(1), K);
|
||||
}
|
||||
|
||||
auto dispatch_type = mat1.scalar_type();
|
||||
auto out = at::empty({M, N}, mat1.options());
|
||||
// strides
|
||||
int64_t out_strideM = out.stride(0);
|
||||
int64_t mat1_strideM = mat1.stride(0);
|
||||
|
||||
const bool has_bias = bias.has_value();
|
||||
const float* bias_data = nullptr;
|
||||
if (has_bias) {
|
||||
CHECK_EQ(bias.value().size(0), N);
|
||||
bias_data = bias.value().data_ptr<float>();
|
||||
}
|
||||
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(dispatch_type, "weight_packed_linear_kernel_impl", [&] {
|
||||
if (use_fma_gemm) {
|
||||
weight_packed_linear_kernel_impl<scalar_t>(
|
||||
out.data_ptr<scalar_t>(),
|
||||
mat1.data_ptr<scalar_t>(),
|
||||
packed_w.data_ptr<float>(),
|
||||
bias_data,
|
||||
nullptr,
|
||||
M,
|
||||
N,
|
||||
K,
|
||||
mat1_strideM,
|
||||
out_strideM);
|
||||
} else {
|
||||
weight_packed_linear_kernel_impl<scalar_t>(
|
||||
out.data_ptr<scalar_t>(),
|
||||
mat1.data_ptr<scalar_t>(),
|
||||
packed_w.data_ptr<scalar_t>(),
|
||||
bias_data,
|
||||
M,
|
||||
N,
|
||||
K,
|
||||
mat1_strideM,
|
||||
out_strideM);
|
||||
}
|
||||
});
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// mat1 : [M, K]
|
||||
// mat2 : [K, 1]
|
||||
// post_mul_mat : [M, K]
|
||||
// bias : [N]
|
||||
// out : [M, N]
|
||||
//
|
||||
at::Tensor fused_linear_sigmoid_mul(
|
||||
at::Tensor& mat1,
|
||||
at::Tensor& mat2,
|
||||
const std::optional<at::Tensor>& bias,
|
||||
bool is_vnni,
|
||||
const at::Tensor& post_mul_mat) {
|
||||
RECORD_FUNCTION("sgl-kernel::fused_linear_sigmoid_mul", std::vector<c10::IValue>({mat1, mat2, bias, post_mul_mat}));
|
||||
|
||||
auto packed_w = is_vnni ? mat2 : convert_weight_packed(mat2);
|
||||
TORCH_CHECK(packed_w.scalar_type() == at::kFloat, "fused_linear_sigmoid_mul requires packed float weight")
|
||||
|
||||
int64_t M = mat1.size(0);
|
||||
int64_t K = mat1.size(1);
|
||||
int64_t N = mat2.size(1);
|
||||
|
||||
CHECK_LAST_DIM_CONTIGUOUS_INPUT(mat1);
|
||||
CHECK_INPUT(mat2);
|
||||
CHECK_DIM(2, mat1);
|
||||
CHECK_DIM(2, mat2);
|
||||
|
||||
int64_t out_strideM = post_mul_mat.size(1);
|
||||
int64_t mat1_strideM = mat1.stride(0);
|
||||
auto dispatch_type = mat1.scalar_type();
|
||||
auto out = at::empty({M, out_strideM}, mat1.options());
|
||||
|
||||
TORCH_CHECK(
|
||||
N == 1 && out_strideM % 32 == 0,
|
||||
"post_mul_mat tensor size(1) should be 32 dividable, and the mat2 OC=1 (Mx1 as linear output shape)")
|
||||
|
||||
const bool has_bias = bias.has_value();
|
||||
const float* bias_data = nullptr;
|
||||
if (has_bias) {
|
||||
CHECK_EQ(bias.value().size(0), N);
|
||||
bias_data = bias.value().data_ptr<float>();
|
||||
}
|
||||
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(dispatch_type, "fused_linear_sigmoid_mul", [&] {
|
||||
weight_packed_linear_kernel_impl<scalar_t>(
|
||||
out.data_ptr<scalar_t>(),
|
||||
mat1.data_ptr<scalar_t>(),
|
||||
packed_w.data_ptr<float>(),
|
||||
bias_data,
|
||||
post_mul_mat.data_ptr<scalar_t>(),
|
||||
M,
|
||||
N,
|
||||
K,
|
||||
mat1_strideM,
|
||||
out_strideM);
|
||||
});
|
||||
|
||||
return out;
|
||||
}
|
||||
319
third_party/sglang/sgl-kernel/csrc/cpu/gemm.h
vendored
Normal file
319
third_party/sglang/sgl-kernel/csrc/cpu/gemm.h
vendored
Normal file
@@ -0,0 +1,319 @@
|
||||
#pragma once
|
||||
#include <ATen/native/CPUBlas.h>
|
||||
|
||||
#include "common.h"
|
||||
|
||||
// amx-bf16
|
||||
#define TILE_M 16
|
||||
#define TILE_N 16
|
||||
#define TILE_K 32
|
||||
|
||||
// block size for AMX gemm
|
||||
constexpr int block_size_m() {
|
||||
return 2 * TILE_M;
|
||||
}
|
||||
constexpr int block_size_n() {
|
||||
return 2 * TILE_N;
|
||||
}
|
||||
|
||||
// define threshold using brgemm (intel AMX)
|
||||
template <typename T>
|
||||
inline bool can_use_brgemm(int M);
|
||||
template <>
|
||||
inline bool can_use_brgemm<at::BFloat16>(int M) {
|
||||
return M > 4;
|
||||
}
|
||||
template <>
|
||||
inline bool can_use_brgemm<at::Half>(int M) {
|
||||
return true;
|
||||
}
|
||||
// this requires PyTorch 2.7 or above
|
||||
template <>
|
||||
inline bool can_use_brgemm<int8_t>(int M) {
|
||||
return M > 4;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool can_use_brgemm<uint8_t>(int M) {
|
||||
return M > 4;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool can_use_brgemm<at::Float8_e4m3fn>(int M) {
|
||||
return M > 4;
|
||||
}
|
||||
|
||||
// work around compiler internal error
|
||||
#define BLOCK_K 128 // 4 * TILE_K
|
||||
|
||||
// adjust leading dimension size for K
|
||||
template <typename T>
|
||||
inline int64_t get_row_size(int64_t K) {
|
||||
return K;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline int64_t get_row_size<int8_t>(int64_t K) {
|
||||
return K + sizeof(int32_t);
|
||||
}
|
||||
|
||||
// uint8: mxfp4 or int4
|
||||
template <>
|
||||
inline int64_t get_row_size<uint8_t>(int64_t K) {
|
||||
return K >> 1;
|
||||
}
|
||||
|
||||
inline int64_t get_row_size(int64_t K, bool use_int8_w8a8) {
|
||||
return use_int8_w8a8 ? K + sizeof(int32_t) : K;
|
||||
}
|
||||
|
||||
enum class CPUQuantMethod : int64_t { BF16 = 0, INT8_W8A8 = 1, FP8_W8A16 = 2, INT4_W4A8 = 3 };
|
||||
|
||||
constexpr bool operator==(CPUQuantMethod a, int64_t b) {
|
||||
return static_cast<int64_t>(a) == b;
|
||||
}
|
||||
|
||||
constexpr bool operator==(int64_t a, CPUQuantMethod b) {
|
||||
return a == static_cast<int64_t>(b);
|
||||
}
|
||||
|
||||
inline int64_t get_4bit_block_k_size(int64_t group_size) {
|
||||
return group_size > 128 ? 128 : group_size;
|
||||
}
|
||||
|
||||
// pack weight to vnni format
|
||||
at::Tensor convert_weight_packed(at::Tensor& weight);
|
||||
|
||||
// pack weight to vnni format for int4
|
||||
std::tuple<at::Tensor, at::Tensor, at::Tensor>
|
||||
convert_weight_packed_scale_zp(at::Tensor qweight, at::Tensor qzeros, at::Tensor scales);
|
||||
|
||||
// moe implementations for int8 w8a8
|
||||
template <typename scalar_t>
|
||||
void fused_experts_int8_kernel_impl(
|
||||
scalar_t* __restrict__ output,
|
||||
scalar_t* __restrict__ ic1,
|
||||
scalar_t* __restrict__ ic2,
|
||||
uint8_t* __restrict__ A_tmp,
|
||||
float* __restrict__ C_tmp,
|
||||
uint8_t* __restrict__ Aq_tmp,
|
||||
float* __restrict__ As_tmp,
|
||||
const scalar_t* __restrict__ input,
|
||||
const int8_t* __restrict__ packed_w1,
|
||||
const int8_t* __restrict__ packed_w2,
|
||||
const float* __restrict__ w1s,
|
||||
const float* __restrict__ w2s,
|
||||
const float* __restrict__ topk_weights,
|
||||
const int32_t* __restrict__ sorted_ids,
|
||||
const int32_t* __restrict__ expert_ids,
|
||||
const int32_t* __restrict__ offsets,
|
||||
int64_t M,
|
||||
int64_t N,
|
||||
int64_t K,
|
||||
int64_t E,
|
||||
int64_t topk,
|
||||
int64_t num_tokens_post_pad);
|
||||
|
||||
// moe implementations for fp8 w8a16
|
||||
template <typename scalar_t>
|
||||
void fused_experts_fp8_kernel_impl(
|
||||
scalar_t* __restrict__ output,
|
||||
scalar_t* __restrict__ ic0,
|
||||
scalar_t* __restrict__ ic1,
|
||||
scalar_t* __restrict__ ic2,
|
||||
scalar_t* __restrict__ A_tmp,
|
||||
scalar_t* __restrict__ B_tmp,
|
||||
float* __restrict__ C_tmp,
|
||||
const scalar_t* __restrict__ input,
|
||||
const at::Float8_e4m3fn* __restrict__ packed_w1,
|
||||
const at::Float8_e4m3fn* __restrict__ packed_w2,
|
||||
const float* __restrict__ w1s,
|
||||
const float* __restrict__ w2s,
|
||||
int64_t block_size_N,
|
||||
int64_t block_size_K,
|
||||
const float* __restrict__ topk_weights,
|
||||
const int32_t* __restrict__ sorted_ids,
|
||||
const int32_t* __restrict__ expert_ids,
|
||||
const int32_t* __restrict__ offsets,
|
||||
int64_t M,
|
||||
int64_t N,
|
||||
int64_t K,
|
||||
int64_t E,
|
||||
int64_t topk,
|
||||
int64_t num_tokens_post_pad);
|
||||
|
||||
// shared expert implementation for int8 w8a8
|
||||
template <typename scalar_t>
|
||||
void shared_expert_int8_kernel_impl(
|
||||
scalar_t* __restrict__ output,
|
||||
scalar_t* __restrict__ ic1,
|
||||
float* __restrict__ C_tmp,
|
||||
uint8_t* __restrict__ Aq_tmp,
|
||||
float* __restrict__ As_tmp,
|
||||
const scalar_t* __restrict__ input,
|
||||
const int8_t* __restrict__ packed_w1,
|
||||
const int8_t* __restrict__ packed_w2,
|
||||
const float* __restrict__ w1s,
|
||||
const float* __restrict__ w2s,
|
||||
const scalar_t* __restrict__ fused_experts_out,
|
||||
float routed_scaling_factor,
|
||||
int64_t M,
|
||||
int64_t N,
|
||||
int64_t K);
|
||||
|
||||
template <typename scalar_t>
|
||||
void fused_experts_int4_w4a8_kernel_impl(
|
||||
scalar_t* __restrict__ output,
|
||||
scalar_t* __restrict__ ic0,
|
||||
scalar_t* __restrict__ ic1,
|
||||
scalar_t* __restrict__ ic2,
|
||||
uint8_t* __restrict__ A_tmp,
|
||||
uint8_t* __restrict__ Aq_tmp,
|
||||
float* __restrict__ As_tmp,
|
||||
int32_t* __restrict__ Azp_tmp,
|
||||
float* __restrict__ C_tmp,
|
||||
int8_t* __restrict__ dqB_tmp,
|
||||
const scalar_t* __restrict__ input,
|
||||
const uint8_t* __restrict__ packed_w1,
|
||||
const uint8_t* __restrict__ packed_w2,
|
||||
const int8_t* __restrict__ w1z,
|
||||
const int8_t* __restrict__ w2z,
|
||||
const float* __restrict__ w1s,
|
||||
const float* __restrict__ w2s,
|
||||
int group_size,
|
||||
const float* __restrict__ topk_weights,
|
||||
const int32_t* __restrict__ sorted_ids,
|
||||
const int32_t* __restrict__ expert_ids,
|
||||
const int32_t* __restrict__ offsets,
|
||||
int64_t M,
|
||||
int64_t N,
|
||||
int64_t K,
|
||||
int64_t E,
|
||||
int64_t topk,
|
||||
int64_t num_tokens_post_pad);
|
||||
|
||||
template <typename scalar_t>
|
||||
void shared_expert_fp8_kernel_impl(
|
||||
scalar_t* __restrict__ output,
|
||||
scalar_t* __restrict__ ic0,
|
||||
scalar_t* __restrict__ ic1,
|
||||
scalar_t* __restrict__ B_tmp,
|
||||
float* __restrict__ C_tmp,
|
||||
const scalar_t* __restrict__ input,
|
||||
const at::Float8_e4m3fn* __restrict__ packed_w1,
|
||||
const at::Float8_e4m3fn* __restrict__ packed_w2,
|
||||
const float* __restrict__ w1s,
|
||||
const float* __restrict__ w2s,
|
||||
int64_t block_size_N,
|
||||
int64_t block_size_K,
|
||||
const scalar_t* __restrict__ fused_experts_out,
|
||||
float routed_scaling_factor,
|
||||
int64_t M,
|
||||
int64_t N,
|
||||
int64_t K);
|
||||
|
||||
// tinygemm interface
|
||||
template <typename scalar_t>
|
||||
void tinygemm_kernel(
|
||||
const scalar_t* __restrict__ A,
|
||||
const scalar_t* __restrict__ B,
|
||||
scalar_t* __restrict__ C,
|
||||
float* __restrict__ Ctmp,
|
||||
int64_t M,
|
||||
int64_t N,
|
||||
int64_t K,
|
||||
int64_t lda,
|
||||
int64_t ldb,
|
||||
int64_t ldc,
|
||||
bool brg);
|
||||
|
||||
template <typename scalar_t>
|
||||
void tinygemm_kernel(
|
||||
const uint8_t* __restrict__ A,
|
||||
const int8_t* __restrict__ B,
|
||||
scalar_t* __restrict__ C,
|
||||
int32_t* __restrict__ Ctmp,
|
||||
const float* __restrict__ As,
|
||||
const float* __restrict__ Bs,
|
||||
int64_t M,
|
||||
int64_t N,
|
||||
int64_t K,
|
||||
int64_t lda,
|
||||
int64_t ldb,
|
||||
int64_t ldc,
|
||||
bool brg);
|
||||
|
||||
// block quantization
|
||||
template <typename scalar_t>
|
||||
void tinygemm_kernel(
|
||||
const scalar_t* __restrict__ A,
|
||||
const at::Float8_e4m3fn* __restrict__ B,
|
||||
scalar_t* __restrict__ C,
|
||||
scalar_t* __restrict__ Btmp,
|
||||
float* __restrict__ Ctmp,
|
||||
const float* __restrict__ scale,
|
||||
int64_t M,
|
||||
int64_t N,
|
||||
int64_t K,
|
||||
int64_t lda,
|
||||
int64_t ldb,
|
||||
int64_t ldc,
|
||||
bool brg,
|
||||
int64_t block_size_K,
|
||||
bool do_unpack = true);
|
||||
|
||||
// per tensor quantization
|
||||
template <typename scalar_t>
|
||||
void tinygemm_kernel(
|
||||
const scalar_t* __restrict__ A,
|
||||
const at::Float8_e4m3fn* __restrict__ B,
|
||||
scalar_t* __restrict__ C,
|
||||
scalar_t* __restrict__ Btmp,
|
||||
float* __restrict__ Ctmp,
|
||||
float scale,
|
||||
int64_t M,
|
||||
int64_t N,
|
||||
int64_t K,
|
||||
int64_t lda,
|
||||
int64_t ldb,
|
||||
int64_t ldc,
|
||||
bool brg);
|
||||
|
||||
template <typename scalar_t>
|
||||
void tinygemm_kernel(
|
||||
scalar_t* C,
|
||||
float* C_temp,
|
||||
const uint8_t* A,
|
||||
const float* scales_a,
|
||||
const int32_t* qzeros_a,
|
||||
const uint8_t* B,
|
||||
const float* scales_b,
|
||||
const int8_t* qzeros_b,
|
||||
const int32_t* compensation,
|
||||
int8_t* dqB_tmp,
|
||||
int64_t M,
|
||||
int64_t K,
|
||||
int64_t lda,
|
||||
int64_t ldc_f,
|
||||
int64_t ldc_s,
|
||||
bool store_out,
|
||||
bool use_brgemm);
|
||||
|
||||
// mxfp4
|
||||
template <typename scalar_t>
|
||||
void tinygemm_kernel(
|
||||
const scalar_t* __restrict__ A,
|
||||
const uint8_t* __restrict__ B,
|
||||
scalar_t* __restrict__ C,
|
||||
scalar_t* __restrict__ Btmp,
|
||||
float* __restrict__ Ctmp,
|
||||
const uint8_t* __restrict__ scale,
|
||||
int64_t M,
|
||||
int64_t N,
|
||||
int64_t K,
|
||||
int64_t lda,
|
||||
int64_t ldb,
|
||||
int64_t ldc,
|
||||
bool brg,
|
||||
int64_t block_size_K,
|
||||
bool do_unpack = true);
|
||||
1159
third_party/sglang/sgl-kernel/csrc/cpu/gemm_fp8.cpp
vendored
Normal file
1159
third_party/sglang/sgl-kernel/csrc/cpu/gemm_fp8.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
811
third_party/sglang/sgl-kernel/csrc/cpu/gemm_int4.cpp
vendored
Normal file
811
third_party/sglang/sgl-kernel/csrc/cpu/gemm_int4.cpp
vendored
Normal file
@@ -0,0 +1,811 @@
|
||||
#include <torch/all.h>
|
||||
|
||||
#include "gemm.h"
|
||||
#include "vec.h"
|
||||
|
||||
namespace {
|
||||
|
||||
#define BLOCK_N block_size_n()
|
||||
#define BLOCK_M 128
|
||||
|
||||
template <bool sym_quant_act>
|
||||
struct ActDtype;
|
||||
template <>
|
||||
struct ActDtype<true> {
|
||||
using type = int8_t;
|
||||
};
|
||||
template <>
|
||||
struct ActDtype<false> {
|
||||
using type = uint8_t;
|
||||
};
|
||||
|
||||
struct alignas(32) m256i_wrapper {
|
||||
__m256i data;
|
||||
};
|
||||
|
||||
#if defined(CPU_CAPABILITY_AVX512)
|
||||
inline std::array<m256i_wrapper, 2> load_zps_4vnni(const int8_t* __restrict__ zps) {
|
||||
// broadcast 01234567 to
|
||||
// 01234567012345670123456701234567
|
||||
__m256i vzps_low = _mm256_set1_epi64x(*reinterpret_cast<const long*>(zps));
|
||||
__m256i vzps_high = _mm256_set1_epi64x(*reinterpret_cast<const long*>(zps + 8));
|
||||
// shuffle from
|
||||
// 01234567012345670123456701234567
|
||||
// to
|
||||
// 00001111222233334444555566667777
|
||||
__m256i shuffle_mask =
|
||||
_mm256_set_epi8(7, 7, 7, 7, 6, 6, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 3, 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0);
|
||||
vzps_low = _mm256_shuffle_epi8(vzps_low, shuffle_mask);
|
||||
vzps_high = _mm256_shuffle_epi8(vzps_high, shuffle_mask);
|
||||
m256i_wrapper vzps_low_wp, vzps_high_wp;
|
||||
vzps_low_wp.data = vzps_low;
|
||||
vzps_high_wp.data = vzps_high;
|
||||
return {vzps_low_wp, vzps_high_wp};
|
||||
}
|
||||
|
||||
inline std::array<m256i_wrapper, 2> load_uint4_as_int8(const uint8_t* __restrict__ qB) {
|
||||
__m256i packed = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(qB));
|
||||
const __m256i low_mask = _mm256_set1_epi8(0x0f);
|
||||
__m256i high = _mm256_srli_epi16(packed, 4);
|
||||
high = _mm256_and_si256(high, low_mask);
|
||||
__m256i low = _mm256_and_si256(packed, low_mask);
|
||||
m256i_wrapper low_wp, high_wp;
|
||||
low_wp.data = low;
|
||||
high_wp.data = high;
|
||||
return {low_wp, high_wp};
|
||||
}
|
||||
|
||||
template <int64_t N, int64_t ldb>
|
||||
void _dequant_weight_zp_only(const uint8_t* __restrict__ B, int8_t* dqB, const int8_t* __restrict__ qzeros, int64_t K) {
|
||||
// unpack weight int8 -> two int4
|
||||
// subtract zero point
|
||||
// B shape = [K, ldb] = [K, N / 2], actual shape = [K / 4, N / 2, 4]
|
||||
// dqB shape = [K, N], actual shape = [K / 4, N, 4]
|
||||
#pragma GCC unroll 2
|
||||
for (int n = 0; n < N; n += 16) {
|
||||
auto [zps_low_wp, zps_high_wp] = load_zps_4vnni(&qzeros[n]);
|
||||
auto zps_low = zps_low_wp.data;
|
||||
auto zps_high = zps_high_wp.data;
|
||||
for (int k = 0; k < K; k += 4) {
|
||||
auto [vb_low_wp, vb_high_wp] = load_uint4_as_int8(B + ldb * k + n / 2 * 4);
|
||||
auto vb_low = vb_low_wp.data;
|
||||
auto vb_high = vb_high_wp.data;
|
||||
vb_high = _mm256_sub_epi8(vb_high, zps_high);
|
||||
vb_low = _mm256_sub_epi8(vb_low, zps_low);
|
||||
// store vb to B
|
||||
_mm256_storeu_si256(reinterpret_cast<__m256i_u*>(dqB + N * k + n * 4), vb_low);
|
||||
_mm256_storeu_si256(reinterpret_cast<__m256i_u*>(dqB + N * k + (n + 8) * 4), vb_high);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <bool accum, int64_t N, bool sym_quant_act>
|
||||
void _dequant_and_store(
|
||||
float* __restrict__ output,
|
||||
const int32_t* __restrict__ input,
|
||||
const float* __restrict__ scale_a,
|
||||
const int32_t* __restrict__ zp_a,
|
||||
const float* __restrict__ scale_b,
|
||||
const int32_t* __restrict__ comp_b,
|
||||
int M,
|
||||
int ldi,
|
||||
int ldo,
|
||||
int ldsa = 1) {
|
||||
for (int m = 0; m < M; ++m) {
|
||||
float a_scale = *(scale_a + m * ldsa);
|
||||
__m512 va_scale = _mm512_set1_ps(a_scale);
|
||||
int32_t a_zp;
|
||||
__m512i va_zp;
|
||||
if constexpr (!sym_quant_act) {
|
||||
a_zp = *(zp_a + m * ldsa);
|
||||
va_zp = _mm512_set1_epi32(a_zp);
|
||||
}
|
||||
int n = 0;
|
||||
#pragma GCC unroll 2
|
||||
for (; n < N; n += 16) {
|
||||
__m512i vc = _mm512_loadu_si512(input + m * ldi + n);
|
||||
if constexpr (!sym_quant_act) {
|
||||
__m512i vb_comp = _mm512_loadu_si512(comp_b + n);
|
||||
vc = _mm512_sub_epi32(vc, _mm512_mullo_epi32(vb_comp, va_zp));
|
||||
}
|
||||
__m512 vc_f = _mm512_cvtepi32_ps(vc);
|
||||
__m512 vc_f_mul = _mm512_mul_ps(vc_f, va_scale);
|
||||
__m512 vb_s = _mm512_loadu_ps(scale_b + n);
|
||||
vc_f_mul = _mm512_mul_ps(vc_f_mul, vb_s);
|
||||
if constexpr (accum) {
|
||||
__m512 vo = _mm512_loadu_ps(output + m * ldo + n);
|
||||
_mm512_storeu_ps(output + m * ldo + n, _mm512_add_ps(vo, vc_f_mul));
|
||||
} else {
|
||||
_mm512_storeu_ps(output + m * ldo + n, vc_f_mul);
|
||||
}
|
||||
}
|
||||
for (; n < N; ++n) {
|
||||
float dq_val;
|
||||
if constexpr (sym_quant_act) {
|
||||
dq_val = (float)input[m * ldi + n] * a_scale * scale_b[n];
|
||||
} else {
|
||||
dq_val = (float)(input[m * ldi + n] - a_zp * comp_b[n]) * a_scale * scale_b[n];
|
||||
}
|
||||
if constexpr (accum) {
|
||||
output[m * ldo + n] += dq_val;
|
||||
} else {
|
||||
output[m * ldo + n] = dq_val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
template <int64_t N, int64_t ldb>
|
||||
void _dequant_weight_zp_only(const uint8_t* B, int8_t* dqB, const int8_t* qzeros, int64_t K) {
|
||||
// B shape = [K, N / 2]
|
||||
// dqB shape = [K, N]
|
||||
for (int k = 0; k < K; ++k) {
|
||||
for (int n = 0; n < N / 2; ++n) {
|
||||
int32_t b = (int32_t)B[k * ldb + n];
|
||||
dqB[k * N + n * 2] = (b & 0xf) - qzeros[n];
|
||||
dqB[k * N + n * 2 + 1] = (b >> 4) - qzeros[n];
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(CPU_CAPABILITY_AVX512)
|
||||
inline __m512i combine_m256i(__m256i a, __m256i b) {
|
||||
__m512i c = _mm512_castsi256_si512(a);
|
||||
return _mm512_inserti64x4(c, b, 1);
|
||||
}
|
||||
|
||||
inline __m512i combine_m256i(std::array<m256i_wrapper, 2> two_256) {
|
||||
return combine_m256i(two_256[0].data, two_256[1].data);
|
||||
}
|
||||
|
||||
// negate elements in a according to b's sign
|
||||
static inline __m512i _mm512_sign_epi8(__m512i a, __m512i b) {
|
||||
__m512i zero = _mm512_setzero_si512();
|
||||
__mmask64 blt0 = _mm512_movepi8_mask(b);
|
||||
return _mm512_mask_sub_epi8(a, blt0, zero, a);
|
||||
}
|
||||
|
||||
template <int64_t M, int64_t N, int64_t ldb, bool sym_quant_act>
|
||||
void _dequant_gemm_accum_small_M(
|
||||
float* __restrict__ C,
|
||||
const uint8_t* A,
|
||||
const float* scales_a,
|
||||
const int32_t* qzeros_a,
|
||||
const uint8_t* B,
|
||||
const float* scales_b,
|
||||
const int8_t* qzeros_b,
|
||||
int64_t K,
|
||||
int64_t lda,
|
||||
int64_t ldc) {
|
||||
// if sym_quant_act is true, A pointer type is passed in as uint8_t* but actually int8_t*.
|
||||
|
||||
constexpr int COLS = N / 16;
|
||||
// Computing compensation is faster than loading it for small M
|
||||
// because it's memory bound.
|
||||
__m512i ones = _mm512_set1_epi8(1); // used for computing compensation
|
||||
__m512i va;
|
||||
__m512i vb[COLS];
|
||||
__m512i vc[M * COLS];
|
||||
__m512 vscales[COLS];
|
||||
__m512i vzps[COLS];
|
||||
__m512i vcompensate[COLS];
|
||||
|
||||
// Load scales and zps
|
||||
Unroll<COLS>{}([&](auto i) {
|
||||
vscales[i] = _mm512_loadu_ps(scales_b + i * 16);
|
||||
vzps[i] = combine_m256i(load_zps_4vnni(qzeros_b + i * 16));
|
||||
if constexpr (!sym_quant_act) {
|
||||
vcompensate[i] = _mm512_setzero_epi32();
|
||||
}
|
||||
});
|
||||
Unroll<M * COLS>{}([&](auto i) { vc[i] = _mm512_setzero_epi32(); });
|
||||
|
||||
auto compute = [&](auto i, int k) {
|
||||
constexpr const int row = i / COLS;
|
||||
constexpr const int col = i % COLS;
|
||||
|
||||
if constexpr (col == 0) {
|
||||
va = _mm512_set1_epi32(*(int32_t*)(A + row * lda + k));
|
||||
}
|
||||
|
||||
if constexpr (row == 0) {
|
||||
int B_offset = k * ldb + col * 16 * 2;
|
||||
vb[col] = combine_m256i(load_uint4_as_int8(B + B_offset));
|
||||
vb[col] = _mm512_sub_epi8(vb[col], vzps[col]);
|
||||
if constexpr (!sym_quant_act) {
|
||||
vcompensate[col] = _mm512_dpbusd_epi32(vcompensate[col], ones, vb[col]);
|
||||
}
|
||||
_mm_prefetch(B + B_offset + 128 * ldb, _MM_HINT_T0);
|
||||
}
|
||||
if constexpr (sym_quant_act) {
|
||||
auto vsb = _mm512_sign_epi8(vb[col], va);
|
||||
auto vabsa = _mm512_sign_epi8(va, va);
|
||||
vc[i] = _mm512_dpbusds_epi32(vc[i], vabsa, vsb);
|
||||
} else {
|
||||
vc[i] = _mm512_dpbusd_epi32(vc[i], va, vb[col]);
|
||||
}
|
||||
};
|
||||
|
||||
// Accumulate along k
|
||||
constexpr const int unroll = 4;
|
||||
int k = 0;
|
||||
for (; k < K / 4 / unroll; k++) {
|
||||
Unroll<unroll>{}([&](auto i) { Unroll<M * COLS>{}(compute, 4 * (k * unroll + i)); });
|
||||
}
|
||||
k *= 4 * unroll;
|
||||
for (; k < K; k += 4) {
|
||||
Unroll<M * COLS>{}(compute, k);
|
||||
}
|
||||
|
||||
// Store to C
|
||||
auto store = [&](auto i) {
|
||||
constexpr const int row = i / COLS;
|
||||
constexpr const int col = i % COLS;
|
||||
// compute (qC - compensate * zp_a) * scale_a * scale_b
|
||||
__m512 vc_float;
|
||||
if constexpr (!sym_quant_act) {
|
||||
vc[i] = _mm512_sub_epi32(vc[i], _mm512_mullo_epi32(vcompensate[col], _mm512_set1_epi32(*(qzeros_a + row))));
|
||||
}
|
||||
vc_float = _mm512_cvtepi32_ps(vc[i]);
|
||||
vc_float = _mm512_mul_ps(vc_float, _mm512_set1_ps(*(scales_a + row)));
|
||||
|
||||
vc_float = _mm512_mul_ps(vc_float, vscales[col]);
|
||||
auto vc_old = _mm512_loadu_ps(C + row * ldc + col * 16);
|
||||
vc_float = _mm512_add_ps(vc_float, vc_old);
|
||||
_mm512_storeu_ps(C + row * ldc + col * 16, vc_float);
|
||||
};
|
||||
Unroll<M * COLS>{}(store);
|
||||
}
|
||||
|
||||
#define CALL_DEQUANT_GEMM_ACCUM_SMALL_M(M) \
|
||||
_dequant_gemm_accum_small_M<M, N, ldb, sym_quant_act>(C, A, scales_a, qzeros_a, B, scales_b, qzeros_b, K, lda, ldc);
|
||||
#endif
|
||||
|
||||
template <int64_t N, int64_t ldb, bool sym_quant_act>
|
||||
void _dequant_gemm_accum(
|
||||
float* C,
|
||||
const uint8_t* A,
|
||||
const float* scales_a,
|
||||
const int32_t* qzeros_a,
|
||||
const uint8_t* B,
|
||||
const float* scales_b,
|
||||
const int8_t* qzeros_b,
|
||||
const int32_t* compensation,
|
||||
int8_t* dqB,
|
||||
int64_t M,
|
||||
int64_t K,
|
||||
int64_t lda,
|
||||
int64_t ldc,
|
||||
bool use_brgemm) {
|
||||
// Compute GEMM int8 * int8 -> int32
|
||||
// dequant result to float by applying scales/qzeros
|
||||
#if defined(CPU_CAPABILITY_AVX512)
|
||||
if (!use_brgemm) {
|
||||
switch (M) {
|
||||
case 1:
|
||||
CALL_DEQUANT_GEMM_ACCUM_SMALL_M(1);
|
||||
break;
|
||||
case 2:
|
||||
CALL_DEQUANT_GEMM_ACCUM_SMALL_M(2);
|
||||
break;
|
||||
case 3:
|
||||
CALL_DEQUANT_GEMM_ACCUM_SMALL_M(3);
|
||||
break;
|
||||
case 4:
|
||||
CALL_DEQUANT_GEMM_ACCUM_SMALL_M(4);
|
||||
break;
|
||||
default:
|
||||
TORCH_CHECK(false, "tinygemm_kernel: unexpected M for AVX path!");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
_dequant_weight_zp_only<N, ldb>(B, dqB, qzeros_b, K);
|
||||
using Tin = typename ActDtype<sym_quant_act>::type;
|
||||
Tin* A_ptr = (Tin*)A;
|
||||
if (use_brgemm) {
|
||||
int32_t C_i32[M * N];
|
||||
at::native::cpublas::brgemm(
|
||||
M, N, K, lda, N /*ldb*/, N /*ldc*/, false /* add_C */, A_ptr, dqB, C_i32, true /* is_vnni */);
|
||||
_mm_prefetch(B + N * K / 2, _MM_HINT_T0);
|
||||
_mm_prefetch(A + K, _MM_HINT_T0);
|
||||
_dequant_and_store<true, N, sym_quant_act>(
|
||||
C, C_i32, scales_a, qzeros_a, scales_b, compensation, M, N /*ldi*/, ldc, 1 /*ldsa*/);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
TORCH_CHECK(false, "tinygemm_kernel: scalar path not implemented!");
|
||||
}
|
||||
}
|
||||
|
||||
template <int64_t N>
|
||||
inline void copy_bias(const float* bias_ptr, float* y_buf, int64_t m) {
|
||||
if (bias_ptr) {
|
||||
for (int i = 0; i < m; ++i) {
|
||||
int j = 0;
|
||||
#if defined(CPU_CAPABILITY_AVX512)
|
||||
#pragma GCC unroll 2
|
||||
for (; j < N; j += 16) {
|
||||
__m512 bias_vec = _mm512_loadu_ps(bias_ptr + j);
|
||||
_mm512_storeu_ps(y_buf + i * N + j, bias_vec);
|
||||
}
|
||||
#endif
|
||||
for (; j < N; ++j) {
|
||||
y_buf[i * N + j] = bias_ptr[j];
|
||||
}
|
||||
}
|
||||
} else { // initialize to zero
|
||||
for (int i = 0; i < m; ++i) {
|
||||
int j = 0;
|
||||
#if defined(CPU_CAPABILITY_AVX512)
|
||||
#pragma GCC unroll 2
|
||||
for (; j < N; j += 16) {
|
||||
__m512 zero_vec = _mm512_setzero_ps();
|
||||
_mm512_storeu_ps(y_buf + i * N + j, zero_vec);
|
||||
}
|
||||
#endif
|
||||
for (; j < N; ++j) {
|
||||
y_buf[i * N + j] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename out_dtype, int64_t N>
|
||||
inline void store_out(const float* y_buf, out_dtype* c_ptr, int64_t m, /* int64_t n, */ int64_t lda) {
|
||||
for (int i = 0; i < m; ++i) {
|
||||
int j = 0;
|
||||
if constexpr (std::is_same<out_dtype, float>::value) {
|
||||
#if defined(CPU_CAPABILITY_AVX512)
|
||||
#pragma GCC unroll 2
|
||||
for (; j < N; j += 16) {
|
||||
__m512 y_vec = _mm512_loadu_ps(y_buf + i * N + j);
|
||||
_mm512_storeu_ps(c_ptr + i * lda + j, y_vec);
|
||||
}
|
||||
#endif
|
||||
for (; j < N; ++j) {
|
||||
c_ptr[i * lda + j] = y_buf[i * N + j];
|
||||
}
|
||||
} else if constexpr (std::is_same<out_dtype, at::BFloat16>::value) {
|
||||
#if defined(CPU_CAPABILITY_AVX512)
|
||||
#pragma GCC unroll 2
|
||||
for (; j < N; j += 16) {
|
||||
__m512 y_vec = _mm512_loadu_ps(y_buf + i * N + j);
|
||||
__m256i y_bf16_vec = at::vec::cvtfp32_bf16(y_vec);
|
||||
_mm256_storeu_si256(reinterpret_cast<__m256i*>(c_ptr + i * lda + j), y_bf16_vec);
|
||||
}
|
||||
#endif
|
||||
for (; j < N; ++j) {
|
||||
c_ptr[i * lda + j] = at::BFloat16(y_buf[i * N + j]);
|
||||
}
|
||||
} else if constexpr (std::is_same<out_dtype, at::Half>::value) {
|
||||
#if defined(CPU_CAPABILITY_AVX512)
|
||||
#pragma GCC unroll 2
|
||||
for (; j < N; j += 16) {
|
||||
__m512 y_vec = _mm512_loadu_ps(y_buf + i * N + j);
|
||||
__m256i y_fp16_vec = at::vec::cvtfp32_fp16(y_vec);
|
||||
_mm256_storeu_si256(reinterpret_cast<__m256i*>(c_ptr + i * lda + j), y_fp16_vec);
|
||||
}
|
||||
#endif
|
||||
for (; j < N; ++j) {
|
||||
c_ptr[i * lda + j] = at::Half(y_buf[i * N + j]);
|
||||
}
|
||||
} else {
|
||||
TORCH_CHECK(false, "Unsupported output dtype");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void fill_val_stub(int32_t* __restrict__ output, int32_t value, int64_t size) {
|
||||
using iVec = at::vec::Vectorized<int32_t>;
|
||||
constexpr int VecSize = iVec::size();
|
||||
const iVec fill_val_vec = iVec(value);
|
||||
int64_t d;
|
||||
#pragma GCC unroll 4
|
||||
for (d = 0; d <= size - VecSize; d += VecSize) {
|
||||
fill_val_vec.store(output + d);
|
||||
}
|
||||
for (; d < size; ++d) {
|
||||
output[d] = value;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename act_dtype, typename out_dtype, bool sym_quant_act>
|
||||
void _da8w4_linear_impl(
|
||||
act_dtype* __restrict__ input,
|
||||
const float* __restrict__ input_scales,
|
||||
const int32_t* __restrict__ input_qzeros,
|
||||
const uint8_t* __restrict__ weight,
|
||||
const float* __restrict__ weight_scales,
|
||||
const int8_t* __restrict__ weight_qzeros,
|
||||
const float* __restrict__ bias,
|
||||
out_dtype* __restrict__ output,
|
||||
float* __restrict__ output_temp,
|
||||
int8_t* __restrict__ dequant_weight_temp,
|
||||
int64_t M,
|
||||
int64_t N,
|
||||
int64_t K,
|
||||
int64_t num_groups) {
|
||||
// weight + compensation shape = [Nc, Kc, BLOCK_N * _block_k / 2 + BLOCK_N*sizeof(int32_t)]
|
||||
// scales/qzeros shape = [Nc, G, BLOCK_N]
|
||||
const bool use_brgemm = can_use_brgemm<int8_t>(M);
|
||||
int64_t block_m = [&]() -> long {
|
||||
if (M <= 48) {
|
||||
return M;
|
||||
} else if (M < 64) {
|
||||
return 32;
|
||||
} else if (M < 96) {
|
||||
return 64;
|
||||
} else {
|
||||
return 128;
|
||||
}
|
||||
}();
|
||||
int64_t Mc = div_up(M, block_m);
|
||||
bool parallel_on_M = M > 128;
|
||||
int64_t Nc = N / BLOCK_N;
|
||||
int64_t num_blocks = parallel_on_M ? Mc * Nc : Nc;
|
||||
int64_t group_size = div_up(K, num_groups);
|
||||
int64_t _block_k = get_4bit_block_k_size(group_size);
|
||||
int64_t Kc = K / _block_k;
|
||||
int64_t block_per_group = group_size / _block_k;
|
||||
|
||||
at::parallel_for(0, num_blocks, 1, [&](int64_t begin, int64_t end) {
|
||||
int tid = get_thread_num();
|
||||
float* C_tmp = output_temp + tid * block_m * BLOCK_N;
|
||||
int8_t* dqB_tmp = dequant_weight_temp + tid * _block_k * BLOCK_N;
|
||||
for (const auto i : c10::irange(begin, end)) {
|
||||
int64_t mc = parallel_on_M ? i / Nc : 0;
|
||||
int64_t nc = parallel_on_M ? i % Nc : i;
|
||||
int64_t mc_end = parallel_on_M ? mc + 1 : Mc;
|
||||
|
||||
for (int mci = mc; mci < mc_end; ++mci) {
|
||||
int64_t m_size = mci * block_m + block_m > M ? M - mci * block_m : block_m;
|
||||
// copy bias to y_buf if bias is not None
|
||||
auto bias_data = bias ? bias + nc * BLOCK_N : nullptr;
|
||||
copy_bias<BLOCK_N>(bias_data, C_tmp, m_size);
|
||||
for (int kci = 0; kci < Kc; ++kci) {
|
||||
int32_t* compensation_ptr =
|
||||
sym_quant_act
|
||||
? nullptr
|
||||
: (int32_t*)(void*)(weight + (nc * Kc + kci) * (BLOCK_N * (_block_k / 2 + sizeof(int32_t))) +
|
||||
_block_k * BLOCK_N / 2) /*Bcomp*/;
|
||||
_dequant_gemm_accum<BLOCK_N, BLOCK_N / 2, sym_quant_act>(
|
||||
/*C*/ C_tmp,
|
||||
/*A*/ (uint8_t*)input + mci * block_m * K + kci * _block_k,
|
||||
/*scales_a*/ input_scales + mci * block_m,
|
||||
/*qzeros_a*/ input_qzeros + mci * block_m,
|
||||
/*B*/ weight + (nc * Kc + kci) * (BLOCK_N * (_block_k / 2 + sizeof(int32_t))),
|
||||
/*scales_b*/ weight_scales + nc * BLOCK_N * num_groups + kci / block_per_group * BLOCK_N,
|
||||
/*qzeros_b*/ weight_qzeros + nc * BLOCK_N * num_groups + kci / block_per_group * BLOCK_N,
|
||||
/*Bcomp*/ compensation_ptr,
|
||||
/*dqB_tmp*/ dqB_tmp,
|
||||
/*M*/ m_size,
|
||||
/*K*/ _block_k,
|
||||
/*lda*/ K,
|
||||
/*ldc*/ BLOCK_N,
|
||||
/*use_brgemm*/ use_brgemm);
|
||||
}
|
||||
// store y_buf to output with dtype conversion
|
||||
store_out<out_dtype, BLOCK_N>(C_tmp, output + mci * block_m * N + nc * BLOCK_N, m_size, N /*lda*/);
|
||||
}
|
||||
}
|
||||
if (use_brgemm) {
|
||||
at::native::cpublas::brgemm_release();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
/*
|
||||
return: packed_weight, packed_scales, packed_qzeros
|
||||
*/
|
||||
std::tuple<at::Tensor, at::Tensor, at::Tensor> convert_int4_weight_packed_with_compensation(
|
||||
const at::Tensor& weight, const at::Tensor& scales, const at::Tensor& qzeros) {
|
||||
// weight shape = [N, K]
|
||||
// scales shape = [N, G]
|
||||
// qzeros shape = [N, G]
|
||||
TORCH_CHECK(weight.dim() == 2, "DA8W4 CPU: Weight should be a 2D tensor for packing");
|
||||
TORCH_CHECK(weight.size(1) % 2 == 0, "DA8W4 CPU: Weight should have even number of columns for packing");
|
||||
|
||||
auto new_scales = scales;
|
||||
auto new_qzeros = qzeros;
|
||||
if (new_scales.dim() == 1) {
|
||||
new_scales.unsqueeze_(1);
|
||||
}
|
||||
new_scales = new_scales.to(at::kFloat);
|
||||
if (new_qzeros.dim() == 1) {
|
||||
new_qzeros.unsqueeze_(1);
|
||||
}
|
||||
new_qzeros = new_qzeros.to(at::kChar);
|
||||
int64_t N = weight.size(0);
|
||||
int64_t K = weight.size(1);
|
||||
int64_t G = scales.size(1);
|
||||
int64_t group_size = K / G;
|
||||
int64_t _block_k = get_4bit_block_k_size(group_size);
|
||||
constexpr int block_n = block_size_n();
|
||||
int64_t Nc = N / block_n;
|
||||
int64_t Kc = K / _block_k;
|
||||
|
||||
// Reorder weight to [N/block_n, K/_block_k, _block_k, block_n]
|
||||
// Reorder scales/qzeros to [N/block_n, G, block_n]
|
||||
// weight + compensation shape = [Nc, Kc, block_n * _block_k / 2 + block_n*sizeof(int32_t)]
|
||||
// scales/qzeros shape = [Nc, G, block_n]
|
||||
auto weight_view = weight.view({Nc, block_n, Kc, _block_k});
|
||||
at::Tensor weight_reordered = weight_view.permute({0, 2, 3, 1}).contiguous();
|
||||
at::Tensor blocked_weight;
|
||||
at::Tensor blocked_scales = new_scales.view({Nc, block_n, G}).permute({0, 2, 1}).contiguous();
|
||||
at::Tensor blocked_qzeros = new_qzeros.view({Nc, block_n, G}).permute({0, 2, 1}).contiguous();
|
||||
// Compensation = Σ(k)(W[k][n] - ZP[n]) for each block.
|
||||
auto weight_sub_qzero = weight.view({Nc, block_n, G, -1}).to(at::kInt) - new_qzeros.view({Nc, block_n, G, -1});
|
||||
weight_sub_qzero = weight_sub_qzero.view({Nc, block_n, Kc, _block_k});
|
||||
at::Tensor compensation = weight_sub_qzero.sum(-1);
|
||||
compensation = compensation.permute({0, 2, 1}).contiguous().to(at::kInt);
|
||||
int64_t buffer_size_nbytes = _block_k * block_n / 2 + block_n * sizeof(int32_t);
|
||||
blocked_weight = at::empty({Nc, Kc, buffer_size_nbytes}, weight.options());
|
||||
|
||||
auto weight_ptr = weight_reordered.data_ptr<uint8_t>();
|
||||
auto compensation_ptr = compensation.data_ptr<int32_t>();
|
||||
auto blocked_weight_ptr = blocked_weight.data_ptr<uint8_t>();
|
||||
int64_t num_blocks = Nc * Kc;
|
||||
at::parallel_for(0, num_blocks, 1, [&](int64_t begin, int64_t end) {
|
||||
for (const auto i : c10::irange(begin, end)) {
|
||||
auto in_ptr = weight_ptr + i * _block_k * block_n;
|
||||
auto out_ptr = blocked_weight_ptr + i * block_n * (_block_k / 2 + sizeof(int32_t));
|
||||
int32_t* comp_in_prt = compensation_ptr + i * block_n;
|
||||
int32_t* comp_out_prt = (int32_t*)(void*)(blocked_weight_ptr + i * block_n * (_block_k / 2 + sizeof(int32_t)) +
|
||||
_block_k * block_n / 2);
|
||||
// Reorder weight block to VNNI4 and pack two lanes along N
|
||||
// N=16 viewed as two lanes: a0, ...a7, b0, ...b7
|
||||
// pack two lanes: [a0, b0], ..., [a7, b7]
|
||||
// plain shape = [_block_k, block_n]
|
||||
// packed shape = [_block_k / 4, block_n / 2, 4] viewed as [_block_k, block_n / 2]
|
||||
constexpr int n_group_size = 8;
|
||||
constexpr int vnni_size = 4;
|
||||
constexpr int n_group = block_n / n_group_size; // 4
|
||||
for (int nb = 0; nb < n_group; nb += 2) {
|
||||
for (int k = 0; k < _block_k; k += vnni_size) {
|
||||
for (int ni = 0; ni < n_group_size; ++ni) {
|
||||
for (int ki = 0; ki < vnni_size; ++ki) {
|
||||
int src_idx_1 = nb * n_group_size + ni + (k + ki) * block_n;
|
||||
int src_idx_2 = (nb + 1) * n_group_size + ni + (k + ki) * block_n;
|
||||
int dst_idx = (nb / 2 * n_group_size + ni) * vnni_size + k * block_n / 2 + ki;
|
||||
uint8_t src_1 = *(in_ptr + src_idx_1);
|
||||
uint8_t src_2 = *(in_ptr + src_idx_2);
|
||||
uint8_t dst = (src_1 & 0x0f) | ((src_2 & 0x0f) << 4);
|
||||
*(out_ptr + dst_idx) = dst;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// compensation [block_n]
|
||||
for (int nb = 0; nb < block_n; nb++) {
|
||||
*(comp_out_prt + nb) = *(comp_in_prt + nb);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return std::make_tuple(std::move(blocked_weight), std::move(blocked_scales), std::move(blocked_qzeros));
|
||||
}
|
||||
|
||||
std::tuple<at::Tensor, at::Tensor> autoawq_to_int4pack(
|
||||
at::Tensor qweight, // (*, K, N / 8), int32
|
||||
at::Tensor qzeros) // (*, K / group_size, N / 8), int32
|
||||
{
|
||||
// bitshifts: [0, 4, 1, 5, 2, 6, 3, 7] * 4
|
||||
auto bitshifts = at::tensor({0, 4, 1, 5, 2, 6, 3, 7}, at::kInt) * 4;
|
||||
// qweight: assumed shape [..., K, N/8] (int32)
|
||||
auto qweight_unsq = qweight.unsqueeze(-1); // [..., K, N/8, 1]
|
||||
auto shape = qweight_unsq.sizes().vec(); // shape: [A, B, C, 1]
|
||||
shape[3] = 8;
|
||||
auto unpacked = at::bitwise_right_shift(qweight_unsq, bitshifts) & 0xF;
|
||||
auto qweight_final = unpacked.flatten(-2).transpose(-1, -2).to(at::kByte);
|
||||
|
||||
auto qzeros_unsq = qzeros.unsqueeze(-1);
|
||||
auto qzeros_unpacked = at::bitwise_right_shift(qzeros_unsq, bitshifts) & 0xF;
|
||||
auto qzeros_final = qzeros_unpacked.flatten(-2).to(at::kByte);
|
||||
|
||||
return std::make_tuple(qweight_final, qzeros_final);
|
||||
}
|
||||
|
||||
std::tuple<at::Tensor, at::Tensor, at::Tensor> convert_weight_packed_scale_zp(
|
||||
at::Tensor qweight, // (*, K, N / 8), int32
|
||||
at::Tensor qzeros, // (*, K / group_size, N / 8), int32
|
||||
at::Tensor scales // (*, K / group_size, N), bfloat16
|
||||
) {
|
||||
auto res = autoawq_to_int4pack(qweight, qzeros);
|
||||
auto _qweight = std::get<0>(res);
|
||||
auto _qzeros = std::get<1>(res);
|
||||
auto _scales = scales;
|
||||
_qzeros = _qzeros.transpose(-2, -1).contiguous(); // .T
|
||||
_scales = _scales.transpose(-2, -1).contiguous();
|
||||
if (_qweight.dim() == 3) { // Dim=3 for MOE packing, TODO: refine a unified loop
|
||||
int64_t E = _qweight.size(0);
|
||||
int64_t K = _qweight.size(2);
|
||||
int64_t G = _scales.size(2);
|
||||
int64_t group_size = K / G;
|
||||
int64_t _block_k = get_4bit_block_k_size(group_size);
|
||||
int64_t block_n = block_size_n();
|
||||
int64_t Nc = _qweight.size(1) / block_n;
|
||||
int64_t Kc = K / _block_k;
|
||||
int64_t buffer_size_nbytes = _block_k * block_n / 2 + block_n * sizeof(int32_t);
|
||||
auto blocked_weight = at::empty({E, Nc, Kc, buffer_size_nbytes}, _qweight.options());
|
||||
auto blocked_scales = at::empty({E, Nc, G, block_n}, _scales.options()).to(at::kFloat);
|
||||
auto blocked_qzeros = at::empty({E, Nc, G, block_n}, _qzeros.options()).to(at::kChar);
|
||||
for (int i = 0; i < _qweight.size(0); i++) {
|
||||
auto res_ = convert_int4_weight_packed_with_compensation(_qweight[i], _scales[i], _qzeros[i]);
|
||||
blocked_weight[i] = std::get<0>(res_);
|
||||
blocked_scales[i] = std::get<1>(res_);
|
||||
blocked_qzeros[i] = std::get<2>(res_);
|
||||
}
|
||||
_qweight = blocked_weight;
|
||||
_scales = blocked_scales;
|
||||
_qzeros = blocked_qzeros;
|
||||
} else {
|
||||
auto res_ = convert_int4_weight_packed_with_compensation(_qweight, _scales, _qzeros);
|
||||
_qweight = std::get<0>(res_);
|
||||
_scales = std::get<1>(res_);
|
||||
_qzeros = std::get<2>(res_);
|
||||
}
|
||||
|
||||
return std::make_tuple(_qweight, _qzeros, _scales);
|
||||
}
|
||||
|
||||
at::Tensor int4_scaled_mm_cpu_with_quant(
|
||||
const at::Tensor& input,
|
||||
const at::Tensor& weight,
|
||||
const at::Tensor& weight_scales,
|
||||
const at::Tensor& weight_qzeros,
|
||||
const std::optional<at::Tensor>& bias,
|
||||
at::ScalarType output_dtype) {
|
||||
RECORD_FUNCTION("sgl-kernel::int4_scaled_mm_cpu_with_quant", std::vector<c10::IValue>({input, weight}));
|
||||
|
||||
int64_t M_a = input.size(0);
|
||||
int64_t K_a = input.size(1);
|
||||
int64_t lda = input.stride(0);
|
||||
|
||||
const auto st = input.scalar_type();
|
||||
TORCH_CHECK(
|
||||
st == at::kBFloat16 || st == at::kHalf, "int4_scaled_mm_cpu_with_quant: expect A to be bfloat16 or half.");
|
||||
|
||||
constexpr bool sym_quant_act = false; // TODO: add sym quant path
|
||||
using Tin = typename ActDtype<sym_quant_act>::type;
|
||||
int64_t act_buffer_size = /* act quant */ M_a * K_a +
|
||||
/* act scale */ M_a * sizeof(float) +
|
||||
/* act zp */ M_a * sizeof(int32_t);
|
||||
auto act_buffer = at::empty({act_buffer_size}, input.options().dtype(at::kByte));
|
||||
// asym path, activation quants into uint8_t
|
||||
auto Aq_data = act_buffer.data_ptr<uint8_t>();
|
||||
auto As_data = reinterpret_cast<float*>(Aq_data + M_a * K_a);
|
||||
auto Azp_data = reinterpret_cast<int32_t*>(As_data + M_a);
|
||||
fill_val_stub(Azp_data, 128, M_a); // sym_a s8s8 is unified to u8s8 with compensation (128)
|
||||
|
||||
auto out_sizes = input.sizes().vec();
|
||||
int64_t N = weight_scales.size(0) * weight_scales.size(-1);
|
||||
out_sizes.back() = N;
|
||||
auto output = at::empty(out_sizes, input.options());
|
||||
// weight + compensation shape = [Nc, Kc, BLOCK_N * _block_k / 2 + BLOCK_N*sizeof(int32_t)]
|
||||
// scales/qzeros shape = [Nc, G, BLOCK_N]
|
||||
int64_t Nc = weight.size(0);
|
||||
int64_t Kc = weight.size(1);
|
||||
int64_t _block_k = K_a / Kc;
|
||||
TORCH_CHECK(N == Nc * BLOCK_N, "DA8W4: weight and input shapes mismatch");
|
||||
// scales/qzeros shape = [Nc, G, BLOCK_N]
|
||||
int64_t num_groups = weight_scales.size(1);
|
||||
|
||||
const uint8_t* b_ptr = weight.data_ptr<uint8_t>();
|
||||
const float* b_scales_ptr = weight_scales.data_ptr<float>();
|
||||
const int8_t* b_qzeros_ptr = weight_qzeros.data_ptr<int8_t>();
|
||||
const float* bias_ptr = bias.has_value() ? bias.value().data_ptr<float>() : nullptr;
|
||||
int num_threads = at::get_num_threads();
|
||||
int64_t temp_buffer_size = /* output temp */ num_threads * BLOCK_M * BLOCK_N * sizeof(float) +
|
||||
/* weight dequant temp */ num_threads * _block_k * BLOCK_N;
|
||||
auto c_temp_buffer = at::empty({temp_buffer_size}, input.options().dtype(at::kChar));
|
||||
float* c_temp_ptr = (float*)((void*)(c_temp_buffer.data_ptr<int8_t>()));
|
||||
int8_t* dqB_temp_ptr = (int8_t*)((void*)(c_temp_ptr + num_threads * BLOCK_M * BLOCK_N));
|
||||
|
||||
#define LAUNCH_DA8W4_LINEAR_WITH_QUANT_IMPL(sym_quant_act) \
|
||||
AT_DISPATCH_FLOATING_TYPES_AND2( \
|
||||
at::ScalarType::BFloat16, at::ScalarType::Half, output_dtype, "int4_scaled_mm_cpu_with_quant", [&] { \
|
||||
const scalar_t* __restrict__ A_data = input.data_ptr<scalar_t>(); \
|
||||
scalar_t* __restrict__ c_ptr = output.data_ptr<scalar_t>(); \
|
||||
at::parallel_for(0, M_a, 0, [&](int64_t begin, int64_t end) { \
|
||||
for (int64_t m = begin; m < end; ++m) { \
|
||||
quantize_row_int8<scalar_t>(Aq_data + m * K_a, As_data[m], A_data + m * lda, K_a); \
|
||||
} \
|
||||
}); \
|
||||
_da8w4_linear_impl<Tin, scalar_t, sym_quant_act>( \
|
||||
Aq_data, \
|
||||
As_data, \
|
||||
Azp_data, \
|
||||
b_ptr, \
|
||||
b_scales_ptr, \
|
||||
b_qzeros_ptr, \
|
||||
bias_ptr, \
|
||||
c_ptr, \
|
||||
c_temp_ptr, \
|
||||
dqB_temp_ptr, \
|
||||
M_a, \
|
||||
N, \
|
||||
K_a, \
|
||||
num_groups); \
|
||||
});
|
||||
|
||||
LAUNCH_DA8W4_LINEAR_WITH_QUANT_IMPL(sym_quant_act);
|
||||
|
||||
return output;
|
||||
}
|
||||
template <typename scalar_t>
|
||||
inline void copy_stub(scalar_t* __restrict__ out, const float* __restrict__ input, int64_t size) {
|
||||
using Vec = at::vec::Vectorized<scalar_t>;
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
// no remainder
|
||||
#pragma GCC unroll 4
|
||||
for (int64_t d = 0; d < size; d += Vec::size()) {
|
||||
fVec x0 = fVec::loadu(input + d);
|
||||
fVec x1 = fVec::loadu(input + d + fVec::size());
|
||||
Vec res = convert_from_float_ext<scalar_t>(x0, x1);
|
||||
res.store(out + d);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
void tinygemm_kernel(
|
||||
scalar_t* C,
|
||||
float* C_temp,
|
||||
const uint8_t* A,
|
||||
const float* scales_a,
|
||||
const int32_t* qzeros_a,
|
||||
const uint8_t* B,
|
||||
const float* scales_b,
|
||||
const int8_t* qzeros_b,
|
||||
const int32_t* compensation,
|
||||
int8_t* dqB_tmp,
|
||||
int64_t M,
|
||||
int64_t K,
|
||||
int64_t lda,
|
||||
int64_t ldc_f,
|
||||
int64_t ldc_s,
|
||||
bool store_out,
|
||||
bool use_brgemm) {
|
||||
// TODO: add sym quant act, now only asym
|
||||
_dequant_gemm_accum<BLOCK_N, BLOCK_N / 2, false>(
|
||||
C_temp, A, scales_a, qzeros_a, B, scales_b, qzeros_b, compensation, dqB_tmp, M, K, lda, ldc_f, use_brgemm);
|
||||
if (store_out) {
|
||||
// copy from Ctmp to C
|
||||
for (int64_t m = 0; m < M; ++m) {
|
||||
copy_stub<scalar_t>(C + m * ldc_s, C_temp + m * ldc_f, BLOCK_N);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define INSTANTIATE_TINYGEMM_TEMPLATE(TYPE) \
|
||||
template void tinygemm_kernel<TYPE>( \
|
||||
TYPE * C, \
|
||||
float* C_temp, \
|
||||
const uint8_t* A, \
|
||||
const float* scales_a, \
|
||||
const int32_t* qzeros_a, \
|
||||
const uint8_t* B, \
|
||||
const float* scales_b, \
|
||||
const int8_t* qzeros_b, \
|
||||
const int32_t* compensation, \
|
||||
int8_t* dqB_tmp, \
|
||||
int64_t M, \
|
||||
int64_t K, \
|
||||
int64_t lda, \
|
||||
int64_t ldc_f, \
|
||||
int64_t ldc_s, \
|
||||
bool store_out, \
|
||||
bool use_brgemm)
|
||||
|
||||
INSTANTIATE_TINYGEMM_TEMPLATE(at::BFloat16);
|
||||
INSTANTIATE_TINYGEMM_TEMPLATE(at::Half);
|
||||
|
||||
// int4 gemm dispatch api register
|
||||
at::Tensor int4_scaled_mm_cpu(
|
||||
at::Tensor& x, at::Tensor& w, at::Tensor& w_zeros, at::Tensor& w_scales, std::optional<at::Tensor> bias) {
|
||||
return int4_scaled_mm_cpu_with_quant(x, w, w_scales, w_zeros, bias, x.scalar_type());
|
||||
}
|
||||
547
third_party/sglang/sgl-kernel/csrc/cpu/gemm_int8.cpp
vendored
Normal file
547
third_party/sglang/sgl-kernel/csrc/cpu/gemm_int8.cpp
vendored
Normal file
@@ -0,0 +1,547 @@
|
||||
#include "common.h"
|
||||
#include "gemm.h"
|
||||
#include "vec.h"
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename scalar_t, bool has_bias, int BLOCK_N>
|
||||
struct scale_C {
|
||||
static inline void apply(
|
||||
scalar_t* __restrict__ C,
|
||||
const int32_t* __restrict__ Ctmp,
|
||||
const int32_t* __restrict__ Bcomp,
|
||||
const float* __restrict__ bias,
|
||||
float As,
|
||||
const float* __restrict__ Bs) {
|
||||
TORCH_CHECK(false, "scale_C: scalar path not implemented!");
|
||||
}
|
||||
};
|
||||
|
||||
#if defined(CPU_CAPABILITY_AVX512)
|
||||
template <bool has_bias, int BLOCK_N>
|
||||
struct scale_C<at::BFloat16, has_bias, BLOCK_N> {
|
||||
static inline void apply(
|
||||
at::BFloat16* __restrict__ C,
|
||||
const int32_t* __restrict__ Ctmp,
|
||||
const int32_t* __restrict__ Bcomp,
|
||||
const float* __restrict__ bias,
|
||||
float As,
|
||||
const float* __restrict__ Bs) {
|
||||
constexpr int COLS = BLOCK_N / 16;
|
||||
static_assert(COLS % 2 == 0);
|
||||
|
||||
__m512 vc[COLS];
|
||||
__m512 vd0 = _mm512_set1_ps(As);
|
||||
|
||||
auto compute = [&](auto col) {
|
||||
__m512 vd1 = _mm512_loadu_ps(Bs + col * 16);
|
||||
__m512i vcomp = _mm512_loadu_si512(Bcomp + col * 16);
|
||||
__m512i vc32 = _mm512_loadu_si512(Ctmp + col * 16);
|
||||
vc[col] = _mm512_cvtepi32_ps(_mm512_sub_epi32(vc32, vcomp));
|
||||
if constexpr (has_bias) {
|
||||
__m512 vbias = _mm512_loadu_ps(bias + col * 16);
|
||||
vc[col] = _mm512_fmadd_ps(_mm512_mul_ps(vc[col], vd0), vd1, vbias);
|
||||
} else {
|
||||
vc[col] = _mm512_mul_ps(_mm512_mul_ps(vc[col], vd0), vd1);
|
||||
}
|
||||
};
|
||||
Unroll<COLS>{}(compute);
|
||||
|
||||
auto storec = [&](auto col) {
|
||||
// for COLS = 2, 4 use 512bit store
|
||||
if constexpr (col % 2 == 0) {
|
||||
_mm512_storeu_si512(
|
||||
reinterpret_cast<__m512i*>((C + col * 16)), (__m512i)(_mm512_cvtne2ps_pbh(vc[col + 1], vc[col + 0])));
|
||||
}
|
||||
};
|
||||
Unroll<COLS>{}(storec);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
template <typename scalar_t, bool has_bias, int BLOCK_M, int BLOCK_N>
|
||||
struct tinygemm_kernel_nn {
|
||||
static inline void apply(
|
||||
const uint8_t* __restrict__ A,
|
||||
const int8_t* __restrict__ B,
|
||||
scalar_t* __restrict__ C,
|
||||
const float* __restrict__ As,
|
||||
const float* __restrict__ Bs,
|
||||
const int32_t* __restrict__ Bcomp,
|
||||
const float* __restrict__ bias,
|
||||
int64_t K,
|
||||
int64_t lda,
|
||||
int64_t ldb,
|
||||
int64_t ldc) {
|
||||
TORCH_CHECK(false, "tinygemm_kernel_nn: scalar path not implemented!");
|
||||
}
|
||||
};
|
||||
|
||||
#if defined(CPU_CAPABILITY_AVX512)
|
||||
template <bool has_bias, int BLOCK_M, int BLOCK_N>
|
||||
struct tinygemm_kernel_nn<at::BFloat16, has_bias, BLOCK_M, BLOCK_N> {
|
||||
static inline void apply(
|
||||
const uint8_t* __restrict__ A,
|
||||
const int8_t* __restrict__ B,
|
||||
at::BFloat16* __restrict__ C,
|
||||
const float* __restrict__ As,
|
||||
const float* __restrict__ Bs,
|
||||
const int32_t* __restrict__ Bcomp,
|
||||
const float* __restrict__ bias,
|
||||
int64_t K,
|
||||
int64_t lda,
|
||||
int64_t ldb,
|
||||
int64_t ldc) {
|
||||
constexpr int ROWS = BLOCK_M;
|
||||
constexpr int COLS = BLOCK_N / 16;
|
||||
static_assert(COLS % 2 == 0);
|
||||
|
||||
// prefetch distance
|
||||
constexpr int PREFETCH_SIZE_K = 0;
|
||||
|
||||
__m512i va;
|
||||
__m512i vb[COLS];
|
||||
__m512i vc[ROWS * COLS];
|
||||
__m512i vcomp[COLS];
|
||||
__m512 vd0;
|
||||
__m512 vd1[COLS];
|
||||
|
||||
// oops! 4x4 spills but we use 4x2
|
||||
__m512 vbias[COLS];
|
||||
|
||||
// [NOTE]: s8s8 igemm compensation in avx512-vnni
|
||||
//
|
||||
// avx512-vnni has no s8s8, so we need to change s8s8 to u8s8 with compensate:
|
||||
//
|
||||
// a * b = (a + 128) * b - 128 * b
|
||||
// s s u s u s
|
||||
//
|
||||
// 1) 128 * b is pre-computed when packing B to vnni formats
|
||||
// 2) a + 128 is fused when dynamically quantize A
|
||||
//
|
||||
auto loadc = [&](auto i) { vc[i] = _mm512_set1_epi32(0); };
|
||||
Unroll<ROWS * COLS>{}(loadc);
|
||||
|
||||
const int64_t K4 = K >> 2;
|
||||
const int64_t lda4 = lda >> 2;
|
||||
const int64_t ldb4 = ldb; // ldb * 4 >> 2;
|
||||
const int32_t* a_ptr = reinterpret_cast<const int32_t*>(A);
|
||||
const int32_t* b_ptr = reinterpret_cast<const int32_t*>(B);
|
||||
|
||||
auto compute = [&](auto i, int64_t k) {
|
||||
constexpr int row = i / COLS;
|
||||
constexpr int col = i % COLS;
|
||||
|
||||
if constexpr (col == 0) {
|
||||
va = _mm512_set1_epi32(a_ptr[row * lda4 + k]);
|
||||
}
|
||||
if constexpr (row == 0) {
|
||||
vb[col] = _mm512_loadu_si512(b_ptr + k * ldb4 + col * 16);
|
||||
if constexpr (PREFETCH_SIZE_K > 0) {
|
||||
_mm_prefetch(b_ptr + (k + PREFETCH_SIZE_K) * ldb4 + col * 16, _MM_HINT_T0);
|
||||
}
|
||||
}
|
||||
vc[i] = _mm512_dpbusd_epi32(vc[i], va, vb[col]);
|
||||
};
|
||||
for (int64_t k = 0; k < K4; ++k) {
|
||||
Unroll<ROWS * COLS>{}(compute, k);
|
||||
}
|
||||
|
||||
auto storec = [&](auto i) {
|
||||
constexpr int row = i / COLS;
|
||||
constexpr int col = i % COLS;
|
||||
|
||||
// load a scale
|
||||
if constexpr (col == 0) {
|
||||
vd0 = _mm512_set1_ps(As[row]);
|
||||
}
|
||||
// load b scale and vcomp per 2 vectors
|
||||
// also load bias if any
|
||||
if constexpr (row == 0) {
|
||||
if constexpr (col % 2 == 0) {
|
||||
vd1[col + 0] = _mm512_loadu_ps(Bs + col * 16);
|
||||
vd1[col + 1] = _mm512_loadu_ps(Bs + col * 16 + 16);
|
||||
vcomp[col + 0] = _mm512_loadu_si512(Bcomp + col * 16);
|
||||
vcomp[col + 1] = _mm512_loadu_si512(Bcomp + col * 16 + 16);
|
||||
if constexpr (has_bias) {
|
||||
vbias[col + 0] = _mm512_loadu_ps(bias + col * 16);
|
||||
vbias[col + 1] = _mm512_loadu_ps(bias + col * 16 + 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for COLS = 2, 4 use 512bit store
|
||||
if constexpr (col % 2 == 0) {
|
||||
__m512 vc0 = _mm512_cvtepi32_ps(_mm512_sub_epi32(vc[row * COLS + col + 0], vcomp[col + 0]));
|
||||
__m512 vc1 = _mm512_cvtepi32_ps(_mm512_sub_epi32(vc[row * COLS + col + 1], vcomp[col + 1]));
|
||||
if constexpr (has_bias) {
|
||||
vc0 = _mm512_fmadd_ps(_mm512_mul_ps(vc0, vd0), vd1[col + 0], vbias[col + 0]);
|
||||
vc1 = _mm512_fmadd_ps(_mm512_mul_ps(vc1, vd0), vd1[col + 1], vbias[col + 1]);
|
||||
} else {
|
||||
vc0 = _mm512_mul_ps(_mm512_mul_ps(vc0, vd0), vd1[col + 0]);
|
||||
vc1 = _mm512_mul_ps(_mm512_mul_ps(vc1, vd0), vd1[col + 1]);
|
||||
}
|
||||
|
||||
_mm512_storeu_si512(
|
||||
reinterpret_cast<__m512i*>((C + row * ldc + col * 16)), (__m512i)(_mm512_cvtne2ps_pbh(vc1, vc0)));
|
||||
}
|
||||
};
|
||||
Unroll<ROWS * COLS>{}(storec);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
#define LAUNCH_TINYGEMM_KERNEL_NN(MB_SIZE, NB_SIZE) \
|
||||
tinygemm_kernel_nn<scalar_t, has_bias, MB_SIZE, NB_SIZE>::apply( \
|
||||
A + mb_start * lda, \
|
||||
B + nb_start * 4, \
|
||||
C + mb_start * ldc + nb_start, \
|
||||
As + mb_start, \
|
||||
Bs + nb_start, \
|
||||
Bcomp + nb_start, \
|
||||
has_bias ? bias + nb_start : nullptr, \
|
||||
K, \
|
||||
lda, \
|
||||
ldb, \
|
||||
ldc);
|
||||
|
||||
template <typename scalar_t, bool has_bias>
|
||||
void tinygemm_kernel(
|
||||
const uint8_t* __restrict__ A,
|
||||
const int8_t* __restrict__ B,
|
||||
scalar_t* __restrict__ C,
|
||||
int32_t* __restrict__ Ctmp,
|
||||
const float* __restrict__ As,
|
||||
const float* __restrict__ Bs,
|
||||
const float* __restrict__ bias,
|
||||
int64_t M,
|
||||
int64_t N,
|
||||
int64_t K,
|
||||
int64_t lda,
|
||||
int64_t ldb,
|
||||
int64_t ldc,
|
||||
bool brg) {
|
||||
// B compensation
|
||||
const int32_t* Bcomp = reinterpret_cast<const int32_t*>(B + block_size_n() * K);
|
||||
|
||||
if (brg) {
|
||||
constexpr int BLOCK_N = block_size_n();
|
||||
at::native::cpublas::brgemm(M, N, K, lda, ldb, BLOCK_N, /* add_C */ false, A, B, Ctmp);
|
||||
|
||||
// apply compensation and scale
|
||||
for (int64_t m = 0; m < M; ++m) {
|
||||
scale_C<scalar_t, has_bias, BLOCK_N>::apply(C + m * ldc, Ctmp + m * BLOCK_N, Bcomp, bias, As[m], Bs);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// pattern: 1-4-16
|
||||
constexpr int64_t BLOCK_M = 4;
|
||||
constexpr int64_t BLOCK_N = 64;
|
||||
const int64_t MB = div_up(M, BLOCK_M);
|
||||
const int64_t NB = div_up(N, BLOCK_N);
|
||||
for (int64_t mb = 0; mb < MB; ++mb) {
|
||||
int64_t mb_start = mb * BLOCK_M;
|
||||
int64_t mb_size = std::min(BLOCK_M, M - mb_start);
|
||||
for (int64_t nb = 0; nb < NB; ++nb) {
|
||||
int64_t nb_start = nb * BLOCK_N;
|
||||
int64_t nb_size = std::min(BLOCK_N, N - nb_start);
|
||||
|
||||
switch (mb_size << 4 | nb_size >> 4) {
|
||||
// mb_size = 1
|
||||
case 0x12:
|
||||
LAUNCH_TINYGEMM_KERNEL_NN(1, 32);
|
||||
break;
|
||||
case 0x14:
|
||||
LAUNCH_TINYGEMM_KERNEL_NN(1, 64);
|
||||
break;
|
||||
// mb_size = 2
|
||||
case 0x22:
|
||||
LAUNCH_TINYGEMM_KERNEL_NN(2, 32);
|
||||
break;
|
||||
case 0x24:
|
||||
LAUNCH_TINYGEMM_KERNEL_NN(2, 64);
|
||||
break;
|
||||
// mb_size = 3
|
||||
case 0x32:
|
||||
LAUNCH_TINYGEMM_KERNEL_NN(3, 32);
|
||||
break;
|
||||
case 0x34:
|
||||
LAUNCH_TINYGEMM_KERNEL_NN(3, 64);
|
||||
break;
|
||||
// mb_size = 4
|
||||
case 0x42:
|
||||
LAUNCH_TINYGEMM_KERNEL_NN(4, 32);
|
||||
break;
|
||||
case 0x44:
|
||||
LAUNCH_TINYGEMM_KERNEL_NN(4, 64);
|
||||
break;
|
||||
default:
|
||||
TORCH_CHECK(false, "Unexpected block size, ", mb_size, "x", "nb_size");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
void int8_scaled_mm_kernel_impl(
|
||||
scalar_t* __restrict__ out,
|
||||
const uint8_t* __restrict__ mat1,
|
||||
const int8_t* __restrict__ mat2,
|
||||
const float* __restrict__ scales1,
|
||||
const float* __restrict__ scales2,
|
||||
const float* __restrict__ bias,
|
||||
int64_t M,
|
||||
int64_t N,
|
||||
int64_t K) {
|
||||
constexpr int64_t BLOCK_M = block_size_m();
|
||||
constexpr int64_t BLOCK_N = block_size_n();
|
||||
const int64_t MB = div_up(M, BLOCK_M);
|
||||
const int64_t NB = div_up(N, BLOCK_N);
|
||||
|
||||
const bool use_brgemm = can_use_brgemm<int8_t>(M);
|
||||
|
||||
// K + 4 after compensation
|
||||
const int64_t packed_row_size = get_row_size<int8_t>(K);
|
||||
|
||||
AT_DISPATCH_BOOL(bias != nullptr, has_bias, [&] {
|
||||
parallel_2d(MB, NB, [&](int64_t mb0, int64_t mb1, int64_t nb0, int64_t nb1) {
|
||||
// for brgemm, use int32_t for accumulate
|
||||
alignas(64) int32_t Ctmp[BLOCK_M * BLOCK_N];
|
||||
|
||||
loop_2d<int8_t>(mb0, mb1, nb0, nb1, BLOCK_N * K, [&](int64_t mb, int64_t nb, int64_t nb_offset) {
|
||||
int mb_start = mb * BLOCK_M;
|
||||
int mb_size = std::min(M - mb_start, BLOCK_M);
|
||||
int nb_start = nb * BLOCK_N;
|
||||
int nb_size = std::min(N - nb_start, BLOCK_N);
|
||||
|
||||
tinygemm_kernel<scalar_t, has_bias>(
|
||||
/* A */ mat1 + mb_start * K,
|
||||
/* B */ mat2 + nb_start * packed_row_size /* nb * BLOCK_N * (K + 4) */,
|
||||
/* C */ out + mb_start * N + nb_start,
|
||||
/* Ctmp*/ Ctmp,
|
||||
/* As */ scales1 + mb_start,
|
||||
/* Bs */ scales2 + nb_start,
|
||||
/* bias*/ bias + nb_start,
|
||||
/* M */ mb_size,
|
||||
/* N */ nb_size,
|
||||
/* K */ K,
|
||||
/* lda */ K,
|
||||
/* ldb */ nb_size,
|
||||
/* ldc */ N,
|
||||
/* brg */ use_brgemm);
|
||||
});
|
||||
|
||||
if (use_brgemm) {
|
||||
at::native::cpublas::brgemm_release();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// tinygemm interface
|
||||
template <typename scalar_t>
|
||||
void tinygemm_kernel(
|
||||
const uint8_t* __restrict__ A,
|
||||
const int8_t* __restrict__ B,
|
||||
scalar_t* __restrict__ C,
|
||||
int32_t* __restrict__ Ctmp,
|
||||
const float* __restrict__ As,
|
||||
const float* __restrict__ Bs,
|
||||
int64_t M,
|
||||
int64_t N,
|
||||
int64_t K,
|
||||
int64_t lda,
|
||||
int64_t ldb,
|
||||
int64_t ldc,
|
||||
bool brg) {
|
||||
tinygemm_kernel<scalar_t, false>(A, B, C, Ctmp, As, Bs, nullptr, M, N, K, lda, ldb, ldc, brg);
|
||||
}
|
||||
|
||||
#define INSTANTIATE_TINYGEMM_TEMPLATE(TYPE) \
|
||||
template void tinygemm_kernel<TYPE>( \
|
||||
const uint8_t* __restrict__ A, \
|
||||
const int8_t* __restrict__ B, \
|
||||
TYPE* __restrict__ C, \
|
||||
int32_t* __restrict__ Ctmp, \
|
||||
const float* __restrict__ As, \
|
||||
const float* __restrict__ Bs, \
|
||||
int64_t M, \
|
||||
int64_t N, \
|
||||
int64_t K, \
|
||||
int64_t lda, \
|
||||
int64_t ldb, \
|
||||
int64_t ldc, \
|
||||
bool brg)
|
||||
|
||||
INSTANTIATE_TINYGEMM_TEMPLATE(at::BFloat16);
|
||||
INSTANTIATE_TINYGEMM_TEMPLATE(at::Half);
|
||||
|
||||
std::tuple<at::Tensor, at::Tensor> per_token_quant_int8_cpu(at::Tensor& A) {
|
||||
RECORD_FUNCTION("sgl-kernel::per_token_quant_int8_cpu", std::vector<c10::IValue>({A}));
|
||||
|
||||
CHECK_LAST_DIM_CONTIGUOUS_INPUT(A);
|
||||
CHECK_DIM(2, A);
|
||||
|
||||
int64_t M = A.size(0);
|
||||
int64_t K = A.size(1);
|
||||
int64_t lda = A.stride(0);
|
||||
|
||||
const auto st = A.scalar_type();
|
||||
TORCH_CHECK(st == at::kBFloat16 || st == at::kHalf, "per_token_quant_int8: expect A to be bfloat16 or half.");
|
||||
|
||||
auto Aq = at::empty({M, K}, A.options().dtype(at::kByte));
|
||||
auto As = at::empty({M}, A.options().dtype(at::kFloat));
|
||||
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(st, "per_token_quant_int8", [&] {
|
||||
uint8_t* __restrict__ Aq_data = Aq.data_ptr<uint8_t>();
|
||||
float* __restrict__ As_data = As.data_ptr<float>();
|
||||
const scalar_t* __restrict__ A_data = A.data_ptr<scalar_t>();
|
||||
|
||||
at::parallel_for(0, M, 0, [&](int64_t begin, int64_t end) {
|
||||
for (int64_t m = begin; m < end; ++m) {
|
||||
quantize_row_int8<scalar_t>(Aq_data + m * K, As_data[m], A_data + m * lda, K);
|
||||
}
|
||||
});
|
||||
});
|
||||
return std::make_tuple(Aq, As);
|
||||
}
|
||||
|
||||
// weight : static, per-channel, symmetric
|
||||
// activation : dynamic, per-token, symmetric
|
||||
//
|
||||
// mat1 : [M, K]
|
||||
// mat2 : [N, K]
|
||||
// scales1 : [M]
|
||||
// scales2 : [N]
|
||||
// bias : [N]
|
||||
// out : [M, N]
|
||||
//
|
||||
at::Tensor int8_scaled_mm_cpu(
|
||||
at::Tensor& mat1,
|
||||
at::Tensor& mat2,
|
||||
at::Tensor& scales1,
|
||||
at::Tensor& scales2,
|
||||
const std::optional<at::Tensor>& bias,
|
||||
at::ScalarType out_dtype,
|
||||
bool is_vnni) {
|
||||
RECORD_FUNCTION("sgl-kernel::int8_scaled_mm_cpu", std::vector<c10::IValue>({mat1, mat2, scales1, scales2, bias}));
|
||||
|
||||
auto packed_w = is_vnni ? mat2 : convert_weight_packed(mat2);
|
||||
|
||||
CHECK_INPUT(mat1);
|
||||
CHECK_INPUT(mat2);
|
||||
CHECK_INPUT(scales1);
|
||||
CHECK_INPUT(scales2);
|
||||
CHECK_DIM(2, mat1);
|
||||
CHECK_DIM(2, mat2);
|
||||
|
||||
int64_t M = mat1.size(0);
|
||||
int64_t N = mat2.size(0);
|
||||
int64_t K = mat1.size(1);
|
||||
|
||||
// see [NOTE]: s8s8 igemm compensation in avx512-vnni
|
||||
CHECK_EQ(mat2.size(1), (int64_t)(is_vnni ? K + sizeof(int32_t) : K));
|
||||
CHECK_EQ(scales1.numel(), M);
|
||||
CHECK_EQ(scales2.numel(), N);
|
||||
|
||||
TORCH_CHECK(mat1.scalar_type() == at::kByte, "int8_scaled_mm: expect mat1 to be uint8.");
|
||||
TORCH_CHECK(mat2.scalar_type() == at::kChar, "int8_scaled_mm: expect mat2 to be int8.");
|
||||
TORCH_CHECK(
|
||||
scales1.scalar_type() == at::kFloat && scales2.scalar_type() == at::kFloat,
|
||||
"int8_scaled_mm: expect scales to be float32.");
|
||||
|
||||
auto out = at::empty({M, N}, mat1.options().dtype(out_dtype));
|
||||
|
||||
const bool has_bias = bias.has_value();
|
||||
const float* bias_data = nullptr;
|
||||
if (has_bias) {
|
||||
CHECK_EQ(bias.value().size(0), N);
|
||||
bias_data = bias.value().data_ptr<float>();
|
||||
}
|
||||
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(out_dtype, "int8_scaled_mm_kernel_impl", [&] {
|
||||
int8_scaled_mm_kernel_impl<scalar_t>(
|
||||
out.data_ptr<scalar_t>(),
|
||||
mat1.data_ptr<uint8_t>(),
|
||||
packed_w.data_ptr<int8_t>(),
|
||||
scales1.data_ptr<float>(),
|
||||
scales2.data_ptr<float>(),
|
||||
bias_data,
|
||||
M,
|
||||
N,
|
||||
K);
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
// fused `per_token_quant_int8_cpu` and `int8_scaled_mm_cpu`
|
||||
at::Tensor int8_scaled_mm_with_quant(
|
||||
at::Tensor& mat1,
|
||||
at::Tensor& mat2,
|
||||
at::Tensor& scales2,
|
||||
const std::optional<at::Tensor>& bias,
|
||||
at::ScalarType out_dtype,
|
||||
bool is_vnni) {
|
||||
RECORD_FUNCTION("sgl-kernel::int8_scaled_mm_cpu", std::vector<c10::IValue>({mat1, mat2, scales2, bias}));
|
||||
|
||||
auto packed_w = is_vnni ? mat2 : convert_weight_packed(mat2);
|
||||
|
||||
CHECK_LAST_DIM_CONTIGUOUS_INPUT(mat1);
|
||||
CHECK_INPUT(mat2);
|
||||
CHECK_INPUT(scales2);
|
||||
CHECK_DIM(2, mat1);
|
||||
CHECK_DIM(2, mat2);
|
||||
|
||||
int64_t M = mat1.size(0);
|
||||
int64_t N = mat2.size(0);
|
||||
int64_t K = mat1.size(1);
|
||||
int64_t lda = mat1.stride(0);
|
||||
|
||||
// see [NOTE]: s8s8 igemm compensation in avx512-vnni
|
||||
CHECK_EQ(mat2.size(1), (int64_t)(is_vnni ? K + sizeof(int32_t) : K));
|
||||
CHECK_EQ(scales2.numel(), N);
|
||||
|
||||
const auto st = mat1.scalar_type();
|
||||
TORCH_CHECK(st == at::kBFloat16 || st == at::kHalf, "int8_scaled_mm_with_quant: expect A to be bfloat16 or half.");
|
||||
TORCH_CHECK(st == out_dtype, "int8_scaled_mm_with_quant: expect A has same dtype with out_dtype.");
|
||||
TORCH_CHECK(mat2.scalar_type() == at::kChar, "int8_scaled_mm_with_quant: expect mat2 to be int8.");
|
||||
TORCH_CHECK(scales2.scalar_type() == at::kFloat, "int8_scaled_mm_with_quant: expect scales to be float32.");
|
||||
|
||||
const int64_t buffer_size = M * K + M * sizeof(float);
|
||||
auto buffer = at::empty({buffer_size}, mat1.options().dtype(at::kByte));
|
||||
auto out = at::empty({M, N}, mat1.options().dtype(out_dtype));
|
||||
|
||||
const bool has_bias = bias.has_value();
|
||||
const float* bias_data = nullptr;
|
||||
if (has_bias) {
|
||||
CHECK_EQ(bias.value().size(0), N);
|
||||
bias_data = bias.value().data_ptr<float>();
|
||||
}
|
||||
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(out_dtype, "int8_scaled_mm_with_quant_kernel_impl", [&] {
|
||||
uint8_t* __restrict__ Aq_data = buffer.data_ptr<uint8_t>();
|
||||
float* __restrict__ As_data = (float*)((void*)(Aq_data + M * K));
|
||||
const scalar_t* __restrict__ A_data = mat1.data_ptr<scalar_t>();
|
||||
|
||||
at::parallel_for(0, M, 0, [&](int64_t begin, int64_t end) {
|
||||
for (int64_t m = begin; m < end; ++m) {
|
||||
quantize_row_int8<scalar_t>(Aq_data + m * K, As_data[m], A_data + m * lda, K);
|
||||
}
|
||||
});
|
||||
|
||||
int8_scaled_mm_kernel_impl<scalar_t>(
|
||||
out.data_ptr<scalar_t>(),
|
||||
Aq_data,
|
||||
packed_w.data_ptr<int8_t>(),
|
||||
As_data,
|
||||
scales2.data_ptr<float>(),
|
||||
bias_data,
|
||||
M,
|
||||
N,
|
||||
K);
|
||||
});
|
||||
return out;
|
||||
}
|
||||
74
third_party/sglang/sgl-kernel/csrc/cpu/interface.cpp
vendored
Normal file
74
third_party/sglang/sgl-kernel/csrc/cpu/interface.cpp
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
#include <ATen/record_function.h>
|
||||
#include <torch/all.h>
|
||||
|
||||
#include "shm.h"
|
||||
|
||||
// Communication settings
|
||||
static int world_rank = -1;
|
||||
static int world_size = -1;
|
||||
|
||||
static bool is_initialized = false;
|
||||
|
||||
static bool all_ranks_local_p = false;
|
||||
|
||||
void initialize(int64_t size, int64_t rank) {
|
||||
if (is_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check whether all ranks is on the same physical machine.
|
||||
// If true, we will use an SHM based low latency allreduce
|
||||
|
||||
auto ls_string = std::getenv("LOCAL_SIZE");
|
||||
int ls = 0;
|
||||
if (ls_string != NULL) {
|
||||
ls = std::stoi(std::getenv("LOCAL_SIZE"));
|
||||
}
|
||||
|
||||
if (size >= 1 && size == ls) {
|
||||
all_ranks_local_p = true;
|
||||
}
|
||||
|
||||
world_size = size;
|
||||
world_rank = rank;
|
||||
is_initialized = true;
|
||||
|
||||
const char* addr_string = std::getenv("MASTER_ADDR");
|
||||
if (addr_string == NULL) {
|
||||
addr_string = "";
|
||||
}
|
||||
const char* port_string = std::getenv("MASTER_PORT");
|
||||
if (port_string == NULL) {
|
||||
port_string = "";
|
||||
}
|
||||
|
||||
if (all_ranks_local_p) {
|
||||
shm_initialize(size, rank, addr_string, port_string);
|
||||
}
|
||||
}
|
||||
|
||||
void shm_allreduce(torch::Tensor& data, int64_t op) {
|
||||
RECORD_FUNCTION("sgl-kernel::shm_allreduce", std::vector<c10::IValue>({data}));
|
||||
|
||||
TORCH_CHECK(op == c10d::ReduceOp::SUM, "Only torch.distributed.ReduceOp.SUM is supported");
|
||||
|
||||
auto numel = data.numel();
|
||||
int data_size = numel * data.element_size();
|
||||
all_reduce_outer_loop(data, numel, data_size);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
torch::Tensor shm_allgather(torch::Tensor& data, int64_t dim) {
|
||||
RECORD_FUNCTION("sgl-kernel::shm_allgather", std::vector<c10::IValue>({data}));
|
||||
|
||||
auto numel = data.numel();
|
||||
int data_size = numel * data.element_size();
|
||||
if (dim < 0) {
|
||||
dim += data.dim();
|
||||
}
|
||||
std::vector<int64_t> result_shape = data.sizes().vec();
|
||||
result_shape[dim] *= world_size;
|
||||
torch::Tensor result_tensor = torch::empty(result_shape, data.options());
|
||||
return all_gather(result_tensor, data, dim, numel, data_size);
|
||||
}
|
||||
708
third_party/sglang/sgl-kernel/csrc/cpu/mamba/conv.cpp
vendored
Normal file
708
third_party/sglang/sgl-kernel/csrc/cpu/mamba/conv.cpp
vendored
Normal file
@@ -0,0 +1,708 @@
|
||||
#include "common.h"
|
||||
#include "gemm.h"
|
||||
#include "vec.h"
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename scalar_t>
|
||||
inline void copy_stub(scalar_t* __restrict__ y, const scalar_t* __restrict__ x, int64_t size) {
|
||||
using Vec = at::vec::Vectorized<scalar_t>;
|
||||
const bool is_padding = (x == nullptr);
|
||||
for (int64_t d = 0; d < size; d += Vec::size()) {
|
||||
Vec data_vec = is_padding ? Vec(0.f) : Vec::loadu(x + d);
|
||||
data_vec.store(y + d);
|
||||
}
|
||||
}
|
||||
|
||||
// no remainder
|
||||
template <typename scalar_t>
|
||||
void inline update_conv_state(
|
||||
scalar_t* __restrict__ conv_states,
|
||||
const scalar_t* __restrict__ input,
|
||||
int64_t width,
|
||||
int64_t dim,
|
||||
int64_t seqlen,
|
||||
bool has_initial_states) {
|
||||
// width for `conv_states`
|
||||
int64_t width1 = width - 1;
|
||||
int64_t w = 0;
|
||||
for (; w < width1 - seqlen; ++w) {
|
||||
scalar_t* y = conv_states + w * dim;
|
||||
const scalar_t* x = has_initial_states ? conv_states + (w + seqlen) * dim : nullptr;
|
||||
copy_stub(y, x, dim);
|
||||
}
|
||||
for (; w < width1; ++w) {
|
||||
scalar_t* y = conv_states + w * dim;
|
||||
const scalar_t* x = input + (w + seqlen - width1) * dim;
|
||||
copy_stub(y, x, dim);
|
||||
}
|
||||
}
|
||||
|
||||
// A : [M, BLOCK_N]
|
||||
// B : [BLOCK_N, K], prepacked as [K/2, BLOCK_N, 2]
|
||||
// C : [M, BLOCK_N]
|
||||
// bias : [BLOCK_N]
|
||||
//
|
||||
// lda : leading dimension of `input` and `out`
|
||||
//
|
||||
template <typename scalar_t, int K, int BLOCK_N, bool has_bias, bool has_silu>
|
||||
struct tinygemm_kernel {
|
||||
static inline void apply(
|
||||
const scalar_t* __restrict__ A,
|
||||
const scalar_t* __restrict__ B,
|
||||
scalar_t* __restrict__ C,
|
||||
const scalar_t* __restrict__ bias,
|
||||
const scalar_t* __restrict__ conv_states,
|
||||
bool has_initial_state,
|
||||
int64_t M,
|
||||
int64_t lda,
|
||||
bool is_first_token) {
|
||||
TORCH_CHECK(false, "tinygemm_kernel_nn: scalar path not implemented!");
|
||||
}
|
||||
};
|
||||
|
||||
#if defined(CPU_CAPABILITY_AVX512)
|
||||
template <int K, int BLOCK_N, bool has_bias, bool has_silu>
|
||||
struct tinygemm_kernel<at::BFloat16, K, BLOCK_N, has_bias, has_silu> {
|
||||
static inline void apply(
|
||||
const at::BFloat16* __restrict__ A,
|
||||
const at::BFloat16* __restrict__ B,
|
||||
at::BFloat16* __restrict__ C,
|
||||
const at::BFloat16* __restrict__ bias,
|
||||
const at::BFloat16* __restrict__ conv_states,
|
||||
bool has_initial_state,
|
||||
int64_t M,
|
||||
int64_t lda,
|
||||
bool is_first_token) {
|
||||
assert(K == 4);
|
||||
constexpr int ROWS = K;
|
||||
constexpr int COLS = BLOCK_N / block_size_n();
|
||||
|
||||
// leading dimension size for b for next block [K/2, 32, 2]
|
||||
constexpr int ldb = block_size_n() * K;
|
||||
|
||||
__m512bh va[ROWS * COLS];
|
||||
__m512bh vb[ROWS * COLS];
|
||||
__m512 vc[COLS * 2];
|
||||
|
||||
// k: {-3, -2, -1} -> {0, 1, 2}
|
||||
auto set_conv_states = [&](int k, int col) -> __m512i {
|
||||
return has_initial_state ? _mm512_loadu_si512(conv_states + (k + K - 1) * lda + col * 32)
|
||||
: _mm512_setzero_si512();
|
||||
};
|
||||
|
||||
#define MM512_LOAD_A(idx) \
|
||||
((idx) < 0 && is_first_token) ? (__m512bh)(set_conv_states((idx), col)) \
|
||||
: (__m512bh)(_mm512_loadu_si512(A + (idx) * lda + col * 32))
|
||||
|
||||
#define MM512_PACK_A(ap, bp, a, b) \
|
||||
do { \
|
||||
__m512i r0 = (__m512i)(a); \
|
||||
__m512i r1 = (__m512i)(b); \
|
||||
__m512i d0 = _mm512_unpacklo_epi16(r0, r1); \
|
||||
__m512i d1 = _mm512_unpackhi_epi16(r0, r1); \
|
||||
r0 = _mm512_shuffle_i32x4(d0, d1, 0x88); \
|
||||
r1 = _mm512_shuffle_i32x4(d0, d1, 0xdd); \
|
||||
(ap) = (__m512bh)_mm512_shuffle_i32x4(r0, r1, 0x88); \
|
||||
(bp) = (__m512bh)_mm512_shuffle_i32x4(r0, r1, 0xdd); \
|
||||
} while (0)
|
||||
|
||||
// step 0 : preload a at time step [-3][-2][-1]
|
||||
auto preloada = [&](auto i) {
|
||||
constexpr int col = i;
|
||||
int64_t m = 0;
|
||||
va[1 * COLS + col] = MM512_LOAD_A(m - 3);
|
||||
va[2 * COLS + col] = MM512_LOAD_A(m - 2);
|
||||
va[3 * COLS + col] = MM512_LOAD_A(m - 1);
|
||||
};
|
||||
Unroll<COLS>{}(preloada);
|
||||
|
||||
auto loada = [&](auto i, int64_t m) {
|
||||
constexpr int col = i;
|
||||
// update previous time step
|
||||
va[0 * COLS + col] = va[1 * COLS + col];
|
||||
va[1 * COLS + col] = va[2 * COLS + col];
|
||||
va[2 * COLS + col] = va[3 * COLS + col];
|
||||
// load current time step
|
||||
va[3 * COLS + col] = MM512_LOAD_A(m);
|
||||
};
|
||||
|
||||
// step 1 : load weight for just once
|
||||
auto loadb = [&](auto i) {
|
||||
constexpr int row = i / COLS;
|
||||
constexpr int col = i % COLS;
|
||||
vb[row * COLS + col] = (__m512bh)(_mm512_loadu_si512(B + col * ldb + row * 32));
|
||||
};
|
||||
Unroll<ROWS * COLS>{}(loadb);
|
||||
|
||||
// [NB] accumulates 4x32 bfloat16 blocks
|
||||
//
|
||||
// +------------+------------+
|
||||
// | col0 | col1 |
|
||||
// +------------+------------+
|
||||
// | va0 va1 | va0 va1 |
|
||||
// | va2 va3 | va2 va3 |
|
||||
// +------------+------------+
|
||||
// | vc0 vc1 | vc0 vc1 |
|
||||
// +------------+------------+
|
||||
//
|
||||
// * va and vb shares the same memory layout
|
||||
// * block_n 32 with 4 rows equals to 4 registers
|
||||
// * 37 uops with avx512bf16 v.s. 57 uops with avx512f
|
||||
//
|
||||
auto compute = [&](auto i) {
|
||||
constexpr int col = i;
|
||||
|
||||
// init accumulators
|
||||
if constexpr (has_bias) {
|
||||
__m512i b16 = _mm512_loadu_si512(reinterpret_cast<const __m512i*>(bias + col * 32));
|
||||
vc[col * 2 + 0] = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32(b16, 0));
|
||||
vc[col * 2 + 1] = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32(b16, 1));
|
||||
} else {
|
||||
vc[col * 2 + 0] = _mm512_set1_ps(0.f);
|
||||
vc[col * 2 + 1] = _mm512_set1_ps(0.f);
|
||||
}
|
||||
|
||||
// convert to vnni2 format
|
||||
__m512bh va0, va1, va2, va3;
|
||||
MM512_PACK_A(va0, va1, va[0 * COLS + col], va[1 * COLS + col]);
|
||||
MM512_PACK_A(va2, va3, va[2 * COLS + col], va[3 * COLS + col]);
|
||||
|
||||
// accumulate
|
||||
vc[col * 2 + 0] = _mm512_dpbf16_ps(vc[col * 2 + 0], va0, vb[0 * COLS + col]);
|
||||
vc[col * 2 + 0] = _mm512_dpbf16_ps(vc[col * 2 + 0], va2, vb[2 * COLS + col]);
|
||||
vc[col * 2 + 1] = _mm512_dpbf16_ps(vc[col * 2 + 1], va1, vb[1 * COLS + col]);
|
||||
vc[col * 2 + 1] = _mm512_dpbf16_ps(vc[col * 2 + 1], va3, vb[3 * COLS + col]);
|
||||
};
|
||||
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
using bVec = at::vec::Vectorized<at::BFloat16>;
|
||||
const fVec one = fVec(1.f);
|
||||
auto storec = [&](auto i, int64_t m) {
|
||||
constexpr int col = i;
|
||||
fVec x0 = fVec(vc[col * 2 + 0]);
|
||||
fVec x1 = fVec(vc[col * 2 + 1]);
|
||||
if constexpr (has_silu) {
|
||||
x0 = x0 / (one + x0.neg().exp_u20());
|
||||
x1 = x1 / (one + x1.neg().exp_u20());
|
||||
}
|
||||
bVec out_vec = convert_from_float_ext<at::BFloat16>(x0, x1);
|
||||
out_vec.store(C + m * lda + col * 32);
|
||||
};
|
||||
|
||||
for (int64_t m = 0; m < M; ++m) {
|
||||
// step 3.a : load a at current time step
|
||||
Unroll<COLS>{}(loada, m);
|
||||
// step 3.b : accumulate for window size (4)
|
||||
Unroll<COLS>{}(compute);
|
||||
// step 3.c : store c at current time step
|
||||
Unroll<COLS>{}(storec, m);
|
||||
}
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
#define LAUNCH_TINYGEMM_KERNEL(K, NB_SIZE) \
|
||||
tinygemm_kernel<scalar_t, K, NB_SIZE, has_bias, has_silu>::apply( \
|
||||
input + bs * seqlen * dim + mb_start * dim + nb_start, \
|
||||
weight + nb_start * width, \
|
||||
out + bs * seqlen * dim + mb_start * dim + nb_start, \
|
||||
has_bias ? bias + nb_start : nullptr, \
|
||||
has_conv_states ? conv_states + conv_state_index * (K - 1) * dim + nb_start : nullptr, \
|
||||
has_initial_states_value, \
|
||||
mb_size, \
|
||||
dim, \
|
||||
mb_start == 0);
|
||||
|
||||
template <typename scalar_t>
|
||||
void causal_conv1d_fwd_kernel_impl(
|
||||
scalar_t* __restrict__ out,
|
||||
const scalar_t* __restrict__ input,
|
||||
const scalar_t* __restrict__ weight,
|
||||
const scalar_t* __restrict__ bias,
|
||||
scalar_t* __restrict__ conv_states,
|
||||
const int32_t* __restrict__ conv_indices,
|
||||
const bool* __restrict__ has_initial_state,
|
||||
bool silu_activation,
|
||||
int64_t batch,
|
||||
int64_t dim,
|
||||
int64_t seqlen,
|
||||
int64_t width,
|
||||
int64_t num_seq_blocks) {
|
||||
// handle 32 x 64 per block
|
||||
constexpr int64_t BLOCK_M = block_size_m();
|
||||
constexpr int64_t BLOCK_N = block_size_n() * 2;
|
||||
const int64_t NB = div_up(dim, BLOCK_N);
|
||||
|
||||
const int64_t num_blocks_per_seq = div_up(seqlen, BLOCK_M);
|
||||
const bool has_conv_states = conv_states != nullptr;
|
||||
const bool has_conv_indices = conv_indices != nullptr;
|
||||
|
||||
// parallel on [batch, seq, NB]
|
||||
AT_DISPATCH_BOOL2(bias != nullptr, has_bias, silu_activation, has_silu, [&] {
|
||||
at::parallel_for(0, num_seq_blocks * NB, 0, [&](int64_t begin, int64_t end) {
|
||||
int64_t mb{0}, nb{0};
|
||||
data_index_init(begin, mb, num_seq_blocks, nb, NB);
|
||||
|
||||
for (int64_t i = begin; i < end; ++i) {
|
||||
int64_t bs = mb / num_blocks_per_seq;
|
||||
|
||||
int64_t mb_start = (mb % num_blocks_per_seq) * BLOCK_M;
|
||||
int64_t mb_size = std::min(seqlen - mb_start, BLOCK_M);
|
||||
int64_t nb_start = nb * BLOCK_N;
|
||||
int64_t nb_size = std::min(dim - nb_start, BLOCK_N);
|
||||
|
||||
const bool has_initial_states_value = has_conv_states ? has_initial_state[bs] : false;
|
||||
int32_t conv_state_index = has_conv_indices ? conv_indices[bs] : bs;
|
||||
|
||||
switch (width << 4 | nb_size >> 4) {
|
||||
case 0x42:
|
||||
LAUNCH_TINYGEMM_KERNEL(4, 32);
|
||||
break;
|
||||
case 0x44:
|
||||
LAUNCH_TINYGEMM_KERNEL(4, 64);
|
||||
break;
|
||||
default:
|
||||
TORCH_CHECK(false, "Unexpected block size, ", width, " x ", nb_size);
|
||||
}
|
||||
|
||||
// move to the next index
|
||||
data_index_step(mb, num_seq_blocks, nb, NB);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// update conv_states if necessary
|
||||
if (has_conv_states) {
|
||||
at::parallel_for(0, batch, 0, [&](int64_t begin, int64_t end) {
|
||||
for (int64_t bs = begin; bs < end; ++bs) {
|
||||
update_conv_state(
|
||||
conv_states + bs * (width - 1) * dim, input + bs * seqlen * dim, width, dim, seqlen, has_initial_state[bs]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#define LAUNCH_TINYGEMM_VARLEN_KERNEL(K, NB_SIZE) \
|
||||
tinygemm_kernel<scalar_t, K, NB_SIZE, has_bias, has_silu>::apply( \
|
||||
input + batch_offset * dim + mb_start * dim + nb_start, \
|
||||
weight + nb_start * width, \
|
||||
out + batch_offset * dim + mb_start * dim + nb_start, \
|
||||
has_bias ? bias + nb_start : nullptr, \
|
||||
nullptr, \
|
||||
false, \
|
||||
mb_size, \
|
||||
dim, \
|
||||
mb_start == 0);
|
||||
|
||||
// TODO: add `has_initial_state` support for varlen kernel
|
||||
template <typename scalar_t>
|
||||
void causal_conv1d_fwd_varlen_kernel_impl(
|
||||
scalar_t* __restrict__ out,
|
||||
const scalar_t* __restrict__ input,
|
||||
const scalar_t* __restrict__ weight,
|
||||
const scalar_t* __restrict__ bias,
|
||||
scalar_t* __restrict__ conv_states,
|
||||
const int32_t* __restrict__ query_start_loc,
|
||||
const int32_t* __restrict__ conv_indices,
|
||||
const bool* __restrict__ has_initial_state,
|
||||
const int32_t* __restrict__ block_indices,
|
||||
bool silu_activation,
|
||||
int64_t batch,
|
||||
int64_t dim,
|
||||
int64_t width,
|
||||
int64_t num_seq_blocks) {
|
||||
// handle 32 x 64 per block
|
||||
constexpr int64_t BLOCK_M = block_size_m();
|
||||
constexpr int64_t BLOCK_N = block_size_n() * 2;
|
||||
const int64_t NB = div_up(dim, BLOCK_N);
|
||||
|
||||
const bool has_conv_states = conv_states != nullptr;
|
||||
const bool has_conv_indices = conv_indices != nullptr;
|
||||
|
||||
// parallel on [batch, seq, NB]
|
||||
AT_DISPATCH_BOOL2(bias != nullptr, has_bias, silu_activation, has_silu, [&] {
|
||||
at::parallel_for(0, num_seq_blocks * NB, 0, [&](int64_t begin, int64_t end) {
|
||||
int64_t mb{0}, nb{0};
|
||||
data_index_init(begin, mb, num_seq_blocks, nb, NB);
|
||||
|
||||
for (int64_t i = begin; i < end; ++i) {
|
||||
int32_t bs = block_indices[mb * 2 + 0];
|
||||
int32_t batch_offset = query_start_loc[bs];
|
||||
int32_t seqlen = query_start_loc[bs + 1] - query_start_loc[bs];
|
||||
|
||||
int64_t mb_start = block_indices[mb * 2 + 1] * BLOCK_M;
|
||||
int64_t mb_size = std::min(seqlen - mb_start, BLOCK_M);
|
||||
int64_t nb_start = nb * BLOCK_N;
|
||||
int64_t nb_size = std::min(dim - nb_start, BLOCK_N);
|
||||
|
||||
switch (width << 4 | nb_size >> 4) {
|
||||
case 0x42:
|
||||
LAUNCH_TINYGEMM_VARLEN_KERNEL(4, 32);
|
||||
break;
|
||||
case 0x44:
|
||||
LAUNCH_TINYGEMM_VARLEN_KERNEL(4, 64);
|
||||
break;
|
||||
default:
|
||||
TORCH_CHECK(false, "Unexpected block size, ", width, " x ", nb_size);
|
||||
}
|
||||
|
||||
// move to the next index
|
||||
data_index_step(mb, num_seq_blocks, nb, NB);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// update conv_states if necessary
|
||||
if (has_conv_states) {
|
||||
at::parallel_for(0, batch, 0, [&](int64_t begin, int64_t end) {
|
||||
for (int64_t bs = begin; bs < end; ++bs) {
|
||||
int32_t conv_state_index = has_conv_indices ? conv_indices[bs] : bs;
|
||||
int32_t seqlen = query_start_loc[bs + 1] - query_start_loc[bs];
|
||||
int32_t batch_offset = query_start_loc[bs];
|
||||
update_conv_state(
|
||||
conv_states + conv_state_index * (width - 1) * dim,
|
||||
input + batch_offset * dim,
|
||||
width,
|
||||
dim,
|
||||
seqlen,
|
||||
/* has_initial_state */ false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
void causal_conv1d_update_kernel_impl(
|
||||
scalar_t* __restrict__ out,
|
||||
const scalar_t* __restrict__ input,
|
||||
scalar_t* __restrict__ conv_states,
|
||||
const scalar_t* __restrict__ weight,
|
||||
const scalar_t* __restrict__ bias,
|
||||
const int32_t* __restrict__ conv_indices,
|
||||
bool silu_activation,
|
||||
int64_t batch,
|
||||
int64_t dim,
|
||||
int64_t seqlen,
|
||||
int64_t width) {
|
||||
// handle 32 x 64 per block
|
||||
constexpr int64_t BLOCK_M = block_size_m();
|
||||
constexpr int64_t BLOCK_N = block_size_n() * 2;
|
||||
const int64_t NB = div_up(dim, BLOCK_N);
|
||||
|
||||
const bool has_conv_states = conv_states != nullptr;
|
||||
const bool has_conv_indices = conv_indices != nullptr;
|
||||
|
||||
// parallel on [batch, NB]
|
||||
AT_DISPATCH_BOOL2(bias != nullptr, has_bias, silu_activation, has_silu, [&] {
|
||||
at::parallel_for(0, batch * NB, 0, [&](int64_t begin, int64_t end) {
|
||||
int64_t bs{0}, nb{0};
|
||||
data_index_init(begin, bs, batch, nb, NB);
|
||||
|
||||
for (int64_t i = begin; i < end; ++i) {
|
||||
int64_t mb_start = 0;
|
||||
int64_t mb_size = 1;
|
||||
int64_t nb_start = nb * BLOCK_N;
|
||||
int64_t nb_size = std::min(dim - nb_start, BLOCK_N);
|
||||
|
||||
const bool has_initial_states_value = true;
|
||||
int32_t conv_state_index = has_conv_indices ? conv_indices[bs] : bs;
|
||||
|
||||
switch (width << 4 | nb_size >> 4) {
|
||||
case 0x42:
|
||||
LAUNCH_TINYGEMM_KERNEL(4, 32);
|
||||
break;
|
||||
case 0x44:
|
||||
LAUNCH_TINYGEMM_KERNEL(4, 64);
|
||||
break;
|
||||
default:
|
||||
TORCH_CHECK(false, "Unexpected block size, ", width, " x ", nb_size);
|
||||
}
|
||||
|
||||
// move to the next index
|
||||
data_index_step(bs, batch, nb, NB);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
#define CONV_STATE_INDEXR(w) conv_states + conv_state_index*(width - 1) * dim + (w) * dim
|
||||
|
||||
// update conv_states
|
||||
at::parallel_for(0, batch, 0, [&](int64_t begin, int64_t end) {
|
||||
for (int64_t bs = begin; bs < end; ++bs) {
|
||||
// update old states, range [1, width - 1)
|
||||
int32_t conv_state_index = has_conv_indices ? conv_indices[bs] : bs;
|
||||
for (int64_t w = 1; w < width - 1; ++w) {
|
||||
std::memcpy(CONV_STATE_INDEXR(w - 1), CONV_STATE_INDEXR(w), dim * sizeof(scalar_t));
|
||||
}
|
||||
// copy new states
|
||||
std::memcpy(CONV_STATE_INDEXR(width - 2), input + bs * dim, dim * sizeof(scalar_t));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// from [dim, width] or [N, K]
|
||||
// to [N/BLOCK_N, K/2, BLOCK_N, 2]
|
||||
at::Tensor causal_conv1d_weight_pack(const at::Tensor& weight) {
|
||||
CHECK_INPUT(weight);
|
||||
|
||||
int64_t dim = weight.size(0);
|
||||
int64_t width = weight.size(1);
|
||||
constexpr int64_t BLOCK_N = block_size_n();
|
||||
TORCH_CHECK(width == 4, "causal_conv1d_weight_pack: support only width of 4");
|
||||
TORCH_CHECK(dim % BLOCK_N == 0, "causal_conv1d_weight_pack: invalid dim size ", dim);
|
||||
|
||||
const int64_t N = dim, K2 = width >> 1;
|
||||
const int64_t NB = div_up(N, BLOCK_N);
|
||||
|
||||
auto packed_weight = at::empty_like(weight);
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(weight.scalar_type(), "causal_conv1d_fwd_kernel_impl", [&] {
|
||||
// cast to float32 as vnni size is 2
|
||||
const float* w_data = reinterpret_cast<float*>(weight.data_ptr<scalar_t>());
|
||||
float* packed_data = reinterpret_cast<float*>(packed_weight.data_ptr<scalar_t>());
|
||||
|
||||
at::parallel_for(0, NB * K2 * BLOCK_N, 0, [&](int64_t begin, int64_t end) {
|
||||
int64_t nb{0}, k2{0}, n{0};
|
||||
data_index_init(begin, nb, NB, k2, K2, n, BLOCK_N);
|
||||
|
||||
// TODO: optimize this if we need to online prepacking.
|
||||
for (int64_t i = begin; i < end; ++i) {
|
||||
packed_data[i] = w_data[nb * BLOCK_N * K2 + n * K2 + k2];
|
||||
|
||||
// move to the next index
|
||||
data_index_step(nb, NB, k2, K2, n, BLOCK_N);
|
||||
}
|
||||
});
|
||||
});
|
||||
return packed_weight;
|
||||
}
|
||||
|
||||
#define CHECK_OPTIONAL_SHAPE_DTYPE(OPT, SIZE, DTYPE) \
|
||||
if (OPT.has_value()) { \
|
||||
const auto tensor = OPT.value(); \
|
||||
CHECK_CONTIGUOUS(tensor); \
|
||||
CHECK_EQ(tensor.size(0), SIZE); \
|
||||
CHECK_EQ(tensor.scalar_type(), DTYPE); \
|
||||
}
|
||||
|
||||
template <int BLOCK_M>
|
||||
int64_t get_block_count(const std::optional<at::Tensor>& offsets, int64_t batch, int64_t seqlen) {
|
||||
if (offsets.has_value()) {
|
||||
const int32_t* offsets_data = offsets.value().data_ptr<int32_t>();
|
||||
int32_t num_seq_blocks = 0;
|
||||
for (int64_t row = 0; row < batch; ++row) {
|
||||
num_seq_blocks += div_up(offsets_data[row + 1] - offsets_data[row], BLOCK_M);
|
||||
}
|
||||
return num_seq_blocks;
|
||||
}
|
||||
return batch * div_up(seqlen, int64_t(BLOCK_M));
|
||||
}
|
||||
|
||||
template <int BLOCK_M>
|
||||
at::Tensor get_block_indices(const std::optional<at::Tensor>& offsets, int64_t num_seq_blocks) {
|
||||
if (!offsets.has_value()) {
|
||||
return at::Tensor();
|
||||
}
|
||||
|
||||
const at::Tensor& offsets_ = offsets.value();
|
||||
at::Tensor indices = at::empty({num_seq_blocks, 2}, offsets_.options());
|
||||
|
||||
int64_t batch = offsets_.size(0) - 1;
|
||||
|
||||
const int32_t* offsets_data = offsets_.data_ptr<int32_t>();
|
||||
int32_t* indices_data = indices.data_ptr<int32_t>();
|
||||
|
||||
int64_t idx = 0;
|
||||
for (int32_t row = 0; row < batch; ++row) {
|
||||
int32_t blocks = div_up(offsets_data[row + 1] - offsets_data[row], BLOCK_M);
|
||||
|
||||
for (int32_t col = 0; col < blocks; ++col) {
|
||||
indices_data[idx * 2 + 0] = row;
|
||||
indices_data[idx * 2 + 1] = col;
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
return indices;
|
||||
}
|
||||
|
||||
// API aligned with GPUs
|
||||
//
|
||||
// x: (batch, dim, seqlen) or (dim, cu_seq_len) for varlen
|
||||
// weight: (dim, width)
|
||||
// bias: (dim,)
|
||||
// query_start_loc: (batch + 1) int32
|
||||
// cache_indices: (batch) int32
|
||||
// has_initial_state: (batch) bool
|
||||
// conv_states: (..., dim, width - 1) itype
|
||||
// activation: either None or "silu" or "swish"
|
||||
// pad_slot_id: int
|
||||
//
|
||||
at::Tensor causal_conv1d_fwd_cpu(
|
||||
const at::Tensor& x,
|
||||
const at::Tensor& weight,
|
||||
const std::optional<at::Tensor>& bias,
|
||||
const std::optional<at::Tensor>& conv_states,
|
||||
const std::optional<at::Tensor>& query_start_loc,
|
||||
const std::optional<at::Tensor>& conv_state_indices,
|
||||
const std::optional<at::Tensor>& has_initial_state,
|
||||
bool silu_activation,
|
||||
int64_t pad_slot_id,
|
||||
bool is_vnni) {
|
||||
RECORD_FUNCTION("sgl-kernel::causal_conv1d_fwd_cpu", std::vector<c10::IValue>({x, weight, bias}));
|
||||
|
||||
CHECK_CONTIGUOUS(weight);
|
||||
auto packed_w = is_vnni ? weight : causal_conv1d_weight_pack(weight);
|
||||
|
||||
const bool is_var_seqlen = query_start_loc.has_value();
|
||||
const int64_t input_ndim = is_var_seqlen ? 2 : 3;
|
||||
TORCH_CHECK(x.dim() == input_ndim, "causal_conv1d_fwd_cpu: expect x to be ", input_ndim, "D tensor.");
|
||||
TORCH_CHECK(x.stride(-2) == 1 && x.stride(-1) == x.size(-2), "causal_conv1d_fwd_cpu: expect x to be transposed.");
|
||||
|
||||
const int64_t batch = is_var_seqlen ? query_start_loc.value().size(0) - 1 : x.size(0);
|
||||
const int64_t dim = x.size(-2);
|
||||
const int64_t seqlen = x.size(-1);
|
||||
const int64_t width = weight.size(-1);
|
||||
|
||||
const auto scalar_type = x.scalar_type();
|
||||
CHECK_EQ(weight.scalar_type(), scalar_type);
|
||||
CHECK_OPTIONAL_SHAPE_DTYPE(bias, dim, scalar_type);
|
||||
CHECK_OPTIONAL_SHAPE_DTYPE(query_start_loc, batch + 1, at::kInt);
|
||||
CHECK_OPTIONAL_SHAPE_DTYPE(conv_state_indices, batch, at::kInt);
|
||||
CHECK_OPTIONAL_SHAPE_DTYPE(has_initial_state, batch, at::kBool);
|
||||
|
||||
if (conv_states.has_value()) {
|
||||
auto& conv_states_val = conv_states.value();
|
||||
int64_t padded_batch = conv_states_val.size(0);
|
||||
CHECK_EQ(conv_states_val.scalar_type(), scalar_type);
|
||||
CHECK_GE(padded_batch, batch);
|
||||
CHECK_EQ(conv_states_val.size(1), dim);
|
||||
CHECK_EQ(conv_states_val.size(2), width - 1);
|
||||
|
||||
// adjust `conv_states` to be contiguous on `dim`
|
||||
// should happen only once
|
||||
if (conv_states_val.stride(-2) != 1) {
|
||||
auto conv_states_copy = conv_states_val.clone();
|
||||
conv_states_val.as_strided_({padded_batch, dim, width - 1}, {(width - 1) * dim, 1, dim});
|
||||
conv_states_val.copy_(conv_states_copy);
|
||||
}
|
||||
}
|
||||
|
||||
// block size for sequence blocks, 32
|
||||
constexpr int64_t BLOCK_M = block_size_m();
|
||||
|
||||
// total number of sequence blocks
|
||||
int64_t num_seq_blocks = get_block_count<BLOCK_M>(query_start_loc, batch, seqlen);
|
||||
|
||||
at::Tensor out = at::empty_like(x);
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(scalar_type, "causal_conv1d_fwd_kernel_impl", [&] {
|
||||
if (is_var_seqlen) {
|
||||
// record seq blocks in Coordinate format, aka [num_seq_blocks, 2]
|
||||
at::Tensor block_indices = get_block_indices<BLOCK_M>(query_start_loc, num_seq_blocks);
|
||||
|
||||
causal_conv1d_fwd_varlen_kernel_impl(
|
||||
out.data_ptr<scalar_t>(),
|
||||
x.data_ptr<scalar_t>(),
|
||||
packed_w.data_ptr<scalar_t>(),
|
||||
conditional_data_ptr<scalar_t>(bias),
|
||||
conditional_data_ptr<scalar_t>(conv_states),
|
||||
conditional_data_ptr<int32_t>(query_start_loc),
|
||||
conditional_data_ptr<int32_t>(conv_state_indices),
|
||||
conditional_data_ptr<bool>(has_initial_state),
|
||||
block_indices.data_ptr<int32_t>(),
|
||||
silu_activation,
|
||||
batch,
|
||||
dim,
|
||||
width,
|
||||
num_seq_blocks);
|
||||
} else {
|
||||
causal_conv1d_fwd_kernel_impl<scalar_t>(
|
||||
out.data_ptr<scalar_t>(),
|
||||
x.data_ptr<scalar_t>(),
|
||||
packed_w.data_ptr<scalar_t>(),
|
||||
conditional_data_ptr<scalar_t>(bias),
|
||||
conditional_data_ptr<scalar_t>(conv_states),
|
||||
conditional_data_ptr<int32_t>(conv_state_indices),
|
||||
conditional_data_ptr<bool>(has_initial_state),
|
||||
silu_activation,
|
||||
batch,
|
||||
dim,
|
||||
seqlen,
|
||||
width,
|
||||
num_seq_blocks);
|
||||
}
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
// API aligned with GPUs
|
||||
//
|
||||
// x: (batch, dim) or (batch, dim, seqlen)
|
||||
// conv_state: (..., dim, state_len), where state_len >= width - 1
|
||||
// weight: (dim, width)
|
||||
// bias: (dim,)
|
||||
// cache_seqlens: (batch,), dtype int32.
|
||||
// conv_state_indices: (batch,), dtype int32
|
||||
// pad_slot_id: int
|
||||
// out: (batch, dim) or (batch, dim, seqlen)
|
||||
//
|
||||
at::Tensor causal_conv1d_update_cpu(
|
||||
const at::Tensor& x,
|
||||
const at::Tensor& conv_states,
|
||||
const at::Tensor& weight,
|
||||
const std::optional<at::Tensor>& bias,
|
||||
bool silu_activation,
|
||||
const std::optional<at::Tensor>& cache_seqlens,
|
||||
const std::optional<at::Tensor>& conv_state_indices,
|
||||
int64_t pad_slot_id,
|
||||
bool is_vnni) {
|
||||
RECORD_FUNCTION("sgl-kernel::causal_conv1d_update_cpu", std::vector<c10::IValue>({x, weight, bias}));
|
||||
|
||||
CHECK_CONTIGUOUS(x);
|
||||
CHECK_CONTIGUOUS(weight);
|
||||
auto packed_w = is_vnni ? weight : causal_conv1d_weight_pack(weight);
|
||||
|
||||
// TODO: add multi-token prediction support
|
||||
TORCH_CHECK(x.dim() == 2, "causal_conv1d_update_cpu: expect x to be 2D tensor.");
|
||||
TORCH_CHECK(!cache_seqlens.has_value(), "causal_conv1d_update_cpu: don't support cache_seqlens.");
|
||||
|
||||
int64_t batch = x.size(0);
|
||||
int64_t dim = x.size(1);
|
||||
int64_t seqlen = 1;
|
||||
int64_t width = weight.size(-1);
|
||||
|
||||
const auto scalar_type = x.scalar_type();
|
||||
CHECK_EQ(weight.scalar_type(), scalar_type);
|
||||
CHECK_OPTIONAL_SHAPE_DTYPE(bias, dim, scalar_type);
|
||||
CHECK_OPTIONAL_SHAPE_DTYPE(conv_state_indices, batch, at::kInt);
|
||||
|
||||
CHECK_EQ(conv_states.scalar_type(), scalar_type);
|
||||
CHECK_EQ(conv_states.size(1), dim);
|
||||
CHECK_EQ(conv_states.size(2), width - 1);
|
||||
|
||||
// adjust `conv_states` to be contiguous on `dim`
|
||||
if (conv_states.stride(-2) != 1) {
|
||||
int64_t num_cache_lines = conv_states.size(0);
|
||||
auto conv_states_copy = conv_states.clone();
|
||||
conv_states.as_strided_({num_cache_lines, dim, width - 1}, {(width - 1) * dim, 1, dim});
|
||||
conv_states.copy_(conv_states_copy);
|
||||
}
|
||||
|
||||
at::Tensor out = at::empty_like(x);
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(scalar_type, "causal_conv1d_update_kernel_impl", [&] {
|
||||
causal_conv1d_update_kernel_impl<scalar_t>(
|
||||
out.data_ptr<scalar_t>(),
|
||||
x.data_ptr<scalar_t>(),
|
||||
conv_states.data_ptr<scalar_t>(),
|
||||
packed_w.data_ptr<scalar_t>(),
|
||||
conditional_data_ptr<scalar_t>(bias),
|
||||
conditional_data_ptr<int32_t>(conv_state_indices),
|
||||
silu_activation,
|
||||
batch,
|
||||
dim,
|
||||
seqlen,
|
||||
width);
|
||||
});
|
||||
return out;
|
||||
}
|
||||
1341
third_party/sglang/sgl-kernel/csrc/cpu/mamba/fla.cpp
vendored
Normal file
1341
third_party/sglang/sgl-kernel/csrc/cpu/mamba/fla.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
115
third_party/sglang/sgl-kernel/csrc/cpu/model/qwen3.cpp
vendored
Normal file
115
third_party/sglang/sgl-kernel/csrc/cpu/model/qwen3.cpp
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
#include "common.h"
|
||||
#include "vec.h"
|
||||
namespace {
|
||||
|
||||
template <typename scalar_t>
|
||||
inline void copy_stub(scalar_t* __restrict__ out, const scalar_t* __restrict__ src, int64_t size) {
|
||||
using bVec = at::vec::Vectorized<scalar_t>;
|
||||
constexpr int kVecSize = bVec::size();
|
||||
int64_t d = 0;
|
||||
#pragma GCC unroll 4
|
||||
for (; d <= size - kVecSize; d += kVecSize) {
|
||||
bVec out_bvec = bVec::loadu(src + d);
|
||||
out_bvec.store(out + d);
|
||||
}
|
||||
for (; d < size; ++d) {
|
||||
out[d] = src[d];
|
||||
}
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
void fused_qkvzba_split_reshape_cat_impl(
|
||||
const scalar_t* __restrict__ mixed_qkvz,
|
||||
const scalar_t* __restrict__ mixed_ba,
|
||||
scalar_t* __restrict__ mixed_qkv,
|
||||
scalar_t* __restrict__ z,
|
||||
scalar_t* __restrict__ b,
|
||||
scalar_t* __restrict__ a,
|
||||
int64_t batch,
|
||||
int64_t num_heads_qk,
|
||||
int64_t num_heads_v,
|
||||
int64_t head_qk,
|
||||
int64_t group,
|
||||
int64_t head_v,
|
||||
int64_t qkv_strideB,
|
||||
int64_t qkvz_strideB,
|
||||
int64_t ba_strideB) {
|
||||
int64_t qkvz_stride_per_head = head_qk * 2 + head_v * 2 * group;
|
||||
at::parallel_for(0, batch * num_heads_qk, 0, [&](int64_t begin, int64_t end) {
|
||||
int64_t bi{0}, hi{0};
|
||||
data_index_init(begin, bi, batch, hi, num_heads_qk);
|
||||
for (int64_t i = begin; i < end; ++i) {
|
||||
scalar_t* __restrict__ q_out_ptr = mixed_qkv + bi * qkv_strideB + hi * head_qk;
|
||||
const scalar_t* __restrict__ q_in_ptr = mixed_qkvz + bi * qkvz_strideB + hi * qkvz_stride_per_head;
|
||||
scalar_t* __restrict__ k_out_ptr = q_out_ptr + num_heads_qk * head_qk;
|
||||
const scalar_t* __restrict__ k_in_ptr = q_in_ptr + head_qk;
|
||||
scalar_t* __restrict__ v_out_ptr = k_out_ptr + num_heads_qk * head_qk + hi * head_qk * (group - 1);
|
||||
const scalar_t* __restrict__ v_in_ptr = k_in_ptr + head_qk;
|
||||
scalar_t* __restrict__ z_out_ptr = z + bi * num_heads_v * head_v + hi * group * head_v;
|
||||
const scalar_t* __restrict__ z_in_ptr = v_in_ptr + head_qk * group;
|
||||
copy_stub(q_out_ptr, q_in_ptr, head_qk);
|
||||
copy_stub(k_out_ptr, k_in_ptr, head_qk);
|
||||
copy_stub(v_out_ptr, v_in_ptr, head_qk * group);
|
||||
copy_stub(z_out_ptr, z_in_ptr, head_qk * group);
|
||||
scalar_t* __restrict__ b_out_ptr = b + bi * num_heads_v + hi * group;
|
||||
const scalar_t* __restrict__ b_in_ptr = mixed_ba + bi * ba_strideB + hi * group * 2;
|
||||
scalar_t* __restrict__ a_out_ptr = a + bi * num_heads_v + hi * group;
|
||||
const scalar_t* __restrict__ a_in_ptr = b_in_ptr + group;
|
||||
copy_stub(b_out_ptr, b_in_ptr, group);
|
||||
copy_stub(a_out_ptr, a_in_ptr, group);
|
||||
data_index_step(bi, batch, hi, num_heads_qk);
|
||||
}
|
||||
});
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
// mixed_qkvz: [batch, num_heads_qk * head_qk * 2 + num_heads_v * head_v * 2]
|
||||
// mixed_ba: [batch, num_heads_v * 2]
|
||||
std::tuple<at::Tensor, at::Tensor, at::Tensor, at::Tensor> fused_qkvzba_split_reshape_cat_cpu(
|
||||
const at::Tensor& mixed_qkvz,
|
||||
const at::Tensor& mixed_ba,
|
||||
int64_t num_heads_qk,
|
||||
int64_t num_heads_v,
|
||||
int64_t head_qk,
|
||||
int64_t head_v) {
|
||||
RECORD_FUNCTION("sgl-kernel::fused_qkvzba_split_reshape_cat_cpu", std::vector<c10::IValue>({mixed_qkvz, mixed_ba}));
|
||||
CHECK_DIM(2, mixed_qkvz);
|
||||
CHECK_DIM(2, mixed_ba);
|
||||
CHECK_INPUT(mixed_qkvz);
|
||||
CHECK_INPUT(mixed_ba);
|
||||
int64_t batch = mixed_qkvz.size(0);
|
||||
int64_t qkv_dim = num_heads_qk * head_qk * 2 + num_heads_v * head_v;
|
||||
int64_t ba_dim = num_heads_v * 2;
|
||||
int64_t expected_dim = qkv_dim + num_heads_v * head_v;
|
||||
CHECK_EQ(mixed_qkvz.size(1), expected_dim);
|
||||
CHECK_EQ(mixed_ba.size(0), batch);
|
||||
CHECK_EQ(mixed_ba.size(1), ba_dim);
|
||||
CHECK_EQ(num_heads_v % num_heads_qk, 0);
|
||||
at::Tensor mixed_qkv = at::empty({batch, qkv_dim}, mixed_qkvz.options());
|
||||
at::Tensor z = at::empty({batch, num_heads_v, head_v}, mixed_qkvz.options());
|
||||
at::Tensor b = at::empty({batch, num_heads_v}, mixed_ba.options());
|
||||
at::Tensor a = at::empty({batch, num_heads_v}, mixed_ba.options());
|
||||
int64_t group = num_heads_v / num_heads_qk;
|
||||
int64_t qkvz_strideB = mixed_qkvz.size(1);
|
||||
int64_t qkv_strideB = mixed_qkv.size(1);
|
||||
int64_t ba_strideB = mixed_ba.size(1);
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(mixed_qkvz.scalar_type(), "fused_qkvzba_split_reshape_cat_impl", [&] {
|
||||
fused_qkvzba_split_reshape_cat_impl<scalar_t>(
|
||||
mixed_qkvz.data_ptr<scalar_t>(),
|
||||
mixed_ba.data_ptr<scalar_t>(),
|
||||
mixed_qkv.data_ptr<scalar_t>(),
|
||||
z.data_ptr<scalar_t>(),
|
||||
b.data_ptr<scalar_t>(),
|
||||
a.data_ptr<scalar_t>(),
|
||||
batch,
|
||||
num_heads_qk,
|
||||
num_heads_v,
|
||||
head_qk,
|
||||
group,
|
||||
head_v,
|
||||
qkv_strideB,
|
||||
qkvz_strideB,
|
||||
ba_strideB);
|
||||
});
|
||||
return std::make_tuple(mixed_qkv, z, b, a);
|
||||
}
|
||||
1375
third_party/sglang/sgl-kernel/csrc/cpu/moe.cpp
vendored
Normal file
1375
third_party/sglang/sgl-kernel/csrc/cpu/moe.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
491
third_party/sglang/sgl-kernel/csrc/cpu/moe_fp8.cpp
vendored
Normal file
491
third_party/sglang/sgl-kernel/csrc/cpu/moe_fp8.cpp
vendored
Normal file
@@ -0,0 +1,491 @@
|
||||
#include "common.h"
|
||||
#include "gemm.h"
|
||||
#include "vec.h"
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename scalar_t>
|
||||
inline void copy_stub(scalar_t* __restrict__ out, const scalar_t* __restrict__ input, int64_t size) {
|
||||
using Vec = at::vec::Vectorized<scalar_t>;
|
||||
// no remainder
|
||||
#pragma GCC unroll 4
|
||||
for (int64_t d = 0; d < size; d += Vec::size()) {
|
||||
Vec data = Vec::loadu(input + d);
|
||||
data.store(out + d);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
inline void copy_mul_stub(scalar_t* __restrict__ out, const scalar_t* __restrict__ input, float weight, int64_t size) {
|
||||
using bVec = at::vec::Vectorized<scalar_t>;
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
constexpr int kVecSize = bVec::size();
|
||||
const fVec weight_vec = fVec(weight);
|
||||
int64_t d;
|
||||
#pragma GCC unroll 4
|
||||
for (d = 0; d <= size - kVecSize; d += kVecSize) {
|
||||
bVec x = bVec::loadu(input + d);
|
||||
fVec x0, x1;
|
||||
std::tie(x0, x1) = at::vec::convert_to_float(x);
|
||||
x0 = x0 * weight_vec;
|
||||
x1 = x1 * weight_vec;
|
||||
bVec out_vec = convert_from_float_ext<scalar_t>(x0, x1);
|
||||
out_vec.store(out + d);
|
||||
}
|
||||
for (; d < size; ++d) {
|
||||
out[d] = static_cast<scalar_t>(input[d] * weight);
|
||||
}
|
||||
}
|
||||
|
||||
// acc from [topk, K] to [K]
|
||||
template <typename scalar_t>
|
||||
inline void sum_stub(scalar_t* __restrict__ out, const scalar_t* __restrict__ input, int64_t topk, int64_t K) {
|
||||
using bVec = at::vec::Vectorized<scalar_t>;
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
constexpr int kVecSize = bVec::size();
|
||||
if (topk == 1) {
|
||||
// do copy for topk = 1
|
||||
copy_stub(out, input, K);
|
||||
} else {
|
||||
// do sum for topk != 1
|
||||
int64_t d;
|
||||
#pragma GCC unroll 4
|
||||
for (d = 0; d <= K - kVecSize; d += kVecSize) {
|
||||
fVec sum_fvec0 = fVec(0.f);
|
||||
fVec sum_fvec1 = fVec(0.f);
|
||||
for (int t = 0; t < topk; ++t) {
|
||||
bVec x_bvec = bVec::loadu(input + t * K + d);
|
||||
fVec x_fvec0, x_fvec1;
|
||||
std::tie(x_fvec0, x_fvec1) = at::vec::convert_to_float(x_bvec);
|
||||
|
||||
sum_fvec0 += x_fvec0;
|
||||
sum_fvec1 += x_fvec1;
|
||||
}
|
||||
bVec out_bvec = convert_from_float_ext<scalar_t>(sum_fvec0, sum_fvec1);
|
||||
out_bvec.store(out + d);
|
||||
}
|
||||
for (; d < K; ++d) {
|
||||
float sum_val = 0.f;
|
||||
for (int t = 0; t < topk; ++t) {
|
||||
sum_val += static_cast<float>(input[t * K + d]);
|
||||
}
|
||||
out[d] = static_cast<scalar_t>(sum_val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// out = input + input2 * scale
|
||||
template <typename scalar_t>
|
||||
inline void add_mul_stub(
|
||||
scalar_t* __restrict__ out,
|
||||
const scalar_t* __restrict__ input,
|
||||
const scalar_t* __restrict__ input2,
|
||||
float scale,
|
||||
int64_t size) {
|
||||
using bVec = at::vec::Vectorized<scalar_t>;
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
constexpr int kVecSize = bVec::size();
|
||||
const fVec s_vec = fVec(scale);
|
||||
|
||||
int64_t d;
|
||||
#pragma GCC unroll 4
|
||||
for (d = 0; d <= size - kVecSize; d += kVecSize) {
|
||||
bVec x_bvec = bVec::loadu(input + d);
|
||||
fVec x0, x1;
|
||||
std::tie(x0, x1) = at::vec::convert_to_float(x_bvec);
|
||||
|
||||
bVec y_bvec = bVec::loadu(input2 + d);
|
||||
fVec y0, y1;
|
||||
std::tie(y0, y1) = at::vec::convert_to_float(y_bvec);
|
||||
|
||||
x0 = x0 + y0 * s_vec;
|
||||
x1 = x1 + y1 * s_vec;
|
||||
bVec out_vec = convert_from_float_ext<scalar_t>(x0, x1);
|
||||
out_vec.store(out + d);
|
||||
}
|
||||
for (; d < size; ++d) {
|
||||
out[d] = static_cast<scalar_t>(input[d] + float(input2[d]) * scale);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
inline void silu_and_mul_stub(
|
||||
scalar_t* __restrict__ out, const scalar_t* __restrict__ input, const scalar_t* __restrict__ input2, int64_t size) {
|
||||
using bVec = at::vec::Vectorized<scalar_t>;
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
const fVec one = fVec(1.f);
|
||||
|
||||
// no remainder
|
||||
#pragma GCC unroll 4
|
||||
for (int64_t d = 0; d < size; d += bVec::size()) {
|
||||
bVec x = bVec::loadu(input + d);
|
||||
fVec x0, x1;
|
||||
std::tie(x0, x1) = at::vec::convert_to_float(x);
|
||||
bVec y = bVec::loadu(input2 + d);
|
||||
fVec y0, y1;
|
||||
std::tie(y0, y1) = at::vec::convert_to_float(y);
|
||||
x0 = x0 / (one + x0.neg().exp_u20());
|
||||
x1 = x1 / (one + x1.neg().exp_u20());
|
||||
x0 = x0 * y0;
|
||||
x1 = x1 * y1;
|
||||
bVec out_vec = convert_from_float_ext<scalar_t>(x0, x1);
|
||||
out_vec.store(out + d);
|
||||
}
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
template <typename scalar_t>
|
||||
void fused_experts_fp8_kernel_impl(
|
||||
scalar_t* __restrict__ output,
|
||||
scalar_t* __restrict__ ic0,
|
||||
scalar_t* __restrict__ ic1,
|
||||
scalar_t* __restrict__ ic2,
|
||||
scalar_t* __restrict__ A_tmp,
|
||||
scalar_t* __restrict__ B_tmp,
|
||||
float* __restrict__ C_tmp,
|
||||
const scalar_t* __restrict__ input,
|
||||
const at::Float8_e4m3fn* __restrict__ packed_w1,
|
||||
const at::Float8_e4m3fn* __restrict__ packed_w2,
|
||||
const float* __restrict__ w1s,
|
||||
const float* __restrict__ w2s,
|
||||
int64_t block_size_N,
|
||||
int64_t block_size_K,
|
||||
const float* __restrict__ topk_weights,
|
||||
const int32_t* __restrict__ sorted_ids,
|
||||
const int32_t* __restrict__ expert_ids,
|
||||
const int32_t* __restrict__ offsets,
|
||||
int64_t M,
|
||||
int64_t N,
|
||||
int64_t K,
|
||||
int64_t E,
|
||||
int64_t topk,
|
||||
int64_t num_tokens_post_pad) {
|
||||
constexpr int64_t BLOCK_M = block_size_m();
|
||||
constexpr int64_t BLOCK_N = block_size_n();
|
||||
|
||||
// stage 1: intermediate_cache0 = hidden_states @ w1
|
||||
const int64_t MB = div_up(num_tokens_post_pad, BLOCK_M);
|
||||
const int64_t NB = div_up(2 * N, BLOCK_N);
|
||||
int64_t scale_size_N = div_up(2 * N, block_size_N);
|
||||
int64_t scale_size_K = div_up(K, block_size_K);
|
||||
int64_t blocks_n_per_group = block_size_N / BLOCK_N;
|
||||
|
||||
const int64_t stride_e = 2 * N * K;
|
||||
const int64_t stride_n = K;
|
||||
|
||||
int64_t avg_M = std::max(int64_t(1), M * topk / E);
|
||||
const bool use_brgemm = can_use_brgemm<at::Float8_e4m3fn>(avg_M);
|
||||
|
||||
int64_t B_tmp_size_per_thread = MAX_CACHE_BLOCK_SIZE * BLOCK_N * std::max(K, N);
|
||||
|
||||
// here we only parallel on half of 2N to fuse silu_and_mul with gemm
|
||||
parallel_2d(MB, NB, [&](int64_t mb0, int64_t mb1, int64_t nb0, int64_t nb1) {
|
||||
// get local pointers
|
||||
int tid = get_thread_num();
|
||||
scalar_t* __restrict__ A = A_tmp + tid * BLOCK_M * K;
|
||||
|
||||
loop_2d<at::Float8_e4m3fn>(mb0, mb1, nb0, nb1, BLOCK_N * K, [&](int64_t mb, int64_t nb, int64_t nb_offset) {
|
||||
int64_t n_size = std::min(2 * N - nb * BLOCK_N, BLOCK_N);
|
||||
|
||||
// B shape [K, n_size] in vnni format
|
||||
int32_t expert_id = expert_ids[mb];
|
||||
const at::Float8_e4m3fn* __restrict__ B = packed_w1 + expert_id * stride_e + nb * BLOCK_N * stride_n;
|
||||
const float* __restrict__ Bs =
|
||||
w1s + expert_id * scale_size_N * scale_size_K + (nb / blocks_n_per_group) * scale_size_K;
|
||||
|
||||
// do unpacking for the first row or a new expert
|
||||
int32_t pre_expert_id = mb == 0 ? -1 : expert_ids[mb - 1];
|
||||
bool do_unpack = (mb == mb0) || (expert_id != pre_expert_id);
|
||||
|
||||
// 1.a load A
|
||||
const int32_t* A_ids = sorted_ids + mb * BLOCK_M;
|
||||
int64_t m_size = offsets[mb + 1] - offsets[mb];
|
||||
|
||||
for (int64_t m = 0; m < m_size; ++m) {
|
||||
int32_t index = A_ids[m] / topk;
|
||||
copy_stub(A + m * K, input + index * K, K);
|
||||
}
|
||||
|
||||
const int64_t offset = offsets[mb];
|
||||
tinygemm_kernel<scalar_t>(
|
||||
/* A */ A,
|
||||
/* B */ B,
|
||||
/* C */ ic0 + offset * 2 * N + nb * BLOCK_N,
|
||||
/* Btmp */ B_tmp + tid * B_tmp_size_per_thread + nb_offset * BLOCK_N * K,
|
||||
/* Ctmp */ C_tmp + tid * 2 * BLOCK_M * BLOCK_N,
|
||||
/* scale */ Bs,
|
||||
/* M */ m_size,
|
||||
/* N */ n_size,
|
||||
/* K */ K,
|
||||
/* lda */ K,
|
||||
/* ldb */ n_size,
|
||||
/* ldc */ 2 * N,
|
||||
/* brg */ use_brgemm,
|
||||
/* block_size_K */ block_size_K,
|
||||
/* do_unpack */ do_unpack);
|
||||
});
|
||||
|
||||
if (use_brgemm) {
|
||||
at::native::cpublas::brgemm_release();
|
||||
}
|
||||
});
|
||||
|
||||
// stage 1.5: intermediate_cache1 = silu(intermediate_cache0)
|
||||
at::parallel_for(0, M * topk, 0, [&](int64_t begin, int64_t end) {
|
||||
for (int64_t m = begin; m < end; ++m) {
|
||||
silu_and_mul_stub(ic1 + m * N, ic0 + m * 2 * N, ic0 + m * 2 * N + N, N);
|
||||
}
|
||||
});
|
||||
|
||||
// stage 2: intermediate_cache2 = intermediate_cache1 @ w2
|
||||
// w2 : [E, K, N] as [E, OC, IC]
|
||||
const int64_t OC = K; // rename K as OC
|
||||
const int64_t IC = N; // rename N as IC
|
||||
const int64_t MB2 = MB;
|
||||
const int64_t NB2 = div_up(OC, BLOCK_N);
|
||||
scale_size_N = div_up(K, block_size_N);
|
||||
scale_size_K = div_up(N, block_size_K);
|
||||
const int64_t stride_e2 = OC * IC;
|
||||
const int64_t stride_oc = IC;
|
||||
|
||||
// parallel on [MB2, NB2]
|
||||
parallel_2d(MB2, NB2, [&](int64_t mb0, int64_t mb1, int64_t nb0, int64_t nb1) {
|
||||
int tid = get_thread_num();
|
||||
alignas(64) scalar_t C[BLOCK_M * BLOCK_K];
|
||||
|
||||
loop_2d<at::Float8_e4m3fn>(mb0, mb1, nb0, nb1, BLOCK_N * IC, [&](int64_t mb, int64_t nb, int64_t nb_offset) {
|
||||
int64_t m_size = offsets[mb + 1] - offsets[mb];
|
||||
int64_t n_size = std::min(OC - nb * BLOCK_N, BLOCK_N);
|
||||
|
||||
// A ptr from ic1 of [M * topk, N] in sorted order
|
||||
// so as to avoid copy A to tmp buffer again
|
||||
const scalar_t* __restrict__ A = ic1 + offsets[mb] * N;
|
||||
const int32_t* A_ids = sorted_ids + mb * BLOCK_M;
|
||||
|
||||
// B shape [IC, n_size] in vnni format
|
||||
int32_t expert_id = expert_ids[mb];
|
||||
const at::Float8_e4m3fn* __restrict__ B = packed_w2 + expert_id * stride_e2 + nb * BLOCK_N * stride_oc;
|
||||
const float* __restrict__ Bs =
|
||||
w2s + expert_id * scale_size_N * scale_size_K + (nb / blocks_n_per_group) * scale_size_K;
|
||||
|
||||
// do unpacking for the first row or a new expert
|
||||
int32_t pre_expert_id = mb == 0 ? -1 : expert_ids[mb - 1];
|
||||
bool do_unpack = (mb == mb0) || (expert_id != pre_expert_id);
|
||||
|
||||
tinygemm_kernel<scalar_t>(
|
||||
/* A */ A,
|
||||
/* B */ B,
|
||||
/* C */ C,
|
||||
/* Btmp */ B_tmp + tid * B_tmp_size_per_thread + nb_offset * BLOCK_N * IC,
|
||||
/* Ctmp */ C_tmp + tid * 2 * BLOCK_M * BLOCK_N,
|
||||
/* scale */ Bs,
|
||||
/* M */ m_size,
|
||||
/* N */ n_size,
|
||||
/* K */ IC,
|
||||
/* lda */ IC,
|
||||
/* ldb */ n_size,
|
||||
/* ldc */ BLOCK_N,
|
||||
/* brg */ use_brgemm,
|
||||
/* block_size_K */ block_size_K,
|
||||
/* do_unpack */ do_unpack);
|
||||
|
||||
// 2.b copy from C to ic2 in original order
|
||||
// and also mul topk_weights in float32
|
||||
for (int64_t m = 0; m < m_size; ++m) {
|
||||
int32_t index = A_ids[m];
|
||||
float weight = topk_weights[index];
|
||||
copy_mul_stub(ic2 + index * K + nb * BLOCK_N, C + m * BLOCK_N, weight, n_size);
|
||||
}
|
||||
});
|
||||
|
||||
if (use_brgemm) {
|
||||
at::native::cpublas::brgemm_release();
|
||||
}
|
||||
});
|
||||
|
||||
// stage 3: out = intermediate_cache2.sum(dim=1)
|
||||
// from [M, topk, K] to [M, K]
|
||||
at::parallel_for(0, M, 0, [&](int64_t begin, int64_t end) {
|
||||
for (int64_t m = begin; m < end; ++m) {
|
||||
sum_stub(output + m * K, ic2 + m * topk * K, topk, K);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#define INSTANTIATE_MOE_FP8_TEMPLATE(TYPE) \
|
||||
template void fused_experts_fp8_kernel_impl<TYPE>( \
|
||||
TYPE* __restrict__ output, \
|
||||
TYPE* __restrict__ ic0, \
|
||||
TYPE* __restrict__ ic1, \
|
||||
TYPE* __restrict__ ic2, \
|
||||
TYPE* __restrict__ A_tmp, \
|
||||
TYPE* __restrict__ B_tmp, \
|
||||
float* __restrict__ C_tmp, \
|
||||
const TYPE* __restrict__ input, \
|
||||
const at::Float8_e4m3fn* __restrict__ packed_w1, \
|
||||
const at::Float8_e4m3fn* __restrict__ packed_w2, \
|
||||
const float* __restrict__ w1s, \
|
||||
const float* __restrict__ w2s, \
|
||||
int64_t block_size_N, \
|
||||
int64_t block_size_K, \
|
||||
const float* __restrict__ topk_weights, \
|
||||
const int32_t* __restrict__ sorted_ids, \
|
||||
const int32_t* __restrict__ expert_ids, \
|
||||
const int32_t* __restrict__ offsets, \
|
||||
int64_t M, \
|
||||
int64_t N, \
|
||||
int64_t K, \
|
||||
int64_t E, \
|
||||
int64_t topk, \
|
||||
int64_t num_tokens_post_pad)
|
||||
|
||||
INSTANTIATE_MOE_FP8_TEMPLATE(at::BFloat16);
|
||||
INSTANTIATE_MOE_FP8_TEMPLATE(at::Half);
|
||||
|
||||
template <typename scalar_t>
|
||||
void shared_expert_fp8_kernel_impl(
|
||||
scalar_t* __restrict__ output,
|
||||
scalar_t* __restrict__ ic0,
|
||||
scalar_t* __restrict__ ic1,
|
||||
scalar_t* __restrict__ B_tmp,
|
||||
float* __restrict__ C_tmp,
|
||||
const scalar_t* __restrict__ input,
|
||||
const at::Float8_e4m3fn* __restrict__ packed_w1,
|
||||
const at::Float8_e4m3fn* __restrict__ packed_w2,
|
||||
const float* __restrict__ w1s,
|
||||
const float* __restrict__ w2s,
|
||||
int64_t block_size_N,
|
||||
int64_t block_size_K,
|
||||
const scalar_t* __restrict__ fused_experts_out,
|
||||
float routed_scaling_factor,
|
||||
int64_t M,
|
||||
int64_t N,
|
||||
int64_t K) {
|
||||
constexpr int64_t BLOCK_M = block_size_m();
|
||||
constexpr int64_t BLOCK_N = block_size_n();
|
||||
|
||||
// stage 1: intermediate_cache0 = hidden_states @ w1
|
||||
const int64_t MB = div_up(M, BLOCK_M);
|
||||
const int64_t NB = div_up(2 * N, BLOCK_N);
|
||||
int64_t scale_size_K = div_up(K, block_size_K);
|
||||
int64_t blocks_n_per_group = block_size_N / BLOCK_N;
|
||||
|
||||
const bool use_brgemm = can_use_brgemm<at::Float8_e4m3fn>(M);
|
||||
|
||||
int64_t B_tmp_size_per_thread = MAX_CACHE_BLOCK_SIZE * BLOCK_N * std::max(K, N);
|
||||
|
||||
parallel_2d(MB, NB, [&](int64_t mb0, int64_t mb1, int64_t nb0, int64_t nb1) {
|
||||
int tid = get_thread_num();
|
||||
|
||||
loop_2d<at::Float8_e4m3fn>(mb0, mb1, nb0, nb1, BLOCK_N * K, [&](int64_t mb, int64_t nb, int64_t nb_offset) {
|
||||
int64_t m_size = std::min(M - mb * BLOCK_M, BLOCK_M);
|
||||
int64_t n_size = std::min(2 * N - nb * BLOCK_N, BLOCK_N);
|
||||
|
||||
// do unpacking for the first row
|
||||
bool do_unpack = (mb == mb0);
|
||||
|
||||
tinygemm_kernel<scalar_t>(
|
||||
/* A */ input + mb * BLOCK_M * K,
|
||||
/* B */ packed_w1 + nb * BLOCK_N * K,
|
||||
/* C */ ic0 + mb * BLOCK_M * 2 * N + nb * BLOCK_N,
|
||||
/* Btmp */ B_tmp + tid * B_tmp_size_per_thread + nb_offset * BLOCK_N * K,
|
||||
/* Ctmp */ C_tmp + tid * 2 * BLOCK_M * BLOCK_N,
|
||||
/* scale */ w1s + (nb / blocks_n_per_group) * scale_size_K,
|
||||
/* M */ m_size,
|
||||
/* N */ n_size,
|
||||
/* K */ K,
|
||||
/* lda */ K,
|
||||
/* ldb */ n_size,
|
||||
/* ldc */ 2 * N,
|
||||
/* brg */ use_brgemm,
|
||||
/* block_size_K */ block_size_K,
|
||||
/* do_unpack */ do_unpack);
|
||||
});
|
||||
|
||||
if (use_brgemm) {
|
||||
at::native::cpublas::brgemm_release();
|
||||
}
|
||||
});
|
||||
|
||||
// stage 1.5: intermediate_cache1 = silu(intermediate_cache0)
|
||||
at::parallel_for(0, M, 0, [&](int64_t begin, int64_t end) {
|
||||
for (int64_t m = begin; m < end; ++m) {
|
||||
silu_and_mul_stub(ic1 + m * N, ic0 + m * 2 * N, ic0 + m * 2 * N + N, N);
|
||||
}
|
||||
});
|
||||
|
||||
// stage 2: intermediate_cache2 = intermediate_cache1 @ w2
|
||||
// w2 : [K, N] as [OC, IC]
|
||||
const int64_t OC = K; // rename K as OC
|
||||
const int64_t IC = N; // rename N as IC
|
||||
const int64_t MB2 = MB;
|
||||
const int64_t NB2 = div_up(K, BLOCK_N);
|
||||
scale_size_K = div_up(N, block_size_K);
|
||||
|
||||
// parallel on [MB2, NB2]
|
||||
parallel_2d(MB2, NB2, [&](int64_t mb0, int64_t mb1, int64_t nb0, int64_t nb1) {
|
||||
int tid = get_thread_num();
|
||||
alignas(64) scalar_t C[BLOCK_M * BLOCK_K];
|
||||
|
||||
loop_2d<at::Float8_e4m3fn>(mb0, mb1, nb0, nb1, BLOCK_N * IC, [&](int64_t mb, int64_t nb, int64_t nb_offset) {
|
||||
int64_t m_size = std::min(M - mb * BLOCK_M, BLOCK_M);
|
||||
int64_t n_size = std::min(OC - nb * BLOCK_N, BLOCK_N);
|
||||
|
||||
// do unpacking for the first row
|
||||
bool do_unpack = (mb == mb0);
|
||||
|
||||
// 2.a gemm: C = A @ B
|
||||
tinygemm_kernel<scalar_t>(
|
||||
/* A */ ic1 + mb * BLOCK_M * N,
|
||||
/* B */ packed_w2 + nb * BLOCK_N * N,
|
||||
/* C */ C,
|
||||
/* Btmp */ B_tmp + tid * B_tmp_size_per_thread + nb_offset * BLOCK_N * IC,
|
||||
/* Ctmp */ C_tmp + tid * 2 * BLOCK_M * BLOCK_N,
|
||||
/* scale */ w2s + (nb / blocks_n_per_group) * scale_size_K,
|
||||
/* M */ m_size,
|
||||
/* N */ n_size,
|
||||
/* K */ IC,
|
||||
/* lda */ IC,
|
||||
/* ldb */ n_size,
|
||||
/* ldc */ BLOCK_N,
|
||||
/* brg */ use_brgemm,
|
||||
/* block_size_K */ block_size_K,
|
||||
/* do_unpack */ do_unpack);
|
||||
|
||||
// 2.b copy from C to output and add fused_experts_out
|
||||
scalar_t* __restrict__ out = output + mb * BLOCK_M * K + nb * BLOCK_N;
|
||||
const scalar_t* __restrict__ fused_out = fused_experts_out + mb * BLOCK_M * K + nb * BLOCK_N;
|
||||
for (int64_t m = 0; m < m_size; ++m) {
|
||||
add_mul_stub(out + m * K, C + m * BLOCK_N, fused_out + m * K, routed_scaling_factor, n_size);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (use_brgemm) {
|
||||
at::native::cpublas::brgemm_release();
|
||||
}
|
||||
}
|
||||
|
||||
#define INSTANTIATE_SHARED_EXPERT_FP8_TEMPLATE(TYPE) \
|
||||
template void shared_expert_fp8_kernel_impl<TYPE>( \
|
||||
TYPE* __restrict__ output, \
|
||||
TYPE* __restrict__ ic0, \
|
||||
TYPE* __restrict__ ic1, \
|
||||
TYPE* __restrict__ B_tmp, \
|
||||
float* __restrict__ C_tmp, \
|
||||
const TYPE* __restrict__ input, \
|
||||
const at::Float8_e4m3fn* __restrict__ packed_w1, \
|
||||
const at::Float8_e4m3fn* __restrict__ packed_w2, \
|
||||
const float* __restrict__ w1s, \
|
||||
const float* __restrict__ w2s, \
|
||||
int64_t block_size_N, \
|
||||
int64_t block_size_K, \
|
||||
const TYPE* __restrict__ fused_experts_out, \
|
||||
float routed_scaling_factor, \
|
||||
int64_t M, \
|
||||
int64_t N, \
|
||||
int64_t K)
|
||||
|
||||
INSTANTIATE_SHARED_EXPERT_FP8_TEMPLATE(at::BFloat16);
|
||||
INSTANTIATE_SHARED_EXPERT_FP8_TEMPLATE(at::Half);
|
||||
484
third_party/sglang/sgl-kernel/csrc/cpu/moe_int4.cpp
vendored
Normal file
484
third_party/sglang/sgl-kernel/csrc/cpu/moe_int4.cpp
vendored
Normal file
@@ -0,0 +1,484 @@
|
||||
#include "common.h"
|
||||
#include "gemm.h"
|
||||
#include "vec.h"
|
||||
namespace {
|
||||
|
||||
template <typename scalar_t>
|
||||
inline void copy_stub(scalar_t* __restrict__ out, const scalar_t* __restrict__ input, int64_t size) {
|
||||
using Vec = at::vec::Vectorized<scalar_t>;
|
||||
// no remainder
|
||||
#pragma GCC unroll 4
|
||||
for (int64_t d = 0; d < size; d += Vec::size()) {
|
||||
Vec data = Vec::loadu(input + d);
|
||||
data.store(out + d);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
inline void copy_stub(scalar_t* __restrict__ out, const float* __restrict__ input, int64_t size) {
|
||||
using bVec = at::vec::Vectorized<scalar_t>;
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
constexpr int kVecSize = bVec::size();
|
||||
int64_t d;
|
||||
#pragma GCC unroll 4
|
||||
for (d = 0; d <= size - kVecSize; d += kVecSize) {
|
||||
bVec x = bVec::loadu(input + d);
|
||||
fVec x0, x1;
|
||||
std::tie(x0, x1) = at::vec::convert_to_float(x);
|
||||
bVec out_vec = convert_from_float_ext<scalar_t>(x0, x1);
|
||||
out_vec.store(out + d);
|
||||
}
|
||||
for (; d < size; ++d) {
|
||||
out[d] = static_cast<scalar_t>(input[d]);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
inline void copy_mul_stub(scalar_t* __restrict__ out, const float* __restrict__ input, float weight, int64_t size) {
|
||||
using bVec = at::vec::Vectorized<scalar_t>;
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
constexpr int kVecSize = bVec::size();
|
||||
const fVec weight_vec = fVec(weight);
|
||||
int64_t d;
|
||||
#pragma GCC unroll 4
|
||||
for (d = 0; d <= size - kVecSize; d += kVecSize) {
|
||||
fVec data0 = fVec::loadu(input + d) * weight_vec;
|
||||
fVec data1 = fVec::loadu(input + d + fVec::size()) * weight_vec;
|
||||
bVec out_vec = convert_from_float_ext<scalar_t>(data0, data1);
|
||||
out_vec.store(out + d);
|
||||
}
|
||||
for (; d < size; ++d) {
|
||||
out[d] = static_cast<scalar_t>(input[d] * weight);
|
||||
}
|
||||
}
|
||||
|
||||
// acc from [topk, K] to [K]
|
||||
template <typename scalar_t>
|
||||
inline void sum_stub(scalar_t* __restrict__ out, const scalar_t* __restrict__ input, int64_t topk, int64_t K) {
|
||||
using bVec = at::vec::Vectorized<scalar_t>;
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
constexpr int kVecSize = bVec::size();
|
||||
if (topk == 1) {
|
||||
// do copy for topk = 1
|
||||
copy_stub(out, input, K);
|
||||
} else {
|
||||
// do sum for topk != 1
|
||||
int64_t d;
|
||||
#pragma GCC unroll 4
|
||||
for (d = 0; d <= K - kVecSize; d += kVecSize) {
|
||||
fVec sum_fvec0 = fVec(0.f);
|
||||
fVec sum_fvec1 = fVec(0.f);
|
||||
for (int t = 0; t < topk; ++t) {
|
||||
bVec x_bvec = bVec::loadu(input + t * K + d);
|
||||
fVec x_fvec0, x_fvec1;
|
||||
std::tie(x_fvec0, x_fvec1) = at::vec::convert_to_float(x_bvec);
|
||||
|
||||
sum_fvec0 += x_fvec0;
|
||||
sum_fvec1 += x_fvec1;
|
||||
}
|
||||
bVec out_bvec = convert_from_float_ext<scalar_t>(sum_fvec0, sum_fvec1);
|
||||
out_bvec.store(out + d);
|
||||
}
|
||||
for (; d < K; ++d) {
|
||||
float sum_val = 0.f;
|
||||
for (int t = 0; t < topk; ++t) {
|
||||
sum_val += static_cast<float>(input[t * K + d]);
|
||||
}
|
||||
out[d] = static_cast<scalar_t>(sum_val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// out = input + input2 * scale
|
||||
template <typename scalar_t>
|
||||
inline void add_mul_stub(
|
||||
scalar_t* __restrict__ out,
|
||||
const scalar_t* __restrict__ input,
|
||||
const scalar_t* __restrict__ input2,
|
||||
float scale,
|
||||
int64_t size) {
|
||||
using bVec = at::vec::Vectorized<scalar_t>;
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
constexpr int kVecSize = bVec::size();
|
||||
const fVec s_vec = fVec(scale);
|
||||
|
||||
int64_t d;
|
||||
#pragma GCC unroll 4
|
||||
for (d = 0; d <= size - kVecSize; d += kVecSize) {
|
||||
bVec x_bvec = bVec::loadu(input + d);
|
||||
fVec x0, x1;
|
||||
std::tie(x0, x1) = at::vec::convert_to_float(x_bvec);
|
||||
|
||||
bVec y_bvec = bVec::loadu(input2 + d);
|
||||
fVec y0, y1;
|
||||
std::tie(y0, y1) = at::vec::convert_to_float(y_bvec);
|
||||
|
||||
x0 = x0 + y0 * s_vec;
|
||||
x1 = x1 + y1 * s_vec;
|
||||
bVec out_vec = convert_from_float_ext<scalar_t>(x0, x1);
|
||||
out_vec.store(out + d);
|
||||
}
|
||||
for (; d < size; ++d) {
|
||||
out[d] = static_cast<scalar_t>(input[d] + float(input2[d]) * scale);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
inline void silu_and_mul_stub(
|
||||
scalar_t* __restrict__ out, const scalar_t* __restrict__ input, const scalar_t* __restrict__ input2, int64_t size) {
|
||||
using bVec = at::vec::Vectorized<scalar_t>;
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
const fVec one = fVec(1.f);
|
||||
|
||||
// no remainder
|
||||
#pragma GCC unroll 4
|
||||
for (int64_t d = 0; d < size; d += bVec::size()) {
|
||||
bVec x = bVec::loadu(input + d);
|
||||
fVec x0, x1;
|
||||
std::tie(x0, x1) = at::vec::convert_to_float(x);
|
||||
bVec y = bVec::loadu(input2 + d);
|
||||
fVec y0, y1;
|
||||
std::tie(y0, y1) = at::vec::convert_to_float(y);
|
||||
x0 = x0 / (one + x0.neg().exp_u20());
|
||||
x1 = x1 / (one + x1.neg().exp_u20());
|
||||
x0 = x0 * y0;
|
||||
x1 = x1 * y1;
|
||||
bVec out_vec = convert_from_float_ext<scalar_t>(x0, x1);
|
||||
out_vec.store(out + d);
|
||||
}
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// TODO: stride access
|
||||
template <int64_t N>
|
||||
inline void copy_bias(const float* bias_ptr, float* y_buf, int64_t m, int64_t ldn) {
|
||||
if (bias_ptr) {
|
||||
for (int i = 0; i < m; ++i) {
|
||||
int j = 0;
|
||||
#if defined(CPU_CAPABILITY_AVX512)
|
||||
#pragma GCC unroll 2
|
||||
for (; j < N; j += 16) {
|
||||
__m512 bias_vec = _mm512_loadu_ps(bias_ptr + j);
|
||||
_mm512_storeu_ps(y_buf + i * ldn + j, bias_vec);
|
||||
}
|
||||
#endif
|
||||
for (; j < N; ++j) {
|
||||
y_buf[i * ldn + j] = bias_ptr[j];
|
||||
}
|
||||
}
|
||||
} else { // initialize to zero
|
||||
for (int i = 0; i < m; ++i) {
|
||||
int j = 0;
|
||||
#if defined(CPU_CAPABILITY_AVX512)
|
||||
#pragma GCC unroll 2
|
||||
for (; j < N; j += 16) {
|
||||
__m512 zero_vec = _mm512_setzero_ps();
|
||||
_mm512_storeu_ps(y_buf + i * ldn + j, zero_vec);
|
||||
}
|
||||
#endif
|
||||
for (; j < N; ++j) {
|
||||
y_buf[i * ldn + j] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
void fused_experts_int4_w4a8_kernel_impl(
|
||||
scalar_t* __restrict__ output,
|
||||
scalar_t* __restrict__ ic0,
|
||||
scalar_t* __restrict__ ic1,
|
||||
scalar_t* __restrict__ ic2,
|
||||
uint8_t* __restrict__ A_tmp,
|
||||
uint8_t* __restrict__ Aq_tmp,
|
||||
float* __restrict__ As_tmp,
|
||||
int32_t* __restrict__ Azp_tmp,
|
||||
float* __restrict__ C_tmp,
|
||||
int8_t* __restrict__ dqB_tmp,
|
||||
const scalar_t* __restrict__ input,
|
||||
const uint8_t* __restrict__ packed_w1,
|
||||
const uint8_t* __restrict__ packed_w2,
|
||||
const int8_t* __restrict__ w1z,
|
||||
const int8_t* __restrict__ w2z,
|
||||
const float* __restrict__ w1s,
|
||||
const float* __restrict__ w2s,
|
||||
int group_size,
|
||||
const float* __restrict__ topk_weights,
|
||||
const int32_t* __restrict__ sorted_ids,
|
||||
const int32_t* __restrict__ expert_ids,
|
||||
const int32_t* __restrict__ offsets,
|
||||
int64_t M,
|
||||
int64_t N,
|
||||
int64_t K,
|
||||
int64_t E,
|
||||
int64_t topk,
|
||||
int64_t num_tokens_post_pad) {
|
||||
constexpr int64_t BLOCK_M = block_size_m();
|
||||
constexpr int64_t BLOCK_N = block_size_n();
|
||||
int num_threads = at::get_num_threads();
|
||||
// int64_t buffer_size_nbytes = M * topk * N * 2
|
||||
// M * topk * K * 2 +
|
||||
// num_threads * BLOCK_M * K +
|
||||
// num_threads * 2 * BLOCK_M * BLOCK_N * sizeof(float) +
|
||||
// M * topk * 2 * N * 2 +
|
||||
// max(M * K, M * topk * N) +
|
||||
// M * topk * sizeof(float);
|
||||
|
||||
// intermediate_cache1 (scalar_t): START + M * topk * N
|
||||
// intermediate_cache2 (scalar_t): + M * topk * K
|
||||
// A_tmp (uint8_t): + num_threads * BLOCK_M * K
|
||||
// C_tmp (float): + num_threads * 2 * BLOCK_M * BLOCK_N
|
||||
// intermediate_cache0 (scalar_t): + M * topk * 2 * N
|
||||
// Aq_tmp (uint8_t): + max(M * K, M * topk * N)
|
||||
// As_tmp (float): + M * topk
|
||||
// dqB_tmp (int8_t) + num_threads * _block_k * BlOCK_N
|
||||
|
||||
// stage 0: quantize input to uint8, [M, K]
|
||||
at::parallel_for(0, M, 0, [&](int64_t begin, int64_t end) {
|
||||
for (int64_t m = begin; m < end; ++m) {
|
||||
quantize_row_int8<scalar_t>(Aq_tmp + m * K, As_tmp[m], input + m * K, K);
|
||||
}
|
||||
});
|
||||
int64_t _block_k = get_4bit_block_k_size(group_size);
|
||||
auto Azp = at::ones({M * topk}).to(at::kInt).mul(128);
|
||||
auto Azp_ptr = Azp.data_ptr<int32_t>();
|
||||
// stage 1: intermediate_cache0 = hidden_states @ w1
|
||||
const int64_t MB = div_up(num_tokens_post_pad, BLOCK_M);
|
||||
const int64_t NB = div_up(N, BLOCK_N);
|
||||
|
||||
int64_t block_per_group = group_size / _block_k;
|
||||
int64_t Kc = K / _block_k;
|
||||
int64_t num_groups = K / group_size;
|
||||
|
||||
const int64_t stride_e = 2 * NB * Kc * (BLOCK_N * (_block_k / 2 + sizeof(int32_t)));
|
||||
const bool sym_quant_act = false;
|
||||
// weight + compensation shape = [E, Nc, Kc, block_n * _block_k / 2 + block_n*sizeof(int32_t)]
|
||||
// scales/qzeros shape = [E, Nc, G, block_n]
|
||||
|
||||
// here we only parallel on half of 2N to fuse silu_and_mul with gemm
|
||||
at::parallel_for(0, MB * NB, 0, [&](int64_t begin, int64_t end) {
|
||||
// get local pointers
|
||||
int tid = at::get_thread_num();
|
||||
int8_t* dqB_tmp1 = dqB_tmp + tid * 2 * _block_k * BLOCK_N;
|
||||
int8_t* dqB_tmp2 = dqB_tmp1 + _block_k * BLOCK_N;
|
||||
alignas(64) float As[BLOCK_M];
|
||||
uint8_t* __restrict__ A = A_tmp + tid * BLOCK_M * K;
|
||||
float* __restrict__ C0 = C_tmp + tid * 2 * BLOCK_M * BLOCK_N;
|
||||
float* __restrict__ C1 = C0 + BLOCK_M * BLOCK_N;
|
||||
bool is_brgemm_used = false;
|
||||
for (int64_t i = begin; i < end; ++i) {
|
||||
int64_t mb = i / NB;
|
||||
int64_t nb = i % NB;
|
||||
int64_t nb1 = nb + NB;
|
||||
int64_t n_size = std::min(N - nb * BLOCK_N, BLOCK_N);
|
||||
// B shape [K, n_size] in vnni format
|
||||
int32_t expert_id = expert_ids[mb];
|
||||
const uint8_t* __restrict__ B = packed_w1 + expert_id * stride_e;
|
||||
// Bz and Bs: [E, K/gs, 2N]
|
||||
const int8_t* __restrict__ Bz = w1z + expert_id * (num_groups) * (2 * N);
|
||||
const float* __restrict__ Bs = w1s + expert_id * (num_groups) * (2 * N);
|
||||
|
||||
// 1.a load A
|
||||
const int32_t* A_ids = sorted_ids + mb * BLOCK_M;
|
||||
int64_t m_size = offsets[mb + 1] - offsets[mb];
|
||||
const bool use_brgemm = can_use_brgemm<int8_t>(m_size);
|
||||
is_brgemm_used = is_brgemm_used || use_brgemm;
|
||||
// copy to A [BLOCK_M, K]
|
||||
for (int64_t m = 0; m < m_size; ++m) {
|
||||
int32_t index = A_ids[m] / topk;
|
||||
copy_stub(A + m * K, Aq_tmp + index * K, K);
|
||||
As[m] = As_tmp[index];
|
||||
}
|
||||
const int64_t offset = offsets[mb];
|
||||
copy_bias<BLOCK_N>(nullptr, C0, m_size, BLOCK_N);
|
||||
copy_bias<BLOCK_N>(nullptr, C1, m_size, BLOCK_N);
|
||||
for (int kci = 0; kci < Kc; ++kci) {
|
||||
int32_t* compensation_ptr =
|
||||
sym_quant_act ? nullptr
|
||||
: (int32_t*)(void*)(B + (nb * Kc + kci) * (BLOCK_N * (_block_k / 2 + sizeof(int32_t))) +
|
||||
_block_k * BLOCK_N / 2) /*Bcomp*/;
|
||||
tinygemm_kernel<scalar_t>(
|
||||
ic0 + offset * 2 * N + nb * BLOCK_N,
|
||||
C0,
|
||||
A + kci * _block_k,
|
||||
As,
|
||||
Azp_ptr,
|
||||
B + (nb * Kc + kci) * (BLOCK_N * (_block_k / 2 + sizeof(int32_t))) /*B*/,
|
||||
Bs + nb * BLOCK_N * num_groups + kci / block_per_group * BLOCK_N /*scales_b*/,
|
||||
Bz + nb * BLOCK_N * num_groups + kci / block_per_group * BLOCK_N /*qzeros_b*/,
|
||||
compensation_ptr,
|
||||
dqB_tmp1,
|
||||
m_size,
|
||||
_block_k,
|
||||
K,
|
||||
BLOCK_N,
|
||||
2 * N,
|
||||
kci == Kc - 1,
|
||||
use_brgemm);
|
||||
}
|
||||
|
||||
for (int kci = 0; kci < Kc; ++kci) {
|
||||
int32_t* compensation_ptr =
|
||||
sym_quant_act ? nullptr
|
||||
: (int32_t*)(void*)(B + (nb1 * Kc + kci) * (BLOCK_N * (_block_k / 2 + sizeof(int32_t))) +
|
||||
_block_k * BLOCK_N / 2) /*Bcomp*/;
|
||||
tinygemm_kernel<scalar_t>(
|
||||
ic0 + offset * 2 * N + nb1 * BLOCK_N,
|
||||
C1,
|
||||
A + kci * _block_k,
|
||||
As,
|
||||
Azp_ptr,
|
||||
B + (nb1 * Kc + kci) * (BLOCK_N * (_block_k / 2 + sizeof(int32_t))) /*B*/,
|
||||
Bs + nb1 * BLOCK_N * num_groups + kci / block_per_group * BLOCK_N /*scales_b*/,
|
||||
Bz + nb1 * BLOCK_N * num_groups + kci / block_per_group * BLOCK_N /*qzeros_b*/,
|
||||
compensation_ptr,
|
||||
dqB_tmp2,
|
||||
m_size,
|
||||
_block_k,
|
||||
K,
|
||||
BLOCK_N,
|
||||
2 * N,
|
||||
kci == Kc - 1,
|
||||
use_brgemm);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_brgemm_used) {
|
||||
at::native::cpublas::brgemm_release();
|
||||
}
|
||||
});
|
||||
|
||||
// stage 1.5: intermediate_cache1 = silu(intermediate_cache0)
|
||||
at::parallel_for(0, M * topk, 0, [&](int64_t begin, int64_t end) {
|
||||
for (int64_t m = begin; m < end; ++m) {
|
||||
silu_and_mul_stub(ic1 + m * N, ic0 + m * 2 * N, ic0 + m * 2 * N + N, N);
|
||||
}
|
||||
});
|
||||
|
||||
// stage 1.5: quantize ic1 to uint8, [M * topk, N]
|
||||
at::parallel_for(0, M * topk, 0, [&](int64_t begin, int64_t end) {
|
||||
for (int64_t m = begin; m < end; ++m) {
|
||||
quantize_row_int8<scalar_t>(Aq_tmp + m * N, As_tmp[m], ic1 + m * N, N);
|
||||
}
|
||||
});
|
||||
// stage 2: intermediate_cache2 = intermediate_cache1 @ w2
|
||||
// w2 : [E, K, N] as [E, OC, IC]
|
||||
const int64_t OC = K; // rename K as OC
|
||||
const int64_t IC = N; // rename N as IC
|
||||
const int64_t MB2 = MB;
|
||||
const int64_t NB2 = div_up(OC, BLOCK_N);
|
||||
const int64_t stride_oc = IC;
|
||||
num_groups = IC / group_size;
|
||||
Kc = IC / _block_k;
|
||||
const int64_t stride_e2 = NB2 * Kc * (BLOCK_N * (_block_k / 2 + sizeof(int32_t)));
|
||||
// parallel on [MB2, NB2]
|
||||
at::parallel_for(0, MB2 * NB2, 0, [&](int64_t begin, int64_t end) {
|
||||
int tid = at::get_thread_num();
|
||||
int8_t* dqB_tmp1 = dqB_tmp + tid * 2 * _block_k * BLOCK_N;
|
||||
float* __restrict__ C2 = C_tmp + tid * 2 * BLOCK_M * BLOCK_N;
|
||||
bool is_brgemm_used = false;
|
||||
for (int64_t i = begin; i < end; ++i) {
|
||||
int64_t mb = i / NB2;
|
||||
int64_t nb = i % NB2;
|
||||
|
||||
int64_t m_size = offsets[mb + 1] - offsets[mb];
|
||||
int64_t n_size = std::min(OC - nb * BLOCK_N, BLOCK_N);
|
||||
const bool use_brgemm = can_use_brgemm<int8_t>(m_size);
|
||||
is_brgemm_used = is_brgemm_used || use_brgemm;
|
||||
const int32_t* A_ids = sorted_ids + mb * BLOCK_M;
|
||||
|
||||
// B shape [IC, n_size] in vnni format
|
||||
int32_t expert_id = expert_ids[mb];
|
||||
const uint8_t* __restrict__ B = packed_w2 + expert_id * stride_e2;
|
||||
|
||||
// Bz and Bs: [E, IC/gs, OC]
|
||||
const int8_t* __restrict__ Bz = w2z + expert_id * (num_groups)*OC;
|
||||
const float* __restrict__ Bs = w2s + expert_id * (num_groups)*OC;
|
||||
|
||||
// A ptr from ic1 of [M * topk, N] in sorted order
|
||||
// so as to avoid copy A to tmp buffer again
|
||||
const uint8_t* __restrict__ A = Aq_tmp + offsets[mb] * IC;
|
||||
const float* __restrict__ As = As_tmp + offsets[mb];
|
||||
copy_bias<BLOCK_N>(nullptr, C2, m_size, BLOCK_N);
|
||||
for (int kci = 0; kci < Kc; ++kci) {
|
||||
int32_t* compensation_ptr =
|
||||
sym_quant_act ? nullptr
|
||||
: (int32_t*)(void*)(B + (nb * Kc + kci) * (BLOCK_N * (_block_k / 2 + sizeof(int32_t))) +
|
||||
_block_k * BLOCK_N / 2) /*Bcomp*/;
|
||||
tinygemm_kernel<scalar_t>(
|
||||
nullptr, /*store_out is false*/
|
||||
C2,
|
||||
A + kci * _block_k,
|
||||
As,
|
||||
Azp_ptr,
|
||||
B + (nb * Kc + kci) * (BLOCK_N * (_block_k / 2 + sizeof(int32_t))),
|
||||
Bs + nb * BLOCK_N * num_groups + kci / block_per_group * BLOCK_N /*scales_b*/,
|
||||
Bz + nb * BLOCK_N * num_groups + kci / block_per_group * BLOCK_N /*zeros_b*/,
|
||||
compensation_ptr,
|
||||
dqB_tmp1,
|
||||
m_size,
|
||||
_block_k,
|
||||
IC,
|
||||
BLOCK_N,
|
||||
BLOCK_N,
|
||||
false,
|
||||
use_brgemm);
|
||||
}
|
||||
|
||||
// 2.b copy from C to ic2 in original order
|
||||
// and also mul topk_weights in float32
|
||||
for (int64_t m = 0; m < m_size; ++m) {
|
||||
int32_t index = A_ids[m];
|
||||
float weight = topk_weights[index];
|
||||
copy_mul_stub(ic2 + index * K + nb * BLOCK_N, C2 + m * BLOCK_N, weight, n_size);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_brgemm_used) {
|
||||
at::native::cpublas::brgemm_release();
|
||||
}
|
||||
});
|
||||
|
||||
// stage 3: out = intermediate_cache2.sum(dim=1)
|
||||
// from [M, topk, K] to [M, K]
|
||||
at::parallel_for(0, M, 0, [&](int64_t begin, int64_t end) {
|
||||
for (int64_t m = begin; m < end; ++m) {
|
||||
sum_stub(output + m * K, ic2 + m * topk * K, topk, K);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#define INSTANTIATE_MOE_INT4_W4A8_TEMPLATE(TYPE) \
|
||||
template void fused_experts_int4_w4a8_kernel_impl<TYPE>( \
|
||||
TYPE* __restrict__ output, \
|
||||
TYPE* __restrict__ ic0, \
|
||||
TYPE* __restrict__ ic1, \
|
||||
TYPE* __restrict__ ic2, \
|
||||
uint8_t* __restrict__ A_tmp, \
|
||||
uint8_t* __restrict__ Aq_tmp, \
|
||||
float* __restrict__ As_tmp, \
|
||||
int32_t* __restrict__ Azp_tmp, \
|
||||
float* __restrict__ C_tmp, \
|
||||
int8_t* __restrict__ dqB_tmp, \
|
||||
const TYPE* __restrict__ input, \
|
||||
const uint8_t* __restrict__ packed_w1, \
|
||||
const uint8_t* __restrict__ packed_w2, \
|
||||
const int8_t* __restrict__ w1z, \
|
||||
const int8_t* __restrict__ w2z, \
|
||||
const float* __restrict__ w1s, \
|
||||
const float* __restrict__ w2s, \
|
||||
int group_size, \
|
||||
const float* __restrict__ topk_weights, \
|
||||
const int32_t* __restrict__ sorted_ids, \
|
||||
const int32_t* __restrict__ expert_ids, \
|
||||
const int32_t* __restrict__ offsets, \
|
||||
int64_t M, \
|
||||
int64_t N, \
|
||||
int64_t K, \
|
||||
int64_t E, \
|
||||
int64_t topk, \
|
||||
int64_t num_tokens_post_pad)
|
||||
|
||||
INSTANTIATE_MOE_INT4_W4A8_TEMPLATE(at::BFloat16);
|
||||
INSTANTIATE_MOE_INT4_W4A8_TEMPLATE(at::Half);
|
||||
1068
third_party/sglang/sgl-kernel/csrc/cpu/moe_int8.cpp
vendored
Normal file
1068
third_party/sglang/sgl-kernel/csrc/cpu/moe_int8.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
875
third_party/sglang/sgl-kernel/csrc/cpu/norm.cpp
vendored
Normal file
875
third_party/sglang/sgl-kernel/csrc/cpu/norm.cpp
vendored
Normal file
@@ -0,0 +1,875 @@
|
||||
#include "common.h"
|
||||
#include "vec.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// NB: avoid using `at::vec::map<>` on bfloat16 or half
|
||||
// Llama4TextL2Norm
|
||||
template <typename scalar_t>
|
||||
void l2norm_kernel_impl(
|
||||
scalar_t* __restrict__ output,
|
||||
const scalar_t* __restrict__ input,
|
||||
int64_t batch_size,
|
||||
int64_t hidden_size,
|
||||
float eps = 1e-5) {
|
||||
using bVec = at::vec::Vectorized<scalar_t>;
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
|
||||
constexpr int kVecSize = bVec::size();
|
||||
at::parallel_for(0, batch_size, 0, [&](int64_t begin, int64_t end) {
|
||||
for (int64_t i = begin; i < end; ++i) {
|
||||
// local ptrs
|
||||
scalar_t* __restrict__ out_ptr = output + i * hidden_size;
|
||||
const scalar_t* __restrict__ input_ptr = input + i * hidden_size;
|
||||
|
||||
fVec sum_fvec = fVec(float(0));
|
||||
float sum_val = float(0);
|
||||
|
||||
int64_t d;
|
||||
#pragma GCC unroll 4
|
||||
for (d = 0; d <= hidden_size - kVecSize; d += kVecSize) {
|
||||
bVec x_bvec = bVec::loadu(input_ptr + d);
|
||||
fVec x_fvec0, x_fvec1;
|
||||
std::tie(x_fvec0, x_fvec1) = at::vec::convert_to_float(x_bvec);
|
||||
|
||||
sum_fvec += x_fvec0 * x_fvec0;
|
||||
sum_fvec += x_fvec1 * x_fvec1;
|
||||
}
|
||||
#pragma GCC unroll 4
|
||||
for (; d < hidden_size; ++d) {
|
||||
float x_val = static_cast<float>(input_ptr[d]);
|
||||
sum_val += x_val * x_val;
|
||||
}
|
||||
|
||||
sum_val += vec_reduce_sum(sum_fvec);
|
||||
float rsqrt_var = float(1) / std::sqrt(sum_val / hidden_size + eps);
|
||||
const fVec scale_fvec = fVec(rsqrt_var);
|
||||
|
||||
#pragma GCC unroll 4
|
||||
for (d = 0; d <= hidden_size - kVecSize; d += kVecSize) {
|
||||
bVec x_bvec = bVec::loadu(input_ptr + d);
|
||||
fVec x_fvec0, x_fvec1;
|
||||
std::tie(x_fvec0, x_fvec1) = at::vec::convert_to_float(x_bvec);
|
||||
|
||||
x_fvec0 = x_fvec0 * scale_fvec;
|
||||
x_fvec1 = x_fvec1 * scale_fvec;
|
||||
|
||||
bVec out_bvec = convert_from_float_ext<scalar_t>(x_fvec0, x_fvec1);
|
||||
out_bvec.store(out_ptr + d);
|
||||
}
|
||||
#pragma GCC unroll 4
|
||||
for (; d < hidden_size; ++d) {
|
||||
float x_val = static_cast<float>(input_ptr[d]);
|
||||
out_ptr[d] = static_cast<scalar_t>(x_val * rsqrt_var);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
template <typename scalar_t, typename func_t, typename vec_func_t>
|
||||
void rmsnorm_kernel_impl(
|
||||
scalar_t* __restrict__ output,
|
||||
const scalar_t* __restrict__ input,
|
||||
const scalar_t* __restrict__ weight,
|
||||
int64_t batch_size,
|
||||
int64_t hidden_size,
|
||||
int64_t input_strideN,
|
||||
const func_t& f,
|
||||
const vec_func_t& vf,
|
||||
float eps = 1e-5) {
|
||||
using bVec = at::vec::Vectorized<scalar_t>;
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
|
||||
constexpr int kVecSize = bVec::size();
|
||||
at::parallel_for(0, batch_size, 0, [&](int64_t begin, int64_t end) {
|
||||
for (int64_t i = begin; i < end; ++i) {
|
||||
// local ptrs
|
||||
scalar_t* __restrict__ out_ptr = output + i * hidden_size;
|
||||
const scalar_t* __restrict__ input_ptr = input + i * input_strideN;
|
||||
|
||||
fVec sum_fvec = fVec(float(0));
|
||||
float sum_val = float(0);
|
||||
|
||||
int64_t d;
|
||||
#pragma GCC unroll 4
|
||||
for (d = 0; d <= hidden_size - kVecSize; d += kVecSize) {
|
||||
bVec x_bvec = bVec::loadu(input_ptr + d);
|
||||
fVec x_fvec0, x_fvec1;
|
||||
std::tie(x_fvec0, x_fvec1) = at::vec::convert_to_float(x_bvec);
|
||||
|
||||
sum_fvec += x_fvec0 * x_fvec0;
|
||||
sum_fvec += x_fvec1 * x_fvec1;
|
||||
}
|
||||
#pragma GCC unroll 4
|
||||
for (; d < hidden_size; ++d) {
|
||||
float x_val = static_cast<float>(input_ptr[d]);
|
||||
sum_val += x_val * x_val;
|
||||
}
|
||||
|
||||
sum_val += vec_reduce_sum(sum_fvec);
|
||||
float rsqrt_var = float(1) / std::sqrt(sum_val / hidden_size + eps);
|
||||
const fVec scale_fvec = fVec(rsqrt_var);
|
||||
|
||||
#pragma GCC unroll 4
|
||||
for (d = 0; d <= hidden_size - kVecSize; d += kVecSize) {
|
||||
bVec x_bvec = bVec::loadu(input_ptr + d);
|
||||
fVec x_fvec0, x_fvec1;
|
||||
std::tie(x_fvec0, x_fvec1) = at::vec::convert_to_float(x_bvec);
|
||||
|
||||
bVec w_bvec = bVec::loadu(weight + d);
|
||||
fVec w_fvec0, w_fvec1;
|
||||
std::tie(w_fvec0, w_fvec1) = at::vec::convert_to_float(w_bvec);
|
||||
|
||||
x_fvec0 = x_fvec0 * scale_fvec * vf(w_fvec0);
|
||||
x_fvec1 = x_fvec1 * scale_fvec * vf(w_fvec1);
|
||||
|
||||
bVec out_bvec = convert_from_float_ext<scalar_t>(x_fvec0, x_fvec1);
|
||||
out_bvec.store(out_ptr + d);
|
||||
}
|
||||
#pragma GCC unroll 4
|
||||
for (; d < hidden_size; ++d) {
|
||||
float x_val = static_cast<float>(input_ptr[d]);
|
||||
float w_val = static_cast<float>(weight[d]);
|
||||
out_ptr[d] = static_cast<scalar_t>(x_val * rsqrt_var * f(w_val));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
void gemma3_rmsnorm_kernel_4d_impl(
|
||||
scalar_t* __restrict__ output,
|
||||
const scalar_t* __restrict__ input,
|
||||
const scalar_t* __restrict__ weight,
|
||||
int64_t batch_size,
|
||||
int64_t num_head,
|
||||
int64_t seq_len,
|
||||
int64_t hidden_size,
|
||||
int64_t input_strideB,
|
||||
int64_t input_strideH,
|
||||
int64_t input_strideS,
|
||||
int64_t output_strideB,
|
||||
int64_t output_strideH,
|
||||
int64_t output_strideS,
|
||||
float eps = 1e-5) {
|
||||
using bVec = at::vec::Vectorized<scalar_t>;
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
|
||||
constexpr int kVecSize = bVec::size();
|
||||
at::parallel_for(0, batch_size * num_head * seq_len, 0, [&](int64_t begin, int64_t end) {
|
||||
int64_t bi{0}, hi{0}, si{0};
|
||||
data_index_init(begin, bi, batch_size, hi, num_head, si, seq_len);
|
||||
for (int64_t i = begin; i < end; ++i) {
|
||||
// local ptrs
|
||||
scalar_t* __restrict__ out_ptr = output + bi * output_strideB + hi * output_strideH + si * output_strideS;
|
||||
const scalar_t* __restrict__ input_ptr = input + bi * input_strideB + hi * input_strideH + si * input_strideS;
|
||||
|
||||
fVec sum_fvec = fVec(float(0));
|
||||
float sum_val = float(0);
|
||||
fVec one_fvec = fVec(float(1));
|
||||
|
||||
int64_t d;
|
||||
#pragma GCC unroll 4
|
||||
for (d = 0; d <= hidden_size - kVecSize; d += kVecSize) {
|
||||
bVec x_bvec = bVec::loadu(input_ptr + d);
|
||||
fVec x_fvec0, x_fvec1;
|
||||
std::tie(x_fvec0, x_fvec1) = at::vec::convert_to_float(x_bvec);
|
||||
|
||||
sum_fvec += x_fvec0 * x_fvec0;
|
||||
sum_fvec += x_fvec1 * x_fvec1;
|
||||
}
|
||||
#pragma GCC unroll 4
|
||||
for (; d < hidden_size; ++d) {
|
||||
float x_val = static_cast<float>(input_ptr[d]);
|
||||
sum_val += x_val * x_val;
|
||||
}
|
||||
|
||||
sum_val += vec_reduce_sum(sum_fvec);
|
||||
float rsqrt_var = float(1) / std::sqrt(sum_val / hidden_size + eps);
|
||||
const fVec scale_fvec = fVec(rsqrt_var);
|
||||
|
||||
#pragma GCC unroll 4
|
||||
for (d = 0; d <= hidden_size - kVecSize; d += kVecSize) {
|
||||
bVec x_bvec = bVec::loadu(input_ptr + d);
|
||||
fVec x_fvec0, x_fvec1;
|
||||
std::tie(x_fvec0, x_fvec1) = at::vec::convert_to_float(x_bvec);
|
||||
|
||||
bVec w_bvec = bVec::loadu(weight + d);
|
||||
fVec w_fvec0, w_fvec1;
|
||||
std::tie(w_fvec0, w_fvec1) = at::vec::convert_to_float(w_bvec);
|
||||
|
||||
x_fvec0 = x_fvec0 * scale_fvec * (w_fvec0 + one_fvec);
|
||||
x_fvec1 = x_fvec1 * scale_fvec * (w_fvec1 + one_fvec);
|
||||
|
||||
bVec out_bvec = convert_from_float_ext<scalar_t>(x_fvec0, x_fvec1);
|
||||
out_bvec.store(out_ptr + d);
|
||||
}
|
||||
#pragma GCC unroll 4
|
||||
for (; d < hidden_size; ++d) {
|
||||
float x_val = static_cast<float>(input_ptr[d]);
|
||||
float w_val = static_cast<float>(weight[d]);
|
||||
out_ptr[d] = static_cast<scalar_t>(x_val * rsqrt_var * (w_val + 1));
|
||||
}
|
||||
// move to the next index
|
||||
data_index_step(bi, batch_size, hi, num_head, si, seq_len);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <typename scalar_t, typename func_t, typename vec_func_t>
|
||||
void fused_add_rmsnorm_kernel_impl(
|
||||
scalar_t* __restrict__ input,
|
||||
scalar_t* __restrict__ residual,
|
||||
const scalar_t* __restrict__ weight,
|
||||
float* __restrict__ buffer,
|
||||
int64_t batch_size,
|
||||
int64_t hidden_size,
|
||||
int64_t input_strideN,
|
||||
const func_t& f,
|
||||
const vec_func_t& vf,
|
||||
float eps = 1e-5) {
|
||||
using bVec = at::vec::Vectorized<scalar_t>;
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
|
||||
constexpr int kVecSize = bVec::size();
|
||||
at::parallel_for(0, batch_size, 0, [&](int64_t begin, int64_t end) {
|
||||
int tid = at::get_thread_num();
|
||||
float* __restrict__ buffer_ptr = buffer + tid * hidden_size;
|
||||
|
||||
for (int64_t i = begin; i < end; ++i) {
|
||||
// local ptrs
|
||||
scalar_t* __restrict__ input_ptr = input + i * input_strideN;
|
||||
scalar_t* __restrict__ residual_ptr = residual + i * hidden_size;
|
||||
|
||||
fVec sum_fvec = fVec(float(0));
|
||||
float sum_val = float(0);
|
||||
|
||||
int64_t d;
|
||||
#pragma GCC unroll 4
|
||||
for (d = 0; d <= hidden_size - kVecSize; d += kVecSize) {
|
||||
bVec x_bvec = bVec::loadu(input_ptr + d);
|
||||
fVec x_fvec0, x_fvec1;
|
||||
std::tie(x_fvec0, x_fvec1) = at::vec::convert_to_float(x_bvec);
|
||||
|
||||
bVec r_bvec = bVec::loadu(residual_ptr + d);
|
||||
fVec r_fvec0, r_fvec1;
|
||||
std::tie(r_fvec0, r_fvec1) = at::vec::convert_to_float(r_bvec);
|
||||
|
||||
x_fvec0 += r_fvec0;
|
||||
x_fvec1 += r_fvec1;
|
||||
|
||||
bVec out_bvec = convert_from_float_ext<scalar_t>(x_fvec0, x_fvec1);
|
||||
out_bvec.store(residual_ptr + d);
|
||||
|
||||
sum_fvec += x_fvec0 * x_fvec0;
|
||||
sum_fvec += x_fvec1 * x_fvec1;
|
||||
|
||||
x_fvec0.store(buffer_ptr + d);
|
||||
x_fvec1.store(buffer_ptr + d + fVec::size());
|
||||
}
|
||||
#pragma GCC unroll 4
|
||||
for (; d < hidden_size; ++d) {
|
||||
float x_val = static_cast<float>(input_ptr[d]);
|
||||
float r_val = static_cast<float>(residual_ptr[d]);
|
||||
|
||||
x_val += r_val;
|
||||
residual_ptr[d] = static_cast<scalar_t>(x_val);
|
||||
|
||||
sum_val += x_val * x_val;
|
||||
buffer_ptr[d] = x_val;
|
||||
}
|
||||
|
||||
sum_val += vec_reduce_sum(sum_fvec);
|
||||
float rsqrt_var = float(1) / std::sqrt(sum_val / hidden_size + eps);
|
||||
const fVec scale_fvec = fVec(rsqrt_var);
|
||||
|
||||
#pragma GCC unroll 4
|
||||
for (d = 0; d <= hidden_size - kVecSize; d += kVecSize) {
|
||||
fVec x_fvec0 = fVec::loadu(buffer_ptr + d);
|
||||
fVec x_fvec1 = fVec::loadu(buffer_ptr + d + fVec::size());
|
||||
|
||||
bVec w_bvec = bVec::loadu(weight + d);
|
||||
fVec w_fvec0, w_fvec1;
|
||||
std::tie(w_fvec0, w_fvec1) = at::vec::convert_to_float(w_bvec);
|
||||
|
||||
x_fvec0 = x_fvec0 * scale_fvec * vf(w_fvec0);
|
||||
x_fvec1 = x_fvec1 * scale_fvec * vf(w_fvec1);
|
||||
bVec x_bvec = convert_from_float_ext<scalar_t>(x_fvec0, x_fvec1);
|
||||
x_bvec.store(input_ptr + d);
|
||||
}
|
||||
#pragma GCC unroll 4
|
||||
for (; d < hidden_size; ++d) {
|
||||
float x_val = buffer_ptr[d] * rsqrt_var * static_cast<float>(f(weight[d]));
|
||||
input_ptr[d] = x_val;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
void fused_rmsnorm_gated_kernel_impl(
|
||||
scalar_t* __restrict__ output,
|
||||
const scalar_t* __restrict__ input,
|
||||
const scalar_t* __restrict__ weight,
|
||||
const scalar_t* __restrict__ gate,
|
||||
int64_t batch_size,
|
||||
int64_t hidden_size,
|
||||
int64_t input_strideN,
|
||||
float eps = 1e-5) {
|
||||
using bVec = at::vec::Vectorized<scalar_t>;
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
const fVec one = fVec(1.f);
|
||||
|
||||
constexpr int kVecSize = bVec::size();
|
||||
at::parallel_for(0, batch_size, 0, [&](int64_t begin, int64_t end) {
|
||||
for (int64_t i = begin; i < end; ++i) {
|
||||
// local ptrs
|
||||
scalar_t* __restrict__ out_ptr = output + i * hidden_size;
|
||||
const scalar_t* __restrict__ input_ptr = input + i * input_strideN;
|
||||
const scalar_t* __restrict__ gate_ptr = gate + i * hidden_size;
|
||||
|
||||
fVec sum_fvec = fVec(float(0));
|
||||
float sum_val = float(0);
|
||||
|
||||
int64_t d;
|
||||
#pragma GCC unroll 4
|
||||
for (d = 0; d <= hidden_size - kVecSize; d += kVecSize) {
|
||||
bVec x_bvec = bVec::loadu(input_ptr + d);
|
||||
fVec x_fvec0, x_fvec1;
|
||||
std::tie(x_fvec0, x_fvec1) = at::vec::convert_to_float(x_bvec);
|
||||
|
||||
sum_fvec += x_fvec0 * x_fvec0;
|
||||
sum_fvec += x_fvec1 * x_fvec1;
|
||||
}
|
||||
#pragma GCC unroll 4
|
||||
for (; d < hidden_size; ++d) {
|
||||
float x_val = static_cast<float>(input_ptr[d]);
|
||||
sum_val += x_val * x_val;
|
||||
}
|
||||
|
||||
sum_val += vec_reduce_sum(sum_fvec);
|
||||
float rsqrt_var = float(1) / std::sqrt(sum_val / hidden_size + eps);
|
||||
const fVec scale_fvec = fVec(rsqrt_var);
|
||||
|
||||
#pragma GCC unroll 4
|
||||
for (d = 0; d <= hidden_size - kVecSize; d += kVecSize) {
|
||||
bVec x_bvec = bVec::loadu(input_ptr + d);
|
||||
fVec x_fvec0, x_fvec1;
|
||||
std::tie(x_fvec0, x_fvec1) = at::vec::convert_to_float(x_bvec);
|
||||
|
||||
bVec w_bvec = bVec::loadu(weight + d);
|
||||
fVec w_fvec0, w_fvec1;
|
||||
std::tie(w_fvec0, w_fvec1) = at::vec::convert_to_float(w_bvec);
|
||||
|
||||
bVec g_bvec = bVec::loadu(gate_ptr + d);
|
||||
fVec g_fvec0, g_fvec1;
|
||||
std::tie(g_fvec0, g_fvec1) = at::vec::convert_to_float(g_bvec);
|
||||
g_fvec0 = g_fvec0 / (one + g_fvec0.neg().exp_u20());
|
||||
g_fvec1 = g_fvec1 / (one + g_fvec1.neg().exp_u20());
|
||||
|
||||
x_fvec0 = x_fvec0 * scale_fvec * w_fvec0 * g_fvec0;
|
||||
x_fvec1 = x_fvec1 * scale_fvec * w_fvec1 * g_fvec1;
|
||||
|
||||
bVec out_bvec = convert_from_float_ext<scalar_t>(x_fvec0, x_fvec1);
|
||||
out_bvec.store(out_ptr + d);
|
||||
}
|
||||
#pragma GCC unroll 4
|
||||
for (; d < hidden_size; ++d) {
|
||||
float x_val = static_cast<float>(input_ptr[d]);
|
||||
float w_val = static_cast<float>(weight[d]);
|
||||
float g_val = static_cast<float>(gate_ptr[d]);
|
||||
|
||||
out_ptr[d] = static_cast<scalar_t>(x_val * rsqrt_var * w_val * g_val / (1.f + std::exp(-g_val)));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
template <typename scalar_t>
|
||||
void fused_add_layernorm_kernel_impl(
|
||||
scalar_t* __restrict__ output,
|
||||
const scalar_t* __restrict__ input,
|
||||
scalar_t* __restrict__ residual,
|
||||
const scalar_t* __restrict__ weight,
|
||||
const scalar_t* __restrict__ bias,
|
||||
float* __restrict__ buffer,
|
||||
int64_t batch_size,
|
||||
int64_t seq_len,
|
||||
int64_t hidden_size,
|
||||
int64_t input_strideN,
|
||||
float eps = 1e-5) {
|
||||
using bVec = at::vec::Vectorized<scalar_t>;
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
constexpr int kVecSize = bVec::size();
|
||||
|
||||
const bool has_residual{residual != nullptr};
|
||||
const bool has_bias{bias != nullptr};
|
||||
const int64_t parallel_size{batch_size * seq_len};
|
||||
at::parallel_for(0, parallel_size, 0, [&](int64_t begin, int64_t end) {
|
||||
float* __restrict__ buffer_ptr = buffer + at::get_thread_num() * hidden_size;
|
||||
|
||||
for (int64_t i = begin; i < end; ++i) {
|
||||
scalar_t* __restrict__ out_ptr = output + i * hidden_size;
|
||||
const scalar_t* __restrict__ input_ptr = input + i * input_strideN;
|
||||
scalar_t* __restrict__ residual_ptr{(scalar_t*)nullptr};
|
||||
if (has_residual) {
|
||||
residual_ptr = residual + i * hidden_size;
|
||||
}
|
||||
|
||||
// First pass: compute mean and var
|
||||
fVec sum_fvec{fVec(0.0)}, sum_sq_fvec{fVec(0.0)};
|
||||
float sum_val{0.0}, sum_sq_val{0.0};
|
||||
int64_t d{0};
|
||||
|
||||
#pragma GCC unroll 4
|
||||
for (; d <= hidden_size - kVecSize; d += kVecSize) {
|
||||
bVec x_bvec = bVec::loadu(input_ptr + d);
|
||||
fVec x_fvec0, x_fvec1;
|
||||
std::tie(x_fvec0, x_fvec1) = at::vec::convert_to_float(x_bvec);
|
||||
|
||||
if (has_residual) {
|
||||
bVec r_bvec = bVec::loadu(residual_ptr + d);
|
||||
fVec r_fvec0, r_fvec1;
|
||||
std::tie(r_fvec0, r_fvec1) = at::vec::convert_to_float(r_bvec);
|
||||
|
||||
x_fvec0 += r_fvec0;
|
||||
x_fvec1 += r_fvec1;
|
||||
|
||||
bVec out_bvec = convert_from_float_ext<scalar_t>(x_fvec0, x_fvec1);
|
||||
out_bvec.store(residual_ptr + d);
|
||||
}
|
||||
|
||||
sum_fvec += x_fvec0;
|
||||
sum_fvec += x_fvec1;
|
||||
sum_sq_fvec += x_fvec0 * x_fvec0;
|
||||
sum_sq_fvec += x_fvec1 * x_fvec1;
|
||||
|
||||
x_fvec0.store(buffer_ptr + d);
|
||||
x_fvec1.store(buffer_ptr + d + fVec::size());
|
||||
}
|
||||
#pragma GCC unroll 4
|
||||
for (; d < hidden_size; ++d) {
|
||||
float x_val = static_cast<float>(input_ptr[d]);
|
||||
if (has_residual) {
|
||||
float r_val = static_cast<float>(residual_ptr[d]);
|
||||
x_val += r_val;
|
||||
residual_ptr[d] = static_cast<scalar_t>(x_val);
|
||||
}
|
||||
|
||||
sum_val += x_val;
|
||||
sum_sq_val += x_val * x_val;
|
||||
buffer_ptr[d] = x_val;
|
||||
}
|
||||
|
||||
// Var(X) = E(X^2) - (E(X))^2
|
||||
// Refer to FlashInfer impl:
|
||||
// https://github.com/flashinfer-ai/flashinfer/blob/6bb01d19c2d9ab3b6a3a5e9e97448891a5ed2844/include/flashinfer/norm.cuh#L554
|
||||
sum_val += vec_reduce_sum(sum_fvec);
|
||||
sum_sq_val += vec_reduce_sum(sum_sq_fvec);
|
||||
|
||||
float mean{sum_val / hidden_size};
|
||||
float mean_sq{sum_sq_val / hidden_size};
|
||||
float variance{mean_sq - (mean * mean)};
|
||||
float rsqrt_var{float(1) / std::sqrt(variance + eps)};
|
||||
|
||||
const fVec mean_fvec = fVec(mean);
|
||||
const fVec scale_fvec = fVec(rsqrt_var);
|
||||
|
||||
// Second pass: apply normalization
|
||||
#pragma GCC unroll 4
|
||||
for (d = 0; d <= hidden_size - kVecSize; d += kVecSize) {
|
||||
fVec x_fvec0 = fVec::loadu(buffer_ptr + d);
|
||||
fVec x_fvec1 = fVec::loadu(buffer_ptr + d + fVec::size());
|
||||
bVec w_bvec = bVec::loadu(weight + d);
|
||||
fVec w_fvec0, w_fvec1;
|
||||
std::tie(w_fvec0, w_fvec1) = at::vec::convert_to_float(w_bvec);
|
||||
|
||||
x_fvec0 = (x_fvec0 - mean_fvec) * scale_fvec * w_fvec0;
|
||||
x_fvec1 = (x_fvec1 - mean_fvec) * scale_fvec * w_fvec1;
|
||||
|
||||
if (has_bias) {
|
||||
bVec b_bvec = bVec::loadu(bias + d);
|
||||
fVec b_fvec0, b_fvec1;
|
||||
std::tie(b_fvec0, b_fvec1) = at::vec::convert_to_float(b_bvec);
|
||||
x_fvec0 += b_fvec0;
|
||||
x_fvec1 += b_fvec1;
|
||||
}
|
||||
|
||||
bVec o_bvec = convert_from_float_ext<scalar_t>(x_fvec0, x_fvec1);
|
||||
o_bvec.store(out_ptr + d);
|
||||
}
|
||||
#pragma GCC unroll 4
|
||||
for (; d < hidden_size; ++d) {
|
||||
float normalized = (buffer_ptr[d] - mean) * rsqrt_var;
|
||||
float x_val = normalized * static_cast<float>(weight[d]);
|
||||
if (has_bias) {
|
||||
x_val += static_cast<float>(bias[d]);
|
||||
}
|
||||
out_ptr[d] = static_cast<scalar_t>(x_val);
|
||||
}
|
||||
}
|
||||
});
|
||||
} // anonymous namespace
|
||||
|
||||
// input : {batch_size, hidden_size}
|
||||
at::Tensor l2norm_cpu(at::Tensor& input, double eps) {
|
||||
RECORD_FUNCTION("sgl-kernel::l2norm_cpu", std::vector<c10::IValue>({input}));
|
||||
|
||||
CHECK_INPUT(input);
|
||||
CHECK_DIM(2, input);
|
||||
int64_t batch_size = input.size(0);
|
||||
int64_t hidden_size = input.size(1);
|
||||
at::Tensor output = at::empty_like(input);
|
||||
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(input.scalar_type(), "l2norm_kernel", [&] {
|
||||
l2norm_kernel_impl<scalar_t>(output.data_ptr<scalar_t>(), input.data_ptr<scalar_t>(), batch_size, hidden_size, eps);
|
||||
});
|
||||
return output;
|
||||
}
|
||||
|
||||
// input : {batch_size, hidden_size}
|
||||
// weight: {hidden_size}
|
||||
at::Tensor rmsnorm_cpu(at::Tensor& input, at::Tensor& weight, double eps) {
|
||||
RECORD_FUNCTION("sgl-kernel::rmsnorm_cpu", std::vector<c10::IValue>({input, weight}));
|
||||
|
||||
CHECK_LAST_DIM_CONTIGUOUS_INPUT(input);
|
||||
CHECK_INPUT(weight);
|
||||
CHECK_DIM(2, input);
|
||||
CHECK_DIM(1, weight);
|
||||
CHECK_EQ(input.size(1), weight.size(0));
|
||||
int64_t batch_size = input.size(0);
|
||||
int64_t hidden_size = input.size(1);
|
||||
at::Tensor output = at::empty_like(input);
|
||||
int64_t input_strideN = input.stride(0);
|
||||
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(input.scalar_type(), "rmsnorm_kernel", [&] {
|
||||
using Vec = at::vec::Vectorized<float>;
|
||||
rmsnorm_kernel_impl<scalar_t>(
|
||||
output.data_ptr<scalar_t>(),
|
||||
input.data_ptr<scalar_t>(),
|
||||
weight.data_ptr<scalar_t>(),
|
||||
batch_size,
|
||||
hidden_size,
|
||||
input_strideN,
|
||||
[](float x) { return x; },
|
||||
[](Vec x) { return x; },
|
||||
eps);
|
||||
});
|
||||
return output;
|
||||
}
|
||||
|
||||
// input : {batch_size, hidden_size} or {batch_size, seq_len, hidden_size}
|
||||
// weight: {hidden_size}
|
||||
// bias : {hidden_size}
|
||||
at::Tensor
|
||||
layernorm_cpu(const at::Tensor& input, const at::Tensor& weight, const std::optional<at::Tensor>& bias, double eps) {
|
||||
RECORD_FUNCTION("sgl-kernel::layernorm_cpu", std::vector<c10::IValue>({input, weight}));
|
||||
|
||||
CHECK_LAST_DIM_CONTIGUOUS_INPUT(input);
|
||||
CHECK_INPUT(weight);
|
||||
int64_t inp_dim{input.dim()};
|
||||
TORCH_CHECK(inp_dim == 2 || inp_dim == 3, "Expected input dim to be 2 or 3, but got ", inp_dim);
|
||||
CHECK_DIM(1, weight);
|
||||
if (bias.has_value()) {
|
||||
CHECK_DIM(1, bias.value());
|
||||
CHECK_EQ(bias.value().size(0), weight.size(0));
|
||||
}
|
||||
|
||||
int64_t batch_size{input.size(0)}, seq_len{1}, hidden_size{input.size(1)}, input_strideN{input.stride(0)};
|
||||
if (inp_dim == 3) {
|
||||
CHECK_EQ(input.size(2), weight.size(0));
|
||||
seq_len = input.size(1);
|
||||
hidden_size = input.size(2);
|
||||
input_strideN = input.stride(1);
|
||||
} else {
|
||||
CHECK_EQ(input.size(1), weight.size(0));
|
||||
}
|
||||
|
||||
at::Tensor output = at::empty_like(input);
|
||||
int64_t num_threads = at::get_num_threads();
|
||||
at::Tensor buffer = at::empty({num_threads, hidden_size}, input.options().dtype(at::kFloat));
|
||||
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(input.scalar_type(), "layernorm_kernel", [&] {
|
||||
fused_add_layernorm_kernel_impl<scalar_t>(
|
||||
output.data_ptr<scalar_t>(),
|
||||
input.data_ptr<scalar_t>(),
|
||||
nullptr,
|
||||
weight.data_ptr<scalar_t>(),
|
||||
conditional_data_ptr<scalar_t>(bias),
|
||||
buffer.data_ptr<float>(),
|
||||
batch_size,
|
||||
seq_len,
|
||||
hidden_size,
|
||||
input_strideN,
|
||||
eps);
|
||||
});
|
||||
return output;
|
||||
}
|
||||
|
||||
at::Tensor gemma_rmsnorm_cpu(at::Tensor& input, at::Tensor& weight, double eps) {
|
||||
RECORD_FUNCTION("sgl-kernel::gemma_rmsnorm_cpu", std::vector<c10::IValue>({input, weight}));
|
||||
|
||||
CHECK_LAST_DIM_CONTIGUOUS_INPUT(input);
|
||||
CHECK_INPUT(weight);
|
||||
CHECK_DIM(2, input);
|
||||
CHECK_DIM(1, weight);
|
||||
CHECK_EQ(input.size(1), weight.size(0));
|
||||
int64_t batch_size = input.size(0);
|
||||
int64_t hidden_size = input.size(1);
|
||||
at::Tensor output = at::empty_like(input);
|
||||
int64_t input_strideN = input.stride(0);
|
||||
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(input.scalar_type(), "gemma_rmsnorm_kernel", [&] {
|
||||
using Vec = at::vec::Vectorized<float>;
|
||||
Vec one_vec = Vec(float(1));
|
||||
rmsnorm_kernel_impl<scalar_t>(
|
||||
output.data_ptr<scalar_t>(),
|
||||
input.data_ptr<scalar_t>(),
|
||||
weight.data_ptr<scalar_t>(),
|
||||
batch_size,
|
||||
hidden_size,
|
||||
input_strideN,
|
||||
[](float x) { return x + 1; },
|
||||
[one_vec](Vec x) { return x + one_vec; },
|
||||
eps);
|
||||
});
|
||||
return output;
|
||||
}
|
||||
|
||||
// input : {batch_size, hidden_size} or {batch_size, num_head, seq_len, head_dim}
|
||||
// weight: {hidden_size}
|
||||
at::Tensor gemma3_rmsnorm_cpu(at::Tensor& input, at::Tensor& weight, double eps) {
|
||||
RECORD_FUNCTION("sgl-kernel::gemma3_rmsnorm_cpu", std::vector<c10::IValue>({input, weight}));
|
||||
|
||||
CHECK_LAST_DIM_CONTIGUOUS_INPUT(input);
|
||||
CHECK_INPUT(weight);
|
||||
TORCH_CHECK(
|
||||
input.dim() == 2 || input.dim() == 4, "gemma3_rmsnorm_cpu: input must be 2D or 4D, got ", input.dim(), "D");
|
||||
CHECK_DIM(1, weight);
|
||||
CHECK_EQ(input.size(-1), weight.size(0));
|
||||
int64_t batch_size = input.size(0);
|
||||
int64_t hidden_size = weight.size(0);
|
||||
at::Tensor output = at::empty_like(input);
|
||||
if (input.dim() == 2) {
|
||||
int64_t input_strideN = input.stride(0);
|
||||
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(input.scalar_type(), "gemma3_rmsnorm_kernel", [&] {
|
||||
using Vec = at::vec::Vectorized<float>;
|
||||
Vec one_vec = Vec(float(1));
|
||||
rmsnorm_kernel_impl<scalar_t>(
|
||||
output.data_ptr<scalar_t>(),
|
||||
input.data_ptr<scalar_t>(),
|
||||
weight.data_ptr<scalar_t>(),
|
||||
batch_size,
|
||||
hidden_size,
|
||||
input_strideN,
|
||||
[](float x) { return x + 1; },
|
||||
[one_vec](Vec x) { return x + one_vec; },
|
||||
eps);
|
||||
});
|
||||
} else {
|
||||
int64_t input_strideB = input.stride(0);
|
||||
int64_t input_strideH = input.stride(1);
|
||||
int64_t input_strideS = input.stride(2);
|
||||
int64_t output_strideB = output.stride(0);
|
||||
int64_t output_strideH = output.stride(1);
|
||||
int64_t output_strideS = output.stride(2);
|
||||
int64_t num_head = input.size(1);
|
||||
int64_t seq_len = input.size(2);
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(input.scalar_type(), "gemma3_rmsnorm_kernel", [&] {
|
||||
gemma3_rmsnorm_kernel_4d_impl<scalar_t>(
|
||||
output.data_ptr<scalar_t>(),
|
||||
input.data_ptr<scalar_t>(),
|
||||
weight.data_ptr<scalar_t>(),
|
||||
batch_size,
|
||||
num_head,
|
||||
seq_len,
|
||||
hidden_size,
|
||||
input_strideB,
|
||||
input_strideH,
|
||||
input_strideS,
|
||||
output_strideB,
|
||||
output_strideH,
|
||||
output_strideS,
|
||||
eps);
|
||||
});
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
// input : {batch_size, hidden_size}
|
||||
// weight: {hidden_size}
|
||||
// gate: {batch_size, hidden_size}
|
||||
at::Tensor fused_rmsnorm_gated_cpu(at::Tensor& input, at::Tensor& weight, at::Tensor& gate, double eps) {
|
||||
RECORD_FUNCTION("sgl-kernel::fused_rmsnorm_gated_cpu", std::vector<c10::IValue>({input, weight, gate}));
|
||||
|
||||
CHECK_LAST_DIM_CONTIGUOUS_INPUT(input);
|
||||
CHECK_INPUT(weight);
|
||||
CHECK_INPUT(gate);
|
||||
CHECK_DIM(2, input);
|
||||
CHECK_DIM(1, weight);
|
||||
CHECK_DIM(2, gate);
|
||||
CHECK_EQ(input.size(1), weight.size(0));
|
||||
int64_t batch_size = input.size(0);
|
||||
int64_t hidden_size = input.size(1);
|
||||
CHECK_EQ(input.size(0), gate.size(0));
|
||||
CHECK_EQ(input.size(1), gate.size(1));
|
||||
at::Tensor output = at::empty_like(input);
|
||||
int64_t input_strideN = input.stride(0);
|
||||
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(input.scalar_type(), "fused_rmsnorm_gated_kernel", [&] {
|
||||
fused_rmsnorm_gated_kernel_impl<scalar_t>(
|
||||
output.data_ptr<scalar_t>(),
|
||||
input.data_ptr<scalar_t>(),
|
||||
weight.data_ptr<scalar_t>(),
|
||||
gate.data_ptr<scalar_t>(),
|
||||
batch_size,
|
||||
hidden_size,
|
||||
input_strideN,
|
||||
eps);
|
||||
});
|
||||
return output;
|
||||
}
|
||||
|
||||
// input : {batch_size, hidden_size}
|
||||
// residual: {batch_size, hidden_size}
|
||||
// weight : {hidden_size}
|
||||
void fused_add_rmsnorm_cpu(at::Tensor& input, at::Tensor& residual, at::Tensor& weight, double eps) {
|
||||
RECORD_FUNCTION("sgl-kernel::fused_add_rmsnorm_cpu", std::vector<c10::IValue>({input, residual, weight}));
|
||||
CHECK_LAST_DIM_CONTIGUOUS_INPUT(input);
|
||||
CHECK_INPUT(residual);
|
||||
CHECK_INPUT(weight);
|
||||
CHECK_DIM(2, input);
|
||||
CHECK_DIM(2, residual);
|
||||
CHECK_DIM(1, weight);
|
||||
CHECK_EQ(input.size(0), residual.size(0));
|
||||
CHECK_EQ(input.size(1), residual.size(1));
|
||||
CHECK_EQ(input.size(1), weight.size(0));
|
||||
int64_t batch_size = input.size(0);
|
||||
int64_t hidden_size = input.size(1);
|
||||
int64_t input_strideN = input.stride(0);
|
||||
|
||||
// allocate temp buffer to store x in float32 per thread
|
||||
// TODO: implement a singleton for context
|
||||
int64_t num_threads = at::get_num_threads();
|
||||
at::Tensor buffer = at::empty({num_threads, hidden_size}, input.options().dtype(at::kFloat));
|
||||
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(input.scalar_type(), "fused_add_rmsnorm_kernel", [&] {
|
||||
using Vec = at::vec::Vectorized<float>;
|
||||
fused_add_rmsnorm_kernel_impl<scalar_t>(
|
||||
input.data_ptr<scalar_t>(),
|
||||
residual.data_ptr<scalar_t>(),
|
||||
weight.data_ptr<scalar_t>(),
|
||||
buffer.data_ptr<float>(),
|
||||
batch_size,
|
||||
hidden_size,
|
||||
input_strideN,
|
||||
[](float x) { return x; },
|
||||
[](Vec x) { return x; },
|
||||
eps);
|
||||
});
|
||||
}
|
||||
|
||||
// input : {batch_size, hidden_size}
|
||||
// residual: {batch_size, hidden_size}
|
||||
// weight : {hidden_size}
|
||||
void gemma_fused_add_rmsnorm_cpu(at::Tensor& input, at::Tensor& residual, at::Tensor& weight, double eps) {
|
||||
RECORD_FUNCTION("sgl-kernel::gemma_fused_add_rmsnorm_cpu", std::vector<c10::IValue>({input, residual, weight}));
|
||||
CHECK_LAST_DIM_CONTIGUOUS_INPUT(input);
|
||||
CHECK_INPUT(residual);
|
||||
CHECK_INPUT(weight);
|
||||
CHECK_DIM(2, input);
|
||||
CHECK_DIM(2, residual);
|
||||
CHECK_DIM(1, weight);
|
||||
CHECK_EQ(input.size(0), residual.size(0));
|
||||
CHECK_EQ(input.size(1), residual.size(1));
|
||||
CHECK_EQ(input.size(1), weight.size(0));
|
||||
int64_t batch_size = input.size(0);
|
||||
int64_t hidden_size = input.size(1);
|
||||
int64_t input_strideN = input.stride(0);
|
||||
|
||||
// allocate temp buffer to store x in float32 per thread
|
||||
// TODO: implement a singleton for context
|
||||
int64_t num_threads = at::get_num_threads();
|
||||
at::Tensor buffer = at::empty({num_threads, hidden_size}, input.options().dtype(at::kFloat));
|
||||
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(input.scalar_type(), "gemma_fused_add_rmsnorm_kernel", [&] {
|
||||
using Vec = at::vec::Vectorized<float>;
|
||||
Vec one_vec = Vec(float(1));
|
||||
fused_add_rmsnorm_kernel_impl<scalar_t>(
|
||||
input.data_ptr<scalar_t>(),
|
||||
residual.data_ptr<scalar_t>(),
|
||||
weight.data_ptr<scalar_t>(),
|
||||
buffer.data_ptr<float>(),
|
||||
batch_size,
|
||||
hidden_size,
|
||||
input_strideN,
|
||||
[](float x) { return x + 1; },
|
||||
[one_vec](Vec x) { return x + one_vec; },
|
||||
eps);
|
||||
});
|
||||
}
|
||||
|
||||
// input : {batch_size, hidden_size} or {batch_size, seq_len, hidden_size}
|
||||
// residual: {batch_size, hidden_size} or {batch_size, seq_len, hidden_size}
|
||||
// weight : {hidden_size}
|
||||
// bias : {hidden_size}
|
||||
at::Tensor fused_add_layernorm_cpu(
|
||||
const at::Tensor& input,
|
||||
at::Tensor& residual,
|
||||
const at::Tensor& weight,
|
||||
const std::optional<at::Tensor>& bias,
|
||||
double eps) {
|
||||
RECORD_FUNCTION("sgl-kernel::fused_add_layernorm_cpu", std::vector<c10::IValue>({input, residual, weight}));
|
||||
CHECK_LAST_DIM_CONTIGUOUS_INPUT(input);
|
||||
CHECK_INPUT(residual);
|
||||
CHECK_INPUT(weight);
|
||||
int64_t inp_dim{input.dim()}, res_dim{residual.dim()};
|
||||
CHECK_EQ(inp_dim, res_dim);
|
||||
TORCH_CHECK(inp_dim == 2 || inp_dim == 3, "Expected input dim to be 2 or 3, but got ", inp_dim);
|
||||
TORCH_CHECK(res_dim == 2 || res_dim == 3, "Expected residual dim to be 2 or 3, but got ", res_dim);
|
||||
|
||||
CHECK_DIM(1, weight);
|
||||
if (bias.has_value()) {
|
||||
CHECK_DIM(1, bias.value());
|
||||
CHECK_EQ(bias.value().size(0), weight.size(0));
|
||||
}
|
||||
CHECK_EQ(input.size(0), residual.size(0));
|
||||
CHECK_EQ(input.size(1), residual.size(1));
|
||||
if (inp_dim == 3) {
|
||||
CHECK_EQ(input.size(2), residual.size(2));
|
||||
CHECK_EQ(input.size(2), weight.size(0));
|
||||
} else {
|
||||
CHECK_EQ(input.size(1), weight.size(0));
|
||||
}
|
||||
|
||||
int64_t batch_size{input.size(0)}, seq_len{1}, hidden_size{input.size(1)}, input_strideN{input.stride(0)};
|
||||
if (inp_dim == 3) {
|
||||
seq_len = input.size(1);
|
||||
hidden_size = input.size(2);
|
||||
input_strideN = input.stride(1);
|
||||
}
|
||||
at::Tensor output = at::empty_like(input);
|
||||
|
||||
// Allocate temp buffer to store x in float32 per thread
|
||||
// It is necessary to store FP32 precision of residual-add results to pass UT acc test
|
||||
int64_t num_threads = at::get_num_threads();
|
||||
at::Tensor buffer = at::empty({num_threads, hidden_size}, input.options().dtype(at::kFloat));
|
||||
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(input.scalar_type(), "fused_add_layernorm_kernel", [&] {
|
||||
fused_add_layernorm_kernel_impl<scalar_t>(
|
||||
output.data_ptr<scalar_t>(),
|
||||
input.data_ptr<scalar_t>(),
|
||||
residual.data_ptr<scalar_t>(),
|
||||
weight.data_ptr<scalar_t>(),
|
||||
conditional_data_ptr<scalar_t>(bias),
|
||||
buffer.data_ptr<float>(),
|
||||
batch_size,
|
||||
seq_len,
|
||||
hidden_size,
|
||||
input_strideN,
|
||||
eps);
|
||||
});
|
||||
return output;
|
||||
}
|
||||
98
third_party/sglang/sgl-kernel/csrc/cpu/numa_utils.cpp
vendored
Normal file
98
third_party/sglang/sgl-kernel/csrc/cpu/numa_utils.cpp
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
#include <numa.h>
|
||||
#include <sched.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "common.h"
|
||||
|
||||
std::string init_cpu_threads_env(const std::string& cpu_ids) {
|
||||
bitmask* omp_cpu_mask = numa_parse_cpustring(cpu_ids.c_str());
|
||||
TORCH_CHECK(omp_cpu_mask->size > 0);
|
||||
std::vector<int> omp_cpu_ids;
|
||||
omp_cpu_ids.reserve(omp_cpu_mask->size);
|
||||
|
||||
constexpr int group_size = 8 * sizeof(*omp_cpu_mask->maskp);
|
||||
|
||||
for (int offset = 0; offset < omp_cpu_mask->size; offset += group_size) {
|
||||
unsigned long group_mask = omp_cpu_mask->maskp[offset / group_size];
|
||||
int i = 0;
|
||||
while (group_mask) {
|
||||
if (group_mask & 1) {
|
||||
omp_cpu_ids.emplace_back(offset + i);
|
||||
}
|
||||
++i;
|
||||
group_mask >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Memory node binding
|
||||
if (numa_available() != -1) {
|
||||
TORCH_CHECK(!omp_cpu_ids.empty(), "Cannot bind memory, no CPUs specified.");
|
||||
int mem_node_id_st = numa_node_of_cpu(omp_cpu_ids.front());
|
||||
int mem_node_id_ed = numa_node_of_cpu(omp_cpu_ids.back());
|
||||
if (mem_node_id_st > mem_node_id_ed) {
|
||||
std::swap(mem_node_id_st, mem_node_id_ed);
|
||||
}
|
||||
|
||||
bitmask* mask =
|
||||
numa_parse_nodestring((std::to_string(mem_node_id_st) + "-" + std::to_string(mem_node_id_ed)).c_str());
|
||||
bitmask* src_mask = numa_get_membind();
|
||||
|
||||
int pid = getpid();
|
||||
|
||||
// move all existing pages to the specified numa node.
|
||||
*(src_mask->maskp) = *(src_mask->maskp) ^ *(mask->maskp);
|
||||
int page_num = numa_migrate_pages(pid, src_mask, mask);
|
||||
if (page_num == -1) {
|
||||
TORCH_WARN(false, "numa_migrate_pages failed. errno: " + std::to_string(errno));
|
||||
}
|
||||
|
||||
// restrict memory allocation node.
|
||||
numa_set_membind(mask);
|
||||
numa_set_strict(1);
|
||||
}
|
||||
|
||||
// OMP threads binding
|
||||
omp_set_num_threads((int)omp_cpu_ids.size());
|
||||
at::set_num_threads((int)omp_cpu_ids.size());
|
||||
TORCH_CHECK_EQ(omp_cpu_ids.size(), at::get_num_threads());
|
||||
TORCH_CHECK_EQ(omp_cpu_ids.size(), omp_get_max_threads());
|
||||
|
||||
std::vector<std::pair<int, int>> thread_core_mapping;
|
||||
thread_core_mapping.reserve(omp_cpu_ids.size());
|
||||
omp_lock_t writelock;
|
||||
omp_init_lock(&writelock);
|
||||
|
||||
#pragma omp parallel for schedule(static, 1)
|
||||
for (size_t i = 0; i < omp_cpu_ids.size(); ++i) {
|
||||
cpu_set_t mask;
|
||||
CPU_ZERO(&mask);
|
||||
CPU_SET(omp_cpu_ids[i], &mask);
|
||||
int ret = sched_setaffinity(0, sizeof(cpu_set_t), &mask);
|
||||
if (ret == -1) {
|
||||
TORCH_CHECK(false, "sched_setaffinity failed. errno: " + std::to_string(errno));
|
||||
}
|
||||
|
||||
omp_set_lock(&writelock);
|
||||
thread_core_mapping.emplace_back(syscall(SYS_gettid), omp_cpu_ids[i]);
|
||||
omp_unset_lock(&writelock);
|
||||
}
|
||||
|
||||
omp_destroy_lock(&writelock);
|
||||
|
||||
numa_free_nodemask(omp_cpu_mask);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "OMP threads binding of Process " << getpid() << ":\n";
|
||||
std::sort(
|
||||
thread_core_mapping.begin(), thread_core_mapping.end(), [](auto&& a, auto&& b) { return a.second < b.second; });
|
||||
for (auto&& item : thread_core_mapping) {
|
||||
ss << "\t"
|
||||
<< "OMP tid: " << item.first << ", core " << item.second << "\n";
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
373
third_party/sglang/sgl-kernel/csrc/cpu/preprocessor.cpp
vendored
Normal file
373
third_party/sglang/sgl-kernel/csrc/cpu/preprocessor.cpp
vendored
Normal file
@@ -0,0 +1,373 @@
|
||||
/*****************************************************************************************
|
||||
* Copyright (c) 2025 - 2025 Codeplay Software Ltd. All rights reserved.
|
||||
* Copyright (C) 2025 Intel Corporation, All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
****************************************************************************************/
|
||||
#include "common.h"
|
||||
#include "vec.h"
|
||||
|
||||
// [NOTE] Preprocessor Optimization
|
||||
// 1. this file is apple-to-apple to `Qwen2VLImageProcessorFast`.
|
||||
// 2. `out_dtype` set to torch.bfloat16 skips outplace dtype conversion.
|
||||
// 3. skip all redundant memory copy and dtype conversion.
|
||||
// 4. TODO: rewrite `_upsample_bicubic2d_aa`.
|
||||
//
|
||||
// ref: https://github.com/huggingface/transformers/blob/main/src/transformers
|
||||
// /models/qwen2_vl/image_processing_qwen2_vl_fast.py
|
||||
//
|
||||
namespace {
|
||||
|
||||
template <typename scalar_t>
|
||||
inline void normalize(
|
||||
scalar_t* __restrict__ out,
|
||||
const uint8_t* __restrict__ input,
|
||||
const std::vector<float>& image_mean,
|
||||
const std::vector<float>& image_std,
|
||||
int64_t channel,
|
||||
int64_t temporal_patch_size,
|
||||
int64_t patch_size,
|
||||
int64_t stride_ch,
|
||||
int64_t stride_pt,
|
||||
int64_t stride_ph) {
|
||||
TORCH_CHECK(false, "normalize: scalar path not implemented.");
|
||||
}
|
||||
|
||||
#if defined(CPU_CAPABILITY_AVX512)
|
||||
template <>
|
||||
inline void normalize<float>(
|
||||
float* __restrict__ out,
|
||||
const uint8_t* __restrict__ input,
|
||||
const std::vector<float>& image_mean,
|
||||
const std::vector<float>& image_std,
|
||||
int64_t channel,
|
||||
int64_t temporal_patch_size,
|
||||
int64_t patch_size,
|
||||
int64_t stride_ch,
|
||||
int64_t stride_pt,
|
||||
int64_t stride_ph) {
|
||||
// we do vectorization on patch_size dim
|
||||
assert(patch_size == 16);
|
||||
|
||||
// loop last 4 dimensions:
|
||||
// {channel, patch_t(repeated), patch_h, patch_w}
|
||||
for (int64_t c = 0; c < channel; ++c) {
|
||||
__m512 vmean = _mm512_set1_ps(image_mean[c]);
|
||||
__m512 vrstd = _mm512_set1_ps(1.f / image_std[c]);
|
||||
|
||||
float* __restrict__ out_ptr = out + c * temporal_patch_size * patch_size * patch_size;
|
||||
#pragma GCC unroll 4
|
||||
for (int64_t ph = 0; ph < patch_size; ++ph) {
|
||||
__m128i u8 = _mm_loadu_si128((const __m128i*)(input + c * stride_ch + /* pt */ 0 * stride_pt + ph * stride_ph));
|
||||
__m512 x = _mm512_cvtepi32_ps(_mm512_cvtepu8_epi32(u8));
|
||||
x = _mm512_mul_ps(_mm512_sub_ps(x, vmean), vrstd);
|
||||
#pragma GCC unroll 2
|
||||
for (int64_t pt = 0; pt < temporal_patch_size; ++pt) {
|
||||
_mm512_storeu_ps(out_ptr + pt * patch_size * patch_size + ph * patch_size, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void normalize<at::BFloat16>(
|
||||
at::BFloat16* __restrict__ out,
|
||||
const uint8_t* __restrict__ input,
|
||||
const std::vector<float>& image_mean,
|
||||
const std::vector<float>& image_std,
|
||||
int64_t channel,
|
||||
int64_t temporal_patch_size,
|
||||
int64_t patch_size,
|
||||
int64_t stride_ch,
|
||||
int64_t stride_pt,
|
||||
int64_t stride_ph) {
|
||||
// we do vectorization on patch_size dim
|
||||
assert(patch_size == 16);
|
||||
|
||||
// loop last 4 dimensions:
|
||||
// {channel, patch_t(repeated), patch_h, patch_w}
|
||||
for (int64_t c = 0; c < channel; ++c) {
|
||||
__m512 vmean = _mm512_set1_ps(image_mean[c]);
|
||||
__m512 vrstd = _mm512_set1_ps(1.f / image_std[c]);
|
||||
|
||||
at::BFloat16* __restrict__ out_ptr = out + c * temporal_patch_size * patch_size * patch_size;
|
||||
#pragma GCC unroll 4
|
||||
for (int64_t ph = 0; ph < patch_size; ++ph) {
|
||||
__m128i u8 = _mm_loadu_si128((const __m128i*)(input + c * stride_ch + /* pt */ 0 * stride_pt + ph * stride_ph));
|
||||
__m512 x = _mm512_cvtepi32_ps(_mm512_cvtepu8_epi32(u8));
|
||||
x = _mm512_mul_ps(_mm512_sub_ps(x, vmean), vrstd);
|
||||
__m256i x16 = (__m256i)_mm512_cvtneps_pbh(x);
|
||||
#pragma GCC unroll 2
|
||||
for (int64_t pt = 0; pt < temporal_patch_size; ++pt) {
|
||||
_mm256_storeu_si256(reinterpret_cast<__m256i*>(out_ptr + pt * patch_size * patch_size + ph * patch_size), x16);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
template <typename scalar_t>
|
||||
void rescale_and_normalize_kernel_impl(
|
||||
scalar_t* __restrict__ out,
|
||||
const uint8_t* __restrict__ input,
|
||||
const std::vector<float>& image_mean,
|
||||
const std::vector<float>& image_std,
|
||||
int64_t grid_t,
|
||||
int64_t grid_h,
|
||||
int64_t grid_w,
|
||||
int64_t merge_size,
|
||||
int64_t channel,
|
||||
int64_t temporal_patch_size,
|
||||
int64_t patch_size) {
|
||||
// [NOTE]: temporal patching uses repeat on last image
|
||||
//
|
||||
// input : {grid_t, patch_t, channel, grid_h, merge_h, patch_h, grid_w, merge_w, patch_w}
|
||||
// out : {grid_t, grid_h, grid_w, merge_h, merge_w, channel, patch_t, patch_h, patch_w}
|
||||
//
|
||||
int64_t height = grid_h * merge_size * patch_size;
|
||||
int64_t width = grid_w * merge_size * patch_size;
|
||||
|
||||
int64_t stride_gt = /* temporal_patch_size */ 1 * channel * height * width;
|
||||
int64_t stride_gh = merge_size * patch_size * width;
|
||||
int64_t stride_gw = merge_size * patch_size;
|
||||
int64_t stride_mh = patch_size * width;
|
||||
int64_t stride_mw = patch_size;
|
||||
int64_t stride_ch = height * width;
|
||||
int64_t stride_pt = channel * height * width;
|
||||
int64_t stride_ph = width;
|
||||
int64_t stride_grid = channel * temporal_patch_size * patch_size * patch_size;
|
||||
|
||||
// parallel on first 5 dims, aka, grids
|
||||
at::parallel_for(0, grid_t * grid_h * grid_w * merge_size * merge_size, 0, [&](int64_t begin, int64_t end) {
|
||||
int64_t gt{0}, gh{0}, gw{0}, mh{0}, mw{0};
|
||||
data_index_init(begin, gt, grid_t, gh, grid_h, gw, grid_w, mh, merge_size, mw, merge_size);
|
||||
|
||||
for (int64_t i = begin; i < end; ++i) {
|
||||
normalize<scalar_t>(
|
||||
out + i * stride_grid,
|
||||
input + gt * stride_gt + gh * stride_gh + gw * stride_gw + mh * stride_mh + mw * stride_mw,
|
||||
image_mean,
|
||||
image_std,
|
||||
channel,
|
||||
temporal_patch_size,
|
||||
patch_size,
|
||||
stride_ch,
|
||||
stride_pt,
|
||||
stride_ph);
|
||||
|
||||
// move to the next index
|
||||
data_index_step(gt, grid_t, gh, grid_h, gw, grid_w, mh, merge_size, mw, merge_size);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
void check_input_image(const at::Tensor& image) {
|
||||
TORCH_CHECK(image.scalar_type() == at::kByte, "expect image to be uint8.");
|
||||
TORCH_CHECK(image.dim() == 3, "expect image to be CHW.");
|
||||
}
|
||||
|
||||
// https://github.com/huggingface/transformers/blob/main/src/transformers/models/qwen2_vl/image_processing_qwen2_vl.py
|
||||
std::pair<int64_t, int64_t>
|
||||
smart_resize(int64_t height, int64_t width, int64_t factor, int64_t min_pixels, int64_t max_pixels) {
|
||||
// aspect ratio check
|
||||
int64_t mx = std::max(height, width);
|
||||
int64_t mn = std::min(height, width);
|
||||
|
||||
TORCH_CHECK(static_cast<double>(mx) / mn <= 200.0, "absolute aspect ratio must be smaller than 200");
|
||||
|
||||
// round to nearest multiple of factor
|
||||
auto round_to_factor = [&](int64_t x) {
|
||||
return static_cast<int64_t>(std::round(static_cast<double>(x) / factor)) * factor;
|
||||
};
|
||||
|
||||
int64_t h_bar = round_to_factor(height);
|
||||
int64_t w_bar = round_to_factor(width);
|
||||
|
||||
int64_t area = h_bar * w_bar;
|
||||
|
||||
if (area > max_pixels) {
|
||||
double beta = std::sqrt((1.0 * height * width) / max_pixels);
|
||||
h_bar = std::max(factor, (static_cast<int64_t>(std::floor(height / beta / factor)) * factor));
|
||||
w_bar = std::max(factor, (static_cast<int64_t>(std::floor(width / beta / factor)) * factor));
|
||||
} else if (area < min_pixels) {
|
||||
double beta = std::sqrt((double)min_pixels / (height * width));
|
||||
h_bar = static_cast<int64_t>(std::ceil(height * beta / factor)) * factor;
|
||||
w_bar = static_cast<int64_t>(std::ceil(width * beta / factor)) * factor;
|
||||
}
|
||||
|
||||
return {h_bar, w_bar};
|
||||
}
|
||||
|
||||
// do rescale and normalize
|
||||
// from `resized_image` to `pixel_values`
|
||||
void rescale_and_normalize_image(
|
||||
at::Tensor& pixel_values,
|
||||
const at::Tensor& image,
|
||||
double rescale_factor,
|
||||
c10::ArrayRef<double> image_mean,
|
||||
c10::ArrayRef<double> image_std,
|
||||
int64_t grid_t,
|
||||
int64_t grid_h,
|
||||
int64_t grid_w,
|
||||
int64_t merge_size,
|
||||
int64_t channel,
|
||||
int64_t temporal_patch_size,
|
||||
int64_t patch_size,
|
||||
int64_t grid_offset,
|
||||
int64_t grid_stride) {
|
||||
// update mean and std
|
||||
std::vector<float> mean_vec(channel), std_vec(channel);
|
||||
for (int64_t c = 0; c < channel; ++c) {
|
||||
mean_vec[c] = static_cast<float>(image_mean[c] * (1 / rescale_factor));
|
||||
std_vec[c] = static_cast<float>(image_std[c] * (1 / rescale_factor));
|
||||
}
|
||||
|
||||
AT_DISPATCH_FLOATING_TYPES_AND(at::kBFloat16, pixel_values.scalar_type(), "rescale_and_normalize_image", [&] {
|
||||
rescale_and_normalize_kernel_impl<scalar_t>(
|
||||
pixel_values.data_ptr<scalar_t>() + grid_offset * grid_stride,
|
||||
image.data_ptr<uint8_t>(),
|
||||
mean_vec,
|
||||
std_vec,
|
||||
grid_t,
|
||||
grid_h / merge_size,
|
||||
grid_w / merge_size,
|
||||
merge_size,
|
||||
channel,
|
||||
temporal_patch_size,
|
||||
patch_size);
|
||||
});
|
||||
}
|
||||
|
||||
std::tuple<at::Tensor, at::Tensor> image_preprocess_cpu(
|
||||
at::TensorList images,
|
||||
bool do_convert_rgb,
|
||||
bool do_resize,
|
||||
int64_t shortest_edge,
|
||||
int64_t longest_edge,
|
||||
const std::string& interpolation,
|
||||
bool do_rescale,
|
||||
double rescale_factor,
|
||||
bool do_normalize,
|
||||
c10::ArrayRef<double> image_mean,
|
||||
c10::ArrayRef<double> image_std,
|
||||
int64_t patch_size,
|
||||
int64_t temporal_patch_size,
|
||||
int64_t merge_size,
|
||||
bool disable_grouping,
|
||||
at::ScalarType out_dtype) {
|
||||
RECORD_FUNCTION("sgl_kernel::image_preprocess_cpu", std::vector<c10::IValue>({}));
|
||||
|
||||
// TODO: lift C++ kernel limitations
|
||||
TORCH_CHECK(interpolation == "bicubic", "image_preprocess_cpu: support only bicubic mode.");
|
||||
TORCH_CHECK(do_rescale && do_normalize, "image_preprocess_cpu: support only do_rescale and do_normalize.");
|
||||
TORCH_CHECK(disable_grouping, "image_preprocess_cpu: support only disable_grouping.");
|
||||
|
||||
// support only float32 or bfloat16 as output
|
||||
TORCH_CHECK(
|
||||
out_dtype == at::kFloat || out_dtype == at::kBFloat16,
|
||||
"image_preprocess_cpu: support only float32 and bfloat16 as pixel_values dtype.");
|
||||
|
||||
int64_t batch_size = images.size();
|
||||
int64_t channel = image_mean.size();
|
||||
CHECK_GT(batch_size, 0);
|
||||
CHECK_EQ(channel, image_std.size());
|
||||
CHECK_EQ(channel, 3);
|
||||
|
||||
const at::Tensor& first_image = images[0];
|
||||
const auto options = first_image.options();
|
||||
at::Tensor pixel_values = at::empty({}, options.dtype(out_dtype));
|
||||
at::Tensor image_grid_thw = at::empty({batch_size, channel}, options.dtype(at::kLong));
|
||||
|
||||
// index type use int64_t
|
||||
int64_t* image_grid_thw_data = image_grid_thw.data_ptr<int64_t>();
|
||||
|
||||
// resized image sizes and global grid offset
|
||||
std::vector<std::pair<int64_t, int64_t>> image_sizes(batch_size);
|
||||
std::vector<int64_t> grid_offsets(batch_size + 1, 0);
|
||||
|
||||
// Stage 1: compute resized shapes and fill in `image_grid_thw`
|
||||
for (int64_t idx = 0; idx < batch_size; ++idx) {
|
||||
const auto& image = images[idx];
|
||||
check_input_image(image);
|
||||
|
||||
auto [resized_h, resized_w] =
|
||||
smart_resize(image.size(-2), image.size(-1), patch_size * merge_size, shortest_edge, longest_edge);
|
||||
|
||||
image_sizes[idx] = {resized_h, resized_w};
|
||||
|
||||
// temporal dimension for image is 1
|
||||
int64_t grid_t = div_up((int64_t)1, temporal_patch_size);
|
||||
int64_t grid_h = div_up(resized_h, patch_size);
|
||||
int64_t grid_w = div_up(resized_w, patch_size);
|
||||
|
||||
// fill in image_grid_thw
|
||||
image_grid_thw_data[idx * 3 + 0] = grid_t;
|
||||
image_grid_thw_data[idx * 3 + 1] = grid_h;
|
||||
image_grid_thw_data[idx * 3 + 2] = grid_w;
|
||||
|
||||
// fill in global grid offset
|
||||
grid_offsets[idx + 1] = grid_offsets[idx] + grid_t * grid_h * grid_w;
|
||||
}
|
||||
|
||||
// last element holds the total sum of grids
|
||||
int64_t grid_size = grid_offsets[batch_size];
|
||||
int64_t grid_stride = channel * temporal_patch_size * patch_size * patch_size;
|
||||
// allocate memory
|
||||
pixel_values.resize_({grid_size, grid_stride});
|
||||
|
||||
// Stage 2: compute `pixel_values`
|
||||
for (int64_t idx = 0; idx < batch_size; ++idx) {
|
||||
const auto& image = images[idx];
|
||||
int64_t resized_h = image_sizes[idx].first;
|
||||
int64_t resized_w = image_sizes[idx].second;
|
||||
auto resized_image = at::_upsample_bicubic2d_aa(
|
||||
image.unsqueeze(0),
|
||||
{resized_h, resized_w},
|
||||
/* align_corners */ false);
|
||||
|
||||
rescale_and_normalize_image(
|
||||
pixel_values,
|
||||
resized_image,
|
||||
rescale_factor,
|
||||
image_mean,
|
||||
image_std,
|
||||
/* grid_t */ image_grid_thw_data[idx * 3 + 0],
|
||||
/* grid_h */ image_grid_thw_data[idx * 3 + 1],
|
||||
/* grid_w */ image_grid_thw_data[idx * 3 + 2],
|
||||
merge_size,
|
||||
channel,
|
||||
temporal_patch_size,
|
||||
patch_size,
|
||||
grid_offsets[idx],
|
||||
grid_stride);
|
||||
}
|
||||
|
||||
return std::make_tuple(pixel_values, image_grid_thw);
|
||||
}
|
||||
703
third_party/sglang/sgl-kernel/csrc/cpu/qkv_proj.cpp
vendored
Normal file
703
third_party/sglang/sgl-kernel/csrc/cpu/qkv_proj.cpp
vendored
Normal file
@@ -0,0 +1,703 @@
|
||||
#include "common.h"
|
||||
#include "gemm.h"
|
||||
#include "vec.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// [NOTE]: Fused kernel for QKV projection with weight absorption and RoPE
|
||||
//
|
||||
// 1. `q_a_proj` and `kv_a_proj_with_mqa` fused into one gemm,
|
||||
// otherwise we need to split IC for the 2nd gemm.
|
||||
// 2. `q_a_layernorm` and `kv_a_layernorm` fused into one parallel loop.
|
||||
// 3. k_input and v_input share the same storage, the torch API did
|
||||
// this in `set_kv_buffer`. No additional memory movement.
|
||||
//
|
||||
|
||||
// [C0, C1] = A @ [B0, B1]
|
||||
template <typename scalar_t>
|
||||
void segment_gemm_kernel_impl(
|
||||
scalar_t* __restrict__ C0,
|
||||
scalar_t* __restrict__ C1,
|
||||
const scalar_t* __restrict__ A,
|
||||
const scalar_t* __restrict__ B0,
|
||||
const scalar_t* __restrict__ B1,
|
||||
int64_t M,
|
||||
int64_t N0,
|
||||
int64_t N1,
|
||||
int64_t K) {
|
||||
// convert_weight_packed make sure N0 and N1 are 32x
|
||||
constexpr int64_t BLOCK_M = block_size_m();
|
||||
constexpr int64_t BLOCK_N = block_size_n();
|
||||
const int64_t MB = div_up(M, BLOCK_M);
|
||||
const int64_t NB0 = div_up(N0, BLOCK_N);
|
||||
const int64_t NB1 = div_up(N1, BLOCK_N);
|
||||
const int64_t NB = NB0 + NB1;
|
||||
|
||||
const bool use_brgemm = can_use_brgemm<scalar_t>(M);
|
||||
|
||||
// parallel on [MB, NB0 + NB1]
|
||||
at::parallel_for(0, MB * NB, 0, [&](int64_t begin, int64_t end) {
|
||||
int64_t mb{0}, nb{0};
|
||||
data_index_init(begin, mb, MB, nb, NB);
|
||||
|
||||
// for brgemm, use float32 for accumulate
|
||||
alignas(64) float Ctmp[BLOCK_M * BLOCK_N];
|
||||
|
||||
for (int64_t i = begin; i < end; ++i) {
|
||||
UNUSED(i);
|
||||
int mb_start = mb * BLOCK_M;
|
||||
int mb_size = std::min(M - mb_start, BLOCK_M);
|
||||
int nb_start = nb * BLOCK_N;
|
||||
int nb_size = BLOCK_N;
|
||||
|
||||
const scalar_t* __restrict__ B = nb < NB0 ? B0 : B1;
|
||||
scalar_t* __restrict__ C = nb < NB0 ? C0 : C1;
|
||||
int64_t ldc = nb < NB0 ? N0 : N1;
|
||||
int64_t local_nb_start = nb < NB0 ? nb_start : nb_start - N0;
|
||||
|
||||
tinygemm_kernel<scalar_t>(
|
||||
/* A */ A + mb_start * K,
|
||||
/* B */ B + local_nb_start * K /* nb * BLOCK_N * K */,
|
||||
/* C */ C + mb_start * ldc + local_nb_start,
|
||||
/* Ctmp*/ Ctmp,
|
||||
/* M */ mb_size,
|
||||
/* N */ nb_size,
|
||||
/* K */ K,
|
||||
/* lda */ K,
|
||||
/* ldb */ nb_size,
|
||||
/* ldc */ ldc,
|
||||
/* brg */ use_brgemm);
|
||||
|
||||
// move to the next index
|
||||
data_index_step(mb, MB, nb, NB);
|
||||
}
|
||||
|
||||
if (use_brgemm) {
|
||||
at::native::cpublas::brgemm_release();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// [C0, C1] = A @ [B0, B1]
|
||||
template <typename scalar_t>
|
||||
void segment_gemm_kernel_impl(
|
||||
scalar_t* __restrict__ C0,
|
||||
scalar_t* __restrict__ C1,
|
||||
const uint8_t* __restrict__ A,
|
||||
const int8_t* __restrict__ B0,
|
||||
const int8_t* __restrict__ B1,
|
||||
const float* __restrict__ As,
|
||||
const float* __restrict__ Bs0,
|
||||
const float* __restrict__ Bs1,
|
||||
int64_t M,
|
||||
int64_t N0,
|
||||
int64_t N1,
|
||||
int64_t K) {
|
||||
constexpr int64_t BLOCK_M = block_size_m();
|
||||
constexpr int64_t BLOCK_N = block_size_n();
|
||||
const int64_t MB = div_up(M, BLOCK_M);
|
||||
const int64_t NB0 = div_up(N0, BLOCK_N);
|
||||
const int64_t NB1 = div_up(N1, BLOCK_N);
|
||||
const int64_t NB = NB0 + NB1;
|
||||
|
||||
const bool use_brgemm = can_use_brgemm<int8_t>(M);
|
||||
|
||||
// K + 4 after compensation
|
||||
const int64_t packed_row_size = get_row_size<int8_t>(K);
|
||||
|
||||
// parallel on [MB, NB0 + NB1]
|
||||
at::parallel_for(0, MB * NB, 0, [&](int64_t begin, int64_t end) {
|
||||
int64_t mb{0}, nb{0};
|
||||
data_index_init(begin, mb, MB, nb, NB);
|
||||
|
||||
// for brgemm, use float32 for accumulate
|
||||
alignas(64) int32_t Ctmp[BLOCK_M * BLOCK_N];
|
||||
|
||||
for (int64_t i = begin; i < end; ++i) {
|
||||
UNUSED(i);
|
||||
int mb_start = mb * BLOCK_M;
|
||||
int mb_size = std::min(M - mb_start, BLOCK_M);
|
||||
int nb_start = nb * BLOCK_N;
|
||||
int nb_size = BLOCK_N;
|
||||
|
||||
const int8_t* __restrict__ B = nb < NB0 ? B0 : B1;
|
||||
const float* __restrict__ Bs = nb < NB0 ? Bs0 : Bs1;
|
||||
scalar_t* __restrict__ C = nb < NB0 ? C0 : C1;
|
||||
int64_t ldc = nb < NB0 ? N0 : N1;
|
||||
int64_t local_nb_start = nb < NB0 ? nb_start : nb_start - N0;
|
||||
|
||||
tinygemm_kernel<scalar_t>(
|
||||
/* A */ A + mb_start * K,
|
||||
/* B */ B + local_nb_start * packed_row_size /* nb * BLOCK_N * (K + 4) */,
|
||||
/* C */ C + mb_start * ldc + local_nb_start,
|
||||
/* Ctmp*/ Ctmp,
|
||||
/* As */ As + mb_start,
|
||||
/* Bs */ Bs + local_nb_start,
|
||||
/* M */ mb_size,
|
||||
/* N */ nb_size,
|
||||
/* K */ K,
|
||||
/* lda */ K,
|
||||
/* ldb */ nb_size,
|
||||
/* ldc */ ldc,
|
||||
/* brg */ use_brgemm);
|
||||
|
||||
// move to the next index
|
||||
data_index_step(mb, MB, nb, NB);
|
||||
}
|
||||
|
||||
if (use_brgemm) {
|
||||
at::native::cpublas::brgemm_release();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// [C0, C1] = A @ [B0, B1]
|
||||
template <typename scalar_t>
|
||||
void segment_gemm_kernel_impl(
|
||||
scalar_t* __restrict__ C0,
|
||||
scalar_t* __restrict__ C1,
|
||||
const scalar_t* __restrict__ A,
|
||||
const at::Float8_e4m3fn* __restrict__ B0,
|
||||
const at::Float8_e4m3fn* __restrict__ B1,
|
||||
const float* __restrict__ Bs0,
|
||||
const float* __restrict__ Bs1,
|
||||
scalar_t* __restrict__ Btmp,
|
||||
int64_t M,
|
||||
int64_t N0,
|
||||
int64_t N1,
|
||||
int64_t K,
|
||||
int64_t block_size_N,
|
||||
int64_t block_size_K) {
|
||||
constexpr int64_t BLOCK_M = block_size_m();
|
||||
constexpr int64_t BLOCK_N = block_size_n();
|
||||
const int64_t MB = div_up(M, BLOCK_M);
|
||||
const int64_t NB0 = div_up(N0, BLOCK_N);
|
||||
const int64_t NB1 = div_up(N1, BLOCK_N);
|
||||
const int64_t NB = NB0 + NB1;
|
||||
|
||||
const int64_t scale_size_K = div_up(K, block_size_K);
|
||||
const int64_t blocks_n_per_group = block_size_N / BLOCK_N;
|
||||
|
||||
const bool use_brgemm = can_use_brgemm<at::Float8_e4m3fn>(M);
|
||||
|
||||
// parallel on [MB, NB0 + NB1]
|
||||
at::parallel_for(0, MB * NB, 0, [&](int64_t begin, int64_t end) {
|
||||
int64_t mb{0}, nb{0};
|
||||
data_index_init(begin, mb, MB, nb, NB);
|
||||
|
||||
int tid = at::get_thread_num();
|
||||
// for brgemm, use float32 for accumulate
|
||||
alignas(64) float Ctmp[BLOCK_M * BLOCK_N];
|
||||
|
||||
for (int64_t i = begin; i < end; ++i) {
|
||||
UNUSED(i);
|
||||
|
||||
int mb_start = mb * BLOCK_M;
|
||||
int mb_size = std::min(M - mb_start, BLOCK_M);
|
||||
int nb_start = nb * BLOCK_N;
|
||||
int nb_size = BLOCK_N;
|
||||
|
||||
const at::Float8_e4m3fn* __restrict__ B = nb < NB0 ? B0 : B1;
|
||||
const float* __restrict__ Bs = nb < NB0 ? Bs0 : Bs1;
|
||||
scalar_t* __restrict__ C = nb < NB0 ? C0 : C1;
|
||||
int64_t ldc = nb < NB0 ? N0 : N1;
|
||||
int64_t local_nb_start = nb < NB0 ? nb_start : nb_start - N0;
|
||||
int64_t new_nb = nb < NB0 ? nb : nb - NB0;
|
||||
|
||||
tinygemm_kernel<scalar_t>(
|
||||
/* A */ A + mb_start * K,
|
||||
/* B */ B + local_nb_start * K /* nb * BLOCK_N * K */,
|
||||
/* C */ C + mb_start * ldc + local_nb_start,
|
||||
/* Btmp*/ Btmp + tid * BLOCK_N * K,
|
||||
/* Ctmp*/ Ctmp,
|
||||
/* Bs */ Bs + (new_nb / blocks_n_per_group) * scale_size_K,
|
||||
/* M */ mb_size,
|
||||
/* N */ nb_size,
|
||||
/* K */ K,
|
||||
/* lda */ K,
|
||||
/* ldb */ nb_size,
|
||||
/* ldc */ ldc,
|
||||
/* brg */ use_brgemm,
|
||||
/* block_size_K */ block_size_K);
|
||||
|
||||
// move to the next index
|
||||
data_index_step(mb, MB, nb, NB);
|
||||
}
|
||||
|
||||
if (use_brgemm) {
|
||||
at::native::cpublas::brgemm_release();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
inline float reduce(const scalar_t* __restrict__ x, int64_t size) {
|
||||
using bVec = at::vec::Vectorized<scalar_t>;
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
fVec sum_fvec = fVec(float(0));
|
||||
|
||||
// no remainder
|
||||
#pragma GCC unroll 4
|
||||
for (int64_t d = 0; d < size; d += bVec::size()) {
|
||||
bVec x_bvec = bVec::loadu(x + d);
|
||||
fVec x_fvec0, x_fvec1;
|
||||
std::tie(x_fvec0, x_fvec1) = at::vec::convert_to_float(x_bvec);
|
||||
sum_fvec += x_fvec0 * x_fvec0;
|
||||
sum_fvec += x_fvec1 * x_fvec1;
|
||||
}
|
||||
return vec_reduce_sum(sum_fvec);
|
||||
}
|
||||
|
||||
// map2 from aten functional doesn't have fast bf16->fp32 conversion
|
||||
template <typename scalar_t>
|
||||
inline void map2(scalar_t* y, const scalar_t* x, const scalar_t* __restrict__ w, float scale, int64_t size) {
|
||||
using bVec = at::vec::Vectorized<scalar_t>;
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
fVec scale_fvec = fVec(scale);
|
||||
|
||||
// no remainder
|
||||
#pragma GCC unroll 4
|
||||
for (int64_t d = 0; d < size; d += bVec::size()) {
|
||||
bVec x_bvec = bVec::loadu(x + d);
|
||||
fVec x_fvec0, x_fvec1;
|
||||
std::tie(x_fvec0, x_fvec1) = at::vec::convert_to_float(x_bvec);
|
||||
bVec w_bvec = bVec::loadu(w + d);
|
||||
fVec w_fvec0, w_fvec1;
|
||||
std::tie(w_fvec0, w_fvec1) = at::vec::convert_to_float(w_bvec);
|
||||
x_fvec0 = x_fvec0 * scale_fvec * w_fvec0;
|
||||
x_fvec1 = x_fvec1 * scale_fvec * w_fvec1;
|
||||
bVec out_bvec = convert_from_float_ext<scalar_t>(x_fvec0, x_fvec1);
|
||||
out_bvec.store(y + d);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
void rms_norm_kernel_impl(
|
||||
scalar_t* __restrict__ input0,
|
||||
scalar_t* __restrict__ input1,
|
||||
const scalar_t* __restrict__ weight0,
|
||||
const scalar_t* __restrict__ weight1,
|
||||
int64_t M,
|
||||
int64_t N0,
|
||||
int64_t N1,
|
||||
int64_t stride1,
|
||||
float eps = 1e-5) {
|
||||
at::parallel_for(0, M, 0, [&](int64_t begin, int64_t end) {
|
||||
for (int64_t m = begin; m < end; ++m) {
|
||||
scalar_t* x0 = input0 + m * N0;
|
||||
scalar_t* x1 = input1 + m * stride1;
|
||||
float scale0 = reduce(x0, N0);
|
||||
float scale1 = reduce(x1, N1);
|
||||
scale0 = float(1) / std::sqrt(scale0 / N0 + eps);
|
||||
scale1 = float(1) / std::sqrt(scale1 / N1 + eps);
|
||||
map2(x0, x0, weight0, scale0, N0);
|
||||
map2(x1, x1, weight1, scale1, N1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
inline void rotary(const scalar_t* input, scalar_t* out, const scalar_t* cos, const scalar_t* sin, int64_t size) {
|
||||
TORCH_CHECK(false, "rotary scalar path not implemented.");
|
||||
}
|
||||
|
||||
#if defined(CPU_CAPABILITY_AVX512)
|
||||
template <>
|
||||
inline void rotary<at::BFloat16>(
|
||||
const at::BFloat16* input, at::BFloat16* out, const at::BFloat16* cos, const at::BFloat16* sin, int64_t size) {
|
||||
// permute indices
|
||||
const __m512i idx1 = _mm512_set_epi32(30, 28, 26, 24, 22, 20, 18, 16, 14, 12, 10, 8, 6, 4, 2, 0);
|
||||
const __m512i idx2 = _mm512_set_epi32(31, 29, 27, 25, 23, 21, 19, 17, 15, 13, 11, 9, 7, 5, 3, 1);
|
||||
const __m512i idy1 = _mm512_set_epi32(23, 7, 22, 6, 21, 5, 20, 4, 19, 3, 18, 2, 17, 1, 16, 0);
|
||||
const __m512i idy2 = _mm512_set_epi32(31, 15, 30, 14, 29, 13, 28, 12, 27, 11, 26, 10, 25, 9, 24, 8);
|
||||
|
||||
// rotary dim is 64, just 2 iters
|
||||
#pragma GCC unroll 2
|
||||
for (int64_t d = 0; d < size; d += 32) {
|
||||
int64_t d2 = d >> 1;
|
||||
// load coefs
|
||||
__m512 vcos = CVT_BF16_TO_FP32(_mm256_loadu_si256(reinterpret_cast<const __m256i*>(cos + d2)));
|
||||
__m512 vsin = CVT_BF16_TO_FP32(_mm256_loadu_si256(reinterpret_cast<const __m256i*>(sin + d2)));
|
||||
// load input
|
||||
__m512i a16 = _mm512_loadu_si512(reinterpret_cast<const __m512i*>(input + d));
|
||||
__m512 a = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32(a16, 0));
|
||||
__m512 b = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32(a16, 1));
|
||||
// from [16, 2] to [2, 16]
|
||||
__m512 in1 = _mm512_mask_permutex2var_ps(a, 0xffff, idx1, b);
|
||||
__m512 in2 = _mm512_mask_permutex2var_ps(a, 0xffff, idx2, b);
|
||||
// out1 = in1 * cos - in2 * sin;
|
||||
// out2 = in2 * cos + in1 * sin
|
||||
__m512 out1 = _mm512_sub_ps(_mm512_mul_ps(in1, vcos), _mm512_mul_ps(in2, vsin));
|
||||
__m512 out2 = _mm512_add_ps(_mm512_mul_ps(in2, vcos), _mm512_mul_ps(in1, vsin));
|
||||
// from [2, 16] to [16, 2]
|
||||
a = _mm512_mask_permutex2var_ps(out1, 0xffff, idy1, out2);
|
||||
b = _mm512_mask_permutex2var_ps(out1, 0xffff, idy2, out2);
|
||||
|
||||
_mm512_storeu_si512(reinterpret_cast<__m512i*>((out + d)), (__m512i)(_mm512_cvtne2ps_pbh(b, a)));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
template <typename scalar_t>
|
||||
void rotary_emb_kernel_impl(
|
||||
scalar_t* q_pe_out,
|
||||
scalar_t* k_pe_out,
|
||||
const scalar_t* q_pe,
|
||||
const scalar_t* k_pe,
|
||||
const int64_t* pos,
|
||||
const scalar_t* cos_sin,
|
||||
int64_t num_seqs,
|
||||
int64_t num_heads,
|
||||
int64_t rotary_dim,
|
||||
int64_t q_strideB,
|
||||
int64_t q_strideH,
|
||||
int64_t k_strideB,
|
||||
int64_t oq_strideB,
|
||||
int64_t oq_strideH,
|
||||
int64_t ok_strideB) {
|
||||
TORCH_CHECK(rotary_dim % 32 == 0, "rotary_dim is not 32x.");
|
||||
const int64_t rotary_offset = rotary_dim / 2;
|
||||
|
||||
// parallel on [num_seqs, num_heads + 1]
|
||||
// top [num_heads] handle q_pe and bottom [1] handle k_pe
|
||||
at::parallel_for(0, num_seqs * (num_heads + 1), GRAIN_SIZE / rotary_dim, [&](int64_t begin, int64_t end) {
|
||||
int64_t seq{0}, head_id{0};
|
||||
data_index_init(begin, seq, num_seqs, head_id, num_heads + 1);
|
||||
|
||||
for (int64_t i = begin; i < end; ++i) {
|
||||
UNUSED(i);
|
||||
// get cos and sin cache ptr
|
||||
int64_t index = pos[seq];
|
||||
const scalar_t* cos = cos_sin + index * rotary_dim;
|
||||
const scalar_t* sin = cos + rotary_offset;
|
||||
|
||||
const scalar_t* input =
|
||||
(head_id < num_heads) ? q_pe + seq * q_strideB + head_id * q_strideH : k_pe + seq * k_strideB;
|
||||
scalar_t* out =
|
||||
(head_id < num_heads) ? q_pe_out + seq * oq_strideB + head_id * oq_strideH : k_pe_out + seq * ok_strideB;
|
||||
rotary<scalar_t>(input, out, cos, sin, rotary_dim);
|
||||
|
||||
// move to the next index
|
||||
data_index_step(seq, num_seqs, head_id, num_heads + 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
extern at::Tensor
|
||||
weight_packed_linear(at::Tensor& mat1, at::Tensor& mat2, const std::optional<at::Tensor>& bias, bool is_vnni);
|
||||
|
||||
extern at::Tensor int8_scaled_mm_with_quant(
|
||||
at::Tensor& mat1,
|
||||
at::Tensor& mat2,
|
||||
at::Tensor& scales2,
|
||||
const std::optional<at::Tensor>& bias,
|
||||
at::ScalarType out_dtype,
|
||||
bool is_vnni);
|
||||
|
||||
extern void
|
||||
bmm_cpu(at::Tensor& out, at::Tensor& mat1, at::Tensor& mat2, bool is_vnni, const std::optional<at::Tensor>& scale);
|
||||
|
||||
extern at::Tensor fp8_scaled_mm_cpu(
|
||||
at::Tensor& mat1,
|
||||
at::Tensor& mat2,
|
||||
at::Tensor& scales2,
|
||||
std::vector<int64_t> block_size,
|
||||
const std::optional<at::Tensor>& bias,
|
||||
at::ScalarType out_dtype,
|
||||
bool is_vnni);
|
||||
|
||||
// NB: shapes in DeepDeek R1
|
||||
//
|
||||
// hidden_states : [num_seqs, hidden_size] [1, 7168]
|
||||
// q_a_proj_weight : [q_lora_rank, hidden_size] [1536, 7168]
|
||||
// q_b_proj_weight : [num_heads * qk_head_dim, q_lora_rank] [4224, 1536]
|
||||
// kv_a_proj_weight : [kv_lora_rank + qk_rope_head_dim, hidden_size] [576, 7168]
|
||||
// w_kc : [num_heads, kv_lora_rank, qk_nope_head_dim] [22, 512, 128]
|
||||
// q_a_layernorm_weight : [q_lora_rank] [1536]
|
||||
// kv_a_layernorm_weight : [kv_lora_rank] [512]
|
||||
//
|
||||
std::tuple<at::Tensor, at::Tensor, at::Tensor> qkv_proj_with_rope(
|
||||
at::Tensor& hidden_states,
|
||||
at::Tensor& q_a_proj_weight,
|
||||
at::Tensor& q_b_proj_weight,
|
||||
at::Tensor& kv_a_proj_weight,
|
||||
at::Tensor& w_kc,
|
||||
at::Tensor& q_a_layernorm_weight,
|
||||
at::Tensor& kv_a_layernorm_weight,
|
||||
at::Tensor& positions,
|
||||
at::Tensor& cos_sin_cache,
|
||||
double eps,
|
||||
bool use_int8_w8a8,
|
||||
bool use_fp8_w8a16,
|
||||
std::optional<at::Tensor> q_a_proj_scale,
|
||||
std::optional<at::Tensor> q_b_proj_scale,
|
||||
std::optional<at::Tensor> kv_a_proj_scale,
|
||||
std::optional<at::Tensor> w_scale,
|
||||
bool is_vnni,
|
||||
std::optional<std::vector<int64_t>> block_size) {
|
||||
RECORD_FUNCTION(
|
||||
"sgl-kernel::qkv_proj_with_rope",
|
||||
std::vector<c10::IValue>({hidden_states, q_a_proj_weight, q_b_proj_weight, kv_a_proj_weight, w_kc}));
|
||||
|
||||
const auto st = hidden_states.scalar_type();
|
||||
CHECK_INPUT(hidden_states);
|
||||
CHECK_INPUT(positions);
|
||||
CHECK_INPUT(cos_sin_cache);
|
||||
CHECK_EQ(q_a_layernorm_weight.scalar_type(), st);
|
||||
CHECK_EQ(kv_a_layernorm_weight.scalar_type(), st);
|
||||
CHECK_EQ(positions.scalar_type(), at::kLong);
|
||||
CHECK_EQ(cos_sin_cache.scalar_type(), st);
|
||||
CHECK_DIM(2, hidden_states);
|
||||
CHECK_DIM(3, w_kc);
|
||||
CHECK_DIM(1, q_a_layernorm_weight);
|
||||
CHECK_DIM(1, kv_a_layernorm_weight);
|
||||
CHECK_DIM(1, positions);
|
||||
CHECK_DIM(2, cos_sin_cache);
|
||||
|
||||
// skip contiguous checks for weights, expect prepacked
|
||||
TORCH_CHECK(is_vnni, "qkv_proj_with_rope: expect weights are prepacked!");
|
||||
|
||||
int64_t num_seqs = hidden_states.size(0);
|
||||
int64_t hidden_size = hidden_states.size(1);
|
||||
int64_t q_lora_rank = q_a_proj_weight.size(0);
|
||||
int64_t num_heads = w_kc.size(0);
|
||||
int64_t kv_lora_rank = w_kc.size(1);
|
||||
int64_t qk_head_dim = q_b_proj_weight.size(0) / num_heads;
|
||||
int64_t qk_nope_head_dim = w_kc.size(2);
|
||||
int64_t qk_rope_head_dim = kv_a_proj_weight.size(0) - kv_lora_rank;
|
||||
int64_t rotary_dim = cos_sin_cache.size(1);
|
||||
|
||||
CHECK_EQ(positions.numel(), num_seqs);
|
||||
CHECK_EQ(rotary_dim, qk_rope_head_dim);
|
||||
CHECK_EQ(q_a_layernorm_weight.numel(), q_lora_rank);
|
||||
CHECK_EQ(kv_a_layernorm_weight.numel(), kv_lora_rank);
|
||||
|
||||
// check the packed dimension
|
||||
CHECK_EQ(q_a_proj_weight.size(1), get_row_size(hidden_size, use_int8_w8a8));
|
||||
CHECK_EQ(q_b_proj_weight.size(1), get_row_size(q_lora_rank, use_int8_w8a8));
|
||||
CHECK_EQ(kv_a_proj_weight.size(1), get_row_size(hidden_size, use_int8_w8a8));
|
||||
|
||||
if (use_int8_w8a8) {
|
||||
TORCH_CHECK(q_a_proj_scale.has_value(), "missing q_a_proj_scale for int8 w8a8.");
|
||||
TORCH_CHECK(q_b_proj_scale.has_value(), "missing q_b_proj_scale for int8 w8a8.");
|
||||
TORCH_CHECK(kv_a_proj_scale.has_value(), "missing kv_a_proj_scale for int8 w8a8.");
|
||||
}
|
||||
if (use_fp8_w8a16) {
|
||||
TORCH_CHECK(q_a_proj_scale.has_value(), "missing q_a_proj_scale for fp8 w8a16.");
|
||||
TORCH_CHECK(q_b_proj_scale.has_value(), "missing q_b_proj_scale for fp8 w8a16.");
|
||||
TORCH_CHECK(kv_a_proj_scale.has_value(), "missing kv_a_proj_scale for fp8 w8a16.");
|
||||
TORCH_CHECK(block_size.has_value(), "missing block_size for fp8 w8a16.");
|
||||
TORCH_CHECK(block_size.value().size() == 2, "block_size should be 2D for fp8 w8a16.");
|
||||
}
|
||||
// outputs and temp buffer
|
||||
const auto options = hidden_states.options();
|
||||
auto q_input = at::empty({num_seqs, num_heads, kv_lora_rank + qk_rope_head_dim}, options);
|
||||
auto k_input = at::empty({num_seqs, 1, kv_lora_rank + qk_rope_head_dim}, options);
|
||||
auto v_input = k_input.narrow(-1, 0, kv_lora_rank);
|
||||
|
||||
// outputs of q_a_proj and q_b_proj
|
||||
auto qa = at::empty({num_seqs, q_lora_rank}, options);
|
||||
|
||||
// stage 1: q_a_proj and kv_a_proj
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(st, "qkv_proj_kernel_impl", [&] {
|
||||
if (use_int8_w8a8) {
|
||||
auto q_a_proj_s = q_a_proj_scale.value();
|
||||
auto kv_a_proj_s = kv_a_proj_scale.value();
|
||||
TORCH_CHECK(q_a_proj_s.numel() == q_lora_rank);
|
||||
TORCH_CHECK(kv_a_proj_s.numel() == kv_lora_rank + qk_rope_head_dim);
|
||||
|
||||
auto buffer = at::empty({num_seqs * hidden_size + num_seqs * 4}, options.dtype(at::kByte));
|
||||
uint8_t* __restrict__ Aq_data = buffer.data_ptr<uint8_t>();
|
||||
float* __restrict__ As_data = (float*)((void*)(Aq_data + num_seqs * hidden_size));
|
||||
const scalar_t* __restrict__ A_data = hidden_states.data_ptr<scalar_t>();
|
||||
|
||||
at::parallel_for(0, num_seqs, 0, [&](int64_t begin, int64_t end) {
|
||||
for (int64_t m = begin; m < end; ++m) {
|
||||
quantize_row_int8<scalar_t>(Aq_data + m * hidden_size, As_data[m], A_data + m * hidden_size, hidden_size);
|
||||
}
|
||||
});
|
||||
|
||||
segment_gemm_kernel_impl<scalar_t>(
|
||||
qa.data_ptr<scalar_t>(),
|
||||
k_input.data_ptr<scalar_t>(),
|
||||
Aq_data,
|
||||
q_a_proj_weight.data_ptr<int8_t>(),
|
||||
kv_a_proj_weight.data_ptr<int8_t>(),
|
||||
As_data,
|
||||
q_a_proj_s.data_ptr<float>(),
|
||||
kv_a_proj_s.data_ptr<float>(),
|
||||
num_seqs,
|
||||
q_lora_rank,
|
||||
kv_lora_rank + qk_rope_head_dim,
|
||||
hidden_size);
|
||||
} else if (use_fp8_w8a16) {
|
||||
int64_t block_size_N = block_size.value()[0];
|
||||
int64_t block_size_K = block_size.value()[1];
|
||||
auto q_a_proj_s = q_a_proj_scale.value();
|
||||
auto kv_a_proj_s = kv_a_proj_scale.value();
|
||||
CHECK_EQ(q_a_proj_s.size(0), div_up(q_lora_rank, block_size_N));
|
||||
CHECK_EQ(q_a_proj_s.size(1), div_up(hidden_size, block_size_K));
|
||||
CHECK_EQ(kv_a_proj_s.size(0), div_up(kv_lora_rank + qk_rope_head_dim, block_size_N));
|
||||
CHECK_EQ(kv_a_proj_s.size(1), div_up(hidden_size, block_size_K));
|
||||
|
||||
const int BLOCK_N = block_size_n();
|
||||
const int num_threads = at::get_num_threads();
|
||||
auto buffer = at::empty({num_threads, BLOCK_N * hidden_size}, options);
|
||||
segment_gemm_kernel_impl<scalar_t>(
|
||||
qa.data_ptr<scalar_t>(),
|
||||
k_input.data_ptr<scalar_t>(),
|
||||
hidden_states.data_ptr<scalar_t>(),
|
||||
q_a_proj_weight.data_ptr<at::Float8_e4m3fn>(),
|
||||
kv_a_proj_weight.data_ptr<at::Float8_e4m3fn>(),
|
||||
q_a_proj_s.data_ptr<float>(),
|
||||
kv_a_proj_s.data_ptr<float>(),
|
||||
buffer.data_ptr<scalar_t>(),
|
||||
num_seqs,
|
||||
q_lora_rank,
|
||||
kv_lora_rank + qk_rope_head_dim,
|
||||
hidden_size,
|
||||
block_size_N,
|
||||
block_size_K);
|
||||
} else {
|
||||
segment_gemm_kernel_impl<scalar_t>(
|
||||
qa.data_ptr<scalar_t>(),
|
||||
k_input.data_ptr<scalar_t>(),
|
||||
hidden_states.data_ptr<scalar_t>(),
|
||||
q_a_proj_weight.data_ptr<scalar_t>(),
|
||||
kv_a_proj_weight.data_ptr<scalar_t>(),
|
||||
num_seqs,
|
||||
q_lora_rank,
|
||||
kv_lora_rank + qk_rope_head_dim,
|
||||
hidden_size);
|
||||
}
|
||||
});
|
||||
|
||||
// stage 2: apply rmsnorm inplace
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(st, "rms_norm_kernel_impl", [&] {
|
||||
rms_norm_kernel_impl<scalar_t>(
|
||||
qa.data_ptr<scalar_t>(),
|
||||
v_input.data_ptr<scalar_t>(),
|
||||
q_a_layernorm_weight.data_ptr<scalar_t>(),
|
||||
kv_a_layernorm_weight.data_ptr<scalar_t>(),
|
||||
num_seqs,
|
||||
q_lora_rank,
|
||||
kv_lora_rank,
|
||||
kv_lora_rank + qk_rope_head_dim,
|
||||
eps);
|
||||
});
|
||||
|
||||
// stage 3: q_b_proj
|
||||
at::Tensor qb;
|
||||
std::optional<at::Tensor> bias;
|
||||
if (use_int8_w8a8) {
|
||||
qb = int8_scaled_mm_with_quant(qa, q_b_proj_weight, q_b_proj_scale.value(), bias, at::kBFloat16, is_vnni);
|
||||
} else if (use_fp8_w8a16) {
|
||||
qb = fp8_scaled_mm_cpu(
|
||||
qa, q_b_proj_weight, q_b_proj_scale.value(), block_size.value(), bias, at::kBFloat16, is_vnni);
|
||||
} else {
|
||||
qb = weight_packed_linear(qa, q_b_proj_weight, bias, is_vnni);
|
||||
}
|
||||
qb.as_strided_({num_seqs, num_heads, qk_head_dim}, {num_heads * qk_head_dim, qk_head_dim, 1});
|
||||
|
||||
// stage 4: bmm
|
||||
auto q_nope = qb.narrow(2, 0, qk_nope_head_dim).transpose_(0, 1);
|
||||
auto q_nope_out = q_input.narrow(2, 0, kv_lora_rank).transpose_(0, 1);
|
||||
bmm_cpu(q_nope_out, q_nope, w_kc, is_vnni, w_scale);
|
||||
|
||||
// stage 5: rope
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(st, "rotary_emb_kernel_impl", [&] {
|
||||
rotary_emb_kernel_impl<scalar_t>(
|
||||
q_input.data_ptr<scalar_t>() + kv_lora_rank,
|
||||
k_input.data_ptr<scalar_t>() + kv_lora_rank,
|
||||
qb.data_ptr<scalar_t>() + qk_nope_head_dim,
|
||||
k_input.data_ptr<scalar_t>() + kv_lora_rank,
|
||||
positions.data_ptr<int64_t>(),
|
||||
cos_sin_cache.data_ptr<scalar_t>(),
|
||||
num_seqs,
|
||||
num_heads,
|
||||
rotary_dim,
|
||||
num_heads * qk_head_dim,
|
||||
qk_head_dim,
|
||||
kv_lora_rank + qk_rope_head_dim,
|
||||
num_heads * (kv_lora_rank + qk_rope_head_dim),
|
||||
kv_lora_rank + qk_rope_head_dim,
|
||||
kv_lora_rank + qk_rope_head_dim);
|
||||
});
|
||||
|
||||
return std::make_tuple(q_input, k_input, v_input);
|
||||
}
|
||||
|
||||
std::tuple<at::Tensor, at::Tensor, at::Tensor> qkv_proj_with_rope_fused_weight(
|
||||
at::Tensor& hidden_states,
|
||||
at::Tensor& qkv_a_proj_weight,
|
||||
at::Tensor& q_b_proj_weight,
|
||||
at::Tensor& w_kc,
|
||||
at::Tensor& q_a_layernorm_weight,
|
||||
at::Tensor& kv_a_layernorm_weight,
|
||||
at::Tensor& positions,
|
||||
at::Tensor& cos_sin_cache,
|
||||
double eps,
|
||||
bool use_int8_w8a8,
|
||||
bool use_fp8_w8a16,
|
||||
std::optional<at::Tensor> qkv_a_proj_scale,
|
||||
std::optional<at::Tensor> q_b_proj_scale,
|
||||
std::optional<at::Tensor> w_scale,
|
||||
bool is_vnni,
|
||||
std::optional<std::vector<int64_t>> block_size,
|
||||
int64_t q_lora_rank,
|
||||
int64_t kv_lora_rank,
|
||||
int64_t qk_rope_head_dim) {
|
||||
RECORD_FUNCTION(
|
||||
"sgl-kernel::qkv_proj_with_rope_fused_weight",
|
||||
std::vector<c10::IValue>({hidden_states, qkv_a_proj_weight, q_b_proj_weight, w_kc}));
|
||||
|
||||
int64_t hidden_size = hidden_states.size(1);
|
||||
CHECK_EQ(qkv_a_proj_weight.size(0), q_lora_rank + kv_lora_rank + qk_rope_head_dim);
|
||||
CHECK_EQ(qkv_a_proj_weight.size(1), get_row_size(hidden_size, use_int8_w8a8));
|
||||
|
||||
std::vector<at::Tensor> weight_chunks =
|
||||
at::split(qkv_a_proj_weight, {q_lora_rank, kv_lora_rank + qk_rope_head_dim}, 0);
|
||||
at::Tensor q_a_proj_weight = weight_chunks[0];
|
||||
at::Tensor kv_a_proj_weight = weight_chunks[1];
|
||||
at::Tensor q_a_proj_s;
|
||||
at::Tensor kv_a_proj_s;
|
||||
|
||||
if (use_int8_w8a8) {
|
||||
TORCH_CHECK(qkv_a_proj_scale.has_value(), "missing qkv_a_proj_scale for int8 w8a8.");
|
||||
std::vector<at::Tensor> scale_chunks =
|
||||
at::split(qkv_a_proj_scale.value(), {q_lora_rank, kv_lora_rank + qk_rope_head_dim}, 0);
|
||||
q_a_proj_s = scale_chunks[0];
|
||||
kv_a_proj_s = scale_chunks[1];
|
||||
}
|
||||
if (use_fp8_w8a16) {
|
||||
TORCH_CHECK(qkv_a_proj_scale.has_value(), "missing qkv_a_proj_scale for fp8 w8a16.");
|
||||
int64_t block_size_N = block_size.value()[0];
|
||||
int64_t q_a_proj_s_dim0 = div_up(q_lora_rank, block_size_N);
|
||||
int64_t kv_a_proj_s_dim0 = div_up(kv_lora_rank + qk_rope_head_dim, block_size_N);
|
||||
std::vector<at::Tensor> scale_chunks = at::split(qkv_a_proj_scale.value(), {q_a_proj_s_dim0, kv_a_proj_s_dim0}, 0);
|
||||
q_a_proj_s = scale_chunks[0];
|
||||
kv_a_proj_s = scale_chunks[1];
|
||||
}
|
||||
|
||||
return qkv_proj_with_rope(
|
||||
hidden_states,
|
||||
q_a_proj_weight,
|
||||
q_b_proj_weight,
|
||||
kv_a_proj_weight,
|
||||
w_kc,
|
||||
q_a_layernorm_weight,
|
||||
kv_a_layernorm_weight,
|
||||
positions,
|
||||
cos_sin_cache,
|
||||
eps,
|
||||
use_int8_w8a8,
|
||||
use_fp8_w8a16,
|
||||
q_a_proj_s,
|
||||
q_b_proj_scale,
|
||||
kv_a_proj_s,
|
||||
w_scale,
|
||||
is_vnni,
|
||||
block_size);
|
||||
}
|
||||
959
third_party/sglang/sgl-kernel/csrc/cpu/rope.cpp
vendored
Normal file
959
third_party/sglang/sgl-kernel/csrc/cpu/rope.cpp
vendored
Normal file
@@ -0,0 +1,959 @@
|
||||
#include "common.h"
|
||||
#include "vec.h"
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename scalar_t>
|
||||
void rotary_embedding_3D_kernel_impl(
|
||||
scalar_t* __restrict__ query_out,
|
||||
scalar_t* __restrict__ key_out,
|
||||
int64_t* __restrict__ positions,
|
||||
scalar_t* __restrict__ query,
|
||||
scalar_t* __restrict__ key,
|
||||
scalar_t* __restrict__ cos_sin_cache,
|
||||
int64_t num_tokens,
|
||||
int64_t num_heads,
|
||||
int64_t num_kv_heads,
|
||||
int64_t head_size,
|
||||
int64_t rotary_dim,
|
||||
int64_t query_stride_s,
|
||||
int64_t query_out_stride_s,
|
||||
int64_t key_out_stride_s,
|
||||
int64_t key_stride_s,
|
||||
int64_t query_stride_h,
|
||||
int64_t query_out_stride_h) {
|
||||
int64_t HR = rotary_dim;
|
||||
int64_t HK = rotary_dim;
|
||||
int64_t COFF = HR / 2;
|
||||
at::parallel_for(0, num_tokens * num_heads, GRAIN_SIZE / rotary_dim, [&](int64_t begin, int64_t end) {
|
||||
int64_t seq{0}, head_id{0};
|
||||
data_index_init(begin, seq, num_tokens, head_id, num_heads);
|
||||
for (int64_t i = begin; i < end; ++i) {
|
||||
int64_t in_offset_q = seq * query_stride_s + head_id * query_stride_h;
|
||||
int64_t out_offset_q = seq * query_out_stride_s + head_id * query_out_stride_h;
|
||||
int64_t out_offset_k = seq * key_out_stride_s;
|
||||
int64_t p = 0;
|
||||
scalar_t* sin_start = nullptr;
|
||||
scalar_t* cos_start = nullptr;
|
||||
// step 0) get the rotary position embedding for the current position
|
||||
p = positions[seq];
|
||||
sin_start = cos_sin_cache + p * HR + COFF;
|
||||
cos_start = cos_sin_cache + p * HR;
|
||||
// step 1) apply_rotary_pos_emb for the rotary_dim elements in every
|
||||
// head of query/key
|
||||
for (int64_t h = 0; h < rotary_dim; h += 2) {
|
||||
scalar_t cos = cos_start[h >> 1];
|
||||
scalar_t sin = sin_start[h >> 1];
|
||||
scalar_t in1 = query[in_offset_q + h];
|
||||
scalar_t in2 = query[in_offset_q + h + 1];
|
||||
scalar_t out1 = in1 * cos - in2 * sin;
|
||||
scalar_t out2 = in2 * cos + in1 * sin;
|
||||
query_out[out_offset_q + h] = out1;
|
||||
query_out[out_offset_q + h + 1] = out2;
|
||||
}
|
||||
for (int64_t h = 0; h < HK; h += 2) {
|
||||
scalar_t cos = cos_start[h >> 1];
|
||||
scalar_t sin = sin_start[h >> 1];
|
||||
int64_t k_pe_offset = seq * key_stride_s;
|
||||
scalar_t in1_k = key[k_pe_offset + h];
|
||||
scalar_t in2_k = key[k_pe_offset + h + 1];
|
||||
scalar_t out1_k = in1_k * cos - in2_k * sin;
|
||||
scalar_t out2_k = in2_k * cos + in1_k * sin;
|
||||
key_out[out_offset_k + h] = out1_k;
|
||||
key_out[out_offset_k + h + 1] = out2_k;
|
||||
}
|
||||
// move to the next index
|
||||
data_index_step(seq, num_tokens, head_id, num_heads);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
void rotary_embedding_neox_4D_kernel_impl(
|
||||
int64_t* __restrict__ positions,
|
||||
scalar_t* __restrict__ query,
|
||||
scalar_t* __restrict__ key,
|
||||
scalar_t* __restrict__ cos_sin_cache,
|
||||
int64_t rotary_dim,
|
||||
int64_t query_stride_b,
|
||||
int64_t query_stride_s,
|
||||
int64_t query_stride_h,
|
||||
int64_t key_stride_b,
|
||||
int64_t key_stride_s,
|
||||
int64_t key_stride_h,
|
||||
int64_t num_heads,
|
||||
int64_t num_kv_heads,
|
||||
int64_t head_size,
|
||||
int64_t batch_size,
|
||||
int64_t seq_len) {
|
||||
using bVec = at::vec::Vectorized<scalar_t>;
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
constexpr int64_t bVecSize = bVec::size();
|
||||
|
||||
int64_t embed_dim = rotary_dim / 2;
|
||||
bool flag = (embed_dim % bVecSize == 0);
|
||||
int64_t loop_upper = flag ? embed_dim : embed_dim - bVecSize;
|
||||
|
||||
auto compute_loop = [&](int64_t token_head, scalar_t* cache_ptr, scalar_t* qk) {
|
||||
int64_t j = 0;
|
||||
for (; j < loop_upper; j += bVecSize) {
|
||||
int64_t rot_offset = j;
|
||||
int64_t x_index = rot_offset;
|
||||
int64_t y_index = embed_dim + rot_offset;
|
||||
|
||||
int64_t out_x = token_head + x_index;
|
||||
int64_t out_y = token_head + y_index;
|
||||
|
||||
bVec _cos = bVec::loadu(cache_ptr + x_index);
|
||||
bVec _sin = bVec::loadu(cache_ptr + y_index);
|
||||
|
||||
bVec _q_x = bVec::loadu(qk + out_x);
|
||||
bVec _q_y = bVec::loadu(qk + out_y);
|
||||
fVec _cos_0, _cos_1;
|
||||
std::tie(_cos_0, _cos_1) = at::vec::convert_to_float(_cos);
|
||||
fVec _sin_0, _sin_1;
|
||||
std::tie(_sin_0, _sin_1) = at::vec::convert_to_float(_sin);
|
||||
fVec _q_x_0, _q_x_1;
|
||||
std::tie(_q_x_0, _q_x_1) = at::vec::convert_to_float(_q_x);
|
||||
fVec _q_y_0, _q_y_1;
|
||||
std::tie(_q_y_0, _q_y_1) = at::vec::convert_to_float(_q_y);
|
||||
|
||||
auto out1_0 = _q_x_0 * _cos_0 - _q_y_0 * _sin_0;
|
||||
auto out1_1 = _q_x_1 * _cos_1 - _q_y_1 * _sin_1;
|
||||
auto out1 = convert_from_float_ext<scalar_t>(out1_0, out1_1);
|
||||
out1.store(qk + out_x);
|
||||
|
||||
auto out2_0 = _q_y_0 * _cos_0 + _q_x_0 * _sin_0;
|
||||
auto out2_1 = _q_y_1 * _cos_1 + _q_x_1 * _sin_1;
|
||||
auto out2 = convert_from_float_ext<scalar_t>(out2_0, out2_1);
|
||||
out2.store(qk + out_y);
|
||||
}
|
||||
if (!flag) {
|
||||
for (; j < embed_dim; ++j) {
|
||||
int64_t x_index = j;
|
||||
int64_t y_index = embed_dim + j;
|
||||
|
||||
int64_t out_x = token_head + x_index;
|
||||
int64_t out_y = token_head + y_index;
|
||||
|
||||
float _cos = cache_ptr[x_index];
|
||||
float _sin = cache_ptr[y_index];
|
||||
|
||||
float _q_x = qk[out_x];
|
||||
float _q_y = qk[out_y];
|
||||
|
||||
qk[out_x] = _q_x * _cos - _q_y * _sin;
|
||||
qk[out_y] = _q_y * _cos + _q_x * _sin;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#pragma omp parallel for collapse(2)
|
||||
for (int64_t bs = 0; bs < batch_size; ++bs) {
|
||||
for (int64_t seq = 0; seq < seq_len; ++seq) {
|
||||
int64_t pos = positions[bs * seq_len + seq];
|
||||
scalar_t* cache_ptr = cos_sin_cache + pos * rotary_dim;
|
||||
|
||||
for (int64_t i = 0; i < num_heads; ++i) {
|
||||
int64_t head_idx = i;
|
||||
int64_t token_head = bs * query_stride_b + seq * query_stride_s + head_idx * query_stride_h;
|
||||
compute_loop(token_head, cache_ptr, query);
|
||||
}
|
||||
|
||||
for (int64_t i = 0; i < num_kv_heads; ++i) {
|
||||
int64_t head_idx = i;
|
||||
int64_t token_head = bs * key_stride_b + seq * key_stride_s + head_idx * key_stride_h;
|
||||
compute_loop(token_head, cache_ptr, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
void apply_rotary_pos_emb_kernel_impl(
|
||||
scalar_t* __restrict__ query,
|
||||
scalar_t* __restrict__ key,
|
||||
float* __restrict__ cos,
|
||||
float* __restrict__ sin,
|
||||
int64_t query_stride_s,
|
||||
int64_t key_stride_s,
|
||||
int64_t num_heads,
|
||||
int64_t num_kv_heads,
|
||||
int64_t head_size,
|
||||
int64_t num_tokens) {
|
||||
using bVec = at::vec::Vectorized<scalar_t>;
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
constexpr int64_t bVecSize = bVec::size();
|
||||
constexpr int64_t fVecSize = fVec::size();
|
||||
|
||||
int64_t embed_dim = head_size / 2;
|
||||
bool flag = (embed_dim % bVecSize == 0);
|
||||
int64_t loop_upper = flag ? embed_dim : embed_dim - bVecSize;
|
||||
|
||||
auto compute_loop = [&](int64_t token_head, float* cos_ptr, float* sin_ptr, scalar_t* qk) {
|
||||
int64_t j = 0;
|
||||
for (; j < loop_upper; j += bVecSize) {
|
||||
int64_t rot_offset = j;
|
||||
int64_t x_index = rot_offset;
|
||||
int64_t y_index = embed_dim + rot_offset;
|
||||
|
||||
int64_t out_x = token_head + x_index;
|
||||
int64_t out_y = token_head + y_index;
|
||||
|
||||
fVec _cos_x_0 = fVec::loadu(cos_ptr + x_index);
|
||||
fVec _sin_x_0 = fVec::loadu(sin_ptr + x_index);
|
||||
fVec _cos_x_1 = fVec::loadu(cos_ptr + x_index + fVecSize);
|
||||
fVec _sin_x_1 = fVec::loadu(sin_ptr + x_index + fVecSize);
|
||||
|
||||
fVec _cos_y_0 = fVec::loadu(cos_ptr + y_index);
|
||||
fVec _sin_y_0 = fVec::loadu(sin_ptr + y_index);
|
||||
fVec _cos_y_1 = fVec::loadu(cos_ptr + y_index + fVecSize);
|
||||
fVec _sin_y_1 = fVec::loadu(sin_ptr + y_index + fVecSize);
|
||||
|
||||
bVec _q_x = bVec::loadu(qk + out_x);
|
||||
bVec _q_y = bVec::loadu(qk + out_y);
|
||||
fVec _q_x_0, _q_x_1;
|
||||
std::tie(_q_x_0, _q_x_1) = at::vec::convert_to_float(_q_x);
|
||||
fVec _q_y_0, _q_y_1;
|
||||
std::tie(_q_y_0, _q_y_1) = at::vec::convert_to_float(_q_y);
|
||||
|
||||
auto out1_0 = _q_x_0 * _cos_x_0 - _q_y_0 * _sin_x_0;
|
||||
auto out1_1 = _q_x_1 * _cos_x_1 - _q_y_1 * _sin_x_1;
|
||||
auto out1 = convert_from_float_ext<scalar_t>(out1_0, out1_1);
|
||||
out1.store(qk + out_x);
|
||||
|
||||
auto out2_0 = _q_y_0 * _cos_y_0 + _q_x_0 * _sin_y_0;
|
||||
auto out2_1 = _q_y_1 * _cos_y_1 + _q_x_1 * _sin_y_1;
|
||||
auto out2 = convert_from_float_ext<scalar_t>(out2_0, out2_1);
|
||||
out2.store(qk + out_y);
|
||||
}
|
||||
if (!flag) {
|
||||
for (; j < embed_dim; ++j) {
|
||||
int64_t x_index = j;
|
||||
int64_t y_index = embed_dim + j;
|
||||
|
||||
int64_t out_x = token_head + x_index;
|
||||
int64_t out_y = token_head + y_index;
|
||||
|
||||
float _cos_x = cos_ptr[x_index];
|
||||
float _sin_x = sin_ptr[x_index];
|
||||
float _cos_y = cos_ptr[y_index];
|
||||
float _sin_y = sin_ptr[y_index];
|
||||
|
||||
float _q_x = qk[out_x];
|
||||
float _q_y = qk[out_y];
|
||||
|
||||
qk[out_x] = _q_x * _cos_x - _q_y * _sin_x;
|
||||
qk[out_y] = _q_y * _cos_y + _q_x * _sin_y;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
at::parallel_for(0, num_tokens, 0, [&](int64_t begin, int64_t end) {
|
||||
int64_t token_idx = {0};
|
||||
data_index_init(begin, token_idx, num_tokens);
|
||||
for (int i = begin; i < end; ++i) {
|
||||
float* cos_ptr = cos + token_idx * head_size;
|
||||
float* sin_ptr = sin + token_idx * head_size;
|
||||
|
||||
for (int64_t i = 0; i < num_heads; ++i) {
|
||||
int64_t head_idx = i;
|
||||
int64_t token_head = token_idx * query_stride_s + head_idx * head_size;
|
||||
compute_loop(token_head, cos_ptr, sin_ptr, query);
|
||||
}
|
||||
|
||||
for (int64_t i = 0; i < num_kv_heads; ++i) {
|
||||
int64_t head_idx = i;
|
||||
int64_t token_head = token_idx * key_stride_s + head_idx * head_size;
|
||||
compute_loop(token_head, cos_ptr, sin_ptr, key);
|
||||
}
|
||||
data_index_step(token_idx, num_tokens);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
void apply_rotary_pos_emb_kernel_impl(
|
||||
scalar_t* __restrict__ query,
|
||||
scalar_t* __restrict__ key,
|
||||
scalar_t* __restrict__ cos,
|
||||
scalar_t* __restrict__ sin,
|
||||
int64_t query_stride_s,
|
||||
int64_t key_stride_s,
|
||||
int64_t num_heads,
|
||||
int64_t num_kv_heads,
|
||||
int64_t head_size,
|
||||
int64_t num_tokens) {
|
||||
using bVec = at::vec::Vectorized<scalar_t>;
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
constexpr int64_t bVecSize = bVec::size();
|
||||
|
||||
int64_t embed_dim = head_size / 2;
|
||||
bool flag = (embed_dim % bVecSize == 0);
|
||||
int64_t loop_upper = flag ? embed_dim : embed_dim - bVecSize;
|
||||
|
||||
auto compute_loop = [&](int64_t token_head, scalar_t* cos_ptr, scalar_t* sin_ptr, scalar_t* qk) {
|
||||
int64_t j = 0;
|
||||
for (; j < loop_upper; j += bVecSize) {
|
||||
int64_t rot_offset = j;
|
||||
int64_t x_index = rot_offset;
|
||||
int64_t y_index = embed_dim + rot_offset;
|
||||
|
||||
int64_t out_x = token_head + x_index;
|
||||
int64_t out_y = token_head + y_index;
|
||||
|
||||
bVec _cos_x = bVec::loadu(cos_ptr + x_index);
|
||||
bVec _sin_x = bVec::loadu(sin_ptr + x_index);
|
||||
bVec _cos_y = bVec::loadu(cos_ptr + y_index);
|
||||
bVec _sin_y = bVec::loadu(sin_ptr + y_index);
|
||||
fVec _cos_x_0, _cos_x_1;
|
||||
std::tie(_cos_x_0, _cos_x_1) = at::vec::convert_to_float(_cos_x);
|
||||
fVec _sin_x_0, _sin_x_1;
|
||||
std::tie(_sin_x_0, _sin_x_1) = at::vec::convert_to_float(_sin_x);
|
||||
fVec _cos_y_0, _cos_y_1;
|
||||
std::tie(_cos_y_0, _cos_y_1) = at::vec::convert_to_float(_cos_y);
|
||||
fVec _sin_y_0, _sin_y_1;
|
||||
std::tie(_sin_y_0, _sin_y_1) = at::vec::convert_to_float(_sin_y);
|
||||
|
||||
bVec _q_x = bVec::loadu(qk + out_x);
|
||||
bVec _q_y = bVec::loadu(qk + out_y);
|
||||
fVec _q_x_0, _q_x_1;
|
||||
std::tie(_q_x_0, _q_x_1) = at::vec::convert_to_float(_q_x);
|
||||
fVec _q_y_0, _q_y_1;
|
||||
std::tie(_q_y_0, _q_y_1) = at::vec::convert_to_float(_q_y);
|
||||
|
||||
auto out1_0 = _q_x_0 * _cos_x_0 - _q_y_0 * _sin_x_0;
|
||||
auto out1_1 = _q_x_1 * _cos_x_1 - _q_y_1 * _sin_x_1;
|
||||
auto out1 = convert_from_float_ext<scalar_t>(out1_0, out1_1);
|
||||
out1.store(qk + out_x);
|
||||
|
||||
auto out2_0 = _q_y_0 * _cos_y_0 + _q_x_0 * _sin_y_0;
|
||||
auto out2_1 = _q_y_1 * _cos_y_1 + _q_x_1 * _sin_y_1;
|
||||
auto out2 = convert_from_float_ext<scalar_t>(out2_0, out2_1);
|
||||
out2.store(qk + out_y);
|
||||
}
|
||||
if (!flag) {
|
||||
for (; j < embed_dim; ++j) {
|
||||
int64_t x_index = j;
|
||||
int64_t y_index = embed_dim + j;
|
||||
|
||||
int64_t out_x = token_head + x_index;
|
||||
int64_t out_y = token_head + y_index;
|
||||
|
||||
float _cos_x = cos_ptr[x_index];
|
||||
float _sin_x = sin_ptr[x_index];
|
||||
float _cos_y = cos_ptr[y_index];
|
||||
float _sin_y = sin_ptr[y_index];
|
||||
|
||||
float _q_x = qk[out_x];
|
||||
float _q_y = qk[out_y];
|
||||
|
||||
qk[out_x] = _q_x * _cos_x - _q_y * _sin_x;
|
||||
qk[out_y] = _q_y * _cos_y + _q_x * _sin_y;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
at::parallel_for(0, num_tokens, 0, [&](int64_t begin, int64_t end) {
|
||||
int64_t token_idx = {0};
|
||||
data_index_init(begin, token_idx, num_tokens);
|
||||
for (int i = begin; i < end; ++i) {
|
||||
scalar_t* cos_ptr = cos + token_idx * head_size;
|
||||
scalar_t* sin_ptr = sin + token_idx * head_size;
|
||||
|
||||
for (int64_t i = 0; i < num_heads; ++i) {
|
||||
int64_t head_idx = i;
|
||||
int64_t token_head = token_idx * query_stride_s + head_idx * head_size;
|
||||
compute_loop(token_head, cos_ptr, sin_ptr, query);
|
||||
}
|
||||
|
||||
for (int64_t i = 0; i < num_kv_heads; ++i) {
|
||||
int64_t head_idx = i;
|
||||
int64_t token_head = token_idx * key_stride_s + head_idx * head_size;
|
||||
compute_loop(token_head, cos_ptr, sin_ptr, key);
|
||||
}
|
||||
data_index_step(token_idx, num_tokens);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
inline scalar_t* get_cache_ptr(
|
||||
int64_t j,
|
||||
scalar_t* cache_t_ptr,
|
||||
scalar_t* cache_h_ptr,
|
||||
scalar_t* cache_w_ptr,
|
||||
int64_t mrope_section_t,
|
||||
int64_t mrope_section_h,
|
||||
int64_t mrope_section_w,
|
||||
bool mrope_interleaved) {
|
||||
if (mrope_interleaved) {
|
||||
if (j % 3 == 1 && j <= mrope_section_h * 3) return cache_h_ptr;
|
||||
if (j % 3 == 2 && j <= mrope_section_w * 3) return cache_w_ptr;
|
||||
return cache_t_ptr;
|
||||
}
|
||||
if (j < mrope_section_t) return cache_t_ptr;
|
||||
if (j < mrope_section_t + mrope_section_h) return cache_h_ptr;
|
||||
return cache_w_ptr;
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
void multimodal_rotary_embedding_neox_2D_kernel_impl(
|
||||
int64_t* __restrict__ positions,
|
||||
scalar_t* __restrict__ query,
|
||||
scalar_t* __restrict__ key,
|
||||
scalar_t* __restrict__ cos_sin_cache,
|
||||
int64_t rotary_dim,
|
||||
int64_t query_stride_s,
|
||||
int64_t key_stride_s,
|
||||
int64_t num_heads,
|
||||
int64_t num_kv_heads,
|
||||
int64_t head_size,
|
||||
int64_t num_tokens,
|
||||
int64_t mrope_section_t,
|
||||
int64_t mrope_section_h,
|
||||
int64_t mrope_section_w,
|
||||
int64_t positions_stride0,
|
||||
bool mrope_interleaved) {
|
||||
int64_t embed_dim = rotary_dim / 2;
|
||||
auto compute_loop =
|
||||
[&](int64_t token_head, scalar_t* cache_t_ptr, scalar_t* cache_h_ptr, scalar_t* cache_w_ptr, scalar_t* qk) {
|
||||
for (int64_t j = 0; j < embed_dim; ++j) {
|
||||
int64_t x_index = j;
|
||||
int64_t y_index = embed_dim + j;
|
||||
|
||||
int64_t out_x = token_head + x_index;
|
||||
int64_t out_y = token_head + y_index;
|
||||
|
||||
scalar_t* cache_ptr = get_cache_ptr(
|
||||
j,
|
||||
cache_t_ptr,
|
||||
cache_h_ptr,
|
||||
cache_w_ptr,
|
||||
mrope_section_t,
|
||||
mrope_section_h,
|
||||
mrope_section_w,
|
||||
mrope_interleaved);
|
||||
float _cos = cache_ptr[x_index];
|
||||
float _sin = cache_ptr[y_index];
|
||||
|
||||
float _q_x = qk[out_x];
|
||||
float _q_y = qk[out_y];
|
||||
|
||||
qk[out_x] = _q_x * _cos - _q_y * _sin;
|
||||
qk[out_y] = _q_y * _cos + _q_x * _sin;
|
||||
}
|
||||
};
|
||||
at::parallel_for(0, num_tokens, 0, [&](int64_t begin, int64_t end) {
|
||||
int64_t token_idx = {0};
|
||||
data_index_init(begin, token_idx, num_tokens);
|
||||
for (int i = begin; i < end; ++i) {
|
||||
int64_t pos_t = positions[token_idx];
|
||||
int64_t pos_h = positions[positions_stride0 + token_idx];
|
||||
int64_t pos_w = positions[positions_stride0 * 2 + token_idx];
|
||||
scalar_t* cache_t_ptr = cos_sin_cache + pos_t * rotary_dim;
|
||||
scalar_t* cache_h_ptr = cos_sin_cache + pos_h * rotary_dim;
|
||||
scalar_t* cache_w_ptr = cos_sin_cache + pos_w * rotary_dim;
|
||||
|
||||
for (int64_t i = 0; i < num_heads; ++i) {
|
||||
int64_t head_idx = i;
|
||||
int64_t token_head = token_idx * query_stride_s + head_idx * head_size;
|
||||
compute_loop(token_head, cache_t_ptr, cache_h_ptr, cache_w_ptr, query);
|
||||
}
|
||||
|
||||
for (int64_t i = 0; i < num_kv_heads; ++i) {
|
||||
int64_t head_idx = i;
|
||||
int64_t token_head = token_idx * key_stride_s + head_idx * head_size;
|
||||
compute_loop(token_head, cache_t_ptr, cache_h_ptr, cache_w_ptr, key);
|
||||
}
|
||||
data_index_step(token_idx, num_tokens);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
void rotary_embedding_4D_kernel_impl(
|
||||
int64_t* __restrict__ positions,
|
||||
scalar_t* __restrict__ query,
|
||||
scalar_t* __restrict__ key,
|
||||
scalar_t* __restrict__ cos_sin_cache,
|
||||
int64_t rotary_dim,
|
||||
int64_t query_stride_b,
|
||||
int64_t query_stride_s,
|
||||
int64_t query_stride_h,
|
||||
int64_t key_stride_b,
|
||||
int64_t key_stride_s,
|
||||
int64_t key_stride_h,
|
||||
int64_t num_heads,
|
||||
int64_t num_kv_heads,
|
||||
int64_t head_size,
|
||||
int64_t batch_size,
|
||||
int64_t seq_len) {
|
||||
int64_t embed_dim = rotary_dim / 2;
|
||||
|
||||
at::parallel_for(0, batch_size * seq_len * num_heads, GRAIN_SIZE / rotary_dim, [&](int64_t begin, int64_t end) {
|
||||
int64_t bs = {0}, seq = {0}, i = {0};
|
||||
data_index_init(begin, bs, batch_size, seq, seq_len, i, num_heads);
|
||||
for ([[maybe_unused]] auto z : c10::irange(begin, end)) {
|
||||
int64_t pos = positions[bs * seq_len + seq];
|
||||
scalar_t* cache_ptr = cos_sin_cache + pos * rotary_dim;
|
||||
scalar_t* cos_cache_ptr = cache_ptr;
|
||||
scalar_t* sin_cache_ptr = cache_ptr + embed_dim;
|
||||
int64_t head_idx = i;
|
||||
int64_t token_head = bs * query_stride_b + seq * query_stride_s + head_idx * query_stride_h;
|
||||
scalar_t* head_query = token_head + query;
|
||||
for (int64_t j = 0; j < embed_dim; j += 1) {
|
||||
int64_t rot_offset = j;
|
||||
int64_t x_index = 2 * rot_offset;
|
||||
int64_t y_index = 2 * rot_offset + 1;
|
||||
|
||||
float cos = cos_cache_ptr[rot_offset];
|
||||
float sin = sin_cache_ptr[rot_offset];
|
||||
|
||||
float x = head_query[x_index];
|
||||
float y = head_query[y_index];
|
||||
|
||||
head_query[x_index] = x * cos - y * sin;
|
||||
head_query[y_index] = y * cos + x * sin;
|
||||
}
|
||||
data_index_step(bs, batch_size, seq, seq_len, i, num_heads);
|
||||
}
|
||||
});
|
||||
|
||||
at::parallel_for(0, batch_size * seq_len * num_kv_heads, GRAIN_SIZE / rotary_dim, [&](int64_t begin, int64_t end) {
|
||||
int64_t bs = {0}, seq = {0}, i = {0};
|
||||
data_index_init(begin, bs, batch_size, seq, seq_len, i, num_kv_heads);
|
||||
for ([[maybe_unused]] auto z : c10::irange(begin, end)) {
|
||||
int64_t pos = positions[bs * seq_len + seq];
|
||||
scalar_t* cache_ptr = cos_sin_cache + pos * rotary_dim;
|
||||
scalar_t* cos_cache_ptr = cache_ptr;
|
||||
scalar_t* sin_cache_ptr = cache_ptr + embed_dim;
|
||||
int64_t head_idx = i;
|
||||
int64_t token_head = bs * key_stride_b + seq * key_stride_s + head_idx * head_size;
|
||||
scalar_t* head_key = key + token_head;
|
||||
for (int64_t j = 0; j < embed_dim; j += 1) {
|
||||
int64_t rot_offset = j;
|
||||
int64_t x_index = 2 * rot_offset;
|
||||
int64_t y_index = 2 * rot_offset + 1;
|
||||
|
||||
float cos = cos_cache_ptr[rot_offset];
|
||||
float sin = sin_cache_ptr[rot_offset];
|
||||
|
||||
float x = head_key[x_index];
|
||||
float y = head_key[y_index];
|
||||
|
||||
head_key[x_index] = x * cos - y * sin;
|
||||
head_key[y_index] = y * cos + x * sin;
|
||||
}
|
||||
data_index_step(bs, batch_size, seq, seq_len, i, num_kv_heads);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
void multimodal_rotary_embedding_2D_kernel_impl(
|
||||
int64_t* __restrict__ positions,
|
||||
scalar_t* __restrict__ query,
|
||||
scalar_t* __restrict__ key,
|
||||
scalar_t* __restrict__ cos_sin_cache,
|
||||
int64_t rotary_dim,
|
||||
int64_t query_stride_s,
|
||||
int64_t key_stride_s,
|
||||
int64_t num_heads,
|
||||
int64_t num_kv_heads,
|
||||
int64_t head_size,
|
||||
int64_t num_tokens,
|
||||
int64_t mrope_section_t,
|
||||
int64_t mrope_section_h,
|
||||
int64_t mrope_section_w,
|
||||
int64_t positions_stride0,
|
||||
bool mrope_interleaved) {
|
||||
int64_t embed_dim = rotary_dim / 2;
|
||||
auto compute_loop = [&](scalar_t* cache_t_ptr, scalar_t* cache_h_ptr, scalar_t* cache_w_ptr, scalar_t* head_query) {
|
||||
for (int64_t j = 0; j < embed_dim; j += 1) {
|
||||
int64_t rot_offset = j;
|
||||
int64_t x_index = 2 * rot_offset;
|
||||
int64_t y_index = 2 * rot_offset + 1;
|
||||
|
||||
scalar_t* cache_ptr = get_cache_ptr(
|
||||
j,
|
||||
cache_t_ptr,
|
||||
cache_h_ptr,
|
||||
cache_w_ptr,
|
||||
mrope_section_t,
|
||||
mrope_section_h,
|
||||
mrope_section_w,
|
||||
mrope_interleaved);
|
||||
float cos = cache_ptr[rot_offset];
|
||||
float sin = cache_ptr[rot_offset + embed_dim];
|
||||
|
||||
float x = head_query[x_index];
|
||||
float y = head_query[y_index];
|
||||
|
||||
head_query[x_index] = x * cos - y * sin;
|
||||
head_query[y_index] = y * cos + x * sin;
|
||||
}
|
||||
};
|
||||
at::parallel_for(0, num_tokens * num_heads, GRAIN_SIZE / rotary_dim, [&](int64_t begin, int64_t end) {
|
||||
int64_t token_idx = {0}, i = {0};
|
||||
data_index_init(begin, token_idx, num_tokens, i, num_heads);
|
||||
for ([[maybe_unused]] auto z : c10::irange(begin, end)) {
|
||||
int64_t pos_t = positions[token_idx];
|
||||
int64_t pos_h = positions[positions_stride0 + token_idx];
|
||||
int64_t pos_w = positions[positions_stride0 * 2 + token_idx];
|
||||
scalar_t* cache_t_ptr = cos_sin_cache + pos_t * rotary_dim;
|
||||
scalar_t* cache_h_ptr = cos_sin_cache + pos_h * rotary_dim;
|
||||
scalar_t* cache_w_ptr = cos_sin_cache + pos_w * rotary_dim;
|
||||
int64_t head_idx = i;
|
||||
int64_t token_head = token_idx * query_stride_s + head_idx * head_size;
|
||||
scalar_t* head_query = token_head + query;
|
||||
compute_loop(cache_t_ptr, cache_h_ptr, cache_w_ptr, head_query);
|
||||
data_index_step(token_idx, num_tokens, i, num_heads);
|
||||
}
|
||||
});
|
||||
|
||||
at::parallel_for(0, num_tokens * num_kv_heads, GRAIN_SIZE / rotary_dim, [&](int64_t begin, int64_t end) {
|
||||
int64_t token_idx{0}, i = {0};
|
||||
data_index_init(begin, token_idx, num_tokens, i, num_kv_heads);
|
||||
for ([[maybe_unused]] auto z : c10::irange(begin, end)) {
|
||||
int64_t pos_t = positions[token_idx];
|
||||
int64_t pos_h = positions[positions_stride0 + token_idx];
|
||||
int64_t pos_w = positions[positions_stride0 * 2 + token_idx];
|
||||
scalar_t* cache_t_ptr = cos_sin_cache + pos_t * rotary_dim;
|
||||
scalar_t* cache_h_ptr = cos_sin_cache + pos_h * rotary_dim;
|
||||
scalar_t* cache_w_ptr = cos_sin_cache + pos_w * rotary_dim;
|
||||
int64_t head_idx = i;
|
||||
int64_t token_head = token_idx * key_stride_s + head_idx * head_size;
|
||||
scalar_t* head_key = key + token_head;
|
||||
compute_loop(cache_t_ptr, cache_h_ptr, cache_w_ptr, head_key);
|
||||
data_index_step(token_idx, num_tokens, i, num_kv_heads);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::tuple<at::Tensor, at::Tensor> rotary_embedding_cpu(
|
||||
at::Tensor& positions,
|
||||
at::Tensor& query,
|
||||
at::Tensor& key,
|
||||
int64_t head_size,
|
||||
at::Tensor& cos_sin_cache,
|
||||
bool is_neox) {
|
||||
RECORD_FUNCTION("sgl-kernel::rotary_embedding_cpu", std::vector<c10::IValue>({query, key}));
|
||||
CHECK_DIM(1, positions);
|
||||
const auto input_dim = query.dim();
|
||||
const auto input_dtype = query.scalar_type();
|
||||
TORCH_CHECK(
|
||||
input_dim == 2 || input_dim == 3 || input_dim == 4,
|
||||
" Query/Key must be 2D [num_tokens, num_heads*head_size] or 3D [num_tokens, num_heads, head_size] or 4D "
|
||||
"[batch_size, seq_len, num_heads, head_size] tensor");
|
||||
CHECK_DIM(2, cos_sin_cache);
|
||||
CHECK_LAST_DIM_CONTIGUOUS_INPUT(query);
|
||||
CHECK_LAST_DIM_CONTIGUOUS_INPUT(key);
|
||||
|
||||
int64_t rotary_dim = cos_sin_cache.size(1);
|
||||
if (input_dim == 3) {
|
||||
// TODO: add support for head_dim != rotary_dim case when input_dim=3
|
||||
CHECK_EQ(query.size(-1), rotary_dim);
|
||||
// TODO: add support for kv_head != 1
|
||||
CHECK_EQ(key.size(1), 1);
|
||||
}
|
||||
|
||||
int64_t num_tokens = positions.numel();
|
||||
if (input_dim <= 3) {
|
||||
CHECK_EQ(key.size(0), num_tokens);
|
||||
CHECK_EQ(query.size(0), num_tokens);
|
||||
}
|
||||
|
||||
TORCH_CHECK(positions.scalar_type() == at::kLong, "expect positions to be int64, got ", positions.scalar_type());
|
||||
TORCH_CHECK(input_dtype == key.scalar_type(), "query and key must have the same data type");
|
||||
TORCH_CHECK(input_dtype == cos_sin_cache.scalar_type(), "query and cos_sin_cache must have the same data type");
|
||||
|
||||
int64_t num_heads = input_dim == 2 ? query.size(-1) / head_size : query.size(-2);
|
||||
int64_t num_kv_heads = input_dim == 2 ? key.size(-1) / head_size : key.size(-2);
|
||||
int64_t key_stride_s = key.stride(0);
|
||||
int64_t query_stride_s = query.stride(0);
|
||||
|
||||
int64_t query_stride_h = input_dim == 2 ? head_size : query.stride(-2);
|
||||
int64_t key_stride_h = input_dim == 2 ? head_size : key.stride(-2);
|
||||
at::Tensor query_out = at::empty_like(query);
|
||||
at::Tensor key_out = at::empty_like(key);
|
||||
int64_t query_out_stride_s = query_out.stride(0);
|
||||
int64_t key_out_stride_s = key_out.stride(0);
|
||||
// output stride of num head dim is meaningful only when input dim = 3
|
||||
int64_t query_out_stride_h = input_dim == 3 ? query_out.stride(1) : -1;
|
||||
int64_t batch_size = 1;
|
||||
int64_t seq_len = num_tokens;
|
||||
int64_t query_stride_b = 0;
|
||||
int64_t key_stride_b = 0;
|
||||
if (input_dim == 4) {
|
||||
batch_size = query.size(0);
|
||||
seq_len = query.size(1);
|
||||
query_stride_b = query.stride(0);
|
||||
key_stride_b = key.stride(0);
|
||||
query_stride_s = query.stride(1);
|
||||
key_stride_s = key.stride(1);
|
||||
CHECK_EQ(batch_size, key.size(0));
|
||||
CHECK_EQ(seq_len, key.size(1));
|
||||
CHECK_EQ(key.size(0) * key.size(1), num_tokens);
|
||||
CHECK_EQ(query.size(0) * query.size(1), num_tokens);
|
||||
}
|
||||
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(input_dtype, "rotary_embedding_cpu", [&] {
|
||||
if (input_dim == 2 || input_dim == 4) {
|
||||
if (is_neox) {
|
||||
rotary_embedding_neox_4D_kernel_impl<scalar_t>(
|
||||
positions.data_ptr<int64_t>(),
|
||||
query.data_ptr<scalar_t>(),
|
||||
key.data_ptr<scalar_t>(),
|
||||
cos_sin_cache.data_ptr<scalar_t>(),
|
||||
rotary_dim,
|
||||
query_stride_b,
|
||||
query_stride_s,
|
||||
query_stride_h,
|
||||
key_stride_b,
|
||||
key_stride_s,
|
||||
key_stride_h,
|
||||
num_heads,
|
||||
num_kv_heads,
|
||||
head_size,
|
||||
batch_size,
|
||||
seq_len);
|
||||
} else {
|
||||
rotary_embedding_4D_kernel_impl<scalar_t>(
|
||||
positions.data_ptr<int64_t>(),
|
||||
query.data_ptr<scalar_t>(),
|
||||
key.data_ptr<scalar_t>(),
|
||||
cos_sin_cache.data_ptr<scalar_t>(),
|
||||
rotary_dim,
|
||||
query_stride_b,
|
||||
query_stride_s,
|
||||
query_stride_h,
|
||||
key_stride_b,
|
||||
key_stride_s,
|
||||
key_stride_h,
|
||||
num_heads,
|
||||
num_kv_heads,
|
||||
head_size,
|
||||
batch_size,
|
||||
seq_len);
|
||||
}
|
||||
query_out = query;
|
||||
key_out = key;
|
||||
|
||||
} else {
|
||||
TORCH_CHECK(
|
||||
is_neox == false, " Query/Key with 3D [num_tokens, num_heads, head_size] does not support neox rope yet");
|
||||
// TODO: add neox style support for rope impl with 3D inputs
|
||||
rotary_embedding_3D_kernel_impl<scalar_t>(
|
||||
query_out.data_ptr<scalar_t>(),
|
||||
key_out.data_ptr<scalar_t>(),
|
||||
positions.data_ptr<int64_t>(),
|
||||
query.data_ptr<scalar_t>(),
|
||||
key.data_ptr<scalar_t>(),
|
||||
cos_sin_cache.data_ptr<scalar_t>(),
|
||||
num_tokens,
|
||||
num_heads,
|
||||
num_kv_heads,
|
||||
head_size,
|
||||
rotary_dim,
|
||||
query_stride_s,
|
||||
query_out_stride_s,
|
||||
key_out_stride_s,
|
||||
key_stride_s,
|
||||
query_stride_h,
|
||||
query_out_stride_h);
|
||||
}
|
||||
});
|
||||
return std::make_tuple(query_out, key_out);
|
||||
}
|
||||
|
||||
// query: [num_tokens, num_heads, head_size]
|
||||
// key: [num_tokens, num_heads, head_size]
|
||||
// cos: [num_tokens, head_size]
|
||||
// sin: [num_tokens, head_size]
|
||||
std::tuple<at::Tensor, at::Tensor>
|
||||
apply_rotary_pos_emb_cpu(at::Tensor& query, at::Tensor& key, at::Tensor& cos, at::Tensor& sin) {
|
||||
RECORD_FUNCTION("sgl-kernel::apply_rotary_pos_emb_cpu", std::vector<c10::IValue>({query, key}));
|
||||
CHECK_LAST_DIM_CONTIGUOUS_INPUT(query);
|
||||
CHECK_LAST_DIM_CONTIGUOUS_INPUT(key);
|
||||
CHECK_INPUT(cos);
|
||||
CHECK_INPUT(sin);
|
||||
CHECK_DIM(3, query);
|
||||
CHECK_DIM(3, key);
|
||||
CHECK_DIM(2, cos);
|
||||
CHECK_DIM(2, sin);
|
||||
const auto input_dtype = query.scalar_type();
|
||||
int64_t num_tokens = query.size(0);
|
||||
CHECK_EQ(num_tokens, key.size(0));
|
||||
CHECK_EQ(num_tokens, cos.size(0));
|
||||
CHECK_EQ(num_tokens, sin.size(0));
|
||||
int64_t num_heads = query.size(1);
|
||||
CHECK_EQ(num_heads, key.size(1));
|
||||
int64_t head_size = query.size(2);
|
||||
CHECK_EQ(head_size, key.size(2));
|
||||
CHECK_EQ(head_size, cos.size(1));
|
||||
CHECK_EQ(head_size, sin.size(1));
|
||||
int64_t q_stride_s = query.stride(0);
|
||||
int64_t k_stride_s = key.stride(0);
|
||||
TORCH_CHECK(input_dtype == key.scalar_type(), "query and key must have the same data type");
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(query.scalar_type(), "apply_rotary_pos_emb_cpu", [&] {
|
||||
if (cos.scalar_type() == at::kFloat && sin.scalar_type() == at::kFloat) {
|
||||
apply_rotary_pos_emb_kernel_impl<scalar_t>(
|
||||
query.data_ptr<scalar_t>(),
|
||||
key.data_ptr<scalar_t>(),
|
||||
cos.data_ptr<float>(),
|
||||
sin.data_ptr<float>(),
|
||||
q_stride_s,
|
||||
k_stride_s,
|
||||
num_heads,
|
||||
num_heads,
|
||||
head_size,
|
||||
num_tokens);
|
||||
} else if (cos.scalar_type() == input_dtype && sin.scalar_type() == input_dtype) {
|
||||
apply_rotary_pos_emb_kernel_impl<scalar_t>(
|
||||
query.data_ptr<scalar_t>(),
|
||||
key.data_ptr<scalar_t>(),
|
||||
cos.data_ptr<scalar_t>(),
|
||||
sin.data_ptr<scalar_t>(),
|
||||
q_stride_s,
|
||||
k_stride_s,
|
||||
num_heads,
|
||||
num_heads,
|
||||
head_size,
|
||||
num_tokens);
|
||||
} else {
|
||||
TORCH_CHECK(
|
||||
false, "cos and sin must have the same data type, and must be either float or the same type as query/key");
|
||||
}
|
||||
});
|
||||
return std::make_tuple(query, key);
|
||||
}
|
||||
|
||||
// positions: [num_tokens] (text only) or [3, num_tokens] (T/H/W positions with multimodal inputs)
|
||||
// query: [num_tokens, num_heads * head_size]
|
||||
// key: [num_tokens, num_kv_heads * head_size]
|
||||
// cos_sin_cache: [max_position_embeddings, rotary_dim]
|
||||
// mrope_section: [t, h, w]
|
||||
std::tuple<at::Tensor, at::Tensor> multimodal_rotary_embedding_cpu(
|
||||
at::Tensor& positions,
|
||||
at::Tensor& query,
|
||||
at::Tensor& key,
|
||||
int64_t head_size,
|
||||
at::Tensor& cos_sin_cache,
|
||||
const std::optional<std::vector<int64_t>>& mrope_section,
|
||||
bool mrope_interleaved,
|
||||
bool is_neox) {
|
||||
RECORD_FUNCTION("sgl-kernel::multimodal_rotary_embedding_cpu", std::vector<c10::IValue>({query, key}));
|
||||
TORCH_CHECK(positions.dim() == 1 || positions.dim() == 2, "positions must be a 1D or 2D tensor");
|
||||
CHECK_DIM(2, query);
|
||||
CHECK_DIM(2, key);
|
||||
CHECK_DIM(2, cos_sin_cache);
|
||||
CHECK_LAST_DIM_CONTIGUOUS_INPUT(query);
|
||||
CHECK_LAST_DIM_CONTIGUOUS_INPUT(key);
|
||||
int64_t rotary_dim = cos_sin_cache.size(1);
|
||||
int64_t num_tokens = positions.size(-1);
|
||||
CHECK_EQ(key.size(0), num_tokens);
|
||||
CHECK_EQ(query.size(0), num_tokens);
|
||||
const auto input_dtype = query.scalar_type();
|
||||
TORCH_CHECK(positions.scalar_type() == at::kLong, "expect positions to be int64, got ", positions.scalar_type());
|
||||
TORCH_CHECK(input_dtype == key.scalar_type(), "query and key must have the same data type");
|
||||
TORCH_CHECK(input_dtype == cos_sin_cache.scalar_type(), "query and cos_sin_cache must have the same data type");
|
||||
|
||||
int64_t num_heads = query.size(-1) / head_size;
|
||||
int64_t num_kv_heads = key.size(-1) / head_size;
|
||||
int64_t key_stride_s = key.stride(0);
|
||||
int64_t query_stride_s = query.stride(0);
|
||||
|
||||
if (positions.dim() == 2) {
|
||||
TORCH_CHECK(mrope_section.has_value(), "mrope_section must be provided when positions is 2D");
|
||||
auto mrope_section_val = mrope_section.value();
|
||||
CHECK_EQ(mrope_section_val.size(), 3);
|
||||
CHECK_EQ(positions.size(0), 3);
|
||||
int64_t mrope_section_t = mrope_section_val[0];
|
||||
int64_t mrope_section_h = mrope_section_val[1];
|
||||
int64_t mrope_section_w = mrope_section_val[2];
|
||||
int64_t positions_stride0 = positions.stride(0);
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(input_dtype, "rotary_embedding_cpu", [&] {
|
||||
if (is_neox) {
|
||||
multimodal_rotary_embedding_neox_2D_kernel_impl<scalar_t>(
|
||||
positions.data_ptr<int64_t>(),
|
||||
query.data_ptr<scalar_t>(),
|
||||
key.data_ptr<scalar_t>(),
|
||||
cos_sin_cache.data_ptr<scalar_t>(),
|
||||
rotary_dim,
|
||||
query_stride_s,
|
||||
key_stride_s,
|
||||
num_heads,
|
||||
num_kv_heads,
|
||||
head_size,
|
||||
num_tokens,
|
||||
mrope_section_t,
|
||||
mrope_section_h,
|
||||
mrope_section_w,
|
||||
positions_stride0,
|
||||
mrope_interleaved);
|
||||
} else {
|
||||
multimodal_rotary_embedding_2D_kernel_impl<scalar_t>(
|
||||
positions.data_ptr<int64_t>(),
|
||||
query.data_ptr<scalar_t>(),
|
||||
key.data_ptr<scalar_t>(),
|
||||
cos_sin_cache.data_ptr<scalar_t>(),
|
||||
rotary_dim,
|
||||
query_stride_s,
|
||||
key_stride_s,
|
||||
num_heads,
|
||||
num_kv_heads,
|
||||
head_size,
|
||||
num_tokens,
|
||||
mrope_section_t,
|
||||
mrope_section_h,
|
||||
mrope_section_w,
|
||||
positions_stride0,
|
||||
mrope_interleaved);
|
||||
}
|
||||
});
|
||||
} else { // positions.dim() == 1
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(input_dtype, "rotary_embedding_cpu", [&] {
|
||||
if (is_neox) {
|
||||
rotary_embedding_neox_4D_kernel_impl<scalar_t>(
|
||||
positions.data_ptr<int64_t>(),
|
||||
query.data_ptr<scalar_t>(),
|
||||
key.data_ptr<scalar_t>(),
|
||||
cos_sin_cache.data_ptr<scalar_t>(),
|
||||
rotary_dim,
|
||||
0,
|
||||
query_stride_s,
|
||||
head_size,
|
||||
0,
|
||||
key_stride_s,
|
||||
head_size,
|
||||
num_heads,
|
||||
num_kv_heads,
|
||||
head_size,
|
||||
1,
|
||||
num_tokens);
|
||||
} else {
|
||||
rotary_embedding_4D_kernel_impl<scalar_t>(
|
||||
positions.data_ptr<int64_t>(),
|
||||
query.data_ptr<scalar_t>(),
|
||||
key.data_ptr<scalar_t>(),
|
||||
cos_sin_cache.data_ptr<scalar_t>(),
|
||||
rotary_dim,
|
||||
0,
|
||||
query_stride_s,
|
||||
head_size,
|
||||
0,
|
||||
key_stride_s,
|
||||
head_size,
|
||||
num_heads,
|
||||
num_kv_heads,
|
||||
head_size,
|
||||
1,
|
||||
num_tokens);
|
||||
}
|
||||
});
|
||||
}
|
||||
return std::make_tuple(query, key);
|
||||
}
|
||||
389
third_party/sglang/sgl-kernel/csrc/cpu/shm.cpp
vendored
Normal file
389
third_party/sglang/sgl-kernel/csrc/cpu/shm.cpp
vendored
Normal file
@@ -0,0 +1,389 @@
|
||||
#include "shm.h"
|
||||
#if defined(__x86_64__)
|
||||
#include "x86_64/shm.h"
|
||||
#elif defined(__aarch64__)
|
||||
#include "aarch64/shm.h"
|
||||
#else
|
||||
#error "unsupported architecture"
|
||||
#endif
|
||||
|
||||
#include <ATen/ATen.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// states for collectives
|
||||
enum coll_state {
|
||||
coll_begin = 0,
|
||||
coll_allreduce_naive__copy_in_done,
|
||||
coll_allreduce_naive__reduce_done,
|
||||
// alternative state when allreduce is working on alternative buffer
|
||||
// of the double buffer.
|
||||
coll_alt1_allreduce_naive__copy_in_done,
|
||||
coll_alt2_allreduce_naive__copy_in_done,
|
||||
coll_alt1_allreduce_naive__reduce_done,
|
||||
coll_allgather_naive__copy_in_done,
|
||||
coll_alt1_allgather_naive__copy_in_done,
|
||||
coll_alt2_allgather_naive__copy_in_done,
|
||||
};
|
||||
|
||||
// SHM building blocks
|
||||
struct SharedData {
|
||||
const char* name;
|
||||
int descriptor;
|
||||
void* bytes;
|
||||
size_t nbytes;
|
||||
};
|
||||
|
||||
void shared_open(SharedData* data, const char* name, size_t nbytes) {
|
||||
int d = shm_open(name, O_RDWR, S_IRUSR | S_IWUSR);
|
||||
if (d != -1) {
|
||||
void* bytes = mmap(NULL, nbytes, PROT_READ | PROT_WRITE, MAP_SHARED, d, 0);
|
||||
data->name = name;
|
||||
data->descriptor = d;
|
||||
data->bytes = bytes;
|
||||
data->nbytes = nbytes;
|
||||
} else {
|
||||
if (errno != ENOENT) {
|
||||
// don't print if shm can not be found because we want to loop over from
|
||||
// caller again until the other ranks created the shm
|
||||
printf("shared_open %s failed, errno=%d\n", name, errno);
|
||||
}
|
||||
data->descriptor = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void shared_create(SharedData* data, const char* name, void* bytes, size_t nbytes) {
|
||||
int d = shm_open(name, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
|
||||
if (d != -1) {
|
||||
nbytes = write(d, bytes, nbytes);
|
||||
if (nbytes > 0) {
|
||||
shared_open(data, name, nbytes);
|
||||
}
|
||||
} else {
|
||||
printf("shared_create %s failed\n", name);
|
||||
}
|
||||
}
|
||||
|
||||
static int world_size;
|
||||
|
||||
// SHM based allreduce helper functions
|
||||
// buffer that holds shm name
|
||||
#define NAME_BUF_SIZE 1000
|
||||
#define MAX_BUF_SIZE 1048576 * 32
|
||||
#define NAIVE_ALLREDUCE_THRESHOLD 1048576
|
||||
#define SHM_BUFFER_NAME "deepspeed_allreduce_buffer"
|
||||
struct allreduce_workspace {
|
||||
enum coll_state states[2]; // idx=0 -- state for symmetric_naive_all_reduce
|
||||
// idx=1 -- state for distributed_naive_all_reduce
|
||||
// double buffer to avoid syncing between rounds
|
||||
// offset=0 -- 2*NAIVE_ALLREDUCE_THRESHOLD : buffer for
|
||||
// symmetric_naive_all_reduce after that : buffer for
|
||||
// distributed_naive_all_reduce
|
||||
char buffer[2 * NAIVE_ALLREDUCE_THRESHOLD + 2 * MAX_BUF_SIZE];
|
||||
};
|
||||
|
||||
#define BUFFER0_OFFSET(current_buffer) current_buffer* NAIVE_ALLREDUCE_THRESHOLD
|
||||
#define BUFFER1_OFFSET(current_buffer) 2 * NAIVE_ALLREDUCE_THRESHOLD + current_buffer* MAX_BUF_SIZE
|
||||
|
||||
struct allreduce_workspace** workspace;
|
||||
|
||||
// buffer for small messages, double buffer
|
||||
char** symmetric_buffer[2];
|
||||
// buffer for large messages, double buffer
|
||||
char** distributed_buffer[2];
|
||||
|
||||
void wait_buffer_state_until_2(int index, enum coll_state state0, enum coll_state state1, int state_group) {
|
||||
volatile enum coll_state* state_ptr = &(workspace[index]->states[state_group]);
|
||||
|
||||
while (1) {
|
||||
volatile enum coll_state cur_state = *state_ptr;
|
||||
if (cur_state == state0 || cur_state == state1) break;
|
||||
}
|
||||
}
|
||||
|
||||
void reduce_all_buffers(
|
||||
int start_elements,
|
||||
int num_elements,
|
||||
c10::ScalarType scalar_type,
|
||||
int to_buffer_idx,
|
||||
char* to_buffer,
|
||||
char** buffers) {
|
||||
switch (scalar_type) {
|
||||
case c10::ScalarType::BFloat16:
|
||||
reduce_bf16_buffers(start_elements, num_elements, to_buffer, buffers, world_size);
|
||||
break;
|
||||
case c10::ScalarType::Half:
|
||||
reduce_fp16_buffers(start_elements, num_elements, to_buffer, buffers, world_size);
|
||||
break;
|
||||
case c10::ScalarType::Float:
|
||||
reduce_fp32_buffers(start_elements, num_elements, to_buffer, buffers, world_size);
|
||||
break;
|
||||
default:
|
||||
assert(!"Should not get here");
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_initialized = false;
|
||||
static int world_rank;
|
||||
|
||||
void shm_initialize(int size, int rank, const char* addr_string, const char* port_string) {
|
||||
if (is_initialized) {
|
||||
return;
|
||||
}
|
||||
is_initialized = true;
|
||||
|
||||
world_size = size;
|
||||
world_rank = rank;
|
||||
|
||||
char shm_name_prefix[NAME_BUF_SIZE];
|
||||
char shm_name[NAME_BUF_SIZE];
|
||||
snprintf(shm_name_prefix, NAME_BUF_SIZE, "%s_%d_%s_%s", SHM_BUFFER_NAME, getuid(), addr_string, port_string);
|
||||
// create shared workspace for SHM based allreduce
|
||||
SharedData allreduce_buffer;
|
||||
// allocate workspace_buf for current rank
|
||||
struct allreduce_workspace* workspace_buf;
|
||||
struct allreduce_workspace* workspace_buf_other;
|
||||
workspace_buf = (struct allreduce_workspace*)malloc(sizeof(struct allreduce_workspace));
|
||||
snprintf(shm_name, NAME_BUF_SIZE, "%.900s_%d", shm_name_prefix, rank);
|
||||
shared_create(&allreduce_buffer, shm_name, workspace_buf, sizeof(struct allreduce_workspace));
|
||||
workspace_buf = (struct allreduce_workspace*)allreduce_buffer.bytes;
|
||||
workspace_buf->states[0] = coll_alt2_allreduce_naive__copy_in_done;
|
||||
workspace_buf->states[1] = coll_begin;
|
||||
|
||||
// create the workspace pointer list
|
||||
workspace = (struct allreduce_workspace**)malloc(size * sizeof(struct allreduce_workspace*));
|
||||
symmetric_buffer[0] = (char**)malloc(size * sizeof(char**));
|
||||
symmetric_buffer[1] = (char**)malloc(size * sizeof(char**));
|
||||
distributed_buffer[0] = (char**)malloc(size * sizeof(char**));
|
||||
distributed_buffer[1] = (char**)malloc(size * sizeof(char**));
|
||||
|
||||
// map shm of all ranks
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (i != rank) {
|
||||
snprintf(shm_name, NAME_BUF_SIZE, "%.900s_%d", shm_name_prefix, i);
|
||||
// printf("open %s, %d\n", shm_name, rank);
|
||||
do {
|
||||
shared_open(&allreduce_buffer, shm_name, sizeof(struct allreduce_workspace));
|
||||
} while (allreduce_buffer.descriptor == -1 && errno == ENOENT);
|
||||
workspace_buf_other = (struct allreduce_workspace*)allreduce_buffer.bytes;
|
||||
workspace[i] = workspace_buf_other;
|
||||
} else {
|
||||
workspace[i] = workspace_buf;
|
||||
}
|
||||
symmetric_buffer[0][i] = workspace[i]->buffer + BUFFER0_OFFSET(0);
|
||||
symmetric_buffer[1][i] = workspace[i]->buffer + BUFFER0_OFFSET(1);
|
||||
distributed_buffer[0][i] = workspace[i]->buffer + BUFFER1_OFFSET(0);
|
||||
distributed_buffer[1][i] = workspace[i]->buffer + BUFFER1_OFFSET(1);
|
||||
}
|
||||
}
|
||||
|
||||
#define positive_mod(num, mod) ((((num) % (mod)) + (mod)) % (mod))
|
||||
#define rank_mod(rank) positive_mod(rank, world_size)
|
||||
size_t slice_size(size_t chunk_el, int slice_idx) {
|
||||
size_t slice_size = chunk_el / world_size;
|
||||
return slice_idx == world_size - 1 ? slice_size + (chunk_el % world_size) : slice_size;
|
||||
}
|
||||
|
||||
char* slice_data(char* data_ptr, size_t chunk_el, int el_size, int slice_idx) {
|
||||
size_t slice_size = chunk_el / world_size;
|
||||
size_t el_offset = slice_size * slice_idx;
|
||||
return data_ptr + el_offset * el_size;
|
||||
}
|
||||
|
||||
size_t slice_el_start(size_t chunk_el, int slice_idx) {
|
||||
size_t slice_size = chunk_el / world_size;
|
||||
return slice_size * slice_idx;
|
||||
}
|
||||
|
||||
void symmetric_naive_all_reduce(char* data_ptr, c10::ScalarType scalar_type, size_t chunk_size, size_t chunk_el) {
|
||||
const int state_group = 0;
|
||||
static int current_buffer = 0;
|
||||
static int state_idx = 0;
|
||||
|
||||
// init states to case 0 to get rid of "maybe-uninitialized" warning.
|
||||
enum coll_state copy_current = coll_allreduce_naive__copy_in_done;
|
||||
enum coll_state copy_next = coll_alt1_allreduce_naive__copy_in_done;
|
||||
|
||||
switch (state_idx) {
|
||||
case 0:
|
||||
copy_current = coll_allreduce_naive__copy_in_done;
|
||||
copy_next = coll_alt1_allreduce_naive__copy_in_done;
|
||||
break;
|
||||
case 1:
|
||||
copy_current = coll_alt1_allreduce_naive__copy_in_done;
|
||||
copy_next = coll_alt2_allreduce_naive__copy_in_done;
|
||||
break;
|
||||
case 2:
|
||||
copy_current = coll_alt2_allreduce_naive__copy_in_done;
|
||||
copy_next = coll_allreduce_naive__copy_in_done;
|
||||
break;
|
||||
default:
|
||||
assert(!"Should not get here.");
|
||||
}
|
||||
state_idx = (state_idx + 1) % 3;
|
||||
|
||||
parallel_memcpy(symmetric_buffer[current_buffer][world_rank], data_ptr, chunk_size);
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
workspace[world_rank]->states[state_group] = copy_current;
|
||||
|
||||
for (int i = 0; i < world_size; i++) {
|
||||
// wait until the other rank copy the buffer
|
||||
if (i != world_rank) {
|
||||
wait_buffer_state_until_2(i, copy_current, copy_next, state_group);
|
||||
}
|
||||
}
|
||||
|
||||
// each rank reduce the buffer independently so therre is no need for
|
||||
// synchronization afterward
|
||||
reduce_all_buffers(0, chunk_el, scalar_type, world_rank, data_ptr, symmetric_buffer[current_buffer]);
|
||||
|
||||
// switch buffer
|
||||
current_buffer = 1 - current_buffer;
|
||||
}
|
||||
|
||||
// naive allreduce distributed, each rank do naive reduce on its slice
|
||||
void distributed_naive_reduce(char* data_ptr, c10::ScalarType scalar_type, size_t chunk_size, size_t chunk_el) {
|
||||
const int state_group = 1;
|
||||
static int current_buffer = 0;
|
||||
static int state_idx = 0;
|
||||
|
||||
// init states to case 0 to get rid of "maybe-uninitialized" warning.
|
||||
enum coll_state copy_current = coll_allreduce_naive__copy_in_done;
|
||||
enum coll_state reduce_current = coll_allreduce_naive__reduce_done;
|
||||
enum coll_state copy_next = coll_alt1_allreduce_naive__copy_in_done;
|
||||
|
||||
// similar to symmetric_naive_allreduce, but here we only need two sets of
|
||||
// states, because distributed naive reduce has two barriers in the algorithm
|
||||
switch (state_idx) {
|
||||
case 0:
|
||||
copy_current = coll_allreduce_naive__copy_in_done;
|
||||
reduce_current = coll_allreduce_naive__reduce_done;
|
||||
copy_next = coll_alt1_allreduce_naive__copy_in_done;
|
||||
break;
|
||||
case 1:
|
||||
copy_current = coll_alt1_allreduce_naive__copy_in_done;
|
||||
reduce_current = coll_alt1_allreduce_naive__reduce_done;
|
||||
copy_next = coll_allreduce_naive__copy_in_done;
|
||||
break;
|
||||
default:
|
||||
assert(!"Should not get here.");
|
||||
}
|
||||
state_idx = (state_idx + 1) % 2;
|
||||
|
||||
int data_size = chunk_size / chunk_el;
|
||||
parallel_memcpy(distributed_buffer[current_buffer][world_rank], data_ptr, chunk_size);
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
workspace[world_rank]->states[state_group] = copy_current;
|
||||
|
||||
for (int i = 0; i < world_size; i++) {
|
||||
// wait until all the other ranks copy the buffer
|
||||
if (i != world_rank) wait_buffer_state_until_2(i, copy_current, reduce_current, state_group);
|
||||
}
|
||||
|
||||
// reduce scatter
|
||||
reduce_all_buffers(
|
||||
slice_el_start(chunk_el, world_rank),
|
||||
slice_size(chunk_el, world_rank),
|
||||
scalar_type,
|
||||
world_rank,
|
||||
distributed_buffer[current_buffer][world_rank],
|
||||
distributed_buffer[current_buffer]);
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
workspace[world_rank]->states[state_group] = reduce_current;
|
||||
|
||||
for (int i = 0; i < world_size; i++) {
|
||||
// wait until all the other ranks reduce the buffer
|
||||
if (i != world_rank) wait_buffer_state_until_2(i, reduce_current, copy_next, state_group);
|
||||
}
|
||||
|
||||
for (int i = 0; i < world_size; i++) {
|
||||
int rank = (i + world_rank) % world_size;
|
||||
parallel_memcpy(
|
||||
slice_data(data_ptr, chunk_el, data_size, rank),
|
||||
slice_data(distributed_buffer[current_buffer][rank], chunk_el, chunk_size / chunk_el, rank),
|
||||
slice_size(chunk_el, rank) * data_size);
|
||||
}
|
||||
|
||||
current_buffer = 1 - current_buffer;
|
||||
}
|
||||
|
||||
void all_reduce_outer_loop(torch::Tensor& data, size_t numel, int data_size) {
|
||||
for (int offset = 0; offset < data_size; offset += MAX_BUF_SIZE) {
|
||||
auto data_ptr = ((char*)(data.data_ptr()) + offset);
|
||||
size_t chunk_size = data_size - offset > MAX_BUF_SIZE ? MAX_BUF_SIZE : data_size - offset;
|
||||
size_t chunk_el = chunk_size / (data_size / numel);
|
||||
if (chunk_size < NAIVE_ALLREDUCE_THRESHOLD) {
|
||||
symmetric_naive_all_reduce(data_ptr, data.scalar_type(), chunk_size, chunk_el);
|
||||
} else {
|
||||
distributed_naive_reduce(data_ptr, data.scalar_type(), chunk_size, chunk_el);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void naive_all_gather(char* result_ptr, char* data_ptr, size_t res_stride, size_t chunk_size, size_t chunk_el) {
|
||||
const int state_group = 1;
|
||||
static int current_buffer = 0;
|
||||
static int state_idx = 0;
|
||||
|
||||
// init states to case 0 to get rid of "maybe-uninitialized" warning.
|
||||
enum coll_state copy_current = coll_allgather_naive__copy_in_done;
|
||||
enum coll_state copy_next = coll_alt1_allgather_naive__copy_in_done;
|
||||
|
||||
switch (state_idx) {
|
||||
case 0:
|
||||
copy_current = coll_allgather_naive__copy_in_done;
|
||||
copy_next = coll_alt1_allgather_naive__copy_in_done;
|
||||
break;
|
||||
case 1:
|
||||
copy_current = coll_alt1_allgather_naive__copy_in_done;
|
||||
copy_next = coll_alt2_allgather_naive__copy_in_done;
|
||||
break;
|
||||
case 2:
|
||||
copy_current = coll_alt2_allgather_naive__copy_in_done;
|
||||
copy_next = coll_allgather_naive__copy_in_done;
|
||||
break;
|
||||
default:
|
||||
assert(!"Should not get here.");
|
||||
}
|
||||
state_idx = (state_idx + 1) % 3;
|
||||
|
||||
parallel_memcpy(distributed_buffer[current_buffer][world_rank], data_ptr, chunk_size);
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
workspace[world_rank]->states[state_group] = copy_current;
|
||||
|
||||
for (int i = 0; i < world_size; i++) {
|
||||
// wait until all the other ranks copy the buffer
|
||||
if (i != world_rank) wait_buffer_state_until_2(i, copy_current, copy_next, state_group);
|
||||
}
|
||||
for (int i = 0; i < world_size; i++) {
|
||||
parallel_memcpy(result_ptr + i * res_stride, distributed_buffer[current_buffer][i], chunk_size);
|
||||
}
|
||||
current_buffer = 1 - current_buffer;
|
||||
}
|
||||
|
||||
torch::Tensor& all_gather(torch::Tensor& result, torch::Tensor& data, int dim, size_t numel, int data_size) {
|
||||
size_t dim_el = data.stride(dim) * data.size(dim);
|
||||
int dtype_size = data_size / numel;
|
||||
size_t dim_size = dim_el * dtype_size;
|
||||
int dim_count = data_size / dim_size;
|
||||
auto data_ptr = (char*)(data.data_ptr());
|
||||
auto result_ptr = (char*)(result.data_ptr());
|
||||
for (int i = 0; i < dim_count; i++) {
|
||||
for (size_t offset = 0; offset < dim_size; offset += MAX_BUF_SIZE) {
|
||||
size_t chunk_size = dim_size - offset > MAX_BUF_SIZE ? MAX_BUF_SIZE : dim_size - offset;
|
||||
size_t chunk_el = chunk_size / dtype_size;
|
||||
naive_all_gather(
|
||||
result_ptr + i * dim_size * world_size + offset,
|
||||
data_ptr + i * dim_size + offset,
|
||||
dim_size,
|
||||
chunk_size,
|
||||
chunk_el);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
10
third_party/sglang/sgl-kernel/csrc/cpu/shm.h
vendored
Normal file
10
third_party/sglang/sgl-kernel/csrc/cpu/shm.h
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
#include <torch/all.h>
|
||||
|
||||
#include <torch/csrc/distributed/c10d/ProcessGroup.hpp>
|
||||
|
||||
#ifndef __SHM_COLLECTIVES__
|
||||
#define __SHM_COLLECTIVES__
|
||||
void shm_initialize(int size, int rank, const char* addr_string, const char* port_string);
|
||||
void all_reduce_outer_loop(torch::Tensor& data, size_t numel, int data_size);
|
||||
torch::Tensor& all_gather(torch::Tensor& result, torch::Tensor& data, int dim, size_t numel, int data_size);
|
||||
#endif
|
||||
669
third_party/sglang/sgl-kernel/csrc/cpu/topk.cpp
vendored
Normal file
669
third_party/sglang/sgl-kernel/csrc/cpu/topk.cpp
vendored
Normal file
@@ -0,0 +1,669 @@
|
||||
#include "common.h"
|
||||
#include "vec.h"
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename scalar_t, int SIZE>
|
||||
inline void softmax(float* __restrict__ out, const scalar_t* __restrict__ input) {
|
||||
using bVec = at::vec::Vectorized<scalar_t>;
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
|
||||
constexpr int kVecSize = bVec::size();
|
||||
|
||||
// step 1: get max
|
||||
fVec max_fvec = fVec(-std::numeric_limits<float>::infinity());
|
||||
if constexpr (SIZE < kVecSize) {
|
||||
// SIZE = 1, 2, 4, 8, 16; only the top half is used
|
||||
bVec x_bvec = bVec::loadu(input, SIZE);
|
||||
fVec x_fvec0, x_fvec1;
|
||||
std::tie(x_fvec0, x_fvec1) = at::vec::convert_to_float(x_bvec);
|
||||
x_fvec0 = fVec::set(max_fvec, x_fvec0, SIZE);
|
||||
max_fvec = at::vec::maximum(max_fvec, x_fvec0);
|
||||
x_fvec0.store(out, SIZE);
|
||||
} else {
|
||||
for (int d = 0; d < SIZE; d += kVecSize) {
|
||||
bVec x_bvec = bVec::loadu(input + d);
|
||||
fVec x_fvec0, x_fvec1;
|
||||
std::tie(x_fvec0, x_fvec1) = at::vec::convert_to_float(x_bvec);
|
||||
|
||||
max_fvec = at::vec::maximum(max_fvec, x_fvec0);
|
||||
max_fvec = at::vec::maximum(max_fvec, x_fvec1);
|
||||
x_fvec0.store(out + d);
|
||||
x_fvec1.store(out + d + fVec::size());
|
||||
}
|
||||
}
|
||||
float max_val = vec_reduce_max(max_fvec);
|
||||
max_fvec = fVec(max_val);
|
||||
|
||||
// step 2: sum of (x - max).exp()
|
||||
fVec sum_fvec = fVec(float(0));
|
||||
if constexpr (SIZE < fVec::size()) {
|
||||
// SIZE = 1, 2, 4, 8
|
||||
fVec x_fvec = (fVec::loadu(out, SIZE) - max_fvec).exp_u20();
|
||||
x_fvec = fVec::set(sum_fvec, x_fvec, SIZE);
|
||||
sum_fvec += x_fvec;
|
||||
x_fvec.store(out, SIZE);
|
||||
} else {
|
||||
for (int d = 0; d < SIZE; d += fVec::size()) {
|
||||
fVec x_fvec = (fVec::loadu(out + d) - max_fvec).exp_u20();
|
||||
sum_fvec += x_fvec;
|
||||
x_fvec.store(out + d);
|
||||
}
|
||||
}
|
||||
float sum_val = vec_reduce_sum(sum_fvec);
|
||||
|
||||
// step 3: x * (1 / sum)
|
||||
sum_fvec = fVec(1.f / sum_val);
|
||||
if constexpr (SIZE < fVec::size()) {
|
||||
// SIZE = 1, 2, 4, 8
|
||||
fVec out_fvec = fVec::loadu(out, SIZE) * sum_fvec;
|
||||
out_fvec.store(out, SIZE);
|
||||
} else {
|
||||
for (int d = 0; d < SIZE; d += fVec::size()) {
|
||||
fVec out_fvec = fVec::loadu(out + d) * sum_fvec;
|
||||
out_fvec.store(out + d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename scalar_t, int NUM_EXPERTS>
|
||||
void grouped_topk_kernel_impl(
|
||||
float* __restrict__ topk_weights,
|
||||
int32_t* __restrict__ topk_ids,
|
||||
const scalar_t* __restrict__ gating_output,
|
||||
int64_t num_tokens,
|
||||
int64_t topk,
|
||||
int64_t num_groups,
|
||||
int64_t topk_group,
|
||||
bool renormalize) {
|
||||
const int64_t num_experts_per_group = NUM_EXPERTS / num_groups;
|
||||
at::parallel_for(0, num_tokens, 0, [&](int64_t begin, int64_t end) {
|
||||
alignas(64) float scores[NUM_EXPERTS];
|
||||
|
||||
using elem_t = std::pair<float, int32_t>;
|
||||
std::vector<elem_t> queue(num_groups);
|
||||
std::vector<elem_t> queue2(topk_group * num_experts_per_group);
|
||||
|
||||
for (int64_t i = begin; i < end; ++i) {
|
||||
// do softmax to get scores
|
||||
softmax<scalar_t, NUM_EXPERTS>(scores, gating_output + i * NUM_EXPERTS);
|
||||
|
||||
// find max score per group
|
||||
for (int64_t g = 0; g < num_groups; ++g) {
|
||||
float gmax = -std::numeric_limits<float>::infinity();
|
||||
for (int64_t e = 0; e < num_experts_per_group; ++e) {
|
||||
gmax = std::max(gmax, scores[g * num_experts_per_group + e]);
|
||||
}
|
||||
queue[g] = {gmax, g};
|
||||
}
|
||||
|
||||
// find group topk
|
||||
std::partial_sort(
|
||||
queue.begin(), queue.begin() + topk_group, queue.end(), [](const elem_t& x, const elem_t& y) -> bool {
|
||||
return x.first > y.first;
|
||||
});
|
||||
|
||||
for (int64_t g = 0; g < topk_group; ++g) {
|
||||
int32_t group_idx = queue[g].second;
|
||||
for (int64_t e = 0; e < num_experts_per_group; ++e) {
|
||||
int32_t expert_idx = group_idx * num_experts_per_group + e;
|
||||
queue2[g * num_experts_per_group + e] = {scores[expert_idx], expert_idx};
|
||||
}
|
||||
}
|
||||
|
||||
// find global topk
|
||||
std::partial_sort(
|
||||
queue2.begin(), queue2.begin() + topk, queue2.end(), [](const elem_t& x, const elem_t& y) -> bool {
|
||||
return x.first > y.first;
|
||||
});
|
||||
|
||||
for (int64_t j = 0; j < topk; ++j) {
|
||||
topk_weights[i * topk + j] = queue2[j].first;
|
||||
topk_ids[i * topk + j] = queue2[j].second;
|
||||
}
|
||||
|
||||
if (renormalize) {
|
||||
float sum = 0.f;
|
||||
for (int64_t j = 0; j < topk; ++j) {
|
||||
sum += topk_weights[i * topk + j];
|
||||
}
|
||||
float scale = 1.f / sum;
|
||||
for (int64_t j = 0; j < topk; ++j) {
|
||||
topk_weights[i * topk + j] *= scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <typename scalar_t, int SIZE>
|
||||
inline void sigmoid(float* __restrict__ out, const scalar_t* __restrict__ input) {
|
||||
using bVec = at::vec::Vectorized<scalar_t>;
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
|
||||
const fVec one = fVec(1.f);
|
||||
|
||||
constexpr int kVecSize = bVec::size();
|
||||
for (int d = 0; d < SIZE; d += kVecSize) {
|
||||
bVec x_bvec = bVec::loadu(input + d);
|
||||
fVec x_fvec0, x_fvec1;
|
||||
std::tie(x_fvec0, x_fvec1) = at::vec::convert_to_float(x_bvec);
|
||||
|
||||
x_fvec0 = one / (one + x_fvec0.neg().exp_u20());
|
||||
x_fvec1 = one / (one + x_fvec1.neg().exp_u20());
|
||||
|
||||
x_fvec0.store(out + d);
|
||||
x_fvec1.store(out + d + fVec::size());
|
||||
}
|
||||
}
|
||||
|
||||
template <typename scalar_t, int NUM_EXPERTS>
|
||||
void topk_sigmoid_kernel_impl(
|
||||
float* __restrict__ topk_weights,
|
||||
int32_t* __restrict__ topk_ids,
|
||||
const scalar_t* __restrict__ gating_output,
|
||||
int64_t num_tokens,
|
||||
int64_t topk,
|
||||
bool renormalize) {
|
||||
using Vec = at::vec::Vectorized<float>;
|
||||
const int64_t num_experts_per_group = NUM_EXPERTS;
|
||||
at::parallel_for(0, num_tokens, 0, [&](int64_t begin, int64_t end) {
|
||||
alignas(64) float scores[NUM_EXPERTS];
|
||||
using elem_t = std::pair<float, int32_t>;
|
||||
std::vector<elem_t> queue(num_experts_per_group);
|
||||
|
||||
for (int64_t i = begin; i < end; ++i) {
|
||||
at::vec::convert<scalar_t, float>(gating_output + i * NUM_EXPERTS, scores, NUM_EXPERTS);
|
||||
|
||||
float gmax = at::vec::reduce_all<float>(
|
||||
[](Vec& x, Vec& y) { return at::vec::maximum(x, y); }, scores, num_experts_per_group);
|
||||
|
||||
// find position of first max,
|
||||
// note that we may have multiple max values.
|
||||
int first_max_idx = -1;
|
||||
for (int64_t e = 0; e < num_experts_per_group; ++e) {
|
||||
if (scores[e] == gmax) {
|
||||
first_max_idx = e;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// scalar sigmoid
|
||||
topk_weights[i] = 1.0 / (1.0 + exp(0.0 - gmax));
|
||||
topk_ids[i] = first_max_idx;
|
||||
|
||||
if (renormalize) {
|
||||
float sum = 0.f;
|
||||
for (int64_t j = 0; j < topk; ++j) {
|
||||
sum += topk_weights[i * topk + j];
|
||||
}
|
||||
float scale = 1.f / sum;
|
||||
for (int64_t j = 0; j < topk; ++j) {
|
||||
topk_weights[i * topk + j] *= scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <typename scalar_t, int NUM_EXPERTS>
|
||||
void topk_softmax_kernel_impl(
|
||||
float* __restrict__ topk_weights,
|
||||
int32_t* __restrict__ topk_ids,
|
||||
const scalar_t* __restrict__ gating_output,
|
||||
int64_t num_tokens,
|
||||
int64_t topk,
|
||||
bool renormalize) {
|
||||
const int64_t num_experts_per_group = NUM_EXPERTS;
|
||||
at::parallel_for(0, num_tokens, 0, [&](int64_t begin, int64_t end) {
|
||||
alignas(64) float scores[NUM_EXPERTS];
|
||||
using elem_t = std::pair<float, int32_t>;
|
||||
std::vector<elem_t> queue(num_experts_per_group);
|
||||
|
||||
for (int64_t i = begin; i < end; ++i) {
|
||||
softmax<scalar_t, NUM_EXPERTS>(scores, gating_output + i * NUM_EXPERTS);
|
||||
|
||||
for (int64_t e = 0; e < num_experts_per_group; ++e) {
|
||||
queue[e] = {scores[e], e};
|
||||
}
|
||||
|
||||
std::partial_sort(queue.begin(), queue.begin() + topk, queue.end(), [](const elem_t& x, const elem_t& y) -> bool {
|
||||
return x.first > y.first;
|
||||
});
|
||||
|
||||
for (int64_t j = 0; j < topk; ++j) {
|
||||
topk_weights[i * topk + j] = queue[j].first;
|
||||
topk_ids[i * topk + j] = queue[j].second;
|
||||
}
|
||||
|
||||
if (renormalize) {
|
||||
float sum = 0.f;
|
||||
for (int64_t j = 0; j < topk; ++j) {
|
||||
sum += topk_weights[i * topk + j];
|
||||
}
|
||||
float scale = 1.f / sum;
|
||||
for (int64_t j = 0; j < topk; ++j) {
|
||||
topk_weights[i * topk + j] *= scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <typename scalar_t, typename param_t, int SIZE>
|
||||
inline void
|
||||
apply_bias(float* __restrict__ scores2, const float* __restrict__ scores, const param_t* __restrict__ bias) {
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
using bVec = at::vec::Vectorized<scalar_t>;
|
||||
auto vec_size = bVec::size();
|
||||
int d = 0;
|
||||
for (; d <= SIZE - vec_size; d += vec_size) {
|
||||
fVec bias0, bias1, x0, x1;
|
||||
std::tie(bias0, bias1) = load_float_vec2(bias + d);
|
||||
std::tie(x0, x1) = load_float_vec2(scores + d);
|
||||
x0 = x0 + bias0;
|
||||
x1 = x1 + bias1;
|
||||
x0.store(scores2 + d);
|
||||
x1.store(scores2 + d + fVec::size());
|
||||
}
|
||||
for (; d < SIZE; d++) {
|
||||
scores2[d] = scores[d] + (float)bias[d];
|
||||
}
|
||||
}
|
||||
|
||||
template <typename scalar_t, typename param_t, int NUM_EXPERTS, int TOPK>
|
||||
void biased_grouped_topk_kernel_impl(
|
||||
float* __restrict__ topk_weights,
|
||||
int32_t* __restrict__ topk_ids,
|
||||
const scalar_t* __restrict__ gating_output,
|
||||
const param_t* __restrict__ bias,
|
||||
int64_t num_tokens,
|
||||
int64_t num_groups,
|
||||
int64_t topk_group,
|
||||
bool renormalize) {
|
||||
using Vec = at::vec::Vectorized<float>;
|
||||
|
||||
const int64_t num_experts_per_group = NUM_EXPERTS / num_groups;
|
||||
at::parallel_for(0, num_tokens, 0, [&](int64_t begin, int64_t end) {
|
||||
// scores: sigmoid
|
||||
alignas(64) float scores[NUM_EXPERTS];
|
||||
// scores for choice: sigmoid + bias
|
||||
alignas(64) float scores2[NUM_EXPERTS];
|
||||
|
||||
using elem_t = std::pair<float, int32_t>;
|
||||
std::vector<elem_t> queue(num_groups);
|
||||
std::vector<elem_t> queue2(topk_group * num_experts_per_group);
|
||||
|
||||
for (int64_t i = begin; i < end; ++i) {
|
||||
// do sigmoid to get scores
|
||||
sigmoid<scalar_t, NUM_EXPERTS>(scores, gating_output + i * NUM_EXPERTS);
|
||||
|
||||
apply_bias<scalar_t, param_t, NUM_EXPERTS>(scores2, scores, bias);
|
||||
|
||||
for (int64_t g = 0; g < num_groups; ++g) {
|
||||
// find the max
|
||||
float gmax = at::vec::reduce_all<float>(
|
||||
[](Vec& x, Vec& y) { return at::vec::maximum(x, y); },
|
||||
scores2 + g * num_experts_per_group,
|
||||
num_experts_per_group);
|
||||
|
||||
// find position of first max,
|
||||
// note that we may have multiple max values.
|
||||
int first_max_idx = -1;
|
||||
for (int64_t e = 0; e < num_experts_per_group; ++e) {
|
||||
if (scores2[g * num_experts_per_group + e] == gmax) {
|
||||
first_max_idx = g * num_experts_per_group + e;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// find the 2nd max
|
||||
scores2[first_max_idx] = -std::numeric_limits<float>::infinity();
|
||||
float gmax2 = at::vec::reduce_all<float>(
|
||||
[](Vec& x, Vec& y) { return at::vec::maximum(x, y); },
|
||||
scores2 + g * num_experts_per_group,
|
||||
num_experts_per_group);
|
||||
// restore scores for choice
|
||||
scores2[first_max_idx] = gmax;
|
||||
|
||||
queue[g] = {gmax + gmax2, g};
|
||||
}
|
||||
|
||||
// find group topk
|
||||
std::partial_sort(
|
||||
queue.begin(), queue.begin() + topk_group, queue.end(), [](const elem_t& x, const elem_t& y) -> bool {
|
||||
return x.first > y.first;
|
||||
});
|
||||
|
||||
for (int64_t g = 0; g < topk_group; ++g) {
|
||||
int32_t group_idx = queue[g].second;
|
||||
for (int64_t e = 0; e < num_experts_per_group; ++e) {
|
||||
int32_t expert_idx = group_idx * num_experts_per_group + e;
|
||||
queue2[g * num_experts_per_group + e] = {scores2[expert_idx], expert_idx};
|
||||
}
|
||||
}
|
||||
|
||||
// find global topk
|
||||
std::partial_sort(
|
||||
queue2.begin(), queue2.begin() + TOPK, queue2.end(), [](const elem_t& x, const elem_t& y) -> bool {
|
||||
return x.first > y.first;
|
||||
});
|
||||
|
||||
for (int j = 0; j < TOPK; ++j) {
|
||||
int32_t index = queue2[j].second;
|
||||
topk_ids[i * TOPK + j] = index;
|
||||
topk_weights[i * TOPK + j] = scores[index];
|
||||
}
|
||||
|
||||
#if defined(CPU_CAPABILITY_AVX512)
|
||||
if (renormalize) {
|
||||
__mmask16 mask = (1ULL << TOPK) - 1;
|
||||
__m512 x = _mm512_maskz_loadu_ps(mask, topk_weights + i * TOPK);
|
||||
float sum = _mm512_reduce_add_ps(x);
|
||||
__m512 vscale = _mm512_set1_ps(1.f / sum);
|
||||
__m512 y = _mm512_mul_ps(x, vscale);
|
||||
_mm512_mask_storeu_ps(topk_weights + i * TOPK, mask, y);
|
||||
}
|
||||
#else
|
||||
if (renormalize) {
|
||||
float sum = 0.f;
|
||||
for (int64_t j = 0; j < TOPK; ++j) {
|
||||
sum += topk_weights[i * TOPK + j];
|
||||
}
|
||||
float scale = 1.f / sum;
|
||||
for (int64_t j = 0; j < TOPK; ++j) {
|
||||
topk_weights[i * TOPK + j] *= scale;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#define LAUNCH_GROUPED_TOPK_KERNEL(NE) \
|
||||
grouped_topk_kernel_impl<scalar_t, NE>( \
|
||||
topk_weights.data_ptr<float>(), \
|
||||
topk_ids.data_ptr<int32_t>(), \
|
||||
gating_output.data_ptr<scalar_t>(), \
|
||||
num_tokens, \
|
||||
topk, \
|
||||
num_expert_group, \
|
||||
topk_group, \
|
||||
renormalize);
|
||||
|
||||
#define LAUNCH_TOPK_SIGMOID_KERNEL(NE) \
|
||||
topk_sigmoid_kernel_impl<scalar_t, NE>( \
|
||||
topk_weights.data_ptr<float>(), \
|
||||
topk_ids.data_ptr<int32_t>(), \
|
||||
gating_output.data_ptr<scalar_t>(), \
|
||||
num_tokens, \
|
||||
topk, \
|
||||
renormalize);
|
||||
|
||||
#define LAUNCH_TOPK_SOFTMAX_KERNEL(NE) \
|
||||
topk_softmax_kernel_impl<scalar_t, NE>( \
|
||||
topk_weights.data_ptr<float>(), \
|
||||
topk_ids.data_ptr<int32_t>(), \
|
||||
gating_output.data_ptr<scalar_t>(), \
|
||||
num_tokens, \
|
||||
topk, \
|
||||
renormalize);
|
||||
|
||||
#define LAUNCH_BIASED_GROUPED_TOPK_KERNEL(NE, NTOPK) \
|
||||
biased_grouped_topk_kernel_impl<scalar_t, param_t, NE, NTOPK>( \
|
||||
topk_weights.data_ptr<float>(), \
|
||||
topk_ids.data_ptr<int32_t>(), \
|
||||
gating_output.data_ptr<scalar_t>(), \
|
||||
correction_bias.data_ptr<param_t>(), \
|
||||
num_tokens, \
|
||||
num_expert_group, \
|
||||
topk_group, \
|
||||
renormalize);
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
std::tuple<at::Tensor, at::Tensor>
|
||||
topk_sigmoid_cpu(at::Tensor& hidden_states, at::Tensor& gating_output, int64_t topk, bool renormalize) {
|
||||
RECORD_FUNCTION("sgl-kernel::topk_sigmoid_cpu", std::vector<c10::IValue>({hidden_states, gating_output}));
|
||||
CHECK_INPUT(gating_output);
|
||||
|
||||
const auto st = hidden_states.scalar_type();
|
||||
CHECK_EQ(gating_output.scalar_type(), st);
|
||||
|
||||
int64_t num_tokens = hidden_states.size(0);
|
||||
int64_t num_experts = gating_output.size(1);
|
||||
TORCH_CHECK(gating_output.size(0) == num_tokens, "Number of tokens mismatch");
|
||||
TORCH_CHECK(topk == 1, "topk_sigmoid only supports topk=1 case");
|
||||
at::Tensor topk_weights = at::empty({num_tokens, topk}, hidden_states.options().dtype(at::kFloat));
|
||||
at::Tensor topk_ids = at::empty({num_tokens, topk}, hidden_states.options().dtype(at::kInt));
|
||||
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(st, "topk_sigmoid_kernel", [&] {
|
||||
switch (num_experts) {
|
||||
case 1:
|
||||
LAUNCH_TOPK_SIGMOID_KERNEL(1);
|
||||
break;
|
||||
case 2:
|
||||
LAUNCH_TOPK_SIGMOID_KERNEL(2);
|
||||
break;
|
||||
case 4:
|
||||
LAUNCH_TOPK_SIGMOID_KERNEL(4);
|
||||
break;
|
||||
case 8:
|
||||
LAUNCH_TOPK_SIGMOID_KERNEL(8);
|
||||
break;
|
||||
case 16:
|
||||
LAUNCH_TOPK_SIGMOID_KERNEL(16);
|
||||
break;
|
||||
case 32:
|
||||
LAUNCH_TOPK_SIGMOID_KERNEL(32);
|
||||
break;
|
||||
case 64:
|
||||
LAUNCH_TOPK_SIGMOID_KERNEL(64);
|
||||
break;
|
||||
case 128:
|
||||
LAUNCH_TOPK_SIGMOID_KERNEL(128);
|
||||
break;
|
||||
case 160:
|
||||
LAUNCH_TOPK_SIGMOID_KERNEL(160);
|
||||
break;
|
||||
case 256:
|
||||
LAUNCH_TOPK_SIGMOID_KERNEL(256);
|
||||
break;
|
||||
default:
|
||||
TORCH_CHECK(false, "Unexpected num_experts: ", num_experts);
|
||||
}
|
||||
});
|
||||
return std::make_tuple(topk_weights, topk_ids);
|
||||
}
|
||||
|
||||
std::tuple<at::Tensor, at::Tensor>
|
||||
topk_softmax_cpu(at::Tensor& hidden_states, at::Tensor& gating_output, int64_t topk, bool renormalize) {
|
||||
RECORD_FUNCTION("sgl-kernel::topk_softmax_cpu", std::vector<c10::IValue>({hidden_states, gating_output}));
|
||||
CHECK_INPUT(gating_output);
|
||||
|
||||
const auto st = hidden_states.scalar_type();
|
||||
CHECK_EQ(gating_output.scalar_type(), st);
|
||||
|
||||
int64_t num_tokens = hidden_states.size(0);
|
||||
int64_t num_experts = gating_output.size(1);
|
||||
TORCH_CHECK(gating_output.size(0) == num_tokens, "Number of tokens mismatch");
|
||||
|
||||
at::Tensor topk_weights = at::empty({num_tokens, topk}, hidden_states.options().dtype(at::kFloat));
|
||||
at::Tensor topk_ids = at::empty({num_tokens, topk}, hidden_states.options().dtype(at::kInt));
|
||||
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(st, "topk_softmax_cpu", [&] {
|
||||
switch (num_experts) {
|
||||
case 1:
|
||||
LAUNCH_TOPK_SOFTMAX_KERNEL(1);
|
||||
break;
|
||||
case 2:
|
||||
LAUNCH_TOPK_SOFTMAX_KERNEL(2);
|
||||
break;
|
||||
case 4:
|
||||
LAUNCH_TOPK_SOFTMAX_KERNEL(4);
|
||||
break;
|
||||
case 8:
|
||||
LAUNCH_TOPK_SOFTMAX_KERNEL(8);
|
||||
break;
|
||||
case 16:
|
||||
LAUNCH_TOPK_SOFTMAX_KERNEL(16);
|
||||
break;
|
||||
case 32:
|
||||
LAUNCH_TOPK_SOFTMAX_KERNEL(32);
|
||||
break;
|
||||
case 64:
|
||||
LAUNCH_TOPK_SOFTMAX_KERNEL(64);
|
||||
break;
|
||||
case 128:
|
||||
LAUNCH_TOPK_SOFTMAX_KERNEL(128);
|
||||
break;
|
||||
case 160:
|
||||
LAUNCH_TOPK_SOFTMAX_KERNEL(160);
|
||||
break;
|
||||
case 256:
|
||||
LAUNCH_TOPK_SOFTMAX_KERNEL(256);
|
||||
break;
|
||||
case 384:
|
||||
LAUNCH_TOPK_SOFTMAX_KERNEL(384);
|
||||
break;
|
||||
case 512:
|
||||
LAUNCH_TOPK_SOFTMAX_KERNEL(512);
|
||||
break;
|
||||
default:
|
||||
TORCH_CHECK(false, "Unexpected num_experts: ", num_experts);
|
||||
}
|
||||
});
|
||||
return std::make_tuple(topk_weights, topk_ids);
|
||||
}
|
||||
|
||||
// grouped topk for DeepSeek V2
|
||||
std::tuple<at::Tensor, at::Tensor> grouped_topk_cpu(
|
||||
at::Tensor& hidden_states,
|
||||
at::Tensor& gating_output,
|
||||
int64_t topk,
|
||||
bool renormalize,
|
||||
int64_t num_expert_group,
|
||||
int64_t topk_group,
|
||||
int64_t num_fused_shared_experts,
|
||||
std::optional<double> routed_scaling_factor,
|
||||
std::optional<at::Tensor> num_token_non_padded) {
|
||||
// TODO: Will support num_fused_shared_experts, routed_scaling_factor and num_token_non_padded.
|
||||
// For now, we just check them as default value.
|
||||
TORCH_CHECK(
|
||||
num_fused_shared_experts == 0,
|
||||
"num_fused_shared_experts must be 0 default value, got: ",
|
||||
num_fused_shared_experts);
|
||||
TORCH_CHECK(
|
||||
!routed_scaling_factor.has_value() || routed_scaling_factor.value() == 1.0f,
|
||||
"routed_scaling_factor must be None or 1.0f default value, got: ",
|
||||
routed_scaling_factor.value());
|
||||
TORCH_CHECK(
|
||||
!num_token_non_padded.has_value(),
|
||||
"num_token_non_padded must be None default value, got: ",
|
||||
num_token_non_padded.value());
|
||||
|
||||
RECORD_FUNCTION("sgl-kernel::grouped_topk_cpu", std::vector<c10::IValue>({hidden_states, gating_output}));
|
||||
CHECK_INPUT(gating_output);
|
||||
|
||||
const auto st = hidden_states.scalar_type();
|
||||
CHECK_EQ(gating_output.scalar_type(), st);
|
||||
|
||||
int64_t num_tokens = hidden_states.size(0);
|
||||
int64_t num_experts = gating_output.size(1);
|
||||
TORCH_CHECK(gating_output.size(0) == num_tokens, "Number of tokens mismatch");
|
||||
at::Tensor topk_weights = at::empty({num_tokens, topk}, hidden_states.options().dtype(at::kFloat));
|
||||
at::Tensor topk_ids = at::empty({num_tokens, topk}, hidden_states.options().dtype(at::kInt));
|
||||
|
||||
AT_DISPATCH_REDUCED_FLOATING_TYPES(st, "grouped_topk_kernel", [&] {
|
||||
switch (num_experts) {
|
||||
case 1:
|
||||
LAUNCH_GROUPED_TOPK_KERNEL(1);
|
||||
break;
|
||||
case 2:
|
||||
LAUNCH_GROUPED_TOPK_KERNEL(2);
|
||||
break;
|
||||
case 4:
|
||||
LAUNCH_GROUPED_TOPK_KERNEL(4);
|
||||
break;
|
||||
case 8:
|
||||
LAUNCH_GROUPED_TOPK_KERNEL(8);
|
||||
break;
|
||||
case 16:
|
||||
LAUNCH_GROUPED_TOPK_KERNEL(16);
|
||||
break;
|
||||
case 32:
|
||||
LAUNCH_GROUPED_TOPK_KERNEL(32);
|
||||
break;
|
||||
case 64:
|
||||
LAUNCH_GROUPED_TOPK_KERNEL(64);
|
||||
break;
|
||||
case 128:
|
||||
LAUNCH_GROUPED_TOPK_KERNEL(128);
|
||||
break;
|
||||
case 160:
|
||||
LAUNCH_GROUPED_TOPK_KERNEL(160);
|
||||
break;
|
||||
case 256:
|
||||
LAUNCH_GROUPED_TOPK_KERNEL(256);
|
||||
break;
|
||||
default:
|
||||
TORCH_CHECK(false, "Unexpected num_experts: ", num_experts);
|
||||
}
|
||||
});
|
||||
return std::make_tuple(topk_weights, topk_ids);
|
||||
}
|
||||
|
||||
// biased grouped topk DeepSeek V3/R1
|
||||
std::tuple<at::Tensor, at::Tensor> biased_grouped_topk_cpu(
|
||||
at::Tensor& hidden_states,
|
||||
at::Tensor& gating_output,
|
||||
at::Tensor& correction_bias,
|
||||
int64_t topk,
|
||||
bool renormalize,
|
||||
int64_t num_expert_group,
|
||||
int64_t topk_group,
|
||||
int64_t num_fused_shared_experts,
|
||||
std::optional<double> routed_scaling_factor,
|
||||
std::optional<at::Tensor> num_token_non_padded) {
|
||||
// TODO: Will support num_fused_shared_experts, routed_scaling_factor and num_token_non_padded.
|
||||
// For now, we just check them as default value.
|
||||
TORCH_CHECK(
|
||||
num_fused_shared_experts == 0,
|
||||
"num_fused_shared_experts must be 0 default value, got: ",
|
||||
num_fused_shared_experts);
|
||||
TORCH_CHECK(
|
||||
!num_token_non_padded.has_value(),
|
||||
"num_token_non_padded must be None default value, got: ",
|
||||
num_token_non_padded.value());
|
||||
|
||||
RECORD_FUNCTION(
|
||||
"sgl-kernel::biased_grouped_topk_cpu", std::vector<c10::IValue>({hidden_states, gating_output, correction_bias}));
|
||||
|
||||
CHECK_INPUT(gating_output);
|
||||
CHECK_INPUT(correction_bias);
|
||||
|
||||
const auto st = hidden_states.scalar_type();
|
||||
CHECK_EQ(gating_output.scalar_type(), st);
|
||||
|
||||
int64_t num_tokens = hidden_states.size(0);
|
||||
int64_t num_experts = gating_output.size(1);
|
||||
TORCH_CHECK(gating_output.size(0) == num_tokens, "Number of tokens mismatch");
|
||||
TORCH_CHECK(correction_bias.numel() == num_experts, "Bias shape mismatch");
|
||||
at::Tensor topk_weights = at::empty({num_tokens, topk}, hidden_states.options().dtype(at::kFloat));
|
||||
at::Tensor topk_ids = at::empty({num_tokens, topk}, hidden_states.options().dtype(at::kInt));
|
||||
|
||||
CPU_DISPATCH_REDUCED_FLOATING_TYPES_EXT(st, correction_bias.scalar_type(), "biased_grouped_topk_kernel", [&] {
|
||||
TORCH_CHECK(topk == 8, "Unexpected topk: ", topk);
|
||||
switch (num_experts) {
|
||||
case 256:
|
||||
LAUNCH_BIASED_GROUPED_TOPK_KERNEL(256, 8);
|
||||
break;
|
||||
case 384:
|
||||
LAUNCH_BIASED_GROUPED_TOPK_KERNEL(384, 8);
|
||||
break;
|
||||
default:
|
||||
TORCH_CHECK(false, "Unexpected num_experts: ", num_experts);
|
||||
}
|
||||
});
|
||||
return std::make_tuple(topk_weights, topk_ids);
|
||||
}
|
||||
633
third_party/sglang/sgl-kernel/csrc/cpu/torch_extension_cpu.cpp
vendored
Normal file
633
third_party/sglang/sgl-kernel/csrc/cpu/torch_extension_cpu.cpp
vendored
Normal file
@@ -0,0 +1,633 @@
|
||||
/* 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/ATen.h>
|
||||
#include <torch/all.h>
|
||||
#include <torch/library.h>
|
||||
|
||||
#include "sgl_kernel_ops.h"
|
||||
#include "shm.h"
|
||||
|
||||
// silu_and_mul
|
||||
at::Tensor silu_and_mul_cpu(at::Tensor& input);
|
||||
|
||||
// gelu_and_mul
|
||||
at::Tensor gelu_tanh_and_mul_cpu(const at::Tensor& input);
|
||||
at::Tensor gelu_and_mul_cpu(const at::Tensor& input);
|
||||
|
||||
// l2norm
|
||||
at::Tensor l2norm_cpu(at::Tensor& input, double eps);
|
||||
|
||||
// rmsnorm
|
||||
at::Tensor rmsnorm_cpu(at::Tensor& input, at::Tensor& weight, double eps);
|
||||
at::Tensor gemma_rmsnorm_cpu(at::Tensor& input, at::Tensor& weight, double eps);
|
||||
at::Tensor gemma3_rmsnorm_cpu(at::Tensor& input, at::Tensor& weight, double eps);
|
||||
|
||||
// layernorm
|
||||
at::Tensor
|
||||
layernorm_cpu(const at::Tensor& input, const at::Tensor& weight, const std::optional<at::Tensor>& bias, double eps);
|
||||
|
||||
// qwen3_next_rmsnorm_gated
|
||||
at::Tensor fused_rmsnorm_gated_cpu(at::Tensor& input, at::Tensor& weight, at::Tensor& gate, double eps);
|
||||
|
||||
// fused_add_rmsnorm
|
||||
void fused_add_rmsnorm_cpu(at::Tensor& input, at::Tensor& residual, at::Tensor& weight, double eps);
|
||||
void gemma_fused_add_rmsnorm_cpu(at::Tensor& input, at::Tensor& residual, at::Tensor& weight, double eps);
|
||||
|
||||
// fused_add_layernorm
|
||||
at::Tensor fused_add_layernorm_cpu(
|
||||
const at::Tensor& input,
|
||||
at::Tensor& residual,
|
||||
const at::Tensor& weight,
|
||||
const std::optional<at::Tensor>& bias,
|
||||
double eps);
|
||||
|
||||
// topk
|
||||
std::tuple<at::Tensor, at::Tensor>
|
||||
topk_sigmoid_cpu(at::Tensor& hidden_states, at::Tensor& gating_output, int64_t topk, bool renormalize);
|
||||
std::tuple<at::Tensor, at::Tensor>
|
||||
topk_softmax_cpu(at::Tensor& hidden_states, at::Tensor& gating_output, int64_t topk, bool renormalize);
|
||||
|
||||
std::tuple<at::Tensor, at::Tensor> grouped_topk_cpu(
|
||||
at::Tensor& hidden_states,
|
||||
at::Tensor& gating_output,
|
||||
int64_t topk,
|
||||
bool renormalize,
|
||||
int64_t num_expert_group,
|
||||
int64_t topk_group,
|
||||
int64_t num_fused_shared_experts,
|
||||
std::optional<double> routed_scaling_factor,
|
||||
std::optional<at::Tensor> num_token_non_padded);
|
||||
|
||||
std::tuple<at::Tensor, at::Tensor> biased_grouped_topk_cpu(
|
||||
at::Tensor& hidden_states,
|
||||
at::Tensor& gating_output,
|
||||
at::Tensor& correction_bias,
|
||||
int64_t topk,
|
||||
bool renormalize,
|
||||
int64_t num_expert_group,
|
||||
int64_t topk_group,
|
||||
int64_t num_fused_shared_experts,
|
||||
std::optional<double> routed_scaling_factor,
|
||||
std::optional<at::Tensor> num_token_non_padded);
|
||||
|
||||
// attention
|
||||
void decode_attention_cpu(
|
||||
at::Tensor& query,
|
||||
at::Tensor& k_cache,
|
||||
at::Tensor& v_cache,
|
||||
at::Tensor& output,
|
||||
at::Tensor& key,
|
||||
at::Tensor& value,
|
||||
at::Tensor& loc,
|
||||
at::Tensor& attn_logits,
|
||||
at::Tensor& req_to_token,
|
||||
at::Tensor& req_pool_indices,
|
||||
at::Tensor& seq_lens,
|
||||
double sm_scale,
|
||||
double logit_cap);
|
||||
|
||||
void extend_attention_cpu(
|
||||
at::Tensor& q_extend,
|
||||
at::Tensor& k_extend,
|
||||
at::Tensor& v_extend,
|
||||
at::Tensor& o_extend,
|
||||
at::Tensor& k_buffer,
|
||||
at::Tensor& v_buffer,
|
||||
at::Tensor& req_to_token,
|
||||
at::Tensor& req_pool_indices,
|
||||
at::Tensor& seq_lens,
|
||||
at::Tensor& extend_seq_lens,
|
||||
at::Tensor& extend_start_loc,
|
||||
int64_t max_len_extend,
|
||||
double sm_scale,
|
||||
double logit_cap);
|
||||
|
||||
// flash attention
|
||||
at::Tensor flash_attn_varlen_func(
|
||||
const at::Tensor& q,
|
||||
const at::Tensor& k,
|
||||
const at::Tensor& v,
|
||||
const at::Tensor& cu_seqlens_q,
|
||||
const at::Tensor& cu_seqlens_k,
|
||||
int64_t max_seqlen_q,
|
||||
int64_t max_seqlen_k,
|
||||
bool causal);
|
||||
|
||||
// linear attention
|
||||
std::tuple<at::Tensor, at::Tensor> chunk_gated_delta_rule_cpu(
|
||||
const at::Tensor& query,
|
||||
const at::Tensor& key,
|
||||
const at::Tensor& value,
|
||||
const at::Tensor& g,
|
||||
const at::Tensor& beta,
|
||||
const at::Tensor& initial_state,
|
||||
bool output_final_state,
|
||||
const at::Tensor& cu_seqlens,
|
||||
bool head_first,
|
||||
bool use_qk_l2norm_in_kernel,
|
||||
double eps = 1e-5);
|
||||
|
||||
// weight prepack
|
||||
at::Tensor convert_weight_packed(at::Tensor& weight);
|
||||
|
||||
// scale prepack for mxfp4
|
||||
at::Tensor convert_scale_packed(at::Tensor& scale);
|
||||
|
||||
// quant
|
||||
std::tuple<at::Tensor, at::Tensor> per_token_quant_int8_cpu(at::Tensor& A);
|
||||
|
||||
// gemm
|
||||
at::Tensor
|
||||
weight_packed_linear(at::Tensor& mat1, at::Tensor& mat2, const std::optional<at::Tensor>& bias, bool is_vnni);
|
||||
|
||||
// gemm fusion
|
||||
at::Tensor fused_linear_sigmoid_mul(
|
||||
at::Tensor& mat1,
|
||||
at::Tensor& mat2,
|
||||
const std::optional<at::Tensor>& bias,
|
||||
bool is_vnni,
|
||||
const at::Tensor& post_mul_mat);
|
||||
|
||||
// igemm
|
||||
at::Tensor int8_scaled_mm_cpu(
|
||||
at::Tensor& mat1,
|
||||
at::Tensor& mat2,
|
||||
at::Tensor& scales1,
|
||||
at::Tensor& scales2,
|
||||
const std::optional<at::Tensor>& bias,
|
||||
at::ScalarType out_dtype,
|
||||
bool is_vnni);
|
||||
|
||||
// fp8 gemm
|
||||
at::Tensor fp8_scaled_mm_cpu(
|
||||
at::Tensor& mat1,
|
||||
at::Tensor& mat2,
|
||||
at::Tensor& scales2,
|
||||
std::vector<int64_t> block_size,
|
||||
const std::optional<at::Tensor>& bias,
|
||||
at::ScalarType out_dtype,
|
||||
bool is_vnni);
|
||||
|
||||
// mxfp4 gemm
|
||||
at::Tensor mxfp4_scaled_mm_cpu(
|
||||
at::Tensor& mat1, at::Tensor& mat2, at::Tensor& scales2, const std::optional<at::Tensor>& bias, bool is_vnni);
|
||||
|
||||
// quant + igemm
|
||||
at::Tensor int8_scaled_mm_with_quant(
|
||||
at::Tensor& mat1,
|
||||
at::Tensor& mat2,
|
||||
at::Tensor& scales2,
|
||||
const std::optional<at::Tensor>& bias,
|
||||
at::ScalarType out_dtype,
|
||||
bool is_vnni);
|
||||
|
||||
// int4 gemm
|
||||
at::Tensor int4_scaled_mm_cpu(
|
||||
at::Tensor& x, at::Tensor& w, at::Tensor& w_zeros, at::Tensor& w_scales, std::optional<at::Tensor> bias);
|
||||
|
||||
// weight prepack for int4 weights
|
||||
std::tuple<at::Tensor, at::Tensor, at::Tensor>
|
||||
convert_weight_packed_scale_zp(at::Tensor qweight, at::Tensor qzeros, at::Tensor scales);
|
||||
|
||||
// bmm
|
||||
void bmm_cpu(at::Tensor& out, at::Tensor& mat1, at::Tensor& mat2, bool is_vnni, const std::optional<at::Tensor>& scale);
|
||||
|
||||
// fused moe
|
||||
at::Tensor fused_experts_cpu(
|
||||
at::Tensor& hidden_states,
|
||||
at::Tensor& w1,
|
||||
at::Tensor& w2,
|
||||
at::Tensor& topk_weights,
|
||||
at::Tensor& topk_ids,
|
||||
bool inplace,
|
||||
int64_t moe_comp_method,
|
||||
const std::optional<at::Tensor>& w1_scale,
|
||||
const std::optional<at::Tensor>& w2_scale,
|
||||
const std::optional<at::Tensor>& w1_zero,
|
||||
const std::optional<at::Tensor>& w2_zero,
|
||||
const std::optional<std::vector<int64_t>> block_size,
|
||||
bool is_vnni);
|
||||
|
||||
at::Tensor shared_expert_cpu(
|
||||
at::Tensor& hidden_states,
|
||||
at::Tensor& w1,
|
||||
at::Tensor& w2,
|
||||
at::Tensor& fused_experts_out,
|
||||
double routed_scaling_factor,
|
||||
bool inplace,
|
||||
bool use_int8_w8a8,
|
||||
bool use_fp8_w8a16,
|
||||
const std::optional<at::Tensor>& w1_scale,
|
||||
const std::optional<at::Tensor>& w2_scale,
|
||||
const std::optional<std::vector<int64_t>> block_size,
|
||||
bool is_vnni);
|
||||
|
||||
// weight absorption
|
||||
std::tuple<at::Tensor, at::Tensor, at::Tensor> qkv_proj_with_rope(
|
||||
at::Tensor& hidden_states,
|
||||
at::Tensor& q_a_proj_weight,
|
||||
at::Tensor& q_b_proj_weight,
|
||||
at::Tensor& kv_a_proj_weight,
|
||||
at::Tensor& w_kc,
|
||||
at::Tensor& q_a_layernorm_weight,
|
||||
at::Tensor& kv_a_layernorm_weight,
|
||||
at::Tensor& positions,
|
||||
at::Tensor& cos_sin_cache,
|
||||
double eps,
|
||||
bool use_int8_w8a8,
|
||||
bool use_fp8_w8a16,
|
||||
std::optional<at::Tensor> q_a_proj_scale,
|
||||
std::optional<at::Tensor> q_b_proj_scale,
|
||||
std::optional<at::Tensor> kv_a_proj_scale,
|
||||
std::optional<at::Tensor> w_scale,
|
||||
bool is_vnni,
|
||||
std::optional<std::vector<int64_t>> block_size);
|
||||
|
||||
std::tuple<at::Tensor, at::Tensor, at::Tensor> qkv_proj_with_rope_fused_weight(
|
||||
at::Tensor& hidden_states,
|
||||
at::Tensor& qkv_a_proj_weight,
|
||||
at::Tensor& q_b_proj_weight,
|
||||
at::Tensor& w_kc,
|
||||
at::Tensor& q_a_layernorm_weight,
|
||||
at::Tensor& kv_a_layernorm_weight,
|
||||
at::Tensor& positions,
|
||||
at::Tensor& cos_sin_cache,
|
||||
double eps,
|
||||
bool use_int8_w8a8,
|
||||
bool use_fp8_w8a16,
|
||||
std::optional<at::Tensor> qkv_a_proj_scale,
|
||||
std::optional<at::Tensor> q_b_proj_scale,
|
||||
std::optional<at::Tensor> w_scale,
|
||||
bool is_vnni,
|
||||
std::optional<std::vector<int64_t>> block_size,
|
||||
int64_t q_lora_rank,
|
||||
int64_t kv_lora_rank,
|
||||
int64_t qk_rope_head_dim);
|
||||
|
||||
// mamba causal conv1d
|
||||
at::Tensor causal_conv1d_weight_pack(const at::Tensor& weight);
|
||||
|
||||
at::Tensor causal_conv1d_fwd_cpu(
|
||||
const at::Tensor& x,
|
||||
const at::Tensor& weight,
|
||||
const std::optional<at::Tensor>& bias,
|
||||
const std::optional<at::Tensor>& conv_states,
|
||||
const std::optional<at::Tensor>& query_start_loc,
|
||||
const std::optional<at::Tensor>& cache_indices,
|
||||
const std::optional<at::Tensor>& has_initial_state,
|
||||
bool silu_activation,
|
||||
int64_t pad_slot_id,
|
||||
bool is_vnni);
|
||||
|
||||
at::Tensor causal_conv1d_update_cpu(
|
||||
const at::Tensor& x,
|
||||
const at::Tensor& conv_states,
|
||||
const at::Tensor& weight,
|
||||
const std::optional<at::Tensor>& bias,
|
||||
bool silu_activation,
|
||||
const std::optional<at::Tensor>& cache_seqlens,
|
||||
const std::optional<at::Tensor>& conv_state_indices,
|
||||
int64_t pad_slot_id,
|
||||
bool is_vnni);
|
||||
|
||||
// conv3d fast path for patch embedding
|
||||
at::Tensor conv3d_embed_weight_pack(const at::Tensor& weight);
|
||||
|
||||
at::Tensor conv3d_embed_cpu(const at::Tensor& input, const at::Tensor& weight, const at::Tensor& bias, bool is_vnni);
|
||||
|
||||
// shared memory init
|
||||
void initialize(int64_t size, int64_t rank);
|
||||
|
||||
// shared mmeory all_reduce
|
||||
void shm_allreduce(at::Tensor& data, int64_t op);
|
||||
|
||||
// shared memory all_gather
|
||||
at::Tensor shm_allgather(at::Tensor& data, int64_t dim);
|
||||
|
||||
// rope
|
||||
std::tuple<at::Tensor, at::Tensor> rotary_embedding_cpu(
|
||||
at::Tensor& positions,
|
||||
at::Tensor& query,
|
||||
at::Tensor& key,
|
||||
int64_t head_size,
|
||||
at::Tensor& cos_sin_cache,
|
||||
bool is_neox);
|
||||
std::tuple<at::Tensor, at::Tensor>
|
||||
apply_rotary_pos_emb_cpu(at::Tensor& query, at::Tensor& key, at::Tensor& cos, at::Tensor& sin);
|
||||
|
||||
// mrope
|
||||
std::tuple<at::Tensor, at::Tensor> multimodal_rotary_embedding_cpu(
|
||||
at::Tensor& positions,
|
||||
at::Tensor& query,
|
||||
at::Tensor& key,
|
||||
int64_t head_size,
|
||||
at::Tensor& cos_sin_cache,
|
||||
const std::optional<std::vector<int64_t>>& mrope_section,
|
||||
bool mrope_interleaved,
|
||||
bool is_neox);
|
||||
|
||||
// CPU and memory binding
|
||||
std::string init_cpu_threads_env(const std::string& cpu_ids);
|
||||
|
||||
// fused_sigmoid_gating_delta_rule_update
|
||||
at::Tensor fused_sigmoid_gating_delta_rule_update_cpu(
|
||||
const at::Tensor& A_log,
|
||||
const at::Tensor& dt_bias,
|
||||
const at::Tensor& q,
|
||||
const at::Tensor& k,
|
||||
const at::Tensor& v,
|
||||
const at::Tensor& a,
|
||||
const at::Tensor& b,
|
||||
at::Tensor& initial_state_source,
|
||||
const at::Tensor& initial_state_indices,
|
||||
const at::Tensor& cu_seqlens,
|
||||
bool use_qk_l2norm_in_kernel,
|
||||
double softplus_beta = 1.0,
|
||||
double softplus_threshold = 20.0);
|
||||
// fused_gdn_gating
|
||||
std::tuple<at::Tensor, at::Tensor>
|
||||
fused_gdn_gating_cpu(const at::Tensor& A_log, const at::Tensor& a, const at::Tensor& b, const at::Tensor& dt_bias);
|
||||
|
||||
// fused_qkvzba_split_reshape_cat_cpu
|
||||
std::tuple<at::Tensor, at::Tensor, at::Tensor, at::Tensor> fused_qkvzba_split_reshape_cat_cpu(
|
||||
const at::Tensor& mixed_qkvz,
|
||||
const at::Tensor& mixed_ba,
|
||||
int64_t num_heads_qk,
|
||||
int64_t num_heads_v,
|
||||
int64_t head_qk,
|
||||
int64_t head_v);
|
||||
|
||||
// image preprocessor
|
||||
std::tuple<at::Tensor, at::Tensor> image_preprocess_cpu(
|
||||
at::TensorList images,
|
||||
bool do_convert_rgb,
|
||||
bool do_resize,
|
||||
int64_t shortest_edge,
|
||||
int64_t longest_edge,
|
||||
const std::string& interpolation,
|
||||
bool do_rescale,
|
||||
double rescale_factor,
|
||||
bool do_normalize,
|
||||
c10::ArrayRef<double> image_mean,
|
||||
c10::ArrayRef<double> image_std,
|
||||
int64_t patch_size,
|
||||
int64_t temporal_patch_size,
|
||||
int64_t merge_size,
|
||||
bool disable_grouping,
|
||||
at::ScalarType out_dtype);
|
||||
|
||||
// [NOTE] When registering kernels, we should accurately describe the in-place information.
|
||||
// Taking fused_add_rmsnorm_cpu as an example, add `Tensor(a!)` modifier to all tensors that
|
||||
// will be modified in-place to avoid incorrect fusing and execution order on graph mode.
|
||||
TORCH_LIBRARY_FRAGMENT(sgl_kernel, m) {
|
||||
// activation
|
||||
m.def("silu_and_mul_cpu(Tensor input) -> Tensor");
|
||||
m.impl("silu_and_mul_cpu", torch::kCPU, &silu_and_mul_cpu);
|
||||
m.def("gelu_tanh_and_mul_cpu(Tensor input) -> Tensor");
|
||||
m.impl("gelu_tanh_and_mul_cpu", torch::kCPU, &gelu_tanh_and_mul_cpu);
|
||||
m.def("gelu_and_mul_cpu(Tensor input) -> Tensor");
|
||||
m.impl("gelu_and_mul_cpu", torch::kCPU, &gelu_and_mul_cpu);
|
||||
|
||||
// norm
|
||||
m.def("rmsnorm_cpu(Tensor input, Tensor weight, float eps) -> Tensor");
|
||||
m.impl("rmsnorm_cpu", torch::kCPU, &rmsnorm_cpu);
|
||||
m.def("gemma_rmsnorm_cpu(Tensor input, Tensor weight, float eps) -> Tensor");
|
||||
m.impl("gemma_rmsnorm_cpu", torch::kCPU, &gemma_rmsnorm_cpu);
|
||||
m.def("gemma3_rmsnorm_cpu(Tensor input, Tensor weight, float eps) -> Tensor");
|
||||
m.impl("gemma3_rmsnorm_cpu", torch::kCPU, &gemma3_rmsnorm_cpu);
|
||||
m.def("layernorm_cpu(Tensor input, Tensor weight, Tensor? bias, float eps) -> Tensor");
|
||||
m.impl("layernorm_cpu", torch::kCPU, &layernorm_cpu);
|
||||
m.def("l2norm_cpu(Tensor input, float eps) -> Tensor");
|
||||
m.impl("l2norm_cpu", torch::kCPU, &l2norm_cpu);
|
||||
m.def("fused_rmsnorm_gated_cpu(Tensor input, Tensor weight, Tensor gate, float eps) -> Tensor");
|
||||
m.impl("fused_rmsnorm_gated_cpu", torch::kCPU, &fused_rmsnorm_gated_cpu);
|
||||
m.def("fused_add_rmsnorm_cpu(Tensor(a!) input, Tensor(a!) residual, Tensor weight, float eps) -> ()");
|
||||
m.impl("fused_add_rmsnorm_cpu", torch::kCPU, &fused_add_rmsnorm_cpu);
|
||||
m.def("gemma_fused_add_rmsnorm_cpu(Tensor(a!) input, Tensor(a!) residual, Tensor weight, float eps) -> ()");
|
||||
m.impl("gemma_fused_add_rmsnorm_cpu", torch::kCPU, &gemma_fused_add_rmsnorm_cpu);
|
||||
m.def(
|
||||
"fused_add_layernorm_cpu(Tensor input, Tensor residual, Tensor weight, Tensor? bias, float eps) -> "
|
||||
"Tensor");
|
||||
m.impl("fused_add_layernorm_cpu", torch::kCPU, &fused_add_layernorm_cpu);
|
||||
|
||||
// topk
|
||||
m.def("topk_sigmoid_cpu(Tensor hidden_states, Tensor gating_output, int topk, bool renormalize) -> (Tensor, Tensor)");
|
||||
m.impl("topk_sigmoid_cpu", torch::kCPU, &topk_sigmoid_cpu);
|
||||
m.def("topk_softmax_cpu(Tensor hidden_states, Tensor gating_output, int topk, bool renormalize) -> (Tensor, Tensor)");
|
||||
m.impl("topk_softmax_cpu", torch::kCPU, &topk_softmax_cpu);
|
||||
m.def(
|
||||
"grouped_topk_cpu(Tensor hidden_states, Tensor gating_output, int topk, bool renormalize, int num_expert_group, "
|
||||
"int topk_group, int num_fused_shared_experts, float? routed_scaling_factor, Tensor? num_token_non_padded) -> "
|
||||
"(Tensor, Tensor)");
|
||||
m.impl("grouped_topk_cpu", torch::kCPU, &grouped_topk_cpu);
|
||||
|
||||
// biased group topk
|
||||
m.def(
|
||||
"biased_grouped_topk_cpu(Tensor hidden_states, Tensor gating_output, Tensor correction_bias, int topk, bool "
|
||||
"renormalize, int num_expert_group, int topk_group, int num_fused_shared_experts, float? routed_scaling_factor, "
|
||||
"Tensor? num_token_non_padded) -> (Tensor, Tensor)");
|
||||
m.impl("biased_grouped_topk_cpu", torch::kCPU, &biased_grouped_topk_cpu);
|
||||
|
||||
// decode
|
||||
m.def(
|
||||
"decode_attention_cpu(Tensor query, Tensor k_cache, Tensor v_cahce, Tensor(a!) output, Tensor key, Tensor value, "
|
||||
"Tensor loc, Tensor attn_logits, Tensor req_to_token, Tensor req_pool_indices, Tensor seq_lens, float sm_scale, "
|
||||
"float logit_cap) -> ()");
|
||||
m.impl("decode_attention_cpu", torch::kCPU, &decode_attention_cpu);
|
||||
|
||||
// extend
|
||||
m.def(
|
||||
"extend_attention_cpu(Tensor q_extend, Tensor k_extend, Tensor v_extend, Tensor(a!) o_extend, Tensor k_buffer, "
|
||||
"Tensor v_buffer, Tensor req_to_token, Tensor req_pool_indices, Tensor seq_lens, Tensor extend_seq_lens, Tensor "
|
||||
"extend_start_loc, int max_len_extend, float sm_scale, float logit_cap) -> ()");
|
||||
m.impl("extend_attention_cpu", torch::kCPU, &extend_attention_cpu);
|
||||
|
||||
// flash attn
|
||||
m.def(
|
||||
"flash_attn_varlen_func(Tensor q, Tensor k, Tensor v, Tensor cu_seqlens_q, Tensor cu_seqlens_k, "
|
||||
"int max_seqlen_q, int max_seqlen_k, bool causal) -> Tensor");
|
||||
m.impl("flash_attn_varlen_func", torch::kCPU, &flash_attn_varlen_func);
|
||||
|
||||
// linear attn
|
||||
m.def(
|
||||
"chunk_gated_delta_rule_cpu(Tensor query, Tensor key, Tensor value, Tensor g, Tensor beta, "
|
||||
"Tensor initial_state, bool output_final_state, Tensor cu_seqlens, bool head_first, "
|
||||
"bool use_qk_l2norm_in_kernel, float eps=1e-5) -> (Tensor, Tensor)");
|
||||
m.impl("chunk_gated_delta_rule_cpu", torch::kCPU, &chunk_gated_delta_rule_cpu);
|
||||
|
||||
// weight prepack
|
||||
m.def("convert_weight_packed(Tensor weight) -> Tensor");
|
||||
m.impl("convert_weight_packed", torch::kCPU, &convert_weight_packed);
|
||||
|
||||
// scale prepack for mxfp4
|
||||
m.def("convert_scale_packed(Tensor scale) -> Tensor");
|
||||
m.impl("convert_scale_packed", torch::kCPU, &convert_scale_packed);
|
||||
|
||||
// quant
|
||||
m.def("per_token_quant_int8_cpu(Tensor A) -> (Tensor, Tensor)");
|
||||
m.impl("per_token_quant_int8_cpu", torch::kCPU, &per_token_quant_int8_cpu);
|
||||
|
||||
// gemm
|
||||
m.def("weight_packed_linear(Tensor mat1, Tensor mat2, Tensor? bias, bool is_vnni) -> Tensor");
|
||||
m.impl("weight_packed_linear", torch::kCPU, &weight_packed_linear);
|
||||
|
||||
// gemm fusion
|
||||
m.def(
|
||||
"fused_linear_sigmoid_mul(Tensor mat1, Tensor mat2, Tensor? bias, bool is_vnni, Tensor post_mul_mat) -> Tensor");
|
||||
m.impl("fused_linear_sigmoid_mul", torch::kCPU, &fused_linear_sigmoid_mul);
|
||||
|
||||
// igemm
|
||||
m.def(
|
||||
"int8_scaled_mm_cpu(Tensor mat1, Tensor mat2, Tensor scales1, Tensor scales2, Tensor? bias, ScalarType "
|
||||
"out_dtype, bool is_vnni) -> Tensor");
|
||||
m.impl("int8_scaled_mm_cpu", torch::kCPU, &int8_scaled_mm_cpu);
|
||||
|
||||
// fp8 gemm
|
||||
m.def(
|
||||
"fp8_scaled_mm_cpu(Tensor mat1, Tensor mat2, Tensor scales2, int[] block_size, Tensor? bias, ScalarType "
|
||||
"out_dtype, bool is_vnni) -> Tensor");
|
||||
m.impl("fp8_scaled_mm_cpu", torch::kCPU, &fp8_scaled_mm_cpu);
|
||||
|
||||
// mxfp4 gemm
|
||||
m.def("mxfp4_scaled_mm_cpu(Tensor mat1, Tensor mat2, Tensor scales2, Tensor? bias, bool is_vnni) -> Tensor");
|
||||
m.impl("mxfp4_scaled_mm_cpu", torch::kCPU, &mxfp4_scaled_mm_cpu);
|
||||
|
||||
// quant + igemm
|
||||
m.def(
|
||||
"int8_scaled_mm_with_quant(Tensor mat1, Tensor mat2, Tensor scales2, Tensor? bias, ScalarType out_dtype, bool "
|
||||
"is_vnni) -> Tensor");
|
||||
m.impl("int8_scaled_mm_with_quant", torch::kCPU, &int8_scaled_mm_with_quant);
|
||||
|
||||
// int4 gemm
|
||||
m.def("int4_scaled_mm_cpu(Tensor x, Tensor w, Tensor w_zeros, Tensor w_scales, Tensor? bias) -> Tensor");
|
||||
m.impl("int4_scaled_mm_cpu", torch::kCPU, &int4_scaled_mm_cpu);
|
||||
|
||||
// weight prepack for int4 weights
|
||||
m.def(
|
||||
"convert_weight_packed_scale_zp(Tensor weight, Tensor qzeros, Tensor scales) -> (Tensor, Tensor, "
|
||||
"Tensor)");
|
||||
m.impl("convert_weight_packed_scale_zp", torch::kCPU, &convert_weight_packed_scale_zp);
|
||||
|
||||
// bmm
|
||||
m.def("bmm_cpu(Tensor(a!) out, Tensor mat1, Tensor mat2, bool is_vnni, Tensor? scale) -> ()");
|
||||
m.impl("bmm_cpu", torch::kCPU, &bmm_cpu);
|
||||
|
||||
// moe
|
||||
m.def(
|
||||
"fused_experts_cpu(Tensor hidden_states, Tensor w1, Tensor w2, Tensor topk_weights, Tensor topk_ids, bool "
|
||||
"inplace, int moe_comp_method, Tensor? w1_scale, Tensor? w2_scale, "
|
||||
"Tensor? w1_zero, Tensor? w2_zero, int[]? block_size, bool is_vnni) -> Tensor");
|
||||
m.impl("fused_experts_cpu", torch::kCPU, &fused_experts_cpu);
|
||||
|
||||
// weight absorption
|
||||
m.def(
|
||||
"qkv_proj_with_rope(Tensor hidden_states, Tensor q_a_proj_weight, Tensor q_b_proj_weight, Tensor "
|
||||
"kv_a_proj_weight, Tensor w_kc, Tensor q_a_layernorm_weight, Tensor kv_a_layernorm_weight, Tensor positions, "
|
||||
"Tensor cos_sin_cache, float eps, bool use_int8_w8a8, bool use_fp8_w8a16, Tensor? q_a_proj_scale, Tensor? "
|
||||
"q_b_proj_scale, Tensor? kv_a_proj_scale, Tensor? w_scale, "
|
||||
"bool is_vnni, int[]? block_size) -> (Tensor, Tensor, Tensor)");
|
||||
m.impl("qkv_proj_with_rope", torch::kCPU, &qkv_proj_with_rope);
|
||||
m.def(
|
||||
"qkv_proj_with_rope_fused_weight(Tensor hidden_states, Tensor qkv_a_proj_weight, Tensor q_b_proj_weight, "
|
||||
"Tensor w_kc, Tensor q_a_layernorm_weight, Tensor kv_a_layernorm_weight, Tensor positions, "
|
||||
"Tensor cos_sin_cache, float eps, bool use_int8_w8a8, bool use_fp8_w8a16, Tensor? qkv_a_proj_scale, Tensor? "
|
||||
"q_b_proj_scale, Tensor? w_scale,"
|
||||
"bool is_vnni, int[]? block_size, int q_lora_rank, int kv_lora_rank,"
|
||||
"int qk_rope_head_dim) -> (Tensor, Tensor, Tensor)");
|
||||
m.impl("qkv_proj_with_rope_fused_weight", torch::kCPU, &qkv_proj_with_rope_fused_weight);
|
||||
|
||||
// shared expert
|
||||
m.def(
|
||||
"shared_expert_cpu(Tensor hidden_states, Tensor w1, Tensor w2, Tensor fused_experts_out, float "
|
||||
"routed_scaling_factor, bool inplace, bool use_int8_w8a8, bool use_fp8_w8a16, Tensor? w1_scale, Tensor? "
|
||||
"w2_scale, int[]? block_size, bool is_vnni) -> Tensor");
|
||||
m.impl("shared_expert_cpu", torch::kCPU, &shared_expert_cpu);
|
||||
|
||||
// causal conv1d
|
||||
m.def("causal_conv1d_weight_pack(Tensor weight) -> Tensor");
|
||||
m.impl("causal_conv1d_weight_pack", torch::kCPU, &causal_conv1d_weight_pack);
|
||||
|
||||
m.def(
|
||||
"causal_conv1d_fwd_cpu(Tensor x, Tensor weight, Tensor? bias, Tensor? conv_states, Tensor? query_start_loc,"
|
||||
"Tensor? cache_indices, Tensor? has_initial_state, bool silu_activation, int pad_slot_id, bool is_vnni) -> "
|
||||
"Tensor");
|
||||
m.impl("causal_conv1d_fwd_cpu", torch::kCPU, &causal_conv1d_fwd_cpu);
|
||||
|
||||
m.def(
|
||||
"causal_conv1d_update_cpu(Tensor x, Tensor(a!) conv_states, Tensor weight, Tensor? bias, bool silu_activation,"
|
||||
"Tensor? cache_seqlens, Tensor? conv_state_indices, int pad_slot_id, bool is_vnni) -> Tensor");
|
||||
m.impl("causal_conv1d_update_cpu", torch::kCPU, &causal_conv1d_update_cpu);
|
||||
|
||||
// conv3d fast path for patch embedding
|
||||
m.def("conv3d_embed_weight_pack(Tensor weight) -> Tensor");
|
||||
m.impl("conv3d_embed_weight_pack", torch::kCPU, &conv3d_embed_weight_pack);
|
||||
m.def("conv3d_embed_cpu(Tensor input, Tensor weight, Tensor bias, bool is_vnni) -> Tensor");
|
||||
m.impl("conv3d_embed_cpu", torch::kCPU, &conv3d_embed_cpu);
|
||||
|
||||
// all reduce
|
||||
m.def("initialize(int size, int rank) -> ()");
|
||||
m.def("shm_allreduce(Tensor(a!) data, int reduce_op) -> ()");
|
||||
m.impl("shm_allreduce", torch::kCPU, &shm_allreduce);
|
||||
m.def("shm_allgather(Tensor data, int dim) -> Tensor");
|
||||
m.impl("shm_allgather", torch::kCPU, &shm_allgather);
|
||||
|
||||
// rope
|
||||
m.def(
|
||||
"rotary_embedding_cpu(Tensor positions, Tensor query, Tensor key, int head_size, Tensor cos_sin_cache, "
|
||||
"bool is_neox) -> (Tensor, Tensor)");
|
||||
m.impl("rotary_embedding_cpu", torch::kCPU, &rotary_embedding_cpu);
|
||||
m.def("apply_rotary_pos_emb_cpu(Tensor query, Tensor key, Tensor cos, Tensor sin) -> (Tensor, Tensor)");
|
||||
m.impl("apply_rotary_pos_emb_cpu", torch::kCPU, &apply_rotary_pos_emb_cpu);
|
||||
|
||||
// multimodal rope
|
||||
m.def(
|
||||
"multimodal_rotary_embedding_cpu(Tensor positions, Tensor query, Tensor key, int head_size, Tensor "
|
||||
"cos_sin_cache, int[]? mrope_section, bool mrope_interleaved, bool is_neox) -> (Tensor, Tensor)");
|
||||
m.impl("multimodal_rotary_embedding_cpu", torch::kCPU, &multimodal_rotary_embedding_cpu);
|
||||
|
||||
// CPU and memory binding
|
||||
m.def("init_cpu_threads_env(str cpu_ids) -> str");
|
||||
|
||||
// fused_sigmoid_gating_delta_rule_update
|
||||
m.def(
|
||||
"fused_sigmoid_gating_delta_rule_update_cpu(Tensor A_log, Tensor dt_bias, Tensor q, Tensor k, Tensor v, Tensor "
|
||||
"a, Tensor b, Tensor(a!) initial_state_source, Tensor initial_state_indices, Tensor cu_seqlens, bool "
|
||||
"use_qk_l2norm_in_kernel, float softplus_beta=1.0, float softplus_threshold=20.0) -> Tensor");
|
||||
m.impl("fused_sigmoid_gating_delta_rule_update_cpu", torch::kCPU, &fused_sigmoid_gating_delta_rule_update_cpu);
|
||||
// fused_gdn_gating
|
||||
m.def("fused_gdn_gating_cpu(Tensor A_log, Tensor a, Tensor b, Tensor dt_bias) -> (Tensor, Tensor)");
|
||||
m.impl("fused_gdn_gating_cpu", torch::kCPU, &fused_gdn_gating_cpu);
|
||||
// fused_qkvzba_split_reshape_cat_cpu
|
||||
m.def(
|
||||
"fused_qkvzba_split_reshape_cat_cpu(Tensor mixed_qkvz, Tensor mixed_ba, int num_heads_qk, int num_heads_v, int "
|
||||
"head_qk, int head_v) -> (Tensor, Tensor, Tensor, Tensor)");
|
||||
m.impl("fused_qkvzba_split_reshape_cat_cpu", torch::kCPU, &fused_qkvzba_split_reshape_cat_cpu);
|
||||
|
||||
// image preprocessor
|
||||
m.def(
|
||||
"image_preprocess_cpu(Tensor[] images, bool do_convert_rgb, bool do_resize, int shortest_edge, int longest_edge,"
|
||||
"str interpolation, bool do_rescale, float rescale_factor, bool do_normalize, float[] image_mean, float[] "
|
||||
"image_std, int patch_size, int temporal_patch_size, int merge_size, bool disable_grouping, ScalarType "
|
||||
"out_dtype) -> (Tensor, Tensor)");
|
||||
m.impl("image_preprocess_cpu", torch::kCPU, &image_preprocess_cpu);
|
||||
}
|
||||
|
||||
TORCH_LIBRARY_IMPL(sgl_kernel, CatchAll, m) {
|
||||
m.impl("init_cpu_threads_env", init_cpu_threads_env);
|
||||
m.impl("initialize", &initialize);
|
||||
}
|
||||
|
||||
REGISTER_EXTENSION(common_ops)
|
||||
421
third_party/sglang/sgl-kernel/csrc/cpu/vec.h
vendored
Normal file
421
third_party/sglang/sgl-kernel/csrc/cpu/vec.h
vendored
Normal file
@@ -0,0 +1,421 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(__AVX512F__) && defined(__AVX512BF16__) && defined(__AMX_BF16__)
|
||||
#define CPU_CAPABILITY_AVX512
|
||||
#endif
|
||||
|
||||
#include <ATen/cpu/vec/functional.h>
|
||||
#include <ATen/cpu/vec/vec.h>
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace at::vec;
|
||||
|
||||
template <typename scalar_t, typename std::enable_if_t<is_reduced_floating_point_v<scalar_t>, int> = 0>
|
||||
inline Vectorized<scalar_t> convert_from_float_ext(const Vectorized<float>& a, const Vectorized<float>& b) {
|
||||
return at::vec::convert_from_float<scalar_t>(a, b);
|
||||
}
|
||||
|
||||
// allow f16, bf16
|
||||
template <typename scalar_t, typename std::enable_if_t<is_reduced_floating_point_v<scalar_t>, int> = 1>
|
||||
inline std::tuple<Vectorized<float>, Vectorized<float>> load_float_vec2(const scalar_t* __restrict__ data) {
|
||||
using bVec = at::vec::Vectorized<scalar_t>;
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
bVec x_vec = bVec::loadu(data);
|
||||
fVec x0, x1;
|
||||
std::tie(x0, x1) = at::vec::convert_to_float(x_vec);
|
||||
return std::make_tuple(x0, x1);
|
||||
}
|
||||
|
||||
// allow f32
|
||||
inline std::tuple<Vectorized<float>, Vectorized<float>> load_float_vec2(const float* __restrict__ data) {
|
||||
using fVec = at::vec::Vectorized<float>;
|
||||
fVec x0 = fVec::loadu(data);
|
||||
fVec x1 = fVec::loadu(data + fVec::size());
|
||||
return std::make_tuple(x0, x1);
|
||||
}
|
||||
|
||||
#if defined(CPU_CAPABILITY_AVX512)
|
||||
|
||||
// `at::vec::convert_from_float<>` from PyTorch doesn't have avx512-bf16 intrinsics
|
||||
// use native instruction for bfloat16->float32 conversion
|
||||
template <>
|
||||
inline Vectorized<at::BFloat16>
|
||||
convert_from_float_ext<at::BFloat16>(const Vectorized<float>& a, const Vectorized<float>& b) {
|
||||
return (__m512i)(_mm512_cvtne2ps_pbh(__m512(b), __m512(a)));
|
||||
}
|
||||
|
||||
#define CVT_BF16_TO_FP32(a) _mm512_castsi512_ps(_mm512_slli_epi32(_mm512_cvtepu16_epi32(a), 16))
|
||||
|
||||
#define CVT_FP16_TO_FP32(a) _mm512_cvtph_ps(a)
|
||||
|
||||
// this doesn't handle NaN.
|
||||
inline __m512bh cvt_e4m3_bf16_intrinsic_no_nan(__m256i fp8_vec) {
|
||||
const __m512i x = _mm512_cvtepu8_epi16(fp8_vec);
|
||||
__m512i combined = _mm512_add_epi16(x, _mm512_set1_epi16(0x0780));
|
||||
combined = _mm512_slli_epi16(combined, 4);
|
||||
combined = _mm512_and_si512(combined, _mm512_set1_epi16(0x87f0));
|
||||
combined = _mm512_add_epi16(combined, _mm512_set1_epi16(0x3c00));
|
||||
|
||||
const __mmask32 is_nonzero = _mm512_cmpneq_epi16_mask(x, _mm512_setzero_si512());
|
||||
return (__m512bh)_mm512_maskz_mov_epi16(is_nonzero, combined);
|
||||
}
|
||||
|
||||
inline __m512bh cvt_e4m3_bf16_intrinsic_without_denorm(__m256i fp8_vec) {
|
||||
// The following conversion is without denorm behavior, that is to say,
|
||||
// Max subnorm : S.0000.111 = 0.875 ∗ 2**(−6)
|
||||
// Min subnorm : S.0000.001 = 2**(−9)
|
||||
// 0.0019 ~ 0.0137 cannot be converted correctly.
|
||||
__m512i x = _mm512_cvtepu8_epi16(fp8_vec);
|
||||
auto mask = _mm512_cmpneq_epi16_mask(
|
||||
_mm512_and_si512(x, _mm512_set1_epi16(127)),
|
||||
_mm512_setzero_si512()); // mask = x & 0x7f
|
||||
auto mask_nan = _mm512_cmpneq_epi16_mask(
|
||||
_mm512_and_si512(x, _mm512_set1_epi16(127)),
|
||||
_mm512_set1_epi16(127)); // mask_nan = x & 0x7f
|
||||
auto mantissa = _mm512_slli_epi16(_mm512_and_si512(x, _mm512_set1_epi16(7)), 4); // mantissa = (x & 7) << 4
|
||||
auto exponent = _mm512_add_epi16(
|
||||
_mm512_srli_epi16(_mm512_and_si512(x, _mm512_set1_epi16(120)), 3),
|
||||
_mm512_set1_epi16(120)); // exponent = (((x >> 3) & 15) + 120)
|
||||
auto nonsign = _mm512_maskz_mov_epi16(mask, _mm512_or_si512(mantissa, _mm512_slli_epi16(exponent, 7)));
|
||||
nonsign = _mm512_mask_mov_epi16(_mm512_set1_epi16(0x7fff), mask_nan, nonsign); // deal with Nan
|
||||
return (__m512bh)(_mm512_or_si512(
|
||||
nonsign,
|
||||
_mm512_slli_epi16(
|
||||
_mm512_and_si512(x, _mm512_set1_epi16(128)),
|
||||
8))); // add sign (x & 128) << 8
|
||||
}
|
||||
|
||||
inline __m512bh cvt_e4m3_bf16_intrinsic_with_denorm(__m256i fp8_vec) {
|
||||
__m512i x = _mm512_cvtepu8_epi16(fp8_vec);
|
||||
__m512i lg2mant = _mm512_mask_mov_epi16(
|
||||
_mm512_mask_mov_epi16(
|
||||
_mm512_setzero_si512(), _mm512_test_epi16_mask(x, _mm512_set1_epi16(2)), _mm512_set1_epi16(1)),
|
||||
_mm512_test_epi16_mask(x, _mm512_set1_epi16(4)),
|
||||
_mm512_set1_epi16(2));
|
||||
return (__m512bh)(_mm512_or_si512(
|
||||
_mm512_maskz_mov_epi16(
|
||||
_mm512_cmpneq_epi16_mask(_mm512_and_si512(x, _mm512_set1_epi16(127)), _mm512_setzero_si512()),
|
||||
_mm512_mask_blend_epi16(
|
||||
_mm512_test_epi16_mask(x, _mm512_set1_epi16(120)),
|
||||
_mm512_or_si512(
|
||||
_mm512_and_si512(
|
||||
_mm512_sllv_epi16(
|
||||
_mm512_and_si512(x, _mm512_set1_epi16(3)), _mm512_sub_epi16(_mm512_set1_epi16(7), lg2mant)),
|
||||
_mm512_set1_epi16(0x007f)),
|
||||
_mm512_slli_epi16(_mm512_add_epi16(lg2mant, _mm512_set1_epi16(118)), 7)),
|
||||
_mm512_or_si512(
|
||||
_mm512_slli_epi16(_mm512_and_si512(x, _mm512_set1_epi16(7)), 4),
|
||||
_mm512_slli_epi16(
|
||||
_mm512_add_epi16(
|
||||
_mm512_srli_epi16(_mm512_and_si512(x, _mm512_set1_epi16(120)), 3), _mm512_set1_epi16(120)),
|
||||
7)))),
|
||||
_mm512_slli_epi16(_mm512_and_si512(x, _mm512_set1_epi16(128)), 8)));
|
||||
}
|
||||
|
||||
inline __m512bh CVT_FP8_TO_BF16(__m256i a) {
|
||||
#ifdef SGLANG_CPU_FP8_CVT_FTZ
|
||||
return cvt_e4m3_bf16_intrinsic_no_nan(a);
|
||||
#else
|
||||
return cvt_e4m3_bf16_intrinsic_with_denorm(a);
|
||||
#endif
|
||||
}
|
||||
|
||||
// faster version of float8_e4m3fn conversion to bfloat16
|
||||
//
|
||||
// we mapped cuda implementation from below link and vectorized with avx512:
|
||||
// https://github.com/thu-pacman/chitu/blob/1ed2078ec26581ebdca05b7306d4385f86edaa7c/csrc/cuda/marlin/marlin_gemm/dequant.h#L387
|
||||
//
|
||||
inline __attribute__((always_inline)) __m512bh CVT_FP8_TO_BF16_EXT(__m256i a) {
|
||||
const __m512i mask0 = _mm512_set1_epi16(0x80); // sign bit
|
||||
const __m512i mask1 = _mm512_set1_epi16(0x7F); // exponent and mantissa
|
||||
const __m512i mask2 = _mm512_set1_epi16(0x4000);
|
||||
|
||||
__m512i x = _mm512_cvtepu8_epi16(a);
|
||||
__m512i vsign = _mm512_and_si512(x, mask0);
|
||||
vsign = _mm512_slli_epi16(vsign, 8);
|
||||
|
||||
__m512i vexp_and_mant = _mm512_and_si512(x, mask1);
|
||||
vexp_and_mant = _mm512_slli_epi16(vexp_and_mant, 4);
|
||||
|
||||
// _MM_TERNLOG_A | _MM_TERNLOG_B | _MM_TERNLOG_C: 0b11111110
|
||||
return (__m512bh)(_mm512_ternarylogic_epi32(vsign, mask2, vexp_and_mant, 0b11111110));
|
||||
}
|
||||
|
||||
// bias for conversion of fp8 to bf16 1/256 in float32
|
||||
#define kFP8_BIAS 0x3b800000
|
||||
|
||||
// remove warning: ignoring attributes on template argument ‘__m512bh’ [-Wignored-attributes]
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wignored-attributes"
|
||||
|
||||
#define MXFP4_VALUES \
|
||||
-6.0f, -4.0f, -3.0f, -2.0f, -1.5f, -1.0f, -0.5f, -0.0f, 6.0f, 4.0f, 3.0f, 2.0f, 1.5f, 1.0f, 0.5f, 0.0f
|
||||
|
||||
// convert 64 mxfp4 to 2x bf16 vectors, expect input 32-way packing
|
||||
inline std::tuple<__m512bh, __m512bh> cvt_mxfp4_e2m1_bf16_intrinsic_lut(__m256i a, __m512i s0, __m512i s1) {
|
||||
// LUT
|
||||
const __m512 values = _mm512_set_ps(MXFP4_VALUES);
|
||||
const __m512i lut = (__m512i)(_mm512_cvtne2ps_pbh(values, values));
|
||||
|
||||
const __m512i abs_mask = _mm512_set1_epi16(0x7FFF);
|
||||
const __m512i zero = _mm512_setzero_si512();
|
||||
|
||||
// expand values to 16-bit integers
|
||||
__m512i x0 = _mm512_cvtepu8_epi16(a);
|
||||
__m512i x1 = _mm512_srli_epi32(x0, 4);
|
||||
|
||||
// LUT to convert mxfp4 values to bf16
|
||||
x0 = _mm512_permutexvar_epi16(x0, lut);
|
||||
x1 = _mm512_permutexvar_epi16(x1, lut);
|
||||
|
||||
// check for zeros
|
||||
__mmask32 mask0 = _mm512_cmp_epi16_mask(_mm512_and_si512(x0, abs_mask), zero, _MM_CMPINT_EQ);
|
||||
__mmask32 mask1 = _mm512_cmp_epi16_mask(_mm512_and_si512(x1, abs_mask), zero, _MM_CMPINT_EQ);
|
||||
|
||||
// emulate bf16 mul with scale factor
|
||||
x0 = _mm512_add_epi16(x0, s0);
|
||||
x1 = _mm512_add_epi16(x1, s1);
|
||||
|
||||
// blend with zero
|
||||
x0 = _mm512_mask_blend_epi16(mask0, x0, zero);
|
||||
x1 = _mm512_mask_blend_epi16(mask1, x1, zero);
|
||||
|
||||
return std::make_tuple(__m512bh(x0), __m512bh(x1));
|
||||
}
|
||||
|
||||
#define CVT_MXFP4_TO_BF16(a, s0, s1) cvt_mxfp4_e2m1_bf16_intrinsic_lut(a, s0, s1)
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
#endif
|
||||
|
||||
// vector to scalar reduction
|
||||
#if defined(CPU_CAPABILITY_AVX512)
|
||||
inline float vec_reduce_sum(const Vectorized<float>& a) {
|
||||
return _mm512_reduce_add_ps(__m512(a));
|
||||
}
|
||||
|
||||
inline float vec_reduce_max(const Vectorized<float>& a) {
|
||||
return _mm512_reduce_max_ps(__m512(a));
|
||||
}
|
||||
#else
|
||||
inline float vec_reduce_sum(const Vectorized<float>& a) {
|
||||
return vec_reduce_all([](Vectorized<float>& x, Vectorized<float>& y) { return x + y; }, a);
|
||||
}
|
||||
|
||||
inline float vec_reduce_max(const Vectorized<float>& a) {
|
||||
return vec_reduce_all([](Vectorized<float>& x, Vectorized<float>& y) { return maximum(x, y); }, a);
|
||||
}
|
||||
#endif
|
||||
|
||||
// https://github.com/InternLM/lmdeploy/blob/086481ed84b59bee3b8e4274e5fc69620040c048/lmdeploy/pytorch/kernels/cuda/w8a8_triton_kernels.py#L282
|
||||
template <typename scalar_t>
|
||||
inline void
|
||||
quantize_row_int8(uint8_t* __restrict__ Aq, float& As, const scalar_t* __restrict__ A, int64_t K, float eps = 1e-7) {
|
||||
float amax = 0.f; // absolute max
|
||||
for (int64_t k = 0; k < K; ++k) {
|
||||
const float val = static_cast<float>(A[k]);
|
||||
amax = std::max(amax, std::abs(val));
|
||||
}
|
||||
|
||||
amax = std::max(amax, eps);
|
||||
const float scale = amax / 127;
|
||||
const float inv_scale = 127 / amax;
|
||||
|
||||
for (int64_t k = 0; k < K; ++k) {
|
||||
const float val = static_cast<float>(A[k]) * inv_scale;
|
||||
Aq[k] = (uint8_t)(std::round(val)) + 128;
|
||||
}
|
||||
As = scale;
|
||||
}
|
||||
|
||||
#if defined(CPU_CAPABILITY_AVX512)
|
||||
template <>
|
||||
inline void quantize_row_int8<at::BFloat16>(
|
||||
uint8_t* __restrict__ Aq, float& As, const at::BFloat16* __restrict__ A, int64_t K, float eps) {
|
||||
const __m512 signBit = _mm512_set1_ps(-0.0f);
|
||||
const __m512i off = _mm512_set1_epi32(128);
|
||||
|
||||
// K is 32x, no remainder
|
||||
float amax = 0.f;
|
||||
__m512 vamax0 = _mm512_set1_ps(0.f);
|
||||
__m512 vamax1 = _mm512_set1_ps(0.f);
|
||||
for (int64_t k = 0; k < K; k += 32) {
|
||||
__m512i va = _mm512_loadu_si512((void*)(A + k));
|
||||
__m512 va0 = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32(va, 0));
|
||||
__m512 va1 = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32(va, 1));
|
||||
vamax0 = _mm512_max_ps(vamax0, _mm512_andnot_ps(signBit, va0));
|
||||
vamax1 = _mm512_max_ps(vamax1, _mm512_andnot_ps(signBit, va1));
|
||||
}
|
||||
amax = _mm512_reduce_max_ps(_mm512_max_ps(vamax0, vamax1));
|
||||
amax = std::max(amax, eps);
|
||||
const float scale = amax / 127;
|
||||
const float inv_scale = 127 / amax;
|
||||
const __m512 vd = _mm512_set1_ps(inv_scale);
|
||||
|
||||
for (int64_t k = 0; k < K; k += 32) {
|
||||
__m512i va = _mm512_loadu_si512((void*)(A + k));
|
||||
__m512 va0 = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32(va, 0));
|
||||
__m512 va1 = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32(va, 1));
|
||||
va0 = _mm512_mul_ps(va0, vd);
|
||||
va1 = _mm512_mul_ps(va1, vd);
|
||||
va0 = _mm512_roundscale_ps(va0, (_MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC));
|
||||
va1 = _mm512_roundscale_ps(va1, (_MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC));
|
||||
__m128i i0 = _mm512_cvtepi32_epi8(_mm512_add_epi32(_mm512_cvtps_epi32(va0), off));
|
||||
__m128i i1 = _mm512_cvtepi32_epi8(_mm512_add_epi32(_mm512_cvtps_epi32(va1), off));
|
||||
_mm256_storeu_si256(reinterpret_cast<__m256i*>(Aq + k), _mm256_set_m128i(i1, i0));
|
||||
}
|
||||
As = scale;
|
||||
}
|
||||
#endif
|
||||
|
||||
// transpose utils
|
||||
// taken from my PR in ggml: https://github.com/ggml-org/llama.cpp/pull/8998
|
||||
#if defined(CPU_CAPABILITY_AVX512)
|
||||
inline void transpose_16x16_32bit(__m512i* v) {
|
||||
__m512i v1[16];
|
||||
v1[0] = _mm512_unpacklo_epi32(v[0], v[1]);
|
||||
v1[1] = _mm512_unpackhi_epi32(v[0], v[1]);
|
||||
v1[2] = _mm512_unpacklo_epi32(v[2], v[3]);
|
||||
v1[3] = _mm512_unpackhi_epi32(v[2], v[3]);
|
||||
v1[4] = _mm512_unpacklo_epi32(v[4], v[5]);
|
||||
v1[5] = _mm512_unpackhi_epi32(v[4], v[5]);
|
||||
v1[6] = _mm512_unpacklo_epi32(v[6], v[7]);
|
||||
v1[7] = _mm512_unpackhi_epi32(v[6], v[7]);
|
||||
v1[8] = _mm512_unpacklo_epi32(v[8], v[9]);
|
||||
v1[9] = _mm512_unpackhi_epi32(v[8], v[9]);
|
||||
v1[10] = _mm512_unpacklo_epi32(v[10], v[11]);
|
||||
v1[11] = _mm512_unpackhi_epi32(v[10], v[11]);
|
||||
v1[12] = _mm512_unpacklo_epi32(v[12], v[13]);
|
||||
v1[13] = _mm512_unpackhi_epi32(v[12], v[13]);
|
||||
v1[14] = _mm512_unpacklo_epi32(v[14], v[15]);
|
||||
v1[15] = _mm512_unpackhi_epi32(v[14], v[15]);
|
||||
|
||||
v[0] = _mm512_unpacklo_epi64(v1[0], v1[2]);
|
||||
v[1] = _mm512_unpackhi_epi64(v1[0], v1[2]);
|
||||
v[2] = _mm512_unpacklo_epi64(v1[1], v1[3]);
|
||||
v[3] = _mm512_unpackhi_epi64(v1[1], v1[3]);
|
||||
v[4] = _mm512_unpacklo_epi64(v1[4], v1[6]);
|
||||
v[5] = _mm512_unpackhi_epi64(v1[4], v1[6]);
|
||||
v[6] = _mm512_unpacklo_epi64(v1[5], v1[7]);
|
||||
v[7] = _mm512_unpackhi_epi64(v1[5], v1[7]);
|
||||
v[8] = _mm512_unpacklo_epi64(v1[8], v1[10]);
|
||||
v[9] = _mm512_unpackhi_epi64(v1[8], v1[10]);
|
||||
v[10] = _mm512_unpacklo_epi64(v1[9], v1[11]);
|
||||
v[11] = _mm512_unpackhi_epi64(v1[9], v1[11]);
|
||||
v[12] = _mm512_unpacklo_epi64(v1[12], v1[14]);
|
||||
v[13] = _mm512_unpackhi_epi64(v1[12], v1[14]);
|
||||
v[14] = _mm512_unpacklo_epi64(v1[13], v1[15]);
|
||||
v[15] = _mm512_unpackhi_epi64(v1[13], v1[15]);
|
||||
|
||||
v1[0] = _mm512_shuffle_i32x4(v[0], v[4], 0x88);
|
||||
v1[1] = _mm512_shuffle_i32x4(v[1], v[5], 0x88);
|
||||
v1[2] = _mm512_shuffle_i32x4(v[2], v[6], 0x88);
|
||||
v1[3] = _mm512_shuffle_i32x4(v[3], v[7], 0x88);
|
||||
v1[4] = _mm512_shuffle_i32x4(v[0], v[4], 0xdd);
|
||||
v1[5] = _mm512_shuffle_i32x4(v[1], v[5], 0xdd);
|
||||
v1[6] = _mm512_shuffle_i32x4(v[2], v[6], 0xdd);
|
||||
v1[7] = _mm512_shuffle_i32x4(v[3], v[7], 0xdd);
|
||||
v1[8] = _mm512_shuffle_i32x4(v[8], v[12], 0x88);
|
||||
v1[9] = _mm512_shuffle_i32x4(v[9], v[13], 0x88);
|
||||
v1[10] = _mm512_shuffle_i32x4(v[10], v[14], 0x88);
|
||||
v1[11] = _mm512_shuffle_i32x4(v[11], v[15], 0x88);
|
||||
v1[12] = _mm512_shuffle_i32x4(v[8], v[12], 0xdd);
|
||||
v1[13] = _mm512_shuffle_i32x4(v[9], v[13], 0xdd);
|
||||
v1[14] = _mm512_shuffle_i32x4(v[10], v[14], 0xdd);
|
||||
v1[15] = _mm512_shuffle_i32x4(v[11], v[15], 0xdd);
|
||||
|
||||
v[0] = _mm512_shuffle_i32x4(v1[0], v1[8], 0x88);
|
||||
v[1] = _mm512_shuffle_i32x4(v1[1], v1[9], 0x88);
|
||||
v[2] = _mm512_shuffle_i32x4(v1[2], v1[10], 0x88);
|
||||
v[3] = _mm512_shuffle_i32x4(v1[3], v1[11], 0x88);
|
||||
v[4] = _mm512_shuffle_i32x4(v1[4], v1[12], 0x88);
|
||||
v[5] = _mm512_shuffle_i32x4(v1[5], v1[13], 0x88);
|
||||
v[6] = _mm512_shuffle_i32x4(v1[6], v1[14], 0x88);
|
||||
v[7] = _mm512_shuffle_i32x4(v1[7], v1[15], 0x88);
|
||||
v[8] = _mm512_shuffle_i32x4(v1[0], v1[8], 0xdd);
|
||||
v[9] = _mm512_shuffle_i32x4(v1[1], v1[9], 0xdd);
|
||||
v[10] = _mm512_shuffle_i32x4(v1[2], v1[10], 0xdd);
|
||||
v[11] = _mm512_shuffle_i32x4(v1[3], v1[11], 0xdd);
|
||||
v[12] = _mm512_shuffle_i32x4(v1[4], v1[12], 0xdd);
|
||||
v[13] = _mm512_shuffle_i32x4(v1[5], v1[13], 0xdd);
|
||||
v[14] = _mm512_shuffle_i32x4(v1[6], v1[14], 0xdd);
|
||||
v[15] = _mm512_shuffle_i32x4(v1[7], v1[15], 0xdd);
|
||||
}
|
||||
|
||||
// remove warning : ignoring attributes on template argument ‘__m512i’ [-Wignored-attributes]
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wignored-attributes"
|
||||
|
||||
// transpose from [2, 32] to [32, 2]
|
||||
inline std::tuple<__m512i, __m512i> transpose_2x32_16bit(__m512i r0, __m512i r1) {
|
||||
// r0: {a0, a1, ..., a31}
|
||||
// r1: {b0, b1, ..., b31}
|
||||
//
|
||||
// d0: {a0, b0, ..., a15, b15}
|
||||
// d1: {a16, b16, ..., a31, b31}
|
||||
//
|
||||
__m512i d0 = _mm512_unpacklo_epi16(r0, r1);
|
||||
__m512i d1 = _mm512_unpackhi_epi16(r0, r1);
|
||||
r0 = _mm512_shuffle_i32x4(d0, d1, 0x88);
|
||||
r1 = _mm512_shuffle_i32x4(d0, d1, 0xdd);
|
||||
d0 = _mm512_shuffle_i32x4(r0, r1, 0x88);
|
||||
d1 = _mm512_shuffle_i32x4(r0, r1, 0xdd);
|
||||
return std::make_tuple(d0, d1);
|
||||
}
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
inline __attribute__((always_inline)) __m512 _mm512_fexp_u20_ps(const __m512 values) {
|
||||
const __m512 vec_c0 = _mm512_set1_ps(0.00010703434948458272f);
|
||||
const __m512 vec_c1 = _mm512_set1_ps(0.30354260500649682f);
|
||||
const __m512 vec_c2 = _mm512_set1_ps(-0.22433836478672356);
|
||||
const __m512 vec_c3 = _mm512_set1_ps(-0.079204240219773236);
|
||||
|
||||
const __m512 vec_exp_log2ef = _mm512_castsi512_ps(_mm512_set1_epi32(0x3fb8aa3b)); // log2(e)
|
||||
|
||||
const __m512 vec_a = _mm512_set1_ps(std::pow(2, 23) / std::log2(2));
|
||||
const __m512 vec_b = _mm512_set1_ps(std::pow(2, 23) * 127.f);
|
||||
|
||||
const __m512 vec_ln_flt_min = _mm512_castsi512_ps(_mm512_set1_epi32(0xc2aeac50));
|
||||
const __m512 vec_ln_flt_max = _mm512_castsi512_ps(_mm512_set1_epi32(0x42b17218));
|
||||
__m512i vec_infinity = _mm512_set1_epi32(0x7F800000);
|
||||
__m512i vec_zero = _mm512_setzero_epi32();
|
||||
|
||||
// Fast Exponential Computation on SIMD Architectures
|
||||
// A. Cristiano I. Malossi, Yves Ineichen, Costas Bekas, and Alessandro
|
||||
// Curioni exp(x) = 2**(x * log2(e))
|
||||
// = 2**xi * 2**xf - TIPS we are using the EEEE floating point
|
||||
// representation with identification to the exponent and the
|
||||
// mentissa
|
||||
// 2**xf will be approximated to a polynomial of degree 3 computed with
|
||||
// Horner method
|
||||
// mask for the boundary condition
|
||||
auto min_mask = _mm512_cmp_ps_mask(values, vec_ln_flt_min, _CMP_LT_OS);
|
||||
auto max_mask = _mm512_cmp_ps_mask(values, vec_ln_flt_max, _CMP_GT_OS);
|
||||
|
||||
// transformation with log2(e)
|
||||
auto vec_src = _mm512_mul_ps(values, vec_exp_log2ef);
|
||||
auto vec_fractional = _mm512_sub_ps(vec_src, _mm512_floor_ps(vec_src));
|
||||
|
||||
// compute polynomial using Horner Scheme, for superscalar processor
|
||||
auto vec_res = _mm512_fmadd_ps(vec_fractional, vec_c3, vec_c2);
|
||||
vec_res = _mm512_fmadd_ps(vec_fractional, vec_res, vec_c1);
|
||||
vec_res = _mm512_fmadd_ps(vec_fractional, vec_res, vec_c0);
|
||||
|
||||
vec_src = _mm512_sub_ps(vec_src, vec_res);
|
||||
// the tips is here, headache in perspective
|
||||
auto tmp = _mm512_fmadd_ps(vec_a, vec_src, vec_b);
|
||||
// headache bis - we loose precision with the cast but it "fits", but ok
|
||||
// after f32 -> f16 later
|
||||
__m512i casted_integer = _mm512_cvttps_epi32(tmp);
|
||||
// boundary condition, lower than the min -> 0
|
||||
casted_integer = _mm512_mask_mov_epi32(casted_integer, min_mask, vec_zero);
|
||||
// boundary condition, larger than the max -> +oo
|
||||
casted_integer = _mm512_mask_mov_epi32(casted_integer, max_mask, vec_infinity);
|
||||
// final interpretation to float
|
||||
return _mm512_castsi512_ps(casted_integer);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // anonymous namespace
|
||||
294
third_party/sglang/sgl-kernel/csrc/cpu/vec_pack.h
vendored
Normal file
294
third_party/sglang/sgl-kernel/csrc/cpu/vec_pack.h
vendored
Normal file
@@ -0,0 +1,294 @@
|
||||
// To use the transpose functions
|
||||
#include <ATen/native/cpu/utils.h>
|
||||
|
||||
#include "vec.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace at::vec;
|
||||
|
||||
template <typename index_t>
|
||||
inline index_t get_index(index_t* ind, int i) {
|
||||
return (ind == nullptr) ? (index_t)i : ind[i];
|
||||
}
|
||||
|
||||
#if defined(CPU_CAPABILITY_AVX512)
|
||||
// key: from [N, 32] to [32/2, N, 2]
|
||||
template <typename scalar_t, typename index_t>
|
||||
inline void pack_vnni_Nx32(
|
||||
scalar_t* __restrict__ dst,
|
||||
const scalar_t* __restrict__ src,
|
||||
const index_t* __restrict__ ind,
|
||||
int N,
|
||||
int ld_src,
|
||||
int ld_dst) {
|
||||
__m512i vinputs[16];
|
||||
|
||||
int n = 0;
|
||||
for (; n < N; ++n) {
|
||||
index_t index = get_index(ind, n);
|
||||
vinputs[n] = _mm512_loadu_si512(src + index * ld_src);
|
||||
}
|
||||
// padding with zero to avoid uninitialized vectors
|
||||
for (; n < 16; ++n) {
|
||||
vinputs[n] = _mm512_set1_epi32(0);
|
||||
}
|
||||
|
||||
// pack key
|
||||
transpose_16x16_32bit(vinputs);
|
||||
|
||||
const __mmask16 vmask = (1 << N) - 1;
|
||||
for (int k = 0; k < 16; ++k) {
|
||||
_mm512_mask_storeu_epi32(dst + k * ld_dst * 2, vmask, vinputs[k]);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename scalar_t, typename index_t>
|
||||
inline void pack_vnni_N_remainder(
|
||||
scalar_t* __restrict__ dst,
|
||||
const scalar_t* __restrict__ src,
|
||||
const index_t* __restrict__ ind,
|
||||
int N,
|
||||
int K,
|
||||
int ld_src,
|
||||
int ld_dst) {
|
||||
__m512i vinputs[16];
|
||||
|
||||
int K2 = K >> 1;
|
||||
const __mmask16 vmask = (1 << K2) - 1;
|
||||
|
||||
int n = 0;
|
||||
for (; n < N; ++n) {
|
||||
index_t index = get_index(ind, n);
|
||||
vinputs[n] = _mm512_maskz_loadu_epi32(vmask, src + index * ld_src);
|
||||
}
|
||||
// padding with zero to avoid uninitialized vectors
|
||||
for (; n < 16; ++n) {
|
||||
vinputs[n] = _mm512_set1_epi32(0);
|
||||
}
|
||||
|
||||
// pack key
|
||||
transpose_16x16_32bit(vinputs);
|
||||
|
||||
const __mmask16 vmask2 = (1 << N) - 1;
|
||||
for (int k = 0; k < K2; ++k) {
|
||||
_mm512_mask_storeu_epi32(dst + k * ld_dst * 2, vmask2, vinputs[k]);
|
||||
}
|
||||
}
|
||||
|
||||
// value: from [K, 32] to [K/2, 32, 2]
|
||||
template <typename scalar_t, typename index_t>
|
||||
inline void pack_vnni_Kx32(
|
||||
scalar_t* __restrict__ dst,
|
||||
const scalar_t* __restrict__ src,
|
||||
const index_t* __restrict__ ind,
|
||||
int K,
|
||||
int ld_src,
|
||||
int ld_dst) {
|
||||
__m512i vinputs[2];
|
||||
|
||||
int k = 0;
|
||||
for (; k < K; ++k) {
|
||||
index_t index = get_index(ind, k);
|
||||
vinputs[k] = _mm512_loadu_si512(src + index * ld_src);
|
||||
}
|
||||
// padding with zero to avoid uninitialized vectors
|
||||
for (; k < 2; ++k) {
|
||||
vinputs[k] = _mm512_set1_epi32(0);
|
||||
}
|
||||
|
||||
// pack value
|
||||
__m512i d0, d1;
|
||||
std::tie(d0, d1) = transpose_2x32_16bit(vinputs[0], vinputs[1]);
|
||||
_mm512_storeu_si512(dst + 0 * ld_dst * 2, d0);
|
||||
_mm512_storeu_si512(dst + 0 * ld_dst * 2 + 32, d1);
|
||||
}
|
||||
|
||||
template <typename scalar_t, typename index_t>
|
||||
inline void pack_vnni_K_remainder(
|
||||
scalar_t* __restrict__ dst,
|
||||
const scalar_t* __restrict__ src,
|
||||
const index_t* __restrict__ ind,
|
||||
int K,
|
||||
int N,
|
||||
int ld_src,
|
||||
int ld_dst) {
|
||||
__m512i vinputs[2];
|
||||
|
||||
const __mmask32 vmask = (1 << N) - 1;
|
||||
|
||||
int k = 0;
|
||||
for (; k < K; ++k) {
|
||||
index_t index = get_index(ind, k);
|
||||
vinputs[k] = _mm512_maskz_loadu_epi16(vmask, src + index * ld_src);
|
||||
}
|
||||
// padding with zero to avoid uninitialized vectors
|
||||
for (; k < 2; ++k) {
|
||||
vinputs[k] = _mm512_set1_epi32(0);
|
||||
}
|
||||
|
||||
// pack value
|
||||
__m512i d0, d1;
|
||||
std::tie(d0, d1) = transpose_2x32_16bit(vinputs[0], vinputs[1]);
|
||||
|
||||
if (N <= 16) {
|
||||
// 2N * 16bits: N * 32bits
|
||||
const __mmask16 vmask2 = (1 << N) - 1;
|
||||
_mm512_mask_storeu_epi32(dst + 0 * ld_dst * 2, vmask2, d0);
|
||||
} else {
|
||||
// 2(N-16) * 16bits: (N-16) * 32bits
|
||||
const __mmask16 vmask2 = (1 << (N - 16)) - 1;
|
||||
_mm512_storeu_epi32(dst + 0 * ld_dst * 2, d0);
|
||||
_mm512_mask_storeu_epi32(dst + 0 * ld_dst * 2 + 32, vmask2, d1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// convert to vnni format
|
||||
// from [N, K/2, 2] to [K/2, N, 2] for bfloat16 and float16
|
||||
template <typename scalar_t, typename index_t, bool is_indexed>
|
||||
void pack_vnni(
|
||||
scalar_t* __restrict__ dst,
|
||||
const scalar_t* __restrict__ src,
|
||||
const index_t* __restrict__ ind,
|
||||
int N,
|
||||
int K,
|
||||
int ld_src,
|
||||
int ld_dst) {
|
||||
#if defined(CPU_CAPABILITY_AVX512)
|
||||
const int NB = div_up(N, 16);
|
||||
const int KB = K / 32;
|
||||
const int K_remainder = K - KB * 32;
|
||||
|
||||
for (int nb = 0; nb < NB; ++nb) {
|
||||
int nb_size = std::min(N - nb * 16, 16);
|
||||
for (int kb = 0; kb < KB; ++kb) {
|
||||
// handle 16x512bits each block
|
||||
pack_vnni_Nx32<scalar_t, index_t>(
|
||||
/* dst */ dst + ((kb * 32) >> 1) * ld_dst * 2 + nb * 16 * 2,
|
||||
/* src */ src + kb * 32 + (is_indexed ? 0 : nb * 16 * ld_src),
|
||||
/* ind */ is_indexed ? ind + nb * 16 : nullptr,
|
||||
/* N */ nb_size,
|
||||
/* ld_src */ ld_src,
|
||||
/* ld_dst */ ld_dst);
|
||||
}
|
||||
if (K_remainder > 0) {
|
||||
pack_vnni_N_remainder<scalar_t, index_t>(
|
||||
/* dst */ dst + ((KB * 32) >> 1) * ld_dst * 2 + nb * 16 * 2,
|
||||
/* src */ src + KB * 32 + (is_indexed ? 0 : nb * 16 * ld_src),
|
||||
/* ind */ is_indexed ? ind + nb * 16 : nullptr,
|
||||
/* N */ nb_size,
|
||||
/* K */ K_remainder,
|
||||
/* ld_src */ ld_src,
|
||||
/* ld_dst */ ld_dst);
|
||||
}
|
||||
}
|
||||
#else
|
||||
for (int n = 0; n < N; ++n) {
|
||||
index_t index = get_index(ind, n);
|
||||
for (int k = 0; k < K / 2; ++k) {
|
||||
for (int d = 0; d < 2; ++d) {
|
||||
dst[k * ld_dst * 2 + n * 2 + d] = src[index * ld_src + k * 2 + d];
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
void pack_vnni(scalar_t* __restrict__ dst, const scalar_t* __restrict__ src, int N, int K, int ld_src, int ld_dst) {
|
||||
pack_vnni<scalar_t, int32_t, false>(dst, src, nullptr, N, K, ld_src, ld_dst);
|
||||
}
|
||||
|
||||
template <typename scalar_t, typename index_t>
|
||||
void pack_vnni(
|
||||
scalar_t* __restrict__ dst,
|
||||
const scalar_t* __restrict__ src,
|
||||
const index_t* __restrict__ ind,
|
||||
int N,
|
||||
int K,
|
||||
int ld_src,
|
||||
int ld_dst) {
|
||||
assert(ind != nullptr);
|
||||
pack_vnni<scalar_t, index_t, true>(dst, src, ind, N, K, ld_src, ld_dst);
|
||||
}
|
||||
|
||||
// convert to vnni format
|
||||
// from [K/2, 2, N] to [K/2, N, 2] for bfloat16 and float16
|
||||
template <typename scalar_t, typename index_t, bool is_indexed>
|
||||
void pack_vnni2(
|
||||
scalar_t* __restrict__ dst,
|
||||
const scalar_t* __restrict__ src,
|
||||
const index_t* __restrict__ ind,
|
||||
int K,
|
||||
int N,
|
||||
int ld_src,
|
||||
int ld_dst) {
|
||||
#if defined(CPU_CAPABILITY_AVX512)
|
||||
const int KB = div_up(K, 2);
|
||||
const int NB = N / 32;
|
||||
const int N_remainder = N - NB * 32;
|
||||
|
||||
for (int kb = 0; kb < KB; ++kb) {
|
||||
int kb_size = std::min(K - kb * 2, 2);
|
||||
for (int nb = 0; nb < NB; ++nb) {
|
||||
// handle 2x512bits each block
|
||||
pack_vnni_Kx32<scalar_t, index_t>(
|
||||
/* dst */ dst + ((kb * 2) >> 1) * ld_dst * 2 + nb * 32 * 2,
|
||||
/* src */ src + (is_indexed ? 0 : kb * 2 * ld_src) + nb * 32,
|
||||
/* ind */ is_indexed ? ind + kb * 2 : nullptr,
|
||||
/* K */ kb_size,
|
||||
/* ld_src */ ld_src,
|
||||
/* ld_dst */ ld_dst);
|
||||
}
|
||||
if (N_remainder > 0) {
|
||||
pack_vnni_K_remainder(
|
||||
/* dst */ dst + ((kb * 2) >> 1) * ld_dst * 2 + NB * 32 * 2,
|
||||
/* src */ src + (is_indexed ? 0 : kb * 2 * ld_src) + NB * 32,
|
||||
/* ind */ is_indexed ? ind + kb * 2 : nullptr,
|
||||
/* K */ kb_size,
|
||||
/* N */ N_remainder,
|
||||
/* ld_src */ ld_src,
|
||||
/* ld_dst */ ld_dst);
|
||||
}
|
||||
}
|
||||
#else
|
||||
int k = 0;
|
||||
for (; k < (K >> 1) * 2; k += 2) {
|
||||
index_t index0 = get_index(ind, k + 0);
|
||||
index_t index1 = get_index(ind, k + 1);
|
||||
for (int n = 0; n < N; ++n) {
|
||||
dst[(k >> 1) * ld_dst * 2 + n * 2 + 0] = src[index0 * ld_src + n];
|
||||
dst[(k >> 1) * ld_dst * 2 + n * 2 + 1] = src[index1 * ld_src + n];
|
||||
}
|
||||
}
|
||||
if (K % 2 != 0) {
|
||||
index_t index = get_index(ind, K - 1);
|
||||
for (int n = 0; n < N; ++n) {
|
||||
dst[(K >> 1) * ld_dst * 2 + n * 2 + 0] = src[index * ld_src + n];
|
||||
dst[(K >> 1) * ld_dst * 2 + n * 2 + 1] = 0;
|
||||
}
|
||||
k += 2;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename scalar_t>
|
||||
void pack_vnni2(scalar_t* __restrict__ dst, const scalar_t* __restrict__ src, int K, int N, int ld_src, int ld_dst) {
|
||||
pack_vnni2<scalar_t, int32_t, false>(dst, src, nullptr, K, N, ld_src, ld_dst);
|
||||
}
|
||||
|
||||
template <typename scalar_t, typename index_t>
|
||||
void pack_vnni2(
|
||||
scalar_t* __restrict__ dst,
|
||||
const scalar_t* __restrict__ src,
|
||||
const index_t* __restrict__ ind,
|
||||
int K,
|
||||
int N,
|
||||
int ld_src,
|
||||
int ld_dst) {
|
||||
assert(ind != nullptr);
|
||||
pack_vnni2<scalar_t, index_t, true>(dst, src, ind, K, N, ld_src, ld_dst);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
280
third_party/sglang/sgl-kernel/csrc/cpu/x86_64/shm.h
vendored
Normal file
280
third_party/sglang/sgl-kernel/csrc/cpu/x86_64/shm.h
vendored
Normal file
@@ -0,0 +1,280 @@
|
||||
#pragma once
|
||||
#include <immintrin.h>
|
||||
|
||||
// Reduce functions down below use vectorized algorithm, the number of bytes
|
||||
// processed each iteration depends on vector length. 256bit vector ==> 32
|
||||
// bytes, 512bit vector ==> 64 bytes If you change implementation of
|
||||
// reduce_bf16_buffers, etc. , check whether this number needs to be changed
|
||||
#define VECTOR_LENGTH_IN_BYTES 32
|
||||
|
||||
inline __m512 cvt_bf16_to_fp32(const __m256i src) __attribute__((target("avx512bw")));
|
||||
inline __m512 cvt_bf16_to_fp32(const __m256i src) {
|
||||
auto y = _mm512_cvtepu16_epi32(src);
|
||||
return _mm512_castsi512_ps(_mm512_bslli_epi128(y, 2));
|
||||
}
|
||||
|
||||
inline __m256i cvt_fp32_to_bf16(const __m512 src) __attribute__((target("avx512bw")));
|
||||
inline __m256i cvt_fp32_to_bf16(const __m512 src) {
|
||||
__m512i value = _mm512_castps_si512(src);
|
||||
__m512i nan = _mm512_set1_epi32(0xffff);
|
||||
auto mask_value = _mm512_cmp_ps_mask(src, src, _CMP_ORD_Q);
|
||||
__m512i ones = _mm512_set1_epi32(0x1);
|
||||
__m512i vec_bias = _mm512_set1_epi32(0x7fff);
|
||||
// uint32_t lsb = (input >> 16) & 1;
|
||||
auto t_value = _mm512_and_si512(_mm512_srli_epi32(value, 16), ones);
|
||||
// uint32_t rounding_bias = 0x7fff + lsb;
|
||||
t_value = _mm512_add_epi32(t_value, vec_bias);
|
||||
// input += rounding_bias;
|
||||
t_value = _mm512_add_epi32(t_value, value);
|
||||
// input = input >> 16;
|
||||
t_value = _mm512_srli_epi32(t_value, 16);
|
||||
// Check NaN before converting back to bf16
|
||||
t_value = _mm512_mask_blend_epi32(mask_value, nan, t_value);
|
||||
return _mm512_cvtusepi32_epi16(t_value);
|
||||
}
|
||||
|
||||
inline __m512 cvt_fp16_to_fp32(const __m256i src) __attribute__((target("avx512bw")));
|
||||
inline __m512 cvt_fp16_to_fp32(const __m256i src) {
|
||||
return _mm512_cvtph_ps(src);
|
||||
}
|
||||
|
||||
inline __m256i cvt_fp32_to_fp16(const __m512 src) __attribute__((target("avx512bw")));
|
||||
inline __m256i cvt_fp32_to_fp16(const __m512 src) {
|
||||
return _mm512_cvtps_ph(src, (_MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC));
|
||||
}
|
||||
|
||||
#define CVT_ADD_BF16(x) \
|
||||
do { \
|
||||
auto in##x##_val = cvt_bf16_to_fp32(_mm256_loadu_si256((__m256i*)(buffers[x] + i))); \
|
||||
inout_val = _mm512_add_ps(inout_val, in##x##_val); \
|
||||
} while (0)
|
||||
|
||||
__attribute__((target("avx512bw"))) inline void
|
||||
reduce_bf16_buffers(int start_elements, int num_elements, char* to_buffer, char** buffers, int world_size) {
|
||||
const int element_size = 2;
|
||||
const int vector_length = VECTOR_LENGTH_IN_BYTES / element_size;
|
||||
int main_elements = num_elements - (num_elements % vector_length);
|
||||
int remain_elements = num_elements % vector_length;
|
||||
|
||||
// process aligned part
|
||||
#pragma omp parallel for
|
||||
for (int i = start_elements * element_size; i < (start_elements + main_elements) * element_size;
|
||||
i += VECTOR_LENGTH_IN_BYTES) {
|
||||
auto inout_val = cvt_bf16_to_fp32(_mm256_loadu_si256((__m256i*)(buffers[0] + i)));
|
||||
switch (world_size) {
|
||||
case 16:
|
||||
CVT_ADD_BF16(15);
|
||||
case 15:
|
||||
CVT_ADD_BF16(14);
|
||||
case 14:
|
||||
CVT_ADD_BF16(13);
|
||||
case 13:
|
||||
CVT_ADD_BF16(12);
|
||||
case 12:
|
||||
CVT_ADD_BF16(11);
|
||||
case 11:
|
||||
CVT_ADD_BF16(10);
|
||||
case 10:
|
||||
CVT_ADD_BF16(9);
|
||||
case 9:
|
||||
CVT_ADD_BF16(8);
|
||||
case 8:
|
||||
CVT_ADD_BF16(7);
|
||||
case 7:
|
||||
CVT_ADD_BF16(6);
|
||||
case 6:
|
||||
CVT_ADD_BF16(5);
|
||||
case 5:
|
||||
CVT_ADD_BF16(4);
|
||||
case 4:
|
||||
CVT_ADD_BF16(3);
|
||||
case 3:
|
||||
CVT_ADD_BF16(2);
|
||||
case 2:
|
||||
CVT_ADD_BF16(1);
|
||||
case 1:
|
||||
break;
|
||||
default:
|
||||
for (int j = 1; j < world_size; j++) {
|
||||
auto in_val = cvt_bf16_to_fp32(_mm256_loadu_si256((__m256i*)(buffers[j] + i)));
|
||||
inout_val = _mm512_add_ps(inout_val, in_val);
|
||||
}
|
||||
}
|
||||
_mm256_storeu_si256((__m256i*)(to_buffer + i), cvt_fp32_to_bf16(inout_val));
|
||||
}
|
||||
|
||||
// process remaining part
|
||||
int i = (start_elements + main_elements) * element_size;
|
||||
while (remain_elements > 0) {
|
||||
float val = 0.0f;
|
||||
for (int j = 0; j < world_size; j++) {
|
||||
val += *(at::BFloat16*)(buffers[j] + i);
|
||||
}
|
||||
*(at::BFloat16*)(to_buffer + i) = val;
|
||||
remain_elements--;
|
||||
i += element_size;
|
||||
}
|
||||
}
|
||||
|
||||
#define CVT_ADD_FP16(x) \
|
||||
do { \
|
||||
auto in##x##_val = cvt_fp16_to_fp32(_mm256_loadu_si256((__m256i*)(buffers[x] + i))); \
|
||||
inout_val = _mm512_add_ps(inout_val, in##x##_val); \
|
||||
} while (0)
|
||||
|
||||
__attribute__((target("avx512bw"))) inline void
|
||||
reduce_fp16_buffers(int start_elements, int num_elements, char* to_buffer, char** buffers, int world_size) {
|
||||
const int element_size = 2;
|
||||
const int vector_length = VECTOR_LENGTH_IN_BYTES / element_size;
|
||||
int main_elements = num_elements - (num_elements % vector_length);
|
||||
int remain_elements = num_elements % vector_length;
|
||||
|
||||
// process aligned part
|
||||
#pragma omp parallel for
|
||||
for (int i = start_elements * element_size; i < (start_elements + main_elements) * element_size;
|
||||
i += VECTOR_LENGTH_IN_BYTES) {
|
||||
auto inout_val = cvt_fp16_to_fp32(_mm256_loadu_si256((__m256i*)(buffers[0] + i)));
|
||||
switch (world_size) {
|
||||
case 16:
|
||||
CVT_ADD_FP16(15);
|
||||
case 15:
|
||||
CVT_ADD_FP16(14);
|
||||
case 14:
|
||||
CVT_ADD_FP16(13);
|
||||
case 13:
|
||||
CVT_ADD_FP16(12);
|
||||
case 12:
|
||||
CVT_ADD_FP16(11);
|
||||
case 11:
|
||||
CVT_ADD_FP16(10);
|
||||
case 10:
|
||||
CVT_ADD_FP16(9);
|
||||
case 9:
|
||||
CVT_ADD_FP16(8);
|
||||
case 8:
|
||||
CVT_ADD_FP16(7);
|
||||
case 7:
|
||||
CVT_ADD_FP16(6);
|
||||
case 6:
|
||||
CVT_ADD_FP16(5);
|
||||
case 5:
|
||||
CVT_ADD_FP16(4);
|
||||
case 4:
|
||||
CVT_ADD_FP16(3);
|
||||
case 3:
|
||||
CVT_ADD_FP16(2);
|
||||
case 2:
|
||||
CVT_ADD_FP16(1);
|
||||
case 1:
|
||||
break;
|
||||
default:
|
||||
for (int j = 1; j < world_size; j++) {
|
||||
auto in_val = cvt_fp16_to_fp32(_mm256_loadu_si256((__m256i*)(buffers[j] + i)));
|
||||
inout_val = _mm512_add_ps(inout_val, in_val);
|
||||
}
|
||||
}
|
||||
_mm256_storeu_si256((__m256i*)(to_buffer + i), cvt_fp32_to_fp16(inout_val));
|
||||
}
|
||||
|
||||
// process remaining part
|
||||
int i = (start_elements + main_elements) * element_size;
|
||||
while (remain_elements > 0) {
|
||||
float val = 0.0f;
|
||||
for (int j = 0; j < world_size; j++) {
|
||||
val += *(at::Half*)(buffers[j] + i);
|
||||
}
|
||||
*(at::Half*)(to_buffer + i) = val;
|
||||
remain_elements--;
|
||||
i += element_size;
|
||||
}
|
||||
}
|
||||
|
||||
#define CVT_ADD_F32(x) \
|
||||
do { \
|
||||
auto in##x##_val = _mm256_loadu_ps((float*)(buffers[x] + i)); \
|
||||
inout_val = _mm256_add_ps(inout_val, in##x##_val); \
|
||||
} while (0)
|
||||
|
||||
__attribute__((target("avx512bw"))) inline void
|
||||
reduce_fp32_buffers(int start_elements, int num_elements, char* to_buffer, char** buffers, int world_size) {
|
||||
const int element_size = 4;
|
||||
const int vector_length = VECTOR_LENGTH_IN_BYTES / element_size;
|
||||
int main_elements = num_elements - (num_elements % vector_length);
|
||||
int remain_elements = num_elements % vector_length;
|
||||
|
||||
// process aligned part
|
||||
#pragma omp parallel for
|
||||
for (int i = start_elements * element_size; i < (start_elements + main_elements) * element_size;
|
||||
i += VECTOR_LENGTH_IN_BYTES) {
|
||||
auto inout_val = _mm256_loadu_ps((float*)(buffers[0] + i));
|
||||
switch (world_size) {
|
||||
case 16:
|
||||
CVT_ADD_F32(15);
|
||||
case 15:
|
||||
CVT_ADD_F32(14);
|
||||
case 14:
|
||||
CVT_ADD_F32(13);
|
||||
case 13:
|
||||
CVT_ADD_F32(12);
|
||||
case 12:
|
||||
CVT_ADD_F32(11);
|
||||
case 11:
|
||||
CVT_ADD_F32(10);
|
||||
case 10:
|
||||
CVT_ADD_F32(9);
|
||||
case 9:
|
||||
CVT_ADD_F32(8);
|
||||
case 8:
|
||||
CVT_ADD_F32(7);
|
||||
case 7:
|
||||
CVT_ADD_F32(6);
|
||||
case 6:
|
||||
CVT_ADD_F32(5);
|
||||
case 5:
|
||||
CVT_ADD_F32(4);
|
||||
case 4:
|
||||
CVT_ADD_F32(3);
|
||||
case 3:
|
||||
CVT_ADD_F32(2);
|
||||
case 2:
|
||||
CVT_ADD_F32(1);
|
||||
case 1:
|
||||
break;
|
||||
default:
|
||||
for (int j = 1; j < world_size; j++) {
|
||||
auto in_val = _mm256_loadu_ps((float*)(buffers[j] + i));
|
||||
inout_val = _mm256_add_ps(inout_val, in_val);
|
||||
}
|
||||
}
|
||||
_mm256_storeu_ps((float*)(to_buffer + i), inout_val);
|
||||
}
|
||||
|
||||
// process remaining part
|
||||
int i = (start_elements + main_elements) * element_size;
|
||||
while (remain_elements > 0) {
|
||||
float val = 0.0f;
|
||||
for (int j = 0; j < world_size; j++) {
|
||||
val += *(float*)(buffers[j] + i);
|
||||
}
|
||||
*(float*)(to_buffer + i) = val;
|
||||
remain_elements--;
|
||||
i += element_size;
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((target("avx512bw"))) inline void parallel_memcpy(void* to, void* from, size_t n_bytes) {
|
||||
auto aligned_bytes = n_bytes - (n_bytes % VECTOR_LENGTH_IN_BYTES);
|
||||
// process aligned part
|
||||
#pragma omp parallel for
|
||||
for (size_t i = 0; i < aligned_bytes; i += VECTOR_LENGTH_IN_BYTES) {
|
||||
auto val = _mm256_loadu_si256((__m256i*)((char*)from + i));
|
||||
_mm256_storeu_si256((__m256i*)((char*)to + i), val);
|
||||
}
|
||||
|
||||
// process remaining part
|
||||
for (size_t i = aligned_bytes; i < n_bytes; i++) {
|
||||
*((char*)to + i) = *((char*)from + i);
|
||||
}
|
||||
}
|
||||
|
||||
#undef VECTOR_LENGTH_IN_BYTES
|
||||
Reference in New Issue
Block a user