Fix picture and eference of chapter frontend and ir

This commit is contained in:
LiangZhibo
2022-03-11 21:53:59 +08:00
parent 5df16006a4
commit 133e3d312b
6 changed files with 183 additions and 43 deletions

View File

@@ -6,16 +6,16 @@
### 自动微分的基本概念
自动微分Automatic
DifferentiationAD是一种对计算机程序进行高效且准确求导的技术在上个世纪六七十年代就已经被广泛应用于流体力学、天文学、数学金融等领域[@10.5555/1455489]。时至今日,自动微分的实现及其理论仍然是一个活跃的研究领域。随着近些年深度学习在越来越多的机器学习任务上取得领先成果[@lecun2015deep]自动微分被广泛的应用于机器学习领域。许多机器学习模型使用的优化算法都需要获取模型的导数因此自动微分技术成为了一些热门的机器学习框架例如TensorFlow和PyTorch的核心特性。
DifferentiationAD是一种对计算机程序进行高效且准确求导的技术在上个世纪六七十年代就已经被广泛应用于流体力学、天文学、数学金融等领域 :cite:`10.5555/1455489`。时至今日自动微分的实现及其理论仍然是一个活跃的研究领域。随着近些年深度学习在越来越多的机器学习任务上取得领先成果自动微分被广泛的应用于机器学习领域。许多机器学习模型使用的优化算法都需要获取模型的导数因此自动微分技术成为了一些热门的机器学习框架例如TensorFlow和PyTorch的核心特性。
常见的计算机程序求导的方法可以归纳为以下四种[@2015Automatic]手工微分Manual
常见的计算机程序求导的方法可以归纳为以下四种 :cite:`2015Automatic`手工微分Manual
Differentiation、数值微分Numerical
Differentiation、符号微分Symbolic
Differentiation和自动微分Automatic Differentiation
1手工微分需手工求解函数导数的表达式并在程序运行时根据输入的数值直接计算结果。手工微分需根据函数的变化重新推导表达式工作量大且容易出错。
2数值微分[@2015Numerical]:数值微分通过差分近似方法完成,其本质是根据导数的定义推导而来。
2数值微分 :cite:`2015Numerical`:数值微分通过差分近似方法完成,其本质是根据导数的定义推导而来。
$$f^{'}(x)=\lim_{h \to 0}\frac{f(x+h)-f(x)}{h}$$
@@ -24,12 +24,12 @@ error。理论上数值微分中的截断误差与步长$h$有关,$h$越
Error。舍入误差会随着$h$变小而逐渐增大。当h较大时截断误差占主导。而当h较小时舍入误差占主导。
在截断误差和舍入误差的共同作用下,数值微分的精度将会在某一个$h$值处达到最小值,并不会无限的减小。因此,虽然数值微分容易实现,但是存在精度误差问题。
3符号微分[@2003Computer]:利用计算机程序自动地通过如下的数学规则对函数表达式进行递归变换来完成求导。
3符号微分 :cite:`2003Computer`:利用计算机程序自动地通过如下的数学规则对函数表达式进行递归变换来完成求导。
$$\frac{d}{dx}(f(x)+g(x))\rightsquigarrow\frac{d}{dx}f(x)+\frac{d}{dx}g(x)$$
$$\frac{d}{dx}(f(x)g(x))\rightsquigarrow(\frac{d}{dx}f(x))g(x)+f(x)(\frac{d}{dx}g(x))$$
符号微分常被应用于现代代数系统工具中例如Mathematica、Maxima和Maple以及机器学习框架如Theano。符号微分虽然消除了手工微分硬编码的缺陷。但因为对表达式进行严格的递归变换和展开不复用产生的变换结果很容易产生表达式膨胀expression
swell[@10.5555/60181.60188])问题。如:numref:`symbolic_differentiation`所示,用符号微分计算递归表达式$l_{n+1}=4l_n(1-l_n)$$l_1=x$的导数表达式,其结果随着迭代次数增加快速膨胀。
swell :cite:`10.5555/60181.60188` )问题。如 :numref:`symbolic_differentiation` 所示,用符号微分计算递归表达式$l_{n+1}=4l_n(1-l_n)$$l_1=x$的导数表达式,其结果随着迭代次数增加快速膨胀。
![符号微分的表达式膨胀问题](../img/ch04/符号微分的表达式膨胀问题.png)
:width:`800px`
@@ -37,7 +37,7 @@ swell[@10.5555/60181.60188]))问题。如图:numref:`symbolic_differentia
并且符号微分需要表达式被定义成闭合式的closed-form不能带有或者严格限制控制流的语句表达使用符号微分会很大程度上地限制了机器学习框架网络的设计与表达。
4自动微分[@2000An]:自动微分的思想是将计算机程序中的运算操作分解为一个有限的基本操作集合,且集合中基本操作的求导规则均为已知,在完成每一个基本操作的求导后,使用链式法则将结果组合得到整体程序的求导结果。自动微分是一种介于数值微分和符号微分之间的求导方法,结合了数值微分和符号微分的思想。相比于数值微分,自动微分可以精确地计算函数的导数;相比符号微分,自动微分将程序分解为基本表达式的组合,仅对基本表达式应用符号微分规则,并复用每一个基本表达式的求导结果,从而避免了符号微分中的表达式膨胀问题。而且自动微分可以处理分支、循环和递归等控制流语句。目前的深度学习框架基本都采用自动微分机制进行求导运算,下面我们将重点介绍自动微分机制以及自动微分的实现。
4自动微分 :cite:`2000An`:自动微分的思想是将计算机程序中的运算操作分解为一个有限的基本操作集合,且集合中基本操作的求导规则均为已知,在完成每一个基本操作的求导后,使用链式法则将结果组合得到整体程序的求导结果。自动微分是一种介于数值微分和符号微分之间的求导方法,结合了数值微分和符号微分的思想。相比于数值微分,自动微分可以精确地计算函数的导数;相比符号微分,自动微分将程序分解为基本表达式的组合,仅对基本表达式应用符号微分规则,并复用每一个基本表达式的求导结果,从而避免了符号微分中的表达式膨胀问题。而且自动微分可以处理分支、循环和递归等控制流语句。目前的深度学习框架基本都采用自动微分机制进行求导运算,下面我们将重点介绍自动微分机制以及自动微分的实现。
### 前向与反向自动微分
@@ -52,7 +52,7 @@ $$\frac{dy}{dx}=(((\frac{dy}{da}\frac{da}{db})\frac{db}{dc})\frac{dc}{dx})$$
我们以下面的函数为例介绍两种模式的计算方式,我们希望计算函数在$(x_1, x_2)=(2,5)$处的导数$\frac{\partial y}{\partial x_1}$
$$y=f(x_1,x_2)=ln(x_1)+{x_1}{x_2}-sin(x_2)$$
该函数对应的计算图如:numref:`example_compute_graph`
该函数对应的计算图如 :numref:`example_compute_graph`
![示例计算图](../img/ch04/自动微分-示例计算图.svg)
:width:`800px`
@@ -64,7 +64,7 @@ $$y=f(x_1,x_2)=ln(x_1)+{x_1}{x_2}-sin(x_2)$$
:width:`800px`
:label:`forward_AD`
前向模式的计算过程如:numref:`forward_AD`所示,左侧是源程序分解后得到的基本操作集合,右侧展示了运用链式法则和已知的求导规则,从上至下计算每一个中间变量${\dot{v}_i}=\frac{\partial v_i}{\partial x_1}$,从而计算出最后的变量${\dot{v}_5}=\frac{\partial y}{\partial x_1}$。
前向模式的计算过程如 :numref:`forward_AD`所示,左侧是源程序分解后得到的基本操作集合,右侧展示了运用链式法则和已知的求导规则,从上至下计算每一个中间变量${\dot{v}_i}=\frac{\partial v_i}{\partial x_1}$,从而计算出最后的变量${\dot{v}_5}=\frac{\partial y}{\partial x_1}$。
当我们想要对一个函数求导时,我们想要得到的是该函数的任意一个输出对任意一个输入的偏微分的集合。对于一个带有$n$个独立输入$x_i$和$m$个独立输出$y_i$的函数$f:{\mathbf{R}^n}\to \mathbf{R}^m$该函数的求导结果可以构成如下的雅克比矩阵Jacobian
Matrix $$\mathbf{J}_{f}=
@@ -101,7 +101,7 @@ $$\mathbf{J}_{f}\mathbf{r}=
:width:`800px`
:label:`backward_AD`
反向模式的计算过程如上:numref:`backward_AD`所示,左侧是源程序分解后得到的基本操作集合,右侧展示了运用链式法则和已知的求导规则,从$\bar{v}_5=\bar{y}=\frac{\partial y}{\partial y}=1$开始,
反向模式的计算过程如上 :numref:`backward_AD`所示,左侧是源程序分解后得到的基本操作集合,右侧展示了运用链式法则和已知的求导规则,从$\bar{v}_5=\bar{y}=\frac{\partial y}{\partial y}=1$开始,
由下至上地计算每一个中间变量${\bar{v}_i}=\frac{\partial y_j}{\partial v_i}$,从而计算出最后的变量${\bar{x}_1}=\frac{\partial y}{\partial x_1}$和${\bar{x}_2}=\frac{\partial y}{\partial x_2}$。
反向模式每次计算的是函数$f$的某一个输出对任一输入的偏微分也就是雅克比矩阵的某一行如下面的向量所示。因此通过运行m次反向模式自动微分我们就可以得到整个雅克比矩阵。
@@ -125,11 +125,11 @@ $$\mathbf{r}^{T}\mathbf{J}_{f}=
但是反向模式也存在一定的缺陷。在源程序分解为一系列基本操作后前向模式由于求导顺序与基本操作的执行顺序一致输入值可以在执行基本操作的过程中同步获得。而在反向模式中由于求导顺序与源程序的执行顺序是相反的计算过程需要分为两个阶段第一个阶段先执行源程序且将源程序的中间结果保存起来在第二阶段才把中间结果取出来去计算导数。因此反向模式会有额外的内存消耗。业界也一直在研究反向模式的内存占用优化方法例如检查点策略checkpointing
strategies和数据流分析data-flow
analysis[@2006The];[@2017Divide]
analysis :cite:`2006The,2017Divide`
### 自动微分的实现
上一节我们介绍了自动微分的基本概念,可以总结为将程序分解为一系列微分规则已知的基本操作,然后运用链式法则将它们的微分结果组合起来得到程序的微分结果。而在机器学习的应用中,因为输入的数量远远大于输出的数量,所以反向模式的自动微分更受青睐。虽然自动微分的基本思想是明确的,但是具体的实现方法也分为几类[@2015Automatic]大体可以划分为基本表达式法Elemental
上一节我们介绍了自动微分的基本概念,可以总结为将程序分解为一系列微分规则已知的基本操作,然后运用链式法则将它们的微分结果组合起来得到程序的微分结果。而在机器学习的应用中,因为输入的数量远远大于输出的数量,所以反向模式的自动微分更受青睐。虽然自动微分的基本思想是明确的,但是具体的实现方法也分为几类 :cite:`2015Automatic` 大体可以划分为基本表达式法Elemental
Libraries、操作符重载法Operator
OverloadingOO和代码变换法Source Code TransformationST
@@ -201,7 +201,7 @@ OO对程序的运行跟踪经过了函数调用和控制流因此实现起来
3代码变换法Source
TransformationST提供对编程语言的扩展分析程序的源码或抽象语法树AST将程序自动地分解为一系列可微分的基本操作而这些基本操作的微分规则已预定义好最后使用链式法则对基本操作的微分表达式进行组合生成新的程序表达来完成微分。TensorFlowMindSpore等机器学习框架都采用了该方式。
不同于OO在编程语言内部操作ST需要语法分析器parser和操作中间表示的工具。除此以外ST需要定义对函数调用和控制流语句如循环和条件等的转换规则。其优势在于对每一个程序自动微分的转换只做一次因此不会造成运行时的额外性能损耗。而且因为整个微分程序在编译时就能获得编译器可以对微分程序进行进一步的编译优化。但ST实现起来更加复杂需要扩展语言的预处理器、编译器或解释器且需要支持更多的数据类型和操作需要更强的类型检查系统。另外虽然ST不需要在运行时做自动微分的转换但是对于反向模式在反向部分执行时仍然需要确保前向执行的一部分中间变量可以被获取到有两种方式可以解决该问题[@van2018Automatic]
不同于OO在编程语言内部操作ST需要语法分析器parser和操作中间表示的工具。除此以外ST需要定义对函数调用和控制流语句如循环和条件等的转换规则。其优势在于对每一个程序自动微分的转换只做一次因此不会造成运行时的额外性能损耗。而且因为整个微分程序在编译时就能获得编译器可以对微分程序进行进一步的编译优化。但ST实现起来更加复杂需要扩展语言的预处理器、编译器或解释器且需要支持更多的数据类型和操作需要更强的类型检查系统。另外虽然ST不需要在运行时做自动微分的转换但是对于反向模式在反向部分执行时仍然需要确保前向执行的一部分中间变量可以被获取到有两种方式可以解决该问题 :cite:`van2018Automatic`
1基于Tape的方式。该方式使用一个全局的"tape"去确保中间变量可以被获取到。原始函数被扩展为在前向部分执行时把中间变量写入到tape中的函数在程序执行反向部分时会从tape中读取这些中间变量。除了存储中间变量外OO中的tape还会存储执行的操作类型。然而因为tape是一个在运行时构造的数据结构所以需要添加一些定制化的编译器优化方法。且为了支持高阶微分对于tape的读写都需要是可微分的。而大多数基于tape的工具都没有实现对tape的读写操作的微分因此它们都不支持多次嵌套执行反向模式的自动微分reverse-over-reverse。机器学习框架Tangent采用了该方式。

View File

@@ -19,7 +19,7 @@
1\. 无用与不可达代码消除
:numref:`pass_useless_code_elimination`所示。无用代码是指输出结果没有被任何其他代码所使用的代码。不可达代码是指没有有效的控制流路径包含该代码。删除无用或不可达的代码可以使得中间表示更小,提高程序的编译与执行速度。无用与不可达代码一方面有可能来自于程序编写者的编写失误,也有可能是其他编译优化所产生的结果。
如 :numref:`pass_useless_code_elimination`所示。无用代码是指输出结果没有被任何其他代码所使用的代码。不可达代码是指没有有效的控制流路径包含该代码。删除无用或不可达的代码可以使得中间表示更小,提高程序的编译与执行速度。无用与不可达代码一方面有可能来自于程序编写者的编写失误,也有可能是其他编译优化所产生的结果。
![无用代码消除](../img/ch04/编译优化-无用代码消除.svg)
:width:`600px`
@@ -27,9 +27,9 @@
2\. 常量传播、常量折叠
常量传播:如 :numref:`pass_constant_broadcast`所示,如果某些量为已知值的常量,那么可以在编译时刻将使用这些量的地方进行替换。
常量传播:如 :numref:`pass_constant_broadcast`所示,如果某些量为已知值的常量,那么可以在编译时刻将使用这些量的地方进行替换。
常量折叠:如 :numref:`pass_constant_broadcast`所示,多个量进行计算时,如果能够在编译时刻直接计算出其结果,那么变量将由常量替换。
常量折叠:如 :numref:`pass_constant_broadcast`所示,多个量进行计算时,如果能够在编译时刻直接计算出其结果,那么变量将由常量替换。
![常量传播与常量折叠](../img/ch04/编译优化-常量传播与常量折叠.svg)
:width:`600px`
@@ -37,7 +37,7 @@
3\. 公共子表达式消除
:numref:`pass_CSE`所示如果一个表达式E已经计算过了并且从先前的计算到现在E中所有变量的值都没有发生变化那么E就成为了公共子表达式。对于这种表达式没有必要花时间再对它进行计算只需要直接用前面计算过的表达式结果代替E就可以了。
如 :numref:`pass_CSE`所示如果一个表达式E已经计算过了并且从先前的计算到现在E中所有变量的值都没有发生变化那么E就成为了公共子表达式。对于这种表达式没有必要花时间再对它进行计算只需要直接用前面计算过的表达式结果代替E就可以了。
![公共子表达式消除](../img/ch04/编译优化-公共子表达式消除.svg)
:width:`600px`

View File

@@ -2,7 +2,7 @@
在上一章节中我们详细讨论了计算图的生成和调度在进阶部分的介绍中简单介绍了深度学习编译器的作用。定义深度学习模型、计算图使用系统为用户提供的高级编程API我们将用户使用高级编程API编写的程序称为源程序将与硬件相关的程序称为目标程序深度学习编译器需要理解输入的源程序并将其映射到目标机。为了实现这两项任务编译器的设计被分解为两个主要部分前端和后端。传统编译器的前端专注于理解源程序后端则专注于将功能映射到目标机。为了将前后端相连接我们需要一种结构来表示转换后的源代码这就是中间表示Intermediate
Representation, IR
:numref:`compiler_frontend_structure`展示了机器学习编译器的前端的流程。其中,对源程序的解析过程与传统编译器是大致相同的,本章节不对这部分进行更细致的讨论。机器学习框架的编译器前端的独特之处主要在于自动微分功能的支持。为了满足自动微分功能带来的新需求,机器学习框架需要在传统中间表示的基础上设计新的中间表示结构。因此,本章节的介绍重点会放在中间表示以及自动微分这两个部分。最后,我们会简要探讨类型系统,静态分析和前端优化等编译器基础概念。
:numref:`compiler_frontend_structure`展示了机器学习编译器的前端的流程。其中,对源程序的解析过程与传统编译器是大致相同的,本章节不对这部分进行更细致的讨论。机器学习框架的编译器前端的独特之处主要在于自动微分功能的支持。为了满足自动微分功能带来的新需求,机器学习框架需要在传统中间表示的基础上设计新的中间表示结构。因此,本章节的介绍重点会放在中间表示以及自动微分这两个部分。最后,我们会简要探讨类型系统,静态分析和前端优化等编译器基础概念。
![编译器前端基础结构](../img/ch04/编译器前端基础架构.svg)
:width:`1000px`

View File

@@ -8,7 +8,7 @@
中间表示(IR),是编译器用于表示源代码的数据结构或代码,是程序编译过程中介于源语言和目标语言之间的程序表示。几乎所有的编译器都需要某种形式的中间表示,来对被分析、转换和优化的代码进行建模。在编译过程中,中间表示必须具备足够的表达力,在不丢失信息的情况下准确表达源代码,并且充分考虑从源代码到目标代码编译的完备性、编译优化的易用性和性能。
引入中间表示后,中间表示既能面向多个前端,表达多种源程序语言,又能对接多个后端,连接不同目标机器,如 :numref:`intermediate_representation`所示。在此基础上编译流程就可以在前后端直接增加更多的优化流程这些优化流程以现有IR为输入又以新生成的IR为输出被称为优化器。优化器负责分析并改进中间表示极大程度的提高了编译流程的可拓展性也降低了优化流程对前端和后端的破坏。
引入中间表示后,中间表示既能面向多个前端,表达多种源程序语言,又能对接多个后端,连接不同目标机器,如 :numref:`intermediate_representation`所示。在此基础上编译流程就可以在前后端直接增加更多的优化流程这些优化流程以现有IR为输入又以新生成的IR为输出被称为优化器。优化器负责分析并改进中间表示极大程度的提高了编译流程的可拓展性也降低了优化流程对前端和后端的破坏。
![中间表示](../img/ch04/中间表示-中间表示结构.svg)
:width:`800px`
@@ -18,7 +18,7 @@
### 中间表示的种类
上一节介绍了中间表示的基本概念,初步阐述了中间表示的重要作用和发展历程。接下来从组织结构的角度出发,介绍通用编译器的中间表示的类型以及各自特点[@2007Engineering],如下表所示。中间表示组织结构的设计,对编译阶段的分析优化、代码生成等有着重要影响。编译器的设计需求不同,采用的中间表示组织结构也有所不同。
上一节介绍了中间表示的基本概念,初步阐述了中间表示的重要作用和发展历程。接下来从组织结构的角度出发,介绍通用编译器的中间表示的类型以及各自特点 :cite:`2020MLIR`,如下表所示。中间表示组织结构的设计,对编译阶段的分析优化、代码生成等有着重要影响。编译器的设计需求不同,采用的中间表示组织结构也有所不同。
::: {#tab:ch04/ch04-categorize}
组织结构 特点 举例
@@ -35,8 +35,7 @@
线性中间表示类似抽象机的汇编代码,将被编译代码表示为操作的有序序列,对操作序列规定了一种清晰且实用的顺序。由于大多数处理器采用线性的汇编语言,线性中间表示广泛应用于编译器设计。
常用线性中间表示有堆栈机代码(Stack-Machine Code)和三地址代码(Three
Address Code) [@2007Compilers]
。堆栈机代码是一种单地址代码提供了简单紧凑的表示。堆栈机代码的指令通常只有一个操作码其操作数存在一个栈中。大多数操作指令从栈获得操作数并将其结果推入栈中。三地址代码简称为3AC模拟了现代RISC机器的指令格式。它通过一组四元组实现每个四元组包括一个运算符和三个地址(两个操作数、一个目标)。对于表达式a-b\*5堆栈机代码和三地址代码如图 :numref:`linear_ir`所示。
Address Code) :cite:`2007Compilers` 。堆栈机代码是一种单地址代码提供了简单紧凑的表示。堆栈机代码的指令通常只有一个操作码其操作数存在一个栈中。大多数操作指令从栈获得操作数并将其结果推入栈中。三地址代码简称为3AC模拟了现代RISC机器的指令格式。它通过一组四元组实现每个四元组包括一个运算符和三个地址(两个操作数、一个目标)。对于表达式a-b\*5堆栈机代码和三地址代码如 :numref:`linear_ir`所示。
![堆栈机代码和三地址代码](../img/ch04/中间表示-线性中间表示.svg)
:width:`800px`
@@ -48,7 +47,7 @@ Address Code) [@2007Compilers]
Syntax TreeAST)、有向无环图(Directed Acyclic
GraphDAG)、控制流图(Control-Flow GraphCFG)等。
AST抽象语法树采用树型中间表示的形式是一种接近源代码层次的表示。对于表达式$a*5+a*5*b$其AST表示如 :numref:`AST_DAG`所示。可以看到AST形式包含$a*5$的两个不同副本存在冗余。在AST的基础上DAG提供了简化的表达形式一个节点可以有多个父节点相同子树可以重用。如果编译器能够证明$a$的值没有改变则DAG可以重用子树降低求值过程的代价。
AST抽象语法树采用树型中间表示的形式是一种接近源代码层次的表示。对于表达式$a*5+a*5*b$其AST表示如 :numref:`AST_DAG`所示。可以看到AST形式包含$a*5$的两个不同副本存在冗余。在AST的基础上DAG提供了简化的表达形式一个节点可以有多个父节点相同子树可以重用。如果编译器能够证明$a$的值没有改变则DAG可以重用子树降低求值过程的代价。
![AST图和DAG图](../img/ch04/中间表示-ASTDAG.svg)
:width:`600px`
@@ -56,11 +55,10 @@ AST抽象语法树采用树型中间表示的形式是一种接近源代码
3、混合中间表示
混合中间表示是线性中间表示和图中间表示的结合这里以LLVM IR
[@2004LLVM] 为例进行说明。LLVM(Low Level Virtual
混合中间表示是线性中间表示和图中间表示的结合这里以LLVM IR :cite:`2004LLVM` 为例进行说明。LLVM(Low Level Virtual
Machine)是2000年提出的开源编译器框架项目旨在为不同的前端后端提供统一的中间表示。LLVM
IR使用线性中间表示表示基本块使用图中间表示表示这些块之间的控制流 :numref:`LLVM_IR`所示。基本块中,每条指令以静态单赋值(Static
Single Assignment SSA) [@Richard1995A]
IR使用线性中间表示表示基本块使用图中间表示表示这些块之间的控制流如 :numref:`LLVM_IR`所示。基本块中,每条指令以静态单赋值(Static
Single Assignment SSA) :cite:`Richard1995A`
形式呈现这些指令构成一个指令线性列表。SSA形式要求每个变量只赋值一次并且每个变量在使用之前定义。控制流图中每个节点为一个基本块基本块之间通过边实现控制转移。
![LLVM IR](../img/ch04/中间表示-LLVMIR.svg)
@@ -84,7 +82,7 @@ IR能够很好地满足通用编译器的基本功能需求包括类型系
计算图模式。主流机器学习框架如TensorFlow、PyTorch、MindSpore等都提供了静态图和动态图两种计算图模式静态计算图模式先创建定义计算图再显式执行有利于对计算图进行优化高效但不灵活。动态计算图模式则是每使用一个算子后该算子会在计算图中立即执行得到结果使用灵活、便于调试但运行速度较低。机器学习框架的中间表示设计同时支持静态图和动态图可以针对待解决的任务需求选择合适的模式构建算法模型。
4\)
支持高阶函数和闭包[@2010C]。高阶函数和闭包是函数式编程的重要特性,高阶函数是指使用其它函数作为参数、或者返回一个函数作为结果的函数,闭包是指代码块和作用域环境的结合,可以在另一个作用域中调用一个函数的内部函数,并访问到该函数作用域中的成员。支持高阶函数和闭包,可以抽象通用问题、减少重复代码、提升框架表达的灵活性和简洁性。
支持高阶函数和闭包 :cite:`2010C`。高阶函数和闭包是函数式编程的重要特性,高阶函数是指使用其它函数作为参数、或者返回一个函数作为结果的函数,闭包是指代码块和作用域环境的结合,可以在另一个作用域中调用一个函数的内部函数,并访问到该函数作用域中的成员。支持高阶函数和闭包,可以抽象通用问题、减少重复代码、提升框架表达的灵活性和简洁性。
5\)
编译优化。机器学习框架的编译优化主要包括硬件无关的优化、硬件相关的优化、部署推理相关的优化等,这些优化都依赖于中间表示的实现。
@@ -101,7 +99,7 @@ IR作为PyTorch模型的中间表示通过JIT即时编译的形式将Pytho
PyTorch框架采用命令式编程方式其TorchScript
IR以基于SSA的线性IR为基本组成形式并通过JIT即时编译的Tracing和Scripting两种方法将Python代码转换成TorchScript
IR。 :numref:`TorchScript_IR`给出了Python示例代码及其TorchScript
IR。 :numref:`TorchScript_IR`给出了Python示例代码及其TorchScript
IR。
![Python代码及输出的TorchScript IR](../img/ch04/中间表示-torchscript.png)
@@ -123,7 +121,7 @@ IR是一种强类型、纯函数的中间表示其输入、输出都带有类
:label:`Jaxpr`
Jaxpr IR的表达采用ANF(A-norm
Form)函数式表达形式,如 :numref:`Jaxpr`所示。ANF形式将表达式划分为两类原子表达式(aexp)和复合表达式(cexp)。原子表达式用于表示常数、变量、原语、匿名函数,复合表达式由多个原子表达式组成,可看作一个匿名函数或原语函数调用,组合的第一个输入是调用的函数,其余输入是调用的参数。
Form)函数式表达形式,如 :numref:`Jaxpr`所示。ANF形式将表达式划分为两类原子表达式(aexp)和复合表达式(cexp)。原子表达式用于表示常数、变量、原语、匿名函数,复合表达式由多个原子表达式组成,可看作一个匿名函数或原语函数调用,组合的第一个输入是调用的函数,其余输入是调用的参数。
Jax框架结合了Autograd 和 JIT基于Jaxpr
IR支持循环、分支、递归、闭包函数求导以及三阶求导并且支持自动微分的反向传播和前向传播。
@@ -132,21 +130,16 @@ IR支持循环、分支、递归、闭包函数求导以及三阶求导
TensorFlow框架同时支持静态图和动态图是一个基于数据流编程的机器学习框架使用数据流图作为数据结构进行各种数值计算。TensorFlow机器学习框架的静态图机制更为人所熟知。在静态图机制中运行TensorFlow的程序会经历一系列的抽象以及分析程序会逐步从高层的中间表示向底层的中间表示进行转换我们把这种变换成为lowering。
为了适配不同的硬件平台基于静态计算图TensorFlow采用了多种IR设计其编译生态系统如:numref:`TFIR`所示。蓝色部分是基于图的中间表示绿色部分是基于SSA的中间表示。在中间表示的转换过程中各个层级的中间表示各自为政无法互相有效地沟通信息也不清楚其他层级的中间表示做了哪些优化因此每个中间表示只能尽力将当前的优化做到最好造成了很多优化在每个层级的中间表示中重复进行, 从而导致优化效率的低下。尤其是从图中间表示到SSA中间表示的变化过大转换开销极大。此外各个层级的相同优化的代码无法复用也降低了开发效率。
为了适配不同的硬件平台基于静态计算图TensorFlow采用了多种IR设计其编译生态系统如:numref:`TFIR`所示。蓝色部分是基于图的中间表示绿色部分是基于SSA的中间表示。在中间表示的转换过程中各个层级的中间表示各自为政无法互相有效地沟通信息也不清楚其他层级的中间表示做了哪些优化因此每个中间表示只能尽力将当前的优化做到最好造成了很多优化在每个层级的中间表示中重复进行, 从而导致优化效率的低下。尤其是从图中间表示到SSA中间表示的变化过大转换开销极大。此外各个层级的相同优化的代码无法复用也降低了开发效率。
![TensorFlow](../img/ch04/中间表示-MLIR.svg)
:width:`600px`
:label:`TFIR`
针对这个问题TensorFlow团队提出了MLIR(Multi-Level Intermediate
Represent多级中间表示)
([@2020MLIR]允许使用TensorFlow和其它机器学习库的项目编译更有效的代码从而最大程度地利用基础硬件。MLIR是用于现代优化编译器的灵活基础架构旨在定义一个通用的中间表示在统一的基础架构中支持多种不同的需求。MLIR采用混合中间表示允许在同一编译单元中结合多个层级的抽象来表示、分析和转换计算图利用其模块化、可扩展的特点解决了各种中间表示之间转换效率和可迁移性不高的问题从而适配多种硬件平台。
4、MLIR
针对这个问题TensorFlow团队提出了MLIR(Multi-Level Intermediate
Represent多级中间表示)
([@2020MLIR]。MLIR不是一种具体的中间表示定义而是为中间表示提供一个统一的抽象表达和概念。 开发者可以使用MLIR开发的一系列基础设施来定义符合自己需求的中间表示 因此我们可以把MLIR理解为“编译器的编译器”。MLIR不局限于TensorFlow框架 还可以用于构建连接其他语言与后端如LLVM的中间表示。
Represent多级中间表示) :cite:`2020MLIR`。MLIR不是一种具体的中间表示定义而是为中间表示提供一个统一的抽象表达和概念。 开发者可以使用MLIR开发的一系列基础设施来定义符合自己需求的中间表示 因此我们可以把MLIR理解为“编译器的编译器”。MLIR不局限于TensorFlow框架 还可以用于构建连接其他语言与后端如LLVM的中间表示。
MLIR深受LLVM设计理念的影响但与LLVM不同的是 MLIR是一个更开放的生态系统。 在MLIR中 没有预设的操作与抽象类型, 这使得开发者可以更自由地定义中间表示并更有针对性地解决其领域的问题。MLIR通过Dialect的概念来支持这种可拓展性 Dialect在特定的命名空间下为抽象提供了分组机制分别为每种中间表示定义对应的产生式并绑定相应的Operation 从而生成一个MLIR类型的中间表示。Operation是MLIR中抽象和计算的核心单元其具有特定的语意可以用于表示LLVM中所有核心的IR结构 例如指令, 函数以及模块等。 如下就是一个MLIR定义下的Operation
```
@@ -173,7 +166,7 @@ based。与TensorFlow类似程序使用图来表示使其容易去做
2纯函数的Purely functional
纯函数是指函数的结果只依赖函数的参数。若函数依赖或影响外部的状态,比如,函数会修改外部全局变量,或者函数的结果依赖全局变量的值,则称函数具有副作用[@spuler1994compiler]。若使用了带有副作用的函数,代码的执行顺序必须得到严格的保证,否则可能会得到错误的结果,比如对全局变量的先写后读变成了先读后写。同时,副作用的存在也会影响自动微分,因为反向部分需要从前向部分获取中间变量,需要确保该中间变量的正确。因此需要保证自动微分的函数是纯函数。
纯函数是指函数的结果只依赖函数的参数。若函数依赖或影响外部的状态,比如,函数会修改外部全局变量,或者函数的结果依赖全局变量的值,则称函数具有副作用 :cite:`spuler1994compiler`。若使用了带有副作用的函数,代码的执行顺序必须得到严格的保证,否则可能会得到错误的结果,比如对全局变量的先写后读变成了先读后写。同时,副作用的存在也会影响自动微分,因为反向部分需要从前向部分获取中间变量,需要确保该中间变量的正确。因此需要保证自动微分的函数是纯函数。
由于Python语言具有高度动态性的特点纯函数式编程对用户使用上有一些编程限制。有些机器学习框架的自动微分功能只支持对纯函数求导且要求用户自行保证这一点。如果用户代码中写了带有副作用的函数那么求导的结果可能会不符合预期。MindIR支持副作用的表达能够将副作用的表达转换为纯函数的表达从而在保持ANF函数式语义不变的同时确保执行顺序的正确性从而实现自由度更高的自动微分。
@@ -183,19 +176,19 @@ representation。反向模式的自动微分需要存储基本操作的中
4强类型的Strongly
typed。每个节点需要有一个具体的类型这个对于性能最大化很重要。在机器学习应用中因为算子可能很耗费时间所以越早捕获错误越好。因为需要支持函数调用和高阶函数相比于TensorFlow的数据流图MindIR的类型和形状推导更加复杂且强大。
在结合MindSpore框架的自身特点后MindIR的定义如 :numref:`MindIR`所示。
在结合MindSpore框架的自身特点后MindIR的定义如 :numref:`MindIR`所示。
![MindIR文法。MindIR中的ANode对应于ANF的原子表达式ValueNode用于表示常数值ParameterNode用于表示函数的形参CNode则对应于ANF的复合表达式表示函数调用](../img/ch04/中间表示-MindIR.svg)
:width:`800px`
:label:`MindIR`
接下来我们通过 :numref:`MindIR_example`中的一段程序作为示例来进一步分析MindIR。
接下来我们通过 :numref:`MindIR_example`中的一段程序作为示例来进一步分析MindIR。
![MindIR的ANF表达](../img/ch04/中间表示-MindIR示例.png)
:width:`600px`
:label:`MindIR_example`
在ANF中每个表达式都用let表达式绑定为一个变量通过对变量的引用来表示对表达式输出的依赖而在MindIR中每个表达式都绑定为一个节点通过节点与节点之间的有向边表示依赖关系。其函数图表示如 :numref:`MindIR_graph`所示。
在ANF中每个表达式都用let表达式绑定为一个变量通过对变量的引用来表示对表达式输出的依赖而在MindIR中每个表达式都绑定为一个节点通过节点与节点之间的有向边表示依赖关系。其函数图表示如 :numref:`MindIR_graph`所示。
![MindIR的函数图表示](../img/ch04/中间表示-MindIR图.png)
:width:`800px`

View File

@@ -16,9 +16,9 @@
4可读性。阅读代码时明确的类型声明有助于理解程序代码。
机器学习框架一般使用Python语言作为描述网络模型结构的前端语言。Python语言是一门动态弱类型的语言入门简单易学习开发代码简洁高效但由于其解释执行的方式运行速度往往较慢。Python前端语言给用户带来了动态灵活的语义和高效的开发效率但是若想要生成运行高效的后端代码后端框架需要优化友好的静态强类型中间表示。因此需要一种高效可靠的静态分析方法作为桥梁将Python前端表示转换成等价的静态强类型中间表示以此给用户同时带来高效的开发效率和运行效率例如Hindley--MilnerHM类型系统。这是一种具有参数多态性的简单类型lambda演算的类型系统。它最初由J.
Roger Hindley 提出[@1969The]并由Robin Milner
进行扩展和验证[@1978A]。后来路易斯·达马斯Luis
Damas对HM类型推导方法进行了详尽的分析和证明[@1982Principal]并将其扩展到支持具有多态引用的系统。Hindley--Milner类型系统的目标是在没有给定类型注解的情况下自动推导出任意表达式的类型。其算法具有抽象性和通用性采用简洁的符号表示能够根据表达式形式推导出明确直观的定义常用于类型推导和类型检查。因此Hindley--Milner类型系统广泛应用于编程语言设计中比如Haskell和Ocaml。
Roger Hindley 提出 :cite:`1969The`并由Robin Milner
进行扩展和验证 :cite:`1978A` 。后来路易斯·达马斯Luis
Damas对HM类型推导方法进行了详尽的分析和证明 :cite:`1982Principal`并将其扩展到支持具有多态引用的系统。Hindley--Milner类型系统的目标是在没有给定类型注解的情况下自动推导出任意表达式的类型。其算法具有抽象性和通用性采用简洁的符号表示能够根据表达式形式推导出明确直观的定义常用于类型推导和类型检查。因此Hindley--Milner类型系统广泛应用于编程语言设计中比如Haskell和Ocaml。
### 静态分析概述

147
mlsys.bib
View File

@@ -445,3 +445,150 @@ series = {ADKDD'14}
journal={arXiv e-prints arXiv:1811.08309},
year={2018}
}
@misc{2020MLIR,
title={MLIR: A Compiler Infrastructure for the End of Moore's Law},
author={ Lattner, C. and Amini, M. and Bondhugula, U. and Cohen, A. and Davis, A. and Pienaar, J. and Riddle, R. and Shpeisman, T. and Vasilache, N. and Zinenko, O. },
year={2020},
}
@book{2007Engineering,
title={Engineering a Compiler},
author={ Cooper, Keith D. and Torczon, Linda },
publisher={Engineering A Compiler},
year={2007},
}
@misc{2007Compilers,
title={Compilers: Principles, Techniques, and Tools (Rental), 2nd Edition},
author={ Aho, A. V. and Lam, M. S. and Ullman, J. D. and Sethi, R. },
year={2007},
}
@inproceedings{2004LLVM,
title={LLVM: A Compilation Framework for Lifelong Program Analysis & Transformation},
author={ Lattner, C. and Adve, V. },
booktitle={Code Generation and Optimization, 2004. CGO 2004. International Symposium on},
year={2004},
}
@article{Richard1995A,
title={A correspondence between continuation passing style and static single assignment form},
author={Richard and A. and Kelsey},
journal={Acm Sigplan Notices},
year={1995},
}
@article{2010C,
title={C++ lambda expressions and closures},
author={ Jaervi, Jaakko and Freeman, J. },
journal={Science of Computer Programming},
volume={75},
number={9},
pages={762-772},
year={2010},
}
@article{spuler1994compiler,
title={Compiler detection of function call side effects},
author={Spuler, David A and Sajeev, A Sayed Muhammed},
journal={Informatica},
volume={18},
number={2},
pages={219--227},
year={1994},
publisher={Citeseer}
}
@book{10.5555/1455489,
author = {Griewank, Andreas and Walther, Andrea},
title = {Evaluating Derivatives: Principles and Techniques of Algorithmic Differentiation},
year = {2008},
isbn = {0898716594},
publisher = {Society for Industrial and Applied Mathematics},
address = {USA},
edition = {Second},
}
@article{2015Automatic,
title={Automatic Differentiation in Machine Learning: a Survey},
author={ Pearlmutter, B. A. },
journal={computer science},
number={February},
year={2015},
}
@article{2015Numerical,
title={Numerical Analysis},
author={ Burden, R. L. and Faires, Jdd },
journal={Journal of the Royal Statistical Society},
volume={71},
number={1},
pages={48-50},
year={2015},
}
@book{2003Computer,
title={Computer Algebra Handbook: Foundations * Applications * Systems},
author={ Grabmeier, J. and Kaltofen, E. and Weispfenning, V. },
publisher={Computer algebra handbook : foundations, applications, systems},
year={2003},
}
@inbook{10.5555/60181.60188,
author = {Corliss, George F.},
title = {Applications of Differentiation Arithmetic},
year = {1988},
isbn = {0125056303},
publisher = {Academic Press Professional, Inc.},
address = {USA},
booktitle = {Reliability in Computing: The Role of Interval Methods in Scientific Computing},
pages = {127148},
numpages = {22}
}
@article{2000An,
title={An introduction to automatic differentiation},
author={ Verma, A. },
journal={Siam Computational Differentiation Techniques Applications & Tools},
volume={78},
number={7},
pages={804-807},
year={2000},
}
@inproceedings{2006The,
title={The Data-Flow Equations of Checkpointing in Reverse Automatic Differentiation},
author={ Dauvergne, B. and L Hascoët},
booktitle={Computational Science-iccs, International Conference, Reading, Uk, May},
year={2006},
}
@article{2017Divide,
title={Divide-and-Conquer Checkpointing for Arbitrary Programs with No User Annotation},
author={ Siskind, Jeffrey Mark and Pearlmutter, Barak A. },
journal={Optimization Methods and Software},
volume={33},
number={4-6},
year={2017},
}
@article{1969The,
title={The Principal Type-Scheme of an Object in Combinatory Logic},
author={ Hindley, R. },
journal={Transactions of the American Mathematical Society},
volume={146},
pages={29-60},
year={1969},
}
@article{1978A,
title={A theory of type polymorphism in programming},
author={ Milner, R. },
journal={Journal of Computer and System Sciences},
volume={17},
number={3},
pages={348-375},
year={1978},
}