Files
paddlepaddle--paddle/paddle/phi/kernels/impl/weight_quantize_kernel_impl.h
T
2026-07-13 12:40:42 +08:00

442 lines
17 KiB
C++

/*
* 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 <typename T>
inline T xabs(const T x) {
return x < static_cast<T>(0.0) ? -x : x;
}
template <typename T, typename ScaleT>
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<float>(input[i]);
for (size_t j = 0; j < m; ++j) {
max = static_cast<float>(xabs(input[j * n + i])) > max
? static_cast<float>(xabs(input[j * n + i]))
: max;
}
scale[i] = static_cast<ScaleT>(max / bound);
}
}
template <typename T, typename ScaleT>
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<float>(0.f);
for (size_t k = 0; k < group_size && j + k < m; ++k) {
max = static_cast<float>(xabs(input[(j + k) * n + i])) > max
? static_cast<float>(xabs(input[(j + k) * n + i]))
: max;
}
scale[static_cast<int>(j / group_size) * n + i] =
static_cast<ScaleT>(max / bound);
}
}
}
template <typename T, int quant_bit = 8, typename ScaleT>
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<float>(scale[jj]);
const float weight_elt = static_cast<float>(current_weight_row[jj]);
const float scaled_weight = round(weight_elt / col_scale);
const int8_t clipped_weight = static_cast<int8_t>(
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<float>(scale[input_idx]);
const float weight_elt =
static_cast<float>(current_weight_row[input_idx]);
const float scaled_weight = round(weight_elt / col_scale);
int int_weight = static_cast<int>(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 <typename T, int quant_bit = 8, typename ScaleT>
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<float>(scale[scale_cur_offset]);
const float weight_elt = static_cast<float>(current_weight_row[jj]);
const float scaled_weight = round(weight_elt / col_scale);
const int8_t clipped_weight = static_cast<int8_t>(
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<float>(scale[scale_cur_offset]);
const float weight_elt =
static_cast<float>(current_weight_row[input_idx]);
const float scaled_weight = round(weight_elt / col_scale);
int int_weight = static_cast<int>(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 <int quant_bit = 8>
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<int8_t>(static_cast<int>(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<uint32_t*>(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 <int quant_bit>
void permute_B_rows_for_mixed_gemm(int8_t* permuted_quantized_tensor,
const int8_t* quantized_tensor,
const std::vector<size_t>& 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<const uint32_t*>(quantized_tensor);
uint32_t* output_byte_ptr =
reinterpret_cast<uint32_t*>(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<int64_t>(read_row) * num_vec_cols + read_col;
const int64_t write_offset =
static_cast<int64_t>(write_row) * num_vec_cols + write_col;
output_byte_ptr[write_offset] = input_byte_ptr[read_offset];
}
}
}
}
template <int quant_bit>
void subbyte_transpose_impl(int8_t* transposed_quantized_tensor,
const int8_t* quantized_tensor,
const std::vector<size_t>& 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<const uint8_t*>(quantized_tensor);
uint8_t* output_byte_ptr =
reinterpret_cast<uint8_t*>(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 <int quant_bit>
void interleave_column_major_tensor(int8_t* interleaved_quantized_tensor,
const int8_t* quantized_tensor,
const std::vector<size_t>& 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<const uint32_t*>(quantized_tensor);
uint32_t* output_byte_ptr =
reinterpret_cast<uint32_t*>(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