/* Copyright (c) 2023 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/weight_dequantize_kernel.h" #include "paddle/phi/backends/gpu/gpu_context.h" #include "paddle/phi/core/kernel_registry.h" #include "paddle/phi/kernels/transpose_kernel.h" #if defined(PADDLE_WITH_CUTLASS) #include "paddle/phi/kernels/funcs/weight_dequant_functor.h" #endif #ifdef PADDLE_WITH_HIP #include "paddle/phi/common/datatype_traits.h" #include "paddle/phi/kernels/funcs/aligned_vector.h" #include "paddle/phi/kernels/funcs/math_function.h" #include "paddle/phi/kernels/matmul_kernel.h" #endif namespace phi { #ifdef PADDLE_WITH_HIP #define NUMPERTHREAD 16 template struct alignas(sizeof(T) * Size) aligned_vector { T val[Size]; }; using int8_8 = aligned_vector; template __device__ inline aligned_vector i82h_convert8(int8_8 signed_chars, T scale) { aligned_vector halves; halves.val[0] = static_cast(static_cast(signed_chars.val[0]) * static_cast(scale)); halves.val[1] = static_cast(static_cast(signed_chars.val[1]) * static_cast(scale)); halves.val[2] = static_cast(static_cast(signed_chars.val[2]) * static_cast(scale)); halves.val[3] = static_cast(static_cast(signed_chars.val[3]) * static_cast(scale)); halves.val[4] = static_cast(static_cast(signed_chars.val[4]) * static_cast(scale)); halves.val[5] = static_cast(static_cast(signed_chars.val[5]) * static_cast(scale)); halves.val[6] = static_cast(static_cast(signed_chars.val[6]) * static_cast(scale)); halves.val[7] = static_cast(static_cast(signed_chars.val[7]) * static_cast(scale)); return halves; } template <> __device__ inline aligned_vector i82h_convert8(int8_8 signed_chars, half scale) { aligned_vector halves; halves.val[0] = __float2half(static_cast(signed_chars.val[0]) * __half2float(scale)); halves.val[1] = __float2half(static_cast(signed_chars.val[1]) * __half2float(scale)); halves.val[2] = __float2half(static_cast(signed_chars.val[2]) * __half2float(scale)); halves.val[3] = __float2half(static_cast(signed_chars.val[3]) * __half2float(scale)); halves.val[4] = __float2half(static_cast(signed_chars.val[4]) * __half2float(scale)); halves.val[5] = __float2half(static_cast(signed_chars.val[5]) * __half2float(scale)); halves.val[6] = __float2half(static_cast(signed_chars.val[6]) * __half2float(scale)); halves.val[7] = __float2half(static_cast(signed_chars.val[7]) * __half2float(scale)); return halves; } struct uint4_2 { uint8_t data; explicit uint4_2(uint8_t x = 0, uint8_t y = 0) { setX(x); setY(y); } __host__ __device__ uint8_t getX() const { return data & 0x0F; // lower 4 bits } __host__ __device__ uint8_t getY() const { return (data >> 4) & 0x0F; // upper 4 bits } __host__ __device__ void setX(uint8_t x) { data = (data & 0xF0) | (x & 0x0F); // set the lower 4 bits } __host__ __device__ void setY(uint8_t y) { data = (data & 0x0F) | ((y & 0x0F) << 4); // set the upper 4 bits } }; using uint4_2_8 = aligned_vector; template __device__ inline aligned_vector, 2> i42h_convert8_2( uint4_2_8 signed_chars, T scale_0, T scale_1) { aligned_vector, 2> halves; aligned_vector halves_0; aligned_vector halves_1; halves_0.val[0] = static_cast( static_cast((int8_t)signed_chars.val[0].getX() - 8) * static_cast(scale_0)); halves_0.val[1] = static_cast( static_cast((int8_t)signed_chars.val[1].getX() - 8) * static_cast(scale_0)); halves_0.val[2] = static_cast( static_cast((int8_t)signed_chars.val[2].getX() - 8) * static_cast(scale_0)); halves_0.val[3] = static_cast( static_cast((int8_t)signed_chars.val[3].getX() - 8) * static_cast(scale_0)); halves_0.val[4] = static_cast( static_cast((int8_t)signed_chars.val[4].getX() - 8) * static_cast(scale_0)); halves_0.val[5] = static_cast( static_cast((int8_t)signed_chars.val[5].getX() - 8) * static_cast(scale_0)); halves_0.val[6] = static_cast( static_cast((int8_t)signed_chars.val[6].getX() - 8) * static_cast(scale_0)); halves_0.val[7] = static_cast( static_cast((int8_t)signed_chars.val[7].getX() - 8) * static_cast(scale_0)); halves_1.val[0] = static_cast( static_cast((int8_t)signed_chars.val[0].getY() - 8) * static_cast(scale_1)); halves_1.val[1] = static_cast( static_cast((int8_t)signed_chars.val[1].getY() - 8) * static_cast(scale_1)); halves_1.val[2] = static_cast( static_cast((int8_t)signed_chars.val[2].getY() - 8) * static_cast(scale_1)); halves_1.val[3] = static_cast( static_cast((int8_t)signed_chars.val[3].getY() - 8) * static_cast(scale_1)); halves_1.val[4] = static_cast( static_cast((int8_t)signed_chars.val[4].getY() - 8) * static_cast(scale_1)); halves_1.val[5] = static_cast( static_cast((int8_t)signed_chars.val[5].getY() - 8) * static_cast(scale_1)); halves_1.val[6] = static_cast( static_cast((int8_t)signed_chars.val[6].getY() - 8) * static_cast(scale_1)); halves_1.val[7] = static_cast( static_cast((int8_t)signed_chars.val[7].getY() - 8) * static_cast(scale_1)); halves.val[0] = halves_0; halves.val[1] = halves_1; return halves; } template <> __device__ inline aligned_vector, 2> i42h_convert8_2( uint4_2_8 signed_chars, half scale_0, half scale_1) { aligned_vector, 2> halves; aligned_vector halves_0; aligned_vector halves_1; halves_0.val[0] = __float2half(static_cast((int8_t)signed_chars.val[0].getX() - 8) * __half2float(scale_0)); halves_0.val[1] = __float2half(static_cast((int8_t)signed_chars.val[1].getX() - 8) * __half2float(scale_0)); halves_0.val[2] = __float2half(static_cast((int8_t)signed_chars.val[2].getX() - 8) * __half2float(scale_0)); halves_0.val[3] = __float2half(static_cast((int8_t)signed_chars.val[3].getX() - 8) * __half2float(scale_0)); halves_0.val[4] = __float2half(static_cast((int8_t)signed_chars.val[4].getX() - 8) * __half2float(scale_0)); halves_0.val[5] = __float2half(static_cast((int8_t)signed_chars.val[5].getX() - 8) * __half2float(scale_0)); halves_0.val[6] = __float2half(static_cast((int8_t)signed_chars.val[6].getX() - 8) * __half2float(scale_0)); halves_0.val[7] = __float2half(static_cast((int8_t)signed_chars.val[7].getX() - 8) * __half2float(scale_0)); halves_1.val[0] = __float2half(static_cast((int8_t)signed_chars.val[0].getY() - 8) * __half2float(scale_1)); halves_1.val[1] = __float2half(static_cast((int8_t)signed_chars.val[1].getY() - 8) * __half2float(scale_1)); halves_1.val[2] = __float2half(static_cast((int8_t)signed_chars.val[2].getY() - 8) * __half2float(scale_1)); halves_1.val[3] = __float2half(static_cast((int8_t)signed_chars.val[3].getY() - 8) * __half2float(scale_1)); halves_1.val[4] = __float2half(static_cast((int8_t)signed_chars.val[4].getY() - 8) * __half2float(scale_1)); halves_1.val[5] = __float2half(static_cast((int8_t)signed_chars.val[5].getY() - 8) * __half2float(scale_1)); halves_1.val[6] = __float2half(static_cast((int8_t)signed_chars.val[6].getY() - 8) * __half2float(scale_1)); halves_1.val[7] = __float2half(static_cast((int8_t)signed_chars.val[7].getY() - 8) * __half2float(scale_1)); halves.val[0] = halves_0; halves.val[1] = halves_1; return halves; } template __global__ void int8_weight_only_dequant(int8_t* mat, T* scales, T* mat_res, unsigned int k, unsigned int k_iteration) { unsigned int tid = threadIdx.x; unsigned int row = blockIdx.y * blockDim.y + threadIdx.y; int8_8* mat8 = reinterpret_cast(mat); aligned_vector* mat_res8 = reinterpret_cast*>(mat_res); T scale = scales[row]; #pragma unroll for (unsigned int iteration = 0; iteration < k_iteration; iteration++) { unsigned int gidx = tid + iteration * blockDim.x; unsigned int gdatax = NUMPERTHREAD / 8 * gidx; #pragma unroll for (unsigned int it = 0; it < NUMPERTHREAD / 8 / 2; it++) { if (gdatax + 2 * it + 1 < k / 8) { mat_res8[row * (k / 8) + gdatax + 2 * it] = i82h_convert8(mat8[row * (k / 8) + gdatax + 2 * it], scale); mat_res8[row * (k / 8) + gdatax + 2 * it + 1] = i82h_convert8(mat8[row * (k / 8) + gdatax + 2 * it + 1], scale); } } } } template __global__ void int8_weight_only_dequant(int8_t* mat, T* scales, T* mat_res, unsigned int k, unsigned int groupsize, unsigned int k_iteration) { unsigned int tid = threadIdx.x; unsigned int row = blockIdx.y * blockDim.y + threadIdx.y; int8_8* mat8 = reinterpret_cast(mat); aligned_vector* mat_res8 = reinterpret_cast*>(mat_res); #pragma unroll for (unsigned int iteration = 0; iteration < k_iteration; iteration++) { unsigned int gidx = tid + iteration * blockDim.x; unsigned int gdatax = NUMPERTHREAD / 8 * gidx; T scale = scales[row * (k / groupsize) + gidx * NUMPERTHREAD / groupsize]; #pragma unroll for (unsigned int it = 0; it < NUMPERTHREAD / 8 / 2; it++) { if (gdatax + 2 * it + 1 < k / 8) { mat_res8[row * (k / 8) + gdatax + 2 * it] = i82h_convert8(mat8[row * (k / 8) + gdatax + 2 * it], scale); mat_res8[row * (k / 8) + gdatax + 2 * it + 1] = i82h_convert8(mat8[row * (k / 8) + gdatax + 2 * it + 1], scale); } } } } template __global__ void int4_weight_only_dequant(uint4_2* mat, T* scales, T* mat_res, unsigned int k, unsigned int k_iteration) { unsigned int tid = threadIdx.x; unsigned int row = blockIdx.y * blockDim.y + threadIdx.y; uint4_2_8* mat16 = reinterpret_cast(mat); aligned_vector* mat_res8 = reinterpret_cast*>(mat_res); aligned_vector, 2> mat_res16; T scale_0 = scales[2 * row]; T scale_1 = scales[2 * row + 1]; #pragma unroll for (unsigned int iteration = 0; iteration < k_iteration; iteration++) { unsigned int gidx = tid + iteration * blockDim.x; unsigned int gdatax = NUMPERTHREAD / 8 * gidx; #pragma unroll for (unsigned int it = 0; it < NUMPERTHREAD / 8 / 2; it++) { if (gdatax + 2 * it + 1 < k / 8) { mat_res16 = i42h_convert8_2( mat16[row * (k / 8) + gdatax + 2 * it], scale_0, scale_1); mat_res8[2 * row * (k / 8) + gdatax + 2 * it] = mat_res16.val[0]; mat_res8[(2 * row + 1) * (k / 8) + gdatax + 2 * it] = mat_res16.val[1]; mat_res16 = i42h_convert8_2( mat16[row * (k / 8) + gdatax + 2 * it + 1], scale_0, scale_1); mat_res8[2 * row * (k / 8) + gdatax + 2 * it + 1] = mat_res16.val[0]; mat_res8[(2 * row + 1) * (k / 8) + gdatax + 2 * it + 1] = mat_res16.val[1]; } } } } template __global__ void int4_weight_only_dequant(uint4_2* mat, T* scales, T* mat_res, unsigned int k, unsigned int groupsize, unsigned int k_iteration) { unsigned int tid = threadIdx.x; unsigned int row = blockIdx.y * blockDim.y + threadIdx.y; uint4_2_8* mat16 = reinterpret_cast(mat); aligned_vector* mat_res8 = reinterpret_cast*>(mat_res); aligned_vector, 2> mat_res16; #pragma unroll for (unsigned int iteration = 0; iteration < k_iteration; iteration++) { unsigned int gidx = tid + iteration * blockDim.x; unsigned int gdatax = NUMPERTHREAD / 8 * gidx; T scale_0 = scales[2 * row * (k / groupsize) + gidx * NUMPERTHREAD / groupsize]; T scale_1 = scales[(2 * row + 1) * (k / groupsize) + gidx * NUMPERTHREAD / groupsize]; #pragma unroll for (unsigned int it = 0; it < NUMPERTHREAD / 8 / 2; it++) { if (gdatax + 2 * it + 1 < k / 8) { mat_res16 = i42h_convert8_2( mat16[row * (k / 8) + gdatax + 2 * it], scale_0, scale_1); mat_res8[2 * row * (k / 8) + gdatax + 2 * it] = mat_res16.val[0]; mat_res8[(2 * row + 1) * (k / 8) + gdatax + 2 * it] = mat_res16.val[1]; mat_res16 = i42h_convert8_2( mat16[row * (k / 8) + gdatax + 2 * it + 1], scale_0, scale_1); mat_res8[2 * row * (k / 8) + gdatax + 2 * it + 1] = mat_res16.val[0]; mat_res8[(2 * row + 1) * (k / 8) + gdatax + 2 * it + 1] = mat_res16.val[1]; } } } } template void WeightDequantize(const Context& dev_ctx, const DenseTensor& x, const DenseTensor& scale, const std::string& algo, const int32_t group_size, DenseTensor* out) { using DataType = typename PDDataTypeTraits::DataType; int64_t n = scale.dims()[0]; int64_t k = x.dims()[1]; // TODO(large-tensor): CUDA grid dims not support int64 PADDLE_ENFORCE_LE_INT_MAX(n, "n"); PADDLE_ENFORCE_LE_INT_MAX(k, "k"); unsigned int grid_y = static_cast(n); PADDLE_ENFORCE_EQ( (k % NUMPERTHREAD == 0), true, common::errors::InvalidArgument( "Currently, WeightDequantize only support k % NUMPERTHREAD == 0.")); unsigned int block_dim_x = 256; unsigned int kperblock = block_dim_x * NUMPERTHREAD; unsigned int block_dim_y = 1; unsigned int k_iteration = k % kperblock == 0 ? k / kperblock : k / kperblock + 1; dim3 grid(1, grid_y / block_dim_y); dim3 block(block_dim_x, block_dim_y); auto stream = dev_ctx.stream(); if (algo == "weight_only_int8" && group_size == -1) { int8_weight_only_dequant<<>>( const_cast(x.data()), const_cast( reinterpret_cast(scale.data())), reinterpret_cast(out->data()), k, k_iteration); } else if (algo == "weight_only_int8" && group_size > 0) { int8_weight_only_dequant<<>>( const_cast(x.data()), const_cast( reinterpret_cast(scale.data())), reinterpret_cast(out->data()), k, group_size, k_iteration); } else if (algo == "weight_only_int4" && group_size == -1) { grid.y /= 2; int4_weight_only_dequant<<>>( reinterpret_cast(const_cast(x.data())), const_cast( reinterpret_cast(scale.data())), reinterpret_cast(out->data()), k, k_iteration); } else if (algo == "weight_only_int4" && group_size > 0) { grid.y /= 2; int4_weight_only_dequant<<>>( reinterpret_cast(const_cast(x.data())), const_cast( reinterpret_cast(scale.data())), reinterpret_cast(out->data()), k, group_size, k_iteration); } } #endif template void WeightDequantizeKernel(const Context& dev_ctx, const DenseTensor& x, const DenseTensor& scale, const std::string& algo, int32_t group_size, DenseTensor* out) { #if defined(PADDLE_WITH_CUTLASS) auto out_dims = out->dims(); dev_ctx.template Alloc(out); WeightDequantize(dev_ctx, x, scale, algo, true, group_size, out); out->Resize({out_dims[1], out_dims[0]}); auto out_tmp = Transpose(dev_ctx, *out, {1, 0}); out->ShareDataWith(out_tmp); #elif defined(PADDLE_WITH_HIP) DenseTensor scale_trans(scale.type()); if (group_size > 0) { scale_trans.Resize({scale.dims()[1], scale.dims()[0]}); dev_ctx.template Alloc(&scale_trans); std::vector axis = {1, 0}; funcs::Transpose trans; trans(dev_ctx, scale, &scale_trans, axis); } auto out_dims = out->dims(); dev_ctx.template Alloc(out); WeightDequantize( dev_ctx, x, group_size > 0 ? scale_trans : scale, algo, group_size, out); out->Resize({out_dims[1], out_dims[0]}); auto out_tmp = Transpose(dev_ctx, *out, {1, 0}); out->ShareDataWith(out_tmp); #else PADDLE_THROW( common::errors::PreconditionNotMet("Not compiled with WITH_CUTLASS=ON")); #endif } } // namespace phi PD_REGISTER_KERNEL(weight_dequantize, GPU, ALL_LAYOUT, phi::WeightDequantizeKernel, phi::float16, phi::bfloat16) {}