模型量化-数据映射方式
对称量化

例子:
👉👉输入数据如下:
x_f = [0.32, -1.76, 0.025, -1.22]
目标是将输入数据量化为INT8
。
👉👉对称量化先取输入数据绝对值的最大值:
absmax = max(
abs(x_f)
)
absmax = 1.76
👉👉计算缩放尺度scale
:
计算缩放尺度scale
前,先明确我们数据映射的源域和目标域。
源域:[s_min, s_max]=[-absmax, absmax]=[-1.76, 1.76]
→
\quad \rightarrow \quad
→目标域:[q_min, q_max]=[-128, 127]
。
注:有时为了计算方便,舍弃目标域的-128
,此时的目标域为:[-127, 127]
。
s c a l e = s m a x − s m i n q m a x − q m i n = 1.76 − ( − 1.76 ) 127 − ( − 128 ) = 3.52 255 = 0.013803921568627451 \mathbf{scale}=\frac{s_{max}-s_{min}}{q_{max}-q_{min}}=\frac{1.76-(-1.76)}{127-(-128)}=\frac{3.52}{255}=0.013803921568627451 scale=qmax−qminsmax−smin=127−(−128)1.76−(−1.76)=2553.52=0.013803921568627451
scale = 0.013803921568627451
👉👉量化:
q_rets = np.round(x_f / scale)
q_rets = [23, -128, 2, -88]
量化结果为:q_rets=[23, -128, 2, -88]
。
👉👉反量化:
x_f_recs = q_rets * scale
x_f_recs = [0.3174902, -1.76690196, 0.02760784, -1.2147451]
反量化结果为:[0.3174902, -1.76690196, 0.02760784, -1.2147451]
。
👉👉矩阵乘法中的应用:
输入 x f x_f xf和权重 w f w_f wf分别被量化为 x q x_q xq和 w q w_q wq,如下。

则 x f w f = ( x q ∗ s x ) @ ( w q ∗ s w ) = ( s x ∗ s w ) ( x q w q ) x_fw_f=(x_q*s_x)@(w_q*s_w)=(s_x*s_w)(x_qw_q) xfwf=(xq∗sx)@(wq∗sw)=(sx∗sw)(xqwq),下图是直接计算与量化反量化计算结果对比:
原始结果 = [ − 1.3485 − 1.5792 − 1.8245 − 2.4014 2.4463 4.4340 0.0904 − 1.2283 − 0.2202 ] 量化计算结果 = [ − 1.3610 − 1.5720 − 1.8344 − 2.3754 2.4279 4.4627 0.0821 − 1.2400 − 0.2306 ] \begin{aligned} \text{原始结果} &= \begin{bmatrix} -1.3485 & -1.5792 & -1.8245 \\ -2.4014 & 2.4463 & 4.4340 \\ 0.0904 & -1.2283 & -0.2202 \end{bmatrix} \\ \text{量化计算结果} &= \begin{bmatrix} -1.3610 & -1.5720 & -1.8344 \\ -2.3754 & 2.4279 & 4.4627 \\ 0.0821 & -1.2400 & -0.2306 \end{bmatrix} \end{aligned} 原始结果量化计算结果= −1.3485−2.40140.0904−1.57922.4463−1.2283−1.82454.4340−0.2202 = −1.3610−2.37540.0821−1.57202.4279−1.2400−1.83444.4627−0.2306
👉👉对称量化的问题:
通过absmax来确定缩放比例,一定存在目标域的某一部分被浪费,如下图所示。
非对称量化

例子:
👉👉输入数据如下:
x_f = [0.32, -1.76, 0.025, -1.22]
目标是将输入数据量化为UINT8
。
👉👉计算缩放尺度scale
:
计算缩放尺度scale
前,先明确我们数据映射的源域和目标域。
源域:[s_min, s_max]=[min(x_f), max(x_f)]=[-1.76, 0.32]
→
\quad \rightarrow \quad
→目标域:[q_min, q_max]=[0, 255]
。
s c a l e = s m a x − s m i n q m a x − q m i n = 0.32 − ( − 1.76 ) 255 − 0 = 2.08 255 = 0.00815686274509804 \mathbf{scale}=\frac{s_{max}-s_{min}}{q_{max}-q_{min}}=\frac{0.32-(-1.76)}{255-0}=\frac{2.08}{255}=0.00815686274509804 scale=qmax−qminsmax−smin=255−00.32−(−1.76)=2552.08=0.00815686274509804
scale = 0.00815686274509804
👉👉计算零点zero_point
:
将上式变形为:
q m a x − q m i n = s m a x − s m i n s c a l e → 一般地 q − q m i n = s − s m i n s c a l e q_{max}-q_{min}=\frac{s_{max}-s_{min}}{\mathbf{scale}} \quad\xrightarrow{\text{一般地}}\quad q-q_{min}=\frac{s-s_{min}}{\mathbf{scale}} qmax−qmin=scalesmax−smin一般地q−qmin=scales−smin
特别地,取上式 s = 0 s=0 s=0,则 q 0 − q m i n = 0 − s m i n s c a l e = − s m i n s c a l e q_0-q_{min}=\frac{0-s_{min}}{scale}=\frac{-s_{min}}{scale} q0−qmin=scale0−smin=scale−smin,最终得到:
q 0 = − s m i n s c a l e + q m i n = 1.76 0.00815686274509804 + 0 = 215.76923076923075 q_0=\frac{-s_{min}}{\mathbf{scale}}+q_{min}=\frac{1.76}{0.00815686274509804}+0=215.76923076923075 q0=scale−smin+qmin=0.008156862745098041.76+0=215.76923076923075
四舍五入取整后: q 0 = r o u n d ( 215.76923076923075 ) = 216 q_0=\mathbf{round}(215.76923076923075)=216 q0=round(215.76923076923075)=216。
q_0 = np.round((-s_min) / scale) + q_min
👉👉量化:
推导一般量化公式:
q − q m i n = s − s m i n s c a l e → q = s s c a l e + ( − s m i n s c a l e + q m i n ) = s s c a l e + q 0 q-q_{min}=\frac{s-s_{min}}{\mathbf{scale}}\quad\rightarrow\quad q=\frac{s}{\mathbf{scale}}+\big(-\frac{s_{min}}{\mathbf{scale}}+q_{min}\big)=\frac{s}{\mathbf{scale}}+q_0 q−qmin=scales−smin→q=scales+(−scalesmin+qmin)=scales+q0
可得: q = r o u n d ( s s c a l e ) + q 0 q=\mathbf{round}(\frac{s}{\mathbf{scale}})+q_0 q=round(scales)+q0,为保证结果在[0, 255]范围内,最后q表示为: q = c l a m p ( r o u n d ( s s c a l e ) + q 0 , 0 , 255 ) q=\mathbf{clamp}(\mathbf{round}(\frac{s}{\mathbf{scale}})+q_0,\;0,\;255) q=clamp(round(scales)+q0,0,255)
q_rets = np.clip(
np.round(x_f / scale) + q_0,
0,
255
)
q_rets = [255, 0, 219, 66]
👉👉反量化:
s = ( q − q 0 ) × s c a l e s = (q - q_0)\times\mathbf{scale} s=(q−q0)×scale
x_f_recs = (q_rets - q_0) * scale
x_f_recs = [0.31811765, -1.76188235, 0.02447059, -1.22352941]
👉👉矩阵乘法中的应用:
输入 x f x_f xf和权重 w f w_f wf分别被量化为 x q x_q xq和 w q w_q wq,如下。

则量化反量化计算:
x f w f = ( x q − z x ) ∗ s x @ ( w q − z w ) ∗ s w = s x ∗ s w ( x q w q − x q z w − w q z x + z x z w ) \begin{aligned} x_fw_f& =(x_{q}-z_{x})*s_{x} @ (w_{q}-z_{w})*s_{w} \\ &=s_{x}*s_{w} (x_{q}w_{q}-x_{q}z_{w}-w_{q}z_{x}+z_{x}z_{w}) \end{aligned} xfwf=(xq−zx)∗sx@(wq−zw)∗sw=sx∗sw(xqwq−xqzw−wqzx+zxzw)
下图是直接计算与量化反量化计算结果对比:
原始结果 = [ − 1.3485 − 1.5792 − 1.8245 − 2.4014 2.4463 4.4340 − 0.2966 − 1.0333 − 0.3492 ] 量化计算结果 = [ − 1.3599 − 1.5718 − 1.8264 − 2.4041 2.4476 4.4357 0.0993 − 1.2225 − 0.2247 ] \begin{aligned} \text{原始结果} &= \begin{bmatrix} -1.3485 & -1.5792 & -1.8245 \\ -2.4014 & 2.4463 & 4.4340 \\ -0.2966 & -1.0333 & -0.3492 \end{bmatrix} \\ \text{量化计算结果} &= \begin{bmatrix} -1.3599 & -1.5718 & -1.8264 \\ -2.4041 & 2.4476 & 4.4357 \\ 0.0993 & -1.2225 & -0.2247 \end{bmatrix} \end{aligned} 原始结果量化计算结果= −1.3485−2.4014−0.2966−1.57922.4463−1.0333−1.82454.4340−0.3492 = −1.3599−2.40410.0993−1.57182.4476−1.2225−1.82644.4357−0.2247
👉👉非对称量化的问题:
1️⃣仍然存在目标域的某一部分被浪费;
2️⃣多个值被量化为同一个整数值。
分位数量化
在标准正态分布中,对于分布X给定的概率值 α \alpha α,如果存在 u α u_{\alpha} uα使得它的累积分布函数(CDF) P ( X < u α ) = α P(X<u_{\alpha})=\alpha P(X<uα)=α则称 u α u_{\alpha} uα是标准正态分布的 α \alpha α分位数,如下图。

相关研究认为,神经网络的激活值和权重的数值分布符合0均值某方差 σ 2 \sigma^2 σ2的正态分布;且当将输入数据进行量化时,每个可能的k-bit的整数值出现的频率是相等的(意思是将连续的输入值离散化为 2 k 2^k 2k个可能整数值时每一个可能的整数值出现的频率相同)。
因此,我们可以借助标准正态分布来产生量化标准对输入数据进行量化。
例子:
👉👉输入数据如下:
x_f = [0.32, -1.76, 0.025, -1.22]
目标是将输入数据量化为UINT4
。
👉👉确定量化标准(如何计算我们后面说明):
Q m a p p i n g = [ − 1.0000 , − 0.8102 , − 0.6541 , − 0.5172 , − 0.3925 , − 0.2755 , − 0.1635 , − 0.0542 , 0.0542 , 0.1635 , 0.2755 , 0.3925 , 0.5172 , 0.6541 , 0.8102 , 1.0000 ] \mathbf{Q_{mapping}}=\begin{aligned} &[-1.0000, -0.8102, -0.6541, -0.5172, -0.3925, -0.2755, -0.1635, \\ &-0.0542, 0.0542, 0.1635, 0.2755, 0.3925, 0.5172, 0.6541, 0.8102, 1.0000] \end{aligned} Qmapping=[−1.0000,−0.8102,−0.6541,−0.5172,−0.3925,−0.2755,−0.1635,−0.0542,0.0542,0.1635,0.2755,0.3925,0.5172,0.6541,0.8102,1.0000]
👉👉输入数据规范化到[-1, 1]
:
constant = np.abs(x_f).max()
constant = 1.76
x_f_normalized = x_f / constant
'''
x_f_normalized:
[0.181818,
-1.000000,
0.014205,
-0.693182]
'''
👉👉从量化标准中找出距离规范化的输入数据最近元素的索引,得到的结果即为将输入数据量化为UNIT4
的结果q_rets=[9, 0, 8, 2]
:
def find_nearest(array, value):
return (np.abs(array - value)).argmin()
q_rets = []
for ele in x_f_normalized:
min_index = find_nearest(Q_mapping, ele)
q_rets.append(min_index)
print(q_rets)
# [9, 0, 8, 2]
👉👉量化标准的确定方式:
量化标准依据标准正态分布的一组等差分位数。
选定概率区间:[1-offset, offset], offset=0.9
,将概率区间划分为
2
n
∣
n
=
4
=
16
2^n|_{n=4}=16
2n∣n=4=16份(因为目标是将输入数据量化为UINT4
)。
注:为什么要添加偏置offset
?因为如果考虑完整的概率区间:[0, 1]
,此时概率范围的首尾为0和1,其在标准正态分布中对应的位置为
−
i
n
f
-inf
−inf和
+
i
n
f
+inf
+inf,这是不可计算的。
n_bits = 4
n_bins = 2 ** n_bits
offset = 0.9
print('考虑的概率范围', '[{},{}]'.format(1 - offset, offset), sep=' | ')
probs = np.linspace(1 - offset, offset, n_bins + 1)
print('概率位置', '{}'.format(probs.tolist()), sep=' | ')
positions = norm.ppf(probs) # inverse of CDF
positions
注意:上面代码中等差分位数对应positions
(在标准正态分布中的位置)的获得是借助标准正态分布的逆累积概率函数(inverse of CDF)得到的。
结果:
考虑的概率范围 | [0.09999999999999998,0.9]
概率位置 | [0.09999999999999998, 0.14999999999999997, 0.19999999999999998, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7000000000000001, 0.75, 0.8, 0.85, 0.9]
positions:
array([-1.2816, -1.0364, -0.8416, -0.6745, -0.5244, -0.3853, -0.2533,
-0.1257, 0. , 0.1257, 0.2533, 0.3853, 0.5244, 0.6745,
0.8416, 1.0364, 1.2816])
如下图所示,生成的17个概率点位probs
将概率区间[0.1, 0.9]
划分为16份,也即产生16个bins。

计算每个bin的中点,一共16个值。
# 计算每个区间的中点位置
midpoints = (positions[:-1] + positions[1:]) / 2
midpoints
结果:
array([-1.159 , -0.939 , -0.7581, -0.5994, -0.4549, -0.3193, -0.1895,
-0.0628, 0.0628, 0.1895, 0.3193, 0.4549, 0.5994, 0.7581,
0.939 , 1.159 ])

将中点位置的值规范化到[-1, 1]
(与输入数据规范化时选定的范围对应,可任意选定)。
r_max, r_min = m_max, m_min
r_max, r_min
# (1.158992477519195, -1.1589924775191949)
# 规范化到[-1, 1]的范围
scale = (r_max - r_min) / (1 - (-1))
scale
# 1.1589924775191949
zero_point = (0 - r_min) / scale + (-1)
zero_point
# 0.0
quantile_mapping = midpoints / scale + zero_point
quantile_mapping
'''
array([-1. , -0.8102, -0.6541, -0.5172, -0.3925, -0.2755, -0.1635,
-0.0542, 0.0542, 0.1635, 0.2755, 0.3925, 0.5172, 0.6541,
0.8102, 1. ])
'''
如下图:

得到的[-1, 1]
区间的映射结果即为最终的量化标准Q_mapping
:
[ − 1.0000 , − 0.8102 , − 0.6541 , − 0.5172 , − 0.3925 , − 0.2755 , − 0.1635 , − 0.0542 , 0.0542 , 0.1635 , 0.2755 , 0.3925 , 0.5172 , 0.6541 , 0.8102 , 1.0000 ] \begin{aligned} &[-1.0000, -0.8102, -0.6541, -0.5172, -0.3925, -0.2755, -0.1635, \\ &-0.0542, 0.0542, 0.1635, 0.2755, 0.3925, 0.5172, 0.6541, 0.8102, 1.0000] \end{aligned} [−1.0000,−0.8102,−0.6541,−0.5172,−0.3925,−0.2755,−0.1635,−0.0542,0.0542,0.1635,0.2755,0.3925,0.5172,0.6541,0.8102,1.0000]
NF4
以上分位数量化的一个问题是在确定Q_mapping
时无法保证0的映射值是0,而0又是神经网络激活值和权重中一个重要的值(神经网络激活值和权重是稀疏的)。
NF4在确定映射标准时将正数,零和负数分别考虑。
offset = 0.9677083
probs_pos = np.linspace(offset, 0.5, 9)[:-1] # 8个值
probs_neg = np.linspace(offset, 0.5, 8)[:-1] # 7个值
👉👉正数部分:
probs_pos:
array([0.9677, 0.9092, 0.8508, 0.7923, 0.7339, 0.6754, 0.6169, 0.5585])
positions_pos = norm.ppf(probs_pos)
positions_pos
# array([1.8481, 1.3361, 1.0398, 0.8145, 0.6245, 0.4548, 0.2974, 0.1471])
正数部分如下图:

👉👉零单独考虑:
positions_zero = np.array([0.])
positions_zero
# array([0.])
probs_zero = norm.cdf(positions_zero)
probs_zero
# array([0.5])
👉👉负数部分:
probs_neg:
array([0.9677, 0.9009, 0.8341, 0.7673, 0.7004, 0.6336, 0.5668])
# 注意这里负数部分positions_neg的产生方式
positions_neg = -norm.ppf(probs_neg)
positions_neg
# array([-1.8481, -1.2867, -0.9704, -0.7299, -0.5257, -0.3415, -0.1683])
true_probs_neg = norm.cdf(positions_neg)
true_probs_neg
# array([0.0323, 0.0991, 0.1659, 0.2327, 0.2996, 0.3664, 0.4332])
负数部分如下图:

👉👉16个位置:
positions = np.concatenate([positions_pos, positions_zero, positions_neg])
positions
'''
array([ 1.8481, 1.3361, 1.0398, 0.8145, 0.6245, 0.4548, 0.2974,
0.1471, 0. , -1.8481, -1.2867, -0.9704, -0.7299, -0.5257,
-0.3415, -0.1683])
'''

与之前的分位数量化不同的是这里是直接使用得到的16个位置(不取中间位置)规范化到[-1, 1]
产生NF4映射标准。
sorted_ = np.sort(positions)
sorted_
'''
array([-1.8481, -1.2867, -0.9704, -0.7299, -0.5257, -0.3415, -0.1683,
0. , 0.1471, 0.2974, 0.4548, 0.6245, 0.8145, 1.0398,
1.3361, 1.8481])
'''
NF4 = sorted_ / sorted_.max()
NF4
'''
array([-1. , -0.6962, -0.5251, -0.3949, -0.2844, -0.1848, -0.091 ,
0. , 0.0796, 0.1609, 0.2461, 0.3379, 0.4407, 0.5626,
0.723 , 1. ])
'''
NF4映射标准如下图所示:

例子:
👉👉输入数据如下:
x_f = [0.32, -1.76, 0.025, -1.22]
目标是将输入数据量化为UINT4
。
👉👉量化和反量化过程:
