/* * Copyright (c) 2019-2023, NVIDIA CORPORATION. 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. */ // 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 "paddle/phi/backends/cpu/cpu_context.h" #include "paddle/phi/core/dense_tensor.h" #include "paddle/phi/core/kernel_registry.h" #include "paddle/phi/kernels/funcs/common_shape.h" namespace phi { template inline T xabs(const T x) { return x < static_cast(0.0) ? -x : x; } template void per_channel_scale( ScaleT* scale, const T* input, size_t m, size_t n, float bound) { for (size_t i = 0; i < n; ++i) { float max = static_cast(input[i]); for (size_t j = 0; j < m; ++j) { max = static_cast(xabs(input[j * n + i])) > max ? static_cast(xabs(input[j * n + i])) : max; } scale[i] = static_cast(max / bound); } } template void group_wise_scale(ScaleT* scale, const T* input, size_t m, size_t n, float bound, size_t group_size) { for (size_t i = 0; i < n; ++i) { for (size_t j = 0; j < m; j += group_size) { float max = static_cast(0.f); for (size_t k = 0; k < group_size && j + k < m; ++k) { max = static_cast(xabs(input[(j + k) * n + i])) > max ? static_cast(xabs(input[(j + k) * n + i])) : max; } scale[static_cast(j / group_size) * n + i] = static_cast(max / bound); } } } template void per_channel_quant(int8_t* output, const T* input, const ScaleT* scale, size_t num_rows, size_t num_cols) { size_t bytes_per_out_col = num_cols * quant_bit / 8; for (size_t ii = 0; ii < num_rows; ++ii) { int8_t* current_quantized_weight_row = output + ii * bytes_per_out_col; const T* current_weight_row = input + ii * num_cols; for (size_t jj = 0; jj < bytes_per_out_col; ++jj) { if (quant_bit == 8) { const float col_scale = static_cast(scale[jj]); const float weight_elt = static_cast(current_weight_row[jj]); const float scaled_weight = round(weight_elt / col_scale); const int8_t clipped_weight = static_cast( std::max(-127.f, std::min(127.f, scaled_weight))); current_quantized_weight_row[jj] = clipped_weight; } else if (quant_bit == 4) { // We will pack two int4 elements per iteration of the inner loop. int8_t packed_int4s = 0; for (int packed_idx = 0; packed_idx < 2; ++packed_idx) { const size_t input_idx = 2 * jj + packed_idx; if (input_idx < num_cols) { const float col_scale = static_cast(scale[input_idx]); const float weight_elt = static_cast(current_weight_row[input_idx]); const float scaled_weight = round(weight_elt / col_scale); int int_weight = static_cast(scaled_weight); #ifdef PADDLE_WITH_HIP const int8_t clipped_weight = std::max(-7, std::min(7, int_weight)) + 8; #else const int8_t clipped_weight = std::max(-7, std::min(7, int_weight)); #endif // Kill the sign extension bits (hence 0x0F mask) then shift to // upper bits if packing the second int4 and or the bits into the // final result. packed_int4s |= ((clipped_weight & 0x0F) << (4 * packed_idx)); } } current_quantized_weight_row[jj] = packed_int4s; } else { common::errors::Unimplemented("Unsupported quantization bits: %d", quant_bit); } } } } template void group_wise_quant(int8_t* output, const T* input, const ScaleT* scale, size_t num_rows, size_t num_cols, const int group_size) { size_t bytes_per_out_col = num_cols * quant_bit / 8; for (size_t ii = 0; ii < num_rows; ++ii) { int8_t* current_quantized_weight_row = output + ii * bytes_per_out_col; const T* current_weight_row = input + ii * num_cols; for (size_t jj = 0; jj < bytes_per_out_col; ++jj) { if (quant_bit == 8) { size_t scale_cur_offset = jj + (ii / group_size) * num_cols; const float col_scale = static_cast(scale[scale_cur_offset]); const float weight_elt = static_cast(current_weight_row[jj]); const float scaled_weight = round(weight_elt / col_scale); const int8_t clipped_weight = static_cast( std::max(-127.f, std::min(127.f, scaled_weight))); current_quantized_weight_row[jj] = clipped_weight; } else if (quant_bit == 4) { // We will pack two int4 elements per iteration of the inner loop. int8_t packed_int4s = 0; for (int packed_idx = 0; packed_idx < 2; ++packed_idx) { const size_t input_idx = 2 * jj + packed_idx; if (input_idx < num_cols) { size_t scale_cur_offset = input_idx + (ii / group_size) * num_cols; const float col_scale = static_cast(scale[scale_cur_offset]); const float weight_elt = static_cast(current_weight_row[input_idx]); const float scaled_weight = round(weight_elt / col_scale); int int_weight = static_cast(scaled_weight); #ifdef PADDLE_WITH_HIP const int8_t clipped_weight = std::max(-7, std::min(7, int_weight)) + 8; #else const int8_t clipped_weight = std::max(-7, std::min(7, int_weight)); #endif // Kill the sign extension bits (hence 0x0F mask) then shift to // upper bits if packing the second int4 and or the bits into the // final result. packed_int4s |= ((clipped_weight & 0x0F) << (4 * packed_idx)); } } current_quantized_weight_row[jj] = packed_int4s; } else { common::errors::Unimplemented("Unsupported quantization bits: %d", quant_bit); } } } } template void add_bias_and_interleave_inplace(int8_t* tensor_ptr, size_t num_elts) { const size_t num_bytes = num_elts * quant_bit / 8; for (size_t ii = 0; ii < num_bytes; ++ii) { if (quant_bit == 8) { tensor_ptr[ii] = static_cast(static_cast(tensor_ptr[ii]) + 128); } else { int8_t transformed_packed_int4s = 0; int8_t transformed_first_elt = (int8_t(tensor_ptr[ii] << 4) >> 4) + 8; // The double shift here is to ensure sign extension int8_t transformed_second_elt = (tensor_ptr[ii] >> 4) + 8; if (!(transformed_first_elt >= 0 && transformed_first_elt <= 15)) { common::errors::InvalidArgument( "Illegal result for int4 transform (first elt)"); } if (!(transformed_second_elt >= 0 && transformed_second_elt <= 15)) { common::errors::InvalidArgument( "Illegal result for int4 transform (second elt)"); } // We don't need to mask in these ops since everything should be in the // range 0-15 transformed_packed_int4s |= transformed_first_elt; transformed_packed_int4s |= (transformed_second_elt << 4); tensor_ptr[ii] = transformed_packed_int4s; } } if (quant_bit == 8) { for (size_t base = 0; base < num_elts; base += 4) { std::swap(tensor_ptr[base + 1], tensor_ptr[base + 2]); } } else { const size_t num_registers = num_bytes / 4; uint32_t* register_ptr = reinterpret_cast(tensor_ptr); for (size_t ii = 0; ii < num_registers; ++ii) { const uint32_t current_register = register_ptr[ii]; uint32_t transformed_register = 0; for (int dest_idx = 0; dest_idx < 8; ++dest_idx) { const int src_idx = dest_idx < 4 ? 2 * dest_idx : 2 * (dest_idx - 4) + 1; const int src_shift = 4 * src_idx; const int dest_shift = 4 * dest_idx; const uint32_t src_bits = (current_register >> src_shift) & 0xF; transformed_register |= (src_bits << dest_shift); } register_ptr[ii] = transformed_register; } } } template void permute_B_rows_for_mixed_gemm(int8_t* permuted_quantized_tensor, const int8_t* quantized_tensor, const std::vector& shape) { // We only want to run this step for weight only quant. const size_t num_rows = shape.size() == 2 ? shape[0] : shape[1]; const size_t num_cols = shape.size() == 2 ? shape[1] : shape[2]; const int BITS_PER_ELT = quant_bit; const int K = 16 / BITS_PER_ELT; const int ELTS_PER_REG = 32 / BITS_PER_ELT; const uint32_t* input_byte_ptr = reinterpret_cast(quantized_tensor); uint32_t* output_byte_ptr = reinterpret_cast(permuted_quantized_tensor); int B_ROWS_PER_MMA = 8 * K; const int elts_in_int32 = 32 / BITS_PER_ELT; const int num_vec_cols = num_cols / elts_in_int32; // The code is written as below so it works for both int8 and packed int4. for (size_t base_row = 0; base_row < num_rows; base_row += B_ROWS_PER_MMA) { for (int tile_row = 0; tile_row < B_ROWS_PER_MMA; ++tile_row) { for (int write_col = 0; write_col < num_vec_cols; ++write_col) { const int write_row = base_row + tile_row; const int tile_read_row = 8 * (((tile_row % ELTS_PER_REG) / 2)) + tile_row % 2 + 2 * (tile_row / ELTS_PER_REG); const int read_row = base_row + tile_read_row; const int read_col = write_col; const int64_t read_offset = static_cast(read_row) * num_vec_cols + read_col; const int64_t write_offset = static_cast(write_row) * num_vec_cols + write_col; output_byte_ptr[write_offset] = input_byte_ptr[read_offset]; } } } } template void subbyte_transpose_impl(int8_t* transposed_quantized_tensor, const int8_t* quantized_tensor, const std::vector& shape) { const int bits_per_elt = quant_bit; // FT_CHECK_WITH_INFO(shape.size() == 2 || shape.size() == 3, "Shape must be // 2-D or 3-D"); // const size_t num_experts = 1; const size_t num_rows = shape.size() == 2 ? shape[0] : shape[1]; const size_t num_cols = shape.size() == 2 ? shape[1] : shape[2]; const size_t col_bytes = num_cols * bits_per_elt / 8; const size_t col_bytes_trans = num_rows * bits_per_elt / 8; // const size_t num_bytes = size_t(num_experts) * num_rows * col_bytes; const uint8_t* input_byte_ptr = reinterpret_cast(quantized_tensor); uint8_t* output_byte_ptr = reinterpret_cast(transposed_quantized_tensor); static constexpr int ELTS_PER_BYTE = 8 / quant_bit; static constexpr int M_TILE_L1 = 64; static constexpr int N_TILE_L1 = M_TILE_L1 / ELTS_PER_BYTE; uint8_t cache_buf[M_TILE_L1][N_TILE_L1]; static constexpr int VECTOR_WIDTH = std::min(32, N_TILE_L1); // const int num_m_tiles = (num_rows + M_TILE_L1 - 1) / M_TILE_L1; // const int num_n_tiles = (col_bytes + N_TILE_L1 - 1) / N_TILE_L1; for (size_t row_tile_start = 0; row_tile_start < num_rows; row_tile_start += M_TILE_L1) { for (size_t col_tile_start_byte = 0; col_tile_start_byte < col_bytes; col_tile_start_byte += N_TILE_L1) { const int row_limit = std::min(row_tile_start + M_TILE_L1, num_rows); const int col_limit = std::min(col_tile_start_byte + N_TILE_L1, col_bytes); for (int ii = 0; ii < M_TILE_L1; ++ii) { const int row = row_tile_start + ii; for (int jj = 0; jj < N_TILE_L1; jj += VECTOR_WIDTH) { const int col = col_tile_start_byte + jj; const size_t logical_src_offset = row * col_bytes + col; if (row < row_limit && col < col_limit) { for (int v = 0; v < VECTOR_WIDTH; ++v) { cache_buf[ii][jj + v] = input_byte_ptr[logical_src_offset + v]; } } } } if (quant_bit == 8) { for (int ii = 0; ii < M_TILE_L1; ++ii) { for (int jj = ii + 1; jj < N_TILE_L1; ++jj) { std::swap(cache_buf[ii][jj], cache_buf[jj][ii]); } } } else if (quant_bit == 4) { for (int ii = 0; ii < M_TILE_L1; ++ii) { // Using M_TILE_L1 here is deliberate since we assume that the cache // tile is square in the number of elements (not necessarily the // number of bytes). for (int jj = ii + 1; jj < M_TILE_L1; ++jj) { const int ii_byte = ii / ELTS_PER_BYTE; const int ii_bit_offset = ii % ELTS_PER_BYTE; const int jj_byte = jj / ELTS_PER_BYTE; const int jj_bit_offset = jj % ELTS_PER_BYTE; uint8_t src_elt = 0xF & (cache_buf[ii][jj_byte] >> (4 * jj_bit_offset)); uint8_t tgt_elt = 0xF & (cache_buf[jj][ii_byte] >> (4 * ii_bit_offset)); cache_buf[ii][jj_byte] &= (0xF0 >> (4 * jj_bit_offset)); cache_buf[jj][ii_byte] &= (0xF0 >> (4 * ii_bit_offset)); cache_buf[ii][jj_byte] |= (tgt_elt << (4 * jj_bit_offset)); cache_buf[jj][ii_byte] |= (src_elt << (4 * ii_bit_offset)); } } } else { common::errors::Unimplemented("Unsupported quantization bits: %d", quant_bit); } const size_t row_tile_start_trans = col_tile_start_byte * ELTS_PER_BYTE; const size_t col_tile_start_byte_trans = row_tile_start / ELTS_PER_BYTE; const int row_limit_trans = std::min(row_tile_start_trans + M_TILE_L1, num_cols); const int col_limit_trans = std::min(col_tile_start_byte_trans + N_TILE_L1, col_bytes_trans); for (int ii = 0; ii < M_TILE_L1; ++ii) { const int row = row_tile_start_trans + ii; for (int jj = 0; jj < N_TILE_L1; jj += VECTOR_WIDTH) { const int col = col_tile_start_byte_trans + jj; const size_t logical_tgt_offset = row * col_bytes_trans + col; if (row < row_limit_trans && col < col_limit_trans) { for (int v = 0; v < VECTOR_WIDTH; ++v) { output_byte_ptr[logical_tgt_offset + v] = cache_buf[ii][jj + v]; } } } } } } } template void interleave_column_major_tensor(int8_t* interleaved_quantized_tensor, const int8_t* quantized_tensor, const std::vector& shape) { // We only want to run this step for weight only quant. const size_t num_rows = shape.size() == 2 ? shape[0] : shape[1]; const size_t num_cols = shape.size() == 2 ? shape[1] : shape[2]; const size_t BITS_PER_ELT = quant_bit; const size_t elts_in_int32 = 32 / BITS_PER_ELT; const size_t rows_per_tile = 64; const uint32_t* input_byte_ptr = reinterpret_cast(quantized_tensor); uint32_t* output_byte_ptr = reinterpret_cast(interleaved_quantized_tensor); const size_t num_vec_rows = num_rows / elts_in_int32; const size_t vec_rows_per_tile = rows_per_tile / elts_in_int32; const size_t interleave = 128 * 8 / quant_bit / rows_per_tile; for (size_t read_col = 0; read_col < num_cols; ++read_col) { const size_t write_col = read_col / interleave; for (size_t base_vec_row = 0; base_vec_row < num_vec_rows; base_vec_row += vec_rows_per_tile) { for (size_t vec_read_row = base_vec_row; vec_read_row < std::min(num_vec_rows, base_vec_row + vec_rows_per_tile); ++vec_read_row) { const size_t vec_write_row = interleave * base_vec_row + vec_rows_per_tile * (read_col % interleave) + vec_read_row % vec_rows_per_tile; const size_t read_offset = size_t(read_col) * num_vec_rows + vec_read_row; const size_t write_offset = size_t(write_col) * num_vec_rows * interleave + vec_write_row; output_byte_ptr[write_offset] = input_byte_ptr[read_offset]; } } } } } // namespace phi