深度学习在推断阶段(inference)的硬件实现方法概述
推断(Inference),就是深度学习把从训练中学习到的能力应用到工作中去。
精心调整权值之后的神经网络基本上就是个笨重、巨大的数据库。为了充分利用训练的结果,完成现实社会的任务,我们需要的是一个能够保留学习到的能力,还能迅速应用到前所未见的数据上的,响应迅速的系统。这就是推断,根据真实世界中的少量数据,迅速地提供正确的答案。 这可是计算机科学的全新领域。现在主要有两种方法来优化庞大笨拙的神经网络,以实现高速低延迟的应用。
第一个方法,是查找神经网络中经过训练后并没有用到、也就是说尚未激活的部分。这些区域在应用中并不需要,所以可以被清洗掉。
第二个方法,则是把神经网络中的多个层融合为一个单独的计算步骤。
众所周知,深度学习(deep learning)在训练阶段完成模型的建立和参数调优,在推断阶段完成具体的实现及应用。这就好比造车的过程与开车的过程一样,造车过程需要不断尝试“新设计-试车-调优”,而设计一旦定型后,就需要大规模生产制造,最终卖给用户使用。设计车型(训练阶段)时候可以借助各种工具和技术来加速研发与达到更高指标,但生产制造(推断阶段)时需要考虑成本、材料、市场等因素作出灵活适当的剪裁。深度学习在训练阶段常用GPU来加速训练,在推断阶段可以用CPU、专用芯片(ASIC)、FPGA等实现。 GPU并行能力非常强大(内部包括上千核),但成本高昂、功耗大,不适合在无人机、航天、手机通信等特定场景中应用; CPU作为通用芯片的代表,理论上将在训练、推断阶段均可使用,但运行速度教慢导致不适合做训练、功耗太大不适合在功耗有强烈要求的场景中做推断; ASIC通常是针对某个算法而设计的芯片,速度非常快,可以应用在推断阶段,但其设计周期长、成本高、缺乏灵活性不能适应深度学习算法快速更新的要求;FPGA是一种通用的芯片,在速度和功耗上位于CPU和ASIC之间,可以针对硬件编程所带来的并行行和灵活性可以很好地适应深度学习的需求(算法更新+推断阶段的要求),但其开发设计难度较大,门槛高,需要掌握硬件电路调试的知识。另外,像goolge的TPU、寒武纪的NPU,有人认为是一种ASIC,也有人称其为专用处理器(并列与CPU、DSP之外的新型处理器),他们是针对深度学习领域的算法(们)设计的一种计算芯片(显然,这是在推断阶段),其内部设计了一套适合算法(们)计算的通用结构,任何新的算法只需映射(map)到特定到硬件结构上并按序执行即可,其好处是功耗、速度均得到了提升,不足是没有配套的成熟的工具来支持其map过程。从应用者的角度来讲,在推断阶段,CPU和FPGA是目前较好的选择。从研究者的角度来讲,寻找深度学习算法的高效通用实现方法,并设计硬件架构和软件编译工具是当务之急。当然,硬件架构可以设计成专用处理、也可以用FPGA现有资源搭建,相应的软件编译工具也会有巨大差异。
当前,深度学习在推断阶段的研究主要集中在以下几个方面:降存储、降计算、降功耗、提速度、 兼顾灵活性; 借助的手段是量化数据、重用数据、优化计算结构、优化访存技术等。其目的是,依据现有的硬件资源,寻找一个简化的、易于实现的近似模型代替原先训练好的模型,来做具体实现和应用,要求近似的精度(测试准确率)尽可能地高,即压缩模型。为了达到模型精度上的逼近,许多方法中对新模型进行重新训练,而训练过程中会加入新的约束来保障后期易于实现。比如,BNN权重量化成1bit数时,需要重新训练权重。(原先训练好的模型是在32bit的浮点数上得到的高精度模型。)从更加宏观角度讲,针对深度学习算法的具体实现问题,可以建立一个模型来求解,该计算模型要运行速度快、计算量尽可能地小、数据尽可能地重用、存储需求量尽可能地小,还能适应各种不同深度学习算法等,假如设计出了该计算模型,软硬件该如何分工与协同设计,硬件功能选择专用处理器设计还是借助FPGA资源来设计或是其他硬件计算平台来设计,软件功能该如何设计实现并与配硬件功能配合起来一起工作等。这就类似计算机采用图灵计算模型、冯诺依曼体系架构实现一样,用通用的计算模型和架构解决各种复杂的计算问题。显然,要设计这样的计算模型困难重重,但值得尝试。 目前,采用这样的思想典型代表有google TPU、寒武纪的NPU、深鉴科技的ESE、MIT的Eyeriss等。仔细分析他们的相似之处,可以发现:1)充分挖掘CNN卷积的内在并行能力,借助移位寄存器和片上buffer实现缓存和数据重用; 2)全部采用低精度的数据计算; 3)借鉴了CPU的设计理念,硬件分时复用+SIMD的处理办法等。
1)数据量化。减少数据的位宽不仅有助于降低存储,还可以降低计算量,甚至简化计算。在GPU上训练时常常采用32位的浮点数; 而推断阶段受到片上存储和带宽的限制,需要减少位宽换取更大的缓存来增加并行能力。 常用的量化精度有16bits,12bits,8bits,4bits,1bit, 有研究表明16bits与32bits在效果上非常接近(小于0.5%), 8bits的效果也在5%以内,因此,8bits非常受欢迎,许多厂商号称支持8bits的深度学习应用。具体量化的方法,有简单缩小位宽的,也有对数表示数据的,还有动态小数点将整数与小数分开的。不论那种都需要重新训练才能确定权重数据。当然,32bits全精度的效果是参考标准。
1bit的研究也是一个热点,尽管看起来1bit的表达能力远远弱于32bits,但针对特定算法的效果却没那么糟糕。从BNN到BCNN、再到XNOR、到现在各种变体,不断尝试各种方法拓展其通用性和提高预测精度。BNN类的算法非常适合在FPGA上实现,这是因为FPGA拥有大量的lut,可以精确地只针对位进行编程,而两个1bit的数据乘法就是XNOR,权重可以实现存储在lut中,然后给定输入就可以立即得到结果,并不用大量的复杂操作,效率非常高。
2)简化网络结构。CNN相对全连接的神经网络是稀疏的,但相对计算资源来讲还是复杂的。目前压缩模型的做法主要包括:剪枝(pruning)、权重共享(shared weights)。剪枝就是将模型中某些连接去掉,减少计算量和存储量,去掉的标准有权重接近0的、相对其他权重较小的等。权重共享就是将卷积核中所有元素(权重)划成几个类,同一个类中的元素都用“质心”来代替,这样只需存储几个“质心”的值和每个元素归属哪个质心的索引即可,由于类别数都很少,索引所需的位宽用少量的几位即可表示,从而起到压缩模型的效果。也有人提出使用hash表来查找权重,也是一种尝试而已。
3)数据重用。CNN图像卷积过程中会有大量数据重复使用,考虑到片内存储空间有限,不可能将其全部读入再去处理; 而按照输出特征图形像素点挨个计算的需求来读取数据,则数据从外存到内部缓存的带宽瓶颈将会降低执行速度,而且每个数据重复读取多次也是不可取的做法。显然,数据重用必须在片上存储空间、带宽、计算资源三者之间寻找一个平衡点。MIT的Vivienne Sze等人仔细研究了可并行的三个维度:输入图像局部数据可在多个卷积核间重用; 卷积核可在输入图像不同区域重用; 卷积核在不同输入特征间的重用(batch size>1),并在基础上设计了一个CNN加速器(Eyeriss)。整张图像(可以是三维的)的卷积可以化为矩阵的乘法运算,因此,理论上讲只要矩阵乘法优化做得足够好,也可以达到加速的效果。为了达到数据重用的目的,研究人员设计了诸多结构,比如采用移位寄存器链+MAC(乘累加)实现卷积,也有采用PE单元+ 加法tree并行实现卷积等。数据重用的方式也会影响到读取外存数据和写出输出特征的方式。从宏观上讲,计算模型决定了数据重用的方式、硬件架构设计、访存方式、软件编译的设计等。
4)计算结构。 现有几乎每篇论文都会涉及计算架构,常用PE(process element)表示一个计算单元,具体功能不尽相同,但至少包括了卷积操作。除了PE之外,可能还会有局部buffer,有的还会有一级缓存负责从外部读取数据再分发给二级缓存进行局部计算。当然,有些设计PE中包含了私有的buffer。 考虑到带宽的影响,常常从外存读取一块数据进来,这时也会用到FIFO作为一级缓存。PE单元内部的设计千差万别,有的借助移位寄存器链实现pipeline计算,也有先设计1-D的卷积单元、再组成2-D、3-D的卷积,还有搭建类似有ALU的结构。有些结构中甚至包括更大粒度的CU(Compute Unit)或PU(Process Unit)来执行更大粒度的并行。不论如何,整个设计中会包括若干PE、CU/PU以便并行加速计算。
5)存储结构。移位寄存器实现数据搬运是最简单有效的方式,可以实现pipeline,也无需寻址,控制器起来相对简单。但是,在开始或数据重新加载时会出现若干周期无效数据,需要单独剔除。而且,其比较适合一维顺序情况。对于二维或三维的就需要同步,有些麻烦。FIFO也是一种常用的结构,可以设计成多维的,一个时钟周期可以同时让若干数据同时入队或出队。因此,FIFO可以满足从片外缓存大块数据的需要(缓存若干行输入图像数据,缓存若干行输出特征图像数据)。计算结构与存储结构密切相关,正如人的左腿与右腿一般,缺一不可站立。
6)编译工具。计算模型解决了如何做的问题,硬件架构(计算结构+存储结构)决定了硬件功能,编译工具需要完成深度学习算法向硬件架构的映射(map)的问题,让其正确可靠地执行起来。编译工具没有一个统一标准,只能随着硬件架构来设计。当前,caffe训练好的模型结构存储在prototxt文件中,权重数据存储在数据库中。鉴于prototxt格式独立与具体编程语言,且有很好地解析工具,诸多学者、厂商最先使用它作为深度学习模型的输入格式,再向硬件结果映射。这里存在软硬件划分的问题,如果硬件做的足够多,软件编译器就会相对简单点,但也会缺乏一些灵活性; 反之,软件工作量会变大。这里需要说明的一点是,这里的编译跟c/c++/python编译到CPU不是一回事,跟FPGA开发中的综合布局布线也不是一回事——因为目标硬件结构不同,映射过程差异也非常大。尽管许多人在FPGA上采用C/C++/Verilog/VHDL设计了深度学习加速器,并借助vivado/quartus编译生成了位流码,但其本质只是将硬件架构在FPGA上实现了,即配置编程后的FPGA变成一个深度学习加速平台,该加速器运行哪个深度学习算法、怎么运行,需要从prototxt编译而来的“指令”才能知道。(这里讲的是通用加速平台而非真的某个具体算法编写的verilog电路)。
总之,当前主流的深度学习算法在硬件上实现的主要集中在压缩模型和设计计算模型两个方面,前者着力寻找一个简单有效易于实现的模型,后者重点在寻找一个基于现有硬件资源可以高效计算的方法,这二者的结合可以更加高效有效地解决推断阶段带来的计算复杂和存储爆炸等问题。
如果这篇文章帮助到了你,你可以请作者喝一杯咖啡