// Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // 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 "paddle/phi/kernels/legacy/gpu/fp8_quant_blockwise_kernel.h" #include #include #include #include "paddle/common/flags.h" #include "paddle/phi/core/dense_tensor.h" #include "paddle/phi/core/utils/data_type.h" #include "paddle/phi/backends/gpu/gpu_context.h" #include "paddle/phi/backends/gpu/gpu_launch_config.h" #include "paddle/phi/core/kernel_registry.h" #include "paddle/phi/kernels/fusion/gpu/quant_utils.h" #include "paddle/phi/kernels/funcs/aligned_vector.h" namespace phi { constexpr size_t k_block_span = 128; constexpr size_t k_threads_per_warp = 32; constexpr size_t k_warp_xdim_size = 64; constexpr size_t k_warp_ydim_size = 32; constexpr size_t k_thread_dim_size = 8; template struct CudaTypeTraits { using Type = T; }; template <> struct CudaTypeTraits { using Type = __nv_bfloat16; }; template <> struct CudaTypeTraits { using Type = __half; }; template struct alignas(16) v128_t { union data_t { T scalar[8]; uint4 vector; // 128-bit vector for 8x bfloat16 }; data_t data; __device__ __forceinline__ void load(const void *ptr) { data.vector = *reinterpret_cast(ptr); } __device__ __forceinline__ void store(void *ptr) const { *reinterpret_cast(ptr) = data.vector; } }; template struct alignas(8) v64_t { T val[4]; }; typedef struct alignas(16) fp8x8_t { union data_t { __nv_fp8_e4m3 scalar[8]; uint2 vector; }; data_t data; __device__ __forceinline__ void load(const void *ptr) { data.vector = *reinterpret_cast(ptr); } __device__ __forceinline__ void store(void *ptr) const { *reinterpret_cast(ptr) = data.vector; } } fp8x8_t; struct alignas(4) fp8x4_t { __nv_fp8_e4m3 val[4]; }; __device__ __forceinline__ __half device_abs(__half x) { #if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 800) return __habs(x); #else return __float2half(fabsf(__half2float(x))); #endif } __device__ __forceinline__ __nv_bfloat16 device_abs(__nv_bfloat16 x) { #if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 800) return __habs(x); #else return __float2bfloat16(fabsf(__bfloat162float(x))); #endif } __device__ __forceinline__ float device_abs(float x) { return fabsf(x); } __device__ __forceinline__ __half device_max(__half a, __half b) { #if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 800) return __hmax(a, b); #else return __float2half(fmaxf(__half2float(a), __half2float(b))); #endif } __device__ __forceinline__ __nv_bfloat16 device_max(__nv_bfloat16 a, __nv_bfloat16 b) { #if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 800) return __hmax(a, b); #else return __float2bfloat16(fmaxf(__bfloat162float(a), __bfloat162float(b))); #endif } __device__ __forceinline__ float device_max(float a, float b) { return fmaxf(a, b); } template __device__ __forceinline__ float ScaleWrapperImpl(const float amax, const float eps) { constexpr float fp8_max = 448.0f; float amax_mod = fmaxf(amax, eps); if (amax_mod == 0.f) { return 1.f; } float scale = fp8_max / amax_mod; if (isinf(scale)) { if constexpr (Power2Scaling) { return 0x1.0p127; } else { return 0x1.FEp127; } } if (scale == 0.0) { return scale; } if constexpr (Power2Scaling) { uint32_t scale_bits = *reinterpret_cast(&scale); uint8_t exp = scale_bits >> 23; int32_t normal_biased_exp = static_cast(exp) - 127; __builtin_assume(exp != 0); scale = ldexpf(1.0f, normal_biased_exp); } return scale; } template __device__ __forceinline__ float ScaleWrapper(const float amax, const float eps) { return RoundPower2Scale( ScaleWrapperImpl(amax, eps)); } template __device__ __forceinline__ void StoreScale(ScaleT *ptr, size_t idx, float val) { if constexpr (using_ue8m0_scale) { int exp = (__float_as_int(val) >> 23) & 0xFF; reinterpret_cast(ptr)[idx] = static_cast(exp); } else { ptr[idx] = val; } } template __global__ void __launch_bounds__(256) quantize_128x128_kernel(const T *const input, __nv_fp8_e4m3 *const output, __nv_fp8_e4m3 *const output_transposed, ScaleT *const scale, ScaleT *const scale_transposed, const size_t cols, const size_t rows, const size_t quanted_cols, const size_t quanted_rows, const float epsilon) { const size_t col_blocks = cols / k_block_span; const size_t block_x = blockIdx.x % col_blocks; const size_t block_y = blockIdx.x / col_blocks; constexpr size_t k_warpnum_x = k_block_span / k_warp_xdim_size; constexpr size_t k_warpnum_y = k_block_span / k_warp_ydim_size; constexpr size_t k_warpnum_total = k_warpnum_x * k_warpnum_y; constexpr size_t k_warp_span_x = k_warp_xdim_size / k_thread_dim_size; constexpr size_t k_warp_span_y = k_threads_per_warp / k_warp_span_x; constexpr size_t scale_stride_x = 1; constexpr size_t scale_t_stride_x = 1; const size_t scale_stride_y = using_ue8m0_scale ? quanted_cols * 4 : quanted_cols; const size_t scale_t_stride_y = using_ue8m0_scale ? quanted_rows * 4 : quanted_rows; const unsigned int lane = threadIdx.x % k_threads_per_warp; const unsigned int tid_in_warp_x = lane % k_warp_span_x; const unsigned int tid_in_warp_y = lane / k_warp_span_x; const unsigned int warp_id_in_block = threadIdx.x / k_threads_per_warp; const unsigned int warp_id_in_block_x = warp_id_in_block % k_warpnum_x; const unsigned int warp_id_in_block_y = warp_id_in_block / k_warpnum_x; float blockwise_amax; // Uninitialized float blockwise_scale; // Uninitialized float warpwise_amax; // Uninitialized float thread_local_amax = 0.0f; v128_t thread_local_input[k_thread_dim_size]; // NOLINT fp8x8_t thread_local_output; fp8x8_t thread_local_output_transposed[input_transpose ? k_thread_dim_size : 1]; // NOLINT __shared__ float shared_warp_amax[k_warpnum_total]; const size_t block_base_idx = block_y * k_block_span * cols + block_x * k_block_span; const size_t warp_base_idx = block_base_idx + warp_id_in_block_y * k_thread_dim_size * k_warp_span_y * cols + warp_id_in_block_x * k_thread_dim_size * k_warp_span_x; const size_t thread_base_idx = warp_base_idx + tid_in_warp_y * k_thread_dim_size * cols + tid_in_warp_x * k_thread_dim_size; const size_t block_transpose_base_idx = input_transpose ? block_x * k_block_span * rows + block_y * k_block_span : -1; const size_t warp_transposed_base_idx = input_transpose ? block_transpose_base_idx + warp_id_in_block_x * k_thread_dim_size * k_warp_span_x * rows + warp_id_in_block_y * k_thread_dim_size * k_warp_span_y : -1; const size_t thread_transposed_base_idx = input_transpose ? warp_transposed_base_idx + tid_in_warp_x * k_thread_dim_size * rows + tid_in_warp_y * k_thread_dim_size : -1; #pragma unroll for (int i = 0; i < k_thread_dim_size; i++) { thread_local_input[i].load(input + thread_base_idx + i * cols); } for (int i = 0; i < k_thread_dim_size; i++) { #pragma unroll for (int j = 0; j < k_thread_dim_size; j++) { thread_local_amax = fmaxf( thread_local_amax, fabsf(static_cast(thread_local_input[i].data.scalar[j]))); } } float warpwise_amax_proposed = thread_local_amax; #pragma unroll for (int offset = 32; offset > 0; offset /= 2) { float received_max = __shfl_down_sync(0xFFFFFFFF, warpwise_amax_proposed, offset); warpwise_amax_proposed = fmaxf(warpwise_amax_proposed, received_max); } if (lane == 0) { shared_warp_amax[warp_id_in_block_y * k_warpnum_x + warp_id_in_block_x] = warpwise_amax_proposed; } __syncthreads(); static_assert(k_warpnum_total == 8, "warpnum_total must be 8"); if (threadIdx.x < 8) { float blockwise_amax_proposed = shared_warp_amax[threadIdx.x]; #pragma unroll for (int offset = 4; offset > 0; offset /= 2) { float received_max = __shfl_down_sync(0xFF, blockwise_amax_proposed, offset); blockwise_amax_proposed = fmaxf(blockwise_amax_proposed, received_max); } if (threadIdx.x == 0) { shared_warp_amax[0] = blockwise_amax_proposed; } } __syncthreads(); blockwise_amax = shared_warp_amax[0]; // 1. Unified scale calculation: Always compute multiplication scale blockwise_scale = ScaleWrapper < using_ue8m0_scale || use_pow2_scale > (blockwise_amax, epsilon); // 2. Compute store scale (inverse of multiplication scale) float store_scale = 1.0f / blockwise_scale; if constexpr (using_ue8m0_scale) { if (threadIdx.x < 128) { size_t row_idx = block_y * 128 + static_cast(threadIdx.x); size_t col_idx = block_x; if constexpr (!return_transpose_only) { size_t idx; idx = output_scale_transpose ? (col_idx / 4) * (rows * 4) + row_idx * 4 + (col_idx % 4) : row_idx * scale_stride_y + col_idx; StoreScale(scale, idx, store_scale); } if constexpr (input_transpose) { row_idx = block_x * 128 + threadIdx.x; col_idx = block_y; size_t idx; // 4. Whether it is 1*128 or 128*128, when output_scale_transpose is // True and using_ue8m0_scale is enabled, the calculation of idx needs // to be independently upgraded, because 4 original floats are packed as // a group and then transposed. If the transposed idx is calculated // directly, the data will be wrong. idx = output_scale_transpose ? (col_idx / 4) * (cols * 4) + row_idx * 4 + (col_idx % 4) : row_idx * scale_t_stride_y + col_idx; StoreScale( scale_transposed, idx, store_scale); } } } else { if (threadIdx.x == 0) { size_t row_idx = block_y; size_t col_idx = block_x; if constexpr (!return_transpose_only) { size_t idx = output_scale_transpose ? col_idx * scale_stride_y + row_idx * scale_stride_x : row_idx * scale_stride_y + col_idx * scale_stride_x; StoreScale(scale, idx, store_scale); } if constexpr (input_transpose) { row_idx = block_x; col_idx = block_y; size_t idx = output_scale_transpose ? col_idx * scale_t_stride_y + row_idx * scale_t_stride_x : row_idx * scale_t_stride_y + col_idx * scale_t_stride_x; StoreScale( scale_transposed, idx, store_scale); } } } for (int i = 0; i < k_thread_dim_size; i++) { #pragma unroll for (int j = 0; j < k_thread_dim_size; j++) { __nv_fp8_e4m3 output_fp8 = static_cast<__nv_fp8_e4m3>( static_cast(thread_local_input[i].data.scalar[j]) * blockwise_scale); if constexpr (!return_transpose_only) { thread_local_output.data.scalar[j] = output_fp8; } if constexpr (input_transpose) { thread_local_output_transposed[j].data.scalar[i] = output_fp8; } } if constexpr (!return_transpose_only) { thread_local_output.store(output + thread_base_idx + i * cols); } } if constexpr (input_transpose) { #pragma unroll for (int i = 0; i < k_thread_dim_size; i++) { thread_local_output_transposed[i].store( output_transposed + thread_transposed_base_idx + i * rows); } } } template __device__ void ComputeColumnScale(const v64_t x[8], float block_scale[128], T *shm, const float epsilon) { // reduce [(8), 16, 32, 4] => [16, 32, 4] T local_max[4]; for (uint32_t i = 0; i < 8; i++) { for (uint32_t j = 0; j < 4; j++) { T val = device_abs(x[i].val[j]); local_max[j] = i == 0 ? val : device_max(val, local_max[j]); } } // reduce [(16), 32, 4] => [8, 32, 4] if (threadIdx.y >= 8) { for (uint32_t j = 0; j < 4; j++) { shm[(threadIdx.y - 8) * 128 + threadIdx.x + j * 32] = local_max[j]; } } __syncthreads(); // reduce [(8), 32, 4] => [32, 4] for (uint32_t offset = 8; offset > 0; offset /= 2) { if (threadIdx.y < offset) { for (uint32_t j = 0; j < 4; j++) { T other = offset == 8 ? local_max[j] : shm[(threadIdx.y + offset) * 128 + threadIdx.x + j * 32]; T new_val = device_max(shm[threadIdx.y * 128 + threadIdx.x + j * 32], other); if (offset > 1) { shm[threadIdx.y * 128 + threadIdx.x + j * 32] = new_val; } else { block_scale[threadIdx.x * 4 + j] = ScaleWrapper < using_ue8m0_scale || use_pow2_scale > (static_cast(new_val), epsilon); } } } __syncthreads(); } } template __device__ void ComputeRowScale(const v64_t x[8], float block_scale[128], T *shm, const float epsilon) { for (uint32_t i = 0; i < 8; i++) { // reduce [32, (4)] => [32] T local_max; for (uint32_t j = 0; j < 4; j++) { T other = device_abs(x[i].val[j]); local_max = j == 0 ? other : device_max(local_max, other); } // reduce [32] => [1] T warp_max = local_max; for (uint32_t offset = 16; offset > 0; offset /= 2) { T other = __shfl_down_sync(0xFFFFFFFF, warp_max, offset); warp_max = device_max(warp_max, other); } if (threadIdx.x == 0) { shm[i * 16 + threadIdx.y] = warp_max; } } __syncthreads(); // compute scale if (threadIdx.y < 4) { T amax = shm[threadIdx.y * 32 + threadIdx.x]; block_scale[threadIdx.y * 32 + threadIdx.x] = ScaleWrapper < using_ue8m0_scale || use_pow2_scale > (static_cast(amax), epsilon); } __syncthreads(); } template __global__ void __launch_bounds__(512) quantize_1x128_kernel(const T *const input, __nv_fp8_e4m3 *const output, __nv_fp8_e4m3 *const output_transposed, ScaleT *const scale, ScaleT *const scale_transposed, const size_t rows, const size_t cols, const size_t quanted_rows, const size_t quanted_cols, const float epsilon) { __shared__ __nv_fp8_e4m3 shm[128][129]; __shared__ float block_scale[128]; const size_t col_blocks = rows / k_block_span; // rows is actually cols const size_t block_x = blockIdx.x % col_blocks; const size_t block_y = blockIdx.x / col_blocks; const size_t block_offset_x = block_x * k_block_span; const size_t block_offset_y = block_y * k_block_span; // 1. Load 128x128 block of input. v64_t x[8]; for (uint32_t i = 0; i < 8; i++) { size_t col_idx = block_offset_y + static_cast(threadIdx.y) + i * 16; size_t row_idx = block_offset_x + static_cast(threadIdx.x) * 4; size_t idx = col_idx * rows + row_idx; x[i] = *reinterpret_cast *>(input + idx); } if constexpr (!return_transpose_only) { // 2. Compute scale along the row. ComputeRowScale( x, block_scale, reinterpret_cast(shm), epsilon); // 3. Write 1x128 scale. if (threadIdx.y < 4) { size_t col_idx = block_offset_y + static_cast(threadIdx.y) * 32 + static_cast(threadIdx.x); size_t row_idx = block_x; // block_scale stores multiplication scale now float store_scale = 1.0f / block_scale[threadIdx.y * 32 + threadIdx.x]; size_t stride_rows = using_ue8m0_scale ? quanted_rows * 4 : quanted_rows; size_t idx; if (using_ue8m0_scale) { // 4. Whether it is 1*128 or 128*128, when output_scale_transpose is // True and using_ue8m0_scale is enabled, the calculation of idx needs // to be independently upgraded, because 4 original floats are packed as // a group and then transposed. If the transposed idx is calculated // directly, the data will be wrong. idx = output_scale_transpose ? (row_idx / 4) * (cols * 4) + col_idx * 4 + (row_idx % 4) : col_idx * stride_rows + row_idx; } else { idx = output_scale_transpose ? row_idx * stride_rows + col_idx : col_idx * stride_rows + row_idx; } StoreScale(scale, idx, store_scale); } // 4. Do quantization on X and write 128x128 output. for (uint32_t i = 0; i < 8; i++) { size_t col_idx = block_offset_y + static_cast(threadIdx.y) + i * 16; size_t row_idx = block_offset_x + static_cast(threadIdx.x) * 4; size_t idx = col_idx * rows + row_idx; float scale_val = block_scale[i * 16 + threadIdx.y]; fp8x4_t data; for (uint32_t j = 0; j < 4; j++) { float x_scaled = static_cast(x[i].val[j]) * scale_val; data.val[j] = static_cast<__nv_fp8_e4m3>(x_scaled); } *reinterpret_cast(output + idx) = data; } } if constexpr (input_transpose) { // 5. Compute scale along the column. ComputeColumnScale( x, block_scale, reinterpret_cast(shm), epsilon); // 6. Write 1x128 transposed scale. if (threadIdx.y < 4) { size_t col_idx = block_y; size_t row_idx = block_offset_x + static_cast(threadIdx.y) * 32 + static_cast(threadIdx.x); // block_scale stores multiplication scale float store_scale = 1.0f / block_scale[threadIdx.y * 32 + threadIdx.x]; size_t stride_cols = using_ue8m0_scale ? quanted_cols * 4 : quanted_cols; size_t idx; if (using_ue8m0_scale) { // 4. Whether it is 1*128 or 128*128, when output_scale_transpose is // True and using_ue8m0_scale is enabled, the calculation of idx needs // to be independently upgraded, because 4 original floats are packed as // a group and then transposed. If the transposed idx is calculated // directly, the data will be wrong. idx = output_scale_transpose ? (col_idx / 4) * (rows * 4) + row_idx * 4 + (col_idx % 4) : row_idx * stride_cols + col_idx; } else { idx = output_scale_transpose ? col_idx * stride_cols + row_idx : row_idx * stride_cols + col_idx; } StoreScale(scale_transposed, idx, store_scale); } // 7. Do quantization on X and transpose in the block. for (uint32_t i = 0; i < 8; i++) { for (uint32_t j = 0; j < 4; j++) { float scale_val = block_scale[threadIdx.x * 4 + j]; float x_scaled = static_cast(x[i].val[j]) * scale_val; shm[threadIdx.x * 4 + j][i * 16 + threadIdx.y] = static_cast<__nv_fp8_e4m3>(x_scaled); } } __syncthreads(); // 8. Write 128x128 transposed output. for (uint32_t i = 0; i < 8; i++) { size_t row_idx = block_offset_x + static_cast(threadIdx.y) + i * 16; size_t col_idx = block_offset_y + static_cast(threadIdx.x) * 4; size_t idx = row_idx * cols + col_idx; fp8x4_t data; for (uint32_t j = 0; j < 4; j++) { data.val[j] = shm[i * 16 + threadIdx.y][threadIdx.x * 4 + j]; } *reinterpret_cast(output_transposed + idx) = data; } } } template __global__ void quant_per_token_per_block(const T *input, __nv_fp8_e4m3 *quanted_res, ScaleT *quanted_scale, const int rows, const int cols, const int padded_rows, const float epsilon) { const size_t bid = blockIdx.x; const size_t tid = threadIdx.x; const size_t warp_id = tid / 32; const size_t lane_id = tid % 32; const size_t num_warp = blockDim.x / 32; static constexpr int NUM_PER_THREADS = 128 / 32; // 4 const size_t end_iter = (cols + 127) / 128; // warp_iter_num AlignedVector load_vec; AlignedVector load_vec_float; AlignedVector<__nv_fp8_e4m3, NUM_PER_THREADS> res_vec; for (size_t token_idx = bid; token_idx < rows; token_idx += gridDim.x) { const T *input_now = input + token_idx * cols; __nv_fp8_e4m3 *quanted_res_now = quanted_res + token_idx * cols; // deal a block per warp for (size_t iter = warp_id; iter < end_iter; iter += num_warp) { size_t idx; if constexpr (using_ue8m0_scale) { idx = output_scale_transpose ? (iter / 4) * padded_rows * 4 + token_idx * 4 + (iter % 4) : token_idx * padded_rows * 4 + iter; } else { idx = output_scale_transpose ? iter * padded_rows + token_idx : token_idx * end_iter + iter; } const size_t offset = iter * 128 + lane_id * NUM_PER_THREADS; const bool is_valid_data = offset < cols; if (is_valid_data) { Load(input_now + offset, &load_vec); } else { #pragma unroll for (int vid = 0; vid < NUM_PER_THREADS; vid++) load_vec[vid] = T(0.f); } // get max value per thread float max_value_thread = -5e4; #pragma unroll for (int vid = 0; vid < NUM_PER_THREADS; vid++) { load_vec_float[vid] = static_cast(load_vec[vid]); max_value_thread = fmax(fabs(load_vec_float[vid]), max_value_thread); } #pragma unroll for (uint32_t offset = 16; offset > 0; offset /= 2) { float other = __shfl_down_sync(0xFFFFFFFF, max_value_thread, offset); max_value_thread = fmax(max_value_thread, other); } max_value_thread = __shfl_sync(0xFFFFFFFF, max_value_thread, 0); float blockwise_scale = ScaleWrapper < use_pow2_scale || using_ue8m0_scale > (max_value_thread, epsilon); float scale_to_store = 1.0f / blockwise_scale; // quant #pragma unroll for (int vid = 0; vid < NUM_PER_THREADS; vid++) { res_vec[vid] = static_cast<__nv_fp8_e4m3>(load_vec_float[vid] * blockwise_scale); } // store if (is_valid_data) { Store<__nv_fp8_e4m3, NUM_PER_THREADS>(res_vec, quanted_res_now + offset); } if (lane_id == 0) { StoreScale( quanted_scale, idx, scale_to_store); } } } } template __global__ void quant_per_token_per_block_padding( const T *const input, __nv_fp8_e4m3 *const quanted_res, ScaleT *const quanted_scale, const size_t rows, const size_t cols, const size_t padded_rows, const float epsilon) { const size_t bid = blockIdx.x; const size_t tid = threadIdx.x; const size_t warp_id = tid / 32; const size_t lane_id = tid % 32; const size_t num_warp = blockDim.x / 32; static constexpr int NUM_PER_THREADS = 128 / 32; // 4 const size_t end_iter = cols / 128; // warp_iter_num AlignedVector load_vec; AlignedVector load_vec_float; AlignedVector<__nv_fp8_e4m3, NUM_PER_THREADS> res_vec; for (size_t token_idx = bid; token_idx < rows; token_idx += gridDim.x) { const T *input_now = input + token_idx * cols; __nv_fp8_e4m3 *quanted_res_now = quanted_res + token_idx * cols; // deal a block per warp for (size_t iter = warp_id; iter < end_iter; iter += num_warp) { size_t idx; if constexpr (using_ue8m0_scale) { idx = output_scale_transpose ? (iter / 4) * padded_rows * 4 + token_idx * 4 + (iter % 4) : token_idx * padded_rows * 4 + iter; } else { idx = output_scale_transpose ? iter * padded_rows + token_idx : token_idx * end_iter + iter; } const size_t offset = iter * 128 + lane_id * NUM_PER_THREADS; Load(input_now + offset, &load_vec); // get max value per thread float max_value_thread = -5e4; #pragma unroll for (int vid = 0; vid < NUM_PER_THREADS; vid++) { load_vec_float[vid] = static_cast(load_vec[vid]); max_value_thread = fmax(fabs(load_vec_float[vid]), max_value_thread); } #pragma unroll for (uint32_t offset = 16; offset > 0; offset /= 2) { float other = __shfl_down_sync(0xFFFFFFFF, max_value_thread, offset); max_value_thread = fmax(max_value_thread, other); } max_value_thread = __shfl_sync(0xFFFFFFFF, max_value_thread, 0); float blockwise_scale = ScaleWrapper < use_pow2_scale || using_ue8m0_scale > (max_value_thread, epsilon); float scale_to_store = 1.0f / blockwise_scale; // quant #pragma unroll for (int vid = 0; vid < NUM_PER_THREADS; vid++) { res_vec[vid] = static_cast<__nv_fp8_e4m3>(load_vec_float[vid] * blockwise_scale); } // store Store<__nv_fp8_e4m3, NUM_PER_THREADS>(res_vec, quanted_res_now + offset); if (lane_id == 0) { StoreScale( quanted_scale, idx, scale_to_store); } } } } template void FP8QuantBlockWiseKernelImpl(const Context &dev_ctx, const DenseTensor &X, float epsilon, DenseTensor *out, DenseTensor *scale, DenseTensor *out_transposed, DenseTensor *scale_transposed) { const size_t src_rows = X.dims()[0]; const size_t src_cols = X.dims()[1]; const size_t quanted_cols = scale->dims()[1]; const size_t quanted_rows = scale_transposed->dims()[1]; using NvType = typename CudaTypeTraits::Type; using ScaleT = typename std::conditional_t; if (src_cols % 128 != 0) { PD_CHECK( !input_transpose && !return_transpose_only, "When cols is not aligned to 128, only supports: input_transpose=False " "and return_transpose_only=False."); PD_CHECK(using_1x128_vec_quant, "When cols is not aligned to 128, only supports: " "using_1x128_vec_quant."); PD_CHECK(src_cols % 4 == 0, "src_cols must be divisible by 4."); const int device_id = phi::backends::gpu::GetCurrentDeviceId(); const int sm_count = phi::backends::gpu::GetGPUMultiProcessors(device_id); const size_t min_grid_x = sm_count * 8; const size_t min_block_x = 1024; const size_t gridx = std::min(min_grid_x, src_rows); const size_t blockx = std::min(min_block_x, (src_cols + 127) / 128 * 32); quant_per_token_per_block <<>>( reinterpret_cast(X.data()), reinterpret_cast<__nv_fp8_e4m3 *>(out->data()), reinterpret_cast(scale->data()), src_rows, src_cols, quanted_cols, epsilon); } else if (src_rows % 128 != 0) { PD_CHECK( !input_transpose && !return_transpose_only, "When rows is not aligned to 128, only supports: input_transpose=False " "and return_transpose_only=False."); PD_CHECK(using_1x128_vec_quant, "When rows is not aligned to 128, only supports: " "using_1x128_vec_quant."); PD_CHECK(src_cols % 128 == 0, "src_cols must be divisible by 128."); const int device_id = phi::backends::gpu::GetCurrentDeviceId(); const int sm_count = phi::backends::gpu::GetGPUMultiProcessors(device_id); const size_t min_grid_x = sm_count * 8; const size_t min_block_x = 1024; const size_t gridx = std::min(min_grid_x, src_rows); const size_t blockx = std::min(min_block_x, src_cols / 128 * 32); quant_per_token_per_block_padding <<>>( reinterpret_cast(X.data()), reinterpret_cast<__nv_fp8_e4m3 *>(out->data()), reinterpret_cast(scale->data()), src_rows, src_cols, quanted_cols, epsilon); } else { dim3 block, grid; const size_t blocks_per_row = src_cols / k_block_span; if constexpr (using_1x128_vec_quant) { block.x = 32; block.y = 16; } else { block.x = 256; } grid.x = (src_cols / k_block_span) * (src_rows / k_block_span); auto kernel = using_1x128_vec_quant ? quantize_1x128_kernel : quantize_128x128_kernel; kernel<<>>( reinterpret_cast(X.data()), reinterpret_cast<__nv_fp8_e4m3 *>(out->data()), input_transpose ? reinterpret_cast<__nv_fp8_e4m3 *>( out_transposed->data()) : nullptr, reinterpret_cast(scale->data()), input_transpose ? reinterpret_cast(scale_transposed->data()) : nullptr, src_cols, src_rows, quanted_cols, quanted_rows, epsilon); } } // T is x's input type and out_dtype is in args template void FP8QuantBlockWiseKernel(const Context &dev_ctx, const DenseTensor &X, float epsilon, bool using_1x128_vec_quant, bool input_transpose, bool output_scale_transpose, bool return_transpose_only, bool using_e5m2, bool using_pow2_scale, bool using_ue8m0_scale, DenseTensor *out, DenseTensor *scale, DenseTensor *out_transposed, DenseTensor *scale_transposed) { PD_CHECK(X.dtype() == phi::DataType::BFLOAT16 || X.dtype() == phi::DataType::FLOAT16, "X datatype error, can only be bfloat16 or float16"); dev_ctx.template Alloc(out); if (using_ue8m0_scale) { dev_ctx.template Alloc(scale); } else { dev_ctx.template Alloc(scale); } if (input_transpose) { dev_ctx.template Alloc(out_transposed); if (using_ue8m0_scale) { dev_ctx.template Alloc(scale_transposed); } else { dev_ctx.template Alloc(scale_transposed); } } if (X.numel() == 0) { return; } // Currently we only support bfloat16 as input type, // fp8_e4m3fn as output type. DISPATCH_BOOL( using_1x128_vec_quant, k_using_1x128_vec_quant, DISPATCH_BOOL( input_transpose, k_input_transpose, DISPATCH_BOOL( output_scale_transpose, k_output_scale_transpose, DISPATCH_BOOL( return_transpose_only, k_return_transpose_only, DISPATCH_BOOL( using_pow2_scale, k_using_pow2_scale, DISPATCH_BOOL( using_ue8m0_scale, k_using_ue8m0_scale, FP8QuantBlockWiseKernelImpl( dev_ctx, X, epsilon, out, scale, out_transposed, scale_transposed);)))))); } } // namespace phi PD_REGISTER_KERNEL(fp8_quant_blockwise, GPU, ALL_LAYOUT, phi::FP8QuantBlockWiseKernel, phi::float16, phi::bfloat16) {} // NOLINTEND