目录:
我发现点下面的链接会跳到一个不知道是谁的CSDN下面需要付费下载,这个很迷惑,麻烦自行复制下面的链接。
Github:https://github.com/MasLiang/CNN-On-FPGA
那个不知道是谁的链接:https://download.csdn.net/download/weixin_42138780/18551586
没有下载不让举报,有办法的朋友麻烦举报一下
上一节我们分析了demo需要的存储空间上限和DSP单元的分配可能方案,这一节我们将前面提到的可行的方案进行比较,并将存储空间的优化考虑进来。
首先这四种可能的DSP单元分配方案方案列举一下:
- 卷积核内全并行,卷积核间全并行(DSP资源不足)
- 卷积核内全并行,卷积核间部分并行(可行)
- 卷积核内部分并行,卷积核间全并行(可行)
- 卷积核内部分并行,卷积核间部分并行(可行)
现在我们来毕竟方案3和4。
首先他们的共同点是卷积核内部分并行,因此我们从这里开始分析,明确卷积核内部如何部分并行。
首先来看卷积的计算公式:
o
u
t
(
x
,
y
,
n
o
u
t
)
=
∑
n
i
n
N
i
n
∑
k
x
=
0
k
−
1
∑
k
y
=
0
k
−
1
w
n
o
u
t
×
i
n
(
x
+
k
x
,
y
+
k
y
,
n
i
n
)
+
b
(
n
o
u
t
)
out(x,y,n_{out}) = \sum^{N_{in}}_{n_{in}}\sum^{k-1}_{k_x=0}\sum^{k-1}_{k_y=0}w_{n_{out}}\times in(x+k_x,y+k_y,n_{in})+b(n_{out})
out(x,y,nout)=nin∑Ninkx=0∑k−1ky=0∑k−1wnout×in(x+kx,y+ky,nin)+b(nout)
这里的
n
o
u
t
n_{out}
nout是指输出的通道,
n
i
n
n_{in}
nin是指输入的通道,这个公式得到的经过卷积操作后的一个像素。公式中有三个
∑
\sum
∑符号,对于一个求和符号而言,我们可以将被求和的数据并行计算完,再进行求和,也就是说我们可以将这个公式拆解为三层:
- y方向的所有乘法并行进行并求和
- x方向的所有乘法并行进行并求和
- 输入通道间的所有乘法并行进行并求和
如果三种同时满足就是我们前面提到的卷积核内部全并行的方式,但是这显然是不可行的。为了整齐性(也就是逻辑简单容易实现),我们的并行结构是在这里面选取的,如果选择1或者2中的一个,与3进行组合,那么在本demo中各层的每一个卷积核需要的DSP单元数量如下表所示:
层 | 单个卷积核的DSP需求 |
---|---|
卷积层1 | 5 |
卷积层2 | 20 |
卷积层3 | 40 |
这种方式我们假设卷积核间全串行,那么DSP需求下限为65个,是可以满足的。如果只选取3的话,也就是输入通道内全串行,输入通道间全并行,那么在本demo中各层的每一个卷积核需要的DSP单元数量如下表所示:
层 | 单个卷积核的DSP需求 |
---|---|
卷积层1 | 1 |
卷积层2 | 4 |
卷积层3 | 8 |
这种方式显然板子是能够cover的,那么我们在此基础上去考虑卷积核间的并行程度。这时候我们来结合缓存进行分析。
首先我们来看一个卷积层的计算流程,这里贴一张传统老图,图片来自这里,这是一个单输入通道卷积核大小为4*4的卷积操作
我们可以看到在计算得到第一个像素的时候,我们只需左上角的4 * 4个数据就好,另外很显然我们可以看到的是,随着卷积核对应位置的移动,每次计算结束后,左上角的数据将不会再被使用,也就是说可以释放这部分存储空间。对于多输入通道的卷积,也就是3D卷积,我们需要的只是对于一个3D输入的左上角部分 4 * 4 * 输入通道数个数据。如下图所示
所以说我们在缓存这部分数据的时候,当满足了这一层的计算要求,我们就可以开始这一层的计算。按照前面动图的顺序执行流程,我们只是需要存储前三行和第四行的前四个数据,就可以开始这一层的计算,而由于每次计算,位于左上角的数据就不会再被利用了,因此新来的数据可以直接覆盖在这个位置。也就是说,只要我当前层计算的速度足够快,上一层的输出速度就追不上我,这样一来缓存空间就只剩下了一小部分。
以上的分析我们基于连续的两个卷积层,实际上,如果这里是卷积层+池化层,池化层+卷积层,这一结论仍然成立。
这样的方式当然不是永远能够使用,前提是所需要缓存的数据的前后两层卷积层必须占用不同的硬件资源可以同时进行计算。当然我们这个demo是恰好完美的符合了这个要求的。
池化层前面的数据显然不用说了,池化的速度是非常快的,足以让前面的卷积层输出追不上,那么池化层的输出与卷积层的输出呢?因此这就又回到了我们DSP资源的分配上来,如何能够让后面的卷积层足够快,快到前面的层的输出追不上。
继续说存储空间,有没有办法进一步减小缓存空间呢?继续来分析前面的图,如果我能够一次性提供所有的左上角的这些数据~~是不是突然觉得这些数据不需要被缓存,爽的一批?可是这些数据还是要被缓存,因为只有左上角的数据后面不会被使用,除非步长大于等于卷积核尺寸。但是一般来说我们不会这样做,但是有一种层确实是这样做的——池化层。一般而言池化层会选择2 * 2的尺寸且步长为2,也就是说,如果我一次性给池化层能够有足够的数据进行一次池化,那么这些数据经过池化操作后可以被丢弃,也就是说这部分不再需要缓存,池化层前面的缓存空间就可以去掉了。
到这里存储空间的分析结束,我们来针对这部分分析分配DSP单元。这里我们的分配需要尽可能去满足两点:
- 能够让卷积的层同时输出足够池化层进行池化的数据
- 能够让后面的层计算速度足够快,使得前面层的输出可以只缓存部分
注意这里说的是尽可能,因为如果DSP单元不能满足,那就增大缓存嘛~反正现在这个板子能cover的。
对于要求1,我们的方法就是同时计算相邻的四组卷积,这样就可以一次性输出四个数据(当然一次输出十六个相邻的数据也是可以的,甚至这是有其他好处的,我们后面会提到),如下图所示:
当然对于多卷积核的层来说,需要所有卷积核对应的通道同时输出,不然有的通道输出了,有的通道没有输出,就会导致已经输出的数据仍需要缓存等待其他通道的数据,这就要求我们在卷积核之间是全并行的,因此前面DSP单元的分配方案就可以选出来了:
卷积核内部部分并行,卷积核之间全并行
为了满足要求2,我们需要分析一下相邻两层计算速度之间的关系。
以我们的demo为例,卷积层1的卷积核尺寸为
5
×
5
5\times5
5×5,输入通道数量为1,输出通道数量为4,由于前面提到我们需要让输入通道间全并行,而相邻多组卷积之间的计算时间是一样的,因此我们只需要去计算一组卷积所需要的时间。前面提到过我们使用DSP单元来例化成乘法器,一个时钟内一个乘法器可以计算一次乘法,而加法可以作为流水线跟随在乘法后面,时序图如下所示,最终的计算时间等于
乘
法
数
量
÷
分
配
D
S
P
数
量
+
1
乘法数量\div 分配DSP数量+1
乘法数量÷分配DSP数量+1
卷积是个流水线过程,乘法的进行无需等待加法的完成而可以连续进行,我们近似认为计算时间等于
乘
法
数
量
÷
分
配
D
S
P
数
量
乘法数量\div 分配DSP数量
乘法数量÷分配DSP数量
第一层每一次卷积的乘法数量为
5
×
5
5\times5
5×5,第二层每一次卷积的乘法数量为
5
×
5
×
4
5\times5\times4
5×5×4。也就是说,二者的区别在于输入通道的数量,所以如果说我们将所有层卷积核内每一个输入通道内部使用同样多的DSP单元,那么不同层之间每一组卷积所需要的卷积核数量的比值就等于输入通道的比值。
而由于我们会使得第一层卷积层同时进行相邻的4组卷积计算,才能够使得第二层卷积层接收到一个数据/每通道,因此两层的比值还需要乘4。
前面提到过,如果同时进行16组卷积并行计算,那么第二层卷积就会每次接收到4个数据/每通道,也就是说第二层卷积仍可以同时进行4组相邻的卷积,这样这一层的输出仍不需要缓存直接池化。
因此,假设我们一个输入通道内分配
x
x
x个DSP单元,第一层需要
16
×
4
×
1
×
x
16\times4\times1\times x
16×4×1×x这里的16指16组卷积并行,4指4个卷积核,1代表着1个输入通道。那么第二层需要
4
×
8
×
4
×
x
4\times8\times4\times x
4×8×4×x
这里还有第三层卷积层,按照前面的要求的话,我们为了能够让池化层不再缓存,我们需要同时计算4组相邻的卷积,那么就需要第二层卷积层同时计算16组卷积,第一层偶同事计算64组卷积,这样一来对DSP单元的需求呈指数型增长,显然是不可以接受的,因此这样的方式最多能够针对相邻两层,再多会导致DSP资源的不足,所以就牺牲一些缓存空间来保证DSP数量的充足。
因此第三层需要DSP单元数量为:
1
×
16
×
8
×
x
1\times16\times8\times x
1×16×8×x
这样一来总计需要的DSP单元数量为:
320
×
x
320\times x
320×x
板载DSP单元共有900个,所以x的最大值为2,就是说,每个输入通道内分配两个DSP单元进行计算。
每个输入通道内进行的是
5
×
5
5\times5
5×5的2D卷积,共有奇数个乘法,如果提供2个DSP单元的话逻辑会稍微复杂一点点(真的只有一点点),所以本着一切从简的原则,我们只分配1个,(当然分配1个的好处是我们还可以买便宜一些的板子,节约money)。
言归正传,我们现在已经得到了每一层卷积层的DSP单元分配数量。我们再回过头来看看我们各个层次的并行情况。
- 输入通道内是串行的
- 输入通道间并行
- 卷积核间并行,也可以被称之为输出通道间并行。
- 层间算不算并行呢?反正是在某些时刻不同的层在同时进行
资源的分配大概的思路就是这样,针对不同的结构,不同的FPGA板载资源,来从不同的角度去考虑。
下一节开始将分别设计不同的功能模块。