文献综述:基于FPGA的RNN硬件加速
这篇博客是基于一些文献对RNN硬件加速的各个模块的总结
文章目录
一. 剪枝
LSTM虽然解决了梯度消失的问题,但由于引入了很多门控单元,导致参数量很大。然而由于神经网络的鲁棒性,即使剪掉很多参数,神经网络的精度依然能通过重训练恢复,我对剪枝的理解是,比如你想了解一群学生的信息,不一定要和每个学生谈话,只和其中几个人脉广的学生(权值大)谈话,也可以获取到全部信息,我就是我理解的,剪枝的合理性。以下是常用的三种FPGA实现时常用的剪枝方式。
1. 稀疏剪枝
设置阈值,权值小于阈值的设为0,ESE【1】发现,即使剪掉90%的权值,神经网络的精度依然能通过重训练恢复。超过90%,精度就会有一定的下降。但稀疏剪枝存在一些问题:
-
- 非0权值较为离散,存储位置信息需要消耗很多资源
-
- 进行矩阵向量乘法运算时,每个处理单元运行的时间不一样(第一行3个,第二三行只有2个,如果一二三行用不同的PE处理,第一行的运算时间必然慢些),造成资源浪费
ESE【1】提出的解决办法:
-
- 每个非零权值,附带上与上一个非零权值的相对距离。例如第一行存储13时,存储0为位置信息,存储85时,在存储2为位置信息(85和13这两个权值间隔了两个非0权值),从而减少存储非0权值的位置信息占用的资源。
-
- 剪枝时保证每个PE单元处理的数据量相同,并引入FIFO结构,实现负载平衡。如图所式,三种颜色表示三个PE处理这些数据的运算,通过三个FIFO结构为其依次输入 x x x或者 h h h,每个PE都执行两次运算,从而解决资源浪费的问题。以第一行为例子,13对应 x 0 x_{0} x0,读取85时,位置信息2,因此FIFO输出 x 3 x_{3} x3进入PE,其他行同理,每一行的运行时间都一样。
作者通过这种剪枝方式实现了 10 × 10\times 10×的剪枝率
2. Top-k剪枝
ELSTM【2】采用分组的方法来剪枝,将矩阵每c个权值分为一组,每组只保留k个最大的非0权值,这样位置信息就只需要
l
o
g
2
c
log_{2}c
log2c比特就可以存储位置信息了,而且每组的非零元素数量相同,符合负载平衡。c=8,k=1的剪枝如图所示,不同颜色为不同组,作者最高实现了(c,k)=(16,2)的剪枝,也就是
8
×
8\times
8×的剪枝率.pytorch实现topk剪枝
3. Circulant矩阵
除了上述两种剪枝方式,还有分块Circulant矩阵剪枝的方式来减少网络的参数量. p=3的分块循环矩阵如图所示,不同颜色为不同的块,剪枝率为p,p最多为16,超过16时,精度损失较大,也就是说可以实现
16
×
16\times
16×的剪枝率。改剪枝方式的实现:pytorch实现circulant压缩
CLSTM【3】使用FFT-IFFT来实现矩阵-向量乘法,原理如下所示:
取
x
=
[
x
0
,
x
1
,
x
2
,
x
3
]
x = [x_{0}, x_{1}, x_{2}, x_{3}]
x=[x0,x1,x2,x3],
w
=
[
w
0
,
w
1
,
w
2
,
w
3
]
w = [w_{0}, w_{1}, w_{2}, w_{3}]
w=[w0,w1,w2,w3]
h(k) = IFFT(FFT(x)FFT(w)) = w和x的循环卷积,可以得到下列表达式:
[
h
0
h
1
h
2
h
3
]
\left[ \begin{matrix} h_{0} \\ h_{1}\\ h_{2}\\ h_{3} \end{matrix} \right]
⎣⎢⎢⎡h0h1h2h3⎦⎥⎥⎤ =
[
w
0
w
3
w
2
w
1
w
1
w
0
w
3
w
2
w
2
w
1
w
0
w
3
w
3
w
2
w
1
w
0
]
\left[ \begin{matrix} w_{0} & w_{3}& w_{2}& w_{1} \\ w_{1} & w_{0}& w_{3}& w_{2}\\ w_{2} & w_{1} & w_{0}& w_{3}\\ w_{3} & w_{2} & w_{1} & w_{0}\end{matrix} \right]
⎣⎢⎢⎡w0w1w2w3w3w0w1w2w2w3w0w1w1w2w3w0⎦⎥⎥⎤ *
[
x
0
x
1
x
2
x
3
]
\left[ \begin{matrix} x_{0} \\ x_{1} \\ x_{2} \\ x_{3} \end{matrix} \right]
⎣⎢⎢⎡x0x1x2x3⎦⎥⎥⎤
因此通过FFT-IFFT可快速计算循环矩阵和向量的乘法。
ARNN【4】则通过重排序的方法,减小了p的值,如下所示:
例如一个p=4的循环矩阵和向量的乘法:
取
h
=
[
h
0
,
h
1
,
h
2
,
h
3
]
h = [h_{0}, h_{1}, h_{2}, h_{3}]
h=[h0,h1,h2,h3],
w
=
[
w
0
,
w
1
,
w
2
,
w
3
]
w = [w_{0}, w_{1}, w_{2}, w_{3}]
w=[w0,w1,w2,w3]
假设w构成的循环矩阵和x有下列表达式:
[
y
0
y
1
y
2
y
3
]
\left[ \begin{matrix} y_{0} \\ y_{1}\\ y_{2}\\ y_{3} \end{matrix} \right]
⎣⎢⎢⎡y0y1y2y3⎦⎥⎥⎤ =
[
w
0
w
1
w
2
w
3
w
1
w
0
w
3
w
2
w
2
w
1
w
0
w
3
w
3
w
2
w
1
w
0
]
\left[ \begin{matrix} w_{0} & w_{1}& w_{2}& w_{3} \\ w_{1} & w_{0}& w_{3}& w_{2}\\ w_{2} & w_{1} & w_{0}& w_{3}\\ w_{3} & w_{2} & w_{1} & w_{0}\end{matrix} \right]
⎣⎢⎢⎡w0w1w2w3w1w0w1w2w2w3w0w1w3w2w3w0⎦⎥⎥⎤ *
[
h
0
h
1
h
2
h
3
]
\left[ \begin{matrix} h_{0} \\ h_{1} \\ h_{2} \\ h_{3} \end{matrix} \right]
⎣⎢⎢⎡h0h1h2h3⎦⎥⎥⎤
- 我们将w矩阵的第1,2列交换(从0开始计数),要使结果不变,必须交换h的1,2行;
- 再将w矩阵1,2行交换,要使等式成立,就必须交换y的1,2行
交换后,我们得到下列式子:
[ y 0 y 2 y 1 y 3 ] \left[ \begin{matrix} y_{0} \\ y_{2}\\ y_{1}\\ y_{3} \end{matrix} \right] ⎣⎢⎢⎡y0y2y1y3⎦⎥⎥⎤ = [ w 0 w 2 w 3 w 1 w 2 w 0 w 1 w 3 w 1 w 3 w 0 w 2 w 3 w 1 w 2 w 0 ] \left[ \begin{matrix} w_{0} & w_{2}& w_{3}& w_{1} \\ w_{2} & w_{0}& w_{1}& w_{3}\\ w_{1} & w_{3} & w_{0}& w_{2}\\ w_{3} & w_{1} & w_{2} & w_{0}\end{matrix} \right] ⎣⎢⎢⎡w0w2w1w3w2w0w3w1w3w1w0w2w1w3w2w0⎦⎥⎥⎤ * [ h 0 h 2 h 1 h 3 ] \left[ \begin{matrix} h_{0} \\ h_{2} \\ h_{1} \\ h_{3} \end{matrix} \right] ⎣⎢⎢⎡h0h2h1h3⎦⎥⎥⎤
此时w矩阵,相当于4个p=2的分块循环矩阵组成,ARNN[4]在FPGA实现RNN加速时,通过这种方式,减少FPGA的资源消耗。
4. 三种剪枝方式的对比
稀疏矩阵(ESE[1]) | Top-k(E-LSTM[2]) | Circulant (CLSTM[3],ARNN[4]) | |
---|---|---|---|
压缩率 | 10 × 10\times 10× | 8 × 8\times 8× | 16 × 16\times 16× |
稀疏矩阵虽然方便,但如果两个非零权值之间隔的距离不稳定,比如w0,1后面的非零元素如果是w0,100那么就需要
l
o
g
2
100
log_{2}^{100}
log2100比特才能存储位置信息。稀疏矩阵存储位置信息依然有一定难度。
Top-k剪枝虽然只能达到8倍的压缩率,但矩阵存在很多零元素,可以减少很多运算资源;
Ciculant矩阵剪枝,虽然能达到16倍的剪枝率,但原理上矩阵几乎没有0元素,运算资源大于前两个剪枝方式。
二. 量化
电脑训练模型时,使用的是32位浮点数,FPGA实现时,使用的时定点数,因此需要将模型参数量化。
1. 线性量化
线性量化如下所示:
Q
m
,
f
(
W
)
=
⌊
2
f
⋅
W
c
l
i
p
+
1
2
⌋
⋅
2
−
f
Q_{m,f}(W)=\lfloor 2^{f}\cdot W_{clip}+\frac{1}{2}\rfloor\cdot2^{-f}
Qm,f(W)=⌊2f⋅Wclip+21⌋⋅2−f
W
c
l
i
p
=
max
(
min
(
W
,
2
m
−
1
2
f
)
,
−
2
m
)
⋅
s
i
g
n
(
W
)
W_{clip}=\max(\min(W, 2^{m}-\dfrac{1}{2^{f}}), -2^{m})\cdot sign(W)
Wclip=max(min(W,2m−2f1),−2m)⋅sign(W)
m代表整数位位数,f代表小数位位数,符号位1位,总的数据位数为m+f+1线性量化的误差恒定。
2. 非线性量化(对数量化)
非线性量化如下所示:
log
Q
m
,
−
f
(
W
)
=
2
min
(
max
(
W
l
o
g
,
−
f
)
,
m
)
⋅
s
i
g
n
(
W
)
\log Q_{m,-f}(W)=2^{\min(\max(W_{log}, -f), m)}\cdot sign(W)
logQm,−f(W)=2min(max(Wlog,−f),m)⋅sign(W)
W
l
o
g
=
⌊
log
2
∣
W
∣
+
1
2
⌋
W_{log}=\lfloor \log_{2}|W|+\frac{1}{2}\rfloor
Wlog=⌊log2∣W∣+21⌋
m代表整数位位数,f代表小数位位数,符号位1位,总的数据位数为
⌈
log
2
(
2
⋅
(
m
+
f
+
1
)
+
1
)
⌉
\lceil\log_{2}(2\cdot(m+f+1)+1)\rceil
⌈log2(2⋅(m+f+1)+1)⌉.非线性量化,误差不稳定。量化出来就是2的幂次方。
3. 两种量化方式的对比
橘色为y=x,蓝色为y=quantization(x)
线性量化如图:
非线性量化如图:
两种量化的效果如图所示,可以看出,非线性量化适用于权值较小的矩阵,权值较大时,非线性量化的误差较大。
三. 激活函数
1. 查找表的方式(LUT)
假如取256个不同的输入x,我们提前计算出这些x对应的激活函数的输出,将256个输出提前存入LUT中,计算时根据输入x得到相应输出。这样的方式耗费资源非常多,如果x有12位,那么对应的需要提前存储输出值就有 2 12 2^{12} 212个,非常繁琐。
2. 分段线性函数替代非线性函数
使用分段线性函数替换非线性函数,是常用的方法,可以通过重训练来恢复精度。分段线性函数在FPGA上的实现就容易多了,资源消耗也小很多。
常用的分段线性函数如下:
H
s
i
g
m
(
x
)
=
{
1
,
x
>
=
4
0.25
x
+
0.5
,
o
t
h
e
r
w
i
s
e
0
,
x
<
=
−
4
Hsigm(x)=\begin{cases} 1 & ,x>=4 \\ 0.25x + 0.5 & ,otherwise \\ 0 & ,x<= -4 \end{cases}
Hsigm(x)=⎩⎪⎨⎪⎧10.25x+0.50,x>=4,otherwise,x<=−4
H t a n h ( x ) = { 1 , x > = 1 x , o t h e r w i s e − 1 , x < = − 1 Htanh(x)=\begin{cases} 1 & ,x>=1 \\ x & ,otherwise \\ -1 & ,x<= -1 \end{cases} Htanh(x)=⎩⎪⎨⎪⎧1x−1,x>=1,otherwise,x<=−1
P t a n h ( x ) = { 1 , x > = 2.5 0.75 x + 0.375 , x ∈ ( 0.5 , 2.5 ) x , x ∈ ( − 0.5 , 0.5 ) 0.75 x − 0.375 , x ∈ ( − 2.5 , − 0.5 ) − 1 , x < − 2.5 Ptanh(x)=\begin{cases} 1 & ,x>=2.5 \\ 0.75x + 0.375 & ,x∈(0.5,2.5) \\ x & ,x∈(-0.5,0.5) \\ 0.75x - 0.375 & ,x∈(-2.5, -0.5) \\ -1 & ,x<-2.5 \\ \end{cases} Ptanh(x)=⎩⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎧10.75x+0.375x0.75x−0.375−1,x>=2.5,x∈(0.5,2.5),x∈(−0.5,0.5),x∈(−2.5,−0.5),x<−2.5
参考文献
- 【1】ESE: Efficient Speech Recognition Engine with Sparse LSTM on FPGA
- 【2】E-LSTM: An Efficient Hardware Architecture for Long Short-Term Memory
- 【3】E-RNN: Design Optimization for Efficient Recurrent Neural Networks in FPGAs
- 【4】C-LSTM: Enabling Efficient LSTM using Structured Compression
Techniques on FPGAs - 【5】Accelerating Recurrent Neural Networks: A Memory-Efficient Approach