作者 / 肖昱 姚天尧 顾嘉盼 张延军 北京理工大学 信息与电子学院(北京 100081)
*第一届(2016-2017)全国大学生集成电路创新创业大赛全国总决赛FPGA设计方向获奖作品
卷积神经网络(Convolutional Neural Networks)是一种主要应用于图像处理领域的人工智能算法。尤其是在计算机视觉领域,CNN在包括识别(recognition)、检测(detection)、分割(segmentation)等很多任务中占主流地位。
卷积神经网络的基本元素:卷积层(convolutional layer)、池化层(pooling)、激活函数(activation)、全连接层(fully-connected layer)。卷积神经网络(例如LeNet[3])对于神经网络(Neural Networks)最大的创新在于卷积层。卷积层在整幅图像上使用相对很小的卷积核(convolutional kernel)进行特征检测,实现了将原始图像直接输入神经网络而不会引发维数灾难。克服了之前基于手工特征和分类器的系统需要分别训练的缺点,使得图像分类任务实现了端到端(end-to-end)。
想要将卷积神经网络投入实际应用,例如在移动端利用服务器训练好的卷积核参数进行实时的物体或者人脸识别,需要对CNN进行许多改进。其中,卷积层运算需要消耗大量计算资源,使用串行计算方式速度不快。因此,提高CNN卷积层的计算速度是CNN进行实际应用时需要解决的一个重要问题。
1设计标准与约束
1.1设计问题
“将上述算法在硬件平台上实现并优化,主要考虑总线带宽,内部缓存,Pipeline设计,计算单元等因素,给出不同缓存下,总线带宽计算公式和典型案例下的数据。”
由赛题分析,本设计方案主要分为两部分:实现方案、优化方案。实现方案涉及系统架构的设计、计算单元的设计等;优化方案涉及总线带宽和片上缓存在不同情况下的设计等。
1.2设计标准
竞赛题目中提到的标准:
1)运算速率:100GMAC,每秒100G次乘加运算;
2)输入图像数量:1024 channel,1024个输入feature map
3)卷积核数量:1024,1024个输出feature map
4)输入输出图像分辨率:150*150
我们对细节进行的假设:
1)数据宽度:8位,像素点取值范围(0~255)
2)卷积核尺寸:3*3
1.3约束条件
本设计方案基于Altera公司的Stratix IV EP4SGX230KF40C2,其有两项硬件资源约束:
1)硬件乘法器;
2)片上RAM;
本设计方案在设计时对不同DDR在不同数据总线带宽下给出相应的缓存方案,在实现中使用DDR2 SRAM。实现中的DDR2数据总线带宽通过实验测出,其他类型的DDR数据带宽通过查阅资料进行估算。图一列出了Micron公司不同DDR在FPGA平台上的参数,我们将根据这些数据进行随后的计算。
图0 不同DDR SRAM参数(Micron)
2设计总体架构
2.1硬件架构
我们会使用如下图中的总体架构:数据开始存放在DDR中;由DDR读写IP核将数据从DDR中读出;数据由DDR读写IP通过FIFO进行跨时钟域操作送给片上RAM缓存;再将数据由RAM中读出送给并行计算单元阵列进行卷积运算,得到运算结果;运算结果暂存在片上RAM中;再从RAM中将运算结果读出,通过FIFO送给DDR读写IP写回DDR中。
图1 系统架构设计
在设计的过程中,我们将在计算单元和缓存中采取两种不同层次的数据复用策略,以降低系统对于带宽资源的需求。
2.2优化问题的数学模型
我们认为,对卷积运算进行加速的关键在于:在充分利用读写带宽的前提下,通过使用片上缓存和并行策略,尽量提高系统的并行度以达到设计标准。
显然地,系统对于DDR的读写速度将远慢于并行运算的速度,所以最大化地利用读写带宽将是不可避免的。根据查询到的资料,以任何现有内存设备的读写速度,都是不能支持100GMAC的串行卷积运算的。因此,并行地进行卷积运算也将是不可避免的。此时,带宽资源还有可能不足,可以使用片上RAM缓存对已读取的数据进行复用以进一步减小对于带宽的需求。
总而言之,我们希望最大化地利用带宽资源,并使用并行设计和RAM缓存使系统达到100GMAC的设计标准。
考虑到片上RAM成本较高,目前的FPGA普遍不具备很大的片上RAM容量,所以在设计中我们希望使用尽可能少的片上RAM资源。由此,可以用数学语言描述本次设计中的优化问题:
(定义复用率X:若一次读取的一个输入图像与若干数量的卷积核进行卷积运算,则此卷积核的数量占全部卷积核数量的比例为X。)
从以上问题中可以分析得出,RAM资源需求是复用率X的线性函数;运算速率与X无关;数据速率是X的反比例函数。因此本优化问题是关于变量X的非线性优化问题。
3设计细节分析
3.1计算单元设计:串行输入,并行计算
3.1.1计算单元设计的目标:数据复用
在卷积运算的计算单元中,我们希望对数据进行复用,
并行计算的可能性根植于卷积层的设计中。在全连接的神经网络的每一层中,每一个输入和每一个输出之间都有一个权重(w),每一个输出都与全部输入的信息相关;而在卷积神经网络中,存在着卷积核的共用(shared-weight),这使得每个输出图像都包含着一个卷积核对于所有输入图像中特征提取的信息。具体而言,每个卷积核会对所有输入图像的各个区域进行运算处理。于是这种卷积核在卷积计算过程中的重复使用,为并行计算提供了可能。如果我们能够由这种重复使用的性质,尽量减少对于卷积核数据的读取次数,那么卷积运算的时间将大大减少。
输入图像在每一个点上的数据参与卷积运算的次数(乘法)是卷积核的尺寸(3×3),不仅卷积核的数据在串行计算的过程中被反复读取,输入图像的数据也在被反复读取。在串行计算中,卷积计算的滑动窗口(sliding window)在读取端,每一个点都被反复读取了相应的次数。我们希望把通过设置一些寄存器,将滑动窗从数据的读取端移动到计算端,实现对输入图像的每一个点进行一次数据读取就完成其在滑动窗中所需进行的所有乘法运算(9次)。也就是说,我们希望能够串行得从内存中读取数据,每一个点仅进行一次读取就足以完成其在卷积运算的滑动窗中所要进行的9次乘法。
3.1.2计算单元的设计
现在,我们暂且假设从内存中读取来的数据是均匀的流(每个时钟周期读取一个像素点的数据);我们也暂且假设卷积核是不更换的,也就是指仅有一个卷积核的情形。本节中,我们对于数据是均匀的流以及只有一个卷积核的情况,设计一个高效的并行计算单元。
在计算端设置一些缓存以实现卷积核数据和输入图像数据的复用是通过图2和图3中的设计实现的。
图2 计算单元(processing element)的设计
图3 计算子(processing unit)的设计
3.1.3计算单元的工作原理
卷积核的值直接被保存在计算子的寄存器中,而输入图像从由SRAM构成的移存器一端输入(同时送给第三行的寄存器)。由SRAM移存输出的图像数据也分别送向第一行和第二行的寄存器。每行的计算子的寄存器之间也被设计为移存关系,数据由第三列送向第二列,再送向第一列。
在这种设计下,每一次计算单元在一个时钟周期可以完成9次乘加运算。由此,可以根据设计标准,在合理的时钟频率下得到系统所需要达到的并行度。经过综合考虑,并行度n为64时可以保证时钟周期的合理且达到100GMAC的运算速率。可以求得所需的时钟周期:
3.2优化缓存和带宽
3.2.1使用缓存实现进一步数据复用
在100GMAC的计算速度下,通过简单的计算可以得知,如果不对已读取的输入图像数据与多个卷积核进行运算,则不可满足带宽资源的需求。根据公式②和图像和卷积核的尺寸以及运算速率得出这种情况下系统所需的最小数据速率:
以上这个大约11G的带宽需求是难以满足的。因此,必须降低系统对于带宽的需求,片上RAM提供了一种合理的途径。
通过RAM降低带宽需求的想法如下:卷积核的尺寸远小于输入图像的尺寸,将大量的卷积核数据储存在片上的代价并不大。如果在读取完一个输入图像后,将其与若干个不同的卷积核进行运算,相当于将同样的数据复用了若干次,使得DDR读写IP有更多时间进行下一幅输入图像数据的读取。这样就降低了系统对于带宽的要求。需要注意的是,同一幅输入图像与不同的卷积核进行运算的结果需要分别储存在片上,这就造成了对于片上RAM的需求。
3.2.2不同复用率下的缓存和带宽需求
正如之前所述,如何在带宽资源需求和片上RAM资源需求之间取舍是本次设计的重点。
下面根据查询到的数据大致计算不同的复用策略下所需的最小片上RAM容量。
表1 不同复用率下的缓存和带宽需求
(*第一列表示不同的并行策略,X表示每次读入一幅输入图像,与其进行卷积运算的卷积核数量占全部卷积核数量(1024)的比例;第二列表示不同并行策略下所需的最小片上RAM空间;第三列表示不同策略下的最小数据读取速率)
3.2.3不同数据带宽下的最小缓存需求
下面根据DDR产品(以Micron公司为例)的用户说明中的数据对于四种DDR在不同运行设置下的数据带宽进行计算,并估计最大复用率时所需的片上RAM容量:
表2 不同DDR的最低缓存需求
(*各列交替表示不同类型的DDR的读写速度和相应的最小片上RAM容量;各行表示各类DDR在不同工作状态下的不同情况:Long Max(16位宽数据总线,最大数据速率),Long Avr(16位宽数据总线,平均数据速率),Short Max(8位数据总线,最大数据速率),Short Avr(8位宽数据总线,平均数据速率))
4实现结构
4.1 DDR2的接口设计和测试
“我是谁?我从哪里来?我要到哪里去?”这是哲学三大终极问题。对于数字系统架构设计,亦是如此:我们不能仅仅关注数据的固有性质和系统的运算方式,更不能忽视数据的读写速度和系统的存储结构。
在本系统中,输入图像、卷积核和运算结果都存储在DDR中,而且输入图像的规模和数据带宽是比较大的,因此DDR的传输速率势必会对整个系统的效率产生非常大的影响。所以设计DDR接口与系统总线并测试其传输速率是非常有必要的。
在Altera FPGA中,使用Avalon总线接口可以简单高效的组件系统,Avalon总线接口适用于高速数据流,读写寄存器,存储器,以及控制片外设备。这些标准接口在Qsys中有效地设计到组件中,其架构示意图如图4所示。
图4 Avalon总线示意图
在此图中,NIOS II处理器使用Avalon-MM接口存取片内组件的控制寄存器和状态寄存器。分散集中DMA使用Avalon-ST接口发送和接收数据。四个组件包括利用软件运行在NIOS II处理器上的中断接口服务程序。PLL通过Avalon clock sink接口接受一个时钟,并提供两个时钟源。两个组件包括Avalon-TC接口存取片外存储器。最后,DDR控制器使用Avalon conduit接口存取外部DDR3存储器。
DDR-SDRAM存储体采用突发传输模式[2]。在此模式下,把多个传递作为一个单元执行,不是独立地处理每个字。突发可提高从器件端口在一个时间处理多个字时达到较大效率的能力。突发中的纯粹作用是为了突发的持续而锁定仲裁。支持读写突发的Avalon-MM接口,必须都支持读写突发。其读写时序规则如图5和图6所示。
图5 Avalon Burst模式 读时序
时序图中的序号,表示随后的变化:
1. 在CLK上升沿之后,主器件断言address(A0),burstcount,和read。从器件断言waitrequest,引起除beginbursttransfer之外的所有输入直到另一个时钟周期保持不变。
2. 在CLK上升沿从器件捕获A0和burstcount。在下一个周期可启动新的传递。
3. 主器件B驱动address(A1),burstcount和read。从器件断言waitrequest,引起除beginbursttransfer之外的所有输入保持不变。此时,从器件最早从第一个读请求返回读数据。
4. 从器件传送有效的readdata和断言readdatavalid,给主器件A传递数据的第一个字。
5. 给主器件A的第二个字已经传递。从器件解除readdatavalid暂停读突发。从器件端口可保持解除readdatavalid任意时钟周期数。
6. 给主器件B的第一个字已经返回。
图6 Avalon Burst模式 写时序
时序图中的序号,表示随后的变化:
1. 主器件断言address,burstcount,write,并驱动writedata的第一个单元。从器件立即断言waitrequest,表示其没有准备好进行传递。
2. waitrequest为低电平。从器件捕获addr1,burstcount和writedata的第一个单元。在传递随后的周期,address和burstcount都被忽略。
3. 在CLK时钟上升沿从器件捕获数据的第二个单元。
4. 突发被暂停直到write被解除。
5. 在CLK时钟上升沿从器件捕获数据的第三个单元。
6. 从器件断言waitrequest。在响应中,所有输出直到另一个时钟周期都保持不变。
7. 在CLK时钟上升沿从器件捕获数据的最后一个单元。从器件写突发结束。
按照以上规范设计DDR2接口[3],仿真结果如图7所示。
图7 DDR读取的ModelSim-Altera仿真波形
在图7中,可以看到读取6个Bank的64位数据需要16个时钟周期的时间,因此可以计算DDR的读取速率为
4.2系统实现设计结构图
图7 系统实现设计结构
(*IP READ/IP WRITE分别为读端和写端控制DDR IP的状态机,均通过AVALON标准总线与DDR IP相连;FIFO用于跨时钟域传输数据,DDR接口部分与卷积运算部分使用不同的时钟。)
5实验
5.1 MATLAB算法验证
我们实现对于卷积算法进行了MATLAB验证,并使用随机生成的卷积核对灰度图像进行了卷积运算,结果如图5所示。
图8 卷积算法的MATLAB验证
5.2计算单元设计的MATLAB验证
为了证实卷积计算单元设计的可行性,我们在MATLAB上编写了计算单元的代码,并使用MNIST数据集中的图片和随机生成的卷积核进行了实验。实验结果如图6所示。
图9 计算单元设计的MATLAB验证
参考文献:
[1]中星微电子集团:深度学习——卷积神经网络(CNN)优化
[2]Chen Zhang, Peng Li, Guangyu Sun, Yijin Guan, Bingjun Xiao, Jason Cong. Optimizing FPGA-based Accelerator Design for Deep Convolutional Neural Networks.
[3] Y. Lecun, L. Bottou, Y. Bengio, and P. Haffner. Gradient-based learning applied to document recognition. Processing of the IEEE, 86(11):2278-2324, 1998.
[4] Xilinx/Micron: Micron DRAM Memory Support for Xilinx Platforms
附录
附录A:卷积算法MATLAB验证代码:
%small scale test for convolutional unit
%fixed size: 224*300, image: 'bbtest.jpg', fixed number of channels: in:3, out:3
%read image
im = imread('bbtest.jpg');
im_input = permute(im, [3, 1, 2]); %exchanging dimension
%initialize kernel
kernels = rand(3, 3, 3, 3) .* 0.005 %kernel size: 3*3, range: 1~0.005
%initialize output figure
output_fm = zeros(3, 300, 224);
%image processing
R = 300, C = 224, M = 3, N = 3, S = 1; %size of input image and input, output channels
for row = 1:R-3
for col = 1:C-3
for to = 1:M
for ti = 1:N
for i = 1:3
for j = 1:3
output_fm(to, row, col) = output_fm(to, row, col)...
+ kernels(to, ti, i, j) * im_input(ti, S * row + i, S * col + j);
end
end
end
end
end
end
%show the images
output_fm;
output_show = permute(output_fm, [2, 3, 1]);
imshow(output_show);
附录B:计算单元设计MATLAB验证代码:
% testing the processing element
% time: 13:40; 3.16.2017
% initializing buffers
procwin = zeros(3, 3);
kernel = rand(3, 3) * 0.005;
buffer1 = zeros(25);
buffer2 = zeros(25);
R_pixel = zeros(1,784); % cauculating result for single pixels
% loading data
data = ones(784);
im = imread('test.jpg');
data = reshape(im,1,784);
% initializing data
procwin(1, :) = data(1: 3);
buffer1 = data(4: 28);
procwin(2, :) = data(29: 31);
buffer2 = data(32: 56);
procwin(3, :) = data(57: 59);
%cauculating
for i = 60:784
% cauculating pixel level result
for m = 1:3
for n = 1:3
R_pixel(i-59) = R_pixel(i-59) + procwin(m,n) * kernel(m,n);
end
end
% moving data in processing window
reg2 = procwin(2, 1); % preserve for moving into ram
reg3 = procwin(3, 1); % preserve for moving inro ram
for j = 1:2
procwin(:, j) = procwin(:, j+1);
end
procwin(1, 3) = buffer1(1);
procwin(2, 3) = buffer2(1);
procwin(3, 3) = data(i);
% moving data in ram
for j = 1:24
buffer1(j) = buffer2(j+1);
buffer2(j) = buffer2(j+1);
end
buffer1(25) = reg2;
buffer2(25) = reg3;
end
im_output = reshape(R_pixel,28,28);
imshow(im_output);