model-deployment: sync from overleaf (#422)

Co-authored-by: hangangqiang <hangangqiang2@huawei.com>
Co-authored-by: Tanzhipeng <Rudysheeppig@users.noreply.github.com>
Co-authored-by: Jiarong Han <jiaronghan@outlook.com>
This commit is contained in:
hangangqiang
2023-03-24 17:53:45 +08:00
committed by GitHub
parent fb9f14f6f7
commit fcdd97397b
7 changed files with 77 additions and 70 deletions

View File

@@ -10,7 +10,7 @@
#### 前处理
前处理主要完成数据预处理,在现实问题中,我们得到的原始数据往往非常混乱机器学习模型无法识别并从中提取信息。数据预处理的目的是将原始数据例如图片、语音、文本等处理成适合网络输入的tensor数据并消除其中无关的信息恢复有用的真实信息增强有关信息的可检测性最大限度地简化数据从而改进模型的特征抽取、图像分割、匹配和识别等可靠性。
前处理主要完成数据预处理在现实问题中原始数据往往非常混乱机器学习模型无法识别并从中提取信息。数据预处理的目的是将原始数据例如图片、语音、文本等处理成适合网络输入的tensor数据并消除其中无关的信息恢复有用的真实信息增强有关信息的可检测性最大限度地简化数据从而改进模型的特征抽取、图像分割、匹配和识别等可靠性。
常见的数据预处理手段有:
@@ -43,18 +43,17 @@
为方便算子并行计算,同时避免频繁创建销毁线程的开销,推理框架一般会使用线程池机制。业界有两种较为通用的做法:
- 使用OpenMp编程接口OpenMPOpen Multi-Processing)是一套支持跨平台共享内存方式的多线程并发的编程API如算子并行最常用的接口\"parallel for\"实现for循环体的代码被多线程并行执行。
- 推理框架实现针对算子并行计算的线程池相对OpenMp提供的接口会更有针对性,性能会更高,且更轻量。
- 使用OpenMP编程接口OpenMPOpen Multi-Processing一套支持跨平台共享内存方式的多线程并发的编程API)提供如算子并行最常用的接口\"parallel for\"实现for循环体的代码被多线程并行执行。
- 推理框架实现针对算子并行计算的线程池相对OpenMP提供的接口会更有针对性,性能会更高,且更轻量。
### 算子优化
:label:`ch08-sec-kernel_optimization`
在部署AI模型时我们期望模型执行训练或推理的时间尽可能地短,以获得更优越的性能。对于一个固定的深度学习网络,框架调度的时间占比往往很小,性能的瓶颈就在算子的执行。下面从硬件指令和算法角度介绍一些算子优化的方法。
在部署AI模型时用户通常期望模型执行训练或推理的时间尽可能地短,以获得更优越的性能。对于深度学习网络,框架调度的时间占比往往很小,性能的瓶颈就在算子的执行。下面从硬件指令和算法角度介绍一些算子优化的方法。
#### 硬件指令优化
绝大多数的设备上都有CPU因此算子在CPU上的时间尤为重要下面介绍一下在ARM
CPU硬件指令优化的方法。
绝大多数的设备上都有CPU因此算子在CPU上的时间尤为重要下面介绍一下在ARM CPU硬件指令优化的方法。
1\. 汇编语言
@@ -72,7 +71,7 @@ ARMv8系列的CPU上有32个NEON寄存器v0-v31如 :numref:`ch08-fig-register
:width:`500px`
:label:`ch08-fig-register`
针对该处理器可以采用SIMD(Single InstructionMultiple Data单指令、多数据)提升数据存取计算的速度。相比于单数据操作指令NEON指令可以一次性操作NEON寄存器的多个数据。例如对于浮点数的fmla指令用法为fmla v0.4s, v1.4s, v2.4s,如 :numref:`ch08-fig-fmla`所示用于将v1和v2两个寄存器中相对应的float值相乘累加到v0的值上。
针对该处理器可以采用SIMD(Single InstructionMultiple Data单指令、多数据)提升数据存取计算的速度。相比于单数据操作指令NEON指令可以一次性操作NEON寄存器的多个数据。例如对于浮点数的fmla指令用法为fmla v0.4s, v1.4s, v2.4s,如 :numref:`ch08-fig-fmla`所示用于将v1和v2两个寄存器中相对应的float值相乘累加到v0的值上。
![fmla指令计算功能](../img/ch08/fmla.png)
:width:`600px`
@@ -80,7 +79,7 @@ ARMv8系列的CPU上有32个NEON寄存器v0-v31如 :numref:`ch08-fig-register
3\. 汇编语言优化
对于已知功能的汇编语言程序来说,计算类指令通常是固定的,性能的瓶颈就在非计算指令上。如 :numref:`ch08-fig-storage`所示计算机各存储设备类似于一个金字塔结构最顶层空间最小但是速度最快最底层速度最慢但是空间最大。L1-L3统称为cache(高速缓冲存储器)CPU访问数据时会首先访问位于CPU内部的cache没找到再访问CPU之外的主存此时引入了缓存命中率的概念来描述在cache中完成数据存取的占比。要想提升程序的性能缓存命中率要尽可能的高。
对于已知功能的汇编语言程序来说,计算类指令通常是固定的,性能的瓶颈就在非计算指令上。如 :numref:`ch08-fig-storage`所示计算机各存储设备类似于一个金字塔结构最顶层空间最小但是速度最快最底层速度最慢但是空间最大。L1-L3统称为cache(高速缓冲存储器)CPU访问数据时会首先访问位于CPU内部的cache没找到再访问CPU之外的主存此时引入了缓存命中率的概念来描述在cache中完成数据存取的占比。要想提升程序的性能缓存命中率要尽可能的高。
下面简单列举一些提升缓存命中率、优化汇编性能的手段:
@@ -97,23 +96,24 @@ ARMv8系列的CPU上有32个NEON寄存器v0-v31如 :numref:`ch08-fig-register
#### 算法优化
多数AI模型的推理时间主要耗费在卷积、矩阵乘算子的计算上占到了整网百分之九十甚至更多的时间。本小节主要介绍卷积算子算法方面的优化手段可以应用到各种硬件设备上。
卷积的计算可以转换为两个矩阵相乘在前述5.3.3小节中已经详细介绍了矩阵乘GEMM运算的优化。对于不同的硬件确定合适的矩阵分块优化数据访存与指令并行可以最大限度的发挥硬件的算力提升推理性能。
卷积的计算可以转换为两个矩阵相乘,在前述 :numref:`ch08-sec-parallel_inference`小节中,已经详细介绍了矩阵乘运算的优化。对于不同的硬件,确定合适的矩阵分块,优化数据访存与指令并行,可以最大限度的发挥硬件的算力,提升推理性能。
1.Img2col
将卷积的计算转换为矩阵乘一般采用Img2col的方法实现。在常见的神经网络中卷积的输入通常都是4维的默认采用的数据排布方式为NHWC如 :numref:`ch08-fig-conv_nhwc`所示是一个卷积示意图。输入维度为1,IH,IW,IC卷积核维度为OC,KH,KW,IC输出维度为1,OH,OW,OC
将卷积的计算转换为矩阵乘一般采用Img2col的方法实现。在常见的神经网络中卷积的输入通常都是4维的默认采用的数据排布方式为NHWC :numref:`ch08-fig-conv_nhwc`所示是一个卷积示意图。输入维度为1,IH,IW,IC卷积核维度为OC,KH,KW,IC输出维度为1,OH,OW,OC
![通用卷积示意图](../img/ch08/conv_nhwc.png)
:width:`800px`
:label:`ch08-fig-conv_nhwc`
对卷积的Img2col规则如下。如 :numref:`ch08-fig-img2col_input`所示对该输入做重排得到的矩阵见右侧行数对应输出的OH\*OW的个数每个行向量里,先排列计算个输出点需要输入上第一个通道的KH\*KW个数据按次序排列之后的通道直到通道IC
对卷积的Img2col规则如下。如 :numref:`ch08-fig-img2col_input`所示对该输入做重排得到的矩阵见右侧行数对应输出的OH*OW的个数对于每个行向量,就对应到一个输出点。计算个输出点需要IC*KH*KW个输入数据Img2col先排列第一个输入通道的KH*KW个输入数据,再排列第二个输入通道的数据直到第IC个通道
![输入Img2col的矩阵](../img/ch08/img2col_input.png)
:width:`800px`
:label:`ch08-fig-img2col_input`
如 :numref:`ch08-fig-img2col_weight`所示对权重数据做重排。将1个卷积核展开为权重矩阵的一列因此共有OC列每个列向量上先排列第一个输入通道上KH\*KW的数据再依次排列后面的通道直到IC。通过重排卷积的计算就可以转换为两个矩阵相乘的求解。在实际实现时Img2col和GEMM的数据重排会同时进行以节省运行时间。
如 :numref:`ch08-fig-img2col_weight`所示对权重数据做重排。将1个卷积核展开为权重矩阵的一列因此共有OC列每个列向量上先排列第一个输入通道上KH*KW的数据再依次排列后面的通道直到IC。通过重排卷积的计算就可以转换为两个矩阵相乘的求解。在实际实现时Img2col和GEMM的数据重排会同时进行以节省运行时间。
![卷积核Img2col的矩阵](../img/ch08/img2col_weight.png)
:width:`600px`
@@ -121,14 +121,14 @@ ARMv8系列的CPU上有32个NEON寄存器v0-v31如 :numref:`ch08-fig-register
2.Winograd算法
卷积计算归根到底是矩阵乘法,两个二维矩阵相乘的时间复杂度是$O(n^3)$。我们可以使用Winograd降低矩阵乘法的复杂度。
卷积计算归根到底是矩阵乘法,两个二维矩阵相乘的时间复杂度是$O(n^3)$。Winograd算法可以降低矩阵乘法的复杂度。
以一维卷积运算为例,记为F(mr),其中,m代表输出的个数,r为卷积核的个数。输入为$d=[d_0 \ d_1 \ d_2 \ d_3]$,卷积核为$g=[g_0 \ g_1 \ g_2]^T$,该卷积计算可以写成矩阵形式如式 :eqref:`ch08-equ-conv_matmul_one_dimension`所示需要6次乘法和4次加法。
以一维卷积运算为例,记为\textit{\textbf{F}}($m$$r$),其中,$m$代表输出的个数,$r$为卷积核的个数。输入为$\textit{\textbf{d}}=[d_0 \ d_1 \ d_2 \ d_3]$,卷积核为$g=[g_0 \ g_1 \ g_2]^{\rm T}$,该卷积计算可以写成矩阵形式如式 :eqref:`ch08-equ-conv_matmul_one_dimension`所示需要6次乘法和4次加法。
$$F(2, 3)=\left[ \begin{matrix} d_0 & d_1 & d_2 \\ d_1 & d_2 & d_3 \end{matrix} \right] \left[ \begin{matrix} g_0 \\ g_1 \\ g_2 \end{matrix} \right]=\left[ \begin{matrix} y_0 \\ y_1 \end{matrix} \right]$$
:eqlabel:`ch08-equ-conv_matmul_one_dimension`
可以观察到,卷积运算转换为矩阵乘法时输入矩阵中存在着重复元素$d_1$和$d_2$,因此,卷积转换的矩阵乘法相对一般的矩阵乘有了优化空间。可以通过计算中间变量$m_0-m_3$得到矩阵乘的结果,见式 :eqref:`ch08-equ-conv-2-winograd`
可以观察到,卷积运算转换为矩阵乘法时输入矩阵中存在着重复元素$d_1$和$d_2$,因此,卷积转换的矩阵乘法相对一般的矩阵乘有了优化空间。可以通过计算中间变量$m_0-m_3$得到矩阵乘的结果,见式 :eqref:`ch08-equ-conv-2-winograd`
$$F(2, 3)=\left[ \begin{matrix} d_0 & d_1 & d_2 \\ d_1 & d_2 & d_3 \end{matrix} \right] \left[ \begin{matrix} g_0 \\ g_1 \\ g_2 \end{matrix} \right]=\left[ \begin{matrix} m_0+m_1+m_2 \\ m_1-m_2+m_3 \end{matrix} \right]$$
:eqlabel:`ch08-equ-conv-2-winograd`
@@ -138,9 +138,9 @@ $$F(2, 3)=\left[ \begin{matrix} d_0 & d_1 & d_2 \\ d_1 & d_2 & d_3 \end{matrix}
$$\begin{aligned}m_0=(d_0-d_2)*g_0 \\m_1=(d_1+d_2)*(\frac{g_0+g_1+g_2}{2}) \\m_2=(d_0-d_2)*(\frac{g_0-g_1+g_2}{2}) \\m_2=(d_1-d_3)*g_2\end{aligned}$$
:eqlabel:`ch08-equ-winograd-param`
通过$m_0-m_3$间接计算r1r2需要的运算次数包括输入d的4次加法输出m的4次乘法和4次加法。在推理阶段权重的数值是常量因此卷积核上的运算可以在图编译阶段计算不计入在线的run时间。所以总的运算次数为4次乘法和8次加法与直接运算的6次乘法和4次加法相比乘法次数减少加法次数增加。在计算机中乘法一般比加法慢通过减少乘法次数增加少量加法可以实现加速。
通过$m_0-m_3$间接计算r1r2需要的运算次数包括输入$d$的4次加法输出$m$的4次乘法和4次加法。在推理阶段权重的数值是常量因此卷积核上的运算可以在图编译阶段计算不计入在线的run时间。所以总的运算次数为4次乘法和8次加法与直接运算的6次乘法和4次加法相比乘法次数减少加法次数增加。在计算机中乘法一般比加法慢通过减少乘法次数增加少量加法可以实现加速。
计算过程写成矩阵形式如式 :eqref:`ch08-equ-winograd-matrix`所示其中⊙为对应位置相乘A、B、G都是常量矩阵。这里写成矩阵计算是为了表达清晰实际使用时按照公式 :eqref:`ch08-equ-winograd-param`手写展开的计算速度更快。
计算过程写成矩阵形式如式 :eqref:`ch08-equ-winograd-matrix`所示其中⊙为对应位置相乘A、B、G都是常量矩阵。这里写成矩阵计算是为了表达清晰实际使用时按照公式 :eqref:`ch08-equ-winograd-param`手写展开的计算速度更快。
$$\mathbf{Y}=\mathbf{A^T}(\mathbf{G}g)*(\mathbf{B^T}d)$$
@@ -167,6 +167,6 @@ Winograd算法的整个计算过程在逻辑上可以分为4步如 :numref:`c
:width:`500px`
:label:`ch08-fig-winograd`
针对任意的输出大小,要使用F(2x23x3)的Winograd算法需要将输出切分成2x2的块找到对应的输入按照上述的四个步骤就可以求出对应的输出值。当然Winograd算法并不局限于求解F(2x23x3)针对任意的F(m\*mr\*r)都可以找到适当的常量矩阵A、B、G,通过间接计算的方式减少乘法次数。但是随着m、r的增大输入、输出涉及的加法以及常量权重的乘法次数都在增加那么乘法次数带来的计算量下降会被加法和常量乘法所抵消。因此在实际使用场景中还需要根据Winograd的实际收益来选择。
针对任意的输出大小,要使用\textit{\textbf{F}}(2$\times$23$\times$3)的Winograd算法需要将输出切分成2$\times$2的块找到对应的输入按照上述的四个步骤就可以求出对应的输出值。当然Winograd算法并不局限于求解\textit{\textbf{F}}(2$\times$23$\times$3),针对任意的\textit{\textbf{F}}($m$$\times$$m$$r$$\times$$r$),都可以找到适当的常量矩阵\textit{\textbf{A}}、\textit{\textbf{B}}、\textit{\textbf{G}},通过间接计算的方式减少乘法次数。但是随着$m$、$r$的增大输入、输出涉及的加法以及常量权重的乘法次数都在增加那么乘法次数带来的计算量下降会被加法和常量乘法所抵消。因此在实际使用场景中还需要根据Winograd的实际收益来选择。
本小节主要介绍了模型推理时的数据处理和性能优化手段。选择合适的数据处理方法,可以更好地提取输入特征,处理输出结果。并行计算以及算子级别的硬件指令与算法优化可以最大限度的发挥硬件的算力。除此之后,内存的占用及访问速率也是影响推理性能的重要因素,因此推理时需要设计合理的内存复用策略,关于内存复用的策略我们在编译器后端章节已经做了阐述。
本小节主要介绍了模型推理时的数据处理和性能优化手段。选择合适的数据处理方法,可以更好地提取输入特征,处理输出结果。并行计算以及算子级别的硬件指令与算法优化可以最大限度的发挥硬件的算力。除此之后,内存的占用及访问速率也是影响推理性能的重要因素,因此推理时需要设计合理的内存复用策略,内存复用的策略已经在编译器后端章节已经做了阐述。