[AMD] Dsv4/pr1 fix run time issue (#25898)

Co-authored-by: wunhuang <wunhuang@amd.com>
Co-authored-by: Thomas Wang <1am9trash@gmail.com>
Co-authored-by: Xinyi Song <86638975+RolaoDenthu@users.noreply.github.com>
Co-authored-by: HaiShaw <hixiao@gmail.com>
Co-authored-by: amd-danli103 <danli103@amd.com>
Co-authored-by: Lin, Soga <soga.lin@amd.com>
Co-authored-by: Raiden-Makoto <Raiden-Makoto@users.noreply.github.com>
Co-authored-by: Hubert Lu <55214931+hubertlu-tw@users.noreply.github.com>
Co-authored-by: yichiche@amd.com <jacky.cheng>
Co-authored-by: yctseng0211 <yctseng@amd.com>
Co-authored-by: Bingxu Chen <bingxche@amd.com>
This commit is contained in:
kk
2026-05-23 16:04:14 -07:00
committed by GitHub
co-authored by wunhuang Thomas Wang Xinyi Song HaiShaw amd-danli103 Lin, Soga Raiden-Makoto Hubert Lu yichiche@amd.com yctseng0211 Bingxu Chen
parent 982f67d9a6
commit af8f66940e
32 changed files with 2523 additions and 129 deletions
+1
View File
@@ -258,6 +258,7 @@ set(SOURCES
"csrc/elementwise/activation.cu"
"csrc/elementwise/concat_mla.cu"
"csrc/elementwise/copy.cu"
"csrc/elementwise/dsv4_norm_rope.cu"
"csrc/elementwise/fused_add_rms_norm_kernel.cu"
"csrc/elementwise/pos_enc.cu"
"csrc/elementwise/topk.cu"
+19
View File
@@ -47,6 +47,25 @@ TORCH_LIBRARY_EXPAND(sgl_kernel, m) {
"topk_indices_offset, Tensor ? row_starts) -> ()");
m.impl("fast_topk_transform_ragged_fused", torch::kCUDA, &fast_topk_transform_ragged_interface);
m.def(
"deepseek_v4_topk_transform_512(Tensor scores, Tensor seq_lens, Tensor page_table, Tensor! "
"page_indices, int page_size, Tensor!? raw_indices) -> ()");
m.impl("deepseek_v4_topk_transform_512", torch::kCUDA, &deepseek_v4_topk_transform_512);
m.def(
"dsv4_fused_q_norm_rope(Tensor q_input, Tensor! q_output, Tensor freqs_cis, Tensor positions, float eps) -> ()");
m.impl("dsv4_fused_q_norm_rope", torch::kCUDA, &dsv4_fused_q_norm_rope);
m.def(
"dsv4_fused_k_norm_rope_flashmla(Tensor kv, Tensor kv_weight, Tensor freqs_cis, Tensor positions, "
"Tensor out_loc, Tensor! kvcache, float eps, int page_size) -> ()");
m.impl("dsv4_fused_k_norm_rope_flashmla", torch::kCUDA, &dsv4_fused_k_norm_rope_flashmla);
m.def(
"dsv4_fused_q_indexer_rope_hadamard_quant(Tensor q_input, Tensor! q_fp8, Tensor weight, "
"Tensor! weights_out, float weight_scale, Tensor freqs_cis, Tensor positions) -> ()");
m.impl("dsv4_fused_q_indexer_rope_hadamard_quant", torch::kCUDA, &dsv4_fused_q_indexer_rope_hadamard_quant);
/*
* From csrc/allreduce
*/
@@ -0,0 +1,372 @@
/* 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/core/TensorBase.h>
#include <ATen/core/TensorBody.h>
#include <c10/cuda/CUDAStream.h>
#include <c10/macros/Macros.h>
#include <c10/util/Exception.h>
#include <cuda.h>
#include <cuda_fp16.h>
#include <cstddef>
#include <cstdint>
#include <optional>
namespace {
constexpr uint32_t kMaxTopK = 1024;
constexpr uint32_t kBlockSize = 512;
#ifdef SGL_TOPK_DYNAMIC_SMEM_BYTES
constexpr size_t kSMEM = static_cast<size_t>(SGL_TOPK_DYNAMIC_SMEM_BYTES);
#else
constexpr size_t kSMEM = 48 * 1024; // bytes
#endif
static_assert(kSMEM % (2 * sizeof(int32_t)) == 0, "kSMEM must be a multiple of 8 bytes.");
struct TopKParams {
const float* __restrict__ scores;
const int32_t* __restrict__ seq_lens;
const int32_t* __restrict__ page_table;
int32_t* __restrict__ page_indices;
int32_t* __restrict__ raw_indices;
int64_t score_stride;
int64_t page_table_stride;
uint32_t page_bits;
uint32_t topk;
int64_t output_stride;
};
__device__ __forceinline__ uint8_t convert_to_uint8(float x) {
__half h = __float2half_rn(x);
uint16_t bits = __half_as_ushort(h);
uint16_t key = (bits & 0x8000) ? static_cast<uint16_t>(~bits) : static_cast<uint16_t>(bits | 0x8000);
return static_cast<uint8_t>(key >> 8);
}
__device__ __forceinline__ uint32_t convert_to_uint32(float x) {
uint32_t bits = __float_as_uint(x);
return (bits & 0x80000000u) ? ~bits : (bits | 0x80000000u);
}
__device__ __forceinline__ int32_t
page_to_slot(const int32_t* __restrict__ page_table, uint32_t i, uint32_t page_bits) {
const uint32_t mask = (1u << page_bits) - 1u;
return (page_table[i >> page_bits] << page_bits) | static_cast<int32_t>(i & mask);
}
__device__ void naive_paged_transform(
int32_t length,
uint32_t topk,
uint32_t page_bits,
const int32_t* __restrict__ page_table,
int32_t* __restrict__ page_indices_out,
int32_t* __restrict__ raw_indices_out) {
for (uint32_t i = threadIdx.x; i < topk; i += kBlockSize) {
if (i < static_cast<uint32_t>(length)) {
page_indices_out[i] = page_to_slot(page_table, i, page_bits);
if (raw_indices_out != nullptr) {
raw_indices_out[i] = static_cast<int32_t>(i);
}
} else {
page_indices_out[i] = -1;
if (raw_indices_out != nullptr) {
raw_indices_out[i] = -1;
}
}
}
}
__device__ void
radix_topk(const float* __restrict__ input, int32_t* __restrict__ output, uint32_t length, uint32_t topk) {
constexpr uint32_t RADIX = 256;
constexpr uint32_t BLOCK_SIZE = kBlockSize;
constexpr uint32_t SMEM_INPUT_SIZE = kSMEM / (2 * sizeof(int32_t));
alignas(128) __shared__ uint32_t _s_histogram_buf[2][RADIX + 32];
alignas(128) __shared__ uint32_t s_counter;
alignas(128) __shared__ uint32_t s_threshold_bin_id;
alignas(128) __shared__ uint32_t s_num_input[2];
alignas(128) __shared__ int32_t s_last_remain;
extern __shared__ uint32_t s_input_idx[][SMEM_INPUT_SIZE];
const uint32_t tx = threadIdx.x;
uint32_t remain_topk = topk;
auto& s_histogram = _s_histogram_buf[0];
const auto run_cumsum = [&] {
#pragma unroll 8
for (int32_t i = 0; i < 8; ++i) {
static_assert(1 << 8 == RADIX);
if (tx < RADIX) {
const auto j = 1 << i;
const auto k = i & 1;
auto value = _s_histogram_buf[k][tx];
if (tx + j < RADIX) {
value += _s_histogram_buf[k][tx + j];
}
_s_histogram_buf[k ^ 1][tx] = value;
}
__syncthreads();
}
};
// stage 1: 8bit coarse histogram
if (tx < RADIX + 1) s_histogram[tx] = 0;
__syncthreads();
for (uint32_t idx = tx; idx < length; idx += BLOCK_SIZE) {
const auto bin = convert_to_uint8(input[idx]);
::atomicAdd(&s_histogram[bin], 1);
}
__syncthreads();
run_cumsum();
if (tx < RADIX && s_histogram[tx] > remain_topk && s_histogram[tx + 1] <= remain_topk) {
s_threshold_bin_id = tx;
s_num_input[0] = 0;
s_counter = 0;
}
__syncthreads();
{
const auto threshold_bin = s_threshold_bin_id;
remain_topk -= s_histogram[threshold_bin + 1];
if (remain_topk == 0) {
for (uint32_t idx = tx; idx < length; idx += BLOCK_SIZE) {
const uint32_t bin = convert_to_uint8(input[idx]);
if (bin > threshold_bin) {
const auto pos = ::atomicAdd(&s_counter, 1);
output[pos] = static_cast<int32_t>(idx);
}
}
__syncthreads();
return;
}
__syncthreads();
if (tx < RADIX + 1) s_histogram[tx] = 0;
__syncthreads();
for (uint32_t idx = tx; idx < length; idx += BLOCK_SIZE) {
const float raw_input = input[idx];
const uint32_t bin = convert_to_uint8(raw_input);
if (bin > threshold_bin) {
const auto pos = ::atomicAdd(&s_counter, 1);
output[pos] = static_cast<int32_t>(idx);
} else if (bin == threshold_bin) {
const auto pos = ::atomicAdd(&s_num_input[0], 1);
if (C10_LIKELY(pos < SMEM_INPUT_SIZE)) {
s_input_idx[0][pos] = idx;
const auto bin32 = convert_to_uint32(raw_input);
const auto sub_bin = (bin32 >> 24) & 0xFF;
::atomicAdd(&s_histogram[sub_bin], 1);
}
}
}
__syncthreads();
}
// stage 2: refine with 8bit radix passes
#pragma unroll 4
for (int round = 0; round < 4; ++round) {
const auto r_idx = round % 2;
const auto raw_num_input = s_num_input[r_idx];
const auto num_input = raw_num_input < SMEM_INPUT_SIZE ? raw_num_input : SMEM_INPUT_SIZE;
run_cumsum();
if (tx < RADIX && s_histogram[tx] > remain_topk && s_histogram[tx + 1] <= remain_topk) {
s_threshold_bin_id = tx;
s_num_input[r_idx ^ 1] = 0;
s_last_remain = static_cast<int32_t>(remain_topk - s_histogram[tx + 1]);
}
__syncthreads();
const auto threshold_bin = s_threshold_bin_id;
remain_topk -= s_histogram[threshold_bin + 1];
if (remain_topk == 0) {
for (uint32_t i = tx; i < num_input; i += BLOCK_SIZE) {
const auto idx = s_input_idx[r_idx][i];
const auto offset = 24 - round * 8;
const auto bin = (convert_to_uint32(input[idx]) >> offset) & 0xFF;
if (bin > threshold_bin) {
const auto pos = ::atomicAdd(&s_counter, 1);
output[pos] = static_cast<int32_t>(idx);
}
}
__syncthreads();
break;
}
__syncthreads();
if (tx < RADIX + 1) s_histogram[tx] = 0;
__syncthreads();
for (uint32_t i = tx; i < num_input; i += BLOCK_SIZE) {
const auto idx = s_input_idx[r_idx][i];
const auto raw_input = input[idx];
const auto offset = 24 - round * 8;
const auto bin = (convert_to_uint32(raw_input) >> offset) & 0xFF;
if (bin > threshold_bin) {
const auto pos = ::atomicAdd(&s_counter, 1);
output[pos] = static_cast<int32_t>(idx);
} else if (bin == threshold_bin) {
if (round == 3) {
const auto pos = ::atomicAdd(&s_last_remain, -1);
if (pos > 0) {
output[topk - pos] = static_cast<int32_t>(idx);
}
} else {
const auto pos = ::atomicAdd(&s_num_input[r_idx ^ 1], 1);
if (C10_LIKELY(pos < SMEM_INPUT_SIZE)) {
s_input_idx[r_idx ^ 1][pos] = idx;
const auto bin32 = convert_to_uint32(raw_input);
const auto sub_bin = (bin32 >> (offset - 8)) & 0xFF;
::atomicAdd(&s_histogram[sub_bin], 1);
}
}
}
}
__syncthreads();
}
}
__global__ __launch_bounds__(kBlockSize) void deepseek_v4_topk_transform_kernel(const TopKParams params) {
const auto bid = blockIdx.x;
const auto seq_len = params.seq_lens[bid];
const auto topk = params.topk;
const auto score_ptr = params.scores + bid * params.score_stride;
const auto page_ptr = params.page_table + bid * params.page_table_stride;
const auto indices_ptr = params.page_indices + bid * params.output_stride;
const auto raw_indices_ptr =
params.raw_indices != nullptr ? params.raw_indices + bid * params.output_stride : nullptr;
if (seq_len <= static_cast<int32_t>(topk)) {
naive_paged_transform(seq_len, topk, params.page_bits, page_ptr, indices_ptr, raw_indices_ptr);
return;
}
__shared__ int32_t s_topk_indices[kMaxTopK];
radix_topk(score_ptr, s_topk_indices, static_cast<uint32_t>(seq_len), topk);
__syncthreads();
for (uint32_t i = threadIdx.x; i < topk; i += kBlockSize) {
const auto raw = s_topk_indices[i];
indices_ptr[i] = page_to_slot(page_ptr, static_cast<uint32_t>(raw), params.page_bits);
if (raw_indices_ptr != nullptr) {
raw_indices_ptr[i] = raw;
}
}
}
template <auto* f, size_t kMaxDynamicSMEM>
void setup_kernel_smem_once() {
[[maybe_unused]]
static const auto result = [] {
#ifdef USE_ROCM
return ::cudaFuncSetAttribute(
reinterpret_cast<const void*>(f), ::cudaFuncAttributeMaxDynamicSharedMemorySize, kMaxDynamicSMEM);
#else
return ::cudaFuncSetAttribute(f, ::cudaFuncAttributeMaxDynamicSharedMemorySize, kMaxDynamicSMEM);
#endif
}();
TORCH_CHECK(
result == cudaSuccess, "deepseek_v4_topk_transform: cudaFuncSetAttribute failed: ", ::cudaGetErrorString(result));
}
} // namespace
#define CHECK_CUDA(x) TORCH_CHECK(x.is_cuda(), #x " must be a CUDA tensor")
void deepseek_v4_topk_transform_512(
const at::Tensor& scores,
const at::Tensor& seq_lens,
const at::Tensor& page_table,
at::Tensor& page_indices,
int64_t page_size,
std::optional<at::Tensor> raw_indices_opt) {
CHECK_CUDA(scores);
CHECK_CUDA(seq_lens);
CHECK_CUDA(page_table);
CHECK_CUDA(page_indices);
if (raw_indices_opt.has_value()) {
CHECK_CUDA(raw_indices_opt.value());
}
TORCH_CHECK(
scores.dim() == 2 && scores.scalar_type() == at::kFloat, "scores must be float32 with shape [B, max_seq_len]");
TORCH_CHECK(scores.stride(1) == 1, "scores must be contiguous along the last dim");
TORCH_CHECK(
seq_lens.dim() == 1 && seq_lens.is_contiguous() && seq_lens.scalar_type() == at::kInt,
"seq_lens must be int32 with shape [B], contiguous");
TORCH_CHECK(
page_table.dim() == 2 && page_table.scalar_type() == at::kInt,
"page_table must be int32 with shape [B, num_pages]");
TORCH_CHECK(page_table.stride(1) == 1, "page_table must be contiguous along the last dim");
const auto topk = page_indices.size(1);
TORCH_CHECK(
page_indices.dim() == 2 && page_indices.is_contiguous() && page_indices.scalar_type() == at::kInt,
"page_indices must be int32 with shape [B, topk], contiguous");
TORCH_CHECK(
topk > 0 && topk <= static_cast<int64_t>(kMaxTopK),
"page_indices last dim must be in [1, ",
kMaxTopK,
"], got ",
topk);
const auto B = scores.size(0);
TORCH_CHECK(
seq_lens.size(0) == B && page_table.size(0) == B && page_indices.size(0) == B,
"batch sizes must match across scores, seq_lens, page_table, page_indices");
TORCH_CHECK(
page_size > 0 && (page_size & (page_size - 1)) == 0, "page_size must be a positive power of 2, got ", page_size);
const auto page_bits = static_cast<uint32_t>(__builtin_ctzll(static_cast<unsigned long long>(page_size)));
int32_t* raw_ptr = nullptr;
if (raw_indices_opt.has_value()) {
auto& raw = raw_indices_opt.value();
TORCH_CHECK(
raw.dim() == 2 && raw.is_contiguous() && raw.scalar_type() == at::kInt,
"raw_indices must be int32 with shape [B, topk], contiguous");
TORCH_CHECK(raw.size(0) == B && raw.size(1) == topk, "raw_indices shape must match page_indices [B, ", topk, "]");
raw_ptr = raw.data_ptr<int32_t>();
}
const TopKParams params{
.scores = scores.data_ptr<float>(),
.seq_lens = seq_lens.data_ptr<int32_t>(),
.page_table = page_table.data_ptr<int32_t>(),
.page_indices = page_indices.data_ptr<int32_t>(),
.raw_indices = raw_ptr,
.score_stride = scores.stride(0),
.page_table_stride = page_table.stride(0),
.page_bits = page_bits,
.topk = static_cast<uint32_t>(topk),
.output_stride = topk,
};
const auto stream = at::cuda::getCurrentCUDAStream().stream();
const dim3 grid(static_cast<uint32_t>(B));
const dim3 block(kBlockSize);
setup_kernel_smem_once<deepseek_v4_topk_transform_kernel, kSMEM>();
deepseek_v4_topk_transform_kernel<<<grid, block, kSMEM, stream>>>(params);
const auto err = cudaGetLastError();
TORCH_CHECK(err == cudaSuccess, "deepseek_v4_topk_transform kernel launch failed: ", ::cudaGetErrorString(err));
}
@@ -0,0 +1,700 @@
/* 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.
==============================================================================*/
// DeepSeek-V4 fused norm + RoPE kernels, ported from JIT kernel
// python/sglang/jit_kernel/csrc/deepseek_v4/main_norm_rope.cuh
// to sgl-kernel AOT compilation with CUDA + HIP (ROCm) support.
#ifndef USE_ROCM
#include <cuda_bf16.h>
#include <cuda_fp16.h>
#include <cuda_fp8.h>
#include <cuda_runtime.h>
#else
#include <hip/hip_bf16.h>
#include <hip/hip_fp16.h>
#include <hip/hip_runtime.h>
#endif
#include <ATen/cuda/CUDAContext.h>
#include <c10/cuda/CUDAGuard.h>
#include <torch/all.h>
#include <cstdint>
#include "utils.h"
// ============================================================================
// Platform-compatible type aliases
// ============================================================================
#ifndef USE_ROCM
using bf16_t = __nv_bfloat16;
using bf16x2_t = __nv_bfloat162;
using fp8x2_e4m3_t = __nv_fp8x2_e4m3;
#else
using bf16_t = __hip_bfloat16;
using bf16x2_t = __hip_bfloat162;
using fp8x2_e4m3_t = uint16_t;
#ifndef __grid_constant__
#define __grid_constant__
#endif
#endif
// ============================================================================
// Utility helpers (inlined, no external header dependency)
// ============================================================================
static constexpr uint32_t kWarpSize = 32;
template <uint32_t kNumThreads = kWarpSize>
__device__ __forceinline__ float warp_reduce_sum(float val) {
#pragma unroll
for (uint32_t mask = kNumThreads / 2; mask > 0; mask >>= 1)
val += SGLANG_SHFL_XOR_SYNC(FULL_MASK, val, mask);
return val;
}
__device__ __forceinline__ float warp_reduce_max(float val) {
#pragma unroll
for (uint32_t mask = kWarpSize / 2; mask > 0; mask >>= 1)
val = fmaxf(val, SGLANG_SHFL_XOR_SYNC(FULL_MASK, val, mask));
return val;
}
// Aligned vector for coalesced memory access.
template <typename T, int N>
struct alignas(sizeof(T) * N) AlignedVec {
T data[N];
__device__ __forceinline__ T& operator[](int i) {
return data[i];
}
__device__ __forceinline__ T operator[](int i) const {
return data[i];
}
__device__ __forceinline__ void load(const void* ptr, int64_t offset = 0) {
*this = reinterpret_cast<const AlignedVec*>(ptr)[offset];
}
__device__ __forceinline__ void store(void* ptr, int64_t offset = 0) const {
reinterpret_cast<AlignedVec*>(ptr)[offset] = *this;
}
};
__device__ __forceinline__ float bf16_to_float(bf16_t v) {
return __bfloat162float(v);
}
__device__ __forceinline__ bf16_t float_to_bf16(float v) {
#ifndef USE_ROCM
return __float2bfloat16_rn(v);
#else
return __float2bfloat16(v);
#endif
}
// ============================================================================
// FP8 E4M3 helpers (portable CUDA + HIP)
// ============================================================================
// UE8M0 scale: round a positive float to the nearest power-of-two
// representable in UE8M0 (unsigned 8-bit exponent, no mantissa).
__device__ __forceinline__ int32_t cast_to_ue8m0(float x) {
uint32_t u = __float_as_uint(x);
int32_t exp = static_cast<int32_t>((u >> 23) & 0xFFu);
uint32_t mant = u & 0x7FFFFFu;
return exp + (mant != 0);
}
__device__ __forceinline__ float inv_scale_ue8m0(int32_t exp) {
return __uint_as_float(static_cast<uint32_t>((127 + 127 - exp) << 23));
}
static constexpr float kFP8Max = 448.0f;
#ifndef USE_ROCM
__device__ __forceinline__ fp8x2_e4m3_t pack_fp8(float x, float y) {
x = fmaxf(fminf(x, kFP8Max), -kFP8Max);
y = fmaxf(fminf(y, kFP8Max), -kFP8Max);
return __nv_fp8x2_e4m3(float2{x, y});
}
#else
// Software float -> FP8 E4M3 conversion for ROCm
__device__ __forceinline__ uint8_t cvt_float_to_fp8_e4m3(float val) {
constexpr float kMax = kFP8Max;
val = fmaxf(fminf(val, kMax), -kMax);
if (val == 0.0f) return 0;
uint32_t f32 = __float_as_uint(val);
uint8_t sign = static_cast<uint8_t>((f32 >> 24) & 0x80u);
f32 &= 0x7FFFFFFFu;
int32_t exp32 = static_cast<int32_t>((f32 >> 23) & 0xFFu);
uint32_t mant32 = f32 & 0x7FFFFFu;
// FP8 E4M3 bias=7, FP32 bias=127, offset=120
int32_t exp8 = exp32 - 120;
if (exp8 <= 0) {
mant32 |= 0x800000u;
int32_t shift = 1 - exp8;
if (shift > 24) return sign;
uint32_t shifted = mant32 >> (20 + shift);
uint32_t rbit = (shift <= 23) ? ((mant32 >> (19 + shift)) & 1u) : 0u;
uint32_t sbit = (shift <= 23) ? ((mant32 & ((1u << (19 + shift)) - 1u)) != 0) : 0u;
shifted += (rbit && (sbit || (shifted & 1u)));
return sign | static_cast<uint8_t>(shifted & 0x7u);
}
if (exp8 >= 15) return sign | 0x7Eu;
uint32_t mant3 = (mant32 >> 20) & 0x7u;
uint32_t rbit = (mant32 >> 19) & 1u;
uint32_t sbit = (mant32 & 0x7FFFFu) != 0;
mant3 += (rbit && (sbit || (mant3 & 1u)));
if (mant3 > 7) {
mant3 = 0;
exp8++;
if (exp8 >= 15) return sign | 0x7Eu;
}
return sign | (static_cast<uint8_t>(exp8) << 3) | static_cast<uint8_t>(mant3);
}
__device__ __forceinline__ fp8x2_e4m3_t pack_fp8(float x, float y) {
uint8_t x8 = cvt_float_to_fp8_e4m3(x);
uint8_t y8 = cvt_float_to_fp8_e4m3(y);
return static_cast<uint16_t>(x8) | (static_cast<uint16_t>(y8) << 8);
}
#endif
// ============================================================================
// Kernel 1: Fused Q Norm + RoPE
// warp-per-(token, head), rmsnorm-self (no weight) + RoPE + write to q_out.
// ============================================================================
namespace {
constexpr uint32_t kFusedQBlockSize = 128;
constexpr uint32_t kFusedQNumWarps = kFusedQBlockSize / kWarpSize;
constexpr uint32_t kFusedKBlockSize = 256;
constexpr uint32_t kFusedKNumWarps = kFusedKBlockSize / kWarpSize;
struct FusedQNormRopeParams {
const void* __restrict__ q_input;
void* __restrict__ q_output;
const float* __restrict__ freqs_cis;
const int32_t* __restrict__ positions;
int64_t q_input_stride_batch;
int64_t q_output_stride_batch;
uint32_t batch_size;
uint32_t num_q_heads;
float eps;
};
// Compute the largest power-of-2 vec size that divides both kHeadDim and
// fits in 16 bytes, while also dividing kRopeDim.
template <int64_t kHeadDim, int64_t kRopeDim>
struct QKernelTraits {
static constexpr int64_t kMaxVecSize = 16 / sizeof(bf16_t); // 8
// Use kRopeDim/kWarpSize (=2 for kRopeDim=64) as the vec size.
// This guarantees kRopeDim % kVecSize == 0 and works for all head dims
// that are multiples of kWarpSize*kVecSize.
static constexpr int64_t kVecSize = kRopeDim / kWarpSize; // 2
static constexpr int64_t kLocalSize = kHeadDim / (kWarpSize * kVecSize);
static constexpr uint32_t kRopeSize = kRopeDim / kVecSize;
static_assert(kHeadDim % (kWarpSize * kVecSize) == 0);
static_assert(kRopeDim % kVecSize == 0);
static_assert(kRopeDim == kWarpSize * 2, "1 (real, imag) pair per lane");
};
template <int64_t kHeadDim, int64_t kRopeDim>
__global__ __launch_bounds__(kFusedQBlockSize, 16) void fused_q_norm_rope_kernel(
const __grid_constant__ FusedQNormRopeParams params) {
using Traits = QKernelTraits<kHeadDim, kRopeDim>;
constexpr int64_t kVecSize = Traits::kVecSize;
constexpr int64_t kLocalSize = Traits::kLocalSize;
constexpr uint32_t kRopeSize = Traits::kRopeSize;
using Storage = AlignedVec<bf16_t, kVecSize>;
using Float2 = AlignedVec<float, 2>;
const auto warp_id = threadIdx.x / kWarpSize;
const auto lane_id = threadIdx.x % kWarpSize;
const auto work_id = blockIdx.x * kFusedQNumWarps + warp_id;
const uint32_t total_works = params.batch_size * params.num_q_heads;
if (work_id >= total_works) return;
const uint32_t batch_id = work_id / params.num_q_heads;
const uint32_t head_id = work_id % params.num_q_heads;
const auto input_ptr =
static_cast<const bf16_t*>(params.q_input) + batch_id * params.q_input_stride_batch + head_id * kHeadDim;
const auto output_ptr =
static_cast<bf16_t*>(params.q_output) + batch_id * params.q_output_stride_batch + head_id * kHeadDim;
const auto position = params.positions[batch_id];
__shared__ Storage s_rope[kFusedQNumWarps][kRopeSize];
// Prefetch freq pair.
Float2 freq;
freq.load(params.freqs_cis + position * kRopeDim, lane_id);
// Part 1: rmsnorm-self (no weight).
Storage input_vec[kLocalSize];
#pragma unroll
for (int i = 0; i < kLocalSize; ++i) {
input_vec[i].load(input_ptr, lane_id + i * kWarpSize);
}
float sum_of_squares = 0.0f;
#pragma unroll
for (int i = 0; i < kLocalSize; ++i) {
#pragma unroll
for (int j = 0; j < kVecSize; ++j) {
float x = bf16_to_float(input_vec[i][j]);
sum_of_squares += x * x;
}
}
sum_of_squares = warp_reduce_sum(sum_of_squares);
const float norm_factor = rsqrtf(sum_of_squares / static_cast<float>(kHeadDim) + params.eps);
#pragma unroll
for (int i = 0; i < kLocalSize; ++i) {
#pragma unroll
for (int j = 0; j < kVecSize; ++j) {
float x = bf16_to_float(input_vec[i][j]);
input_vec[i][j] = float_to_bf16(x * norm_factor);
}
}
// Stash rope tail into shared memory; write nope tiles to gmem.
const bool is_rope_lane = lane_id >= kWarpSize - kRopeSize;
#pragma unroll
for (int i = 0; i < kLocalSize; ++i) {
if (i == kLocalSize - 1 && is_rope_lane) {
const auto rope_id = lane_id - (kWarpSize - kRopeSize);
s_rope[warp_id][rope_id] = input_vec[i];
} else {
input_vec[i].store(output_ptr, lane_id + i * kWarpSize);
}
}
__syncwarp();
// Part 2: RoPE on all 32 lanes -- one (real, imag) bf16x2 pair per lane.
auto elem_ptr = reinterpret_cast<bf16x2_t*>(&s_rope[warp_id][0]);
bf16x2_t elem = elem_ptr[lane_id];
#ifndef USE_ROCM
float2 elem_f = __bfloat1622float2(elem);
float x_real = elem_f.x, x_imag = elem_f.y;
#else
float x_real = __bfloat162float(elem.x), x_imag = __bfloat162float(elem.y);
#endif
float freq_real = freq[0], freq_imag = freq[1];
float rot_real = x_real * freq_real - x_imag * freq_imag;
float rot_imag = x_real * freq_imag + x_imag * freq_real;
bf16x2_t rotated = __float22bfloat162_rn(make_float2(rot_real, rot_imag));
auto out_elem = reinterpret_cast<bf16x2_t*>(output_ptr + (kHeadDim - kRopeDim));
out_elem[lane_id] = rotated;
}
// ============================================================================
// Kernel 2: Fused K Norm + RoPE + FlashMLA Store
// block-per-token, rmsnorm (with kv_weight) + RoPE + FP8 quantized store.
// ============================================================================
struct FusedKNormRopeFlashMLAParams {
const void* __restrict__ kv;
const void* __restrict__ kv_weight;
const float* __restrict__ freqs_cis;
const int32_t* __restrict__ positions;
const int32_t* __restrict__ out_loc;
uint8_t* __restrict__ kvcache;
int64_t kv_stride_batch;
uint32_t batch_size;
float eps;
};
template <int64_t kHeadDim, int64_t kRopeDim, int32_t kPageBits>
__global__ __launch_bounds__(kFusedKBlockSize, 8) void fused_k_norm_rope_flashmla_kernel(
const __grid_constant__ FusedKNormRopeFlashMLAParams params) {
constexpr int64_t kVecSize = 2;
constexpr uint32_t kRopeWarp = kFusedKNumWarps - 1;
constexpr int64_t kPageBytes = ((584ll << kPageBits) + 575) / 576 * 576;
static_assert(kHeadDim == kFusedKBlockSize * kVecSize);
static_assert(kRopeDim == kWarpSize * kVecSize);
using Storage = AlignedVec<bf16_t, kVecSize>;
const auto tx = threadIdx.x;
const auto warp_id = tx / kWarpSize;
const auto lane_id = tx % kWarpSize;
const auto work_id = blockIdx.x;
if (work_id >= params.batch_size) return;
const auto input_ptr = static_cast<const bf16_t*>(params.kv) + work_id * params.kv_stride_batch;
const auto position = params.positions[work_id];
const auto out_loc = params.out_loc[work_id];
const auto freqs_cis = params.freqs_cis + position * kRopeDim;
AlignedVec<float, kVecSize> data, freq;
// Part 1: norm with block-wide reduction.
{
__shared__ float partial_sums[kFusedKNumWarps];
Storage input_vec, weight_vec;
input_vec.load(input_ptr, tx);
weight_vec.load(params.kv_weight, tx);
if (warp_id == kRopeWarp) freq.load(freqs_cis, lane_id);
float sum_of_squares = 0.0f;
#pragma unroll
for (int i = 0; i < kVecSize; ++i) {
float x = bf16_to_float(input_vec[i]);
sum_of_squares += x * x;
}
const float warp_sum = warp_reduce_sum(sum_of_squares);
if (lane_id == 0) partial_sums[warp_id] = warp_sum;
__syncthreads();
sum_of_squares = warp_reduce_sum<kFusedKNumWarps>(partial_sums[lane_id % kFusedKNumWarps]);
const float norm_factor = rsqrtf(sum_of_squares / static_cast<float>(kHeadDim) + params.eps);
#pragma unroll
for (int i = 0; i < kVecSize; ++i) {
float x = bf16_to_float(input_vec[i]);
float w = bf16_to_float(weight_vec[i]);
data[i] = x * norm_factor * w;
}
}
const int32_t page = out_loc >> kPageBits;
const int32_t offset = out_loc & ((1 << kPageBits) - 1);
const auto page_ptr = params.kvcache + page * kPageBytes;
const auto value_ptr = page_ptr + offset * 576;
// Part 2: rope on last warp (BF16 store), per-warp UE8M0 quant + store on others.
if (warp_id == kRopeWarp) {
float x_real = data[0], x_imag = data[1];
float freq_real = freq[0], freq_imag = freq[1];
float rot_real = x_real * freq_real - x_imag * freq_imag;
float rot_imag = x_real * freq_imag + x_imag * freq_real;
bf16x2_t result = __float22bfloat162_rn(make_float2(rot_real, rot_imag));
auto rope_ptr = value_ptr + 448;
reinterpret_cast<bf16x2_t*>(rope_ptr)[lane_id] = result;
} else {
float x = data[0], y = data[1];
float abs_max = warp_reduce_max(fmaxf(fabsf(x), fabsf(y)));
float scale_raw = fmaxf(1e-4f, abs_max) / kFP8Max;
int32_t scale_ue8m0 = cast_to_ue8m0(scale_raw);
float inv_scale = inv_scale_ue8m0(scale_ue8m0);
fp8x2_e4m3_t result = pack_fp8(x * inv_scale, y * inv_scale);
auto scale_ptr = page_ptr + (576ll << kPageBits) + offset * 8;
reinterpret_cast<fp8x2_e4m3_t*>(value_ptr)[tx] = result;
if (lane_id == 0) static_cast<uint8_t*>(scale_ptr)[warp_id] = static_cast<uint8_t>(scale_ue8m0);
}
}
// ============================================================================
// Kernel 3: Fused Q Indexer RoPE + Hadamard + FP8 Quantization
// warp-per-(token, head), no norm, RoPE + Hadamard + fp8 act-quant.
// ============================================================================
struct FusedQIndexerRopeHadamardQuantParams {
const void* __restrict__ q_input;
void* __restrict__ q_fp8;
const void* __restrict__ weight;
float* __restrict__ weights_out;
float weight_scale;
const float* __restrict__ freqs_cis;
const int32_t* __restrict__ positions;
uint32_t batch_size;
uint32_t num_heads;
};
__global__ __launch_bounds__(kFusedQBlockSize, 16) void fused_q_indexer_rope_hadamard_quant_kernel(
const __grid_constant__ FusedQIndexerRopeHadamardQuantParams params) {
constexpr int64_t kHeadDim = 128;
constexpr int64_t kRopeDim = 64;
constexpr int64_t kVecSize = 4;
constexpr uint32_t kRopeSize = kRopeDim / kVecSize;
static_assert(kHeadDim == kWarpSize * kVecSize);
using Storage = AlignedVec<bf16_t, kVecSize>;
using Float4 = AlignedVec<float, kVecSize>;
using OutStorage = AlignedVec<fp8x2_e4m3_t, 2>;
const auto warp_id = threadIdx.x / kWarpSize;
const auto lane_id = threadIdx.x % kWarpSize;
const auto work_id = blockIdx.x * kFusedQNumWarps + warp_id;
const bool is_rope_lane = lane_id >= kWarpSize - kRopeSize;
const uint32_t total_works = params.batch_size * params.num_heads;
if (work_id >= total_works) return;
const uint32_t batch_id = work_id / params.num_heads;
const auto input_ptr = static_cast<const bf16_t*>(params.q_input) + work_id * kHeadDim;
const auto position = params.positions[batch_id];
const auto freqs_cis = params.freqs_cis + position * kRopeDim;
Float4 data, freq;
const float weight_val = bf16_to_float(static_cast<const bf16_t*>(params.weight)[work_id]);
// Part 1: load (no norm).
{
Storage input_vec;
input_vec.load(input_ptr, lane_id);
if (is_rope_lane) freq.load(freqs_cis, lane_id - (kWarpSize - kRopeSize));
#pragma unroll
for (int i = 0; i < kVecSize; ++i)
data[i] = bf16_to_float(input_vec[i]);
}
// Part 2: rope on rope lanes.
if (is_rope_lane) {
float x_r = data[0], x_i = data[1], y_r = data[2], y_i = data[3];
float fxr = freq[0], fxi = freq[1], fyr = freq[2], fyi = freq[3];
data[0] = x_r * fxr - x_i * fxi;
data[1] = x_r * fxi + x_i * fxr;
data[2] = y_r * fyr - y_i * fyi;
data[3] = y_r * fyi + y_i * fyr;
}
// Part 3: 128-point Hadamard (2 local + 5 cross-lane stages).
{
{
float a0 = data[0], a1 = data[1], a2 = data[2], a3 = data[3];
data[0] = a0 + a1;
data[1] = a0 - a1;
data[2] = a2 + a3;
data[3] = a2 - a3;
}
{
float a0 = data[0], a1 = data[1], a2 = data[2], a3 = data[3];
data[0] = a0 + a2;
data[1] = a1 + a3;
data[2] = a0 - a2;
data[3] = a1 - a3;
}
#pragma unroll
for (uint32_t mask = 1; mask < kWarpSize; mask <<= 1) {
#pragma unroll
for (int i = 0; i < kVecSize; ++i) {
float other = SGLANG_SHFL_XOR_SYNC_WIDTH(FULL_MASK, data[i], mask, kWarpSize);
data[i] = (lane_id & mask) ? (other - data[i]) : (data[i] + other);
}
}
const float kHadamardScale = rsqrtf(static_cast<float>(kHeadDim));
#pragma unroll
for (int i = 0; i < kVecSize; ++i)
data[i] *= kHadamardScale;
}
// Part 4: per-warp FP8 quant + store.
{
float local_max = fabsf(data[0]);
#pragma unroll
for (int i = 1; i < kVecSize; ++i)
local_max = fmaxf(local_max, fabsf(data[i]));
float abs_max = warp_reduce_max(local_max);
float scale = fmaxf(1e-4f, abs_max) / kFP8Max;
float inv_scale = 1.0f / scale;
OutStorage result;
result[0] = pack_fp8(data[0] * inv_scale, data[1] * inv_scale);
result[1] = pack_fp8(data[2] * inv_scale, data[3] * inv_scale);
auto out_row = static_cast<uint8_t*>(params.q_fp8) + work_id * kHeadDim;
result.store(out_row, lane_id);
params.weights_out[work_id] = weight_val * params.weight_scale * scale;
}
}
} // anonymous namespace
// ============================================================================
// Host-side launchers (PyTorch C++ extension API)
// ============================================================================
void dsv4_fused_q_norm_rope(
const at::Tensor& q_input,
at::Tensor& q_output,
const at::Tensor& freqs_cis,
const at::Tensor& positions,
double eps) {
TORCH_CHECK(q_input.is_cuda(), "q_input must be a CUDA tensor");
TORCH_CHECK(q_output.is_cuda(), "q_output must be a CUDA tensor");
TORCH_CHECK(q_input.scalar_type() == at::ScalarType::BFloat16, "q_input must be bfloat16");
TORCH_CHECK(q_output.scalar_type() == at::ScalarType::BFloat16, "q_output must be bfloat16");
TORCH_CHECK(q_input.dim() == 3, "q_input must be 3D: (B, H, D)");
TORCH_CHECK(q_output.dim() == 3, "q_output must be 3D: (B, H, D)");
TORCH_CHECK(positions.scalar_type() == at::ScalarType::Int, "positions must be int32");
const int64_t B = q_input.size(0);
const int64_t H = q_input.size(1);
const int64_t D = q_input.size(2);
TORCH_CHECK(
q_output.size(0) == B && q_output.size(1) == H && q_output.size(2) == D, "q_output shape must match q_input");
TORCH_CHECK(q_input.stride(2) == 1 && q_output.stride(2) == 1, "last dim must be contiguous");
TORCH_CHECK(q_input.stride(1) == D && q_output.stride(1) == D, "head dim must be contiguous");
if (B == 0) return;
const auto stream = at::cuda::getCurrentCUDAStream(q_input.get_device());
const auto params = FusedQNormRopeParams{
.q_input = q_input.data_ptr(),
.q_output = q_output.data_ptr(),
.freqs_cis = freqs_cis.data_ptr<float>(),
.positions = positions.data_ptr<int32_t>(),
.q_input_stride_batch = q_input.stride(0),
.q_output_stride_batch = q_output.stride(0),
.batch_size = static_cast<uint32_t>(B),
.num_q_heads = static_cast<uint32_t>(H),
.eps = static_cast<float>(eps),
};
const uint32_t total_works = static_cast<uint32_t>(B * H);
const uint32_t num_blocks = CEILDIV(total_works, kFusedQNumWarps);
// Dispatch on head_dim. DeepSeek V4 uses D=192 with kRopeDim=64.
constexpr int64_t kRopeDim = 64;
switch (D) {
case 128:
fused_q_norm_rope_kernel<128, kRopeDim><<<num_blocks, kFusedQBlockSize, 0, stream>>>(params);
break;
case 192:
fused_q_norm_rope_kernel<192, kRopeDim><<<num_blocks, kFusedQBlockSize, 0, stream>>>(params);
break;
default:
TORCH_CHECK(false, "Unsupported head_dim for dsv4_fused_q_norm_rope: ", D);
}
}
void dsv4_fused_k_norm_rope_flashmla(
const at::Tensor& kv,
const at::Tensor& kv_weight,
const at::Tensor& freqs_cis,
const at::Tensor& positions,
const at::Tensor& out_loc,
at::Tensor& kvcache,
double eps,
int64_t page_size) {
TORCH_CHECK(kv.is_cuda(), "kv must be a CUDA tensor");
TORCH_CHECK(kv.scalar_type() == at::ScalarType::BFloat16, "kv must be bfloat16");
TORCH_CHECK(kv.dim() == 2, "kv must be 2D: (B, D)");
TORCH_CHECK(positions.scalar_type() == at::ScalarType::Int, "positions must be int32");
TORCH_CHECK(out_loc.scalar_type() == at::ScalarType::Int, "out_loc must be int32");
const int64_t B = kv.size(0);
const int64_t D = kv.size(1);
TORCH_CHECK(D == 512, "kv head_dim must be 512 for FlashMLA");
TORCH_CHECK(kv_weight.size(0) == D, "kv_weight size must match head_dim");
if (B == 0) return;
const auto stream = at::cuda::getCurrentCUDAStream(kv.get_device());
const auto params = FusedKNormRopeFlashMLAParams{
.kv = kv.data_ptr(),
.kv_weight = kv_weight.data_ptr(),
.freqs_cis = freqs_cis.data_ptr<float>(),
.positions = positions.data_ptr<int32_t>(),
.out_loc = out_loc.data_ptr<int32_t>(),
.kvcache = static_cast<uint8_t*>(kvcache.data_ptr()),
.kv_stride_batch = kv.stride(0),
.batch_size = static_cast<uint32_t>(B),
.eps = static_cast<float>(eps),
};
constexpr int64_t kHeadDim = 512;
constexpr int64_t kRopeDim = 64;
// Dispatch on page_size (must be power of 2).
TORCH_CHECK(page_size > 0 && (page_size & (page_size - 1)) == 0, "page_size must be a power of 2");
#define LAUNCH_K_KERNEL(PAGE_BITS) \
fused_k_norm_rope_flashmla_kernel<kHeadDim, kRopeDim, PAGE_BITS> \
<<<static_cast<uint32_t>(B), kFusedKBlockSize, 0, stream>>>(params)
switch (page_size) {
case 1:
LAUNCH_K_KERNEL(0);
break;
case 2:
LAUNCH_K_KERNEL(1);
break;
case 4:
LAUNCH_K_KERNEL(2);
break;
case 8:
LAUNCH_K_KERNEL(3);
break;
case 16:
LAUNCH_K_KERNEL(4);
break;
case 32:
LAUNCH_K_KERNEL(5);
break;
case 64:
LAUNCH_K_KERNEL(6);
break;
case 128:
LAUNCH_K_KERNEL(7);
break;
case 256:
LAUNCH_K_KERNEL(8);
break;
default:
TORCH_CHECK(false, "Unsupported page_size: ", page_size);
}
#undef LAUNCH_K_KERNEL
}
void dsv4_fused_q_indexer_rope_hadamard_quant(
const at::Tensor& q_input,
at::Tensor& q_fp8,
const at::Tensor& weight,
at::Tensor& weights_out,
double weight_scale,
const at::Tensor& freqs_cis,
const at::Tensor& positions) {
TORCH_CHECK(q_input.is_cuda(), "q_input must be a CUDA tensor");
TORCH_CHECK(q_input.scalar_type() == at::ScalarType::BFloat16, "q_input must be bfloat16");
TORCH_CHECK(q_input.dim() == 3, "q_input must be 3D: (B, H, D)");
const int64_t B = q_input.size(0);
const int64_t H = q_input.size(1);
constexpr int64_t kHeadDim = 128;
TORCH_CHECK(q_input.size(2) == kHeadDim, "q_input head_dim must be 128 for indexer");
TORCH_CHECK(
q_input.stride(2) == 1 && q_input.stride(1) == kHeadDim, "q_input must be contiguous in (head, elem) dims");
TORCH_CHECK(q_input.stride(0) == H * kHeadDim, "q_input must be contiguous (B, H, D)");
TORCH_CHECK(q_fp8.stride(0) == H * kHeadDim, "q_fp8 must be contiguous (B, H, D)");
TORCH_CHECK(positions.scalar_type() == at::ScalarType::Int, "positions must be int32");
if (B == 0) return;
const auto stream = at::cuda::getCurrentCUDAStream(q_input.get_device());
const auto params = FusedQIndexerRopeHadamardQuantParams{
.q_input = q_input.data_ptr(),
.q_fp8 = q_fp8.data_ptr(),
.weight = weight.data_ptr(),
.weights_out = weights_out.data_ptr<float>(),
.weight_scale = static_cast<float>(weight_scale),
.freqs_cis = freqs_cis.data_ptr<float>(),
.positions = positions.data_ptr<int32_t>(),
.batch_size = static_cast<uint32_t>(B),
.num_heads = static_cast<uint32_t>(H),
};
const uint32_t total_works = static_cast<uint32_t>(B * H);
const uint32_t num_blocks = CEILDIV(total_works, kFusedQNumWarps);
fused_q_indexer_rope_hadamard_quant_kernel<<<num_blocks, kFusedQBlockSize, 0, stream>>>(params);
}
+37
View File
@@ -172,8 +172,45 @@ void fast_topk_transform_ragged_interface(
#ifdef USE_ROCM
void gelu_quick(at::Tensor& out, const at::Tensor& input);
void deepseek_v4_topk_transform_512(
const at::Tensor& scores,
const at::Tensor& seq_lens,
const at::Tensor& page_table,
at::Tensor& page_indices,
int64_t page_size,
std::optional<at::Tensor> raw_indices_opt = std::nullopt);
#endif
/*
* From csrc/elementwise (DeepSeek-V4 norm + rope)
*/
void dsv4_fused_q_norm_rope(
const at::Tensor& q_input,
at::Tensor& q_output,
const at::Tensor& freqs_cis,
const at::Tensor& positions,
double eps);
void dsv4_fused_k_norm_rope_flashmla(
const at::Tensor& kv,
const at::Tensor& kv_weight,
const at::Tensor& freqs_cis,
const at::Tensor& positions,
const at::Tensor& out_loc,
at::Tensor& kvcache,
double eps,
int64_t page_size);
void dsv4_fused_q_indexer_rope_hadamard_quant(
const at::Tensor& q_input,
at::Tensor& q_fp8,
const at::Tensor& weight,
at::Tensor& weights_out,
double weight_scale,
const at::Tensor& freqs_cis,
const at::Tensor& positions);
/*
* From csrc/gemm
*/
@@ -324,6 +324,85 @@ if torch.version.hip is not None:
return out
def dsv4_fused_q_norm_rope(
q_input: torch.Tensor,
freqs_cis: torch.Tensor,
positions: torch.Tensor,
eps: float = 1e-6,
q_output: Optional[torch.Tensor] = None,
) -> torch.Tensor:
"""DeepSeek-V4 fused Q RMSNorm (no weight) + RoPE.
Parameters
----------
q_input : (B, num_q_heads, head_dim) bfloat16
freqs_cis: (max_pos, rope_dim) float32, re/im interleaved
positions: (B,) int32
eps : RMSNorm epsilon
q_output : optional pre-allocated output tensor
"""
if q_output is None:
q_output = torch.empty_like(q_input)
torch.ops.sgl_kernel.dsv4_fused_q_norm_rope.default(
q_input, q_output, freqs_cis, positions, eps
)
return q_output
def dsv4_fused_k_norm_rope_flashmla(
kv: torch.Tensor,
kv_weight: torch.Tensor,
freqs_cis: torch.Tensor,
positions: torch.Tensor,
out_loc: torch.Tensor,
kvcache: torch.Tensor,
eps: float = 1e-6,
page_size: int = 1,
) -> None:
"""DeepSeek-V4 fused K RMSNorm + RoPE + FlashMLA FP8 store.
Parameters
----------
kv : (B, 512) bfloat16
kv_weight: (512,) bfloat16
freqs_cis: (max_pos, 64) float32
positions: (B,) int32
out_loc : (B,) int32 cache slot ids
kvcache : (npages, page_bytes) uint8
eps : RMSNorm epsilon
page_size: page size (power of 2)
"""
torch.ops.sgl_kernel.dsv4_fused_k_norm_rope_flashmla.default(
kv, kv_weight, freqs_cis, positions, out_loc, kvcache, eps, page_size
)
def dsv4_fused_q_indexer_rope_hadamard_quant(
q_input: torch.Tensor,
q_fp8: torch.Tensor,
weight: torch.Tensor,
weights_out: torch.Tensor,
weight_scale: float,
freqs_cis: torch.Tensor,
positions: torch.Tensor,
) -> None:
"""DeepSeek-V4 fused Q indexer: RoPE + Hadamard + FP8 quant.
Parameters
----------
q_input : (B, num_heads, 128) bfloat16
q_fp8 : (B, num_heads, 128) fp8_e4m3 output
weight : (B, num_heads) bfloat16
weights_out: (B, num_heads, 1) float32 output
weight_scale: scalar
freqs_cis : (max_pos, 64) float32
positions : (B,) int32
"""
torch.ops.sgl_kernel.dsv4_fused_q_indexer_rope_hadamard_quant.default(
q_input, q_fp8, weight, weights_out, weight_scale, freqs_cis, positions
)
def rotary_embedding(
positions: torch.Tensor,
query: torch.Tensor,
+32
View File
@@ -80,6 +80,38 @@ def fast_topk_transform_fused(
return dst_page_table
def deepseek_v4_topk_transform_512(
scores: torch.Tensor,
seq_lens: torch.Tensor,
page_table: torch.Tensor,
page_indices: torch.Tensor,
page_size: int,
raw_indices: Optional[torch.Tensor] = None,
) -> None:
"""
Performs the DeepSeek-V4 indexer top-k selection and writes the paged
physical slot indices into ``page_indices``. Supports topk up to 1024.
Optionally also writes the row-relative raw token positions into
``raw_indices`` for hisparse capture.
Args:
scores: float32 ``[B, max_seq_len]`` indexer logits, contiguous on dim 1.
seq_lens: int32 ``[B]``, true KV length per batch row.
page_table: int32 ``[B, num_pages]``, logical->physical page table,
contiguous on dim 1.
page_indices: int32 ``[B, topk]``, output buffer, contiguous. Filled
with paged physical slots; -1 for padding entries.
page_size: power-of-2 page size.
raw_indices: optional int32 ``[B, topk]``, contiguous. If provided,
filled with raw token positions within each row.
"""
if raw_indices is not None:
assert raw_indices.dim() == 2
torch.ops.sgl_kernel.deepseek_v4_topk_transform_512(
scores, seq_lens, page_table, page_indices, page_size, raw_indices
)
def fast_topk_transform_ragged_fused(
score: torch.Tensor,
lengths: torch.Tensor,
+2
View File
@@ -46,6 +46,8 @@ sources = [
"csrc/allreduce/quick_all_reduce.cu",
"csrc/common_extension_rocm.cc",
"csrc/elementwise/activation.cu",
"csrc/elementwise/deepseek_v4_topk.cu",
"csrc/elementwise/dsv4_norm_rope.cu",
"csrc/elementwise/topk.cu",
"csrc/grammar/apply_token_bitmask_inplace_cuda.cu",
"csrc/moe/moe_align_kernel.cu",