[{"data":1,"prerenderedAt":364},["ShallowReactive",2],{"content-query-SRYggfrRCm":3},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"date":10,"cover":11,"type":12,"category":13,"body":14,"_type":358,"_id":359,"_source":360,"_file":361,"_stem":362,"_extension":363},"/technology-blogs/zh/2185","zh",false,"","强悍的编程语法——自由改造MindSpore IR图","了解计算机知识的同学应该知道答案：AI框架和AI芯片的驱动软件会把最开始的Python定义的模型进行多次编译，使它转化成一份硬件可以执行的代码。而且不同的AI框架、不同的计算硬件，在转化过程中产生的中间件的格式也不相同，比如大家应该见过这些格式的AI模型：ckpt、pth、mindir、om、onnx、air等等。","2023-03-03","https://obs-mindspore-file.obs.cn-north-4.myhuaweicloud.com/file/2023/03/09/31093172f2444ab6b78666f566eeb3c3.png","technology-blogs","实践",{"type":15,"children":16,"toc":355},"root",[17,25,44,52,57,66,71,76,81,96,101,106,114,124,132,140,145,153,158,166,171,179,184,194,199,204,212,217,225,239,244,249,257,262,270,275,283,288,296,309,321,332,343],{"type":18,"tag":19,"props":20,"children":22},"element","h1",{"id":21},"强悍的编程语法自由改造mindspore-ir图",[23],{"type":24,"value":8},"text",{"type":18,"tag":26,"props":27,"children":28},"p",{},[29,31,37,39],{"type":24,"value":30},"**作者：**",{"type":18,"tag":32,"props":33,"children":34},"strong",{},[35],{"type":24,"value":36},"AI安全Mr.Jin",{"type":24,"value":38}," ｜",{"type":18,"tag":32,"props":40,"children":41},{},[42],{"type":24,"value":43},"来源：知乎",{"type":18,"tag":26,"props":45,"children":46},{},[47],{"type":18,"tag":32,"props":48,"children":49},{},[50],{"type":24,"value":51},"背景",{"type":18,"tag":26,"props":53,"children":54},{},[55],{"type":24,"value":56},"众所周知，我们在设计、描述AI模型的时候，大多是用AI框架提供的Python API去构建我们的模型，比如MindSpore的nn.Cell，nn.Conv等等。但你有没有想过，最终你的硬件运行这个模型的时候，使用的是什么代码格式？很显然，底层硬件看不懂Python代码。了解计算机知识的同学应该知道答案：AI框架和AI芯片的驱动软件会把最开始的Python定义的模型进行多次编译，使它转化成一份硬件可以执行的代码。而且不同的AI框架、不同的计算硬件，在转化过程中产生的中间件的格式也不相同，比如大家应该见过这些格式的AI模型：ckpt、pth、mindir、om、onnx、air等等。",{"type":18,"tag":26,"props":58,"children":59},{},[60],{"type":18,"tag":61,"props":62,"children":65},"img",{"alt":63,"src":64},"image.png","https://fileserver.developer.huaweicloud.com/FileServer/getFile/cmtybbs/e64/154/b38/90a1d5d431e64154b387b3660e356ff5.20230309055645.47136478805958342012715926428325:20230309062000:2400:DA432B4E9B560152A54B217AC1140B9C2EAF4BEF93E28F8856E48C4134735E92.png",[],{"type":18,"tag":26,"props":67,"children":68},{},[69],{"type":24,"value":70},"AI模型格式转换",{"type":18,"tag":26,"props":72,"children":73},{},[74],{"type":24,"value":75},"MindSpore IR就是昇思MindSpore模型在编译过程中的一种中间表达格式，全场景统一部署依赖于MindSpore IR，不同设备都可以加载MindSpore IR模型或者基于MindSpore IR模型进行编译运行。如果我们有一个MindSpore IR格式的模型，我们就可以对它进行改造，然后使用原有的编译方法对它修改后的MindSpore IR模型进行编译优化，再部署在不同的硬件设备上，而不用对不同的硬件做定制化的语法改造。为了看懂本文下面的章节，建议大家看两遍MindSpore官网对于MindSpore IR的介绍[1]，了解一下MindSpore IR图的构图格式以及形态。",{"type":18,"tag":26,"props":77,"children":78},{},[79],{"type":24,"value":80},"那么我们怎样对MindSpore IR模型进行修改呢？昇思MindSpore提供了FuncGraph[2]这个C++类来表示MindIR模型，同时提供了很多API对FuncGraph进行修改，比如图节点的增删查改和节点复制、节点遍历等等，详细情况可以参考gitee目录。接下来我们详细讲讲怎么构造一个FuncGraph来表示计算流程。",{"type":18,"tag":26,"props":82,"children":83},{},[84,89,91],{"type":18,"tag":32,"props":85,"children":86},{},[87],{"type":24,"value":88},"1.",{"type":24,"value":90}," ",{"type":18,"tag":32,"props":92,"children":93},{},[94],{"type":24,"value":95},"如何得到一个FuncGraph？",{"type":18,"tag":26,"props":97,"children":98},{},[99],{"type":24,"value":100},"可以参考昇思MindSpore提供的ut的具体操作[3]。",{"type":18,"tag":26,"props":102,"children":103},{},[104],{"type":24,"value":105},"我们可以使用mindspore的load()[4]接口加载一个mindir后缀的模型文件得到一个模型的FuncGraph：",{"type":18,"tag":26,"props":107,"children":108},{},[109],{"type":18,"tag":61,"props":110,"children":113},{"alt":111,"src":112},"cke_1049.png","https://fileserver.developer.huaweicloud.com/FileServer/getFile/cmtybbs/e64/154/b38/90a1d5d431e64154b387b3660e356ff5.20230309055739.31146131387679124684298994550962:20230309062000:2400:859E2B8E4877F94FCF3F9F9C0B31568397628C30AB002DCD33961D43195814A4.png",[],{"type":18,"tag":115,"props":116,"children":118},"pre",{"code":117},"// 设置shape为(1, 1)\nShapeVector tensor_shape{1, 1};\n// 初始化Tensor，此时Tensor的值为0\ntensor::TensorPtr const_one_tensor = std::make_shared(mindspore::kNumberTypeInt32, tensor_shape);\n// 把Tensor的值赋为1\nint *int_data = reinterpret_cast(const_one_tensor->data_c());\nint_data[0] = 1;\n// 包装成ValueNode\nmindspore::ValueNodePtr const_one_tensor_vnode = std::make_shared(const_one_tensor);\n",[119],{"type":18,"tag":120,"props":121,"children":122},"code",{"__ignoreMap":7},[123],{"type":24,"value":117},{"type":18,"tag":26,"props":125,"children":126},{},[127],{"type":18,"tag":61,"props":128,"children":131},{"alt":129,"src":130},"cke_1891.png","https://fileserver.developer.huaweicloud.com/FileServer/getFile/cmtybbs/e64/154/b38/90a1d5d431e64154b387b3660e356ff5.20230309055830.98262733558927578852875291231965:20230309062000:2400:13C2BBF3BB5C88F800E2AF2702B44B00457B88BFDCA43B546FFE03A01BF5109D.png",[],{"type":18,"tag":115,"props":133,"children":135},{"code":134},"mindspore::ValueNodePtr add_v_node = std::make_shared(add_prim);\n(void)func_graph->AddValueNode(add_v_node);\n",[136],{"type":18,"tag":120,"props":137,"children":138},{"__ignoreMap":7},[139],{"type":24,"value":134},{"type":18,"tag":26,"props":141,"children":142},{},[143],{"type":24,"value":144},"最后，我们再创建一个CNode，来实现这个“加”的过程，加法算子的valueNode放最前面，加数放后面：",{"type":18,"tag":115,"props":146,"children":148},{"code":147},"CNodePtr add_c_node = func_graph->NewCNode({add_v_node, x, const_one_tensor_vnode});\n",[149],{"type":18,"tag":120,"props":150,"children":151},{"__ignoreMap":7},[152],{"type":24,"value":147},{"type":18,"tag":26,"props":154,"children":155},{},[156],{"type":24,"value":157},"最后，我们还需要给这个CNode设置abstract，告诉编译器这个CNode的输出形状和数据类型，以便让编译器检查整图算子之间输入输出的一致性。由于这个加法算子的输出形状和数据类型和const_one_tensor是一致的，所以直接取const_one_tensor的abstract作为CNode的abstract：",{"type":18,"tag":115,"props":159,"children":161},{"code":160},"add_c_node->set_abstract(const_one_tensor->ToAbstract());\n",[162],{"type":18,"tag":120,"props":163,"children":164},{"__ignoreMap":7},[165],{"type":24,"value":160},{"type":18,"tag":26,"props":167,"children":168},{},[169],{"type":24,"value":170},"最后的最后，我们还需要引入一个return算子，把Add的结果作为图的输出：",{"type":18,"tag":115,"props":172,"children":174},{"code":173},"mindspore::ValueNodePtr return_v = std::make_shared(\"Return\"));\n(void)func_graph->AddValueNode(return_v);\nmindspore::CNodePtr return_c_node = fg_clone->NewCNode({return_v, add_c_node});\nreturn_c_node ->set_abstract(const_one_tensor->ToAbstract());\n",[175],{"type":18,"tag":120,"props":176,"children":177},{"__ignoreMap":7},[178],{"type":24,"value":173},{"type":18,"tag":26,"props":180,"children":181},{},[182],{"type":24,"value":183},"注意，在上述过程中，我们添加到FuncGraph里面的自变量节点x和常量Tensor 1都是作为ValueNode插入的，而计算过程“加”和“返回”都要使用CNode来表示。",{"type":18,"tag":26,"props":185,"children":186},{},[187,189],{"type":24,"value":188},"**3.**",{"type":18,"tag":32,"props":190,"children":191},{},[192],{"type":24,"value":193},"如何在FuncGraph中插入子图？",{"type":18,"tag":26,"props":195,"children":196},{},[197],{"type":24,"value":198},"以上讲了如何创建一个 f(x)=x+1的计算图，如果我们现在有另外一个计算函数g(x)，并且要把这个计算逻辑加到原来的图里面，得到一个fg(x)=g(x)+1的计算图，该怎么做呢？",{"type":18,"tag":26,"props":200,"children":201},{},[202],{"type":24,"value":203},"首先，假设g(x)对应的FuncGraph图为 g_graph，那么我们可以用Partial算子把func_graph的自变量x传到g(x)里面去：",{"type":18,"tag":115,"props":205,"children":207},{"code":206},"/ 创建Partial算子的primitive\nmindspore::ValueNodePtr partial_vnode = std::make_shared(\"Partial\", kSideEffectPropagate);\n(void)func_graph->AddValueNode(partial_vnode);\n// 把子图g_graph作为一个vnode\nmindspore::ValueNodePtr subgraph_node = std::make_shared(g_graph);\nsubgraph_node ->set_abstract(g_graph->ToAbstract());\n(void)func_graph->AddValueNode(subgraph_node);\n// 创建Cnode，把x赋给g_graph\nmindspore::CNodePtr partial_cnode = func_graph->NewCNode({partial_vnode, subgraph_node, x});\n",[208],{"type":18,"tag":120,"props":209,"children":210},{"__ignoreMap":7},[211],{"type":24,"value":206},{"type":18,"tag":26,"props":213,"children":214},{},[215],{"type":24,"value":216},"然后，再根据2.2里面的流程把partial_cnode和常数1相加，再返回。",{"type":18,"tag":115,"props":218,"children":220},{"code":219},"CNodePtr add_c_node = func_graph->NewCNode({add_v_node, partial_cnode, const_one_tensor_vnode});\n",[221],{"type":18,"tag":120,"props":222,"children":223},{"__ignoreMap":7},[224],{"type":24,"value":219},{"type":18,"tag":26,"props":226,"children":227},{},[228,233,234],{"type":18,"tag":32,"props":229,"children":230},{},[231],{"type":24,"value":232},"4.",{"type":24,"value":90},{"type":18,"tag":32,"props":235,"children":236},{},[237],{"type":24,"value":238},"如何在FuncGraph中插入switch控制流节点？",{"type":18,"tag":26,"props":240,"children":241},{},[242],{"type":24,"value":243},"switch控制流结构一般指的是这种计算结构：如果x>1，那么f(x)=x+1；如果x \u003C=1，那么f(x)=x-1。我们可以使用switch算子来实现这个计算流程。switch算子的输入格式一般是{条件判断节点，分支1，分支2}。",{"type":18,"tag":26,"props":245,"children":246},{},[247],{"type":24,"value":248},"首先，我们需要创建条件判断节点：",{"type":18,"tag":115,"props":250,"children":252},{"code":251},"ValueNodePtr greater_v_node = std::make_shared(\"Greater\");\n(void)func_graph->AddValueNode(greater_v_node);\nCNodePtr greater_c_node = func_graph->NewCNode({greater_v_node, x, const_one_tensor_vnode});\nShapeVector the_shape{1, 1};\n// 注意，此处要使用bool类型\ntensor::TensorPtr greater_tensor = std::make_shared(mindspore::kNumberTypeBool, the_shape);\ngreater_c_node->set_abstract(greater_tensor->ToAbstract());\n(void)func_graph->AddNode(greater_c_node);\n",[253],{"type":18,"tag":120,"props":254,"children":255},{"__ignoreMap":7},[256],{"type":24,"value":251},{"type":18,"tag":26,"props":258,"children":259},{},[260],{"type":24,"value":261},"然后创建分支1和分支2。假设f(x)=x+1对应的子图是sub_graph_1，f(x)=x-1对应的子图是sub_graph_2，我们要用2.3小节的方式把sub_graph_1和sub_graph_2用Partial算子包装后传到switch算子里面：",{"type":18,"tag":115,"props":263,"children":265},{"code":264},"// 获得主图的manager\nauto mgr = mindspore::Manage(func_graph);\n// 把两个子图加到主图里面\nmgr->AddFuncGraph(sub_graph_1);\nmgr->AddFuncGraph(sub_graph_2);\n// 假设创建partial分支的函数为AddPartialBranch\nmindspore::CNodePtr switch_partial_1 = AddPartialBranch(sub_graph_1, x);\nmindspore::CNodePtr switch_partial_2 = AddPartialBranch(sub_graph_2, x);\n// 声明switch算子\nmindspore::ValueNodePtr switch_v_node = std::make_shared(\"Switch\"));\n(void)func_graph->AddValueNode(switch_v_node);\n// 把条件控制算子、两个分支加到switch算子里面\nmindspore::CNodePtr switch_c_node = \nfunc_graph->NewCNode({switch_v_node, greater_c_node, switch_partial_1, switch_partial_2});\nswitch_c_node->set_abstract(switch_partial_1->ToAbstract());\nfunc_graph->AddNode(switch_c_node);\n",[266],{"type":18,"tag":120,"props":267,"children":268},{"__ignoreMap":7},[269],{"type":24,"value":264},{"type":18,"tag":26,"props":271,"children":272},{},[273],{"type":24,"value":274},"这样就完成了switch控制流算子的构建，最后，还要加一个call算子才能让switch算子执行：",{"type":18,"tag":115,"props":276,"children":278},{"code":277},"mindspore::CNodePtr call_cnode = func_graph->NewCNode({switch_c_node});     \nfunc_graph->AddNode(call_cnode);\n",[279],{"type":18,"tag":120,"props":280,"children":281},{"__ignoreMap":7},[282],{"type":24,"value":277},{"type":18,"tag":26,"props":284,"children":285},{},[286],{"type":24,"value":287},"最后再把call node传给return node或者下面的其它节点。",{"type":18,"tag":26,"props":289,"children":290},{},[291],{"type":18,"tag":32,"props":292,"children":293},{},[294],{"type":24,"value":295},"参考链接",{"type":18,"tag":26,"props":297,"children":298},{},[299,301],{"type":24,"value":300},"[1]",{"type":18,"tag":302,"props":303,"children":307},"a",{"href":304,"rel":305},"https://www.mindspore.cn/docs/zh-CN/r1.9/design/mindir.html",[306],"nofollow",[308],{"type":24,"value":304},{"type":18,"tag":26,"props":310,"children":311},{},[312,314],{"type":24,"value":313},"[2]",{"type":18,"tag":302,"props":315,"children":318},{"href":316,"rel":317},"https://gitee.com/mindspore/mindspore/blob/r1.9/mindspore/core/ir/func%5C_graph.h",[306],[319],{"type":24,"value":320},"https://gitee.com/mindspore/mindspore/blob/r1.9/mindspore/core/ir/func\\_graph.h",{"type":18,"tag":26,"props":322,"children":323},{},[324,326],{"type":24,"value":325},"[3]",{"type":18,"tag":302,"props":327,"children":330},{"href":328,"rel":329},"https://gitee.com/mindspore/mindspore/tree/r1.9/mindspore/core/ir",[306],[331],{"type":24,"value":328},{"type":18,"tag":26,"props":333,"children":334},{},[335,337],{"type":24,"value":336},"[4]",{"type":18,"tag":302,"props":338,"children":341},{"href":339,"rel":340},"https://gitee.com/mindspore/mindspore/tree/r1.9/tests/ut/cpp/ir",[306],[342],{"type":24,"value":339},{"type":18,"tag":26,"props":344,"children":345},{},[346,348],{"type":24,"value":347},"[5]",{"type":18,"tag":302,"props":349,"children":352},{"href":350,"rel":351},"https://www.mindspore.cn/docs/zh-CN/r1.9/api%5C_python/mindspore/mindspore.load.html",[306],[353],{"type":24,"value":354},"https://www.mindspore.cn/docs/zh-CN/r1.9/api\\_python/mindspore/mindspore.load.html",{"title":7,"searchDepth":356,"depth":356,"links":357},4,[],"markdown","content:technology-blogs:zh:2185.md","content","technology-blogs/zh/2185.md","technology-blogs/zh/2185","md",1776506120676]