AI芯片:深鉴科技基于深度压缩的FPGA设计

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/evolone/article/details/80614312

众所周知,深度学习属于计算密集型,模型中参数众多,占用很大的存储空间。这一特点,在嵌入式终端上应用时,因为硬件资源有限,就成了制约实际应用的瓶颈。
因此,减少模型需要的存储空间有着迫切的理论及现实意义。

深鉴科技的创始人韩松,本科毕业于清华大学,斯坦福博士,目前在MIT,一直研究深度压缩技术,并在FPGA上实现了基于深度压缩技术的ESE,成果发表为论文。
本文主要是分析韩松的2篇论文,来一窥深度压缩的玄妙。
2篇论文,一篇关于深度压缩技术,一篇关于深度压缩技术在语音识别上的FPGA方案ESE。
论文如下:
(1)Deep Compression:Compressing Deep Neural Networks with Pruning, Trained Quantization and Huffman Coding
(2)ESE:Efficient Speech Recognition Engine with Sparse LSTM on FPGA

下文首先分析深度学习技术,然后再看看深度学习技术的FPGA实现。

一、Deep Compression

论文主要介绍采用深度压缩技术存储深度学习神经网络模型的大量权重,将所需的存储空间压缩数十倍之多,并且并不损伤模型的准确性。
总体来说,深度压缩技术比较有现实意义,值得细细品味。
深度压缩技术,采用剪枝+量化+霍夫曼编码,形成三级大流水,如图1所示,实现高度压缩权重占用的存储空间。
在硬件上计算模型时,需要将经过深度压缩存储的权重值重新解压缩。
这里写图片描述
图1 三级压缩流水线
由图1可知,剪枝(pruning)会减少weights的数量,量化(quantization)会减少weights的种类,都对网络模型的准确性造成影响,因此都需要反复训练网络达到原始网络的准确精度。而霍夫曼编码(huffman encoding)并不修改weights的数量,只是改变编码方式,并不影响网络的精度。
这里写图片描述
图2压缩存储稀疏矩阵
经过剪枝和量化之后的weight矩阵,是稀疏矩阵,如果依然将矩阵的所有存元素(非0元素和0元素)都存储,那么存储空间依然和原来的一样,但是,如果只存储非0元素(可能只占总数的10%,甚至更少),就能大幅度减少存储空间,达到压缩存储的目的。
本文采用相邻非0元素之间的相对距离来存储非0元素,如图2所示。其中表格第二行diff,代表相邻非0元素之间的相对位置。这样有个缺点,就是从压缩存储的weight恢复成矩阵模样需要按顺序解码,这样解码就是串行的。另外,本文只采用3bits编码间隔,最大间隔为7,意思是说,假设相邻两个非0元素的间隔不超过是7。如果超过了7,就插入0作为中转,并记录下来。
这里写图片描述
图3 权重的量化共享
如图3所示,为training过程中计算并更新weight的过程。
首先看左侧的两个图,左上图为权重weight,左下图为梯度,都是4X4的矩阵。
观察weight矩阵的元素值,发现16个元素的取值大小其实可以划分成4个类,由四种颜色区分,相同颜色的weight用一个统一的值代表,这就是权值共享。这样可以形成一个共享weight查找表,这里因为只分成4种,故可以只用2bits去编码并替代原来的32bits的浮点数,如图3中间的cluster index所示,实际计算时,根据2bits的编码去查找正确的32bits权值。
根据左上图的四种颜色分类将左下图的梯度值按照同样的分组,将相同颜色的梯度值累加起来,然后与learning rate (lr)相乘,再与查找表中对应的weight相减,得到更新后的权值。

真正在计算权值的分类时,采用的是k-means聚类方法。
另外,权值的聚类分类,只在同一层layer中有效,不能跨layer共享权值。

好吧,说了这么多,那到底效果怎么样呢?
直接上图。
这里写图片描述
图4 深度压缩实验结果
可以看出,在不损失模型准确度的前提下,模型参数参数由最原始的240M压缩到6.9M,压缩率达到惊人的35倍。

可以看出,深鉴科技的深度压缩效果还是很惊人的。

二、ESE

这篇论文,将上文的Deep Compression技术在FPGA上具体实现。
为了达到更高的效率,本文从三个层次提高计算效率:算法优化,编译高效的调度程序,硬件加速。如Figure 2所示。
这里写图片描述
本文设计的语音识别系统中,最占用运算资源和存储资源的是LSTM算法。故本文着重优化LSTM算法的计算。
这里写图片描述
LSTM算法的数据流如图Figure 4所示。的确比较复杂。其中涉及到许多的矩阵计算以及两个向量对应元素相乘的计算。
这里写图片描述

这里写图片描述
因为网络模型中的参数矩阵,本来就比较稀疏,再经过稀疏处理,就更加稀疏了,往往有超过90%的元素变成了0,这就给压缩存储带来了可能,并且能够减少总的乘法计算次数,减少计算时间。
因为参数矩阵变得稀疏,而这种稀疏性往往分布是不均匀的,这样,就会给矩阵计算带来不规则性,造成计算密度不平衡。常规的硬件加速结构对稀疏矩阵的计算,并没有明显的优化,一定程度削弱了通过稀疏化带来的性能增强。如果能够在剪枝的时候,兼顾平衡矩阵稀疏的平衡,那么就可以在此提升计算效率。
如图Figure 5 所示。
这里写图片描述
在计算机编程中,对稀疏矩阵,一般采用三元法进行存储。
本文采用类似的方法存储稀疏矩阵的非0有效系数,只是仅仅考虑同一行元素之间的相对位置关系,不同行之间按照行的顺序依次存储,整体来看,是把矩阵形式的权重参数拆分成以行为单位的向量形式。
并且,因为系数量整体较大,所以在DDR中存储,并通过PCIE传输到计算单元,而DDR一次传输 512bits数据,PCIE一次传输128 bits数据,在考虑到是byte-aligned的存储方式,所以最终本文将权重weights量化压缩成了12 bits的定点数,采用4 bits编码表示相邻两个非零权重参数的相对距离,凑成16 bits。并且在数据不足的地方补零。
如图Figure 7所示。
这里写图片描述

说实话,看了这篇论文之后,理解最模糊的就是下面这个图Figure 8了。一直没有理解这里描述的用意。
先把我自己的理解以及疑惑写在这里,希望有知道的大神能够指点一二。
按照论文中的描述,输入向量a有6个元素{a0, a1, a2, a3, a4, a5},对应的权重矩阵是一个8X6的稀疏矩阵W.
执行矩阵乘法,W X a = b。
那么,整个运算过程可以看作:
W[8,6] X a[6,1] = b[8,1]
其中,a[6,1]表示a是6行1列的矩阵。
整体来看,就是a的某个元素(比如a3)与W的对应列(比如W[*, 3])的所有非0元素都要相乘,也就是文中图上标注的a3 X W[3]。
虽然整个计算来看,的确是要计算a3 X W[3],但是因为W[3]的非零元素在W中分属不同的行,而计算结果应该是W的每一行分别与a的对应元素相乘累加形成一个结果,最终W X a形成一个向量b,所以这些计算是在不同时刻计算的,而且计算结果应该分属不同行。

疑惑:
但是第一眼看到Figure 8 ,我就直接认为是PE0直接计算了三个权重与a3的乘法运算,PE1计算了一个权重与a3的乘法运算。从而一直无法想通这样做的用意。
这样的好处是,这几个计算,都会采用同一个参数a3。
这样的坏处是,一个PE一个周期只能计算一次乘法,所以如果有多个乘法,就需要排队计算,而且,这些乘法的计算结果之间并无直接关系,需要存储起来,之后按照行将计算结果累加。
如果真是这样的设计,那么两个PE执行了a3 X W[3]后,计算结果之间是毫无关系的,需要存储在act buffer中。这样需要一定空间的芯片内存储资源。另外因为计算结果分属不同的行,所以还必须有控制逻辑去准确调度计算结果的存储及读取,这个往往更难。
真这么做,感觉得不偿失。
这里写图片描述
因为权重矩阵的稀疏性,造成每个PE上的计算负载不平衡,又因为这些PE会共享输入向量,所以,必须等都计算完成,才能切换到下一个输入向量,这就会造成计算任务少的PE会有比较长的等待时间,如图Figure 9所示。
这里写图片描述
下面介绍整个硬件加速系统,如图Figure 10(a)所示。
这是CPU+FPGA的混合结构。
因为计算用到的数据量大,需要额外的存储芯片。
CPU中运行控制程序。
FPGA中运行硬件加速器ESE。
CPU通过PCIE与FPGA交换数据。
外部存储芯片通过MEM Controller 与FPGA交换数据。
可以看出,FPGA中的ESE加速器,主体结构是由多个Channel组成,每个Channel由多个PE组成。ESE加速器的输入输出都有buffer来缓存数据。整体上看,结构设计与谷歌的TPU有些类似。

扫描二维码关注公众号,回复: 3045295 查看本文章

这里写图片描述
设计的核心在于每个Channel中的设计,如图Figure 10(b)所示。
图中的SpMV就是核心模块,其他模块可按照配合SpMV的先后顺序做介绍。另外,ElemMul(每个元素对应相乘)与激活函数都不是关键路径,所以这里就不再重点介绍。
SpMV有两个输入,一个是输入数据,这个由上方的ActQueue提供,另外一个是权重参数,这个由SpmatRead及PtrRead提供。

ActQueue,内部有n(n为单个Channel中PE的个数)个16 bits 宽的FIFO,深度1~16,这个由实际试验求得,原则是不影响延迟的情况下选择最短的深度。每个FIFO中存放的是与一行权重参数中非零元素对应相乘的输入数据,专门输入同一个PE。
猜测:
ActQueue每次取来一个Vector a,每个PE根据各自的SpmatRead及PtrRead中提供的某一行中非零权重元素的位置,对应获取Vector a中的元素aj,并存入对应的FIFO(或许会出现同一个元素aj在某个FIFO中出现多次的情况,这样就必须要求ActQueue中的Vector a的单个元素必须支持同时最大n个FIFO的同时读取)。这样PE中的SpMV就只管计算非零的乘法。而因为FIFO的存在,可以避免PE之间负载不均衡导致的等待时间。

PtrRead中的指针按照2个一组存放,一个头指针pj,一个尾指针pj+1,两个指针联合起来指明一行(W[j , *])权重参数中非零参数的开始位置和非零参数的数目。
SpmatRead用pj和pj+1去查询行W[j , *]中的非零元素。

SpMV计算得到乘法结果,送到Accu中累加,中间结果存储到Act Buffer,等一整行的非零元素的乘法结果都累加起来之后,将所有PE的计算结果收集起来,要么作为最终的输出结果y,要么作为中间结果,送到Adder Tree,做加法,比如加上bias。然后将结果送入激活函数处理,之后传入ElemMul中处理。

可以看出,整个PE内部的设计,是完全为了贴合LSTM计算。
这里写图片描述
Figure 11详细划分了LSTM算法的计算过程,包括计算类型和计算先后顺序。
按照表格的行方向,从左往右,是时间先后顺序,由于有先后依赖关系,这会对计算调度形成要求。
按照表格的列方向,同一列,表示可以同时并行计算,但是如果存在计算资源冲突,那么还是无法真正并行计算。
这里写图片描述
说了这么多,最后来看看实际效果,见图Figure 15.
整体来说,因为稀疏性,最终可大幅度删减90%权重参数,而并不明显影响模型精度,而此时,跟原始稠密的计算相比,速度提升了5.5倍,如果加入本文提出的load balance技术,那么可以进一步加速到6.2倍。
总的说来,效果还是很明显的。

猜你喜欢

转载自blog.csdn.net/evolone/article/details/80614312