一种残膜回收机防缠绕挑膜装置的制 一种秧草收获机用电力驱动行走机构

专用于CNN加速器的深度学习编译器优化方法

2022-09-03 18:34:56 来源:中国专利 TAG:

专用于cnn加速器的深度学习编译器优化方法
技术领域
1.本发明涉及编译器优化领域,特别是涉及一种专用于cnn加速器的深度学习编译器优化方法。


背景技术:

2.随着深度学习算法的发展,卷积神经网络模型逐渐从云端向边缘端迁移。同时模型的规模不断变大,导致了其参数量和计算量也在剧增。像cpu和gpu这种通用处理平台因为存在性能不足和功耗高等缺点,因此相关研究开始通过fpga或者asic来定制高性能、高能效的cnn专用加速器,然而专用加速器因为灵活性不足,因此还需要设计深度学习编译器这样的软件工具来配合使用。
3.面对错综复杂的网络结构,深度学习编译器需要将其简化,最后变成各种concat和split分支结构。对于专用加速器来说,此类操作需要将以前的计算结果重新读进缓存,经过相关操作后再写回缓存,频繁的内存访问会造成内存的高时延、高能耗,同时还会增长片上资源的开销。
4.此外,fpga等加速器中因为处理浮点计算比较复杂,编译器还需要对模型进行定点整数的量化,量化分为对称量化和非对称量化。对称量化实现简单,但面对数据正负分布不均匀的情况下会导致精度损失过高,而精度损失更低的非对称却会增加加速器的计算复杂度。同时fpga中采用dsp来执行乘累加运算,但dsp模块是定制化的,无法体现低位宽量化数据的计算性能优势。因此编译器对模型的量化方式选择会受到硬件的限制。


技术实现要素:

5.本发明旨在至少解决现有技术中存在的技术问题,特别创新地提出了一种专用于cnn加速器的深度学习编译器优化方法。
6.为了实现本发明的上述目的,本发明提供了一种专用于cnn加速器的深度学习编译器优化方法,包括以下步骤:
7.s1,通过算子融合和/或bn融合的优化方法减少加速器对内存的访问以及存储空间的浪费,同时使用内存分配地址叠加的方式避免各张量之间的数据覆盖;
8.s2,采用固定硬件下的多种模型量化部署方案,通过融合非对称量化的偏移和卷积偏置使硬件不需要额外的计算模块也能支持非对称量化方式,以此实现不改变硬件也能提升运算精度;同时通过编译器与加速器的协同优化,将dsp的输入数据配置为两个数的移位相加从而在一个dsp中同时进行两次乘法运算,最终实现编译器对可变位宽量化的支持。
9.进一步地,所述s2包括以下步骤:
10.s2-1,运行校准集,获取特征图的动态分布范围;
11.s2-2,检测量化位宽选项,判断是否为int8或int16,如果是int8则采用输入移位相加或权重移位相加;这样int8的计算速度是int16的两倍。
12.s2-3,遍历计算图并判断特征图之间是否有跳跃连接的关系,如果有,则进行缩放
因子和偏移的统一;
13.s2-4,检测量化编译形式选项,根据上一步int8或int16的选择计算出缩放因子scale和零点偏移,如果为非对称量化,将偏置和偏移进行融合。
14.进一步地,所述偏置和偏移进行融合包括:
15.对于非对称量化,卷积计算过程的量化公式为:
[0016][0017]
其中q1、q2、q3、分别表示输入定点数、权重定点数、输出定点数;
[0018]
s1、s2、s3分别为输入的缩放因子、权重的缩放因子、输出的缩放因子;
[0019]
z1、z2、z3分别为输入的偏移、权重的偏移和输出的偏移;
[0020]
2-n
表示右移n位操作;
[0021]
m0为整数;
[0022]
∑表示卷积求和;
[0023]b′
表示新的偏置;
[0024]
round(
·
)表示四舍五入取整;
[0025]
bias表示偏置。
[0026]
进一步地,所述输入移位相加包括:
[0027]
相邻式输入移位相加:与下一个相邻输出点对应同一位置的输入值进行数据移位相加后再送进处理引擎pe,计算的结果经过拆分将会对应输出特征图上两个相邻的输出点;下一组输出的步长为2,相对应的输入特征图的步长也为2;
[0028]
或对半式输入移位相加:输出特征图的上下均分,会跨越式的读取下半张输出特征图对应的输入数据,而上下输入点之间的距离是固定的,此时输入的偏移步长为1,循环的行数oh减半;
[0029]
所述权重移位相加包括:相邻两个卷积核上同一位置的权重进行移位相加后送进pe的权重通道,做完计算后的结果拆分对应于输出特征图同一位置上的两个相邻通道值,单个卷积核的计算流程不变,此时卷积核的数量oc减半。
[0030]
进一步地,所述权重移位相加还包括:对于有符号数的移位相加时,将pe的输出结果按位数均匀拆分为高位数据和低位数据,高位数据需加上低位数据的符号位。
[0031]
进一步地,还包括:
[0032]
在脉动阵列中每个pe单元会累加上一个pe的输出结果,然后送入下一个pe单元继续进行累加,增加一个溢出位用于兼容数据累加的进位。用于保证与int16量化计算过程的一致性以及int8量化模式下低位结果在累加过程中产生的进位不会覆盖高位数据的内容同时高位结果也不会溢出。
[0033]
进一步地,还包括:采用优化卷积循环平铺和循环重排的调度策略来提升加速器的性能,首先外循环顺序采用枚举的方式,不同的外循环之间通过多线程的形式进行循环平铺尺寸空间探索,循环平铺的尺寸同样用枚举的形式从而得到每个缓存对应的分块数据大小,然后根据fpga硬件的信息去计算该种方式消耗的时钟周期,最终通过比较采用消耗
时钟周期最小的循环调度方案。
[0034]
进一步地,所述循环平铺的具体步骤如下:
[0035]
s100,外循环:
[0036]
s100-1,若h小于oh,则继续第一层外循环,且每循环一次h加1;
[0037]
s100-2,若c小于ow,则继续第二层外循环,且每循环一次w=w ow;
[0038]
s100-3,若oc小于oc,则继续第三层外循环,且每循环一次oc=oc to;
[0039]
s100-4,若ic小于ic,则继续第四层外循环,且每循环一次ic=ic ti;
[0040]
s200,加载数据进缓存;
[0041]
s300,内循环:
[0042]
s300-1,若_h小于min(h oh,oh),则继续第一层内循环,且每循环一次_h加1;
[0043]
s300-2,若_w小于min(w ow,ow),则继续第二层内循环,且每循环一次_w加1;
[0044]
s300-3,若occ小于min(oc to,oc),则继续第三层内循环,且每循环一次occ加1;
[0045]
s300-4,若icc小于min(ic ti,ic),则继续第四层内循环,且每循环一次icc加1;
[0046]
s300-5,若kw小于kw,则继续第五层内循环,且每循环一次kw加1;
[0047]
s300-6,若kh小于kh,则继续第六层内循环,且每循环一次kh加1;
[0048]
s300-7,output=input*weight output;
[0049]
其中ow、oh分别表示输出特征图的宽、高;
[0050]
ic表示输入通道数;
[0051]
oc表示卷积核的数量;
[0052]
kw、kh分别表示卷积核的宽、高;
[0053]
h、c、w、oc、ic均为外循环的遍历符号;
[0054]
ow、to、ti均为循环平铺的步长,取值均大于1;
[0055]
_h、_w、occ、icc、kw、kh均为内循环的遍历符号;
[0056]
output表示输出;
[0057]
input表示输入;
[0058]
weight表示权重。
[0059]
进一步地,所述计算该种方式消耗的时钟周期包括:
[0060]
s10,根据内循环顺序、循环平铺的尺寸计算出运算单元处理一个分块的时间;然后用同样的方法遍历所有的分块得到运算单元总计算时间;
[0061]
s20,根据外循环顺序、循环平铺的尺寸以及表3.1所述的数据块更换情况,求出每一次外循环的内存方法时间;表3.1的数据若为未命中就更换。
[0062]
s30,如果第二步的时间大于第一步的时间,则内存阻塞时间=外循环的内存方法时间-固定的脉动阵列计算的时间;如果第二步的时间小于第一步的时间,内存阻塞时间为0;总内存阻塞时间即遍历所有的外循环;
[0063]
s40,加速器总的运行时间=运算单元总计算时间 总内存阻塞时间。
[0064]
进一步地,所述计算出运算单元处理一个分块的时间的计算公式如下:
[0065]an
(

(a2(a1(a0 b) b) b)

)
[0066]
其中an中的n表示的内循环顺序对应的循环数为n;
[0067]
b表示切换循环的额外开销;
[0068]
循环平铺的尺寸就是内循环的具体数值即a0,a1,...,an;
[0069]
所述每一次外循环的内存方法时间=数据的交换量/内存的带宽。
[0070]
进一步地,还包括:采用内存共享和编址的方法,并通过特征图深度复制、算子执行顺序交换策略使编译器可以支持复杂的跳跃连接网络结构;具体步骤包括:
[0071]
s-1,对于不涉及数据计算操作的算子,且满足输入特征图和输出特征图在数据内容上有重叠的部分,则这部分重叠的数据由涉及到的相关特征图共享;
[0072]
s-2,对于内存空间不连续,则将参与数据拼接或拆分的特征图划分在一个共享区域,如果没有参与就单独为一个共享区域,共享区域的内存空间大小根据通道数最大特征图的尺寸计算;然后由编译器根据连续内存或者非连续内存进行相关地址偏移计算;
[0073]
s-3,若一个特征图a与其它多个没有关联的特征图都做拼接,则增加一个深度复制的算子copy,并将所述的一个特征图深度复制成多个相同的特征图即第一特征图a

、第二特征图a

......对于算子copy,将a对应的卷积输出经过alu模块后分别存入到第一特征图a

、第二特征图a

以及其它相同特征图当中,a在alu模块计算完成后将结果写回两个地址;
[0074]
s-4,如果计算图中拼接类算子的输出又作为输入流向线性计算算子,则将线性计算算子向前移动与拼接类算子交换顺序。
[0075]
进一步地,所述地址偏移包括:
[0076]
对于连续内存中进行地址偏移包括:
[0077]
编译器每个循环的地址偏移计算公式为:
[0078]
stridei=∏stride
list
[i 1:]*per
bytes
[0079]
其中i表示的是第几个维度的循环;
[0080]
[i 1:]表示偏移的数值从i 1开始;
[0081]
per
bytes
表示每个数据在内存中占多少字节;
[0082]
stridei表示第i个维度循环所对应在内存中的地址偏移;
[0083]
stride
list
表示循环偏移列表;
[0084]
对于非连续内存中进行地址偏移包括:
[0085]
计算每个循环地址的偏移:
[0086]
stridei=∏stride
list
[i 1:]*per
bytes
[0087]
stride
list
=(1,h,w,cc c)
[0088]
其中h表示特征图的高值;
[0089]
w表示特征图的宽值;
[0090]
c表示特征图的通道值;
[0091]
cc c表示特征图所属共享区域的总通道值。
[0092]
综上所述,由于采用了上述技术方案,本发明具有以下优点:
[0093]
(1)通过算子融合和/或bn融合的优化方法减少加速器对内存的访问以及存储空间的浪费,同时使用内存分配地址叠加的方式避免各张量之间的数据覆盖。
[0094]
(2)在固定的加速器结构上设计了不同类型、可变位宽的模型量化方法,实现编译器对可变位宽量化的支持,以及实现了不改变硬件也能提升运算精度。
[0095]
本发明的附加方面和优点将在下面的描述中部分给出,部分将从下面的描述中变
得明显,或通过本发明的实践了解到。
附图说明
[0096]
本发明的上述和/或附加的方面和优点从结合下面附图对实施例的描述中将变得明显和容易理解,其中:
[0097]
图1是本发明专用深度学习编译器架构示意图。
[0098]
图2是本发明算子节点node与张量tensor的拓扑关系示意图。
[0099]
图3是本发明全连接层转换为1x1卷积示意图。
[0100]
图4是本发明算子融合前后的内存访问示意图。
[0101]
图5是本发明分支卷积合并示意图。
[0102]
图6是本发明内存分配流程图。
[0103]
图7是本发明卷积填充后的首地址读写示意图。
[0104]
图8是本发明输出特征图划分tile示意图。
[0105]
图9是本发明指令生成流程图。
[0106]
图10是本发明densenet跳跃连接示意图。
[0107]
图11是本发明对称量化和非对称量化计算模块示意图。
[0108]
图12是本发明加速器中数据存储层次示意图。
[0109]
图13是本发明nhwc数据布局通道拼接的内存分布示意图。
[0110]
图14是本发明共享区域划分示意图。
[0111]
图15是本发明共享区域重叠部分深度复制示意图。
[0112]
图16是本发明yolov4 tiny残差块结构转换示意图示意图。
[0113]
图17是本发明根据输出位置pe输入移位相加示意图。
[0114]
图18是本发明pe权重移位相加示意图。
[0115]
图19是本发明有符号数移位相加后乘法结果拆分示例图。
[0116]
图20是本发明pe输出结构示意图。
[0117]
图21是本发明int8量化输入数据不同位置的格式示意图。
[0118]
图22是本发明模型量化流程图示意图。
[0119]
图23是本发明xczu19eg开发板示意图。
[0120]
图24是本发明cnn加速器在19eg开发板上资源占比情况。
[0121]
图25是本发明yolov4 tiny网络结构示意图。
[0122]
图26是本发明3*3卷积核并行计算示意图。
[0123]
图27是本发明脉动阵列流水形式示意图。
[0124]
图28是本发明数据更换次数循环累乘示意图。
[0125]
图29是本发明内存阻塞示意图。
[0126]
图30是本发明循环平铺和循环重排优化调度示意图。
[0127]
图31是本发明调度优化前后性能对比示意图。
[0128]
图32是本发明编译器目录结构示意图。
[0129]
图33是本发明conv0 bn leakyrelu算子融合示意图。
[0130]
图34是本发明conv0算子融合后拓扑结构示意图。
[0131]
图35是本发明concat算子结构处理示意图。
[0132]
图36是本发明循环调度日志示意图。
[0133]
图37是本发明编译器生成的部分指令图。
[0134]
图38是本发明仿真获取输出日志示意图。
[0135]
图39是本发明软硬件结果对比示意图。
具体实施方式
[0136]
下面详细描述本发明的实施例,所述实施例的示例在附图中示出,其中自始至终相同或类似的标号表示相同或类似的元件或具有相同或类似功能的元件。下面通过参考附图描述的实施例是示例性的,仅用于解释本发明,而不能理解为对本发明的限制。
[0137]
1研究背景与现状
[0138]
1.1研究背景与意义
[0139]
深度学习是机器学习算法中一个非常重要的分支,近几年来发展迅速,在计算机视觉、语音识别、自然语言处理和自动驾驶等领域都取到了显著的成果,同时深度学习也逐渐将人工智能带入到人们的日常生活当中。卷积神经网络(convolutional neural networks,cnn)是深度学习中应用最广的技术,它主要是从数据集中学习样本的内在规律和特征信息,从而达到识别图像、文字等数据的目的。cnn模型训练是一个逐渐学习并调参的过程,可以调整的参数越多,那么网络能调整的自由度就越大,最终的逼近效果也会越好。因此随着cnn实际应用对模型高准确率的需求,cnn模型的大小以及计算复杂度也在逐渐增加,而与之同时给cnn模型在应用部署上也带来了巨大的挑战。
[0140]
cnn的高密集计算需要硬件平台来提供支持,传统中央处理单元(central processing unit,cpu)的架构并不适合处理大规模的矩阵运算,而高级图形处理单元(graphics processing unit,gpu)具有很高的并行性,被广泛应用于cnn模型的训练和推理。但是gpu存在功耗高和资源利用率低的问题,其高性能也依赖于大批量输入数据,在cnn模型的实际应用场景中,大多数情况下都是对单帧进行处理的,同时部署设备上的计算资源也比较有限,因此gpu并不适合作为cnn模型应用部署中的硬件加速平台。
[0141]
为了解决cnn模型在嵌入式等设备上计算能力不足、功耗高和存储空间有限等问题,一种思路是从模型本身入手,对于资源受限的计算平台,设计一些轻量级的模型,例如squeezenet、mobilenet、xception等等,然后再使用量化、剪枝等模型压缩方法可以进一步减小模型尺寸使其更容易地部署到嵌入式设备上。另外一种思路是采用现场可编程门阵列(field programmable gate array,fpga)和专用集成电路(application specific integrated circuit,asic)来定制硬件平台作为cnn模型推理专用加速器。尽管基于asic的相关加速器已经可以达到了非常可观的性能,但是与cnn算法的快速发展相比,aisc的设计缺乏灵活性并且开发周期长,而fpga的可配置特点可以通过重构使加速器具有很高的灵活性,因此基于fpga的专用加速器受到了产业界和学术界的广泛关注。
[0142]
硬件加速器的计算执行需要相应的编译软件系统才能支持多变的网络结构,同时又因为cnn专用加速器硬件结构的多样性,目前相对通用的深度学习编译器无法为所有硬件平台提供编译支持,因此自研ai编译器对于cnn专用加速器的使用和推广很重要,缺乏相对应的编译软件工具链,硬件就无法发挥出最大功效。然而目前国内对面向cnn专用加速器
的深度学习编译器(简称“专用深度学习编译器”)的相关研究还比较少,cnn模型在部署上还面临着很多挑战。
[0143]
首先,随着cnn算法的快速发展,cnn模型的网络结构也越加复杂,例如densenet、inception等比较流行的网络模型中有着大量的跳跃连接结构,这种结构需要将不同层次的特征图进行组合拼接,然而cnn专用加速器难以灵活地支持这些复杂的网络模型。其次,由于cnn网络模型的规模和深度的逐渐增加,cnn模型的权重参数量也更加庞大,很多研究通过量化来减少模型规模,同时定点运算相比浮点运算还可以减少资源开销。然而不同的模型适用于不同的量化方式,专用加速器难以在硬件硬件结构不变的情况下同时支持不同的量化方式,比如非对称量化中有偏移计算,因此加速器需要额外的偏移计算模块。同时不同的模型会适用于不同的量化位宽,编译器需要充分利用fpga中的数字信号处理器(digital signal processor,dsp)等计算资源来支持不同的量化方案。最后,卷积计算是一个大规模的多层循环操作,由于片上缓存的资源有限,无法存储下全部数据,输入特征图和权重需要进行分块后送进缓存才能在加速器中完成对整个cnn网络的计算,然而卷积计算各层循环中有着对数据的复用,不同的分块尺寸和不同的循环执行顺序都会影响加速器与外存储器的数据交互情况从而产生不同的执行性能,因此如何结合硬件架构进行循环平铺和卷积循环重排是编译器所需要解决的重要问题。
[0144]
综上所述,为了实现cnn模型在嵌入式等设备上的高效部署,本文对面向cnn专用加速器的深度学习编译器提出了优化设计,并在开源cnn专用加速器——dnnweaver上进行了实现与验证,最终使得cnn专用加速器能更灵活、更高效地处理cnn模型。目前国内对专用深度学习编译器这方面的相关研究并不多,因此本文对于推动cnn模型的应用部署具有一定的研究意义。
[0145]
1.2研究发展与现状
[0146]
1.2.1深度学习加速器
[0147]
深度学习编译器的发展与深度学习加速器的发展息息相关,目前深度学习加速器的发展主要朝两个方向。其中一个是沿用传统的计算架构来提高硬件的加速性能,如gpu、aisc、fpga等。2009年clement farabet等人尝试在fpga上通过使用数字信号处理器实现了卷积计算功能,并成功应用了一个简单的人脸检测模型;2014年,国内团队寒武纪提出的diannao神经网络处理器使用多种深度学习加速算法,并通过优化数值和存储模块等方法达到了比当时gpu快好几十倍的效果,同时也开启了深度学习神经网络专用处理器的先河;2016年google发布了一款通过脉动阵列计算核心来加速矩阵乘法和卷积的人工智能芯片tpu,其主要为tensorflow模型服务,第一代还只能用于推理,在第二代中增加了神经网络模型训练的功能;同年国内深鉴科技也提出了基于xilinx fpga芯片的深度学习开发框架dnndk,极大程度地缩短了深度模型到硬件的部署周期;同时yu-hsin chen等人针对缓存与内存之间大量数据搬移问题设计了一种具有可重配置功能的深度学习加速器eyeriss,主要通过行固定(row stationary,rs)等方法来降低数据搬运带来的延迟和能耗开销,之后又提出了eyeriss v2来支持稀疏矩阵的运算;2018年,清华大学thinker团队推出了可重构加速器,这是一种脉动阵列形式的cgra(coarse grained reconfigurable array)结构,通过对加速器的核心计算单元进行指令级别的动态配置,可以实现在不改变硬件的情况下支持绝大部分深度学习模型的运算。
[0148]
加速器的另外一个发展方向是颠覆传统的冯
·
诺依曼架构,采用神经形态架构来设计类脑神经结构以提升计算能力,以ibm的truenorth芯片为代表,但目前还仅能识别比较简单的数据集。
[0149]
1.2.2深度学习编译器
[0150]
早期深度学习模型的部署还只能借助第三方库(例如nvidia的矩阵运算库cublas和英特尔的计算库mkl)来达到优化计算的效果,然而随着越来越多的新算子被提出,算子库的开发和维护工作量也越来越大,由此便产生了深度学习编译器。目前深度学习编译器的发展并不成熟,而现阶段比较知名的有英特尔的ngraph,tvm,facebook的glow、tensor comprehension和google的xla。
[0151]
xla是谷歌推出的高性能机器学习领域编译器,用来编译tensorflow设计的模型,从而解决tensorflow在设计时因只考虑灵活性和可扩展性而带来的计算性能欠缺问题。xla将hlo ir表示的计算图进行与部署平台无关的优化,在后端则基于硬件架构进行再次优化,然后将优化后的hlo ir转变为更低级的llvm ir并生成机器码。同时谷歌还推出了一种中间表达形式mlir,用于提高各种ir之间的转换效率和可迁移性。ngraph可简化跨框架和硬件平台的部署过程,将模型定义的框架与计算剥离开,转化为与框架无关的中间表示ngraph ir,然后进一步把它们转换成可以在硬件后端执行的形式。glow和xla比较类似,都是将计算图中的每个node抽象成一系列简单的线性代数原语来进行实现加速。tvm是一个端到端堆栈,能把深度学习工作负载部署到硬件,它继承了halide计算和调度分离的架构,这一点和tensor comprehension类似,区别在于tvm的调度通过指令用户手工指定,而tensor comprehension在领域专用语言(domain specified language,dsl)中描述了计算之后,就完全交给了后续的编译程序来实现调度的自动转换。
[0152]
因为计算资源有限,深度学习编译器还需要对模型进行量化处理。tensor comprehension并不支持对模型的量化工作,而ngraph、glow、xla和tvm均可以实现对模型的int8量化,但是并不能支持多种量化方式(tvm在通用硬件平台上能支持非定点的fp16量化)。针对专用cnn加速器的模型量化部署,张芳芳在所设计的专用深度学习编译器中因为受到硬件结构的限制采用了固定的int16对称量化,其只为量化模型在硬件部署上的可行性,并没有考虑模型量化后的准确率或者压缩率是否适用。王卓在对人工智能芯片上模型压缩的研究中采用int8非对称量化,其模型的激活函数为relu6,因此将输入数据用uint8表示从而避免了对非对称量化零点偏移的处理,但是这种方法需要限定激活函数来控制输入数据的分布。
[0153]
chen zhang等人在2015年的论文中提出了一种使用roo-fline模型的分析设计方法,对cnn设计的解决方案都可以使用各种优化技术来定量分析其需要的内存带宽和计算的吞吐量,然后在roo-fline模型的帮助下可以确定最低fpga资源需求和最佳性能的解决方案,并在搜索空间内为每一层找到了最优的分块大小,但是没有对卷积循环的执行顺序进行探索。池昊宇以原始特征数据为输入,执行时间为输出,建立了一个尺度特定的矩阵乘法式执行时间预测模型,在此基础上构建了最优分块大小的预测模型,但该预测模型只是针对人工神经网络,无法适用于多层嵌套循环的卷积神经网络。
[0154]
2专用深度学习编译器的设计架构与优化问题分析
[0155]
由于深度学习应用逐渐从云端开始向边缘端进行发展,如何将深度学习模型高效
地部署在集成了专用cnn加速器的嵌入式设备上成为了一个研究热点。目前在资源受限的加速器平台上部署cnn模型仍面临很多挑战,同时国内对面向cnn专用加速器的深度学习编译器的研究比较少,本文以优化cnn模型在专用cnn加速器上的部署为出发点,对面向cnn专用加速器的深度学习编译器提出了优化设计。
[0156]
2.1专用深度学习编译器的基本设计架构
[0157]
深度学习编译器的工作是将深度学习模型通过各种优化技术生成硬件平台执行所需要的指令或者代码,本文基于fpga加速器的专用深度学习编译器架构如图1所示。由图1可知cnn模型从顶端到加速器硬件的部署是一个多级编译流程,主要分为三个阶段:前端、中间优化器和后端。模型在每个层次都有不同的存在形式,在前端主要以模型框架形式为主,比如主流的tensorflow、pytorch和caffe等等;中间层主要以由前端解析生成的自定义计算图形式存在;而后端则是优化后的计算图,其主要做与硬件结构相关的优化以及生成加速器运行所需的指令。这种三段式结构是目前最普遍的深度学习编译器设计方式,下面对这三个层次的主要设计进行阐述。
[0158]
2.1.1编译器前端
[0159]
深度学习编译器前端主要扮演一个解析器(parser)的角色,其主要工作是对模型进行加载、解析以及计算图的转化。对比于传统编译器,解析器相当于其中的语法分析器,用于将程序的文本形式输入转化为编译器内置设定的“抽象语法树”数据格式,再进一步进行优化处理,而深度学习编译器属于专用领域范畴,进行解析的输入对象是事先定义好的模型文件。由于顶层模型框架的多样性,目前的编译器设计大多都会将模型文件事先转换为统一的深度学习模型标准表示格式——onnx,然后再送入解析器进行解析,最后生成自定义的中间表达形式ir。目前主流模型框架都对onnx有着不同程度的支持,这也便于算法模型在不同框架之间的迁移以及对模型解析器接口的设计。
[0160]
在本文编译器的设计中,中间表示ir中有三种数据结构——graph、node和tensor。graph是抽象化的计算图,由节点列表(node list)、张量列表(tensor list)和其他属性字段组成。节点列表中的每一个节点都表示一种算子操作,而张量列表中的每一个张量则是特征数据和权重数据的存储和传输介质,节点与张量之间的拓扑关系组成了完整模型的网络结构,拓扑关系如图2所示。下面对编译器中node和tensor的属性进行详细介绍。
[0161]

node
[0162]
在计算图中,node数据结构可以表示计算图中的全部节点,而node相较tensor来说种类较多,比如卷积、池化等等,因此node是抽象为一个基类,共有属性有名称、输入tensor列表、输出tensor列表和其它一些抽象的方法函数。具体的算子节点都由node基类进行派生,各节点之间有着顺序关系和拓扑关系。顺序关系表示节点之间的执行顺序,而先后执行顺序由在graph的节点列表中存储顺序决定;拓扑关系表示该节点用到了在其它节点中定义的值。
[0163]
由于在卷积神经网络计算图中,绝大部分节点表示的都是卷积算子,下面主要介绍卷积算子中的一些属性:
[0164]
1)data,卷积计算的特征图输入数据,类型为tensor。
[0165]
2)weights,卷积计算的权重数据,类型为tensor,在卷积算子内部设置了异常判
断机制,如果data的通道数和weights单个卷积核的通道数不等则抛出异常。
[0166]
3)bias,卷积计算的偏置数据,类型为tensor,同样的也设置了异常判断机制来检测卷积核的数量与bias的长度是否相等。
[0167]
4)stride,卷积的步长,四个数据对应输入tensor在数据布局nhwc四个方向上的步长,一般情况下stride为,如果卷积步长为2则stride为。
[0168]
5)pad,卷积计算需要的对输入特征图的填充,根据weights的形状和填充类型来决定数值,如果是3*3卷积和“same”填充类型,pad则为[[0,0],[1,1],[1,1],[0,0]]。
[0169]
在专用卷积神经网络加速器中一般没有专门对全连接层进行处理的计算单元,编译器在进行解析模型的时候会将其转为相对应的卷积操作。全连接层的计算操作实际上就是输入特征图大小与单个卷积核大小一致的卷积计算,每个卷积核所计算出来的结果就对应着全连接层输出的一个神经元。因此通常采用“1x1”的卷积运算来取代全连接层,如图3所示的输入神经元长度相当于卷积输入的通道数,而输出神经元的长度则表现为卷积核的数量。
[0170]

tenor
[0171]
在计算图中张量不仅作为数据存储的媒介,同时还负责将各个算子连接起来组成完整的一个计算图。张量中存储的是一种多维数据,在编译器中被抽象为tensor类。tensor中除了比较直观的数据属性外,还扩展了几个必要的属性,包括:
[0172]
1)dtype,表示量化后的数据类型,主要有int8、int16、int32和int64这四个枚举类型,其中int32和int64多用来表示中间计算结果,起过渡作用并不会实际储存在内存当中。
[0173]
2)shape,内存中的数据布局。
[0174]
3)pad,表示数据需要做填充的维度,一般是由于卷积计算需要造成的。
[0175]
4)address,张量在硬件内存中存储的首地址,由于pad的存在首地址并不是指向实际有效数据,对于加速器来说张量的形状是shape pad。
[0176]
5)add_vilid,判断是否需要给张量分配内存空间。
[0177]
6)scale和zero_point,根据张量数据量化产生的缩放因子和偏移。
[0178]
7)output_nodes,该张量流向的节点列表,一个张量可能流向多个计算节点,此属性决定了整个计算图的拓扑结构。
[0179]
除此之外,tensor类还定义了一些可以快速获取到张量信息的方法,比如计算填充后的形状、整个tensor占有的字节大小(填充或非填充)等等。
[0180]
2.1.2编译器中间优化器
[0181]
编译器的中间优化是对计算图进行与硬件结构无关的优化,在不影响计算结果的情况下降低模型的空间复杂度以及计算复杂度从而减少模型在加速器上的推理时间。中间层图优化策略中根据粒度主要分为层级优化、张量级优化和元素级优化。
[0182]
层级优化是从算子层次来简化模型网络结构,常见的有conv relu这样的算子融合以及计算结果为固定值的常量折叠(用常量代替计算表达式)等。下面详细介绍本文编译器实现的bn(batch normalization)融合和算子融合方法:
[0183]

bn融合
[0184]
在深度学习中卷积算子通常后面会有一个bn操作来解决训练时的梯度消失等问
题,一般的优化策略是在推理阶段将bn计算变换成scale*data shift的形式,由于卷积计算和bn操作本身都是线性变换,在推理阶段可以将两者融合,即将bn计算的相关参数融入进卷积计算中从而生成一个新的卷积,但这对于该卷积本身的结构本没有什么影响,只是改变了其中的权重数据,最终通过删除bn计算节点达到加速的效果。融合规则如下所示:
[0185]
x1=w
conv
x0 b
conv
#(3.1)
[0186][0187][0188]
其中卷积层的输入为x0,卷积层的输出或者bn层的输入为x1,bn层的输出为x2;卷积的权重和偏置为w
conv
和b
conv
,bn层学习到的参数有均值μ、方差σ2、扩展参数γ和平移参数β,∈为固定值,为了防止除0的一个非常小的数。最终通过bn融合后形成新的卷积权重和偏置这些参数对于推理阶段是已知的,故在编译阶段就可以求出。
[0189]

算子融合
[0190]
算子融合是将多个计算操作放在一个核心处理单元里进行计算,以避免中间运算结果在内存与缓存之间的移动从而减少加速器对内存的访问,同时还可以减少运行时中间结果的内存存储开销,提高模型在加速器中的计算效率。如图4所示的卷积和激活函数的融合,卷积计算的结果可以在核心处理单元内继续执行激活函数的计算,而不用将卷积计算结果存回再读取进行计算。在计算图graph的算子顺序列表中,conv作为算子融合的主体(除了卷积,其它算子基本上都是逐元素计算),而相邻conv之间的其它算子组合成pu_ops并融合进靠前的一个conv算子以此形成一个新的融合节点marco node。在加速器中一个marco node对应着一次完整的卷积操作:首先在脉动阵列中执行卷积计算并把结果保存在输出缓存中,然后再由输出缓存将中间结果送进alu执行pu_ops里面的线性计算,最终由alu把结果写回外存储器。在算子融合后的marco node中只有alu最终写回的tensor以及卷积相关的输入(输入大部分情况为上一个marco node写回的tensor)、权重和偏置需要实际的外存储空间来保存数据,因此算子融合后会将这些tensor对应的add_vilid设置为true用于后端分配实际内存空间,剩余相关tensor的add_vilid则全部设置为false。
[0191]
张量级优化主要是改变张量在内存中的存储情况以便于加速器计算,常用的有张量数据布局转换、相同输入的分支卷积合并等优化方法,其中本文编译器数据布局转换后特征图和权重的存储格式如公式(2.5)所示。分支卷积合并如图5所示,图中3*3卷积和1*1卷积都使用同一个输入特征图,通过将1*1的卷积核补零填充成3*3并与另外一个3*3卷积核合并成一个较大的卷积核,因此在一次卷积运算里就可以得到两个输入特征图,尽管因为对卷积核进行填充增加了总计算量,但减少了输入特征图的内存重复访问开销,而具体能否提升性能需要根据特定硬件来判断,如果是相同尺寸的卷积核大多数情况下都能达到加速的效果。
[0192]
元素级优化主要是从数据本身入手,最为常用的方法就是模型量化,通过缩小数
据位宽来减少数据存储量和模型计算量。
[0193]
2.1.3编译器后端
[0194]
在深度学习编译器的后端,会接收优化后的ir进行特定硬件的平台相关优化与硬件指令生成。后端优化的一种方法是将计算图表示ir转换为llvm ir,以利用llvm的基础架构生成优化的cpu/gpu代码,而另一种方法是使用深度学习领域知识来设计自定义优化,从而更有效地利用目标硬件。
[0195]
由于本文基于的cnn处理器属于专用加速器(fpga),编译器后端的主要工作是将优化后计算图中的相关计算映射在加速器的各个模块上,最终输出加速器执行所需要的指令序列。下面详细介绍本文设计编译器中基于加速器硬件结构在后端所做的工作:
[0196]

内存分配管理
[0197]
编译器需要根据计算图的结构给张量分配内存空间以便于加速器对计算数据的读写,同时还要进行内存管理防止各张量之间地址冲突从而产生的数据覆盖。同时并不是所有的张量都要分配内存空间,比如算子融合后暂存在缓存中的中间结果并不会写回内存,给其分配后会造成内存资源浪费。内存分配的流程如图6所示,在对计算图进行中间层优化后,首先初始化实际外存储器预留给加速器空间大小的首地址p,然后遍历计算图graph中的tensor列表,只对add_vilid属性为true的张量分配实际内存大小,每次分配时只需要把当前内存空间的地址指针p指向该张量的首地址,然后根据张量大小对地址指针p叠加并更新,而分配的内存空间大小由张量自身的尺寸来维护,最后重复此操作以完成对整个计算图中张量的内存分配工作。
[0198]
由于卷积计算存在对输入进行填充的情况,因此给张量分配的内存空间并不是根据实际shape属性,而是由shape pad来计算size(t),填充空间的数据会根据量化是否对称来决定填入0还是非对称量化的零点偏移。因此加速器在运行前会对每个tensor的内存空间进行初始化,初始化的值就是填充的值,如图7所示,当计算结果数据写回的时候实际写入的首地址应该是在分配内存时的首地址加上pad的偏移,地址偏移(数据布局为nhwc)的计算公式为:
[0199]
(c pad[-1][0] pad[-1][1])*(w 2
×
pad[-2][0] pad[2][1])#(3.4)
[0200]
其中pad[-1][0]和pad[-1][1]表示c方向前后填充数,pad[-2][0]和pad[2][1]表示w方向左右填充数,pad内的数字为数组的下标。当计算结果全部写回偏移地址后相当于整个张量就已经准备好了,然后当读取的时候就根据张量分配内存时的首地址就可以自动读取到填充后的张量数据。
[0201]

计算逻辑管理
[0202]
计算逻辑管理就是基于加速器的指令集架构将计算操作生成相关指令序列来完成卷积以及其它线性计算。卷积计算是一个6层嵌套循环(如果加上批次为7层,但在cnn模型的实际应用中,批次一般都1),如代码3.1所示,这6层循环分别是输出特征图的宽ow和高oh、输入通道数ic、卷积核的数量oc和卷积核的宽kw和高kh。因为加速器的片上缓存有限,需要对卷积进行循环平铺。通常卷积核的宽和高都比较小(基本上在3到11之间,也有为1的),没有做循环平铺的必要,真正需要做平铺的是ow、oh、ic和oc这四个循环。循环平铺的操作是将每次循环的步长都设为大于1,各个循环的步长又组成新的嵌套内循环来完成真正的计算操作,而外循环主要工作就是完成循环平铺后每个分块寻址的功能,然后将一个
块大小的数据读取进缓存,具体划分代码3.2所示。
[0203]
代码3.1卷积计算6层循环伪代码
[0204][0205]
代码3.2循环平铺计算伪代码
[0206][0207][0208]
因此在加速器中完整的卷积计算主要分为三种循环:
[0209]
1)外循环。外循环的循环次数决定了划分块的数量,同时外循环主要工作就是完成每个分块寻址的功能,然后将一个块大小的数据读取进缓存,如图8所示的输出特征图划分,寻址就是计算图中示例的块1首地址到块2首地址的偏移长度。
[0210]
2)访存循环。由外循环确定当前分块的首地址后,根据划分好的分块大小在外存储器中读取到对应的数据(划分后的张量块也是高维数据,需要嵌套循环来读取)。
[0211]
3)内循环。通过访存循环将一个tile块的数据送进各自的缓存后,再由内循环控制缓存将数据送进计算模块中进行卷积计算,因此数据流动的方式是外存储器到缓存,缓存再到脉动阵列计算单元。根据代码3.2所示,最终卷积计算的外循环是4重嵌套循环,而执行计算任务的内循环是6重嵌套循环。
[0212]
根据卷积循环平铺的原理,可以对每个marco node生成相关的指令,marco node为算子融合后的新定义的算子节点;指令生成流程如图9所示。在加速器中主要分为两个计算模块:
[0213]
1)脉动阵列卷积计算。首先生成convsatrtins指令告知加速器进行卷积计算,然后用baseaddrins指令指定输入缓存、权重缓存以及偏置缓存在外存储中进行数据读取的基地址。接着是外循环相关指令,由loopins指令和strideins组合而成,同理访存循环和内循环也是如此。每种类型的循环都是由多个loopins指令形成嵌套循环,由代码3.2所知,外循环有4个loopins指令,访存循环中每个缓存有3个loopins指令,而内循环有6个loopins指令。每个loopins指令下也会生成1个或者2个strideins指令,strideins指令表示每次循环各个数据地址的偏移,例如用四层循环访问形状为(n,h,w,c)的特征图,循环长度分别为n、h、w、c,每个循环对应的地址偏移步长则为(h*w*c,w*c,c,1)。三种循环类型之间实则也
是一种嵌套关系,外循环每执行一次都要完成访存循环和内循环的所有操作,因此外循环属于上层,而访存循环与内循环属于平级。
[0214]
2)alu线性计算。卷积计算的中间结果会保存在输出缓存里,当中间结果累计完成后会送进alu模型进行线性计算。编译器首先生成alustartins指令告知加速器后续指令为alu相关指令,然后会配置输出张量的基地址(张量写回首地址如何计算在内存分配管理部分已经叙述过)。循环指令首先是遍历输出缓存循环用以取得输出缓存中的计算结果,然后生成输出写回的外循环,这也是起到给输出特征图分块寻址的功能,最后由写回循环将实际数据写回,每个写回的数据首先还要经过comins类型指令配置的相关线性计算,例如leakyrelu或relu相关指令为一个mul指令和max指令(leakyrelu乘以0.1,relu乘以0,然后再与原数比较求最大值)。如果是池化算子,会在遍历输出缓存循环前再加两组池化长度的循环,相对应的各个地址偏移也会改变,同时会加一个计数器,例如2*2最大池化每执行4次遍历输出缓存循环才写回寄存器里的值(寄存器会每次更新最大值)。有池化操作的情况下输出缓存里的块大小与实际写回的块大小并不相等,这是因为缓存里的数据块是卷积计算结果的块大小,而alu写回的数据块是算子融合后最后一个线性计算输出的块大小。对于上采样算子,与池化正相反,在写回循环前加上两组上采样长度循环(目前加速器只支持2*2的上采样)。
[0215]
最终编译器通过对全部macro node进行指令生成以完成对整个网络模型的编译工作,加速器将指令和量化后权重写入内存中进行初始化,然后再将检测图片送进指定的地址空间便可以开始加速器的运算。
[0216]
2.2优化设计问题分析
[0217]
目前国内对于专用深度学习编译器的相关研究还比较少(大部分都是基于通用的gpu/cpu平台),在cnn模型应用的部署上还存在很多挑战,下面从三个方面对编译器的进一步优化内容进行分析探讨。
[0218]
2.2.1支持复杂的跳跃连接网络结构
[0219]
随着cnn算法的快速发展,研究人员希望通过使用跨层跳跃连接特征图来解决因为网络层数较深导致的梯度消失问题,因此产生了很多复杂的网络结构。如图10所示densenet的跳跃连接情况,它是由不同层的各个特征图通过concat算子互相串联而成,其中同一个特征图可能会与多个特征图进行跳跃连接。相较于卷积等计算密集类型算子,concat属于访存密集型,而在加速器中要想实现特征图跨层式的连接,加速器需要将以往的计算结果重新读取进缓存或者一直存在缓存中,但这些设计对于专用加速器来说都是十分复杂的。
[0220]
如果拼接后各个子特征图的数据在内存空间中仍然是连续的,那么只需要将各个子特征图放在合适的位置就可以直接实现对子特征图和总特征图的数据访问,例如2.1.2小节中的分支卷积合并,目前很多加速器基于的数据布局都为nchw,在通道c方向的拼接使得在内存中各个张量仍是连续的,因此很容易将图中的output拆分为output1和output2。然而由于数据布局的不同,子特征图的数据在内存中可能是间断存储的,同时同一个特征图可能会有多个无关联的特征图进行跳跃连接,如图10中每个relu输出都有部分相同的特征图进行相互连接,加速器处理这种结构逻辑更加困难,需要通过编译器处理来支持这种复杂的网络结构。
[0221]
2.2.2灵活支持不同的量化方式
[0222]
对于在专用加速器硬件上的部署模型需要量化成定点整数。量化分为对称量化和非对称量化,从量化公式(2.1)和公式(2.2)可知这两种方式的区别在于非对称量化需要对偏移z进行处理,相对应在加速器硬件也要有相关的处理模块。如图11所示的pe乘加计算单元,对称量化的输入和权重可以直接进入pe单元,而非对称量化在进入前需要先经过偏移处理单元,因此在加速器固定硬件结构下这对于模型的量化是不友好的,同时加速器一般为了设计简易性都采用了对称量化这种形式,但是如果数据的分布十分不对称就会导致模型量化的精度损失特别严重,尤其是卷积计算后都会有激活函数,而激活函数的非线性操作必然会造成输出特征图的数据分布差异巨大,例如经过激活函数leakyrelu后,特征值在正负半轴的分布范围能达到相差10倍左右。当然,也有相关研究考虑过将使用激活函数relu将输出数据限定为非负,这样就可以用无符号数来表示数据,同时偏移z将取为0,但这需要固定激活函数来限定范围,同时在模型的输出末尾一般并没有激活函数来约束数据范围,同样需要处理正负数据都有的情况。
[0223]
此外,量化方式从位宽长度上还可以继续划分,比如常用的int8量化和int16量化。高位宽量化相比于低位宽量化计算精度损失比较小,而低位宽量化却有更低的计算复杂度,这两种方式并没有绝对的优劣。然而当硬件固定后,模型的量化位宽也就固定了,模型无法根据自身数据的分布来选择合适的量化位宽。同时在脉动阵列中,每个pe模块是由一个dsp单元形成的,dsp中执行的是输入全部预留位宽的一次乘加操作,而低位宽量化数据只会占用dsp中预留位宽的小部分,不能充分利用dsp的计算资源,例如dsp48e2最大可以做到18*27的乘法,然而输入不管是16位还是8位其计算时间都是一样的。同时固定的pe数量导致每次加速器核心模块的计算量是不变的,因此低位宽量化并不会减少卷积中的计算次数,由于受到硬件上dsp数量的限制低位宽量化并不能体现出相比于高位宽的加速效果,因此需要将dsp中剩下的预留位宽也利用起来以实现更快的加速。
[0224]
2.2.3优化循环平铺和循环重排
[0225]
随着cnn模型规模的增大,有限的片上资源导致了缓存无法存下全部特征图和权重数据,因此需要将特征图和权重进行分块才能在加速器上完成整个网络的计算,即卷积循环计算需要进行循环平铺。加速器每次从存储器加载一块数据到片上缓存,再由缓存进入运算单元进行计算,其中数据的流动如图12所示。特征图和权重的分块尺寸如果太大,缓存可能会装不下从而发生错误;而如果分块的尺寸太小,缓存的利用率会很低,导致缓存与内存之间的数据会频繁更换,最终的耗时也会特别长。
[0226]
同时输入特征图和权重对应着不同的缓存,而每个缓存的命中情况并不同步,这与卷积计算本身的特性有关,循环类型与缓存的命中关系如表3.1所示,true表示数据命中,而false表示未命中。因为卷积的输入和权重存在数据复用的情况,同一块输入数据会与不同的卷积核进行计算生成输出特征图通道上不同数据,而同一块权重数据会与不同的输入进行计算生成输出特征图平面上的不同数据,因此需要根据硬件结构优化卷积操作中的循环平铺和循环重排来提高加速器的性能。
[0227]
表3.1循环类型与缓存数据是否更换关系表
[0228] input_bufferweight_bufferoutput_bufferbias_bufferowtruefalsetruefalse
ohtruefalsetruefalseictruetruefalsefalseocfalsetruetruetruekwtruetruefalsefalsekhtruetruefalsefalse
[0229]
2.3小结
[0230]
通过对专用深度学习编译器的基本设计架构进行了描述以及对存在的一些挑战进行了分析,从三个方面提出了专用深度学习编译器的进一步优化设计,分别是支持复杂的跳跃连接网络结构、灵活支持不同的量化方式和优化循环平铺和循环重排。通过对以上分析,更加明确了本文的研究内容和目标,为之后的进一步的优化设计奠定了基础。
[0231]
3专用深度学习编译器的优化设计与实现
[0232]
随着深度学习应用的普及,嵌入式设备上已逐渐集成了cnn专用加速器,而如何使神经网络模型高效地部署在这种嵌入式环境下成为了研究热点。深度学习编译能将模型快速地映射在深度学习加速器上,但目前对这方面的研究还比较少,对于卷积神经网络模型的部署还面临在许多挑战,在上一章节已经明确了编译器进一步优化设计的目标:其一,支持复杂的跳跃连接网络结构;其二,在固定硬件结构下通过编译器灵活支持不同的量化方式;其三,优化循环平铺和循环重排来提高加速器性能。以解决上述问题为切入点对专用深度学习编译器提出了一些优化设计以提高模型部署的高效性以及灵活性,在3.2和3.3小节设计了的相关实验来对优化后的性能进行测试,而对于优化后编译器的整体功能测试会在下一章节通过仿真平台进行叙述。
[0233]
3.1基于内存共享的跳跃连接网络结构处理
[0234]
很多卷积神经网络的结构之所以复杂是因为存在大量的跳跃连接结构,主要就是特征图拼接算子之间的各种组合,通过硬件设计来处理这种结构比较困难,而这一类算子应由编译器在编译过程中来简化从而生成适应加速器硬件结构的解决方案。
[0235]
3.1.1内存共享
[0236]
对于concat或者split这类算子,并不涉及相关数据的计算操作,而且输入特征图和输出特征图在数据内容上会有重叠的部分,这部分重叠的数据应该由涉及到的相关特征图共享而不是让每个特征图各享有一块独立的内存。总的内存空间大小由尺寸最大的特征图来维护,即concat算子和split算子分别由输出特征图和输入特征图来申请内存空间,总内存中的各个子特征图不用再申请额外的内存空间,只需要维护一个在共享区域内的首地址即可,而这并不违背2.1.3小节所述的首地址叠加,因为子特征图之间不会互相覆盖数据,同时总特征图只有读操作,并不会重写覆盖掉子特征图中的数据内容(split则反过来)。
[0237]
不同的数据布局以及不同的拼接维度都会导致各种特征图在内存中排布的不同,例如nchw的数据布局以及常在c方向上拼接的concat算子,各个子特征图在各自所属内存中是连续储存数据的,这易于加速器的读取,而对于nhwc这种数据布局各个特征图在内存中却是间断存储的。如图13所示,数据布局为nhwc的a、b、c三个特征图通过通道拼接形成特征图d,整个空间的内存大小分配由特征图d的尺寸决定,a、b、c根据拼接的先后顺序并基于d的首地址得到自己的首地址,由于内存空间不连续,需要编译器通过特殊的编址方式来寻
址。
[0238]
3.1.2共享区域划分和编址
[0239]
将参与数据拼接或拆分的特征图划分在一个拼接区域——共享区域(如果没有参与就单独为一个共享区域),而这个区域的内存空间大小根据通道数最大的那个特征图的尺寸来计算。如同4.2所示,6个特征图被划分为三个共享区域,它们分别为:{a}、{b,c,d}和{e,f},而其中决定内存大小分配的为特征图a、d和e。在{b,c,d}这个区域中,d首先申请一块内存大小,b和c各自占领其中属于自己的那部分非连续内存(因b、c在整个共享区域内属于子特征图,分布只占用整个空间的一部分的内存),当b、c都完成卷积操作后,d所对应的那部分内存已经写入了作为下一个卷积输入的数据,加速器直接进行卷积计算即可。因此拼接或者拆分操作这类算子的操作对于加速器来说是无需调用内存进行运算的,而编译器在编址后就将拼接或拆分算子从计算图中删除,加速器只管依次序计算marco node中的每个卷积操作,所以只要管理好地址之间的偏移关系就可以处理任何这种分支结构。下面分别介绍编译器在连续内存和非连续内存中地址偏移:
[0240]

连续内存
[0241]
这种情况属于没有参与共享内存或者像上一小节所描述的nchw数据布局下的c方向拼接。假设特征图的数据布局为nhwc,独自为一个共享区域且形状大小为:
[0242]
shape=(1,h,w,c)#(4.1)
[0243]
对于每个数据的读取需要根据shape拆分为4个嵌套循环(实际上是3个,在模型推理阶段第一维为1)来控制。当运行最里层的c循环时,每个数据的地址偏移为1;当运行次里层的w循环时,地址偏移为c;而最外层h循环时,地址偏移为c*w,因此定义循环偏移列表stride
list
=shape。编译器每个循环的地址偏移计算公式为:
[0244]
stridei=∏stride
list
[i 1:]*per
bytes
#(4.2)
[0245]
其中i表示的是第几个维度的循环,per
bytes
表示每个数据在内存中占多少字节,stridei表示第i个维度循环所对应在内存中的地址偏移。如果是对张量进行分块,此时的stride
list
应该等于完整张量的大小,循环仍然按照分块的尺寸来执行,即循环的次数并不总是跟地址偏移长度是一致的。
[0246]

非连续内存
[0247]
在图13所示的内存分布中,各个特征图的数据并不是在内存中连续存储的,为了得到各个子特征图的所有数据,在计算图中给tensor增加一个属性——concat_channel(简写为cc),这表示与该特征图所属共享区域中参与拼接其余特征图的总通道数(为0表示单独一个共享区域)。因此对于公式(4.1)所示形状大小的特征图,其地址循环偏移列表:
[0248]
stride
list
=(1,h,w,cc c)#(4.3)
[0249]
对于每个循环地址的偏移仍然用公式(4.2)所示计算。这种情况shape和stride
list
的区别在于一个是特征图自己的通道c,另外一个是所属共享区域的总通道cc c,cc也表示特征图在内存中每c个连续数据就会间隔cc个数据,而访问数据的循环数仍然用shape来表示就可以访问到非连续内存里的数据。对于其他维度或者其他数据布局的拼接情况也可以增加相应的concat_weight和concat_height这些属性,因此对于编译器只需要划分共享区域分配首地址和在tensor中增加一个额外属性的负担就可以通过地址偏移指令来处理这种数据在内存中不连续的情况,其中读操作与写操作的地址偏移计算是一样
的。
[0250]
3.1.3特征图深度复制与算子交换顺序
[0251]
若一个特征图与其它多个没有关联的特征图都做拼接,即一个特征图可能同时属于两个不同的共享区域,如图15左边所示有两个共享区域:{a,b,d}和{a,c,e},这两个区域有重叠部分特征图a,因此当给特征图a设置首地址的时候,编译器不知道根据特征图d还是特征图e去求a的首地址。因此如同4.3右边所示,增加了一个深度复制的算子——copy,将特征图a深度复制成特征图a

和a

,此时共享区域变为{a

,b,d}和{a

,c,e},copy算子同样会参与算子融合。对于copy算子的处理就是将a(特征图a没有实际内存)对应的卷积输出经过alu模块后分别存入到特征图a

和特征图a

当中,因为两者数据是一致的并不需要额外计算,a在alu模块计算完成后将结果写回a

和a

的两个地址,即在图9的alu模块指令生成中只需要多一个设置输出基地址的指令和每个写回循环下多一个地址偏移指令(偏移由a

和a

各自所属的共享区域计算)。
[0252]
拼接类算子是由编译器通过共享内存和编址实现的,在硬件结构上并不需要多出相应的计算模块。如果计算图中拼接类算子的输出又作为输入流向了pool或者其它线性计算算子,例如激活函数等,需要对计算图结构做出相应的转变。例如图16所示的yolov4 tiny网络里的残差结构,图中第二个拼接算子后有一个最大池化操作,最大池化或者其它线性计算算子是直接对输出缓存里的卷积结果进行计算,而拼接类算子的处理针对的是外存储器里面数据的地址逻辑,为了实现两者的一致性,将最大池化或其它线性计算算子向前移动与拼接类算子交换顺序。这里只能支持特征图通道c方向上的拼接或者拆分操作,因为池化和激活等算子是在特征图平面上的操作,而各个通道之间是相互独立的,交换顺序并不会影响整个网络的计算结果。此外还可以交换maxpool与激活函数的顺序,因为maxpool在前面能够减少做激活计算的次数。
[0253]
利用内存共享和编址可以实现对多个特征图的拼接,再经过特征图深度复制划分内存共享区域可以进一步简化错综复杂的跳跃连接结构,同时需要交换计算图中的一些算子顺序来契合加速器的硬件结构。通过以上设计基本可以支持大部分有跳跃连接结构的复杂网络模型。
[0254]
3.2卷积神经网络模型的量化方案
[0255]
基于通用平台处理器的模型量化通常并没有考虑对偏置的处理,因为偏置做的是加法计算且量化偏置对性能并没有什么提升,所以偏置仍然是用浮点数处理并在计算过程中存在量化和反量化操作。然而浮点计算在fpga这种架构上却是比较复杂的,同时量化还分为对称量化和非对称量化,非对称量化在计算过程中还需要考虑对偏移的处理,这对处理器来说是需要额外的加减法模块来计算偏移,因此对于模型的部署需要考虑对偏置的量化和偏移的处理。
[0256]
3.2.1基于偏置与偏移融合的量化处理
[0257]
根据卷积神经网络的计算原理和量化公式(2.2)可得卷积计算过程的量化公式为(这里以处理非对称量化为例):
[0258][0259]
其中∑表示卷积求和,q1、q2、q3、s1、s2、s3、z1、z2和z3分别表示输入定点数、权重定
点数、输出定点数、输入的缩放因子、权重的缩放因子、输出的缩放因子、输入的偏移、权重的偏移和输出的偏移。因为cnn专用加速器处理浮点运算比较繁琐,模型的各张量在整个计算过程中都是由定点整数表示的,那么就必须对偏置(bi
as
)也进行量化,偏置的计算在本文加速器中是放在脉动阵列累加过程中的,因此偏置的量化为:
[0260]
b=round(bias*s1*s2)#(4.5)
[0261]
量化偏置后完整的卷积量化公式为:
[0262][0263]
由于表示的是浮点数,取其中2-n
可以用右移n位操作表示,m0为整数;(n值越大误差越小,n的最大取值跟硬件结构有关),因此对于缩放因子的转换可以用乘以一个定点数和右移来实现,这些操作是放在alu相关模块里进行的。
[0264]
对于非对称量化,输入和权重需要有一个减偏移的操作,同时输出也有一个加偏移的操作,即cnn专用加速器在将输入送进卷积计算前需要增加一个减法模块,同理在将结果写回的时候也需要一个加法模块。由于对偏置bias进行了量化,可以将对偏移z的处理一起放入偏置中,同时得益于权重的数据分布是比较均匀的,在业界基本上都是采用的对称量化即z2=0,即非对称量化一般只针对于输入,根据卷积计算的分配律,卷积计算公式变为:
[0265][0266][0267][0268][0269][0270]
s1、s2、s3、z1、z3、q2和bias为定值,在模型推理阶段都不会发生变化,∑z1q2为z1和q2的卷积计算,可以在编译阶段事先计算好,即b

也是一个定值,融合了输入的偏移和输出的偏移作为新量化的卷积偏置放进脉动阵列,而加速器处理这种非对称量化也只需要像对称量化一样计算q1q2的卷积即可,这在同一个硬件结构下处理对称量化和非对称量化是通用的。
[0271]
针对于跳跃连接网络结构的量化,为了实现连接后特征图中数据的缩放比例一致,需要将连接前所有特征图的数据分布统一起来计算公有的s和z,这样就可以直接使用连接后的特征图进行后续计算。
[0272]
3.2.2int8/int16可变位宽量化
[0273]
量化是用定点整型来表示32位浮点数,因数据分布范围的不同一般采用的是int16量化或者int8量化。在实际模型的部署应用中,专用处理器上往往会存在多个模型,比如人脸识别系统上就有人脸检测和人脸识别两个模型,一些模型因精度损失和检测速度需要采用了不同的量化数据位宽,而处理器架构只有一个,因此编译器需要实现对可变位宽量化的处理。
[0274]
加速器里的每个pe模块是由一个dsp单元实现的,而在pe中做的是一次乘法操作(暂时不考虑加法),int16量化时计算的是一个数据点的结果(或者中间结果),而int8量化方式在一个pe里可以同时计算多个数据点的结果,如公式所示:
[0275]
(a<<m b)*(c<<n d)
[0276]
=ac<<(m n) ad<<m bc<<n bd#(4.8)
[0277]
其中a、b分别表示两个输入;
[0278]
c、d分别表示两个权重;
[0279]
m、n表示移位的位数;
[0280]
<<表示左移;
[0281]
如果满足bd《1<<n,bc<<n《1<<m,ad<<m《1<<(m n),可以通过将计算结果进行数据位宽拆分分别得到ac、ad、bc和bd的结果,因为它们并没有相同bit位上的数据重叠。因此同时将两个输入和两个权重值进行移位相加送入pe能同时得到四个计算结果(这是对于输入和权重都为正数的情况,存在负数的情况会在后面讨论),但这是理论上的,由于硬件的限制并不能实现同时输入和权重的移位相加,例如高位宽量化为int16,低位宽量化为int8,满足公式(4.8)的m和n分别至少取和16和32,pe中两个输送通道的最大预留位宽没有办法装下左移32位的数据(dsp48e2中最大位宽也才27位),因此只能采用输入移位相加或者权重移位相加的形式,下面分别介绍这两种方法。
[0282]

输入移位相加
[0283]
送进pe乘法输入通道中的双重输入必须是作用于不同输出结果,在特征图c方向的数据因要进行累加所以不可取,因此拆分输入要从特征图平面入手。如图17所示,根据送进pe输入通道两个数据的位置的距离,可以分为相邻式输入移位相加和对半式输入移位相加,图中假设的是步长为1的3*3卷积计算。
[0284]
相邻式输入移位相加是与下一个相邻输出点对应同一位置的输入值进行数据移位相加后再送进pe,计算的结果经过拆分将会对应输出特征图上两个相邻的输出点。下一组输出的步长为2,相对应的输入特征图的步长也为2,这种方式实际上就是对输出特征图的二分。对半式输入移位相加,即输出特征图的上下均分,会跨越式的读取下半张输出特征图对应的输入数据,而上下输入点之间的距离是固定的(相邻式的固定距离为1),因此同样可以每次轻易地寻找到下面的输入数据进行移位相加后送进pe进行乘法计算,此时输入的偏移步长为1,只不过循环的行数oh需要减半。
[0285]

权重移位相加
[0286]
对于同一个输入,单个卷积核平面的不同权重跟其点乘结果的逻辑不好控制,而不同通道上的权重跟输入又不是一个维度的,因此权重移位相加的两个值只能来自不同卷积核上对应的同一位置。如图18所示,与输入移位相加类似,相邻两个卷积核上同一位置的
权重进行移位相加后送进pe的权重通道,做完计算后的结果拆分对应于输出特征图同一位置上的两个相邻通道值,单个卷积核的计算流程不变,此时相当于卷积核的数量oc减半了。
[0287]
对于有符号数的移位相加,最后乘法计算结果直接拆分后会有误差。如图19中的示例,参与计算的均为3位有符号数,左移6位后最终乘法计算结果为12位有符号数,其中高6位和低6位分别为参与移位相加前两个数与被乘数的计算结果。从图中可见,高6位的结果有偏差,需要加上低6位的符号位才为正确的计算值,因此在对pe的输出结果进行拆分时,高位数据需要加上低位数据的符号位。
[0288]
在脉动阵列中每个pe单元会累加上一个pe的输出结果,然后送入下一个pe单元继续进行累加,为了保证与int16量化计算过程的一致性以及int8量化模式下低位结果在累加过程中产生的进位不会覆盖高位数据的内容同时高位结果也不会溢出,会增加一个“溢出位”用于兼容数据累加的进位。例如int16量化和int8量化,对公式(4-8)只考虑int8量化方式下pe输入的移位相加,公式为:
[0289]
(a<<m b)*c=ac<<m ab#(4.9)
[0290]
此时ab的结果为16位,单个pe的话m只取16即可,但多个pe可能会存在进位的情况,通过增加溢出位可以保证不会影响到高位数据的内容,最终pe输出结构如图20所示。溢出位的选择跟硬件结构有关,即累加pe的数量(考虑最坏情况下会进位多少次,然后取对数),因此m的取值应该为16加上溢出位的位数。但由于dsp中的输入通道位宽有限(加速器中dsp48e2最大能实现18*27的乘法,不考虑溢出位已经占了24位,只剩下3位容错),溢出位无法加在m上对输入进行移位相加,因此m仍然只取16并且放弃了dsp中的加法功能,即在加速器中上下pe不再直接相连进行累加,而是在外面增加一个累加模块,将每个pe的输出拆分并进行“溢出位”的移位(此时也会完成有符号高位结果加低位结果符号位的操作)拼接后再在新的累计模块中进行累加,最终减少了乘法的时间而增加了加法的时间,但在硬件中乘法会比加法慢10倍以上。这些是本课题组为了支持可变位宽量化对加速器与编译器所做的协同优化,编译器会生成量化位宽长度的指令信号和计算进行移位相加两个数的相对地址偏移,同时指定结果累加的路线,int16量化按照原加速器模式进行累计计算,int8量化则会进行输入移位相加并且走新的累计路线。
[0291]
一个输出点的所有对应点乘累加计算完成后会加上融合量化后的偏置b

,因为在int8量化下,一个计算结果实际上是两个点的输出值,所以b

也会进行拼接。如果是pe权重拼接,拼接的b

是取自不同卷积核对应的值;如果是pe输入拼接,拼接的b

是同一个值。
[0292]
int8量化输入数据在不同位置的数据格式如图21所示,上下对应位置的输入仍然存在int16量化数据格式缓存中(分别存在高低位),输入缓存向pe写入的数据对于量化位宽是透明的,每次都会送进16bit,再根据编译器生成int8相关信号指令进行m位的移位相加操作,然后送进pe里面,如果是int16量化,则跳过此操作,因此对于int8模式仍然能充分利用int16的输入缓存,同时也不需要改变缓存结构。
[0293]
3.2.3模型量化流程
[0294]
通过上面两个小节对量化偏移处理以及int8/int16的可变位宽量化进行了设计,编译器可以不用再改变硬件就可以实现多种量化方法,编译器的模型量化流程如图22所示(int8采用了输入的对半式上下移位相加),具体步骤包括如下:
[0295]

运行校准集,获取特征图的动态分布范围,主要是获取其在校准集中的最大值
和最小值,权重由于在推理阶段是静态值,所以不需要校准集也能获取其分布范围。
[0296]

检测量化位宽选项,这里目前只支持int8和int16两种形式,如果是int8量化会生成相应的标志信号,此标志在编译器后端会生成相应的指令int8_ctr_instruction用于告知加速器本层卷积量化数据位宽为int8(默认是int16)。
[0297]

遍历计算图并判断特征图之间是否有跳跃连接的关系,如果有,则进行缩放因子和偏移的统一。
[0298]

检测量化编译形式选项,根据上一步int8或int16的选择计算出缩放因子scale和零点偏移,如果为非对称量化,将偏置和偏移进行融合。
[0299]
3.2.4实验结果与分析
[0300]
本文量化设计在开源fpga卷积神经网络加速器——dnnweaver上进行验证,经过优化设计后通过编译器处理使该处理器既能支持int16量化也能支持int8量化。该加速器片上系统首先使用硬件描述语言进行建模,然后通过综合与实现后将其烧写到ultrascale mpsoc架构系列的xczu19eg开发板(见图23)上,fpga中时钟为100m,cnn加速器占用的资源情况如图24所示,可见为了减轻资源受限目标设备的存储与带宽压力,有必要在编译器中实现量化功能。本小节实验为在固定硬件下不同量化方案的验证以及对比优化后的部分性能。
[0301]
通过基于3.1小节对跳跃连接网络结构的处理设计,本次测试的网络模型选用了yolov4 tiny,它是yolov4模型的精简版,属于轻量化模型并常用于目标检测任务。如图25所示,整个网络结构有38层,其中使用了三个残差单元,每个残差块的结构都如图16所示,本节实验也是对跳跃连接网络模型可支持性的验证。在固定加速器硬件下,编译器对yolov4 tiny模型采用了int8非对称量化、int16对称量化和int8对称量化三种量化方式,本次实验测试集为voc2007-test,置信度confidence=0.24以及nms_iou=0.5,除了编译器处理的几种方式外,还有量化前原pytorch框架浮点模型以及用pytorch量化工具的常规非对称量化计算两组对比实验,它们均在cpu下运行,cpu的规格参数见表4.1。
[0302]
表4.1cpu系统规格参数
[0303]
cpu参数规格大小操作系统ubuntu 20.04型号inter(r)core(tm)i9-10900x主频3.70ghz核心数量10二级缓存10mib
[0304]
表4.2不同量化类型参数量、平均精度和帧率对比
[0305]
量化类型参数(mb)20类map(%)帧率(fps)浮点模型(fp32,cpu)22.61281.0215非对称量化(int8,cpu)5.65579.261.5对称量化(int16,fpga)11.30879.9221非对称量化(int8,fpga)5.65379.3139对称量化(int8,fpga)5.65373.4339
[0306]
最终的实验结果如表4.2所示,从表中可以看出int16量化和int8相较于原模型可
以将参数量分别减少到1/2和1/4,而在cpu上同样是int8量化方式用pytorch量化工具处理的模型参数比在专用加速器上量化略高的原因是因为没有对偏置进行量化,仍然用的是浮点数fp32,而在专用加速器上偏置为了防止量化过程中溢出采用了较高位宽的int16,但也比原模型的浮点数较小。在精度损失方面,int16量化实验组尽管采用了对称量化,但凭借着位宽长度的优势仍然有着比int8非对称量化更低的精度损失。int8对称量化的精度损失比较高,因为模型中激活函数采用的是leakyrelu,导致了特征图的正负分布极其不平衡,负半轴大部分精度都是浪费的,因此对于低位宽采取非对称量化是非常有必要的,最后能将精度提升了约6%。而在加速器上对偏置进行量化的int8组反而比在cpu上不对偏置量化的int8组精度损失略低,其原因可能是在加速器中对数据取整的策略和cpu中取整的方式不一致,从而导致了在加速器中的最终计算结果反而更接近于原浮点模型。因为在fpga中的右移操作是向下取整,因此为了提高精度本文采用了先加上0.5再右移的策略以实现四舍五入取整方式,0.5的二进制表示形式为:
[0307]
{~data[m],{n{data[m]}}}#(4.10)
[0308]
其中{}表示二进制的位拼接,~表示取反操作,data为需要右移的二进制数,m为最高符号位,n为右移数减1。
[0309]
在模型计算加速方面,相比于cpu的浮点计算,int16量化加速比为1.40,而int8量化的加速比为2.60,而通过pytorch量化工具的推理速度特别慢的主要原因是在计算过程中存在大量的量化与反量化模块。本小节通过实验验证了在固定加速器硬件下多种量化方式部署的可行性,同时int8量化相比int16量化还有着接近2倍的加速比。
[0310]
3.3卷积计算循环调度优化策略
[0311]
由于有限的片上资源,各个缓存无法一次性存下完整的特征图和权重,需要将数据进行分块处理,每一块都能执行卷积操作来计算中间结果,同时缓存还可以避免频繁的外存储数据访问,本小节将会探讨如何优化特征图分块和循环重排的调度从而使卷积计算效率达到更高。
[0312]
3.3.1权重缓存带宽优化
[0313]
在做循环调度优化前,需要先对本文基于加速器中权重缓存的带宽进行优化。根据卷积计算的特性得知每个卷积核之间的计算是独立的(每个卷积核对应于输出特征图的一个通道),因此多个卷积核的计算在加速器中是并行进行的(即一个输入会与多个权重进行并行计算),而当脉动阵列中计算数据更换时,权重却需要更换数倍于输入的数据量(数倍由并行程度决定,也就是脉动阵列中pu的数量)。如图26所示,图中输入的通道为32,并行计算的卷积核组中卷积核的数量也为32,假设在脉动阵列中pe的规模为32*32,当从卷积核计算点1到计算点2时,输入缓存会在单位周期内重新输送32个数据到脉动中,则相对应的权重缓存需要重新输送32*32个数据,即在一个时钟周期内缓存需要送8196bit数据进脉动阵列(假设量化类型为int8),尽管对于加速器中的已经对数据做了量化处理,但这对于权重缓存的带宽压力仍然特别大,不易于加速器时序的收敛。
[0314]
由表3.1可知卷积计算不同的内循环顺序(与外循环一致)会产生三种数据固定:
[0315]

输入固定(input stationary)——先做to(oc)循环;
[0316]

权重固定(weight stationary)——先做循环ow、oh(ow、oh)循环;
[0317]

输出固定(output stationary)——先做kw、kh、ti(ic)循环。
[0318]
其中输出特征图的宽ow和高oh、输入通道数ic、卷积核的数量oc和卷积核的宽kw和高kh,小写字母表示内循环。
[0319]
如图27所示,每个pu计算单元负责一个卷积核的并行计算,而在pu内部有32个pe计算引擎执行乘加计算。同样在weight stationary模式下,ow、oh循环内sa中的输入数据每个循环周期都在更换,即每个循环周期会送进32个输入数据进入sa的输入流水线队列(图中进入后第一个周期会执行a0的计算,然后下个周期会执行a1的计算),权重缓存会在一开始对应输入送进32*32个权值数据进入sa的权重流水线队列,然后权重缓存便空闲了。本文这里去掉权重的流水线形式,在ow、oh循环的每个周期内只会输送在当前周期输入数据执行计算所对应pu中各个pe的权重数据(即权重缓存每次只会输送相对于sa一行的32个权重数据),因为期间不会更换新的一组32*32数据,所以分32个周期将权重送到sa中,这样权重缓存的带宽压力就能降低成和输入缓存一样,当然这一切的前提是保证内循环是weight stationary模式且ow*oh总的循环周期数要大于单个pu中pe的数量才能满足送完,这需要编译器在做循环平铺时进行把控。
[0320]
在编译器设置内循环的地址偏移时,对于ow、oh循环中对缓存的地址偏移需要进行特殊处理;文中大写字母为外循环,小写字母为内循环,因此ow、oh为内循环。在正常情况下的ow循环(假设内循环顺序为ow、oh、ti/32、to/32、kw、kh),输入缓存和输出缓存的地址偏移都为1(缓存的宽度是32个数据),而权重缓存则是没有地址偏移的,只有等到做ti/32循环时才会因更换权重数据设置相应的地址偏移。此时在ow循环的权重缓存地址偏移设置为1,而在oh循环的地址偏移设置为ow的大小,当满足ow*oh》=32的时候,ti/32循环切换到下一批权重需要读进时上一批本应在单位时钟周期读写完的权重数据分32个周期内完成了工作,当ow*oh超过32的时候,sa也只会接收前32个周期的权重数据。通过编译器如此设计也是为了提高加速器的时序,因为在带宽优化前该加速器跑100m时钟也比较难收敛。
[0321]
3.3.2基于时延的循环调度优化策略
[0322]
在2.1.3小节中描述了循环平铺后的三个循环操作,外循环是来确定每次循环各个tile块的首地址,根据表3.1不同的外循环顺序涉及到缓存中的tile块的数据是否更换;访存循环是将需要更新的数据分块写进各个缓存中,消耗的时间由总的更新数据量和外存储器的数据带宽决定;内循环可以理解成脉动阵列做流水计算的计算时间消耗,主要也跟循环顺序有关。因此整个卷积计算的时间消耗主要分为两类:

内存访问。在做嵌套外循环的时候,不同的循环类型会产生不同的数据搬移情况,例如做oc循环的时候输入缓存里的数据是不需要更换的,而权重缓存就需要更换相应一个tile大小的数据。如图28所示的多层嵌套循环中,从最底层到最外层,一旦某层循环开始更换缓存里的数据,缓存总的需要更换数据次数为该层的循环次数向外层循环次数做累乘,因为外层循环每循环一次都要做完完整的内层循环,以此类推。在整个卷积计算过程中,每个缓存对外存储器总数据访问量的计算如算法4.1所示,最终用总数据访问量除以外存储器的数据带宽即可得到内存访问大约的消耗时间。

sa计算(alu里做的是流水计算,时间主要集中在将最终结果写回,属于输出缓存内存访问)。脉动阵列计算所消耗的时钟周期与内循环同步,在流水计算形成的情况下,每一次循环消耗一个时钟周期,在这期间缓存会把需要的数据送进脉动阵列,同时脉动阵列“流水式”地执行一次乘累加计算。在加速器中,每次循环类型切换都会涉及到一些状态机的改变,这是需要额外开销的。内循环sa消耗时钟周期计算如公式(4.11)所示,其中
{an}表示的内循环顺序对应的循环数,b表示切换循环的额外开销,显然{an}按照从大到小排列对于计算公式(4.11)来说数值是最小的,因此sa的最小消耗时钟周期的计算如算法4.2所示(这里考虑了3.2.2所述的权重带宽优化)。
[0323]an
(

(a2(a1(a0 b) b) b)

)#(4.11)
[0324][0325][0326]
在上述两种时间消耗中会有重叠部分,由于各个缓存采用乒乓缓存的设计格式,一半缓存在将数据送进脉动阵列进行计算的同时,另一半将会下一个分块的数据从外存储器中写进缓存,从而可以达到隐藏内存访问延迟的效果。如图29所示,由于分块大小的不同以及内外循环顺序的不同,计算的时间并不一定能完成隐藏内存访问的时间,即当前分块已经计算完而下一个分块还未写完(也有可能早已写完),这时脉动阵列就不得不停止等待,这部分因内存访问还未结束而sa还需要等待的时间称为“内存阻塞”。特殊的内存阻塞是最开始的从内存中读数据和所有计算完成后将最后一块数据写回内存,这段时间由于加速器都是空闲的,所以整个时间段都属于内存阻塞阶段。
[0327]
对于上述的几个方面,因循环平铺分块大小、内外循环顺序和缓存大小等硬件参数的不同,会产生不同的计算性能。而一个好的循环调度策略的目的是达到很低的计算时延,本文基于实现加速器消耗的时钟周期最少,提出了一种循环调度策略。如图30所示,首先外循环顺序采用枚举(固定120种计算顺序)的方式,不同的外循环之间通过多线程的形式进行循环平铺尺寸空间探索,循环平铺的尺寸同样用枚举的形式从而得到每个缓存对应的分块数据大小(ow*oh需要大于32),然后根据fpga硬件的信息去计算该种方式消耗的时钟周期,计算伪代码如算法4.3所示,最终通过比较采用消耗时钟周期最小的循环调度方案。在算法4.3中,由于每个缓存一旦在某层循环开始更换数据,外层的所有循环根据表3.1
无论是否会更换数据,都以当层循环为分界点,内层的所有循环数表示数据固定不变的周期ts,而后面的循环总数为需要更换的周期tc,即每ts个周期间隔会更换一次缓存里的数据,总共会更换tc次。而随着循环的往外后退,在这tc次循环里面会逐渐增加其它缓存需要更换的数据。最终计算出每次外循环访存消耗的时钟周期减去最小且固定的脉动阵列计算的时钟周期即可求出相应的内存阻塞周期。
[0328][0329]
3.3.3实验结果与分析
[0330]
表4.4加速器相关参数
[0331]
fpga加速器相关参数规模大小脉动阵列规模32*32循环切换状态开销2(clock)dram带宽256bit/clock输入缓存16*32*3072bit权重缓存16*32*2048bit输出缓存64*32*2048bit偏置缓存32*32*512bit
[0332]
本小节实验使用的加速器架构以及fpga开发板和3.2.4小节的量化实验一致,测
试模型选用了yolov2 tiny、yolov3 tiny和yolov4 tiny三种模型。由于目前没有其它编译器能对本文基于的专用加速器提供编译支持,因此仅对编译器自身优化前后进行实验对比。其中加速器的相关参数如表4.4所示,对比实验为优化前的手动卷积循环平铺和循环重排,实验均基于权重缓存的带宽优化,测试数据为检测3600张图片的平均运行时间,最终实验结果如图31所示。
[0333]
从图31可见,尽管yolov4 tiny的卷积层数更多,但由于很多卷积都是1*1且在yolov4 tiny前两层中卷积步长为2,因此相比于层数更少的yolov2 tiny和yolo v3 tiny,yolov4 tiny的推理速度反而更快(yolov2 tiny中卷积核的数量比较多且只有最后一层输出为1*1卷积)。通过三个模型在编译器优化前后的性能对比,卷积计算循环调度策略能将性能提升10%-20%。本文的循环调度优化策略只是粗略地估计出加速器的执行时间,但是也可以作为一种性能标准,最终通过实验也表明了循环调度优化策略能充分利用加速器的资源达到提升性能的效果。
[0334]
3.4小结
[0335]
首先设计一种内存共享和编址的方式,并通过特征图深度复制与算子交换顺序来简化计算图使编译器能支持跳跃连接网络结构;其次在固定的加速器结构上设计了不同类型、可变位宽的模型量化方法,同时通过实验验证了不增加dsp资源的情况下int8相对于int16的加速比;最后在优化权重缓存带宽的基础上设计了基于时延的卷积计算循环调度优化策略,再通过编译器优化前后的实验对比体现了优化策略的性能提升。
[0336]
4编译器整体功能测试与应用验证
[0337]
本文在第三章对编译器的基本设计架构进行了介绍,并在第四章节完成了进一步的优化设计同时并通过相关实验测试了其性能,本章主要是将通过cocotb仿真平台对优化后编译器进行整体的功能展示与测试以及对目标检测任务的应用验证。
[0338]
4.1编译器目录结构
[0339]
编译器的目录结构如图32所示,其中每个子目录都对应着编译器的一个功能模块,所有模块互相协同完成cnn模型的编译过程,各个功能模块的具体工作内容的解释如下:
[0340]

compiler。编译器后端,用于将cnn模型的计算操作转换成对应硬件执行的指令序列,包括一个卷积计算编译器(conv_compiler)和一个算子融合后的线性计算编译器(pu_compiler),同时还会负责对需要的特征图进行内存分配管理。
[0341]

fpga。硬件平台的相关rtl代码,还包含了脉动阵列的规模和缓存大小等信息。同时负责fpga的内存管理,另外提供仿真过程中进行计算图权重数据初始化、送入输入数据和获取输出结果的接口。
[0342]

graph。计算图,编译器定义的中间表示ir。主要由张量tensor、计算节点nodeop等数据类型通过拓扑关系所组成。
[0343]

isa。加速器对应的指令集架构系统,在compiler模块中主要根据此指令集生成相应的指令序列以完成模型在加速器上的映射。
[0344]

optimizer。计算图优化模块,包括算子融合优化和对跳跃连接处理的计算图结构变换等,并在相关tensor里修改concat_channel的数值(初始化为0)。
[0345]

parser。深度学习模型解析器,用以将cnn模型转换成定义的计算图形式,支持
载入模型文件的格式为onnx。
[0346]

quantization。编译器量化模块,在固定硬件结构下支持多种量化方式,包括int8对称量化、int8非对称量化、int16对称量化和int16非对称量化。非对称量化模式的输入为非对称量化,权重仍然使用对称量化。
[0347]

schedule。卷积循环调度模块,在搜索空间内寻求最优的循环平铺和循环重排方案。如果是int8量化的类型,oh属性在调度时会减半。
[0348]

sim。仿真模块,将编译器生成的指令和对应权重文件在基于python的ic验证平台——cocotb上进行加速器的仿真测试来验证指令的正确性。
[0349]
4.2编译器功能测试
[0350]
为了测试编译器整体结构的编译功能,本小节选用的测试网络模型为yolov4 tiny模型且输入的尺寸大小改为480*352,这也是为了验证编译器在非正方形尺寸输入网络模型上的可行性。因为网络模型在fpga开发板上的实际运行性能在第四章的相关实验中已经展示,因此本章中主要通过仿真来进行编译器的整体功能测试和加速器的应用结果验证。下面介绍其中主要模块的运行测试:
[0351]

量化模块
[0352]
由于片上资源受限和专用加速器处理浮点计算的复杂性,在编译器中增加了量化模块来实现模型推理的加速。通过对量化方案的设计,编译器可以在固定硬件结构下实现多种量化类型,本节的测试量化选项选择为int8非对称量化。如表5.1所示的yolov4 tiny前6层卷积的量化情况,在网络结构中conv2和conv5、conv3和conv4的alu输出会进行concat操作,因此会进行统一处理,alu输出结果会作为输入进行下一层卷积参与计算。因为本编译器不管对称量化还是非对称量化,权重的量化统统采用的对称方式,区分只在于输入特征图的量化方式。因为加速器中没有处理zero_point的模块,对于输入的非零zero_point通过编译器融入到量化后的bias当中,因此明面上加速器中输入采用的也是对称量化,即zero_point=0,但它的scale却是通过非对称量化方式获得的,只是将真正的zero_point隐藏在了bias当中,这样可以减少加速器计算的复杂度。
[0353]
表5.1yolov4 tiny前6层卷积权重和输出特征值的缩放因子
[0354]
layerweight_scalealu_output_scaleconv00.1242455240.138176888conv10.0259781360.121351070conv20.0156197600.065793789conv30.0094662930.087572626conv40.0068445550.087572626conv50.0117484140.065793789
[0355]

计算图优化模块
[0356]
经过量化模块对模型权重进行压缩后,计算图将由optimizer模块进行优化,其中的操作包括bn融合、算子融合等。在图25所示的yolov4 tiny结构中,前三层操作为conv bn leakyrelu,经过计算图优化后的结构如图33所示,融合的新节点为一个macro node,其由sys_array_op和pu_op_list组成。sys_array_op就是用于在脉动阵列中计算的卷积算子,而pu_op是负责从输出缓存直接到alu模块进行的相关线性计算,从图中可见因bn融合后在
pu_op里面并没有bn算子,只有leakyrelu激活函数,而bn是将相关参数融入到了卷积计算的权重和bias当中,这样可以减少模型的计算量。pu_op中还有与模型结构本身无关的算子typecast,这是因为无论是卷积计算还是其它的线性计算都会让其计算结果的数据位宽变大,比如int8类型的卷积经过加bias后最终结果是32位的,其相应的缩放因子也是权重和输入的乘积,最终需要根据量化需求将其转换为8bit,而typecast的负责的工作就在于此。图中可见每个算子节点的input_tensors和output_tensors决定了整个计算图的拓扑结构,conv0的前向输入包含了data、weights和bias这三个张量,而后向输出为卷积的计算结果,其位宽为32位且对应的缩放因子为属性out_scale(只是卷积算子输出的sacle,为输入和权重缩放因子乘积,即也是bias的缩放因子,量化检验集统计的是alu最终输出的缩放因子),因此在计算图优化后conv0的实际拓扑结构如图34所示。图34中的卷积计算结果conv_out是放在输出缓存当中的,然后再进入alu模块进行pu_op里面的计算,最终存回外存储器的结果为张量out1的数据(如果没有激活函数,存回的则是张量out0的数据),同时在外存储中也只有最后写回的张量才会分配内存,可以起到节约内存的作用。
[0357]
另外,对于图25中yolov4 tiny的第一个残差块,其第一个卷积(结果会与最后一个卷积结果进行通道方向的拼接然后做一个最大池化,经过4.1节的分支处理策略后第一个卷积(总网络结构的第三个卷积)以及算子融合后的结构如图35所示,分支处理是将拼接操作和最大池化交换顺序并将拼接操作从计算图中删除,因此对于残差块第一个卷积的macro node中pu_op里有相对应的maxpool算子,同时其输出的concat_c属性为另外一个分支做maxpool操作后结果的通道数。最终分配内存空间后残差分支结构输入张量与输出张量的首地址如表5.2所示,整个内存空间是根据输出张量的大小进行分配,拼接位于前面的maxpool0张量的首地址与输出张量(下一个残差块的输入)的首地址一致均为0x41157800,而位于拼接后面的maxpool1张量的首地址与前面的相差了0x40,即64个int8数据的地址长度,正好为maxpool0的通道数。它们间断的地址长度为属性concat_c,当maxpool0与maxpool1均已写回时,下一个残差块的输入数据自然就准备好了可以直接从内存中读取连续的数据。
[0358]
表5.2残差分支结构输入张量与输出张量的首地址和大小
[0359]
张量首地址大小(b)maxpool00x4115780088*120*64maxpool10x4115784088*120*64next resblock data0x4115780088*120*128
[0360]

卷积计算循环调度模块
[0361]
因加速器片上资源受限需要将卷积计算进行循环平铺,循环调度模块负责在搜索空间内寻求最合理的分块方案和循环执行顺序。如图36打印的编译器循环调度寻优日志,图中展示的是前两层卷积在遍历搜索空间过程中不断更新的最优调度方案(第一层输入为3通道不能满足32个pe的累计需要填充成32通道,加速器在优化改进时为了避免此操作对第一层做了特殊处理,所以编译器需要将第一层平面分块尺寸固定为8*8)。日志第二层中oh这个循环方向的总数为44,并不是在原模型结构中的88,这是因为选用了int8量化模式且用的是输入上下式移位相加,即对应输出的上下拆分,因此oh总的循环数减半用以同时计算上下的结果。ic和oc分别对应着脉动阵列的行与列(32*32),所以在进行划分时的粒度
为32,最终通过比较选出了最优的循环调度方案,在第二层由于输入通道数和卷积核的数量都比较小,因此在外循环中划分的只有oh/oh和ow/ow,而内循环的循环顺序自然基于权重优化带宽的前提下进行从大到小排序。
[0362]

指令生成模块
[0363]
最终编译器生成的指令如图37所示,图中展示的是conv0生成的部分相关指令。0000操作码表示的是卷积操作开始,然后通过操作码1001设置输入、权重和偏置在内存中对应的首地址,即每个缓存从外存储器中读写数据的基地址,后面循环指令的地址偏移都是基于此,由于指令的立即数字段只有16位宽,因此每个缓存对应的两条指令来设置基地址的高低地址,总共6条指令;之后便是由1101操作码指令来设置int8量化模式,其立即数表示的为pe输入上下移位相加的地址偏移,同样的因为地址跨度比较大也使用了两条指令来组合表示高低位。
[0364]
接着便是循环指令,其操作码为0111,中间的字段用以区分表示循环的不同阶段,图中可见外循环只有两层,对应了图36循环调度中conv0只进行tiling划分的ow和oh,指令立即数表示的为循环重复的次数(循环次数减1),图36中外循环的循环重复次数分别为1010=10和11101=29,与oh和ow循环划分的(11,8)、(30,8)一致。指令循环下的0110和0101指令用以表示每次循环各个张量在内存中的地址偏移的高低位,如果没有超出16位表示范围则只用0110地位表示,实际地址偏移是由循环地址偏移从外到里进行叠加的。
[0365]
4.3cnn模型应用验证
[0366]
在生成指令和相关权重文件后,利用python的cocotb平台直接使用rtl硬件代码进行推理仿真测试。通过将图片预处理并量化后送进仿真程序的图片输入接口,然后计算完成后通过输出接口可以得到相应的输出结果。如图38所示,凡是通过编译器分配内存的张量都可以根据其首地址和尺寸大小从内存中读取出相应大小的字节,然后用reshape函数复原成张量原本的形状。对于内存共享的张量也是先把整个共享区域取出再进行通道的数据划分。
[0367]
对于yolov4 tiny目标检测模型,其输出有两个特征图用于目标的分类和回归,大小分别为(11,15,75)和(22,30,75)。分类分支用于进行目标识别,回归分支则用来进行目标定位,最终目标检测的结果的如图39所示,图左边是由yolov4 tiny模型算法计算结果所画的框,图右边是由编译器生成指令经由加速器计算的结果,图中两者框的位置基本一致,只是在置信度数值有一些微小的差别,这是由于模型量化精度损失所导致的,具体损失可见表4.2所统计不同类型量化的精度损失,通过两者的对比说明编译器所设计的量化方式以及生成的指令是可信且精确的。
[0368]
4.4小结
[0369]
首先对编译器的功能模块目录进行了介绍,然后在仿真平台上通过编译yolov4 tiny模型对主要的量化模块、计算图优化模型、循环调度和生成的指令进行了测试,以此说明了编译器基本功能的完整性。最后通过目标检测任务软硬件的结果对比验证了编译器编译结果的精确性。
[0370]
5总结
[0371]
深度学习作为机器学习中一个重要的分支,其发展趋势迅猛,特别在计算机视觉和语音识别等领域里表现出了超高的识别精度。尽管深度学习模型在云端表现出来的功能
十分强大,但是其计算密集的特点也造成了在资源受限的嵌入式设备上部署模型的困难。为了解决深度学习模型落地的问题,许多专用神经网络加速器被设计而出,而模型到硬件的映射离不开深度学习编译器等软件工具。然而目前国内对于专用cnn加速器的编译研究并不多,还需要解决网络结构复杂、量化方式受限和特征图合理划分等问题。因此提出了面向cnn专用加速器的深度学习编译器优化设计与实现,经过总结,本发明完成的主要工作如下:
[0372]

对基于cnn专用加速器进行了深度学习编译器的基本设计,在设计实现中通过算子融合等优化方法减少了加速器对内存的访问以及存储空间的浪费,同时还使用内存分配地址叠加的方式避免了各张量之间的数据覆盖。
[0373]

设计了一种内存共享和寻址的方法来处理跨层式的concat网络结构,通过特征图的深度复制和算子交换顺序等策略编译可以支持yolov3 tiny、yolov4tiny等网络模型。
[0374]

实现了固定硬件下灵活的模型量化方法,通过将非对称量化的偏移和卷积偏置融合量化使对称量化所对应的硬件结构也能支持非对称量化方式。同时在硬件支持下通过编译器配置向dsp中送入两个输入值进行移位相加来同时计算两个输出值,以此实现编译器对模型可变位宽的量化支持。通过实验表明,非对称量化能提升约6%的精度,同时在不增加dsp资源的情况下int8量化能达到int16量化接近2倍的加速比。
[0375]

分析了特征图和权重进行分块后卷积计算的时间开销,在加速器延迟隐藏的前提下,根据硬件信息设计了优化循环平铺和循环重排的调度策略来提高加速器的计算效率,最后通过优化前后实验的对比调度策略能提升10%-20%的性能。
[0376]
深度学习编译器是顶层模型算法和底层复杂硬件结构之间沟通的桥梁,也是整个ai芯片软件生态中很重要的一个组成部分。目前国内对于专用神经网络加速器的编译研究工作并不多,通过ai编译器的研究,不仅可以扩展对深度学习相关知识的了解,还能够对软硬件协调设计的过程有所帮助。
[0377]
尽管已经示出和描述了本发明的实施例,本领域的普通技术人员可以理解:在不脱离本发明的原理和宗旨的情况下可以对这些实施例进行多种变化、修改、替换和变型,本发明的范围由权利要求及其等同物限定。
再多了解一些

本文用于企业家、创业者技术爱好者查询,结果仅供参考。

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表

相关文献