365 lines
13 KiB
Python
365 lines
13 KiB
Python
# Copyright (c) 2024 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.
|
|
|
|
|
|
import numpy as np
|
|
import paddle
|
|
|
|
|
|
def cal_ratio(m, v, eps=1e-8):
|
|
"""
|
|
cal part adam update ratio.
|
|
Args:
|
|
m (`paddle.Tensor`):
|
|
moment in Adam optimizer.
|
|
v (`paddle.Tensor`):
|
|
variance in Adam optimizer.
|
|
eps (`int`):
|
|
epsilon in Adam optimizer.
|
|
"""
|
|
return 1 / (np.sqrt(v) + eps)
|
|
|
|
|
|
def group_wise_quant_dequant(
|
|
inputs,
|
|
mins=None,
|
|
maxs=None,
|
|
quant_bits=4,
|
|
group_size=32,
|
|
quant=True,
|
|
tp_rank=-1,
|
|
tp_degree=1,
|
|
use_pd=False,
|
|
symmetry=False,
|
|
):
|
|
"""
|
|
group-wise quantization (support symmetry, asymmetry).
|
|
Args:
|
|
inputs (`paddle.Tensor`):
|
|
The tensor to quantize.
|
|
mins (`paddle.Tensor`):
|
|
Min scales tensor in asymmetry quantization.
|
|
maxs (`paddle.Tensor`):
|
|
Max scales tensor in asymmetry quantization, or Abs max tensor in symmetry quantization.
|
|
quant_bits (`int`):
|
|
Quantization bits.
|
|
group_size (`int`):
|
|
Group size of group-wise quantization.
|
|
quant (`bool`):
|
|
True when quantization, False in dequantization.
|
|
tp_rank (`int`):
|
|
Tensor parallel rank.
|
|
tp_degree (`int`):
|
|
Tensor parallel world size.
|
|
use_pd (`bool`):
|
|
Whether to use paddle calculation. If False will use numpy.
|
|
symmetry (`bool`):
|
|
Whether to use symmetry quantization.
|
|
"""
|
|
|
|
qmax = (1 << (quant_bits)) - 1
|
|
qmin = 0
|
|
shape = inputs.shape
|
|
|
|
if quant:
|
|
inputs_processed = inputs.reshape([shape[0] // group_size, group_size, shape[1]])
|
|
if symmetry:
|
|
bnt = (1 << (quant_bits - 1)) - 1
|
|
scales = np.max(np.abs(inputs_processed), axis=1)
|
|
new_scales = np.repeat(scales, repeats=group_size, axis=0)
|
|
quant_tensor = np.clip(np.round(inputs / new_scales * bnt), -bnt - 1, bnt)
|
|
return quant_tensor.astype("int8"), scales
|
|
|
|
# scales: [shape[0] // group_size, shape[1]]
|
|
maxs = np.max(inputs_processed, axis=1)
|
|
mins = np.min(inputs_processed, axis=1)
|
|
scales = maxs - mins
|
|
# new_scales: [shape[0], shape[1]]
|
|
new_scales = np.repeat(scales, repeats=group_size, axis=0)
|
|
new_mins = np.repeat(mins, repeats=group_size, axis=0)
|
|
# add eps to avoid devide zero
|
|
quant_tensor = np.clip(np.round((inputs - new_mins) / (new_scales) * qmax), qmin, qmax)
|
|
quant_tensor = np.nan_to_num(quant_tensor)
|
|
return quant_tensor.astype("uint8"), mins, maxs
|
|
else:
|
|
if symmetry:
|
|
scales = mins
|
|
bnt = (1 << (quant_bits - 1)) - 1
|
|
if use_pd:
|
|
new_scales = paddle.repeat_interleave(scales, group_size, 0)
|
|
else:
|
|
new_scales = np.repeat(scales, repeats=group_size, axis=0)
|
|
|
|
if tp_rank == -1:
|
|
dequant_tensor = inputs.astype("float32") * new_scales / bnt
|
|
elif len(new_scales.shape) == 0 or inputs.shape[-1] == new_scales.shape[-1]:
|
|
# input tensor was row parallel in tp.
|
|
dequant_tensor = (
|
|
inputs.astype("float32")
|
|
* new_scales[
|
|
tp_rank * new_scales.shape[0] // tp_degree : (tp_rank + 1) * new_scales.shape[0] // tp_degree
|
|
]
|
|
/ bnt
|
|
)
|
|
else:
|
|
# input tensor was column parallel in tp.
|
|
dequant_tensor = (
|
|
inputs.astype("float32")
|
|
* new_scales[
|
|
:,
|
|
tp_rank
|
|
* new_scales.shape[-1]
|
|
// tp_degree : (tp_rank + 1)
|
|
* new_scales.shape[-1]
|
|
// tp_degree,
|
|
]
|
|
/ bnt
|
|
)
|
|
return dequant_tensor
|
|
|
|
scales = maxs - mins
|
|
if use_pd:
|
|
new_scales = paddle.repeat_interleave(scales, group_size, 0)
|
|
new_mins = paddle.repeat_interleave(mins, group_size, 0)
|
|
else:
|
|
new_scales = np.repeat(scales, repeats=group_size, axis=0)
|
|
new_mins = np.repeat(mins, repeats=group_size, axis=0)
|
|
|
|
if tp_rank == -1:
|
|
dequant_tensor = (inputs.astype("float32") / qmax * new_scales) + new_mins
|
|
elif len(new_scales.shape) == 0 or inputs.shape[-1] == new_scales.shape[-1]:
|
|
# input tensor was row parallel in tp.
|
|
dequant_tensor = (
|
|
inputs.astype("float32")
|
|
/ qmax
|
|
* new_scales[
|
|
tp_rank * new_scales.shape[0] // tp_degree : (tp_rank + 1) * new_scales.shape[0] // tp_degree
|
|
]
|
|
) + new_mins[tp_rank * new_mins.shape[0] // tp_degree : (tp_rank + 1) * new_mins.shape[0] // tp_degree]
|
|
else:
|
|
# input tensor was column parallel in tp.
|
|
dequant_tensor = (
|
|
inputs.astype("float32")
|
|
/ qmax
|
|
* new_scales[
|
|
:, tp_rank * new_scales.shape[-1] // tp_degree : (tp_rank + 1) * new_scales.shape[-1] // tp_degree
|
|
]
|
|
) + new_mins[
|
|
:, tp_rank * new_mins.shape[-1] // tp_degree : (tp_rank + 1) * new_mins.shape[-1] // tp_degree
|
|
]
|
|
return dequant_tensor
|
|
|
|
|
|
def merge_int4(x, y):
|
|
"""
|
|
merge 2 signed int4 to 1 int8
|
|
Args:
|
|
x (`numpy.array`):
|
|
4bits signed int x.
|
|
y (`numpy.array`):
|
|
4bits signed int y.
|
|
"""
|
|
int4_high = x << 4
|
|
int4_low = y & 0x0F
|
|
final = int4_high | int4_low
|
|
return final.astype("int8")
|
|
|
|
|
|
def split_int8(final):
|
|
"""
|
|
split an int8 to 2 int4 elems
|
|
Args:
|
|
final (`numpy.array`):
|
|
8bits signed int.
|
|
"""
|
|
int4_high = final >> 4
|
|
int4_low = final & 0x0F
|
|
|
|
int4_high = np.where(int4_high > 8, int4_high - 16, int4_high)
|
|
|
|
high_tensor = paddle.Tensor(int4_high)
|
|
low_tensor = paddle.Tensor(int4_low)
|
|
|
|
return high_tensor, low_tensor
|
|
|
|
|
|
def cal_abs_min_max_channel(inputs, quant_axis=1):
|
|
"""
|
|
channel-wise min max scales calculation
|
|
Args:
|
|
inputs (`numpy.array`):
|
|
input tensor for quantization.
|
|
quant_axis (`int`):
|
|
dimension where calculating inputs' abs min and max scales on.
|
|
"""
|
|
eps = 1e-8
|
|
reduce_axis = tuple([i for i in range(len(inputs.shape)) if i != quant_axis])
|
|
abs_max_values = np.max(inputs, axis=reduce_axis)
|
|
abs_min_values = np.min(inputs, axis=reduce_axis)
|
|
abs_max_values = np.where(
|
|
abs_max_values == np.array(0, dtype=inputs.dtype), np.array(eps, dtype=inputs.dtype), abs_max_values
|
|
)
|
|
abs_min_values = np.where(
|
|
abs_min_values == np.array(0, dtype=inputs.dtype), np.array(eps, dtype=inputs.dtype), abs_min_values
|
|
)
|
|
return abs_max_values, abs_min_values
|
|
|
|
|
|
def asymmetry_qdq_weight(
|
|
x, quant_bit=8, quant_axis=-1, mins=None, maxs=None, dequant=False, tp_rank=-1, tp_degree=1, use_pd=False
|
|
):
|
|
"""
|
|
channel-wise asymmetry quantization
|
|
Args:
|
|
x (`paddle.Tensor`):
|
|
The tensor to quantize.
|
|
quant_bits (`int`):
|
|
Quantization bits.
|
|
quant_axis (`int`):
|
|
Scales calculation axis.
|
|
mins (`paddle.Tensor`):
|
|
Min scales tensor in asymmetry quantization.
|
|
maxs (`paddle.Tensor`):
|
|
Max scales tensor in asymmetry quantization.
|
|
dequant (`bool`):
|
|
True when dequantization, False in quantization.
|
|
tp_rank (`int`):
|
|
Model parallel rank.
|
|
tp_degree (`int`):
|
|
Model parallel world size.
|
|
use_pd (`bool`):
|
|
Whether to use paddle calculation. If False will use numpy.
|
|
"""
|
|
|
|
if mins is None:
|
|
maxs, mins = cal_abs_min_max_channel(x)
|
|
bnt = (1 << (quant_bit)) - 1
|
|
scales = maxs - mins
|
|
if not dequant:
|
|
# quant
|
|
quant_x = np.clip(np.round((x - mins) / scales * bnt), 0, bnt)
|
|
return quant_x.astype(np.uint8), mins, maxs
|
|
else:
|
|
quant_x = x
|
|
# dequant
|
|
if not use_pd:
|
|
if len(scales.shape) == 0 or quant_x.shape[-1] == scales.shape[-1]:
|
|
# input tensor was row parallel in tp.
|
|
qdq_x = (quant_x / bnt * scales) + mins
|
|
else:
|
|
# input tensor was column parallel in tp.
|
|
qdq_x = (
|
|
quant_x
|
|
/ bnt
|
|
* scales[tp_rank * scales.shape[0] // tp_degree : (tp_rank + 1) * scales.shape[0] // tp_degree]
|
|
) + mins[tp_rank * mins.shape[0] // tp_degree : (tp_rank + 1) * mins.shape[0] // tp_degree]
|
|
return qdq_x.astype(np.float32), scales
|
|
else:
|
|
if len(scales.shape) == 0 or quant_x.shape[-1] == scales.shape[-1]:
|
|
# input tensor was row parallel in tp.
|
|
qdq_x = (quant_x / bnt * scales.unsqueeze(0).expand(quant_x.shape)) + mins
|
|
else:
|
|
# input tensor was column parallel in tp.
|
|
qdq_x = (
|
|
quant_x
|
|
/ bnt
|
|
* scales[tp_rank * scales.shape[0] // tp_degree : (tp_rank + 1) * scales.shape[0] // tp_degree]
|
|
.unsqueeze(0)
|
|
.expand(quant_x.shape)
|
|
) + mins[tp_rank * mins.shape[0] // tp_degree : (tp_rank + 1) * mins.shape[0] // tp_degree]
|
|
return qdq_x.astype(paddle.float32), scales
|
|
|
|
|
|
def cal_abs_max_channel(inputs, quant_axis=1):
|
|
"""
|
|
channel-wise abs max calculation
|
|
Args:
|
|
inputs (`numpy.array`):
|
|
input tensor for quantization.
|
|
quant_axis (`int`):
|
|
dimension where calculating inputs' abs max scales on.
|
|
"""
|
|
epsilon = 1e-8
|
|
reduce_axis = tuple([i for i in range(len(inputs.shape)) if i != quant_axis])
|
|
abs_max_values = np.max(np.abs(inputs), axis=reduce_axis)
|
|
# maybe all elements are zero in one group,
|
|
# so set the scales from those group to an actual number
|
|
# from divide 0.
|
|
abs_max_values = np.where(
|
|
abs_max_values == np.array(0, dtype=inputs.dtype), np.array(epsilon, dtype=inputs.dtype), abs_max_values
|
|
)
|
|
return abs_max_values
|
|
|
|
|
|
def qdq_weight(x, quant_bit=8, quant_axis=-1, scales=None, dequant=False, tp_rank=-1, tp_degree=1, use_pd=False):
|
|
"""
|
|
channel-wise symmetry quantization
|
|
Args:
|
|
x (`paddle.Tensor`):
|
|
The tensor to quantize.
|
|
quant_bits (`int`):
|
|
Quantization bits.
|
|
quant_axis (`int`):
|
|
Scales calculation axis.
|
|
scales (`paddle.Tensor`):
|
|
Abs max scales tensor in symmetry quantization.
|
|
dequant (`bool`):
|
|
True when dequantization, False in quantization.
|
|
tp_rank (`int`):
|
|
Model parallel rank.
|
|
tp_degree (`int`):
|
|
Model parallel world size.
|
|
use_pd (`bool`):
|
|
Whether to use paddle calculation. If False will use numpy.
|
|
"""
|
|
|
|
if scales is None:
|
|
scales = cal_abs_max_channel(x)
|
|
bnt = (1 << (quant_bit - 1)) - 1
|
|
if not dequant:
|
|
# quant
|
|
quant_x = np.clip(np.round(x / scales * bnt), -bnt - 1, bnt)
|
|
return quant_x.astype(np.int8), scales
|
|
else:
|
|
quant_x = x
|
|
# dequant
|
|
if not use_pd:
|
|
if len(scales.shape) == 0 or quant_x.shape[-1] == scales.shape[-1]:
|
|
# input tensor was row parallel in tp.
|
|
qdq_x = quant_x / bnt * scales
|
|
else:
|
|
# input tensor was column parallel in tp.
|
|
qdq_x = (
|
|
quant_x
|
|
/ bnt
|
|
* scales[tp_rank * scales.shape[0] // tp_degree : (tp_rank + 1) * scales.shape[0] // tp_degree]
|
|
)
|
|
# fp32 , int8, int, fp32 or fp64
|
|
return qdq_x.astype(np.float32), scales
|
|
else:
|
|
if len(scales.shape) == 0 or quant_x.shape[-1] == scales.shape[-1]:
|
|
# input tensor was row parallel in tp.
|
|
qdq_x = quant_x / bnt * scales.unsqueeze(0).expand(quant_x.shape)
|
|
else:
|
|
# input tensor was column parallel in tp.
|
|
qdq_x = (
|
|
quant_x
|
|
/ bnt
|
|
* scales[tp_rank * scales.shape[0] // tp_degree : (tp_rank + 1) * scales.shape[0] // tp_degree]
|
|
.unsqueeze(0)
|
|
.expand(quant_x.shape)
|
|
)
|
|
# fp32 , int8, int, fp32 or fp64
|
|
return qdq_x.astype(paddle.float32), scales
|