diff --git a/docs/mindspore/source_zh_cn/features/compile/graph_construction.ipynb b/docs/mindspore/source_zh_cn/features/compile/graph_construction.ipynb deleted file mode 100644 index a8ed39198a1f1a458412c89e5f310d68c67c760e..0000000000000000000000000000000000000000 --- a/docs/mindspore/source_zh_cn/features/compile/graph_construction.ipynb +++ /dev/null @@ -1,273 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# 构图(编译)\n", - "\n", - "[![下载Notebook](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/master/resource/_static/logo_notebook.svg)](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/master/zh_cn/features/compile/mindspore_graph_construction.ipynb) [![下载样例代码](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/master/resource/_static/logo_download_code.svg)](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/master/zh_cn/features/compile/mindspore_graph_construction.py) [![查看源文件](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/master/resource/_static/logo_source.svg)](https://gitee.com/mindspore/docs/blob/master/docs/mindspore/source_zh_cn/features/compile/graph_construction.ipynb)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "MindSpore提供JIT(just-in-time)技术来进行性能优化。JIT模式会通过AST树解析、Python字节码解析或追踪代码执行的方式,将代码解析为一张中间表示图(IR,intermediate representation)。IR图作为该代码的唯一表示,编译器通过对该IR图的优化,来达到对代码的优化,提高运行性能。与动态图模式相对应,这种JIT的编译模式被称为静态图模式。\n", - "\n", - "基于JIT技术,MindSpore提供了动静结合的方法来提高用户的网络的运行效率。动静结合,即在整体运行为动态图的情况下,指定某些代码块以静态图的方式运行。按照静态图方式运行的代码块会采取先编译后执行的运行模式,在编译期对代码进行全局优化,来获取执行期的性能收益。用户可以通过`@jit`装饰器修饰函数,来指定其按照静态图的模式执行。有关`@jit`装饰器的相关文档请见[jit API文档](https://www.mindspore.cn/docs/zh-CN/master/api_python/mindspore/mindspore.jit.html#mindspore.jit)。\n", - "\n", - "MindSpore提供了三种JIT编译方式,分别通过ast、bytecode和trace的方式来构图。ast是通过AST树解析的方式,将用户手工标识需要按照ast方式执行的函数转换成静态图。bytecode则是通过对Python字节码的解析,在动态图中尽可能的构建静态图,无法转换为静态图的部分则会按照动态图进行执行,来达到动静结合的目的。trace是通过追踪Python代码执行的轨迹来构建静态图,当前属于实验性质的特性。后续介绍会详细说明三者原理的不同以及各自的特点。\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Ast\n", - "\n", - "在动态图模式下,用户可以通过`@jit(capture_mode=\"ast\")`装饰器修饰函数来让该函数以ast方式来执行。用ast方式修饰的函数,其内部使用的语法以及数据结构需要遵守静态图语法规范[静态图语法规范](https://www.mindspore.cn/tutorials/zh-CN/master/compile/static_graph.html)。ast方式通过源到源的方式来编译Python代码,先把模型定义的Python源码解析成抽象语法树,然后把抽象语法树解析为MindIR。例如下面的Python代码:\n", - "\n", - "```python\n", - "@jit\n", - "def foo(x, y):\n", - " z = x + y\n", - " return z\n", - "```\n", - "\n", - "它对应的抽象语法树如下:\n", - "\n", - "![抽象语法树](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/master/docs/mindspore/source_zh_cn/features/compile/images/ast.png)\n", - "\n", - "通过解析上面的抽象语法树,我们得到下面的MindIR:\n", - "\n", - "```text\n", - "%para1_x: \n", - "%para2_y: \n", - "\n", - "subgraph instance: foo\n", - "subgraph @foo() {\n", - " %0(CNode_17) = PrimFunc_Add(%para1_x, %para2_y)\n", - " : (, ) -> ()\n", - " Return(%0)\n", - " : ()\n", - "}\n", - "```\n", - "\n", - "**ast的使用方法**:\n", - "\n", - "用户可以通过`@jit`装饰器来指定函数以静态图的方式来执行,例如:" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[[4. 4. 4. 4.]\n", - " [4. 4. 4. 4.]]" - ] - } - ], - "source": [ - "import numpy as np\n", - "import mindspore as ms\n", - "from mindspore import ops\n", - "from mindspore import jit\n", - "from mindspore import Tensor\n", - "\n", - "@jit\n", - "def tensor_cal(x, y, z):\n", - " return ops.matmul(x, y) + z\n", - "\n", - "x = Tensor(np.ones(shape=[2, 3]), ms.float32)\n", - "y = Tensor(np.ones(shape=[3, 4]), ms.float32)\n", - "z = Tensor(np.ones(shape=[2, 4]), ms.float32)\n", - "ret = tensor_cal(x, y, z)\n", - "print(ret)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "上述用例中,tensor_cal函数被@jit装饰器修饰,该函数被调用时就会按照静态图的模式进行执行,以获取该函数执行期的性能收益。\n", - "\n", - "**ast的优点**:\n", - "\n", - "- 使用ast模式,用户的编程自主性更强,性能优化更精准,可以根据函数特征以及使用经验将网络的性能调至最优。\n", - "\n", - "**ast的限制**:\n", - "\n", - "- ast修饰的函数,其内部的语法必须严格遵守静态图语法来进行编程。\n", - "\n", - "**ast模式的使用建议**:\n", - "\n", - "- 相比于动态图执行,被`@jit`修饰的函数,在第一次调用时需要先消耗一定的时间进行静态图的编译。在该函数的后续调用时,若原有的编译结果可以复用,则会直接使用原有的编译结果进行执行。因此,使用@jit装饰器修饰会多次执行的函数通常会获得更多的性能收益。\n", - "\n", - "- 静态图模式的运行效率优势体现在其会将被@jit修饰函数进行全局上的编译优化,函数内含有的操作越多,优化的上限也就越高。因此`@jit`装饰器修饰的函数最好是内含操作很多的大代码块,而不应将很多细碎的、仅含有少量操作的函数分别打上jit标签。否则,则可能会导致性能没有收益甚至劣化。\n", - "\n", - "- MindSpore静态图绝大部分计算以及优化都是基于对Tensor计算的优化,因此我们建议被修饰的函数应该是那种用来进行真正的数据计算的函数,而不是一些简单的标量计算或者数据结构的变换。\n", - "\n", - "- 被`@jit`修饰的函数,若其输入存在常量,那么该函数每次输入值的变化都会导致重新编译,关于变量常量的概念请见[即时编译下的常量与变量](https://www.mindspore.cn/tutorials/zh-CN/master/compile/static_graph.html#%E5%8D%B3%E6%97%B6%E7%BC%96%E8%AF%91%E4%B8%8B%E7%9A%84%E5%B8%B8%E9%87%8F%E4%B8%8E%E5%8F%98%E9%87%8F)。因此,建议被修饰的函数以Tensor或者被Mutable修饰的数据作为输入。避免因多次编译导致的额外性能损耗。" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Bytecode\n", - "\n", - "除了ast,MindSpore提供另外一种静态化加速机制bytecode,用户可以通过`@jit(capture_mode=\"bytecode\")`装饰器修饰函数来让该函数以bytecode模式来执行。当bytecode识别到不支持进入静态图的语法时,会回退到Python执行而非直接编译报错。该功能同时兼顾性能和易用性,减少编译报错的发生。它基于Python字节码的分析,对Python的执行流进行图捕获,让可以以静态图方式运行的子图以静态图方式运行,并让Python语法不支持的子图以动态图方式运行,同时通过修改调整字节码的方式链接动静态图,达到动静混合执行。在满足易用性的前提下,尽可能地提高性能。\n", - "\n", - "**bytecode的运行原理**:\n", - "\n", - "1. 基于Python虚拟机_PyInterpreterState_SetEvalFrameFunc捕获Python函数的执行,采用上下文管理的方式捕获执行区域内的所有Python函数执行。\n", - "2. 按照当前的运行时输入参数结合函数字节码进行分析,构造控制流图(CFG)以及数据流图(DFG)。\n", - "3. 模拟进栈出栈操作,跟踪逐个字节码,根据栈输入,推导输出。Python3.7~Python3.11每条字节码都有对应的模拟实现,注意是推导输出的类型尺寸,而不是真正执行得到值,除非常量折叠。\n", - "4. 在模拟执行字节码的过程中,将推导结果和操作翻译成MindIR,最后,通过常量折叠,UD分析(删除无用的输入输出参数)等方式,优化静态图。\n", - "5. 在执行等效的静态图之前,对输入参数和优化过程中产生的看护Guard条件进行比对,根据运行时信息,选择匹配的静态图执行。\n", - "6. 动态管理看护Guard和静态图缓冲的匹配关系,对不常用的静态图缓冲进行回收,通过Symbolic Shape和Dynamic Shape优化静态图缓冲。\n", - "\n", - "bytecode的编译流程如下图所示\n", - "\n", - "![bytecode的编译流程](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/master/docs/mindspore/source_zh_cn/features/compile/images/bytecode.png)\n", - "\n", - "**bytecode的使用方式**:\n", - "\n", - "将jit的capture_mode参数设置为bytecode,即可将修饰函数的运行模式切换为bytecode,例如:\n" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[[4. 4. 4. 4.]\n", - " [4. 4. 4. 4.]]" - ] - } - ], - "source": [ - "import numpy as np\n", - "import mindspore as ms\n", - "from mindspore import ops\n", - "from mindspore import jit\n", - "from mindspore import Tensor\n", - "\n", - "@jit(capture_mode=\"bytecode\")\n", - "def tensor_cal(x, y, z):\n", - " return ops.matmul(x, y) + z\n", - "\n", - "x = Tensor(np.ones(shape=[2, 3]), ms.float32)\n", - "y = Tensor(np.ones(shape=[3, 4]), ms.float32)\n", - "z = Tensor(np.ones(shape=[2, 4]), ms.float32)\n", - "ret = tensor_cal(x, y, z)\n", - "print(ret)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**bytecode的优点**:\n", - "\n", - "- 用户体验好,无需人工介入,用户编写的网络代码总是能够正常运行,静态图不能执行的代码会自动采用动态图运行。\n", - "- bytecode可以通过对字节码的变换,使得更多的语句进入静态图。用户无需感知或修改代码。\n", - "\n", - "**bytecode的限制**:\n", - "\n", - "- 用户无法明确对某些代码做性能加速,对于裂图较多的场景,性能加速的效果可能会不明显。" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Trace\n", - "\n", - "MindSpore也提供另外一种静态化加速机制trace,用户可以通过`@jit(capture_mode=\"trace\")`装饰器修饰函数来让该函数以trace模式来执行。在该模式下,代码会先以PyNative模式运行,在运行时调用的算子会被记录,并被捕获到计算图中。在后续执行该装饰器修饰的代码时,会直接执行第一次执行所构造出的计算图。该功能不会解析语法,只会捕获运行时调用的算子,因此不会有语法不支持报错的发生。它基于捕获运行PyNative模式时调用的算子,对Python的执行流进行图捕获,将捕获到的算子编入计算图中。没有对应算子的操作将无法生成节点,trace流程将只捕获该操作的返回值,在计算图中作为常量。生成的计算图以静态图的运行方式运行。\n", - "\n", - "**trace的使用方式**:\n", - "\n", - "将jit的capture_mode参数设置为trace,即可将修饰函数的运行模式切换为trace,例如:\n" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[[4. 4. 4. 4.]\n", - " [4. 4. 4. 4.]]" - ] - } - ], - "source": [ - "import numpy as np\n", - "import mindspore as ms\n", - "from mindspore import ops\n", - "from mindspore import jit\n", - "from mindspore import Tensor\n", - "\n", - "@jit(capture_mode=\"trace\")\n", - "def tensor_cal(x, y, z):\n", - " return ops.matmul(x, y) + z\n", - "\n", - "x = Tensor(np.ones(shape=[2, 3]), ms.float32)\n", - "y = Tensor(np.ones(shape=[3, 4]), ms.float32)\n", - "z = Tensor(np.ones(shape=[2, 4]), ms.float32)\n", - "ret = tensor_cal(x, y, z)\n", - "print(ret)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**trace的优点**:\n", - "\n", - "- 构图能力强,只要代码有对应算子就能够入图,不需要额外适配。构建静态图时不会有语法不支持报错。\n", - "- 用户体验好,无需人工介入,用户编写的网络代码总是能够正常运行。\n", - "\n", - "**trace的限制**:\n", - "\n", - "- 无法感知控制流,多次运行时控制流会进入不同分支的场景无法保证正确性。\n", - "- 没有定义为算子的操作,如第三方库会在计算图中被固定为常量,多次运行无法保证正确性。" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python [conda env:base] *", - "language": "python", - "name": "conda-base-py" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.7" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/docs/mindspore/source_zh_cn/features/compile/graph_optimization.md b/docs/mindspore/source_zh_cn/features/compile/graph_optimization.md deleted file mode 100644 index e3d8f794240ffb56d96374c162acfb3da213de7f..0000000000000000000000000000000000000000 --- a/docs/mindspore/source_zh_cn/features/compile/graph_optimization.md +++ /dev/null @@ -1,318 +0,0 @@ -# 图优化(编译) - -[![查看源文件](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/master/resource/_static/logo_source.svg)](https://gitee.com/mindspore/docs/blob/master/docs/mindspore/source_zh_cn/features/compile/graph_optimization.md) - -与传统编译器类似,MindSpore 在进行完构图之后,也会进行编译优化。编译优化的主要目的是通过静态分析技术对 MindSpore 的中间表示 MindIR 进行分析和转换,以达成减小目标代码大小、提升代码执行效率、降低运行时资源开销或者提升其它性能指标的目的。编译优化是图编译系统中的重要一环,对提升整个神经网络模型的性能和资源利用率有着极其重要的意义,相较于未经过编译优化的原始代码,编译优化可能带来数倍甚至数十倍的性能提升。 - -本节主要介绍独立于特定硬件的前端编译优化技术,特定于硬件的后端编译优化技术不在本节的讨论范围之内。 - -## 前端编译优化技术原理 - -与传统编译优化技术类似,MindSpore 中的编译优化也是通过一个个 Pass 来完成的。将每个 Pass 的上一个 Pass 所产生的 MindIR 作为输入,经过本 Pass 优化之后,产生新的 MindIR 表示作为输出。一个大的 Pass 可以包含多个小的 Pass,每个小的 Pass 只负责单点的编译优化,如:代数化简、函数内联(inline)、冗余消除等。一个 Pass 产生的优化结果,可能会为其它的 Pass 带来优化机会,故可以循环运行这些 Pass,直到产生的 MindIR 不再发生变化为止。 - -编译优化过程中,选择运行哪些 Pass,如何安排这些 Pass 的执行顺序对生成的最终的编译结果有着非常重要的影响。可以按照实际情况,通过设定编译优化策略(如优化级别、次数等)来对即将执行的优化动作进行调整。 - -## 常见前端编译优化技术 - -前端编译优化技术有很多,如:代数化简、函数inline(内联)、冗余消除等。本节将介绍部分具有代表性的编译优化技术。 - -### 代数化简 - -在传统编译器中,代数化简是一种编译器优化技术,旨在简化源代码中的代数表达式,消除多余计算,提高程序执行效率、减少内存占用等。 - -例如,在以下代码片段中: - -```cpp -int a = x * 1; -int b = x + 0; -int c = x * 0 + y * 1; -``` - -传统编译器根据代数规则和恒等式对识别出的表达式进行等价替换。常见代数规则包括结合律、交换律和分配律等,编译器尽可能将表达式替换成更为简单的形式。通过对 AST(抽象语法树)或 SSA(静态单赋值形式)的分析来进行优化,识别并简化代码为: - -```cpp -a = x; -b = x; -c = y; -``` - -在 MindSpore编译器中,代数化简原理不同于传统编译器,进行处理的是计算图而非传统控制流图,通过调整计算图中算子的执行顺序,或者删除不必要的算子,以保持计算图的简洁性和提高计算效率。 - -例如,在如下Python代码片段中: - -```python -import numpy as np -from mindspore.common import Tensor, jit - -@jit -def func(x): - return x + 0 - -m = Tensor(np.array([[1, 2, 3], [4, 5, 6]]).astype(np.int32)) -out = func(m) -``` - -MindSpore图编译器会把 Python 程序转换为计算图,计算图由多个子图构成。源程序中的代数运算,转换为子图内部的算子调用,可以看到 PrimFunc_Add 算子调用了一次。 - -```text -%para1_x: - -subgraph @1_func_14() { - %0(CNode_7) = PrimFunc_Add(%para1_x, Tensor(shape=[], dtype=Int32, value=0)) - : (, ) -> () - - Return(%0) - : () -} -``` - -通过代数化简,可以直接删除 PrimFunc_Add 算子,简化计算图结构,将 `x + 0` 简化成 `x`。 - -```text -%para1_x: - -subgraph @1_func_14() { - Return(%para1_x) - : () -} -``` - -代数化简能更多地涉及对计算图结构的修改,它通常还与其他编译器优化技术(如常量折叠、常量传播等)结合使用,共同提高程序性能。 - -### 函数inline - -在传统编译器中,inline(内联)是一种优化技术,可以把被调用函数的代码直接替换到调用该函数的位置,提高程序运行效率。假设我们有一个 C++ 函数`add`,用于对两个数求和: - -```cpp -int add(int a, int b) { - return a + b; -} - -int main() { - int x = add(3, 5); - int y = add(x, 10); - return y; -} -``` - -编译器通过 inline 将函数体直接替换到调用处,这消除了函数调用的开销,同时为后续优化(如消除冗余计算`3 + 5`,直接在编译期求值替换)创造了条件。这种**用代码替换调用**的思想,正是 inline 的核心。 - -```cpp -int main() { - int x = 3 + 5; // 替换第一次调用 - int y = x + 10; // 替换第二次调用 - return y; -} -``` - -在 AI 框架的计算图编译器中,inline 的目标类似,但操作对象从“函数”变成了“子图”(subgraph)。假设我们有一个 Python 程序: - -```python -from mindspore import Tensor, jit, ops - -def f2(x: Tensor, y: Tensor): - return x * 0.5 + y - -@jit -def f1(a: Tensor, b: Tensor, c: Tensor): - x = f2(a, b) - y = f2(a, c) - return x + y - -# 创建3个shape=(2, 4)的随机值Tensor -a = ops.randn(2, 4) -b = ops.randn(2, 4) -c = ops.randn(2, 4) -out = f1(a, b, c) -``` - -首先,MindSpore 的计算图编译器会把 Python 程序转换为计算图。而 Python 程序中的函数调用,会转换为计算图之间的调用,得到类似于下面的原始计算图。其中,主图 f1 调用了 2 次子图 f2。 - -```text -# Params: -%para1_a: -%para2_b: -%para3_c: - -subgraph @f2(%para1_x, %para2_y) { - %0 = PrimFunc_Mul(%para1_x, Float32(0.5)) - - %1 = PrimFunc_Add(%0, %para2_y) - - Return(%1) -} - -subgraph @f1() { - %0(x) = call @f2(%para1_a, %para2_b) # 调用子图f2 - - %1(y) = call @f2(%para1_a, %para3_c) # 调用子图f2 - - %2 = PrimFunc_Add(%0, %1) - - Return(%2) -} -``` - -通过 inline,可以将子图 f2 展开,合并到主图 f1。 - -```text -subgraph @f1() { - # 第一次子图inline - %0 = PrimFunc_Mul(%para1_a, Float32(0.5)) # 重复计算步骤 - %1 = PrimFunc_Add(%0, %para2_b) - - # 第二次子图inline - %2 = PrimFunc_Mul(%para1_a, Float32(0.5)) # 重复计算步骤 - %3 = PrimFunc_Add(%2, %para3_c) - - %4 = PrimFunc_Add(%1, %3) - - Return(%4) -} -``` - -在 inline 将子图展开之前,编译器可能无法识别到两次调用子图 f2 中的重复操作(此时子图通常被当作黑盒处理)。而通过 inline 将子图展开后,此时编译器可以清晰看到`x * 0.5`被计算了两次,就可以触发编译器进一步的优化:**公共子表达式消除** (CSE, Common Subexpression Elimination),这样就降低了计算量。 - -```text -subgraph @f1() { - %0 = PrimFunc_Mul(%para1_a, Float32(0.5)) # CSE合并重复计算 - - %1 = PrimFunc_Add(%0, %para2_b) - - %2 = PrimFunc_Add(%0, %para3_c) # 直接复用%0 - - %3 = PrimFunc_Add(%1, %2) - - Return(%3) -} -``` - -通过 inline 将子图展开,编译器能够更清晰地识别跨子图的优化机会,除了公共子表达式消除 (CSE),还能够触发算子融合、内存管理等许多优化措施。因此 inline 是计算图编译器的一项重要优化机制,也是许多跨图优化的基础。 - -### 冗余消除 - -在传统编译器中,冗余消除包含了多种编译优化技术,旨在通过在编译期间识别出代码中存在冗余的部分并进行消除,达到减少不必要的计算,提高程序的执行效率的目的。 - -通常冗余代码可能是用户出于可读性等目的有意编写的,也可能仅仅是编码过程中的无心之举。此外,编译优化过程本身通过其它优化技术(如:代数化简、inline、公共子表达式消除等)产生的中间结果,也可能带来冗余消除的机会。 - -冗余消除的技术有很多,本节挑选了其中常见的无用代码消除、不可达代码消除进行介绍。 - -1. **无用代码消除** - - 消除计算结果未被使用的代码。例如:下面的 C++ 代码中,变量 `c` 未被任何其它代码使用,编译器可以通过静态分析领域的数据流分析等技术,将计算 `int c = x * y` 的这行代码消除。 - - ```cpp - int func(x, y) { - int a = x + y; - int b = x - y; - int c = x * y; // 无用代码 - int d = a / b; - return d; - } - ``` - -2. **不可达代码消除** - - 消除未被有效控制流路径包含的代码。例如:下面的 C++ 代码中,编译器可以通过静态分析领域的控制流分析技术,分析代码的控制流图,识别到表达式 `1 < 0` 恒不成立,从而控制流 `1 < 0` 包含的代码在实际运行期间必定不会被执行,故可将该分支的代码消除。 - - ```cpp - int func(x, y) { - int a = x + y; - - int b; - if 1 < 0 { // 不可达分支 - b = x + y; - } else { - b = x - y; - } - - int d = a / b; - return d; - } - ``` - -MindSpore 图模式下冗余消除的目的及使用的技术也类似。与传统编译器不同的是,这些冗余优化技术是在 MindIR 上完成的。类似的,MindSpore 中常见的冗余消除技术有: - -1. **无用代码消除** - - 假设有如下存在冗余计算的Python代码: - - ```python - import mindspore as ms - from mindspore.common import Tensor, jit - - @jit - def func(x, y): - a = x + y - b = x - y - c = x * y # 无用代码 - d = a / b - return d - - x = Tensor(20, ms.float32) - y = Tensor(10, ms.float32) - out = func(x, y) - ``` - - MindSpore 图编译器会通过静态分析将 `@jit` 修饰的 Python 代码转换为 MindIR 的表示形式并消除其中冗余的 `c = x * y` 的计算,最终生成的 MindIR 如下: - - ```text - # Params: - %para1_x: - %para2_y: - - subgraph @func_1() { - %0(a) = PrimFunc_Add(%para1_x, %para2_y) - : (, ) -> () - %1(b) = PrimFunc_Sub(%para1_x, %para2_y) - : (, ) -> () - %2(d) = PrimFunc_Div(%0, %1) - : (, ) -> () - Return(%2) - : () - } - ``` - -2. **不可达代码消除** - - 假设有如下存在不可达路径的Python代码: - - ```python - import mindspore as ms - from mindspore.common import Tensor, jit - - @jit - def func(x, y): - a = x + y - if 1 < 0: # 不可达分支 - b = x + y - else: - b = x - y - d = a / b - return d - - x = Tensor(20, ms.float32) - y = Tensor(10, ms.float32) - out = func(x, y) - ``` - - MindSpore 图编译器会通过静态分析将 `@jit` 修饰的 Python 代码转换为 MindIR 的表示形式并消除其中冗余的控制流分支 `1 < 0` 的代码,最终生成的 MindIR 如下: - - ```text - # Params: - %para1_x: - %para2_y: - - subgraph @func_1() { - %0(a) = PrimFunc_Add(%para1_x, %para2_y) - : (, ) -> () - %1(b) = PrimFunc_Sub(%para1_x, %para2_y) - : (, ) -> () - %2(d) = PrimFunc_Div(%0, %1) - : (, ) -> () - Return(%2) cnode_attrs: {checkpoint: Bool(1)} - : () - } - ``` - -冗余消除在编译优化中扮演着重要的角色,在不改变程序原语义的前提下,能够显著提高程序的执行效率,通过减少不必要的运行时计算节省计算资源。冗余消除通常还与其它编译优化技术结合使用以获得更多消除冗余代码的机会。 diff --git a/docs/mindspore/source_zh_cn/features/compile/multi_level_compilation.md b/docs/mindspore/source_zh_cn/features/compile/multi_level_compilation.md index 0675dac4e7bf36a161bcfd117866089765510b1e..d8d35956fcdb7f6b376dfea329624ee335b9bdcb 100644 --- a/docs/mindspore/source_zh_cn/features/compile/multi_level_compilation.md +++ b/docs/mindspore/source_zh_cn/features/compile/multi_level_compilation.md @@ -1,68 +1,383 @@ -# 多级编译介绍(编译) +# mindspore.jit 多级编译优化 [![查看源文件](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/master/resource/_static/logo_source.svg)](https://gitee.com/mindspore/docs/blob/master/docs/mindspore/source_zh_cn/features/compile/multi_level_compilation.md) -## 背景 -随着深度学习大模型时代的到来,网络规模越来越大,对图编译性能、执行性能和调试调优效率的挑战也越来越大。为此,MindSpore提出多级编译架构,提供O(n)多级编译执行模式,它们在图优化、算子融合、内存管理以及执行模式等方面有所不同,旨在提供图模式的多样性选择,用户可以根据自己的网络特点和需求,选择最适合的编译执行模式: +## MindSpore编译架构 + +MindSpore利用jit(just-in-time)来进行性能优化。jit模式会通过AST树解析、Python字节码解析或追踪代码执行的方式,将python代码转换为中间表示图(IR,Intermediate Representation)。我们给它命名MindIR。编译器通过对该IR图的优化,来达到对代码的优化,提高运行性能。与动态图模式相对应,这种JIT的编译模式被称为graph mode。 -1. O0模式:这是一个基础的编译执行模式,除必要影响功能的优化外,其他优化均关闭,使用单算子执行的执行方式。因此执行性能可能不是最优,但它的优点是可以保证图的原始结构,方便用户进行调试和理解,编译性能也较好。如下图中的Add和Mul单算子执行。 -2. O1模式:这个模式会进行一些基础的优化,比如常用图优化和自动算子融合优化,使用单算子执行的执行方式。相比O0,由于使能了融合优化,可以提高执行性能,但可能会影响到图的原始结构,因此编译性能和调试调优效率有所损失。如下图中的Add跟Mul融合成一个fused_op执行。 -3. O2模式:这是一个更高级的优化模式,目前没有实现,后续较为深层次的优化可使用该模式。 +开发者写的python代码默认以动态图模式运行,可以通过`@mindspore.jit`装饰器修饰函数,来指定其按照graph mode执行。有关`@mindspore.jit`装饰器的相关文档请见[jit 文档](https://www.mindspore.cn/docs/zh-CN/master/api_python/mindspore/mindspore.jit.html#mindspore.jit)。 -![jit_level_example](./images/multi_level_compilation/jit_level_example.png) +graph mode大致分为3个阶段: + - 图捕获(构图): python代码 -> MindIR。 + - 图优化(前端): 对MindIR进行硬件无关优化,代数化简、函数inline(内联)、冗余消除等。 + - 图优化(后端): 对MindIR进行硬件相关优化,LazyInline,算子选择,图算融合等。 -## 多级编译架构概述 +## 图捕获(构图) -![jit_level_framework](./images/multi_level_compilation/jit_level_framework.png) +MindSpore提供三种捕获方式,如下 + - AST: 通过AST树解析的方式将执行的函数转换成IR图 + - bytecode(实验性): 对Python字节码的解析,尽可能的构建IR图,无法转换为IR图的部分则会按照动态图进行执行 + - trace(实验性): 通过追踪Python代码执行的轨迹来构建IR图 -1. 多级编译对外接口:通过[mindspore.jit(jit_level="O0/O1")](https://www.mindspore.cn/docs/zh-CN/master/api_python/mindspore/mindspore.jit.html#mindspore.jit)来配置多级编译级别,jit_level默认为O0,通常我们建议用户使用O0模式进行网络调试调优,调试就绪后,为了更好的性能可以一键开启O1运行网络。 -2. 后端图编译:根据配置的多级编译级别,选择不同的编译模式,O0为最基础的原生构图与编译,O1在O0基础增加了自动算子融合功能,主要功能有图优化、图算融合、算子选择、执行序编排,其中图算融合为O1模式下独有功能。 -3. 后端图执行:O0跟O1模式执行层面是一样的,均使用单算子方式调度执行,主要功能有多流并发、多级流水、HAL管理、内存管理。 +这三种模式在mindspore.jit中使用capture_mode来选择,以ast举例: 开发者可用`@mindspore.jit(capture_mode="ast")`装饰器修饰函,用ast方式修饰的函数,其语法有一定限制,我们提供两种模式供开发者选择。 +- strict模式:此模式目标是构成一张图,开发者的python代码如果无法构图,选择此模式运行程序时会报错,需要开发者进行代码修改,变为可构图的语法,适合追求性能的开发者。 +- lax模式:此模式目标是尽可能的让开发者程序可运行,思路是针对无法在strict模式构图的代码进行python fallback,即返回python层运行。 -## O0模式介绍 +graph mode模式约束请参考[语法约束](https://www.mindspore.cn/tutorials/zh-CN/master/compile/static_graph.html)。ast如何将python代码解析并构图,举例如下: -O0为基础的图编译执行模式,除必要影响功能的优化外,其他优化均关闭,使用原生的图结构进行编译和执行,方便调试调优,具备较好的编译性能。下面主要介绍后端图编译相关功能,后端图执行相关功能详见[运行时](https://www.mindspore.cn/docs/zh-CN/master/features/runtime/memory_manager.html)。 +```python +@mindspore.jit +def foo(x, y): + z = x + y + return z +``` -### 图优化 +它对应的抽象语法树如下: -O0模式的图优化较少,基础的优化主要为后端LazyInline和No-task node执行优化。 +![抽象语法树](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/master/docs/mindspore/source_zh_cn/features/compile/images/ast.png) -- **后端LazyInline** +通过解析上面的抽象语法树,我们得到下面的IR: - **LazyInline**:主要思想是将函数调用的开销推迟到实际需要调用的时候,这样可以减少编译时的开销,提高编译效率。LazyInline在图编译阶段是将相同的子图结构复用,不展开放在图中,避免图规模较大导致影响编译性能。 +```text +%para1_x: +%para2_y: - ![jit_level_lazyinline](./images/multi_level_compilation/jit_level_lazyinline.png) +subgraph instance: foo +subgraph @foo() { + %0(CNode_17) = PrimFunc_Add(%para1_x, %para2_y) + : (, ) -> () + Return(%0) + : () +} +``` - **流水线(Pipeline)并行**:将神经网络中的算子切分成多个Stage,再把Stage映射到不同的设备上,使得不同设备去计算神经网络的不同部分。为了提升效率,流水线并行进一步将小批次(MiniBatch)切分成更细粒度的微批次(MicroBatch),在微批次中采用流水线式的调度,从而达到提升效率的目的。 +**ast的优点**: - **后端LazyInline**:由于Pipeline并行的MicroBatch切分会导致整个计算图扩张到MicroBatch的数量倍,从而导致模型规模巨大,编译性能时间较长(可能小时级别)。而这些Micro子图结构都是一样的,为了解决编译性能问题,LazyInline技术则非常契合,不过LazyInline带来的问题就是运行时无法采用最优的方式进行内存复用和流分配、无法做跨图的优化(内存优化、通信融合、算子融合等)等问题。为此,在图编译结束后,在图执行之前,将这些Micro子图做实际的节点Inline,以形成完整的全局整图,再通过图Inline后的内存优化、通信优化、冗余计算消除等方式,从而实现在编译性能、执行性能、执行内存方面都兼顾的目标。 +- 使用ast模式,用户的编程自主性更强,性能优化更精准,可以根据函数特征以及使用经验将网络的性能调至最优。 -- **No-task node执行优化** +**ast的限制**: - ![jit_level_no_task](./images/multi_level_compilation/jit_level_no_task.png) +- ast修饰的函数,其内部的语法必须严格遵守静态图语法来进行编程。 + +**ast模式的使用建议**: + +- 相比于动态图执行,被`@mindspore.jit`修饰的函数,在第一次调用时需要先消耗一定的时间进行编译。在该函数的后续调用时,若原有的编译结果可以复用,则会直接使用原有的编译结果进行执行。因此,使用@jit装饰器修饰会多次执行的函数通常会获得更多的性能收益。 + +- graph mode的运行效率优势体现在其会将被@jit修饰函数进行全局上的编译优化,函数内含有的操作越多,优化的空间越大。因此`@mindspore.jit`装饰器修饰的函数最好是内含操作很多的大代码块,而不应将很多细碎的、仅含有少量操作的函数分别打上jit标签。否则,则可能会导致性能没有收益甚至劣化。 + +- 绝大部分计算以及优化都是基于对Tensor计算的优化,建议被修饰的函数应该是用来进行真正的数据计算的函数,而不是一些简单的标量计算或者数据结构的变换。 + +- 被`@mindspore.jit`修饰的函数,若其输入存在常量,那么该函数每次输入值的变化都会导致重新编译,关于变量常量的概念请见[即时编译下的常量与变量](https://www.mindspore.cn/tutorials/zh-CN/master/compile/static_graph.html#%E5%8D%B3%E6%97%B6%E7%BC%96%E8%AF%91%E4%B8%8B%E7%9A%84%E5%B8%B8%E9%87%8F%E4%B8%8E%E5%8F%98%E9%87%8F)。因此,建议被修饰的函数以Tensor或者被Mutable修饰的数据作为输入。避免因多次编译导致的额外性能损耗。 + +## 图优化(前端) + +与传统编译优化技术类似,MindSpore 中的编译优化也是通过一个个 Pass 来完成的。将每个 Pass 的上一个 Pass 所产生的 MindIR 作为输入,经过本 Pass 优化之后,产生新的 MindIR 表示作为输出。一个大的 Pass 可以包含多个小的 Pass,每个小的 Pass 只负责单点的编译优化,如:代数化简、函数内联(inline)、冗余消除等。一个 Pass 产生的优化结果,可能会为其它的 Pass 带来优化机会,故可以循环运行这些 Pass,直到产生的 MindIR 不再发生变化为止。 + +前端编译优化技术有很多,如: 代数化简、函数inline(内联)、冗余消除等。这里仅介绍具有代表性的编译优化技术。 + +### 1 代数化简 + +在传统编译器中,代数化简是一种编译器优化技术,旨在简化源代码中的代数表达式,消除多余计算,提高程序执行效率、减少内存占用等。 + +例如,在以下代码片段中: + +```cpp +int a = x * 1; +int b = x + 0; +int c = x * 0 + y * 1; +``` + +传统编译器根据代数规则和恒等式对识别出的表达式进行等价替换。常见代数规则包括结合律、交换律和分配律等,编译器尽可能将表达式替换成更为简单的形式。通过对 AST(抽象语法树)或 SSA(静态单赋值形式)的分析来进行优化,识别并简化代码为: + +```cpp +a = x; +b = x; +c = y; +``` + +在 MindSpore编译器中,代数化简原理不同于传统编译器,进行处理的是计算图而非传统控制流图,通过调整计算图中算子的执行顺序,或者删除不必要的算子,以保持计算图的简洁性和提高计算效率。 + +例如,在如下Python代码片段中: + +```python +import numpy as np +from mindspore.common import Tensor, jit + +@mindspore.jit +def func(x): + return x + 0 + +m = mindspore.tensor(np.array([[1, 2, 3], [4, 5, 6]]).astype(np.int32)) +out = func(m) +``` + +MindSpore图编译器会把 Python 程序转换为计算图,计算图由多个子图构成。源程序中的代数运算,转换为子图内部的算子调用,可以看到 PrimFunc_Add 算子调用了一次。 + +```text +%para1_x: + +subgraph @1_func_14() { + %0(CNode_7) = PrimFunc_Add(%para1_x, Tensor(shape=[], dtype=Int32, value=0)) + : (, ) -> () + + Return(%0) + : () +} +``` + +通过代数化简,可以直接删除 PrimFunc_Add 算子,简化计算图结构,将 `x + 0` 简化成 `x`。 + +```text +%para1_x: + +subgraph @1_func_14() { + Return(%para1_x) + : () +} +``` + +代数化简能更多地涉及对计算图结构的修改,它通常还与其他编译器优化技术(如常量折叠、常量传播等)结合使用,共同提高程序性能。 + +### 2 函数inline + +在传统编译器中,inline(内联)是一种优化技术,可以把被调用函数的代码直接替换到调用该函数的位置,提高程序运行效率。假设我们有一个 C++ 函数`add`,用于对两个数求和: + +```cpp +int add(int a, int b) { + return a + b; +} + +int main() { + int x = add(3, 5); + int y = add(x, 10); + return y; +} +``` + +编译器通过 inline 将函数体直接替换到调用处,这消除了函数调用的开销,同时为后续优化(如消除冗余计算`3 + 5`,直接在编译期求值替换)创造了条件。这种**用代码替换调用**的思想,正是 inline 的核心。 + +```cpp +int main() { + int x = 3 + 5; // 替换第一次调用 + int y = x + 10; // 替换第二次调用 + return y; +} +``` + +在 AI 框架的计算图编译器中,inline 的目标类似,但操作对象从“函数”变成了“子图”(subgraph)。假设我们有一个 Python 程序: + +```python +from mindspore + +def f2(x: mindspore.Tensor, y: mindspore.Tensor): + return x * 0.5 + y + +@jit +def f1(a: mindspore.Tensor, b: mindspore.Tensor, c: mindspore.Tensor): + x = f2(a, b) + y = f2(a, c) + return x + y + +# 创建3个shape=(2, 4)的随机值Tensor +a = mindspore.ops.randn(2, 4) +b = mindspore.ops.randn(2, 4) +c = mindspore.ops.randn(2, 4) +out = f1(a, b, c) +``` + +首先,MindSpore 的计算图编译器会把 Python 程序转换为计算图。而 Python 程序中的函数调用,会转换为计算图之间的调用,得到类似于下面的原始计算图。其中,主图 f1 调用了 2 次子图 f2。 + +```text +# Params: +%para1_a: +%para2_b: +%para3_c: + +subgraph @f2(%para1_x, %para2_y) { + %0 = PrimFunc_Mul(%para1_x, Float32(0.5)) + + %1 = PrimFunc_Add(%0, %para2_y) + + Return(%1) +} + +subgraph @f1() { + %0(x) = call @f2(%para1_a, %para2_b) # 调用子图f2 + + %1(y) = call @f2(%para1_a, %para3_c) # 调用子图f2 + + %2 = PrimFunc_Add(%0, %1) - No-task node指的是Reshape、ExpandDims、Squeeze、Flatten、FlattenGrad、Reformat等诸类算子没有计算逻辑,不修改内存排布,仅修改shape、format等信息。在图编译结束后,将No-task node转换成ref node,输出跟输入同地址,执行过程中跳过kernel launch,从而达到执行性能优化目的。 + Return(%2) +} +``` -### 算子选择 +通过 inline,可以将子图 f2 展开,合并到主图 f1。 -算子是深度学习框架中的基本执行单元,它们负责执行特定的计算任务,如矩阵乘法、卷积、池化等。算子选择需要综合考虑算子类型、数据类型、硬件平台和算子优化等因素,以选择最优的算子来实现深度学习任务。 +```text +subgraph @f1() { + # 第一次子图inline + %0 = PrimFunc_Mul(%para1_a, Float32(0.5)) # 重复计算步骤 + %1 = PrimFunc_Add(%0, %para2_b) -MindSpore Ascend后端的算子类型有Aclnn kernel/Aclop kernel/Hccl kernel/Cpu kernel,算子选择流程如下图所示: + # 第二次子图inline + %2 = PrimFunc_Mul(%para1_a, Float32(0.5)) # 重复计算步骤 + %3 = PrimFunc_Add(%2, %para3_c) + + %4 = PrimFunc_Add(%1, %3) + + Return(%4) +} +``` + +在 inline 将子图展开之前,编译器可能无法识别到两次调用子图 f2 中的重复操作(此时子图通常被当作黑盒处理)。而通过 inline 将子图展开后,此时编译器可以清晰看到`x * 0.5`被计算了两次,就可以触发编译器进一步的优化:**公共子表达式消除** (CSE, Common Subexpression Elimination),这样就降低了计算量。 + +```text +subgraph @f1() { + %0 = PrimFunc_Mul(%para1_a, Float32(0.5)) # CSE合并重复计算 + + %1 = PrimFunc_Add(%0, %para2_b) + + %2 = PrimFunc_Add(%0, %para3_c) # 直接复用%0 + + %3 = PrimFunc_Add(%1, %2) + + Return(%3) +} +``` + +通过 inline 将子图展开,编译器能够更清晰地识别跨子图的优化机会,除了公共子表达式消除 (CSE),还能够触发算子融合、内存管理等许多优化措施。因此 inline 是计算图编译器的一项重要优化机制,也是许多跨图优化的基础。 + +### 3 冗余消除 + +在传统编译器中,冗余消除包含了多种编译优化技术,旨在通过在编译期间识别出代码中存在冗余的部分并进行消除,达到减少不必要的计算,提高程序的执行效率的目的。 + +通常冗余代码可能是用户出于可读性等目的有意编写的,也可能仅仅是编码过程中的无心之举。此外,编译优化过程本身通过其它优化技术(如:代数化简、inline、公共子表达式消除等)产生的中间结果,也可能带来冗余消除的机会。 + +MindSpore冗余消除的目的及使用的技术与传统编译器类似。不同的是这些冗余优化是在 MindIR 上完成的。例如: + +1. **无用代码消除** + + 假设有如下存在冗余计算的Python代码: + + ```python + import mindspore + + @mindspore.jit + def func(x, y): + a = x + y + b = x - y + c = x * y # 无用代码 + d = a / b + return d + + x = mindspore.tensor(20, mindspore.float32) + y = mindspore.tensor(10, mindspore.float32) + out = func(x, y) + ``` + + MindSpore 图编译器会通过静态分析将 `@mindspore.jit` 修饰的 Python 代码转换为 MindIR 的表示形式并消除其中冗余的 `c = x * y` 的计算,最终生成的 MindIR 如下: + + ```text + # Params: + %para1_x: + %para2_y: + + subgraph @func_1() { + %0(a) = PrimFunc_Add(%para1_x, %para2_y) + : (, ) -> () + %1(b) = PrimFunc_Sub(%para1_x, %para2_y) + : (, ) -> () + %2(d) = PrimFunc_Div(%0, %1) + : (, ) -> () + Return(%2) + : () + } + ``` + +2. **不可达代码消除** + + 假设有如下存在不可达路径的Python代码: + + ```python + import mindspore + + @mindspore.jit + def func(x, y): + a = x + y + if 1 < 0: # 不可达分支 + b = x + y + else: + b = x - y + d = a / b + return d + + x = mindspore.tensor(20, mindspore.float32) + y = mindspore.tensor(10, mindspore.float32) + out = func(x, y) + ``` + + MindSpore图编译器会通过静态分析将 `@mindspore.jit` 修饰的 Python 代码转换为 MindIR 的表示形式并消除其中冗余的控制流分支 `1 < 0` 的代码,最终生成的 MindIR 如下: + + ```text + # Params: + %para1_x: + %para2_y: + + subgraph @func_1() { + %0(a) = PrimFunc_Add(%para1_x, %para2_y) + : (, ) -> () + %1(b) = PrimFunc_Sub(%para1_x, %para2_y) + : (, ) -> () + %2(d) = PrimFunc_Div(%0, %1) + : (, ) -> () + Return(%2) cnode_attrs: {checkpoint: Bool(1)} + : () + } + ``` + +冗余消除在编译优化中扮演着重要的角色,在不改变程序原语义的前提下,能够显著提高程序的执行效率,通过减少不必要的运行时计算节省计算资源。冗余消除通常还与其它编译优化技术结合使用以获得更多消除冗余代码的机会。 + +## 图优化(后端) + +当MindIR图经过前端优化完成后,需要进行进一步优化(包含目标硬件)。优化模式我们分为O0,O1,用参数jit_level表示 + - jit_level=O0: 只做基本的图切分优化,以及算子选择(硬件相关),优点是可以保证IR图的原始结构,编译速度较快。 + - jit_level=O1: 增加图优化和自动算子融合,编译性能有所损失,但模型开始训练后,效率较高 + +MindIR经过本轮优化后,会由runtime模块进行执行,涉及多级流水并发等技术,可参考[多级流水] + +### jit_level=O0 模式 + +O0模式的优化较少,基础的优化主要为后端LazyInline和No-task node执行优化。 + +- **LazyInline**: 主要思想是将函数调用的开销推迟到实际需要调用的时候,这样可以减少编译时的开销,提高编译效率。LazyInline在图编译阶段是将相同的子图结构复用,不展开放在图中,避免图规模较大导致影响编译性能。 + + ![jit_level_lazyinline](./images/multi_level_compilation/jit_level_lazyinline.png) + +- **No-task node执行优化**: No-task node指的是Reshape、ExpandDims、Squeeze、Flatten、FlattenGrad、Reformat等诸类算子没有计算逻辑,不修改内存排布,仅修改shape、format等信息。在图编译结束后,将No-task node转换成ref node,输出跟输入同地址,执行过程中跳过kernel launch,从而达到执行性能优化目的。 + + ![jit_level_no_task](./images/multi_level_compilation/jit_level_no_task.png) + +#### 算子选择 + +算子是深度学习框架中的基本执行单元,它们负责执行特定的计算任务,如矩阵乘法、卷积、池化等。算子选择需要综合考虑算子类型、数据类型、硬件平台和算子优化等因素,以选择最优的算子来实现模型运行效率最高。 + +MindSpore 在Ascend硬件的算子类型有aclnn kernel/aclop kernel/hccl kernel/cpu kernel,算子选择流程如下图所示: ![jit_level_kernelselect](./images/multi_level_compilation/jit_level_kernelselect.png) 1. 算子类型:首先根据算子类型选择为计算算子还是通信算子。 -2. 硬件平台:如果硬件上有对应算子,则优先选择硬件上的算子,否则选择CPU上的异构算子,例如shape相关的计算算子可能只适合在CPU上支持,没有对应的硬件算子。 -3. 算子效率:Ascend上由于Aclnn算子较好的性能,因此计算类型算子如果有对应Aclnn kernel,则优先选择Aclnn kernel,否则就选择Aclop kernel。 -4. 如果上述3步都未选择到算子,则为不支持的算子,算子选择失败退出。 +2. 硬件平台:如果硬件上有对应算子,则优先选择硬件上的算子,否则选择CPU上的算子(异构),例如shape相关的计算算子可能只适合在CPU上支持,没有对应的硬件算子。 +3. 算子效率:ascend硬件由于aclnn算子较好的性能,因此计算类型算子如果有对应aclnn kernel,则优先选择aclnn kernel,否则就选择aclop kernel。 +4. 如果上述3步都未选择到算子,则为不支持的算子,算子选择失败报错。 -### 执行序编排 +#### 执行序编排 +不同图遍历算法产生的执行序在执行性能跟内存上会有较大的差异,如图所示: ![jit_level_exec_order](./images/multi_level_compilation/jit_level_exec_order.png) -不同图遍历算法产生的执行序在执行性能跟内存上会有较大的差异,如上图所示: - - **BFS得到的执行序**:kernel1-> kernel2-> kernel4-> kernel5-> kernel3-> kernel6,内存峰值为5G(kernel3执行后可以把kernel1和kernel2的释放掉,则轮到kernel6执行的时候则能复用,因此kernel6 不用额外申请多的内存)。 - **DFS得到的执行序**:kernel1-> kernel2-> kernel3-> kernel4-> kernel5-> kernel6,内存峰值为4G(kernel3执行后可以把kernel1和kernel2的释放掉,则轮到kernel4和kernel5执行的时候则能复用,因此kernel4和kernel5不用额外申请多的内存)。 @@ -72,13 +387,11 @@ MindSpore Ascend后端的算子类型有Aclnn kernel/Aclop kernel/Hccl kernel/Cp - 其次,内存限制是执行序优化中不可忽视的关键因素。增大并发虽然可以提升计算效率,但往往会显著增加峰值内存需求,从而可能导致内存溢出(OOM)错误,尤其是在资源受限的环境中。因此,优化模块必须权衡并发与内存使用之间的关系,确保在提升并发的同时,不会超出系统的内存容量。 - MindSpore的执行序调整模块结合了基于规则和基于启发式策略的方式,提供bfs/dfs两种执行序编排算法[mindspore.jit(option={"exec_order":"bfs/dfs"})](https://www.mindspore.cn/docs/zh-CN/master/api_python/mindspore/mindspore.jit.html#mindspore.jit),以实现对计算图执行顺序的精细调整,从而在保证计算效率的同时,有效应对内存限制和系统稳定性等多重挑战。 -## O1模式介绍 - -O1主要定位于在O0基础上实现通用、可泛化的AI编译优化,以支持大部分通用训练、推理场景的更好执行性能需求。 +### jit_level=O1 模式 -在当前阶段,O1主要支持了图算融合优化。其主要思路是:在静态图编译阶段,自动识别计算图中相邻的可融合节点,然后将其融合为更大粒度的可执行算子。通过图算融合,实现增加算子计算局部性、减少整体全局内存访存带宽开销等优化效果。通过对15+网络的实测验证,O1能够实现相比O0平均15%的性能加速。特别是对于访存密集型网络,O1优化效果更加显著。 + 当前O1主要支持了图算融合优化。其主要思路是:在编译阶段,自动识别计算图中相邻的可融合节点,然后将其融合为更大粒度的可执行算子。通过图算融合,实现增加算子计算局部性、减少整体全局内存访存带宽开销等优化效果。通过对主流SOTA模型的实测验证,O1能够实现相比O0平均15%的性能加速。特别是对于访存密集型网络,O1优化效果更加显著。 -### 图算融合 +#### 图算融合 MindSpore等主流AI计算框架对用户提供的算子通常是从用户可理解、易使用角度进行定义。每个算子承载的计算量不等,计算复杂度也各不相同。但从硬件执行角度看,这种天然的、基于用户角度的算子计算量划分,并不高效,也无法充分发挥硬件资源计算能力。主要体现在: @@ -131,9 +444,4 @@ MindSpore AKG的整体框架如上图所示: - 后端优化 - 为了进一步提升算子的性能,我们针对不同硬件后端开发了相应的优化pass,如Ascend后端中实现数据对齐、指令映射,GPU后端中实现向量化存取,插入同步指令等,最终生成相应平台代码。 -### 其它图优化技术 - -除了图算融合之外,在后续版本中,O1可能会逐步扩展增加一些其它图优化技术。比如: - -1. KernelPacket:用于在动态shape场景对shape计算进行自动融合和优化; -2. 通算融合:将通信算子与计算算子进行融合。 +总结: MindSpore编译从图捕获模式,IR优化图算融合等各维度对AI模型代码进行优化,很多特性在易用性和性能方面的取舍也有一定挑战。我们也规划进一步分层解耦整个流程,避免黑盒运行,增加开发者理解的门槛。 \ No newline at end of file