Files
deepspeedai--deepspeed/csrc/includes/quantization_utils.h
T
2026-07-13 13:18:33 +08:00

470 lines
17 KiB
C++

// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0
// DeepSpeed Team
#include <cassert>
#include "conversion_utils.h"
#include "ds_kernel_utils.h"
#include "memory_access_utils.h"
#include "quantization.h"
#include "reduction_utils.h"
#pragma once
using rop = reduce::ROpType;
namespace quantize {
constexpr int granularity = 16;
constexpr int h_per_load = granularity / sizeof(__half);
constexpr int h2_per_load = granularity / sizeof(__half2);
constexpr int max_threads = 1024;
/*
Class to hold the quantization parameters for a given tensor.
Holds the implementation of the quantization operation.
*/
template <Type qType, int numBits>
class Params {
public:
/*
Quantization implementation, supports
1) 4 Bit
2) 8 Bit
3) Symmetric
4) Asymmetric
Function Arguments :
val : The __half value to quantize.
*/
DS_D_INLINE int8_t quantize(__half val);
template <typename T>
DS_D_INLINE T dequantize(int8_t val);
DS_D_INLINE void store(float* params, int group_index);
// Initialize from memory
DS_D_INLINE Params(const float* params, int group_index);
};
template <int numBits>
class Params<Type::Symmetric, numBits> {
public:
float scale;
DS_D_INLINE Params(float max)
{
if (max == 0) {
scale = 1.0;
} else {
scale = (1 << numBits) / (2 * max);
}
}
DS_D_INLINE int8_t quantize(__half val)
{
constexpr int32_t q_min = -(1 << (numBits - 1));
constexpr int32_t q_max = (1 << (numBits - 1)) - 1;
float val_f = conversion::to<float>(val) * scale;
int32_t data_i32 = conversion::to<int32_t>(val_f);
data_i32 = min(max(data_i32, q_min), q_max);
return (int8_t)data_i32;
}
template <typename T>
DS_D_INLINE T dequantize(int8_t val)
{
const float val_deq_f = conversion::to<float>(val) * scale;
return conversion::to<T>(val_deq_f);
}
DS_D_INLINE void store(float* params, int group_index)
{
const float store_scale = 1 / scale;
mem_access::store_global<sizeof(float)>(params + group_index, &store_scale);
}
DS_D_INLINE Params(const float* params, int group_index)
{
mem_access::load_global<sizeof(float)>(&scale, params + group_index);
}
};
template <int numBits>
class Params<Type::Asymmetric, numBits> {
public:
float scale;
float offset;
DS_D_INLINE Params(float max, float min)
{
if (max == min) {
scale = 1.0;
} else {
scale = ((1 << numBits)) / (max - min);
}
offset = (max + min) / 2;
}
DS_D_INLINE int8_t quantize(__half val)
{
constexpr int32_t q_min = -(1 << (numBits - 1));
constexpr int32_t q_max = (1 << (numBits - 1)) - 1;
float val_f = (conversion::to<float>(val) - offset) * scale;
int32_t data_i32 = conversion::to<int32_t>(val_f);
data_i32 = min(max(data_i32, q_min), q_max);
return (int8_t)data_i32;
}
template <typename T>
DS_D_INLINE T dequantize(int8_t val)
{
const float val_deq_f = ((conversion::to<float>(val)) * scale) + offset;
return conversion::to<T>(val_deq_f);
}
DS_D_INLINE void store(float* params, int group_index)
{
// Codegen should turn this into stg.64
const float store_scale = 1 / scale;
mem_access::store_global<sizeof(float)>(params + 2 * group_index, &store_scale);
mem_access::store_global<sizeof(float)>(params + 2 * group_index + 1, &offset);
}
DS_D_INLINE Params(const float* params, int group_index)
{
// Codegen should turn this into ldg.64
mem_access::load_global<sizeof(float)>(&scale, params + 2 * group_index);
mem_access::load_global<sizeof(float)>(&offset, params + 2 * group_index + 1);
}
};
/*
Group stats tracks the necessary statistics about the quantized group
to abstract the particulars for the main loop.
*/
template <Type qType>
class GroupStats {
public:
DS_D_INLINE void update(__half2 val);
DS_D_INLINE void reduce(cg::thread_block& tb, cg::thread_block_tile<hw_warp_size>& warp);
};
template <>
class GroupStats<Type::Symmetric> {
public:
// Symmetric quantization only tracks the maximum absolute value
__half2 cur_max;
float max;
/*
Technically, this would give bad results if there
are 0 values to process since the reduction would
give -inf instead of 0. We do not consider this
to be a reasonable edge case.
*/
DS_D_INLINE GroupStats() { cur_max = reduce::init<rop::Max, __half2>(); }
/*
Updated the running absmax used to calculate params.
Function Arguments :
val : The __half2 value to update the running min and max with.
*/
DS_D_INLINE void update(__half2 val)
{
cur_max = reduce::element<rop::Max>(cur_max, __habs2(val));
}
/*
Function to return calculated quantization params.
Template Arguments :
numBits - Number of bits in quantized element. int : 8 or 4
Function Arguments :
tb - Threadblock object. cg::thread_block
warp - Warp object. cg::thread_block_tile<hw_warp_size>
*/
template <int numBits, int threads_per_group>
DS_D_INLINE Params<Type::Symmetric, numBits> get_params(
cg::thread_block& tb,
cg::thread_block_tile<hw_warp_size>& warp)
{
const float2 partial_max = conversion::to<float2>(cur_max);
float max = reduce::element<rop::Max>(partial_max.x, partial_max.y);
reduce::partitioned_block<rop::Max, threads_per_group>(tb, warp, max);
Params<Type::Symmetric, numBits> params(max);
return params;
}
};
template <>
class GroupStats<Type::Asymmetric> {
public:
__half2 cur_max;
__half2 cur_min;
/*
Initialize cur_max to -inf, cur_min to inf since
we are doing a true range analysis.
*/
DS_D_INLINE GroupStats()
{
cur_max = reduce::init<rop::Max, __half2>();
cur_min = reduce::init<rop::Min, __half2>();
}
/*
Updated the running min and max used to calculate params.
Function Arguments :
val : The __half2 value to update the running min and max with.
*/
DS_D_INLINE void update(__half2 val)
{
cur_max = reduce::element<rop::Max>(cur_max, val);
cur_min = reduce::element<rop::Min>(cur_min, val);
}
/*
Function to return calculated quantization params.
Template Arguments :
numBits - Number of bits in quantized element. int : 8 or 4
Function Arguments :
tb - Threadblock object. cg::thread_block
warp - Warp object. cg::thread_block_tile<hw_warp_size>
*/
template <int numBits, int threads_per_group>
DS_D_INLINE Params<Type::Asymmetric, numBits> get_params(
cg::thread_block& tb,
cg::thread_block_tile<hw_warp_size>& warp)
{
const float2 partial_max = conversion::to<float2>(cur_max);
float max = reduce::element<rop::Max>(partial_max.x, partial_max.y);
const float2 partial_min = conversion::to<float2>(cur_min);
float min = reduce::element<rop::Min>(partial_min.x, partial_min.y);
reduce::partitioned_block<rop::Max, rop::Min, threads_per_group>(tb, warp, max, min);
Params<Type::Asymmetric, numBits> params(max, min);
return params;
}
};
/*
Device function that quantizes 16 bytes of __half type input data.
Template Arguments :
numBits - Number of bits in quantized element. int : 8 or 4
qType - Type of quantization to perform. Type::Symmetric or Type::Asymmetric
Function Arguments :
local_output - Pointer to local memory to store quantized data. int8_t*
data - Pointer to input data. __half*
Params - Parameters for quantization. Params<qType, numBits>
*/
template <int numBits, Type qType>
DS_D_INLINE void _chunk(int8_t* local_output, const __half* data, Params<qType, numBits> q_params);
/*
Device function that quantizes 16 bytes of __half2 type input data.
Template Arguments :
numBits - Number of bits in quantized element. int : 8 or 4
qType - Type of quantization to perform. Type::Symmetric or Type::Asymmetric
Function Arguments :
local_output - Pointer to local memory to store quantized data. int8_t*
data - Pointer to input data. __half2*
Params - Parameters for quantization. Params<qType, numBits>
*/
template <int numBits, Type qType>
DS_D_INLINE void _chunk(int8_t* local_output, const __half2* data, Params<qType, numBits> q_params);
/*
Helper function to do serial reduction on register-file arrays.
Template Arguments :
qType - Type of quantization to perform. Type::Symmetric or Type::Asymmetric
numChunks - Number of bits in quantized element. int : 8 or 4
Function Arguments :
local_buffer - Pointer memory with input half2 data to be quantized.
*/
template <Type qType, int numChunks>
DS_D_INLINE GroupStats<qType> _local_serial_reduce(__half2* local_buffer);
/*
The main loop of the kernel that quantizes array in local memory of __half2 type input data, when
Quantization parameters are pre-computed.
Template Arguments :
qType - Type of quantization to perform. Type::Symmetric or Type::Asymmetric
numBits - Number of bits in quantized element. int : 8 or 4
numChunks - Number of chunks(16 bytes of Input data). int : 8 or 4
Function Arguments :
local_buffer - Pointer memory with input half2 data to be quantized.
scales - Pointer to output scales.
offsets - Pointer to output offsets.
output_data - Pointer to output data.
elems_per_group - Number of elements to quantize in a group.
q_params - Quantization parameters.
*/
template <int numBits, Type qType, int numChunks, int threads_per_group, int max_threads>
DS_D_INLINE void local_array(cg::thread_block& tb,
cg::thread_block_tile<hw_warp_size>& warp,
__half2* local_buffer,
float* __restrict__ scales,
float* __restrict__ offsets,
int8_t* __restrict__ output_data,
const int& elems_per_group,
const int& groups,
Params<qType, numBits> q_params);
/*
The main loop of the kernel that quantizes array in local memory of __half2 type input data.
This function computes quantization parameters for each group.
Template Arguments :
qType - Type of quantization to perform. Type::Symmetric or Type::Asymmetric
numBits - Number of bits in quantized element. int : 8 or 4
numChunks - Number of chunks(16 bytes of Input data). int : 8 or 4
Function Arguments :
local_buffer - Pointer memory with input half2 data to be quantized.
scales - Pointer to output scales.
offsets - Pointer to output offsets.
output_data - Pointer to output data.
elems_per_group - Number of elements to quantize in a group.
*/
template <Type qType, int numBits, int numChunks, int threads_per_group, int max_threads>
__device__ void local_array(__half2* local_buffer,
float* __restrict__ scales,
float* __restrict__ offsets,
int8_t* __restrict__ output_data,
const int& elems_per_group,
const int& groups);
template <int numBits, Type qType>
DS_D_INLINE void _chunk(int8_t* local_output, const __half* data, Params<qType, numBits> q_params)
{
constexpr int32_t elems = 16 / sizeof(__half);
constexpr int32_t num_elems_packed = 8 / numBits;
#pragma unroll
for (int i = 0, oi = 0; i < elems; i += num_elems_packed, oi++) {
if (num_elems_packed == 1) {
// TODO(cmikeh2): refactor to use conversion utils
local_output[i] = q_params.quantize(data[i]);
} else if (num_elems_packed == 2) {
int8_t data_i8_1 = q_params.quantize(data[i]);
int8_t data_i8_2 = q_params.quantize(data[i + 1]);
auto data_i8 = PackedInt4{data_i8_2, data_i8_1};
local_output[oi] = *((int8_t*)(&data_i8));
}
}
}
template <int numBits, Type qType>
DS_D_INLINE void _chunk(int8_t* local_output, const __half2* data, Params<qType, numBits> q_params)
{
const __half* data_cast = reinterpret_cast<const __half*>(data);
_chunk<numBits>(local_output, data_cast, q_params);
}
template <Type qType, int numChunks>
DS_D_INLINE GroupStats<qType> _local_serial_reduce(__half2* local_buffer)
{
GroupStats<qType> stats;
#pragma unroll
for (int i = 0; i < numChunks * h2_per_load; i++) { stats.update(local_buffer[i]); }
return stats;
}
template <Type qType, int numBits, int numChunks, int threads_per_group, int max_threads>
DS_D_INLINE void local_array(cg::thread_block& tb,
cg::thread_block_tile<hw_warp_size>& warp,
__half2* local_buffer,
float* __restrict__ global_params,
int8_t* __restrict__ output_data,
const int& elems_per_group,
const int& groups,
Params<qType, numBits> q_params)
{
constexpr int num_ele_int8 = 8 / numBits;
constexpr int num_int8_out = quantize::h_per_load / num_ele_int8;
// Indexing offsets
const int block_num =
(tb.group_index().x * max_threads / threads_per_group) + tb.thread_index().y;
const int block_offset = block_num * elems_per_group;
const int elem_offset = tb.thread_index().x * quantize::h_per_load;
const int base_offset = (block_offset + elem_offset) / num_ele_int8;
const int stride = tb.size() * quantize::h_per_load / num_ele_int8;
int8_t local_output[num_int8_out];
if (tb.thread_index().x == 0 && block_num < groups) {
q_params.store(
global_params,
(tb.group_index().x * max_threads / threads_per_group) + tb.thread_index().y);
}
#pragma unroll
for (int i = 0; i < numChunks; i++) {
if (elem_offset + i * stride * num_ele_int8 < elems_per_group && block_num < groups) {
quantize::_chunk<numBits, qType>(
local_output, local_buffer + i * quantize::h2_per_load, q_params);
mem_access::store_global<num_int8_out>(output_data + (base_offset + i * stride),
local_output);
}
}
}
template <Type qType, int numBits, int numChunks, int threads_per_group, int max_threads>
DS_D_INLINE void local_array(cg::thread_block& tb,
cg::thread_block_tile<hw_warp_size>& warp,
__half* local_buffer,
float* __restrict__ global_params,
int8_t* __restrict__ output_data,
const int& elems_per_group,
const int& groups,
Params<qType, numBits> q_params)
{
__half2* local_buffer_h2 = reinterpret_cast<__half2*>(local_buffer);
quantize::local_array<qType, numBits, numChunks, threads_per_group, max_threads>(
tb, warp, local_buffer, global_params, output_data, elems_per_group, groups, q_params);
}
template <Type qType,
int numBits,
int numChunks,
int threads_per_group = max_threads,
int max_threads = 256>
__device__ void local_array(__half2* local_buffer,
float* __restrict__ global_params,
int8_t* __restrict__ output_data,
const int& elems_per_group,
const int& groups)
{
cg::thread_block tb = cg::this_thread_block();
cg::thread_block_tile<hw_warp_size> warp = cg::tiled_partition<hw_warp_size>(tb);
auto group_stats = _local_serial_reduce<qType, numChunks>(local_buffer);
auto params = group_stats.template get_params<numBits, threads_per_group>(tb, warp);
quantize::local_array<qType, numBits, numChunks, threads_per_group, max_threads>(
tb, warp, local_buffer, global_params, output_data, elems_per_group, groups, params);
}
template <Type qType, int numBits, int numChunks, int threads_per_group, int max_threads>
__device__ void local_array(__half* local_buffer,
float* __restrict__ global_params,
int8_t* __restrict__ output_data,
const int& elems_per_group,
const int& groups)
{
__half2* local_buffer_h2 = reinterpret_cast<__half2*>(local_buffer);
quantize::local_array<qType, numBits, numChunks, threads_per_group, max_threads>(
local_buffer_h2, global_params, output_data, elems_per_group, groups);
}
} // namespace quantize