技术领域
[0001] 本发明属于深度学习编译器领域,尤其涉及一种面向DL编译器的预量化模型部署方法。
相关背景技术
[0002] 深度学习编译器(Deep Learning Compiler,以下简称DL编译器)的出现,重新定义了在各种硬件平台上部署深度学习模型的挑战。DL编译器通常将表示在框架特定表示(framework‑specific representation)中的模型转换为公共中间表示(IR)。然后,DL编译器从图级到张量级逐步降低图。在图级IR中,编译器优化模型的计算图。在张量级IR中,它优化张量算子的循环结构等,并针对不同的硬件做相应的优化。经过连续的优化后,DL编译器能够更有效地处理前端(框架)和后端(硬件平台)的多样性,从而简化了深度学习模型的部署。一般来说,深度学习模型需要大量的计算和内存资源,相关研究已经实现了各种技术(算法、软件和硬件)来减少深度学习模型的计算和内存负担,从而简化其开发和部署过程。
[0003] 在这些技术中,量化是一种被广泛研究的方法。量化使得模型消耗更少的计算和内存资源,同时保持其精度接近未量化的模型。论文(Efficient Execution of Quantized Deep Learning Models:A Compiler Approach)提出了一种QNN(Quantized Neural Network,量化神经网络)作为一个图级IR方言,可以增强深度学习编译器,并支持多种主流深度学习框架。这种方法提供了一种端到端的解决方案,可以读取预量化的模型,并支持各种硬件后端。
[0004] 然而,这种方法并不支持在定制的AI加速器上部署预量化模型,比如多功能张量加速器(Versatile Tensor Accelerator,VTA)等。
具体实施方式
[0036] 为了更好地了解本发明的目的、结构及功能,下面结合附图,对本发明一种面向DL编译器的预量化模型部署方法做进一步详细的描述。
[0037] 本发明适用于预量化模型采用的量化方法符合《Tensorflow lite int8量化规范》的场景。
[0038] 下面的公式1描述了2D量化卷积算子的计算过程。其中QA是量化的输入,QB是量化的权重,QC是量化的输出,scaleA、scaleB、scaleC和zpA、zpB、zpC分别是对于的量化尺度和量化零点,n表示batch,ci表示输入通道,co表示输出通道,h表示输入高度,w表示输入宽度,kh和kw分别表示卷积核高度和宽度。
[0039]
[0040]
[0041] 根据《Tensorflow Lite int8量化规范》,zpB为0,上面的公式演变为下面的公式2。计算过程可以描述为几个步骤:第一步,先进行QA和QB的普通卷积运算,其中QA是经过了零点填充后的输入,这一步运算可映射为带零点填充的图级卷积算子;第二步,减去项2,可映射为图级加法算子;第三步,乘以(scaleA·scaleB)/scaleC,可映射为图级requantize算子;最后加上输出零点zpC,可映射为图级加法算子。其中,项2是常量,可提前计算结果,然后融合到偏置(Bias)常量中去,这样可减少ALU运算。
[0042]
[0043]
[0044] 如图1所示,本发明的一种面向DL编译器的预量化模型部署方法,包括如下步骤:
[0045] 步骤1:前端框架解析器解析前端预量化的深度学习模型,输出包含Q算子的图;Q算子是包含了量化信息的算子,比图级算子高一级,将会在后续流程中被降级为更低级的图级算子;以2D量化卷积算子为例,前端模型的2D量化卷积算子被转换为若干Q算子和图级算子的组合,即Q.conv2d‑>Q.Bias_add‑>Q.Requantize‑>Q.Relu。
[0046] 图4为本发明实施例提供的量化2D卷积算子的计算流图。图中(a)的计算过程是:通过DMA Load指令将输入数据和权重数据从系统内存搬运到加速器的本地Buffer中;执行GEMM计算,在计算前将加速器累加器Buffer清零;通过DMA Load指令将Bias数据从系统内存搬运到加速器的本地Buffer中,并执行GEMM结果与Bias的矢量ALU ADD运算;执行矢量ALU RMUL、矢量ALU RSHR运算和矢量ALU ADD运算,即执行Requantize过程:乘以multiplier(Rounding Multiply)、除以2^shift(Rounding Right Shift)和加上输出零点(Add output zero point);执行矢量ALU MIN/MAX运算,将结果限制在Int8数据范围[‑
128,127]内;最后通过DMA Store指令将结果存储到系统内存中。(b)的计算过程与(a)基本一致,唯一的区别是(b)在执行GEMM运算前,通过DMA Load指令先将Bias搬运到加速器累加器Buffer中,然后在此基础上,进行GEMM的乘累加计算;这样做带来的好处是减少了硬件指令数目,节省了计算开销,即省略了累加器清零的过程与矢量ALU ADD运算过程。
[0047] 步骤2:进行面向Target(即目标平台)的图切分操作,该操作之后,图将被分割为两部分:一部分(即加速器支持的算子)将在加速器上运行,另一部分(即加速器不支持的算子)将在Host(即CPU)端运行。
[0048] 图2为本发明实施例提供的面向Target的图切分模块技术原理图。首先,通过模式匹配,对(a)中的算子进行分组,得到分组后的(b),(b)中的每个三角形都表示匹配的模式,即Q.conv2d+Q.Bias_add+Q.Requantize+Q.Relu。接着,通过Annotation操作给匹配的模式添加Target标记,表示这部分算子将在Target(加速器)上运行;通过Partitioning操作,将添加了Target标记的Group转换为图级表示的函数模块,即(c),并为该函数添加加速器属性,该属性指示DL编译器采用加速器特定的流程来处理该函数。然后,遍历并且修改图,将加速器函数名修改为带有Target(加速器名)的特殊函数名。接下来执行预优化,比如常量折叠等,以满足Graph Packing的要求。最后,执行Graph Packing(两级图变换),将Q算子图转换为普通图级算子图,并执行Layout变换(即Pack和Unpack),使加速器函数模块的输入输出满足加速器硬件要求,即(d)。(d)表示在进入加速器模块前,需要执行Pack操作;在退出加速器模块且进入Host模块前,需要执行Unpack操作;如果两个加速器模块之间只存在Elementwise算子,则不需要额外Pack和Unpack操作。
[0049] 步骤3:DL编译器将针对步骤2这两部分采用不同的调度和代码生成。对于在CPU上运行的算子,DL编译器将采用Host默认流程进行处理,最终生成在CPU上运行的源代码。而在加速器上运行的算子,将通过加速器特定调度进行优化,并采用两级(函数级和算子级)加速器代码生成器来产生加速器硬件指令和驱动函数。
[0050] 图3为本发明实施例提供的Graph Packing模块技术原理图。采用两级(函数级和算子级)变换方式,对图中的加速器子函数和加速器算子执行变换。首先,以加速器不合法图(即输入输出不满足加速器硬件要求的图)作为输入,执行函数级变换,变换过程如下:
[0051] (1)遍历图,搜集所有子函数在图中的位置信息,搜索结果记录在一个列表中,列表的每个元素都是一个元组,元组包含两个参数:子函数名和子函数在图中的位置。该列表可表示如下:
[0052] List[Tuple(sub_func,index),…]
[0053] (2)遍历第(1)步得到的列表,根据加速器函数的特殊函数名,搜集加速器子模块及其在图中的位置信息,记录到加速器子模块列表中,该列表可表示如下:
[0054] List(Tuple(accelerator_func,index))
[0055] (3)遍历加速器子模块列表,确定当前加速器函数是否需要执行Pack和Unpack操作。确定规则是:如果当前加速器子模块是第1个加速器子模块,且前面的算子中存在非element‑wise算子,则需要执行Pack操作;如果当前加速器子模块是最后一个加速器子模块,且后面的算子中存在非element‑wise算子,则需要执行Unpack操作;如果当前加速器子模块和上一个加速器子模块之间存在非element‑wise算子,则需要执行Pack操作;如果当前加速器子模块和下一个加速器子模块之间存在非element‑wise算子,则需要执行Unpack操作。遍历的结果记录在pack列表中,该列表的每个元素均是一个三元元组,即(加速器子模块函数名,是否pack标志,是否unpack标志)。pack列表可表示如下:
[0056] List(Tuple(accelerator_func,pack_flag,unpack_flag),…)
[0057] (4)遍历图,执行图的函数级变换,即把子函数当作普通算子来操作。对于每一个加速器子模块,如果其在pack列表中的pack标志为True,则对其输入执行Pack操作;如果unpack标志为True,则对其输出执行Unpack操作。经过一轮遍历,所有加速器子模块的输入输出都是合法的,即符合了加速器硬件要求。这一步输出的图即为加速器合法图。
[0058] 然后,以加速器合法图作为输入,执行算子级图变换,对Q算子进行降级,以方便后续处理。遍历图,如果当前算子是加速器子模块,则进一步遍历其中的每个算子,执行算子级变换,具体处理步骤如下:
[0059] (1)处理Q.conv2d算子。如果当前算子为Q.conv2d,则将其替换为Conv2d_zp算子,该算子将Input零点作为Padding值,执行Padding后的Input和Weight的卷积运算。Conv2d_zp算子经过后续加速器特定优化过程降级为加速器GEMM Loop、Load和Store操作。
[0060] (2)处理Q.Bias_add算子。如果当前算子为Q.Bias_add,则将其替换为Bias_add_zp算子,该算子将经过了零点融合的Bias常量与Conv2d_zp的输出结果相加,这里的Bias常量为Qfused_bias=Qbias‑∑zpin*Qwgt。Conv2d_zp算子经过后续加速器特定调度转换为加速器矢量ALU ADD和Load操作。
[0061] (3)处理Q.Requantize算子。如果当前算子为Q.Requantize,则将其替换为带Rounding的量化乘法算子Round_multiply、带Rounding的移位除法算子Round_rshift和加输出零点的Add算子的组合。Round_multiply和Round_rshift算子经过后续加速器特定调度转换为加速器矢量ALU RMUL和RSHR操作,Add算子经过后续加速器特定调度转换为加速器矢量ALU ADD操作。
[0062] (4)处理Q.Relu算子。如果当前算子为Q.Relu,则将其替换为min算子和max算子的组合,即将结果限制在[‑128,127]范围内。min和max算子经过后续加速器特定调度转换为加速器矢量ALU MIN和MAX算子。
[0063] 图5为本发明实施例提供的加速器两级代码生成器的工作示意图。首先,加速器函数级代码生成器配合Host代码生成器,产生C语言版的算子执行控制流和加速器驱动代码;其中,加速器函数级代码生成器遍历图中的每个加速器子函数,为其产生独立的加速器驱动函数,该驱动函数完成两项工作:初始化加速器和启动加速器计算内核,加速器内核按照顺序执行指令数组中的硬件指令。然后,加速器算子级生成器遍历加速器子函数中的每个算子,根据图的连接关系和参数信息,产生Python版的算子执行控制流,并通过Pybind11模块调用加速器Runtime,产生加速器硬件指令数据,并将数据保存到C语言数组中;其中,py2cpp是一个绑定了加速器C或C++Runtime API的Python包,由Pybind11模块生成,可实现在Python中调用C或C++函数,其绑定关系如图6所示。
[0064] 可以理解,本发明是通过一些实施例进行描述的,本领域技术人员知悉的,在不脱离本发明的精神和范围的情况下,可以对这些特征和实施例进行各种改变或等效替换。另外,在本发明的教导下,可以对这些特征和实施例进行修改以适应具体的情况及材料而不会脱离本发明的精神和范围。因此,本发明不受此处所公开的具体实施例的限制,所有落入本申请的权利要求范围内的实施例都属于本发明所保护的范围内。