File size: 5,918 Bytes
c41750d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 | #include "smallm_gemv.h"
#include <ATen/cuda/CUDAContext.h>
#include <c10/cuda/CUDAGuard.h>
#include <cuda_bf16.h>
#include <cuda_fp8.h>
#include <cuda_runtime.h>
#include <cstdint>
#include <limits>
#include <optional>
#include <vector>
namespace {
constexpr int kWarpSize = 32;
constexpr int kWarpsPerBlock = 8;
constexpr int kThreads = kWarpSize * kWarpsPerBlock;
constexpr int kFp4BlockElements = 16;
constexpr int kScaleTileOuter = 128;
__device__ __forceinline__ size_t scale_offset(
int outer,
int inner_scale,
int scale_inner_dim) {
const int outer_tile = outer / kScaleTileOuter;
const int local_outer = outer % kScaleTileOuter;
const int local_inner = inner_scale & 3;
const int inner_tile_start = inner_scale - local_inner;
const size_t tile_base =
static_cast<size_t>(
inner_tile_start + outer_tile * scale_inner_dim) *
kScaleTileOuter;
return tile_base + static_cast<size_t>(local_outer & 31) * 16 +
static_cast<size_t>(local_outer >> 5) * 4 + local_inner;
}
__device__ __forceinline__ float e4m3_to_float(uint8_t raw) {
const __half_raw half_raw = __nv_cvt_fp8_to_halfraw(raw, __NV_E4M3);
return __half2float(static_cast<__half>(half_raw));
}
__device__ __forceinline__ float2 e2m1x2_to_float2(uint8_t packed) {
const __half2_raw raw =
__nv_cvt_fp4x2_to_halfraw2(packed, __NV_E2M1);
const __half2 converted(raw);
return __half22float2(converted);
}
__global__ void smallm_nvfp4_gemv_kernel(
const __nv_bfloat16* __restrict__ input,
const uint8_t* __restrict__ packed_weight,
const uint8_t* __restrict__ weight_block_scales,
const float* __restrict__ weight_tensor_scale,
const __nv_bfloat16* __restrict__ bias,
__nv_bfloat16* __restrict__ output,
int m,
int n,
int k,
int scale_inner_dim) {
const int lane = threadIdx.x & (kWarpSize - 1);
const int warp_in_block = threadIdx.x / kWarpSize;
const int64_t output_linear =
static_cast<int64_t>(blockIdx.x) * kWarpsPerBlock + warp_in_block;
const int64_t output_count = static_cast<int64_t>(m) * n;
if (output_linear >= output_count) {
return;
}
const int row_m = static_cast<int>(output_linear / n);
const int row_n = static_cast<int>(
output_linear - static_cast<int64_t>(row_m) * n);
const int packed_k = k / 2;
const __nv_bfloat16* input_row =
input + static_cast<int64_t>(row_m) * k;
const uint8_t* weight_row =
packed_weight + static_cast<int64_t>(row_n) * packed_k;
float accumulator = 0.0f;
for (int pair = lane; pair < packed_k; pair += kWarpSize) {
const uint8_t packed = weight_row[pair];
const int scale_block = pair / (kFp4BlockElements / 2);
const uint8_t scale_raw = weight_block_scales[
scale_offset(row_n, scale_block, scale_inner_dim)];
const float scale =
e4m3_to_float(scale_raw) * weight_tensor_scale[0];
const float2 weights = e2m1x2_to_float2(packed);
const int input_index = pair * 2;
accumulator = fmaf(
__bfloat162float(input_row[input_index]),
weights.y * scale,
accumulator);
accumulator = fmaf(
__bfloat162float(input_row[input_index + 1]),
weights.x * scale,
accumulator);
}
#pragma unroll
for (int offset = 16; offset > 0; offset >>= 1) {
accumulator += __shfl_down_sync(0xFFFFFFFF, accumulator, offset);
}
if (lane == 0) {
if (bias != nullptr) {
accumulator += __bfloat162float(bias[row_n]);
}
output[output_linear] = __float2bfloat16_rn(accumulator);
}
}
} // namespace
torch::Tensor smallm_nvfp4_linear_cuda(
const torch::Tensor& input,
const torch::Tensor& packed_weight,
const torch::Tensor& weight_block_scales,
const torch::Tensor& weight_tensor_scale,
const std::optional<torch::Tensor>& bias) {
const auto device = input.device();
c10::cuda::CUDAGuard guard(device);
const int64_t out_features64 = packed_weight.size(0);
const int64_t in_features64 = packed_weight.size(1) * 2;
const int64_t logical_m64 = input.numel() / in_features64;
TORCH_CHECK(
logical_m64 > 0 &&
logical_m64 <= static_cast<int64_t>(std::numeric_limits<int>::max()),
"small-M GEMV M is out of range");
TORCH_CHECK(
out_features64 <= static_cast<int64_t>(std::numeric_limits<int>::max()) &&
in_features64 <= static_cast<int64_t>(std::numeric_limits<int>::max()),
"small-M GEMV N or K is out of range");
const int m = static_cast<int>(logical_m64);
const int n = static_cast<int>(out_features64);
const int k = static_cast<int>(in_features64);
std::vector<int64_t> output_shape = input.sizes().vec();
output_shape.back() = out_features64;
torch::Tensor output = torch::empty(
output_shape,
input.options().dtype(at::kBFloat16));
const int64_t output_count = logical_m64 * out_features64;
const int64_t block_count64 =
(output_count + kWarpsPerBlock - 1) / kWarpsPerBlock;
TORCH_CHECK(
block_count64 <= static_cast<int64_t>(std::numeric_limits<int>::max()),
"small-M GEMV grid is too large");
const auto stream =
at::cuda::getCurrentCUDAStream(device.index()).stream();
const __nv_bfloat16* bias_pointer =
bias.has_value()
? reinterpret_cast<const __nv_bfloat16*>(bias->data_ptr())
: nullptr;
smallm_nvfp4_gemv_kernel<<<
static_cast<int>(block_count64),
kThreads,
0,
stream>>>(
reinterpret_cast<const __nv_bfloat16*>(input.data_ptr()),
reinterpret_cast<const uint8_t*>(packed_weight.data_ptr()),
reinterpret_cast<const uint8_t*>(weight_block_scales.data_ptr()),
reinterpret_cast<const float*>(weight_tensor_scale.data_ptr()),
bias_pointer,
reinterpret_cast<__nv_bfloat16*>(output.data_ptr()),
m,
n,
k,
static_cast<int>(weight_block_scales.size(1)));
C10_CUDA_KERNEL_LAUNCH_CHECK();
return output;
}
|