• 项目经验分享:实现一个昇思MindSpore 图层 IR 融合优化 pass


    暑期2021结项审核正在进行中,项目经验分享也未停止。本期分享来自MindSpore 社区胡秋越同学带来的项目经验:实现一个 MindSpore 图层 IR 融合优化 pass。

    欢迎大家持续投喂编辑部信箱,积极分享项目经验以及开源心得!

    邮箱

    summer@iscas.ac.cn

    微信

    项目信息

    项目名称

    实现一个 MindSpore 图层 IR 融合优化 pass

    项目背景

    MindSpore 简介

    随着近年来深度学习的快速发展,国外大的互联网公司谷歌(Google)和脸书(Facebook)分别推出了自己的深度学习框架:采用静态计算图的Tensorflow与采用动态图的Pytorch,二者都受到了广泛的应用。国内华为公司推出的“昇思”(MindSpore),结合了动静态图,发挥了二者的优势。

    MindSpore 架构

    MindSpore总体架构如下图所示:

    https://www.mindspore.cn/tutorials/zh-CN/r1.5/introduction.html

    图1.MindSpore架构图

    IR(Intermediate Representation)简介

    IR本质上是一种中间表示形式,是一个完整编译工具的一部分:

    中间表示(IR)是编译器或虚拟机内部用于表示源代码的数据结构或代码。IR旨在有助于进一步处理,如优化和翻译。“良好”的IR必须准确——能够在不丢失信息的情况下表示源代码——并且独立于任何特定的源语言或目标语言。IR可以采取以下几种形式之一:内存中的数据结构,或程序可读的基于元组或堆栈的特殊代码。在后一种情况下,它也被称为中间语言。

    随着不同的应用场景和需求,出现了大量不同的编程语言和不同的处理器架构,深度学习有如此多不同的前后端,如何找到一个桥梁更有效实现他们之间的优化和影射,这时体现出IR的重要性。

    图2.IR桥梁图

    基于图层IR的实际应用价值,我们有必要对图层进行IR融合优化,对应的应用方案即本项目课题-编写识别特定结构的图层 IR融合优化pass。 

    方案描述

    根据项目要求,实现一个图层 IR 融合优化 pass。在图的编译阶段,通过各个 pass 进行优化,每个 pass 将识别到的图中特定结构,识别成功后进一步进行算子替换,将原网络 图中特定的结构中的算子进行融合,优化替换为相应更简单结构,从而实现网络图结构的优化。本次项目的图层融合优化 pass 是实现一个 prelu 的融合优化 pass,将 neg,relu,mul,add 算子组成的特定结构进行融合优化为 prelu 算子结构,具体的优化前特定算子结构如下图所示。

    图3.优化前结构图

    为实现上图结构的优化,在 ir_fusion 文件中添加编写的优化 pass,即 prelu_fusion,实现的功能具体为识别上图结构,将输入 x,weight 通过算子 prelu 运算从而转换为新的网络结构,并输出新的网络图结点。在图编译的过程中,通过 compilegrahimpl 函数的调用,将运行各类优化 pass,将编写的 prelu_fusion 优化 pass 添加在 ascend_backend_optimization.cc 调用的pass 序列中,即可实现 prelu 的优化 pass。具体的优化后的网络图结构如下图所示。

    图4.优化后结构图

    编写文件

      依照方案编写文件,按照优化前网络结构图,对指定识别结构进行识别并替换,这里的代码偏向底层,各个pass的结构差别都不大,能产出代码的前提就是能看懂各个pass的数据结构及其依赖关系:

    1. namespace mindspore {
    2. namespace opt {
    3. const BaseRef PReluFusion::DefinePattern() const {
    4. VectorRef x_pattern({prim::kPrimRelu, VectorRef({prim::kPrimNeg, x_})});
    5. VectorRef mul_pattern({prim::kPrimMul, VectorRef({prim::kPrimNeg, weight_}), x_pattern});
    6. VectorRef pattern({prim::kPrimAdd, VectorRef({prim::kPrimRelu, x_}), mul_pattern});
    7. return pattern;
    8. }

    在识别到对应算子结构后,需要将其替换为优化后的算子结构,这里即为prelu算子,相关代码如下:

    1. const AnfNodePtr PReluFusion::Process(const FuncGraphPtr &graph, const AnfNodePtr &node, const
    2. EquivPtr &equiv) const {
    3. MS_EXCEPTION_IF_NULL(graph);
    4. MS_EXCEPTION_IF_NULL(node);
    5. MS_EXCEPTION_IF_NULL(equiv);
    6. BaseRef &x_gnode = (*equiv)[x_];
    7. BaseRef &weight_gnode = (*equiv)[weight_];
    8. auto x = utils::cast(x_gnode);
    9. auto weight = utils::cast(weight_gnode);
    10. MS_EXCEPTION_IF_NULL(x);
    11. MS_EXCEPTION_IF_NULL(weight);
    12. auto prim = std::make_shared(kPReluOpName);
    13. MS_EXCEPTION_IF_NULL(prim);
    14. std::vectorinputs = {NewValueNode(prim), x, weight};
    15. auto fusion_node = graph->NewCNode(inputs);
    16. MS_EXCEPTION_IF_NULL(fusion_node);
    17. fusion_node->set_abstract(node->abstract());
    18. fusion_node->set_scope(node->scope());
    19. return fusion_node;
    20. }

    其中这里的kPReluOpName是自己定义的,指向“PReLU”这个算子,在测试过程中也因为这个算子而掉过一个坑,也就是下文分享的问题。

    问题处理经验分享

    完成代码的编写后,在进行测试的过程中遇到一些 bug,一个主要错误为运行测试用例时发生错误:

    我根据错误信息按照以下步骤进行错误排查:

    查看编写代码是否出错

    查看出错测试用例代码,分析可能出错点

    逐层加深到更多文件,查看与编写的代码可能存在冲突的文件

    按照以上步骤,进行检查,排除问题。

    1.  首先对编写文件进行检查

    在这方面由于编写前已经将相关文件查阅较为熟悉,编译过程与demo测试过程都没有问题,我主观上便将其排除了。

    2.  查看出错测试用例代码

    在仔细查看其模型代码后,发现其与我所编写的文件并未存在直接冲突,可能是其中调用的文件中与我所编写的文件存在冲突,接下来便是一一查看与其相关的文件,检查可能存在的冲突。

    3.   检查冲突文件

    在对涉及文件一一查阅的过程中,发现其中涉及的文件延伸更广,这让我发现要通一一查阅文件来判断冲突文件几乎是不可能的,这让我不得不另找出路;

    既然查阅所有文件不可取,我想到先对编写文件涉及的代码文件进行查阅,在其中mindspore/ mindspore / ccsrc / utils / utils.h 中我发现编写pass及其附近pass的涉及算子如下

    1. namespace mindspore {
    2. constexpr auto kSquareSumV1OpName = "SquareSumV1";
    3. constexpr auto kSquareSumV2OpName = "SquareSumV2";
    4. constexpr auto kClipByNormNoDivSumOpName = "ClipByNormNoDivSum";
    5. constexpr auto kPReluOpName = "PReLU";
    6. constexpr auto kGreaterOpName = "Greater";
    7. constexpr auto kSqrtOpName = "Sqrt";

    这让我想到会不会是在pass中替换算子的定义错误,怀着这个想法,我在分配的昇腾910环境下对出错的测试用例进行测试,神奇的发现竟然并没有报错,这让我思考推送到社区代码与本地代码的差别,最终发现是由于优化后算子的定义错误:自定义的prelu算子与实际存在的PReLU算子不匹配,导致了测试用例的错误。而实际上我们需要将自定义算子与已有的算子进行匹配,将匹配到的算子结构替换为已有的优化的算子结构。

    最终终于解决了错误,最后在导师的指导下在 gitee 上提出 issue 与 pr,在导师审查后进行修改优化,也让我进一步学习了为社区贡献代码的规范与进一步优化代码。

    经验总结

      作为一个初识MindSpore(https://www.mindspore.cn)的小白,这次的项目开发经验无疑让我受益匪浅,迈出面向MindSpore的第一步。

    沟通是最好的桥梁

    项目的完成离不开与导师的沟通交流:

    初期学习方案的制定多亏导师的建议与帮助,让我对项目开发有了一定方向;

    按照计划,完成每一阶段及时向导师反馈,并将其中遇到的未解决的问题咨询导师,导师也很耐心地为我寻找解决的方案提供帮助;

    在社区仓库代码的合并过程中与导师沟通交流,规范代码,减少冗余操作,经过导师审查后代码成功并入仓库。

    心得体会

    本次项目的开发偏底层,在项目开发的过程中我更多的时间是在查看相关文件,弄懂编写一个优化pass涉及的文件及其依赖,知道了代码该怎么写,实际的编写过程就显得相对容易,开发的过程中,让我印象深刻的有几点:

    公共环境要慎重操作:在操作分配的公共环境时,由于自己的疏忽操作,影响了整个公共环境的正常运行,这点让我印象尤为深刻,幸好问题并不大,及时得到了解决;

    与导师及时沟通,积累经验:在进行项目开发时,经验显得尤为重要,项目开发初期,由于缺乏经验,让我不知从何下手,好在通过导师的沟通帮助,让我的开发方向明朗了起来,按照计划稳步推进项目;

    深入理解项目内容,确定开发方向后,再进行实际开发:经过一段时间的学习,我认为已经明确了开发方向,进行实际开发后,因为理解的错误,导致了不少问题,如上述中的问题④,便是对相关算子的替换的理解失误造成的;事实上,在编程中因为思考或理解的不到位导致的错误屡见不鲜。

    规范化代码与减少冗余操作:在将代码归并进社区的过程中,我深刻感觉到因为自己编写过程中代码的规范问题与冗余操作问题,当然这些问题不可能完全规避,但这也让我感受到规范化与精简化的社区标准,在平时就应该有意识的将其作为自己的标准。

    自我评价

    完成了项目的要求与目标,解决了对应issue

    https://gitee.com/mindspore/mindspore/issues/I44PH8?from=project-issue,

    将产出代码以pr

    https://gitee.com/mindspore/mindspore/pulls/21572

    的形式合并到社区仓库,实现了一个 MindSpore 图层 IR 融合优化 pass。

    虽然项目的总体难度并不难,但在开发的过程中遇到的困难也不少,收获也很多,让我受益匪浅。

    MindSpore官方资料

    官方QQ群 : 486831414

    官网:https://www.mindspore.cn/

    Gitee : https : //gitee.com/mindspore/mindspore

    GitHub : https://github.com/mindspore-ai/mindspore

    论坛:https://bbs.huaweicloud.com/forum/forum-1076-1.html 

  • 相关阅读:
    Diffusion Models在目标检测领域的应用
    外汇天眼:ADSS已与analytics KX供应商就合作达成一致
    【故障公告】阿里云抢占式实例服务器被释放引发全站故障
    GPU算力租用平台推荐
    Conan安装第三方依赖库时SSL验证失败解决办法
    tp6+vue-elementui-admin实现前后端权限分离框架
    部署一个自己的GPT客户端[以ChatGPT-Next-Web为例]
    AI音乐大模型时代:版权归属与创意产业的新生长点
    git 项目管理操作
    上海00后985毕业女生月薪1.2w,想找年薪40万程序员,网友表示很不理解
  • 原文地址:https://blog.csdn.net/Kenji_Shinji/article/details/125535060