MegEngine 使用小技巧:如何解读 MegCC 编译模型几个阶段 Pass 的作用
MegCC 是一个真真实实的深度学习模型编译器,具备极其轻量的 Runtime 二进制体积,高性能,方便移植,极低内存使用以及快启动等核心特点。用户可在 MLIR 上进行计算图优化,内存规划,最后通过预先写好的 code 模版进行代码生成。
MegCC 中主要的 Pass
MGBToKernelPass:这个 Pass 主要将 MGB IR 转换为 Abstract Kernel IR,转换过程中主要完成几件事情:
将 MGB IR 中的所有输入输出 Tensor 类型转换为 Buffer 类型。
将 MGB IR 中的所有枚举参数转换为对应的字符,这样 Abstract Kernel IR 就可以完全和 MegEngine 解耦。
将一些内存搬运相关的 Opr 全部转换为 Relayout,如:Concat,SetSubtensor 等 Opr(node-level optimizations)。
将判断 Opr 是静态 shape 还是动态 shape,动态 shape 就是输入 tensor 的 shape 需要依赖输入的值才能计算出来的,如:输出一个 tensor 中所有大于 1 的数。如果是静态 shape 直接转换到 Abstract Kernel IR,如果是动态 shape 直接转换到 Kernel IR 的 Instruction 中。
MGBFuseKernelPass:应用在 MGB IR 上,基于 mlir 的模板匹配的方法尽可能的完成 kernel 的融合,比如连续两个 typecvt 合并成为一个 typecvt 等(block-level optimizations,算子融合)。
MemoryForwardingPass:将遍历 Abstract Kernel IR 所有可能不用计算,直接 share 输入内存的 Opr,如果这些 Opr 确实不用计算,则直接 forward memory,如果这些 Opr 需要进行内存搬运,则会用 Relayout Opr 替换原来的 Opr(node-level optimizations)。KernelMaterializationPass:将所有 Abstract Kernel IR 都装载上真正 Kernel code 并转化为 KernelCall,然后添加对应的 KernelDef。KernelCall 和 KernelDef 之间通过 symbol 进行匹配。
StaticMemoryPlanningPass:将所有静态 shape 的 memref 进行内存规划,内存规划算法使用改进的 MegEngine 的内存规划算法--PushDown 算法,能够极大程度的压缩运行时内存使用量。同时将 mlir 的 memref.Alloc 替换为 Kernel IR 的 MemPlan,MemPlan 中主要记录了内存规划的一整块 memref 以及该 Tensor 在规划的内存中的偏移量(dataflow-level optimizations,静态内存规划)。
上面的 Pass 就完成模型的图优化、内存规划以及 Kernel 生成,上文提到的后端优化即在 Kernel 生成阶段体现,目前 MegCC 主要使用人工优化的 Kernel 模版。最终可以根据 Runtime 中定义的模型格式 dump 编译之后的模型,以及生成计算模型所需的 Kernel 文件。 下面以一个简单的模型为例,使用 MegCC 的辅助工具(下载 Release 包) mgb-importer 和 megcc-opt,观察经过各个 Pass 的处理 IR 的变化。也可使用 mgb-to-tinynn 工具直接完成模型的编译过程,详见 MegCC 入门文档。
dump 模型(使用 megengine)
导入模型
这一步主要将上面 dump 好的 MegEngine 模型 import 到 MegCC 的 MGB IR 中,使用的工具是 MegCC 的 release 包中 bin/mgb-importer,执行命令:
执行完成之后打开 test_model_mgb_ir.mlir,结果如下:
这里使用的 LLVM 的 IR 结构,参考 LLVM 的 IR 模块组。从上面的 IR 可以清楚的看到整个模型变成了一个 mlir 的模块,其中模型的入口变成了一个 func,还有如下变化:
参数全部转换为 MGB.ParamStorage,并使用 MGB.ParamProvider 在 func 中作为接口访问,MGB.ParamStorage 并 MGB.ParamProvider 通过 sym_name 连接在一起,如上面 const{5}[0] 这个字符就是一个符号。这个 test_model.mge 变成了名字为 test_model_mgb_ir 的 func 类型,这个 func 的参数就是整个 test_model.mge 的输入 Tensor,这里是:%arg0: tensor<1x1x10x10xf32> {mgb.func_arg_name = "data"}。test_model.mge 中的所有算子一一对应的转换为 MGB IR,如: MGB.ConvBias,MGB.MatrixMul 等。在 mlir 中每个 op 都有一个输入和对一个输入,这些输入输出可以通过链接关系构成一张计算图。
将 Abstract Kernel IR 加载上代码,并降低到 Kernel IR
执行之后在终端中将输出:
上面就是最后编译完成之后的模型:
所有的内核都以 Kernel.KernelDef 字串形式进行定义,在后面将以 Kernel.KernelCall 字串形式进行调用,所有的 Kernel.KernelDef 都是以字串形式存在的纯 C 代码
Kernel.KernelDef 和 Kernel.KernelCall 之间使用符号进行对应,如上面的 kernel_conv2d_3x3_NCHW_DENSE_p1x1_s1x1_d1x1_f32f32f32f32_bias_RELU 字符。所有的内存资源都是以 Kernel.MemPlan 的形式进行申请,所有运算符的参数都在 Kernel.KernelCall 以字符串或者其字符的形式传递给具体的内核每一个 memref 都确定了一个地图来指定其在内存计划中的访问列表。将上面的 Kernel IR 按照 Runtime 确定的模型格式进行序列化以及将对应的代码串写到 xxx.c 文件中,就完成了整个模型的编译过程。
MegCC 中大多数 Kernel 为人工优化并提前写好的 Kernel 模板,这些模板会根据具体的 Operator 参数生成对应的 Kernel。大多数为人工优化的 Kernel 的原因是:目前在 CPU 上不搜参的情况下,mlir 生成的 Kernel 性能和手写的 Kernel 还有一定的距离,但是自动生成 Kernel 的方法长期来看是比较可取的。MegCC 现已开源,仓库地址:github.com/MegEngine/MegCC,欢迎试用、star、issue。
附:
更多 MegEngine 信息获取,您可以:查看文档和 GitHub 项目,或加入 MegEngine 用户交流 QQ 群:1029741705。欢迎参与 MegEngine 社区贡献,成为 Awesome MegEngineer,荣誉证书、定制礼品享不停。
评论