#include "smallm_gemv.h" #include #include #include #include #include #include #include #include #include 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( inner_tile_start + outer_tile * scale_inner_dim) * kScaleTileOuter; return tile_base + static_cast(local_outer & 31) * 16 + static_cast(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(blockIdx.x) * kWarpsPerBlock + warp_in_block; const int64_t output_count = static_cast(m) * n; if (output_linear >= output_count) { return; } const int row_m = static_cast(output_linear / n); const int row_n = static_cast( output_linear - static_cast(row_m) * n); const int packed_k = k / 2; const __nv_bfloat16* input_row = input + static_cast(row_m) * k; const uint8_t* weight_row = packed_weight + static_cast(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& 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(std::numeric_limits::max()), "small-M GEMV M is out of range"); TORCH_CHECK( out_features64 <= static_cast(std::numeric_limits::max()) && in_features64 <= static_cast(std::numeric_limits::max()), "small-M GEMV N or K is out of range"); const int m = static_cast(logical_m64); const int n = static_cast(out_features64); const int k = static_cast(in_features64); std::vector 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(std::numeric_limits::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(bias->data_ptr()) : nullptr; smallm_nvfp4_gemv_kernel<<< static_cast(block_count64), kThreads, 0, stream>>>( reinterpret_cast(input.data_ptr()), reinterpret_cast(packed_weight.data_ptr()), reinterpret_cast(weight_block_scales.data_ptr()), reinterpret_cast(weight_tensor_scale.data_ptr()), bias_pointer, reinterpret_cast<__nv_bfloat16*>(output.data_ptr()), m, n, k, static_cast(weight_block_scales.size(1))); C10_CUDA_KERNEL_LAUNCH_CHECK(); return output; }