图优化模块模块
图优化模块
MindCompiler(编译优化层):图层的核心编译器,主要基于端云统一的MindIR实现三大功能。包括硬件无关的优化(类型推导、自动微分、表达式化简等)、硬件相关优化(自动并行、内存优化、图算融合、流水线执行等)、部署推理相关的优化(量化、剪枝等),其中我负责的模块就是MindIR模块的前俩个硬件无关的优化和硬件相关的优化
1、MindIR的简介
中间表示(IR)是程序编译过程中介于源语言和目标语言之间的程序表示,以方便编译器进行程序分析和优化,因此IR的设计需要考虑从源语言到目标语言的转换难度,同时考虑程序分析和优化的易用性和性能。
MindIR是一种基于图表示的函数式IR,其最核心的目的是服务于自动微分变换。自动微分采用的是基于函数式编程框架的变换方法,因此IR采用了接近于ANF函数式的语义。
2、文法定义
ANF是函数式编程中常用且简洁的中间表示,其文法定义如下所示:
<aexp> ::= NUMBER | STRING | VAR | BOOLEAN | PRIMOP
| (lambda (VAR …) <exp>)
<cexp> ::= (<aexp> <aexp> …)
| (if <aexp> <exp> <exp>)
<exp> ::= (let ([VAR <cexp>]) <exp>) | <cexp> | <aexp>
ANF中表达式分为俩种,一种是原子表达式,一种是复合表达式。
-
原子表达式表示一个常数值或一个变量或一个匿名函数
-
复合表达式由多个原子表达式复合组成,表示一个匿名函数或原语函数调用,组合的第一个输入是调用的函数,其余输入是调用的参数。
MindIR文法继承于ANF,其定义如下所示:
<ANode> ::= <ValueNode> | <ParameterNode>
<ParameterNode> ::= Parameter
<ValueNode> ::= Scalar | Named | Tensor | Type | Shape
| Primitive | MetaFuncGraph | FuncGraph
<CNode> ::= (<AnfNode> …)
<AnfNode> ::= <CNode> | <ANode>
MindIR中的ANode对应于ANF的原子表达式,ANode有两个子类分别为ValueNode和ParameterNode
- ValueNode表示常数节点,可承载一个常数值(标量、符号、张量、类型、维度等),也可以是一个原语函数或一个元函数或一个普通函数,因为在函数式编程中函数定义本身也是一个值。ParameterNode是参数节点,表示函数的形参。
MindIR中CNode对应于ANF的复合表达式,表示一次函数调用。
3、使用
在MindSpore自动微分时,会计算ParameterNode和CNode的梯度贡献,并返回最终ParameterNode的梯度,而不计算ValueNode的梯度。
在MindIR中,一个函数图表示一个普通函数的定义,函数图一般由ParameterNode、ValueNode和CNode组成有向无环图,可以清晰地表达出从参数到返回值的计算过程。在上图中可以看出,python代码中两个函数test_f
和func
转换成了两个函数图,其参数x
和y
转换为函数图的ParameterNode,每一个表达式转换为一个CNode。CNode的第一个输入链接着调用的函数,例如图中的add
、func
、return
。值得注意的是这些节点均是ValueNode
,因为它们被理解为常数函数值。CNode的其他输入链接这调用的参数,参数值可以来自于ParameterNode、ValueNode和其他CNode。
在ANF中每个表达式都用let表达式绑定为一个变量,通过对变量的引用来表示对表达式输出的依赖,而在MindIR中每个表达式都绑定为一个节点,通过节点与节点之间的有向边表示依赖关系。
4、保存IR
通过context.set_context(save_graphs=True)
来保存各个编译阶段的中间代码。被保存的中间代码有两种格式,一个是后缀名为.ir
的文本格式,一个是后缀名为.dot
的图形化格式。当网络规模不大时,建议使用更直观的图形化格式来查看,当网络规模较大时建议使用更高效的文本格式来查看。
5、函数式语义
MindIR较传统计算图的一个重要特性是不仅可以表达算子之间的数据依赖,还可以表达丰富的函数式语义。
5.1、高阶函数
在MindIR中,函数的定义是由一个子图来定义,但其本身可以是一个被传递的值,作为其他高阶函数的输入或输出。 比如:函数f
作为参数传入了函数g
,因此函数g
是一个接收函数输入的高阶函数,函数f
真正的调用点是在函数g
内部。
5.2、控制流
控制流在MindIR中是以高阶函数选择调用的形式表达。这样的形式把控制流转换为高阶函数的数据流,从而使得自动微分算法更加强大。不仅可以支持数据流的自动微分,还可以支持条件跳转、循环和递归等控制流的自动微分。
5.3、自由变量和闭包
闭包是一种编程语言特性,它指的是代码块和作用域环境的结合。自由变量是指在代码块中引用作用域环境中的变量而非局部变量。在MindIR中,代码块是以函数图呈现的,而作用域环境可以理解为该函数被调用时的上下文环境,自由变量的捕获方式是值拷贝而非引用。
6、优秀代码赏析
/* 支持 utils 定义的命名空间 */
namespace mindspore {
bool CheckIfNeedExpand(const std::vector<BaseRef> &list) {
return std::any_of(list.begin(), list.end(), [](#) { return utils::isa<Seq>(any); }); // 遍历
}
// 智能指针shared_ptr
std::shared_ptr<VectorRef> ExpandList(const std::vector<BaseRef> &list) {
std::shared_ptr<VectorRef> new_list = std::make_shared<VectorRef>();
for (auto &item : list) { // 遍历list
if (utils::isa<Seq>(item)) {
const Seq &seq = utils::cast<Seq>(item); // const == utils::cast<Seq>
new_list->insert(new_list->end(), seq.begin(), seq.end()); // seq的遍历
} else {
new_list->push_back(item); // 尾插item
}
}
return new_list;
}
bool DefaultVisitor::Visit(const VectorRef &v_any, BaseRef *const visit_out) const {
std::vector<BaseRef> out; // 定义out列表
(void)std::transform(v_any.begin(), v_any.end(), std::back_inserter(out),
[this](#) { return fn_(item); });
if (visit_out != nullptr) {
*visit_out = ExpandList(out); // 指针
}
return true;
}
bool DefaultVisitor::Visit(const BaseRef &any, BaseRef *const visit_out) const {
if (utils::isa<Seq>(any)) {
return Visit(utils::cast<Seq>(any), visit_out);
} else if (utils: