HLS 加速卷积神经网络前向计算(毕设小结)

时间:2024-03-22 18:22:32

       本文主要是本人本科毕业设计的主要工作。

       主要工作有两部分,一是实现二值卷积神经网络模型,二是将模型的前向计算部署到FPGA上。也就是说FPGA负责的是神经网络的前向计算过程,也就是预测的过程,训练还是通过CPU。

       二值卷积神经网络是参考复现XNOR-Net,训练集就采取了最简单的mnist,实现方法是采用的pytorch,HLS中的前向计算是通过C++编写的,也就是说用python实现了一遍网络训练,又用C++重写了一遍前向计算。这里用pytorch框架将模型训练好之后,将模型参数提取出来,保存为文本或者二进制,再用HLS通过文件读写的方法读取参数从而实现前向计算。

HLS 加速卷积神经网络前向计算(毕设小结)

       模型有两层卷积和两层全连接,因为是二值网络,第二层卷积核和第一层全连接是二值化的参数。

       主要的问题有两点,1是如何加速卷积,2是如何加速全连接。

1.卷积加速

       卷积的加速分为4个层面,位宽加速、算法加速、HLS优化指令加速、访存加速。

       位宽的加速就是放低精度,其实也就是二值化的主要原因,将参数二值化之后大大降低了参数量,也是所有加速中贡献最大的加速。参数的二值化是我们在训练的时候就将前向计算的参数二值化,同时保留参数的缩放因子,利用二值化后的参数求解梯度,更新的却是全精度的参数。这里的相关内容可以参考XNOR-NET的论文或者相关文献。

       算法层面的加速并不能到提高时间复杂度这种量级,但也对性能优化有很大贡献。因为前面我们已经将参数二值化,所以再进行卷积操作的时候就可以利用同或运算和计数器代替乘和加,也就是所谓的XNOR&bitcount,这点参考XNOR-Net。还有一种加速是将**操作后置,放到池化的后面,前提是模型采用的最大池化的方法。因为**函数都是单调递增的,在最大池化的前提下,先池化再**和先**再池化是一样的结果,明显先池化再**的计算量更低。

HLS 加速卷积神经网络前向计算(毕设小结)

        HLS指令优化加速和访存加速都是利用了HLS的优化指令,前者主要通过循环展开,后者引入缓存,将缓存置于访存速度更快的内存块之中,从而加速整个卷积层的计算速度。

HLS 加速卷积神经网络前向计算(毕设小结)

        这一步的加速其实非常玄学,我做了很长时间,并没有发现一个良好的、值得分享的办法。加速贡献最大的是循环展开,但是循环展开,在哪一层循环展开,这就得具体问题具体分析,循环展开的层数高了,资源就会不够用,循环展开的层数低了,加速效果就没那么明显。甚至说光看循环层数都不靠谱,还得加上问题的规模,比如一次卷积了20个核和一次卷积1000核,循环展开层的位置都得有所变动。所以就得试,一次一次的试,而且有时候这个小破本综合半天不出结果。。访存加速也是如此,亲身经历,卷积层加入缓存,缓存置于BRAM,速度有一定的提升,全连接层加入缓存,不管缓存仍哪速度都会变得更慢。

2.全连接层加速

       全连接层亲身写过神经网络的基本都知道,就是一个矩阵乘的事。矩阵乘作为一种常见的运算,HLS提供了一个库函数

hls::matrix_multiply可以进行运算,而且这个函数里面是自带优化指令的。

#include<hls_video.h>
#include"hls_linear_algebra.h"
typedef int mat_a_t;
typedef float result_t;
mat_a_t a[MAT_A_ROWS][MAT_A_COLS],[MAT_B_ROWS][MAT_B_COLS];
result_t res[MAT_A_ROWS][MAT_B_COLS];

hls::matrix_multiply<hls::NoTranspose,
hls::NoTranspose,
MAT_A_ROWS,MAT_A_COLS,MAT_B_ROWS,MAT_B_COLS,MAT_A_ROWS,MAT_B_COLS,mat_a_t,result_t>(a,b,res);

       根据我手写快排但是速度不如STL中sort的经验,我觉得这个肯定比自己写要强吧,然而并没有,确实比一条优化指令都不加的矩阵乘要好,但是并不如自己设置优化指令。不过这个函数在数据量非常小的情况下(两只手数的过来),还是可以用的。

3.小结

       论文前期先用了一段时间学习神经网络,分别在matlab和opencv(C++)实现了一遍简单的,后来又用pytorch实现了复杂的卷积神经网络,再优化成二值卷积神经网络。同时在HLS学习上感觉中文的文献很少,靠谱的英文文献有ug871,但是其中的案例也是不能知其所以然。

       论文中提及的工作用的时间并不长,大部分时间都用来鼓捣zedboard了,不过一直到最后也没跑到板子上,其中原因还是很复杂的,简单的hello world是能跑通的,但是引入自己写的ip核就会出现很多问题,比如怎么调用数组接口,怎么在Xilinx SDK中实现读写模型参数,调用板子等等。这些问题都有明面上的解决办法,只不过在解决过程中新的问题一个接着一个,不知不觉就到了答辩的日子了,以至于到最后也没实现跑板。论文只有HLS仿真的结果。