// 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. #pragma once #include #include "paddle/phi/backends/gpu/gpu_launch_config.h" #include "paddle/phi/core/dense_tensor.h" #include "paddle/phi/core/kernel_registry.h" #include "paddle/phi/kernels/funcs/aligned_vector.h" #include "paddle/phi/kernels/funcs/common_shape.h" namespace phi { template __global__ void weight_permute_kernel_wint8(const int8_t* input_data_dev, int8_t* output_data_dev, IndexT numel, IndexT total_k, IndexT total_n) { CUDA_KERNEL_LOOP_TYPE(linear_idx, numel, IndexT) { IndexT k_id = linear_idx / total_n; IndexT n_id = linear_idx % total_n; IndexT k_mod_16 = k_id % 16; constexpr int map[16] = { 0, 2, 4, 6, 8, 10, 12, 14, 1, 3, 5, 7, 9, 11, 13, 15}; IndexT permute_kk = map[k_mod_16] + k_id / 16 * 16; IndexT permute_index = permute_kk % 64 + permute_kk / 64 * 128 + 64 * (n_id % 2) + total_k * 2 * (n_id / 2); uint8_t shift_quant_weight = static_cast( static_cast(input_data_dev[linear_idx]) + 128); output_data_dev[permute_index] = *reinterpret_cast(&shift_quant_weight); } } template __global__ void weight_permute_kernel_wint4(const int8_t* input_data_dev, int8_t* output_data_dev, IndexT numel, IndexT total_k, IndexT total_n) { CUDA_KERNEL_LOOP_TYPE(linear_idx, numel, IndexT) { IndexT k_id = linear_idx / total_n; IndexT n_id = linear_idx % total_n; // k_id is 8_bit index. constexpr int map[16] = { 0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15}; IndexT permute_kk = map[k_id % 16] + k_id / 16 * 16; IndexT permute_index = permute_kk % 32 + permute_kk / 32 * 128 + 32 * (n_id % 4) + total_k * 2 * (n_id / 4); int8_t shift_quant_weight = input_data_dev[linear_idx]; output_data_dev[permute_index] = *reinterpret_cast(&shift_quant_weight); } } // convetr 0,1,2,3,4,5,6,7 4bit -> 0,2,4,6,1,3,5,7 __global__ void weight_interval_kernel_wint4(int8_t* output_data_dev, int64_t numel) { constexpr int value_per_interval_thread = 4; int64_t linear_idx = (static_cast(blockIdx.x) * blockDim.x + threadIdx.x) * value_per_interval_thread; int64_t stride = static_cast(blockDim.x) * gridDim.x * value_per_interval_thread; for (; linear_idx < numel; linear_idx += stride) { uint32_t value = *reinterpret_cast(output_data_dev + linear_idx); uint32_t result = 0; constexpr int map[8] = {0, 2, 4, 6, 1, 3, 5, 7}; for (int ii = 0; ii < 8; ii++) { uint32_t tmp = value >> (map[ii] * 4); tmp &= 0x0F; tmp = (tmp + 8) & 0x0F; tmp = tmp << (ii * 4); result |= tmp; } *reinterpret_cast(output_data_dev + linear_idx) = result; } } /* For SM70 volta arch, weightonly int8 dequantize invoked in load global memory. So it only need interleave in K-dimension K_index: 0 1 2 3 -> 0 2 1 3 */ template __global__ void weight_interleave_add_bias_kernel_wint8( const int8_t* input_data_dev, int8_t* output_data_dev, IndexT numel, IndexT total_n) { CUDA_KERNEL_LOOP_TYPE(linear_idx, numel, IndexT) { IndexT k_id = linear_idx / total_n; IndexT n_id = linear_idx % total_n; constexpr int n_interleaved_factor = 4; IndexT n_interleave_group_id = n_id / n_interleaved_factor; IndexT n_interleave_id = n_id % n_interleaved_factor; if (n_interleave_id == 1 || n_interleave_id == 2) { /* 0001 xor 0011 -> 0010 0010 xor 0011 -> 0001 */ n_interleave_id ^= 3; } const IndexT new_n_id = n_interleave_group_id * n_interleaved_factor + n_interleave_id; const IndexT interleave_idx = k_id * total_n + new_n_id; uint8_t shift_quant_weight = static_cast( static_cast(input_data_dev[linear_idx]) + 128); output_data_dev[interleave_idx] = *reinterpret_cast(&shift_quant_weight); } } /* For SM70 volta arch, weightonly int4 dequantize invoked in load global memory. So it only need interleave in K-dimension K_index: 0 1 2 3 4 5 6 7 -> 0 2 4 6 1 3 5 7 */ template __global__ void weight_interleave_add_bias_kernel_wint4(int8_t* input_data_dev, int8_t* output_data_dev, IndexT numel, IndexT total_n) { const IndexT num_registers = numel / 4; uint32_t* packed_input = reinterpret_cast(input_data_dev); uint32_t* packed_output = reinterpret_cast(output_data_dev); CUDA_KERNEL_LOOP_TYPE(i, num_registers, IndexT) { uint32_t current_pack = packed_input[i]; uint32_t transformed_pack = 0; #pragma unroll for (int idx = 0; idx < 8; ++idx) { const int offset = idx / 4; const int src = (idx % 4) * 2 + offset; const int src_shift = src * 4; const int dst_shift = idx * 4; const uint32_t src_bits = ((current_pack >> src_shift) + 8) & 0xF; transformed_pack |= (src_bits << dst_shift); } packed_output[i] = transformed_pack; } } template void weight_permute_gpu_impl(const GPUContext& dev_ctx, int8_t* input_data, int8_t* output_data, const std::vector& shape, const int32_t arch, const std::string& algo) { auto total_k = shape[0]; auto total_n = shape[1]; auto numel = total_k * total_n; auto gpu_config = backends::gpu::GetGpuLaunchConfig1D(dev_ctx, numel, 1); int grid_size = gpu_config.GetGridSize(); int block_size = gpu_config.GetBlockSize(); if ((arch == 100) || (arch == 90) || (arch == 89) || (arch == 86) || (arch == 80) || (arch == 75)) { if (algo == "weight_only_int4") { numel /= 2; weight_permute_kernel_wint4<<>>( input_data, output_data, numel, total_k, total_n); weight_interval_kernel_wint4<<>>(output_data, numel); } else { weight_permute_kernel_wint8<<>>( input_data, output_data, numel, total_k, total_n); } } else if (arch == 70) { if (algo == "weight_only_int4") { weight_interleave_add_bias_kernel_wint4 <<>>(input_data, output_data, numel, total_n); } else { weight_interleave_add_bias_kernel_wint8 <<>>(input_data, output_data, numel, total_n); } } } template void weight_permute_gpu(const GPUContext& dev_ctx, int8_t* input_data, int8_t* output_data, const std::vector& shape, const int32_t arch, const std::string& algo) { int64_t numel = shape[0] * shape[1]; if (numel <= std::numeric_limits::max()) { weight_permute_gpu_impl( dev_ctx, input_data, output_data, shape, arch, algo); } else { weight_permute_gpu_impl( dev_ctx, input_data, output_data, shape, arch, algo); } } template __global__ void per_channel_quant_gpu(const T* weight_data, int8_t* quanted_weight_data, ScaleT* scale_data, int total_k, int64_t total_vec_n) { int64_t n = static_cast(blockIdx.x) * blockDim.x + threadIdx.x; if (n < total_vec_n) { const int4* vec_weight_data_ptr = reinterpret_cast(weight_data); int2* vec_quanted_weight_data = reinterpret_cast(quanted_weight_data); AlignedVector abs_max; #pragma unroll for (int i = 0; i < VectorSize; ++i) { abs_max[i] = static_cast(0.0f); } #pragma unroll for (int k = 0; k < total_k; ++k) { int64_t linear_index = k * total_vec_n + n; AlignedVector weight; *reinterpret_cast(&weight) = vec_weight_data_ptr[linear_index]; #pragma unroll for (int i = 0; i < VectorSize; ++i) { abs_max[i] = fmaxf((abs_max[i]), fabsf((weight[i]))); } } AlignedVector scale; #pragma unroll for (int i = 0; i < VectorSize; ++i) { scale[i] = static_cast(abs_max[i] / static_cast(127.0f)); } *reinterpret_cast(scale_data + VectorSize * n) = *reinterpret_cast(&scale); for (int k = 0; k < total_k; ++k) { AlignedVector quanted_weight; int64_t linear_index = k * total_vec_n + n; AlignedVector weight; *reinterpret_cast(&weight) = *reinterpret_cast(vec_weight_data_ptr + linear_index); #pragma unroll for (int i = 0; i < VectorSize; ++i) { float scaled_weight = (static_cast(weight[i]) / static_cast(abs_max[i])) * static_cast(127.0); int8_t clipped_weight = static_cast( lroundf(fmaxf(-127.0f, fminf(127.0f, scaled_weight)))); quanted_weight[i] = clipped_weight; } *reinterpret_cast(vec_quanted_weight_data + linear_index) = *reinterpret_cast(&quanted_weight); } } } template __global__ void per_channel_quant_gpu_int4_row_pack(const T* weight_data, int8_t* quanted_weight_data, ScaleT* scale_data, int total_k, int64_t total_vec_n) { int64_t n = static_cast(blockIdx.x) * blockDim.x + threadIdx.x; if (n < total_vec_n) { const int4* vec_weight_data_ptr = reinterpret_cast(weight_data); int* vec_quanted_weight_data = reinterpret_cast(quanted_weight_data); AlignedVector abs_max; #pragma unroll for (int i = 0; i < VectorSize; ++i) { abs_max[i] = static_cast(0.0f); } #pragma unroll for (int k = 0; k < total_k; ++k) { int64_t linear_index = k * total_vec_n + n; AlignedVector weight; *reinterpret_cast(&weight) = vec_weight_data_ptr[linear_index]; #pragma unroll for (int i = 0; i < VectorSize; ++i) { abs_max[i] = fmaxf((abs_max[i]), fabsf((weight[i]))); } } AlignedVector scale; #pragma unroll for (int i = 0; i < VectorSize; ++i) { scale[i] = static_cast(abs_max[i] / static_cast(7.0f)); } *reinterpret_cast(scale_data + VectorSize * n) = *reinterpret_cast(&scale); for (int k = 0; k < total_k; ++k) { int64_t linear_index = k * total_vec_n + n; AlignedVector weight; AlignedVector quanted_weight; *reinterpret_cast(&weight) = *reinterpret_cast(vec_weight_data_ptr + linear_index); #pragma unroll for (int i = 0; i < VectorSize / 2; ++i) { int8_t packed_int4s = 0; for (int pack = 0; pack < 2; ++pack) { int vector_index = i * 2 + pack; const float r_scale = 1 / static_cast(scale[vector_index]); const float weight_elt = static_cast(weight[vector_index]) * r_scale; float scaled_weight = roundf(weight_elt); int int_weight = static_cast(scaled_weight); #ifdef PADDLE_WITH_HIP int8_t clipped_weight = max(-7, min(7, int_weight)) + 8; #else int8_t clipped_weight = max(-7, min(7, int_weight)); #endif packed_int4s |= ((clipped_weight & 0x0F) << (4 * pack)); } quanted_weight[i] = packed_int4s; } *reinterpret_cast(vec_quanted_weight_data + linear_index) = *reinterpret_cast(&quanted_weight); } } } template __global__ void per_channel_quant_gpu_int4_col_pack(const T* weight_data, int8_t* quanted_weight_data, ScaleT* scale_data, int total_k, int64_t total_vec_n) { int64_t n = static_cast(blockIdx.x) * blockDim.x + threadIdx.x; if (n < total_vec_n) { const int4* vec_weight_data_ptr = reinterpret_cast(weight_data); int2* vec_quanted_weight_data = reinterpret_cast(quanted_weight_data); AlignedVector abs_max; #pragma unroll for (int i = 0; i < VectorSize; ++i) { abs_max[i] = static_cast(0.0f); } #pragma unroll for (int k = 0; k < total_k; ++k) { int64_t linear_index = k * total_vec_n + n; AlignedVector weight; *reinterpret_cast(&weight) = vec_weight_data_ptr[linear_index]; #pragma unroll for (int i = 0; i < VectorSize; ++i) { abs_max[i] = fmaxf((abs_max[i]), static_cast(fabsf(weight[i]))); } } AlignedVector scale; #pragma unroll for (int i = 0; i < VectorSize; ++i) { scale[i] = static_cast(abs_max[i] / static_cast(7.0f)); } *reinterpret_cast(scale_data + VectorSize * n) = *reinterpret_cast(&scale); for (int k = 0; k < total_k / 2; ++k) { AlignedVector quanted_weight; for (int packed_idx = 0; packed_idx < 2; ++packed_idx) { int64_t linear_index = (k * 2 + packed_idx) * total_vec_n + n; AlignedVector weight; *reinterpret_cast(&weight) = *reinterpret_cast(vec_weight_data_ptr + linear_index); #pragma unroll for (int i = 0; i < VectorSize; ++i) { const float weight_elt = (static_cast(weight[i]) / static_cast(abs_max[i])) * static_cast(7.0); const float scaled_weight = lroundf(weight_elt); int int_weight = static_cast(scaled_weight); const int8_t clipped_weight = fmaxf(-7, fminf(7, int_weight)); quanted_weight[i] &= ~(0x0F << (4 * packed_idx)); quanted_weight[i] |= ((clipped_weight & 0x0F) << (4 * packed_idx)); } } int64_t linear_index_new = k * total_vec_n + n; *reinterpret_cast(vec_quanted_weight_data + linear_index_new) = *reinterpret_cast(&quanted_weight); } } } template void weight_quant_gpu(const GPUContext& dev_ctx, const T* weight_data, int8_t* quanted_weight_data, ScaleT* scale_data, const std::vector& shape, const int32_t arch, const std::string& algo) { int64_t total_k = shape[0]; int64_t total_n = shape[1]; int64_t numel = total_k * total_n; constexpr int kWarpSize = 32; constexpr int kBlockSize = 64; constexpr int kWarpNum = kBlockSize / kWarpSize; constexpr int kVectorSize = 128 / sizeof(T) / 8; PADDLE_ENFORCE_EQ(total_n % kVectorSize, 0, common::errors::PreconditionNotMet( "Currently, weight_quant_gpu kernel only support n " "with multiple of %d, please use", kVectorSize)); int64_t vec_total_n = total_n / kVectorSize; int64_t kGridSize = max((vec_total_n + kBlockSize - 1) / kBlockSize, int64_t(1)); if (algo == "weight_only_int4") { #ifdef PADDLE_WITH_HIP per_channel_quant_gpu_int4_row_pack <<>>( weight_data, quanted_weight_data, scale_data, total_k, vec_total_n); #else if ((arch == 100) || (arch == 90) || (arch == 89) || (arch == 86) || (arch == 80) || (arch == 75)) { per_channel_quant_gpu_int4_col_pack <<>>(weight_data, quanted_weight_data, scale_data, total_k, vec_total_n); } else if ((arch == 70)) { per_channel_quant_gpu_int4_row_pack <<>>(weight_data, quanted_weight_data, scale_data, total_k, vec_total_n); } #endif } else { per_channel_quant_gpu<<>>( weight_data, quanted_weight_data, scale_data, total_k, vec_total_n); } } template __global__ void weight_permute_transpose_interleave_kernel_w4a8( const int8_t* input_data_ptr, int8_t* output_data_ptr, IndexT original_k, IndexT original_n) { // every 2 k-direction 8bit , ie 4 k-direction 4bit, // is packed to 2 int8, and assigned to a new new_index. // so here / 4. IndexT numel = original_k * original_n / 4; CUDA_KERNEL_LOOP_TYPE(linear_idx, numel, IndexT) { const IndexT k_group_id = linear_idx / original_n; const IndexT n_id = linear_idx % original_n; uint16_t res = 0; for (int j = 0; j < 2; j++) { const IndexT k_id = k_group_id * 2 + j; uint16_t val = input_data_ptr[k_id * original_n + n_id]; val = val & 0xFF; val = val << (j * 8); res |= val; } constexpr int map[8] = {0, 2, 4, 6, 1, 3, 5, 7}; // remember output(in 16 bit granularity)'shape is // [16, 4, original_k/64, original_n/4] // index is : // [k_group_id % 16, n_id % 4, k_group_id/16, n_id/4] const IndexT new_index = map[k_group_id % 8] + k_group_id % 16 / 8 * 8 + (n_id % 4) * 16 + k_group_id / 16 * (16 * 4) + n_id / 4 * (original_k); reinterpret_cast(output_data_ptr)[new_index] = res; } } __global__ void w4a8_inplace_permute(uint32_t* output_data_ptr, int64_t numel) { CUDA_KERNEL_LOOP_TYPE(linear_idx, numel, int64_t) { const uint32_t value = output_data_ptr[linear_idx]; uint32_t res = 0; const int map[8] = {0, 2, 4, 6, 1, 3, 5, 7}; for (int i = 0; i < 8; i++) { uint32_t tmp = value >> (i * 4); tmp = tmp & 0x0F; tmp = tmp << (map[i] * 4); res |= tmp; } output_data_ptr[linear_idx] = res; } } template void weight_permute_gpu_w4a8(const GPUContext& dev_ctx, const int8_t* input_data, int8_t* output_data, const std::vector& shape, const int32_t arch, const std::string& algo) { auto original_k = shape[0] * 2; auto original_n = shape[1]; auto original_numel = original_k * original_n; auto gpu_config = backends::gpu::GetGpuLaunchConfig1D(dev_ctx, original_numel, 1); int grid_size = gpu_config.GetGridSize(); VLOG(2) << "weight_permute_gpu: original_k = " << original_k << "original_n = " << original_n << "grid size = " << grid_size; if (arch > 70) { if (algo == "w4a8") { dim3 block_dim(128); if (original_numel <= std::numeric_limits::max()) { weight_permute_transpose_interleave_kernel_w4a8 <<>>( input_data, output_data, original_k, original_n); } else { weight_permute_transpose_interleave_kernel_w4a8 <<>>( input_data, output_data, original_k, original_n); } w4a8_inplace_permute<<>>( reinterpret_cast(output_data), original_numel / 8); } } else { common::errors::Unimplemented( "The algo %s support need arch > 70, but got algo = %d.", algo, arch); } } template __global__ void weight_permute_interleave_kernelw4afp8(const int8_t* input_data, int8_t* output_data, IndexT original_k, IndexT original_n) { IndexT numel = original_k * original_n / 4; const IndexT pack_group_size = 64; const IndexT thread_group_size = pack_group_size / 4; // 16 const IndexT thread_k_stride = original_k / 4; CUDA_KERNEL_LOOP_TYPE(linear_idx, numel, IndexT) { const IndexT n_id = linear_idx / thread_k_stride; const IndexT k_id = linear_idx % thread_k_stride; const IndexT k_group_idx = k_id / thread_group_size; const IndexT k_idx_in_group = k_id % thread_group_size; const int8_t* src = input_data + k_group_idx * pack_group_size / 2 * original_n + k_idx_in_group * original_n + n_id; int8_t tmp0 = src[0]; int8_t tmp1 = src[pack_group_size / 4 * original_n]; int8_t tmp00 = (tmp0 & 0xF0) + 112; int8_t tmp01 = ((tmp0 << 4) & 0xF0) + 112; int8_t tmp10 = (tmp1 & 0xF0) + 112; int8_t tmp11 = ((tmp1 << 4) & 0xF0) + 112; uint8_t utmp00 = *(reinterpret_cast(&tmp00)); uint8_t utmp01 = *(reinterpret_cast(&tmp01)); uint8_t utmp10 = *(reinterpret_cast(&tmp10)); uint8_t utmp11 = *(reinterpret_cast(&tmp11)); int8_t dst0 = (utmp01 & 0xF0) | ((utmp11 & 0xF0) >> 4); int8_t dst1 = (utmp00 & 0xF0) | ((utmp10 & 0xF0) >> 4); int8_t* dst = output_data + n_id * original_k / 2 + (k_group_idx * pack_group_size / 2) + k_idx_in_group * 2; dst[0] = dst0; dst[1] = dst1; } } template void weight_permute_gpu_w4afp8(const GPUContext& dev_ctx, const int8_t* input_data, int8_t* output_data, const std::vector& shape, const int32_t arch, const std::string& algo) { auto original_k = shape[0] * 2; auto original_n = shape[1]; auto original_numel = original_k * original_n; auto gpu_config = backends::gpu::GetGpuLaunchConfig1D(dev_ctx, original_numel, 1); int grid_size = gpu_config.GetGridSize(); VLOG(2) << "weight_permute_gpu: original_k = " << original_k << "original_n = " << original_n << "grid size = " << grid_size; if (arch > 70) { if (algo == "w4afp8") { dim3 block_dim(128); if (original_numel <= std::numeric_limits::max()) { weight_permute_interleave_kernelw4afp8<<>>( input_data, output_data, original_k, original_n); } else { weight_permute_interleave_kernelw4afp8 <<>>( input_data, output_data, original_k, original_n); } } } else { common::errors::Unimplemented( "The algo %s support need arch > 70, but got algo = %d.", algo, arch); } } } // namespace phi