量化数据运算
文章目录
- 量化数据运算
- 量化数据乘积
- 仿射映射量化的矩阵运算
- 矩阵表示 矩阵C=AB
- 量化方式表示 矩阵C=AB
- 代码展示基于仿射映射量化的矩阵乘法
- 矩阵乘法计算API
- 通过仿射映射量化形式计算两个矩阵的乘法
- **欢迎关注公众号【三戒纪元】**
量化数据乘积
使用记号(s, z, q)表示为量化中心 z, 步长为 s 以及量化符号为 q 的仿射映射量化数据
因此2个量化数
(
s
a
,
z
a
,
q
a
)
\ (s_a, z_a, q_a)
(sa,za,qa) 和
(
s
b
,
z
b
,
q
b
)
\ (s_b, z_b, q_b)
(sb,zb,qb) 相乘运算,乘积用
(
s
c
,
z
c
,
q
c
)
\ (s_c, z_c, q_c)
(sc,zc,qc) 表示
{
a
=
s
a
(
q
a
−
z
a
)
b
=
s
b
(
q
b
−
z
b
)
c
=
s
c
(
q
c
−
z
c
)
\begin{cases} a = s_a (q_a - z_a) \\ b = s_b (q_b - z_b) \\ c = s_c (q_c - z_c) \end{cases}
⎩
⎨
⎧a=sa(qa−za)b=sb(qb−zb)c=sc(qc−zc)
根据 c = ab 可以得出
q c = S a S b s c ( q a − z a ) ( q b − z b ) + z c q_c = \frac{S_a S_b}{s_c} (q_a - z_a)(q_b - z_b) + z_c qc=scSaSb(qa−za)(qb−zb)+zc
其中 ( q a − z a ) ( q b − z b ) (q_a - z_a)(q_b - z_b) (qa−za)(qb−zb) 使用整数乘法即可完成,对于 S a S b s c \frac{S_a S_b}{s_c} scSaSb需要用到浮点数计算。
通过a和b的量化表示以及它们的格式参数计算c的量化表示一次整数乘法,一次浮点数乘法和一次整数加法
看似运算量没有改进,但应用到量化的矩阵运算就有不同了
仿射映射量化的矩阵运算
矩阵表示 矩阵C=AB
有向量表示A B即为
{ A = [ a ( 1 ) , a ( 2 ) , . . . , a ( N ) ] B = [ b ( 1 ) , b ( 2 ) , . . . , b ( N ) ] \begin{cases} A=[a^{(1)}, a^{(2)}, ... , a^{(N)}] \\ B=[b^{(1)}, b^{(2)}, ... , b^{(N)}] \end{cases} {A=[a(1),a(2),...,a(N)]B=[b(1),b(2),...,b(N)]
C=AB,可以表示为:
∑
n
=
1
N
a
(
n
)
b
(
n
)
\sum_{n=1}^N a^{(n)} b^{(n)}
n=1∑Na(n)b(n)
我们也用仿射映射量化表示
C
=
s
c
(
C
q
−
1
c
z
c
)
C=s_c(C_q−1_cz_c)
C=sc(Cq−1czc)
可以得出
q
c
=
s
a
s
b
s
c
[
∑
n
=
1
N
(
q
a
(
n
)
−
z
a
)
(
q
b
(
n
)
−
z
b
)
]
+
z
c
q_c =\frac{s_as_b}{s_c}[\sum_{n=1}^N (q_a^{(n)}- z_a)(q_b^{(n)}- z_b)] + z_c
qc=scsasb[n=1∑N(qa(n)−za)(qb(n)−zb)]+zc
算式中间的
∑
n
=
1
N
(
q
a
(
n
)
−
z
a
)
(
q
b
(
n
)
−
z
b
)
\sum_{n=1}^N (q_a^{(n)}- z_a)(q_b^{(n)}- z_b)
∑n=1N(qa(n)−za)(qb(n)−zb)是整数减法和乘法运算,整个计算过程只需要1次浮点乘法,即乘以
s
a
s
b
s
c
\frac{s_as_b}{s_c}
scsasb,相比之下,传统向量内积算法需要N次浮点数乘法。
量化方式表示 矩阵C=AB
如果对于A和B中的元素,分别用量化步长
S
a
S_a
Sa和
S
b
S_b
Sb、量化中心
z
a
z_a
za和
z
b
z_b
zb的仿射映射表示:
{
A
=
s
a
(
A
q
−
1
a
z
a
)
B
=
s
b
(
B
q
−
1
b
z
b
)
)
\begin{cases} A=s_a(A_q− 1_a z_a)\\ B=s_b(B_q− 1_b z_b)) \end{cases}
{A=sa(Aq−1aza)B=sb(Bq−1bzb))
整合上面2个式子
C
q
=
s
a
s
b
s
c
(
A
q
−
1
a
z
a
)
(
B
q
−
1
b
z
b
)
+
1
c
C_q=\frac{s_as_b}{s_c}(A_q−1_az_a)(B_q−1_bz_b)+1_c
Cq=scsasb(Aq−1aza)(Bq−1bzb)+1c
注意,这里隐含了取整运算:
浮点数 s a s b s c \frac{s_as_b}{s_c} scsasb相乘需要改成“整数运算”,使用整数乘法加上移位实现,降低运算量,并使得运算结果和右侧矩阵的整数数据类型匹配
比如:0.3*x
用((int)(0.3*256)*x)>>8 ≈ (77*x)>>8
,因为256 >> 8 = 1
。另外77*x
也可以用之前的常整数乘法简化成加减运算
所以,这里计算矩阵 C=AB 的步骤为:
-
使用整数加法计算 ( A q − 1 a z a ) (A_q−1_az_a) (Aq−1aza)
-
使用整数加法计算 ( B q − 1 b z b ) (B_q−1_bz_b) (Bq−1bzb)
-
前两步结果的相乘 ( A q − 1 a z a ) ( B q − 1 b z b ) (A_q−1_az_a)(B_q−1_bz_b) (Aq−1aza)(Bq−1bzb)
-
上一步运算结果乘以 s a s b s c \frac{s_as_b}{s_c} scsasb,得到 s a s b s c ( A q − 1 a z a ) ( B q − 1 b z b ) \frac{s_as_b}{s_c}(A_q−1_az_a)(B_q−1_bz_b) scsasb(Aq−1aza)(Bq−1bzb)
-
使用整数加法计算 1 c z c 1_cz_c 1czc得到 s a s b s c ( A q − 1 a z a ) ( B q − 1 b z b ) + 1 c \frac{s_as_b}{s_c}(A_q−1_az_a)(B_q−1_bz_b)+1_c scsasb(Aq−1aza)(Bq−1bzb)+1c
上面步骤3需要** M ∗ N ∗ P M * N * P M∗N∗P次整数乘法**,步骤4需要** M ∗ P M * P M∗P次浮点乘法**(矩阵乘以常数 s a s b s c \frac{s_as_b}{s_c} scsasb),相比之下,非量化算法需要 M ∗ N ∗ P M * N * P M∗N∗P次浮点乘法。
代码展示基于仿射映射量化的矩阵乘法
矩阵乘法计算API
import numpy as np
#######################
# 演示使用反射变换实现矩阵
# 乘法的过程
#######################
QMIN,QMAX=0,255 # 量化表示的最大最小值
## 根据numpy矩阵data分析并提取量化参数
def calc_quant_param(data):
vmin,vmax=np.min(data.ravel()),np.max(data.ravel())
s=float(vmax-vmin)/float(QMAX-QMIN)
z=float(QMIN)-float(vmin)/s
z=int(round(np.clip(z,QMIN,QMAX)))
return s,z
## 数据量化,计算:dq=round(d/s+z)
def calc_quant_data(d,s,z):
dq=d/s+z
dq=np.round(np.clip(dq,QMIN,QMAX)).astype(int)
return dq
## 数据反量化,计算:d=s*(dq-z)
def calc_dequant_data(dq,s,z):
return s*(dq-z).astype(float)
## 量化矩阵乘法
# 从Aq、Bq计算C=A*B的量化表示Cq
def quant_matmul(Aq, sa, za,
Bq, sb, zb,
sc, zc):
# 整数乘法
Cq=np.dot(Aq-za,
Bq-zb)
# 乘以常数系数(浮点数),可以用整数乘法近似,但这里为演示简单,简单使用了浮点乘法
Cq=(sa*sb/sc)*Cq.astype(float)
Cq=np.round(Cq).astype(int)+zc
return Cq
通过仿射映射量化形式计算两个矩阵的乘法
if __name__ == '__main__':
np.random.seed(1234)
# 生成2个随机矩阵
A=np.random.randn(2,3)
B=np.random.randn(3,3)
# 用浮点运算计算参考答案
C_ref=np.dot(A,B)
# 计算A的量化参数sa,za和量化矩阵Aq
# A=sa*(Aq-za)
sa,za=calc_quant_param(A)
Aq=calc_quant_data(A,sa,za)
#显示量化结果
print('sa:%f, za:%f'%(sa,za))
print('Aq:\n',Aq)
print('A:\n',A)
print('recovered A:\n',calc_dequant_data(Aq,sa,za))
# 计算A的量化参数sa,za和量化矩阵Aq
# A=sa*(Aq-za)
sb,zb=calc_quant_param(B)
Bq=calc_quant_data(B,sb,zb)
print('sb:%f, zb:%f'%(sb,zb))
print('Bq:\n',Bq)
print('B:\n',B)
print('recovered B:\n',calc_dequant_data(Bq,sb,zb))
# 计算C的量化参数sc,zc
# 注意,实际运算时sc,zc是通过数据统计
# 事先指定的,不会像这里从答案计算得到
sc,zc=calc_quant_param(C_ref)
## 使用量化形式计算乘法
Cq=quant_matmul(Aq,sa,za,
Bq,sb,zb,
sc,zc)
## 比较计算误差,我们将Cq反量化后和参考答案比较
C=calc_dequant_data(Cq,sc,zc)
print('sc:%f, zc:%f'%(sc,zc))
print('Cq:\n',Cq)
print('C:\n',C)
print('reference C:\n',C_ref)
print('relative err:',np.linalg.norm(C-C_ref)/np.linalg.norm(C))
结果:
sa:0.010289, za:116.000000
Aq:
[[162 0 255]
[ 86 46 202]]
A:
[[ 0.47143516 -1.19097569 1.43270697]
[-0.3126519 -0.72058873 0.88716294]]
recovered A:
[[ 0.47329177 -1.19351839 1.43016428]
[-0.30866855 -0.72022661 0.88484984]]
sb:0.013305, zb:169.000000
Bq:
[[234 121 170]
[ 0 255 244]
[241 17 144]]
B:
[[ 0.85958841 -0.6365235 0.01569637]
[-2.24268495 1.15003572 0.99194602]
[ 0.95332413 -2.02125482 -0.33407737]]
recovered B:
[[ 0.86481115 -0.63862977 0.01330479]
[-2.248509 1.14421168 0.99785902]
[ 0.95794466 -2.02232762 -0.33261967]]
sc:0.035324, zc:129.000000
Cq:
[[255 0 82]
[191 61 100]]
C:
[[ 4.45084753 -4.55682009 -1.66023678]
[ 2.19009958 -2.4020447 -1.02440142]]
reference C:
[[ 4.4420576 -4.56561002 -1.65261875]
[ 2.1930554 -2.42287488 -1.01607369]]
relative err: 0.0036312932138631597