2329 lines
114 KiB
C++
2329 lines
114 KiB
C++
/**
|
||
* Copyright 2019-2021 Huawei Technologies Co., Ltd
|
||
*
|
||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
* you may not use this file except in compliance with the License.
|
||
* You may obtain a copy of the License at
|
||
*
|
||
* http://www.apache.org/licenses/LICENSE-2.0
|
||
*
|
||
* Unless required by applicable law or agreed to in writing, software
|
||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
* See the License for the specific language governing permissions and
|
||
* limitations under the License.
|
||
*/
|
||
|
||
#include "include/transform/graph_ir/convert.h"
|
||
|
||
#include <cinttypes>
|
||
#include <algorithm>
|
||
#include <stack>
|
||
#include "include/common/utils/utils.h"
|
||
|
||
#include "base/core_ops.h"
|
||
#include "frontend/operator/ops.h"
|
||
#include "utils/log_adapter.h"
|
||
#include "ir/graph_utils.h"
|
||
#include "utils/symbolic.h"
|
||
#include "include/common/utils/config_manager.h"
|
||
#include "include/common/utils/convert_utils.h"
|
||
#include "utils/ms_context.h"
|
||
#include "utils/check_convert_utils.h"
|
||
#include "include/transform/graph_ir/op_adapter_map.h"
|
||
#include "ops/state_ops.h"
|
||
#include "ops/array_ops.h"
|
||
#include "ops/elewise_calculation_ops.h"
|
||
#include "ops/math_ops.h"
|
||
#ifdef ENABLE_D
|
||
#include "ops/save_ops.h"
|
||
#endif
|
||
#include "transform/graph_ir/op_adapter.h"
|
||
#include "transform/graph_ir/op_adapter_desc.h"
|
||
|
||
namespace mindspore { //namespace:命名空间
|
||
namespace transform {
|
||
using std::endl;
|
||
|
||
using ge::Operator;
|
||
using mindspore::kAnyValue;
|
||
using std::make_shared;
|
||
using std::shared_ptr;
|
||
using std::string;
|
||
using std::vector;
|
||
using Variable = ge::op::Variable;
|
||
using Constant = ge::op::Constant;
|
||
using Assign = ge::op::Assign;
|
||
using Data = ge::op::Data;
|
||
|
||
namespace {
|
||
std::vector<AnfNodePtr> GetOrderedCNodes(const FuncGraphPtr fg) { //该函数的功能是通过拓扑排序获取按顺序排列的CNode节点。
|
||
MS_EXCEPTION_IF_NULL(fg); ////检查传入的是否为空,如果为空则抛出异常
|
||
auto BelongSameGraph = std::bind(IncludeBelongGraph, fg, std::placeholders::_1);
|
||
auto succ_include_fv = [&fg](const AnfNodePtr &node) -> std::vector<AnfNodePtr> {
|
||
std::vector<AnfNodePtr> vecs;
|
||
if (node == nullptr) { //如果传入的为空指针,则直接返回空的
|
||
return vecs;
|
||
}
|
||
if (node->isa<CNode>()) { //如果传入的是一个CNode节点,则进入条件判断语句块
|
||
auto cnode = node->cast<CNodePtr>(); //获取该CNode节点的输入,并遍历每个输入。
|
||
auto &inputs = cnode->inputs();
|
||
// Check if free variables used.
|
||
for (const auto &input : inputs) {
|
||
auto input_fg = GetValueNode<FuncGraphPtr>(input); //如果输入是一个函数图的值节点(FuncGraphPtr类型),则进入条件判断语句块
|
||
if (input_fg) {
|
||
for (auto &fv : input_fg->free_variables_nodes()) {
|
||
if (fv->func_graph() == fg && fg->nodes().contains(fv)) {//遍历该函数图的自由变量节点(free_variables_nodes),
|
||
vecs.push_back(fv); //如果自由变量节点所属的函数图与传入的函数图相同,并且函数图中包含该自由变量节点,则将该自由变量节点添加到vecs中
|
||
}
|
||
}
|
||
}
|
||
}
|
||
(void)vecs.insert(vecs.end(), inputs.begin(), inputs.end()); //将该CNode节点的所有输入添加到vecs的末尾
|
||
}
|
||
return vecs; //返回vecs
|
||
};
|
||
|
||
return TopoSort(fg->get_return(), succ_include_fv, BelongSameGraph);
|
||
}
|
||
} // namespace
|
||
|
||
|
||
// ---------------implement of DfGraphConvertor-------------
|
||
bool IsCaseNode(const CNodePtr node) { //定义了一个名为IsCaseNodeCNodePtr的函数,用于判断给定的节点是否为"case"节点。
|
||
MS_EXCEPTION_IF_NULL(node); //使用宏确保传入的不为空,如果为空则抛出异常
|
||
if (!node->inputs().empty() && node->input(0)->isa<CNode>() && //通过条件判断语句检查输入是否非空,并且第一个输入是否为nodeCNode类型
|
||
GetCNodeFuncName(node->input(0)->cast<CNodePtr>()) == "switch_layer") { //通过调用函数GetCNodeFuncName获取第一个输入节点的函数名称,并将其与字符串"switch_layer"进行比较
|
||
return true; //如果函数名称与"switch_layer"相等,则返回true,表示该节点是"case"节点
|
||
}
|
||
return false; //如果不满足上述条件,则返回false,表示该节点不是"case"节点
|
||
}
|
||
|
||
/*
|
||
该函数的目的是获取 CNode 的目标函数名。对于 "case" 节点,目标函数名是 "kNameCase";对于其他节点,目标函数名是
|
||
GetCNodeFuncName(cnode) 的返回值,但如果函数名为"switch_layer",则返回一个空字符串。
|
||
在具体应用中,目标函数名可能用于后续的处理或决策逻辑。
|
||
*/
|
||
std::string GetCNodeTargetFuncName(const CNodePtr cnode) { //接受一个类型为 CNodePtr 的指针 cnode 作为参数,并返回一个 std::string 类型的目标函数名
|
||
if (IsCaseNode(cnode)) { //判断给定的cnode是否是case节点。
|
||
return string(kNameCase); //如果是case节点,则函数直接返回一个字符串常量 kNameCase,表示目标函数名为kNameCase
|
||
}
|
||
auto name = GetCNodeFuncName(cnode); //调用GetCNodeFuncName函数,用于获取 cnode 的函数名,并将其保存在一个名为 name 的局部变量中。
|
||
if (name == "switch_layer") { //检查函数名name是否为switch_layer
|
||
name = ""; //如果是,将 name 清空,即赋值为空字符串
|
||
}
|
||
return name; //返回目标函数名name
|
||
}
|
||
|
||
/*
|
||
该函数的作用是根据节点的类型和目标函数名,查找对应的适配器并返回适配器的指针。
|
||
适配器是用于处理不同类型的操作(函数)的一种模式,通过适配器模式,
|
||
可以使得图操作转换器能够灵活地处理不同类型的节点和操作。
|
||
*/
|
||
OpAdapterPtr DfGraphConvertor::FindAdapter(const AnfNodePtr node, bool train) {
|
||
MS_EXCEPTION_IF_NULL(node); //检查指针 node 是否为空,如果为空,抛出异常
|
||
if (node->isa<CNode>()) { //条件语句,判断 node 是否为 CNode 类型的节点
|
||
auto cnode = node->cast<CNodePtr>(); //如果是 CNode 类型的节点,将其转换为 CNodePtr 类型的智能指针,并赋值给cnode变量
|
||
|
||
std::string name = kNameCustomOp; //创建一个名为name的字符串变量,并将其初始化为一个名为 kNameCustomOp 的字符串常量。
|
||
if (!IsCustomCNode(cnode)) { //如果cnode不是自定义节点(根据 IsCustomCNode 函数判断)
|
||
name = GetCNodeTargetFuncName(cnode); //则将name设置为 GetCNodeTargetFuncName(cnode)的返回值,即获取cnode的目标函数名
|
||
}
|
||
|
||
auto it_adpt = OpAdapterMap::get().find(name); //在OpAdapterMap中查找name对应的适配器。OpAdapterMap 是一个静态单例对象,用于存储不同操作(函数)名对应的适配器。OpAdapterMap::get() 返回 OpAdapterMap 的引用
|
||
if (it_adpt != OpAdapterMap::get().end()) { //如果找到了name对应的适配器,
|
||
return it_adpt->second->Get(train); //则调用适配器的Get方法,将train作为参数传递进去,并返回适配器的指针 it_adpt->second
|
||
}
|
||
MS_LOG(EXCEPTION) << "Can't find OpAdapter for " << name; //如果未找到适配器,则输出异常日志,表示无法找到适配器
|
||
}
|
||
|
||
if (node->isa<ValueNode>()) { //处理不同类型的节点:ValueNode 和 Parameter。
|
||
return OpAdapterMap::get()[kNameConst]->Get(train); //根据节点的类型,选择对应的适配器,并返回对应的指针。
|
||
}
|
||
if (node->isa<Parameter>()) {
|
||
return OpAdapterMap::get()[kNameParam]->Get(train);
|
||
}
|
||
return OpAdapterPtr(nullptr); //如果节点类型不是 CNode、ValueNode 或 Parameter,则返回一个空的 OpAdapterPtr
|
||
}
|
||
|
||
/*
|
||
该函数用于初始化循环变量,并将相关的操作(Operator)添加到 init_input 和 init_ops_ 中
|
||
根据 training_的值决定是否初始化循环变量,并进行相应的变量和操作的创建和管理。
|
||
这在图操作转换过程中可能涉及到控制流和循环的处理。
|
||
*/
|
||
void DfGraphConvertor::InitLoopVar(std::vector<ge::Operator> *init_input) {
|
||
MS_EXCEPTION_IF_NULL(init_input); //宏或函数调用,用于检查指针init_input是否为空,如果为空,则抛出异常
|
||
if (this->training_) { //检查this->training_的值,如果为真(即 training_ 为真),则执行 if 代码块中的内容
|
||
GeTensorDesc desc(GeShape(), ge::FORMAT_NCHW, ge::DT_INT64); //通过调用 std::make_shared<Variable>(...) 创建了四个名为 var_iter_num、var_loop_cond、var_one 和 var_zero 的智能指针。
|
||
auto var_iter_num = std::make_shared<Variable>("npu_runconfig/iterations_per_loop"); //这些智能指针指向 Variable 类的实例,每个实例代表一个变量。
|
||
auto var_loop_cond = std::make_shared<Variable>("npu_runconfig/loop_cond");
|
||
auto var_one = std::make_shared<Variable>("npu_runconfig/one");
|
||
auto var_zero = std::make_shared<Variable>("npu_runconfig/zero");
|
||
(void)var_iter_num->update_output_desc_y(desc); //分别为这四个变量(var_iter_num、var_loop_cond、var_one 和 var_zero)更新了输出描述 GeTensorDesc。
|
||
(void)var_loop_cond->update_output_desc_y(desc);
|
||
(void)var_one->update_output_desc_y(desc);
|
||
(void)var_zero->update_output_desc_y(desc);
|
||
vars_["npu_runconfig/iterations_per_loop"] = var_iter_num; //将这四个变量添加到 vars_ 容器中,
|
||
vars_["npu_runconfig/loop_cond"] = var_loop_cond; //vars_ 可能是一个类成员变量,用于存储变量的映射关系。
|
||
vars_["npu_runconfig/one"] = var_one;
|
||
vars_["npu_runconfig/zero"] = var_zero;
|
||
|
||
//创建了四个名为 const_iter_num、const_loop_cond、const_one 和 const_zero 的智能指针。
|
||
//这些智能指针指向 Constant 类的实例,每个实例代表一个常量。
|
||
int64_t value = 0;
|
||
auto const_iter_num = std::make_shared<Constant>("const/npu_runconfig/iterations_per_loop");
|
||
if (ConfigManager::GetInstance().dataset_mode() == DS_SINK_MODE) {
|
||
value = ConfigManager::GetInstance().iter_num();
|
||
} else {
|
||
MS_LOG(INFO) << "Run with normal(non-sink) mode, the iterator number will always be 1";
|
||
ConfigManager::GetInstance().ResetIterNum();
|
||
}
|
||
|
||
//通过调用 set_attr_value 方法为这四个常量设置了不同的属性值(值为整数类型)
|
||
value -= 1; // iteration start from 0, the max iteration number for n loop should be n-1
|
||
(void)const_iter_num->set_attr_value(GeTensor(desc, reinterpret_cast<uint8_t *>(&value), sizeof(int64_t)));
|
||
|
||
auto const_loop_cond = std::make_shared<Constant>("const/npu_runconfig/loop_cond");
|
||
value = 0;
|
||
(void)const_loop_cond->set_attr_value(GeTensor(desc, reinterpret_cast<uint8_t *>(&value), sizeof(int64_t)));
|
||
|
||
auto const_one = std::make_shared<Constant>("const/npu_runconfig/one");
|
||
value = 1;
|
||
(void)const_one->set_attr_value(GeTensor(desc, reinterpret_cast<uint8_t *>(&value), sizeof(int64_t)));
|
||
|
||
auto const_zero = std::make_shared<Constant>("const/npu_runconfig/zero");
|
||
value = 0;
|
||
(void)const_zero->set_attr_value(GeTensor(desc, reinterpret_cast<uint8_t *>(&value), sizeof(int64_t)));
|
||
|
||
//分别为这四个常量(const_iter_num、const_loop_cond、const_one 和 const_zero)更新了输出描述 GeTensorDesc。
|
||
(void)const_iter_num->update_output_desc_y(desc);
|
||
(void)const_loop_cond->update_output_desc_y(desc);
|
||
(void)const_one->update_output_desc_y(desc);
|
||
(void)const_zero->update_output_desc_y(desc);
|
||
|
||
//创建了四个名为 assign_iter_num、assign_loop_cond、assign_one 和 assign_zero 的智能指针。
|
||
//这些智能指针指向 Assign 类的实例,每个实例代表一个赋值操作。
|
||
//分别通过调用 set_input_ref 和 set_input_value 方法为这四个赋值操作设置了输入引用和输入值
|
||
auto assign_iter_num = std::make_shared<Assign>("assign/npu_runconfig/iterations_per_loop");
|
||
(void)assign_iter_num->set_input_ref(*var_iter_num).set_input_value(*const_iter_num);
|
||
auto assign_loop_cond = std::make_shared<Assign>("assign/npu_runconfig/loop_cond");
|
||
(void)assign_loop_cond->set_input_ref(*var_loop_cond).set_input_value(*const_loop_cond);
|
||
auto assign_one = std::make_shared<Assign>("assign/npu_runconfig/one");
|
||
(void)assign_one->set_input_ref(*var_one).set_input_value(*const_one);
|
||
auto assign_zero = std::make_shared<Assign>("assign/npu_runconfig/zero");
|
||
(void)assign_zero->set_input_ref(*var_zero).set_input_value(*const_zero);
|
||
|
||
//将 var_iter_num、var_loop_cond、var_one 和 var_zero 添加到 init_input 中,init_input 可能是一个传入的参数,用于存储初始化输入的向量。
|
||
//将 var_iter_num、var_loop_cond、var_one、var_zero、const_iter_num、const_loop_cond、const_one、const_zero、assign_iter_num、
|
||
//assign_loop_cond、assign_one 和 assign_zero 添加到 init_ops_ 中,init_ops_ 可能是一个类成员变量,用于存储初始化操作的向量。
|
||
init_input->push_back(*var_iter_num);
|
||
init_input->push_back(*var_loop_cond);
|
||
init_input->push_back(*var_one);
|
||
init_input->push_back(*var_zero);
|
||
init_ops_.push_back(var_iter_num);
|
||
init_ops_.push_back(var_loop_cond);
|
||
init_ops_.push_back(var_one);
|
||
init_ops_.push_back(var_zero);
|
||
init_ops_.push_back(const_iter_num);
|
||
init_ops_.push_back(const_loop_cond);
|
||
init_ops_.push_back(const_one);
|
||
init_ops_.push_back(const_zero);
|
||
init_ops_.push_back(assign_iter_num);
|
||
init_ops_.push_back(assign_loop_cond);
|
||
init_ops_.push_back(assign_one);
|
||
init_ops_.push_back(assign_zero);
|
||
}
|
||
}
|
||
|
||
/*
|
||
该函数的作用是根据给定的操作(函数)名 name查找对应的适配器,并返回适配器的指针。
|
||
适配器是用于处理不同类型的操作(函数)的一种模式,通过适配器模式,可以使得图操作转换器能够灵活地处理不同类型的节点和操作。
|
||
*/
|
||
OpAdapterPtr DfGraphConvertor::FindAdapter(const std::string &name, bool train) {
|
||
auto it = OpAdapterMap::get().find(name); //在 OpAdapterMap中查找name对应的适配器。OpAdapterMap是一个静态单例对象,用于存储不同操作(函数)名对应的适配器。OpAdapterMap::get() 返回 OpAdapterMap 的引用。
|
||
if (it != OpAdapterMap::get().end()) { //如果找到了 name 对应的适配器,则调用适配器的get方法
|
||
return it->second->Get(train); //将train作为参数传递进去,返回适配器的指针 it->second
|
||
}
|
||
MS_LOG(EXCEPTION) << "Can't find OpAdapter for " << name; //如果未找到适配器,则输出异常日志,表示无法找到适配器。
|
||
}
|
||
|
||
/*
|
||
该函数用于生成参数初始化子图,采用Graphviz格式,将描述输出到 init_sout_中。
|
||
Graphviz是一种用于绘制图形的工具,可以将图形可视化,便于理解和调试。
|
||
*/
|
||
void DfGraphConvertor::DrawParamInitSubGraph(const std::string &name, const AnfNodePtr &it) {
|
||
// draw init subgraph 根据参数名 name 和节点 it 来绘制参数初始化子图的描述。
|
||
init_sout_ << "op_assign" << it.get() << "[label=<"; //使用<<运算符将描述信息添加到 init_sout_ 中
|
||
//添加了一个形如 "op_assign{it.get()}[label=<" 的字符串
|
||
//其中 it.get() 是节点 it 的指针值。op_assign 是一个子图节点的标识符,用于表示参数初始化的赋值操作。
|
||
init_sout_ << "<table border='1' cellborder='1'>" << endl;
|
||
init_sout_ << "<tr>";
|
||
init_sout_ << "<td port='1'>resource</td>"; //使用 HTML table 的形式绘制子图的结构,包括 "resource" 和 "value" 两个列,并设置了相应的标签。
|
||
init_sout_ << "<td port='2'>value</td>";
|
||
init_sout_ << "</tr>" << endl;
|
||
init_sout_ << "<tr><td colspan=\"2\">"
|
||
<< "\"assign_" << name << "\"</td></tr>" << endl;
|
||
init_sout_ << "</table>> shape=plaintext]" << endl;
|
||
init_sout_ << "param" << it.get() << "[shape=octagon, label=\"" << name << "\"]" << endl; //绘制一个形如 "param{it.get()}[shape=octagon, label="{name}"]" 的节点,其中 it.get() 是节点 it 的指针值,name 是参数名
|
||
init_sout_ << "const" << it.get() << "[label= \"" << name << "_const" //绘制一个形如 "const{it.get()}[label="{name}_const" shape=ellipse]" 的节点,其中 it.get() 是节点 it 的指针值,name 是参数名。
|
||
<< "\" shape=ellipse]" << endl;
|
||
init_sout_ << "param" << it.get() << "->" //绘制从 param{it.get()} 节点到 op_assign{it.get()}:1 节点的边,表示赋值操作的资源(resource)部分。
|
||
<< "op_assign" << it.get() << ":1" << endl;
|
||
init_sout_ << "const" << it.get() << "->" //绘制从 const{it.get()} 节点到 op_assign{it.get()}:2 节点的边,表示赋值操作的值(value)部分。
|
||
<< "op_assign" << it.get() << ":2" << endl;
|
||
}
|
||
|
||
/*
|
||
该函数用于设置参数初始化子图,构建子图并存储在 init_graph_中,用于参数初始化的计算。
|
||
这在图操作转换过程中可能涉及到参数初始化和常量传播等步骤。
|
||
*/
|
||
void DfGraphConvertor::SetupParamInitSubGraph(const TensorOrderMap &tensors, std::vector<ge::Operator> *init_input) {
|
||
DfGraphPtr init_graph = std::make_shared<DfGraph>("init"); //创建一个名为 init_graph 的 DfGraph 对象,并命名为 "init"。
|
||
std::vector<AnfNodePtr> nodes = GetOrderedCNodes(anf_graph_); //通过调用 GetOrderedCNodes(anf_graph_) 获取图中的有序计算节点,并存储在 nodes 中。
|
||
|
||
for (auto &it : nodes) { //遍历nodes中的每个节点it
|
||
MS_EXCEPTION_IF_NULL(it);
|
||
if (it->isa<ValueNode>()) { //检查节点是否为ValueNode类型
|
||
if (IsValueNode<SymbolicKeyInstance>(it)) { //对于符号节点 SymbolicKeyInstance,找到对应的变量操作 Variable,将其存储在 op_cache_ 中,并输出一条表示连接的 compute_sout_ 语句。
|
||
auto symbolic = GetValueNode<SymbolicKeyInstancePtr>(it);
|
||
auto name = std::static_pointer_cast<Parameter>(symbolic->node())->name();
|
||
auto iter = vars_.find(name); // get corresponding variable op
|
||
if (iter != vars_.end()) {
|
||
op_cache_[it.get()] = iter->second;
|
||
// #ifdef DRAW_GE_GRAPH
|
||
compute_sout_ << op_draw_name_[params_[name].get()] << " -> " << op_draw_name_[it.get()]
|
||
<< "[style=\"dotted\"]" << endl;
|
||
// #endif
|
||
}
|
||
} else if (IsValueNode<RefKey>(it)) { //对于引用键节点 RefKey,也找到对应的变量操作 Variable,将其存储在 op_cache_ 中,并输出一条表示连接的 compute_sout_ 语句。
|
||
auto refkey = GetValueNode<RefKeyPtr>(it);
|
||
MS_EXCEPTION_IF_NULL(refkey);
|
||
auto name = refkey->tag();
|
||
auto iter = vars_.find(name); // get corresponding variable op
|
||
if (iter != vars_.end()) {
|
||
op_cache_[it.get()] = iter->second;
|
||
compute_sout_ << op_draw_name_[params_[name].get()] << " -> " << op_draw_name_[it.get()]
|
||
<< "[style=\"dotted\"]" << endl;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
for (auto &it : tensors) { //检查给定的 TensorOrderMap 中的参数,将不存在于 vars_(变量映射)中的参数添加到 vars_ 中,并置其对应的变量操作为 nullptr。
|
||
if (vars_.find(it.first) == vars_.end()) {
|
||
MS_LOG(WARNING) << "Init parameter " << it.first << " didn't appear in graph.";
|
||
vars_[it.first] = nullptr;
|
||
}
|
||
}
|
||
|
||
// set up init sub graph
|
||
if (init_input->size()) {
|
||
// init sub graph needs no input
|
||
MS_LOG(INFO) << "Build data init subgraph.";
|
||
(void)init_graph->SetInputs(*init_input); //设置初始化子图 init_graph_ 的输入为 init_input,并将其存储在 init_graph_ 中。如果 init_input 为空,说明初始化子图不需要输入,则将 init_graph_ 置为 nullptr
|
||
this->init_graph_ = init_graph;
|
||
} else {
|
||
this->init_graph_ = nullptr;
|
||
}
|
||
}
|
||
|
||
/*
|
||
该函数的作用是根据数据集的名称、输入索引和节点创建数据集处理器,并将其存储在 out_handle_cache_中,这样在构建图时可以使用处理器进行数据集处理。
|
||
这在图操作转换过程中可能涉及到数据集的处理和输入操作的替换。
|
||
*/
|
||
void DfGraphConvertor::MakeDatasetHandler(const std::string &name, const size_t &input_idx, const AnfNodePtr &it) {
|
||
MS_LOG(INFO) << "The " << name << " is the " << input_idx << "(st/nd/th) input"; //输出日志,表示当前处理的数据集的名称和输入索引。
|
||
if (ConfigManager::GetInstance().dataset_mode() == DS_SINK_MODE) { //检查配置管理器中的数据集模式是否为 "DS_SINK_MODE"。
|
||
auto getnext_idx = static_cast<int64_t>(input_idx); //将输入索引转换为 int64_t 类型的变量 getnext_idx。
|
||
DatasetGraphParam param = ConfigManager::GetInstance().dataset_param(); //从配置管理器中获取数据集参数,并将其存储在变量 param 中。
|
||
if (!param.input_indexes().empty() && input_idx <= param.input_indexes().size()) { //如果数据集参数中的输入索引列表 input_indexes() 不为空,并且输入索引 input_idx 小于等于列表的大小,则将 getnext_idx 重新映射为列表中的索引值(减去1,因为索引从0开始)。
|
||
getnext_idx = param.input_indexes()[input_idx] - 1; // input_idx start from 0.
|
||
MS_LOG(INFO) << "remap input_index:" << input_idx << " to getnext_index:" << getnext_idx << ".";
|
||
}
|
||
// use iterator_getnext op with output_name instead of data op in BuildGraph.
|
||
if (dataset_iter_getnext_ != nullptr) { ///如果 dataset_iter_getnext_ 不为空,则将处理器存储在 out_handle_cache_ 中。dataset_iter_getnext_ 可能是一个数据集迭代器节点的操作(Operator)。
|
||
out_handle_cache_[it.get()] = OutHandler(dataset_iter_getnext_, "y" + std::to_string(getnext_idx));
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
该函数的目的是根据广播操作、广播描述和广播图等信息构建广播子图,并将其存储在 broadcast_graph_中,用于实现广播操作。
|
||
广播操作是指在计算中将低维数据自动扩展为高维数据,以便于进行张量运算。
|
||
在图操作转换过程中,广播子图的构建可能涉及到维度扩展和数据对齐等处理。
|
||
*/
|
||
void DfGraphConvertor::SetupBroadcast(const std::shared_ptr<HcomBroadcast> &broadcast,
|
||
const std::vector<GeTensorDesc> &broadcast_desc,
|
||
const DfGraphPtr &broadcast_graph, std::vector<ge::Operator> broadcast_input) {
|
||
//const std::shared_ptr<HcomBroadcast> &broadcast(广播操作的指针)、
|
||
//const std::vector<GeTensorDesc> &broadcast_desc(广播描述的向量)、
|
||
//const DfGraphPtr &broadcast_graph(广播图的指针)
|
||
//std::vector<ge::Operator> broadcast_input(广播输入的向量)。
|
||
MS_LOG(INFO) << "build broadcast subgraph"; //输出日志,表示正在构建广播子图。
|
||
if (broadcast_desc.size() != broadcast_input.size()) { //检查广播描述的数量是否等于广播输入的数量,如果不相等,则抛出异常。
|
||
MS_LOG(EXCEPTION) << "Desc number of BroadCast is not equal to number of Input";
|
||
}
|
||
//通过调用 create_dynamic_input_x 和 create_dynamic_output_y 方法为广播操作创建动态输入和输出
|
||
(void)broadcast->create_dynamic_input_x(static_cast<unsigned int>(broadcast_input.size()));
|
||
(void)broadcast->create_dynamic_output_y(static_cast<unsigned int>(broadcast_desc.size()));
|
||
for (unsigned int i = 0; i < broadcast_input.size(); i++) { //使用循环为广播操作的动态输入和输出设置相应的描述和数据
|
||
(void)broadcast->set_dynamic_input_x(i, broadcast_input[i]);
|
||
(void)broadcast->update_dynamic_output_desc_y(i, broadcast_desc[i]);
|
||
}
|
||
(void)broadcast_graph->SetInputs(broadcast_input); //将广播图 broadcast_graph 的输入设置为 broadcast_input,并将广播图存储在 broadcast_graph_ 中。
|
||
this->broadcast_graph_ = broadcast_graph;
|
||
}
|
||
|
||
/*
|
||
该函数的目的是根据给定的 TensorOrderMap初始化参数,并构建相关的初始化子图和操作。
|
||
在图操作转换过程中,参数初始化是一个重要的步骤,该函数完成了参数的创建、初始化数据的添加以及初始化子图的构建等任务。
|
||
*/
|
||
void DfGraphConvertor::InitParamWithData(const TensorOrderMap &tensors) {
|
||
int index = 0; //初始化一些变量,包括 index(索引),init_input(初始化子图的输入操作向量)等。
|
||
std::vector<Operator> init_input;
|
||
for (auto it : tensors) { //对于 tensors 中的每个参数 it,根据参数名查找对应的节点 node。
|
||
std::string name = it.first;
|
||
auto node_itor = params_.find(name);
|
||
// if name not in params_, create a node in graph
|
||
if (node_itor == params_.end()) { //如果参数名不存在于 params_ 中,则表示该参数节点尚未创建,此时创建一个名为 name + "_temp" 的新节点,并将其转换为图操作。
|
||
MS_LOG(WARNING) << name << " is not in params, and create a new node.";
|
||
ParameterPtr param = std::make_shared<Parameter>(nullptr);
|
||
name = name + "_temp";
|
||
param->set_name(name);
|
||
(void)ConvertParameter(param);
|
||
node_itor = params_.find(name);
|
||
}
|
||
auto node = node_itor->second; //根据节点 node 查找对应的操作(Operator)并存储在 op_itor 中,如果未找到操作则抛出异常。
|
||
auto op_itor = op_cache_.find(node.get());
|
||
if (op_itor == op_cache_.end()) {
|
||
MS_LOG(EXCEPTION) << "Can not find op for node " << node->ToString() << ".";
|
||
}
|
||
auto adpt = FindAdapter(kNameParam, training_); //查找参数适配器 adpt,根据参数名 kNameParam 和训练状态 training_ 来获取适配器,如果适配器为空则继续下一个参数。
|
||
if (adpt == nullptr) continue;
|
||
auto param_op = adpt->generate(name + "_data"); //根据参数名 name 创建一个名为 name + "_data" 的参数操作 param_op。
|
||
MS_LOG(INFO) << "Add parameter " << name << " as input, index " << index << ".";
|
||
|
||
if (!training_) { //如果不处于训练状态 training_,则表示当前是推理阶段,需要创建常量操作,将初始化数据添加到图中。
|
||
auto adpt_const = FindAdapter(kNameConst, training_); //查找常量适配器 adpt_const,根据参数名 kNameConst 和训练状态 training_ 来获取适配器,如果适配器为空则继续下一个参数。
|
||
if (adpt_const == nullptr) continue;
|
||
auto const_op = adpt_const->generate(name + "_const");
|
||
(void)adpt_const->setAttr(const_op, "value", it.second); //创建常量操作 const_op,设置常量操作的属性 "value" 为参数的初始化数据。
|
||
|
||
auto const_op_desc = TransformUtil::GetGeTensorDesc(it.second->shape_c(), it.second->data_type(), kOpFormat_NCHW); //创建初始化数据的输出描述 const_op_desc,并将其更新到常量操作的输出描述。
|
||
if (const_op_desc == nullptr) {
|
||
MS_LOG(WARNING) << "Create variable " << name << " output descriptor failed!";
|
||
continue;
|
||
}
|
||
(void)std::static_pointer_cast<Constant>(const_op)->update_output_desc_y(*const_op_desc);
|
||
|
||
vars_[name] = const_op;
|
||
op_itor->second = const_op;
|
||
continue;
|
||
}
|
||
|
||
// create tensor descriptor for output descriptor 创建输出描述 desc,表示参数的形状、数据类型和格式。
|
||
auto desc = TransformUtil::GetGeTensorDesc(it.second->shape_c(), it.second->data_type(), kOpFormat_NCHW);
|
||
if (desc == nullptr) {
|
||
MS_LOG(ERROR) << "Create variable " << name << " output descriptor failed!";
|
||
continue;
|
||
}
|
||
|
||
// we need three variable ops for each graph with same name
|
||
// build init subgraph
|
||
//对于非初始化数据(it.second->is_init() == 0),创建三个变量操作:param_op、init_var 和 assign_op,并将其加入 init_ops_ 和 init_input。
|
||
if (it.second->is_init() == 0) {
|
||
(void)std::static_pointer_cast<Data>(param_op)->set_attr_index(index++); //对于初始化数据,不再创建变量操作,直接将其替换为参数操作 param_op。
|
||
auto init_var = std::make_shared<Variable>(name);
|
||
auto assign_op = std::make_shared<Assign>("assign_" + name);
|
||
(void)init_var->update_output_desc_y(*desc);
|
||
(void)assign_op->set_input_ref(*init_var).set_input_value(*param_op);
|
||
init_input.push_back(*init_var);
|
||
init_ops_.push_back(param_op);
|
||
init_ops_.push_back(assign_op);
|
||
init_ops_.push_back(init_var);
|
||
}
|
||
|
||
auto variable = std::make_shared<Variable>(name);
|
||
(void)variable->update_output_desc_y(*desc);
|
||
// do not use read variable while variable sink
|
||
MS_LOG(DEBUG) << "InitParam, op_name = " << name << ", var = " << variable->GetName() << ".";
|
||
op_itor->second = variable; // replace parameter with variable
|
||
vars_[name] = variable; // prevent the variable operator from being freed
|
||
DrawParamInitSubGraph(name, node); //调用 DrawParamInitSubGraph 函数绘制参数初始化子图的描述。
|
||
}
|
||
InitLoopVar(&init_input); //调用 InitLoopVar 函数初始化循环变量。
|
||
SetupParamInitSubGraph(tensors, &init_input); //调用 SetupParamInitSubGraph 函数设置参数初始化子图。
|
||
}
|
||
|
||
|
||
/*
|
||
该函数的目的是初始化图操作转换器,并根据给定的 TensorOrderMap进行参数初始化和数据处理。
|
||
在图操作转换过程中,参数的初始化和数据处理是图构建过程中的重要步骤。
|
||
*/
|
||
// convert all parameter need initialize to variable
|
||
DfGraphConvertor &DfGraphConvertor::InitParam(const TensorOrderMap &tensors) {
|
||
size_t input_idx = 0; //初始化变量 input_idx(输入索引)。
|
||
if (error_ != SUCCESS) { //检查 error_ 是否为 SUCCESS,如果不是,则直接返回当前的图操作转换器。
|
||
return *this;
|
||
}
|
||
if (anf_graph_ == nullptr || anf_graph_->output() == nullptr) { //检查 anf_graph_ 和 anf_graph_->output() 是否合法
|
||
error_ = INVALID_ARGUMENT; //如果不合法,则将 error_ 设置为 INVALID_ARGUMENT 并输出错误信息,然后返回当前的图操作转换器
|
||
MS_LOG(ERROR) << "Invalid AnfGraph in InitParam.";
|
||
return *this;
|
||
}
|
||
|
||
// Processing input with MakeDatasetHandler
|
||
for (auto &it : anf_graph_->parameters()) { //遍历 anf_graph_->parameters(),即图的参数节点。
|
||
auto op_itor = op_cache_.find(it.get()); // converted node 对于每个参数节点 it,查找对应的操作(Operator)并存储在 op_itor 中。
|
||
if (it->isa<Parameter>() && op_itor != op_cache_.end()) { //如果节点是 Parameter 类型且在 op_cache_ 中找到了对应的操作,则表示该节点为参数节点,并且还需要进行数据处理。
|
||
string name = std::static_pointer_cast<Parameter>(it)->name(); //获取参数节点的名称 name
|
||
auto tensor_itor = tensors.find(name); // in init value map
|
||
if (tensor_itor == tensors.end()) { //查找给定的 tensors 中是否存在该参数的初始化数据,如果不存在,则需要进行数据处理。
|
||
DfGraphConvertor::MakeDatasetHandler(name, input_idx, it); //调用 MakeDatasetHandler 函数处理数据集,为参数节点创建数据集处理器,并传递参数的名称、输入索引和节点。
|
||
input_idx++; //递增 input_idx,表示处理下一个输入。
|
||
}
|
||
}
|
||
}
|
||
InitParamWithData(tensors); //调用 InitParamWithData 函数进行参数初始化,根据给定的 tensors 完成参数的创建、初始化数据的添加和初始化子图的构建。
|
||
init_sout_ << "}" << endl; //输出初始化子图的描述。
|
||
return *this; //返回当前的图操作转换器的引用。
|
||
}
|
||
|
||
//非活动预处理器块
|
||
/*
|
||
该函数的目的是根据已初始化的变量和保存操作 Save来构建保存检查点子图。
|
||
在图操作转换过程中,保存检查点是一个重要的步骤,该函数完成了保存操作和变量的处理,以及保存检查点子图的构建。
|
||
*/
|
||
#if (defined ENABLE_D) //条件编译的预处理指令,当定义了 ENABLE_D 宏时,才会编译以下代码块。
|
||
void DfGraphConvertor::BuildSaveCheckpointGraph() {
|
||
std::vector<Operator> graph_inputs; //初始化变量 graph_inputs(图的输入操作向量)
|
||
ge::op::Save save_op("save_parms"); //save_op(保存操作 Save 的实例)
|
||
int save_op_is_active = 0; //save_op_is_active(保存操作是否激活的标志,初始值为0)
|
||
size_t index = 0; //index(索引,用于保存操作的动态输入索引)
|
||
string name; //name(变量的名称)
|
||
|
||
auto count_size = std::count_if(vars_.begin(), vars_.end(), [](const auto &it) {
|
||
return LongToUlong(it.second == nullptr || it.first.find("/") != std::string::npos);
|
||
}); //使用 std::count_if 函数统计 vars_ 中值为 nullptr 或名称中包含 "/" 符号的变量的数量,并将结果保存在 count_size 变量中。
|
||
|
||
(void)save_op.create_dynamic_input_tensors(static_cast<uint32_t>(vars_.size() - static_cast<size_t>(count_size)));
|
||
//调用 save_op.create_dynamic_input_tensors 方法创建保存操作 Save 的动态输入张量,数量为 vars_.size() - count_size。
|
||
|
||
// for each "parameter" in anf graph excluding "input"
|
||
for (const auto &it : vars_) { //遍历 vars_ 中的每个变量,对于每个非空且名称不包含 "/" 符号的变量,创建对应的变量操作,并将其添加到 save_op 的动态输入张量中。
|
||
name = it.first;
|
||
if (it.second == nullptr || name.find("/") != std::string::npos) continue;
|
||
Variable variable(name);
|
||
(void)variable.update_output_desc_y(it.second->GetOutputDesc(0));
|
||
(void)save_op.set_dynamic_input_tensors(static_cast<uint32_t>(index++), variable);
|
||
|
||
graph_inputs.push_back(variable); //将每个变量操作添加到 graph_inputs 中,并将其与 save_op 连接起来。
|
||
|
||
if (save_op_is_active == 0) { //如果 save_op_is_active 为0(即没有有效的保存操作),则输出检查点子图的描述。
|
||
checkpoint_sout_ << "op_save" << &save_op << "[label=<";
|
||
checkpoint_sout_ << "<table border='1' cellborder='1'>" << endl;
|
||
checkpoint_sout_ << "<tr><td port='1'>tensor</td></tr>" << endl;
|
||
checkpoint_sout_ << "<tr><td colspan=\"1\">"
|
||
<< "\"saveop"
|
||
<< "\"</td></tr>" << endl;
|
||
checkpoint_sout_ << "</table>> shape=plaintext]" << endl;
|
||
}
|
||
|
||
checkpoint_sout_ << "param" << it.second << "[shape=octagon, label=\"" << name << "\"]" << endl;
|
||
|
||
checkpoint_sout_ << "param" << it.second << "->"
|
||
<< "op_save" << &save_op << ":1" << endl;
|
||
save_op_is_active = 1;
|
||
}
|
||
if (save_op_is_active) { //如果 save_op_is_active 为1(存在有效的保存操作),则创建保存检查点子图 checkpoint_graph,设置其输入为 graph_inputs 和输出为 graph_output(包含 save_op)。
|
||
std::vector<Operator> graph_output;
|
||
graph_output.emplace_back(save_op);
|
||
DfGraphPtr checkpoint_graph = std::make_shared<DfGraph>("checkpoint");
|
||
(void)checkpoint_graph->SetInputs(graph_inputs);
|
||
(void)checkpoint_graph->SetOutputs(graph_output);
|
||
this->save_ckp_graph_ = checkpoint_graph; //将保存检查点子图存储在 save_ckp_graph_ 中。
|
||
} else {
|
||
this->save_ckp_graph_ = nullptr;
|
||
}
|
||
|
||
checkpoint_sout_ << "}" << endl; //输出检查点子图的描述。
|
||
return;
|
||
}
|
||
#endif
|
||
|
||
|
||
/*
|
||
该函数的目的是生成广播子图,用于在分布式训练中对参数进行广播。
|
||
在图操作转换过程中,广播是一个重要的步骤,该函数完成了广播操作和广播子图的构建。
|
||
*/
|
||
DfGraphConvertor &DfGraphConvertor::GenerateBroadcastGraph(const TensorOrderMap &tensors) {
|
||
if (error_ != SUCCESS) { //检查 error_ 是否为 SUCCESS,如果不是,则直接返回当前的图操作转换器。
|
||
return *this;
|
||
}
|
||
if (anf_graph_ == nullptr || anf_graph_->output() == nullptr) { //检查 anf_graph_ 和 anf_graph_->output() 是否合法
|
||
error_ = INVALID_ARGUMENT; //如果不合法,则将 error_ 设置为 INVALID_ARGUMENT 并输出错误信息
|
||
MS_LOG(ERROR) << "Invalid AnfGraph in generate broadcast graph";
|
||
return *this; //然后返回当前的图操作转换器
|
||
}
|
||
|
||
DfGraphPtr broadcast_graph = std::make_shared<DfGraph>("broadcast"); //创建广播子图 broadcast_graph
|
||
// collect the operators create for broadcast sub graph, in order to avoid auto release
|
||
std::vector<Operator> broadcast_input; //初始化变量 broadcast_input(广播子图的输入操作向量)
|
||
std::vector<GeTensorDesc> broadcast_desc; //broadcast_desc(广播子图输入操作的描述)
|
||
auto broadcast = std::make_shared<HcomBroadcast>("broadcast_parameter"); //创建广播操作 HcomBroadcast,命名为 "broadcast_parameter"
|
||
(void)broadcast->set_attr_root_rank(0); //设置广播的根节点 root_rank 为 0
|
||
(void)broadcast->set_attr_group("hccl_world_group"); //设置广播的通信组 group 为 "hccl_world_group"
|
||
broadcast_ops_.push_back(broadcast); //将广播操作保存在 broadcast_ops_ 中
|
||
|
||
// find every parameter, build broadcast subgraph (or initialize the parameter with constant)
|
||
for (auto &it : anf_graph_->parameters()) { //遍历 anf_graph_->parameters(),即图的参数节点。
|
||
auto op_itor = op_cache_.find(it.get()); // converted node 对于每个参数节点 it,查找对应的操作(Operator)并存储在 op_itor 中。
|
||
if (it->isa<Parameter>() && op_itor != op_cache_.end()) { //如果节点是 Parameter 类型且在 op_cache_ 中找到了对应的操作,并且在给定的 tensors 中存在对应的初始化数据,则表示该节点为参数节点,并且需要进行广播操作。
|
||
string name = std::static_pointer_cast<Parameter>(it)->name(); //获取参数节点的名称 name。
|
||
auto tensor_itor = tensors.find(name); // in init tensor map
|
||
if (tensor_itor != tensors.end()) { //查找给定的 tensors 中是否存在该参数的初始化数据,如果存在,则表示需要进行广播。
|
||
auto tensor = tensor_itor->second;
|
||
auto shape_ge = tensor->shape_c(); //获取参数的形状 shape_ge。
|
||
|
||
// create tensor descriptor for output descriptor
|
||
//创建用于输出描述符的张量描述符 desc,表示参数的形状和数据类型。
|
||
auto desc = TransformUtil::GetGeTensorDesc(shape_ge, tensor->data_type(), kOpFormat_NCHW);
|
||
if (desc == nullptr) {
|
||
MS_LOG(ERROR) << "Create variable " << name << " output descriptor failed!";
|
||
continue;
|
||
}
|
||
|
||
// build broadcast subgraph
|
||
if (distribute_) { //如果 distribute_ 为真(表示进行分布式训练),则构建广播子图。
|
||
auto broadcast_var = std::make_shared<Variable>(name); //如果存在需要广播的参数,创建相应的变量操作 broadcast_var,并将其添加到 broadcast_input 和 broadcast_desc 中
|
||
(void)broadcast_var->update_output_desc_y(*desc);
|
||
broadcast_input.push_back(*broadcast_var);
|
||
broadcast_desc.push_back(*desc);
|
||
broadcast_ops_.push_back(broadcast_var); //将变量操作保存在 broadcast_ops_ 中。
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// set up broadcast sub graph
|
||
if (!broadcast_input.empty()) { //设置广播子图的输入和输出,并调用 SetupBroadcast 函数进行广播子图的构建。
|
||
DfGraphConvertor::SetupBroadcast(broadcast, broadcast_desc, broadcast_graph, broadcast_input);
|
||
} else {
|
||
this->broadcast_graph_ = nullptr;
|
||
}
|
||
return *this; //返回当前的图操作转换器的引用。
|
||
}
|
||
|
||
/*
|
||
该函数的目的是生成检查点图,用于在图操作转换过程中保存模型的参数。
|
||
在图操作转换过程中,生成检查点图是一个重要的步骤,用于将训练过程中的模型参数保存到文件中,以便在需要时进行模型的恢复和继续训练。
|
||
*/
|
||
DfGraphConvertor &DfGraphConvertor::GenerateCheckpointGraph() {
|
||
if (error_ != SUCCESS) { //检查 error_ 是否为 SUCCESS,如果不是,则输出错误信息,并直接返回当前的图操作转换器。
|
||
MS_LOG(ERROR) << "Generate checkpoint graph failed, found error code " << error_ << ".";
|
||
return *this;
|
||
}
|
||
if (anf_graph_ == nullptr || anf_graph_->output() == nullptr) { //检查 anf_graph_ 和 anf_graph_->output() 是否合法
|
||
error_ = INVALID_ARGUMENT; //如果不合法,则将 error_ 设置为 INVALID_ARGUMENT 并输出错误信息
|
||
MS_LOG(ERROR) << "Invalid AnfGraph in GenerateCheckpointGraph";
|
||
return *this; //然后返回当前的图操作转换器
|
||
}
|
||
#ifdef ENABLE_D //在条件编译指令 #ifdef ENABLE_D 内部执行以下操作:
|
||
auto ms_context = MsContext::GetInstance(); //获取全局唯一的 MsContext 实例 ms_context
|
||
MS_EXCEPTION_IF_NULL(ms_context); // 检查 ms_context 是否为空,如果为空,则抛出异常
|
||
if (ms_context->backend_policy() == "ge") { //检查当前的后端策略 backend_policy 是否为 "ge"(表示使用基于GraphEngine的后端)
|
||
BuildSaveCheckpointGraph(); //如果后端策略为 "ge",则调用 BuildSaveCheckpointGraph() 函数来构建保存检查点子图
|
||
// Restoring from checkpoint file is done by pyfront, not in graph now.
|
||
}
|
||
#endif
|
||
return *this; //返回当前的图操作转换器的引用
|
||
}
|
||
|
||
/*
|
||
该函数的主要目的是将所有的AnfNode转换为对应的运算算子,为后续的图操作转换和构建数据流图做准备。
|
||
*/
|
||
DfGraphConvertor &DfGraphConvertor::ConvertAllNode() {
|
||
if (error_ != SUCCESS) { //检查 error_ 是否为 SUCCESS,如果不是,则直接返回当前的图操作转换器。
|
||
return *this;
|
||
}
|
||
if (anf_graph_ == nullptr || anf_graph_->output() == nullptr) { //检查 anf_graph_ 和 anf_graph_->output() 是否合法
|
||
MS_LOG(ERROR) << "Invalid AnfGraph";
|
||
error_ = FAILED; //如果不合法,则将 error_ 设置为 FAILED 并输出错误信息
|
||
return *this; //返回当前的图操作转换器
|
||
}
|
||
//清空计算图 compute_sout_、初始化图 init_sout_、恢复检查点图restore_checkpoint_sout_ 和检查点图 checkpoint_sout_
|
||
//的内容,并初始化为新的图。
|
||
compute_sout_.clear();
|
||
compute_sout_ << "digraph {" << endl;
|
||
init_sout_.clear();
|
||
init_sout_ << "digraph {" << endl;
|
||
#ifdef ENABLE_D //在条件编译指令 #ifdef ENABLE_D 内部执行以下操作:
|
||
auto ms_context = MsContext::GetInstance(); //获取全局唯一的 MsContext 实例 ms_context
|
||
MS_EXCEPTION_IF_NULL(ms_context); //检查 ms_context 是否为空,如果为空,则抛出异常
|
||
if (ms_context->backend_policy() == "ge") { //检查当前的后端策略 backend_policy 是否为 "ge"(表示使用基于GraphEngine的后端)
|
||
checkpoint_sout_.clear(); // 如果后端策略为 "ge",则清空检查点图 checkpoint_sout_ 的内容,并初始化为新的图
|
||
checkpoint_sout_ << "digraph {" << endl; //结束条件编译指令
|
||
}
|
||
#endif
|
||
restore_checkpoint_sout_.clear(); //清空恢复检查点图 restore_checkpoint_sout_ 的内容,并初始化为新的图
|
||
restore_checkpoint_sout_ << "digraph {" << endl;
|
||
|
||
// Convert all anf node to Operator
|
||
MS_LOG(DEBUG) << "convert all node";
|
||
std::vector<AnfNodePtr> nodes = GetOrderedCNodes(anf_graph_); //获取有序的AnfNode节点列表 nodes,用于按照拓扑排序的顺序遍历所有的AnfNode。
|
||
for (auto &it : nodes) {
|
||
(void)Convert(it); //对于每个AnfNode it,调用 Convert 函数将其转换为对应的运算算子(Operator)。
|
||
if (this->error_ != SUCCESS) { //检查转换是否成功,如果出现错误,输出错误信息。
|
||
MS_LOG(ERROR) << "failed to convert node: " << it->DebugString() << ".";
|
||
}
|
||
}
|
||
|
||
// Create dataset iterator and iterator_getnext node
|
||
if (ConfigManager::GetInstance().dataset_mode() == DS_SINK_MODE) { //如果处于数据集Sink模式下,则创建数据集迭代器和GetNext算子。
|
||
DatasetGraphParam param = ConfigManager::GetInstance().dataset_param();
|
||
MS_LOG(INFO) << "Dataset param is " << param.ToString() << ".";
|
||
// GetNext
|
||
auto iter_getnext_op = make_shared<ge::op::GetNext>("get_next_tmp");
|
||
std::vector<enum ge::DataType> getnext_types;
|
||
const auto &origin_ge_types = param.ge_types();
|
||
(void)std::transform(
|
||
origin_ge_types.begin(), origin_ge_types.end(), std::back_inserter(getnext_types),
|
||
[](int64_t t_num) -> enum ge::DataType { return static_cast<enum ge::DataType>(t_num); });
|
||
(void)iter_getnext_op->set_attr_output_types(getnext_types);
|
||
(void)iter_getnext_op->set_attr_output_shapes(param.shapes());
|
||
(void)iter_getnext_op->set_attr_channel_name(param.queue_name());
|
||
|
||
// save iter_getnext_op for later use
|
||
dataset_iter_getnext_ = iter_getnext_op;
|
||
}
|
||
|
||
// return the data flow graph
|
||
return *this; //返回当前的图操作转换器的引用。
|
||
}
|
||
|
||
/*
|
||
该函数的目的是从缓存中获取特定AnfNode的输出信息,并将其添加到图的输出列表,以便后续构建数据流图时使用。
|
||
在构建数据流图时,可以根据图的输出列表来确定图的输出节点。
|
||
*/
|
||
void DfGraphConvertor::TraceOutputFromTupleGetItem(const AnfNodePtr &anf_out) {
|
||
auto it = out_handle_cache_.find(anf_out.get()); //通过传入的 anf_out,在缓存 out_handle_cache_ 中查找对应的输出信息
|
||
if (it != out_handle_cache_.end()) { //如果找到了对应的输出信息(即 it 不等于 out_handle_cache_.end()),则获取该输出信息的 OutHandler 对象 handle
|
||
OutHandler handle = it->second;
|
||
auto op = handle.op; //从 handle 中获取运算算子(Operator)的指针 op
|
||
if (op != nullptr) { //如果 op 不为空,则输出该运算算子的名称、类型以及输出名,并将该运算算子与输出名添加到图的输出列表 graph_outputs_ 中。
|
||
MS_LOG(INFO) << "op name: " << op->GetName() << ", op type: " << op->GetOpType() << ", out_name: " << handle.out;
|
||
(void)graph_outputs_.emplace_back(*op, handle.out);
|
||
} else { //如果 op 为空,则表示对应的AnfNode还没有被成功转换为运算算子,此时抛出异常。
|
||
MS_LOG(EXCEPTION) << "tuple_getitem: " << anf_out->fullname_with_scope() << " is not converted";
|
||
}
|
||
} else { //如果在缓存中找不到对应的输出信息,即 it 等于 out_handle_cache_.end(),则输出警告信息,表示出现了无效的 tuple_getitem
|
||
// invalid tuple_getitem e.g. tuple_getitem(tuple_getitem())/tuple_getitem(depend())/tuple_getitem(make_tuple())
|
||
MS_LOG(WARNING) << "Invalid tuple_getitem: " << anf_out->fullname_with_scope();
|
||
}
|
||
}
|
||
|
||
/*
|
||
该函数的目的是跟踪给定AnfNode及其所有的输出,并将其添加到图的输出列表,以便在构建数据流图时使用。
|
||
通过递归调用,可以处理复杂的计算图结构,确保所有输出信息都被正确地记录在图的输出列表中。
|
||
*/
|
||
void DfGraphConvertor::TraceOutput(const AnfNodePtr node) {
|
||
MS_EXCEPTION_IF_NULL(node); //检查输入的AnfNode是否为空,如果为空,则抛出异常
|
||
AnfNodePtr anf_out = node;
|
||
AnfNodePtr pre_node = nullptr;
|
||
|
||
// Trace value node
|
||
if (node->isa<ValueNode>()) { //如果AnfNode是一个ValueNode(值节点)
|
||
auto op = Convert(anf_out); //调用 Convert 函数将其转换为运算算子,并将该运算算子添加到图的输出列表 graph_outputs_ 中。
|
||
if (op != nullptr) {
|
||
(void)graph_outputs_.emplace_back(*op, "");
|
||
AddGraphConstInput(op);
|
||
}
|
||
return;
|
||
}
|
||
|
||
// Trace Parameter node
|
||
TraceOutputFromParameter(anf_out); //如果AnfNode是一个Parameter节点(参数节点),则调用 TraceOutputFromParameter 函数处理该节点。
|
||
|
||
// Then trace cnode
|
||
if (!node->isa<CNode>()) { //检查AnfNode是否是CNode(计算节点)
|
||
return;
|
||
}
|
||
|
||
// trace tuple_getitem
|
||
//如果是 tuple_getitem 节点,通过迭代向上跟踪所有的 tuple_getitem 节点,直到找到源头CNode为止,并调用 TraceOutputFromTupleGetItem 处理输出信息。
|
||
while (anf_out->isa<CNode>() && IsPrimitiveCNode(anf_out, prim::kPrimTupleGetItem)) {
|
||
pre_node = anf_out;
|
||
anf_out = anf_out->cast<CNodePtr>()->input(1);
|
||
}
|
||
// trace every element of make_tuple
|
||
//如果AnfNode是CNode且目标函数名为 "MakeTuple",则遍历所有的输入元素并递归调用 TraceOutput 处理每个输入元素。
|
||
auto c = anf_out->cast<CNodePtr>();
|
||
std::string name = "";
|
||
if (anf_out->isa<CNode>()) {
|
||
name = GetCNodeTargetFuncName(c);
|
||
}
|
||
|
||
if (name == "MakeTuple") {
|
||
for (unsigned int i = 1; i < c->inputs().size(); i++) {
|
||
TraceOutput(c->input(i));
|
||
}
|
||
} else if (name == prim::kPrimDepend->name()) { //如果目标函数名为 "Depend",则跟踪第一个输入元素。
|
||
if (c->inputs().size() < 3) { // "Depend" primitive have 3 inputs
|
||
MS_LOG(EXCEPTION) << "length of inputs is " << c->inputs().size() << ", which is less than 3";
|
||
}
|
||
TraceOutput(c->input(1));
|
||
} else if (name == prim::kTupleGetItem) { //如果目标函数名为 "prim::kPrimTupleGetItem",则调用 TraceOutputFromTupleGetItem 处理输出信息。
|
||
TraceOutputFromTupleGetItem(anf_out);
|
||
} else { //否则,将AnfNode转换为运算算子,并将其添加到图的输出列表 graph_outputs_ 中。
|
||
//如果在处理 tuple_getitem 时,找到了前置节点(pre_node)的输出信息,则将该信息作为当前节点的输出索引。
|
||
// add outputs
|
||
auto op = Convert(anf_out);
|
||
std::string index;
|
||
if (op != nullptr) {
|
||
if ((pre_node != nullptr) && IsPrimitiveCNode(pre_node, prim::kPrimTupleGetItem)) {
|
||
auto item = out_handle_cache_.find(pre_node.get());
|
||
if (item != out_handle_cache_.end()) {
|
||
index = item->second.out;
|
||
} else {
|
||
MS_LOG(WARNING) << "Can't get operator: " << anf_out->fullname_with_scope() << " 's output item";
|
||
}
|
||
}
|
||
MS_LOG(INFO) << "Add graph output: " << anf_out->fullname_with_scope() << ":" << index;
|
||
(void)graph_outputs_.emplace_back(*op, index);
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
该函数的目的是处理给定的Parameter节点,并将其作为图的输出添加到输出列表中。
|
||
它在处理普通参数和在Dataset图模式下的输入参数时分别进行了不同的处理逻辑,确保所有输出信息都被正确地记录在图的输出列表中。
|
||
*/
|
||
void DfGraphConvertor::TraceOutputFromParameter(const AnfNodePtr &anf_out) {
|
||
MS_EXCEPTION_IF_NULL(anf_out); //检查输入的AnfNode是否为空,如果为空,则抛出异常
|
||
if (anf_out->isa<Parameter>()) { //检查AnfNode是否是Parameter节点。如果是Parameter节点,表示该节点是图的输出节点。
|
||
MS_LOG(INFO) << "Add graph output: " << anf_out->fullname_with_scope();
|
||
auto it = out_handle_cache_.find(anf_out.get());
|
||
if (it != out_handle_cache_.end()) { //如果在 out_handle_cache_ 中找到该Parameter节点的输出句柄(OutHandler),
|
||
//说明该Parameter节点是在Dataset图模式下的输入参数,需要特殊处理。将其作为图的输出添加到输出列表 graph_outputs_ 中,并记录其对应的运算算子(op)以及输出名称(out_name)。
|
||
// For dataset graph mode, input parameter is converted to a "iterator_get_next:yn" OutHandler.
|
||
OutHandler handle = it->second;
|
||
auto op = handle.op;
|
||
MS_LOG(INFO) << "op name: " << op->GetName() << ", op type: " << op->GetOpType() << ", out_name: " << handle.out;
|
||
(void)graph_outputs_.emplace_back(*op, handle.out);
|
||
} else { //如果在 out_handle_cache_ 中未找到该Parameter节点的输出句柄,说明该Parameter节点是普通的输入参数,
|
||
//将其转换为运算算子并添加到输出列表 graph_outputs_ 中。
|
||
// common parameter case
|
||
auto op = Convert(anf_out);
|
||
if (op != nullptr) {
|
||
MS_LOG(INFO) << "op name: " << op->GetName() << ", op type: " << op->GetOpType();
|
||
(void)graph_outputs_.emplace_back(*op, "");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
该函数的目的是在Dataset图模式下,根据Dataset图的参数信息,设置 iterator_getnext
|
||
算子的输出个数和输出描述信息,确保与Dataset图的配置相匹配。
|
||
*/
|
||
void SetupDatasetIterGetNextNode(const OperatorPtr &op) {
|
||
if (ConfigManager::GetInstance().dataset_mode() == DS_SINK_MODE) { //检查配置管理器 ConfigManager 的 dataset_mode() 是否为 DS_SINK_MODE,即检查是否在Dataset图模式下。
|
||
DatasetGraphParam param = ConfigManager::GetInstance().dataset_param(); //如果处于Dataset图模式下,从配置管理器中获取Dataset图的参数 param
|
||
size_t output_num = param.ge_types().size();//根据参数 param 中的信息,确定 iterator_getnext 算子的输出个数 output_num,即需要设置多少个输出。
|
||
MS_LOG(INFO) << "Set iterator_getnext op's output num = " << output_num << ".";
|
||
// set iterator_getnext op's output num 将 op 转换为 ge::op::GetNext 类型的算子,以便进行输出的设置
|
||
shared_ptr<ge::op::GetNext> iter_getnext = std::static_pointer_cast<ge::op::GetNext>(op);
|
||
(void)iter_getnext->create_dynamic_output_y(static_cast<unsigned int>(output_num)); //调用 create_dynamic_output_y 方法,设置 iterator_getnext 算子的输出个数为 output_num
|
||
|
||
//对于每个输出,根据 param 中的形状信息和数据类型信息,创建相应的 ge::TensorDesc 对象,并使用 update_dynamic_output_desc_y 方法设置每个输出的描述信息
|
||
for (uint32_t i = 0; i < output_num; i++) {
|
||
ge::TensorDesc desc(GeShape(param.shapes()[i]), ge::FORMAT_NCHW, (ge::DataType)param.ge_types()[i]);
|
||
// we don't SetRealDimCnt here since GE do not use this output's real-dim
|
||
(void)iter_getnext->update_dynamic_output_desc_y((i), desc);
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
|
||
/*
|
||
该函数的目的是处理Case节点的子图,构建Case节点的所有分支的子图,并设置为对应的父算子的子图。
|
||
*/
|
||
void DfGraphConvertor::SetSubgraph(const AnfNodePtr &node) {
|
||
if (!node->isa<CNode>()) { //检查传入的节点 node 是否为CNode类型,如果不是,则直接返回,不做处理
|
||
return;
|
||
}
|
||
auto cnode = node->cast<CNodePtr>();
|
||
if (!IsCaseNode(cnode)) { //判断节点 node 是否为Case节点,通过调用 IsCaseNode 方法来判断。
|
||
return; //如果不是Case节点,则同样直接返回,不做处理
|
||
}
|
||
std::vector<AnfNodePtr> case_inputs; //如果节点 node 是Case节点,那么从Case节点的输入中获取所有的Case分支的输入节点,即 case_inputs。这些输入节点将被用于后续处理子图。
|
||
for (size_t i = 1; i < cnode->inputs().size(); i++) {
|
||
case_inputs.emplace_back(cnode->input(i));
|
||
}
|
||
std::shared_ptr<std::vector<DfGraph>> branches = std::make_shared<std::vector<DfGraph>>();//创建一个存储DfGraph的指针数组 branches,用于存储Case节点的所有分支子图。
|
||
auto bnode = cnode->input(0)->cast<CNodePtr>()->input(2)->cast<CNodePtr>();
|
||
|
||
for (size_t i = 1; i < bnode->inputs().size(); i++) { //从Case节点的输入中获取Case节点的第二个输入,即Case节点的condition值(bnode),并将其转换为CNode类型
|
||
auto branch_node = bnode->input(i)->cast<CNodePtr>();
|
||
for (size_t j = 2; j < branch_node->inputs().size(); j++) {//遍历 bnode 的所有输入(即Case节点的每个分支),获取每个分支的CNode节点 branch_node
|
||
if (std::find(case_inputs.begin(), case_inputs.end(), branch_node->input(j)) == case_inputs.end()) {
|
||
case_inputs.emplace_back(branch_node->input(j)); //对于每个分支,遍历其输入节点,将不在 case_inputs 中的输入节点添加到 case_inputs 中,以确保 case_inputs 包含所有分支的输入节点。
|
||
}
|
||
}
|
||
}
|
||
//分别对每个分支节点调用 ProcessSubgraph 方法进行处理,该方法会处理分支节点的子图,并将结果存储在 branches_map_ 中
|
||
for (size_t i = 1; i < bnode->inputs().size(); i++) {
|
||
ProcessSubgraph(bnode->input(i), case_inputs);
|
||
}
|
||
//遍历 bnode 的所有输入(即Case节点的每个分支),将每个分支的子图从 branches_map_ 中取出,并添加到 branches 中
|
||
for (size_t i = 1; i < bnode->inputs().size(); i++) {
|
||
(void)branches->emplace_back(branches_map_[bnode->input(i).get()]);
|
||
}
|
||
|
||
if (op_cache_.find(node.get()) == op_cache_.end()) {
|
||
return;
|
||
}
|
||
|
||
OpAdapterPtr adpt = FindAdapter(node, training_);
|
||
if (adpt == nullptr) {
|
||
MS_LOG(DEBUG) << "Not found adapter";
|
||
return;
|
||
}
|
||
//通过调用 Convert 方法将节点 node 转换为 OperatorPtr 类型的算子 op
|
||
OperatorPtr op = Convert(node);
|
||
(void)adpt->setSubgraph(op, 0, branches); //查找与节点 node 相应的适配器 adpt,并将分支子图 branches 设置为 op 的子图
|
||
return;
|
||
}
|
||
|
||
/*
|
||
该函数的目的是处理Case节点的输入,将每个Case分支的输出信息存储在 tuple_out_handle_cache_ 中,
|
||
并将Case节点的输入项存储在case_input_handle_cache_ 中。这些信息将在后续的子图构建中用到。
|
||
*/
|
||
void DfGraphConvertor::GetCaseNodeInput(const CNodePtr node, const CNodePtr input_node) {
|
||
std::vector<AnfNodePtr> case_inputs;
|
||
for (size_t i = 1; i < node->inputs().size(); i++) { //从Case节点的输入中获取所有的Case分支的输入节点,并存储在 case_inputs 中
|
||
case_inputs.emplace_back(node->input(i));
|
||
}
|
||
auto bnode = input_node->input(2)->cast<CNodePtr>();
|
||
MS_EXCEPTION_IF_NULL(bnode);
|
||
for (size_t i = 1; i < bnode->inputs().size(); i++) { //从Case节点的输入中获取Case节点的第二个输入,即Case节点的condition值(input_node),并将其转换为CNode类型。
|
||
auto branch_node = bnode->input(i)->cast<CNodePtr>();
|
||
MS_EXCEPTION_IF_NULL(branch_node);
|
||
for (size_t j = 2; j < branch_node->inputs().size(); j++) {
|
||
if (std::find(case_inputs.begin(), case_inputs.end(), branch_node->input(j)) == case_inputs.end()) {
|
||
case_inputs.emplace_back(branch_node->input(j));
|
||
}
|
||
}
|
||
}
|
||
|
||
const size_t case_index = 1;
|
||
const size_t make_tuple_index = 2;
|
||
|
||
AnfNodePtr case_index_iter = input_node->input(case_index);
|
||
AnfNodePtr make_tuple_iter = input_node->input(make_tuple_index);
|
||
auto make_tuple_node = make_tuple_iter->cast<CNodePtr>(); //获取 input_node 的第二个输入(即Case节点的make_tuple),并转换为CNode类型,并存储在 make_tuple_node 中。
|
||
std::shared_ptr<std::vector<OutHandler>> tuple_items = std::make_shared<std::vector<OutHandler>>();//创建一个存储OutHandler的指针数组 tuple_items,用于存储每个Case分支的输出。
|
||
|
||
for (size_t i = 0; i < case_inputs.size(); i++) {
|
||
auto item = case_inputs[i];
|
||
auto op = Convert(item);
|
||
if (op != nullptr) { //对于每个输入节点 item,如果可以将其转换为算子 op,则将其添加到 tuple_items 中。
|
||
(void)tuple_items->emplace_back(OutHandler(op, "", item));
|
||
} else if (out_handle_cache_.find(item.get()) != out_handle_cache_.end()) { //否则,如果 item 已经在 out_handle_cache_ 中有对应的OutHandler缓存,则直接将其添加到 tuple_items 中。
|
||
tuple_items->push_back(out_handle_cache_[item.get()]);
|
||
} else { ////如果既不能转换为算子也不在 out_handle_cache_ 中,那么添加一个空的OutHandler到 tuple_items 中。
|
||
MS_LOG(DEBUG) << "Add an empty out handler: " << item->ToString();
|
||
tuple_items->emplace_back(OutHandler());
|
||
}
|
||
}
|
||
|
||
tuple_out_handle_cache_[make_tuple_node.get()] = tuple_items;//将 tuple_items 存储在 tuple_out_handle_cache_ 中,键为 make_tuple_node。
|
||
|
||
std::shared_ptr<std::vector<AnfNodePtr>> case_input_items = std::make_shared<std::vector<AnfNodePtr>>();
|
||
//创建一个存储AnfNodePtr的指针数组 case_input_items,用于存储Case节点的输入项。
|
||
//将Case节点的第一个输入(case_index_iter)和第二个输入(make_tuple_iter)添加到 case_input_items 中,
|
||
//并将 case_input_items 存储在 case_input_handle_cache_ 中,键为 node。
|
||
(void)case_input_items->emplace_back(case_index_iter);
|
||
(void)case_input_items->emplace_back(make_tuple_iter);
|
||
case_input_handle_cache_[node.get()] = case_input_items;
|
||
}
|
||
|
||
/*
|
||
该函数的目的是将在前面的处理过程中成功转换为算子的OutHandler更新到 tuple_out_handle_cache_中,
|
||
以确保在后续的处理中能够正确获取相关的算子信息。
|
||
*/
|
||
void DfGraphConvertor::UpdateTupleOutCache() {
|
||
for (auto &it : tuple_out_handle_cache_) { //遍历 tuple_out_handle_cache_ 中的每个键值对
|
||
std::size_t len = it.second->size(); //其中键为 it,值为 it.second,即指向 std::vector<OutHandler> 的智能指针
|
||
for (std::size_t i = 0; i < len; i++) { //对于每个OutHandler数组,计算其大小,即 len。
|
||
OutHandler handle = (*it.second)[i]; //遍历该OutHandler数组,对于每个OutHandler:
|
||
if (handle.op == nullptr) { //如果其 op 为nullptr,表示没有对应的算子,跳过该OutHandler。
|
||
continue;
|
||
}
|
||
string name = handle.op->GetName(); //否则,获取该OutHandler对应算子的名称 name。
|
||
if (vars_.count(name) && (vars_[name] != nullptr)) { //检查 vars_ 中是否包含该名称,并且对应的算子不为nullptr(即在前面的处理过程中已经成功转换为算子)
|
||
(*it.second)[i] = OutHandler(vars_[name], handle.out, handle.node);// 如果满足条件,更新当前OutHandler为 vars_[name] 对应的算子,并保持原来的 out 和 node 信息。
|
||
MS_LOG(INFO) << "update tuple_out_handle_cache_ " << name; //输出日志,表示成功更新了 tuple_out_handle_cache_ 中的信息。
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
该函数通过处理ANF图并为图中的每个节点设置所需的输入、输出和依赖项来构建数据流图。
|
||
*/
|
||
DfGraphConvertor &DfGraphConvertor::BuildGraph() {
|
||
SetupDatasetIterGetNextNode(dataset_iter_getnext_); //如果数据集模式为DS_SINK_MODE,则设置用于数据集图模式中迭代器的GetNext操作符。
|
||
|
||
if (error_ != SUCCESS) {
|
||
return *this;
|
||
}
|
||
|
||
// Case node set input.
|
||
std::vector<AnfNodePtr> nodes = GetOrderedCNodes(anf_graph_);
|
||
for (auto &it : nodes) {
|
||
if (it->isa<CNode>() && IsCaseNode(it->cast<CNodePtr>())) {
|
||
auto node = it->cast<CNodePtr>();
|
||
auto input_node = node->input(0)->cast<CNodePtr>();
|
||
GetCaseNodeInput(node, input_node); //对于ANF图中的每个Case节点,通过遍历其输入节点设置Case节点的输入。
|
||
}
|
||
}
|
||
|
||
// update tuple_out_handle_cache_
|
||
UpdateTupleOutCache(); //更新tuple_out_handle_cache_,以包含成功转换的OutHandler。
|
||
|
||
// set up dependencies 设置依赖和输入:该函数遍历ANF图中的所有节点,为每个节点设置输入和控制输入,同时处理任何子图并更新操作符描述。
|
||
MS_LOG(DEBUG) << "set up dependencies";
|
||
nodes = GetOrderedCNodes(anf_graph_);
|
||
for (auto &it : nodes) {
|
||
SetNodeInput(it);
|
||
SetOpControlInput(it);
|
||
SetSubgraph(it);
|
||
UpdateOpDesc(it);
|
||
}
|
||
|
||
if (error_ == SUCCESS) { //如果没有错误,则使用ANF图的名称创建数据流图(df_graph_)。
|
||
df_graph_ = make_shared<DfGraph>(anf_graph_->ToString());
|
||
} else {
|
||
return *this;
|
||
}
|
||
|
||
// set graph input according to the order from anf graph
|
||
//设置图输入:根据数据集模式和是否使用自定义输入,设置图的输入,添加任何用于工作与常量的操作符的常量节点作为图的输入。
|
||
std::vector<Operator> inputs;
|
||
if (ConfigManager::GetInstance().dataset_mode() == DS_SINK_MODE) {
|
||
inputs.push_back(*dataset_iter_getnext_);
|
||
} else {
|
||
auto params = anf_graph_->parameters();
|
||
if (use_inputs_) {
|
||
params = inputs_;
|
||
auto anf_params = anf_graph_->parameters();
|
||
for (size_t i = 0; i < params.size(); i++) {
|
||
for (size_t j = 0; j < anf_params.size(); j++) {
|
||
if (params[i]->ToString() == anf_params[j]->ToString()) {
|
||
params[i] = anf_params[j];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
int index = 0;
|
||
for (auto &it : params) {
|
||
auto name = std::static_pointer_cast<Parameter>(it)->name();
|
||
// the parameters which has not been converted to var
|
||
if (vars_.find(name) == vars_.end()) {
|
||
if (HasAbstractMonad(it)) {
|
||
MS_LOG(INFO) << it->DebugString() << " is a monad parameter, skip.";
|
||
continue;
|
||
}
|
||
auto op = Convert(it);
|
||
MS_EXCEPTION_IF_NULL(op);
|
||
MS_LOG(INFO) << "add not var input " << it->ToString() << ", index " << index;
|
||
if (op == nullptr) {
|
||
MS_LOG(ERROR) << "Convert graph failed!";
|
||
return *this;
|
||
}
|
||
UpdateDataOpDesc(it, op);
|
||
MS_LOG(INFO) << "add input " << it->ToString() << ", index " << index;
|
||
(void)std::static_pointer_cast<Data>(op)->set_attr_index(index++);
|
||
inputs.push_back(*op);
|
||
} else if (vars_[name] != nullptr) {
|
||
MS_LOG(INFO) << "add var input " << it->ToString();
|
||
auto op = Convert(it);
|
||
UpdateConstOpDesc(it, vars_[name]);
|
||
MS_EXCEPTION_IF_NULL(op);
|
||
inputs.push_back(*op);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
MS_LOG(DEBUG) << "trace output";
|
||
graph_outputs_.clear();
|
||
TraceOutput(anf_graph_->get_return()->input(1));//对图的输出节点进行跟踪,以填充graph_outputs_向量。
|
||
|
||
// Add const nodes as graph input for some operator work with constant
|
||
MS_LOG(INFO) << "graph const input size: " << graph_const_inputs_.size();
|
||
(void)std::transform(graph_const_inputs_.begin(), graph_const_inputs_.end(), std::back_inserter(inputs),
|
||
[](const OperatorPtr &x) { return *x; });
|
||
|
||
MS_LOG(INFO) << "set graph input num: " << inputs.size();
|
||
(void)df_graph_->SetInputs(inputs);
|
||
|
||
// set graph output
|
||
// set the value of finale return apply node as the output of dataflow graph
|
||
//设置图输出:使用graph_outputs_向量设置图的输出。
|
||
MS_LOG(DEBUG) << "set output";
|
||
MS_LOG(INFO) << "set graph output num: " << graph_outputs_.size();
|
||
(void)df_graph_->SetOutputs(graph_outputs_);
|
||
|
||
compute_sout_ << "}" << endl;
|
||
// For the graph(e.g. eval_subgraph) whose IterNum is 1, donot set NeedIteration flag.
|
||
//设置NeedIteration标志:如果迭代次数(iter_num)大于1,则将NeedIteration标志设置为true,用于数据流图。
|
||
if (ConfigManager::GetInstance().iter_num() > 1) {
|
||
df_graph_->SetNeedIteration(true);
|
||
}
|
||
return *this;
|
||
}
|
||
|
||
/*
|
||
此函数负责更新运算符的输出描述。
|
||
它确保运算符的输出说明与ConstantkOpFormat_NCHWConstant为参数指定的格式匹配(如果适用)。
|
||
*/
|
||
void DfGraphConvertor::UpdateConstOpDesc(const AnfNodePtr &it, const OperatorPtr &op) const {
|
||
if (!it->isa<Parameter>()) { //检查输入是否为节点。如果不是,它会记录一条调试消息,指示它不是参数,并且函数立即返回而无需进一步处理。
|
||
MS_LOG(DEBUG) << "It is not parameter, name: " << it->DebugString();
|
||
return;
|
||
}
|
||
auto para = it->cast<ParameterPtr>(); //如果是节点,则检索相应的对象(强制转换),并将默认格式分配给字符串变量。
|
||
MS_EXCEPTION_IF_NULL(para);
|
||
std::string format = kOpFormat_NCHW;
|
||
std::string param_debug_info = para->DebugString();
|
||
auto param_format = param_format_.find(param_debug_info); //检索参数param_debug_info()的调试信息,并尝试在 map 中找到与此参数关联的格式。如果找到格式,它将相应地更新变量并记录调试消息
|
||
if (param_format != param_format_.end()) {
|
||
format = param_format->second; //格式未更改,无需更新运算符说明。该函数记录调试消息并返回
|
||
MS_LOG(DEBUG) << "Parameter debug info: " << param_debug_info << ", format is " << format;
|
||
}
|
||
if (format == kOpFormat_NCHW) {
|
||
MS_LOG(DEBUG) << "Format is not changed, no need to update op desc, name: " << param_debug_info;
|
||
return;
|
||
}
|
||
if (!para->has_default()) {
|
||
MS_LOG(DEBUG) << "Parameter has no default, no need to update op desc, name: " << param_debug_info;
|
||
return;
|
||
}
|
||
auto value = para->default_param();
|
||
MS_EXCEPTION_IF_NULL(value);
|
||
auto tensor = value->cast<std::shared_ptr<tensor::Tensor>>(); //假设参数存在默认值,该函数将检索值value() 并将其强制转换为std::shared_ptr<tensor::Tensor>
|
||
MS_EXCEPTION_IF_NULL(tensor); //使用该函数创建新的运算符描述 (),传递张量的形状、数据类型和更新格式(如果适用)。const_op_descTransformUtil::GetGeTensorDesc
|
||
auto const_op_desc = TransformUtil::GetGeTensorDesc(tensor->shape_c(), tensor->data_type(), format);
|
||
if (const_op_desc == nullptr) { //如果创建失败(返回 nullptr),该函数将记录警告并返回。
|
||
MS_LOG(WARNING) << "Create parameter " << para->name() << " output descriptor failed!";
|
||
return;
|
||
}
|
||
(void)std::static_pointer_cast<Constant>(op)->update_output_desc_y(*const_op_desc); //使用新创建的 .Constantopconst_op_desc
|
||
}
|
||
|
||
void DfGraphConvertor::UpdateDataOpDesc(const AnfNodePtr &it, const OperatorPtr &op) const {
|
||
auto node = std::static_pointer_cast<AnfNode>(it); //将节点it转换为std::shared_ptr<AnfNode>
|
||
if (node == nullptr) { //如果转换失败或node是nullptr,则会记录错误并返回。
|
||
MS_LOG(ERROR) << "Update data op descriptor failed! Invalid node.";
|
||
return;
|
||
}
|
||
|
||
std::vector<int64_t> shape; //从abstract::Shape中提取节点的形状。如果无法提取形状或节点具有无效的形状,它将记录一条消息abstract::NoShape并返回。
|
||
if (auto normal_shape_ptr = dyn_cast<abstract::Shape>(node->Shape()); normal_shape_ptr != nullptr) {
|
||
shape = normal_shape_ptr->shape();
|
||
} else if (auto no_shape_ptr = dyn_cast<abstract::NoShape>(node->Shape()); no_shape_ptr != nullptr) {
|
||
shape = {};
|
||
} else {
|
||
MS_LOG(INFO) << "Invalid shape to update data op descriptor.";
|
||
return;
|
||
}
|
||
|
||
if (node->Type() == nullptr) { //检查节点的类型。如果类型不可用(即nullptr),将记录一条消息并返回。
|
||
MS_LOG(INFO) << "Invalid type to update data op descriptor.";
|
||
return;
|
||
}
|
||
TypeId me_type = node->Type()->type_id();
|
||
if (kObjectTypeTensorType == me_type) { //如果节点类型为kObjectTypeTensorType,则尝试从中提取元素类型。
|
||
me_type = dyn_cast<TensorType>(node->Type())->element()->type_id();
|
||
}
|
||
std::ostringstream buf;
|
||
buf << "[" << shape << "]";
|
||
MS_LOG(INFO) << "input shape is " << buf.str() << ", type is " << me_type;//使用MS_LOG(INFO)在日志中打印节点的形状和类型信息。
|
||
std::string format = "NCHW";
|
||
if (it->isa<Parameter>()) { //如果节点的类型为Parameterparam_format_,则尝试提取其名称并查找其格式
|
||
auto param = it->cast<ParameterPtr>();
|
||
std::string param_name = param->DebugString();
|
||
auto param_format = param_format_.find(param_name);
|
||
if (param_format != param_format_.end()) { //确定节点的格式后(如果在param_format_中找不到,则默认为“NCHW”),它会调用形状、类型和格式以获取张量描述符
|
||
format = param_format->second;
|
||
MS_LOG(DEBUG) << "parameter: " << param_name << ", format is " << format;
|
||
}
|
||
}
|
||
auto desc = TransformUtil::GetGeTensorDesc(shape, me_type, format);
|
||
if (desc == nullptr) { //如果为 null,则记录错误;否则,使用获得的张量描述符更新对象的输入和输出描述符
|
||
MS_LOG(ERROR) << "Update data op descriptor failed! TensorDesc is null.";
|
||
} else {
|
||
(void)std::static_pointer_cast<Data>(op)->update_input_desc_x(*desc);
|
||
(void)std::static_pointer_cast<Data>(op)->update_output_desc_y(*desc);
|
||
}
|
||
}
|
||
|
||
DfGraphPtr DfGraphConvertor::GetComputeGraph() { return df_graph_; }
|
||
|
||
DfGraphPtr DfGraphConvertor::GetInitGraph() { return init_graph_; }
|
||
|
||
DfGraphPtr DfGraphConvertor::GetSaveCheckpointGraph() { return save_ckp_graph_; }
|
||
|
||
DfGraphPtr DfGraphConvertor::GetBroadcastGraph() { return broadcast_graph_; }
|
||
|
||
/*
|
||
该函数用于判断一个节点是否为源边节点
|
||
*/
|
||
bool DfGraphConvertor::IsSourceEdgeNode(const AnfNodePtr &node) {
|
||
if (!node->isa<CNode>()) { //判断该节点是否为 CNode 类型,如果不是则返回 false
|
||
return false;
|
||
}
|
||
auto cnode = node->cast<CNodePtr>();
|
||
if (!IsCustomCNode(cnode)) { //获取该 CNode 的目标函数名,如果为空则返回 false
|
||
std::string name = GetCNodeTargetFuncName(cnode);
|
||
if (name.empty()) {
|
||
return false;
|
||
}
|
||
|
||
// Ignore apply node Depend, UpdateState, make_tuple. make_tuple in ge pipeline.
|
||
//忽略一些特定的节点,如 Depend、UpdateState、make_tuple 和 Return
|
||
//如果节点的目标函数名是这些特定节点之一,则返回 false。
|
||
if ((name == prim::kPrimDepend->name()) || (name == prim::kPrimUpdateState->name()) ||
|
||
(name == prim::kPrimReturn->name()) || (name == prim::kPrimMakeTuple->name())) {
|
||
return false;
|
||
}
|
||
}
|
||
// Load and other normal primitives which contain monad node.
|
||
//检查该节点的输入是否包含 monad 节点,如果有则返回 true
|
||
auto has_monad = std::any_of(cnode->inputs().begin(), cnode->inputs().end(),
|
||
[](const AnfNodePtr &node) -> bool { return HasAbstractMonad(node); });
|
||
if (has_monad) {
|
||
return true;
|
||
}
|
||
|
||
// primitive with make_tuple as input
|
||
//检查该节点的输入是否包含以 make_tuple 为目标函数的 CNode。如果是,则检查 make_tuple 的输入是否包含 monad 节点,如果有则返回 true。
|
||
for (auto &input : cnode->inputs()) {
|
||
if (IsPrimitiveCNode(input, prim::kPrimMakeTuple)) {
|
||
auto tuple = input->cast<CNodePtr>();
|
||
auto ret = std::any_of(tuple->inputs().begin(), tuple->inputs().end(),
|
||
[](const AnfNodePtr &node) -> bool { return HasAbstractMonad(node); });
|
||
if (ret) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
//如果以上条件都不满足,则返回 false,表示该节点不是源边节点。
|
||
return false;
|
||
}
|
||
|
||
/*
|
||
该函数用于判断一个节点是否为控制边节点
|
||
*/
|
||
bool DfGraphConvertor::IsControlEdgeNode(const AnfNodePtr &node) {
|
||
if (!node->isa<CNode>()) { //判断该节点是否为 CNode 类型,如果不是则返回 false
|
||
return false;
|
||
}
|
||
auto cnode = node->cast<CNodePtr>();
|
||
if (!IsCustomCNode(cnode)) { //获取该 CNode 的目标函数名,如果为空则返回 false
|
||
std::string name = GetCNodeTargetFuncName(cnode);
|
||
if (name.empty()) {
|
||
return false;
|
||
}
|
||
|
||
// Ignore apply node of Load, Depend, UpdateState, make_tuple, return
|
||
//忽略一些特定的节点,如 Load、Depend、UpdateState、make_tuple 和 Return
|
||
//如果节点的目标函数名是这些特定节点之一,则返回 false
|
||
if ((name == prim::kPrimLoad->name()) || (name == prim::kPrimDepend->name()) ||
|
||
(name == prim::kPrimUpdateState->name()) || (name == prim::kPrimMakeTuple->name()) ||
|
||
(name == prim::kPrimReturn->name())) {
|
||
return false;
|
||
}
|
||
}
|
||
//如果以上条件都不满足,则返回 true,表示该节点是控制边节点
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
将给定的AnfNodePtr对象转换为OperatorPtr对象。
|
||
在这之前,先调用GetRealOpNode函数获取真实操作节点,并将其传递给Convert函数进行转换。
|
||
如果转换失败,将记录错误日志并返回nullptr,否则返回转换后的OperatorPtr对象。
|
||
*/
|
||
OperatorPtr DfGraphConvertor::ToOperatorPtr(const AnfNodePtr &node) {
|
||
auto op = Convert(GetRealOpNode(node)); // 获取真实操作节点
|
||
if (op == nullptr) { //// 如果转换失败,则记录错误日志,并设置error_为FAILED
|
||
MS_LOG(ERROR) << "Convert real op node to operator failed, " << node->ToString();
|
||
error_ = FAILED;
|
||
return nullptr;
|
||
}
|
||
//返回转换后的OperatorPtr对象
|
||
return op;
|
||
}
|
||
|
||
/*
|
||
是为DfGraphConvertor类维护的monad_control_edge_cache_缓存添加控制依赖边。
|
||
在这个缓存中,每个源节点src都有一个对应的目标节点集合,表示src所依赖的控制节点。
|
||
*/
|
||
void DfGraphConvertor::AddEdgeToCache(const AnfNodePtr &src, const AnfNodePtr &dest) {
|
||
auto item = monad_control_edge_cache_.find(src); //检查源节点是否已存在于控制依赖边缓存中
|
||
if (item == monad_control_edge_cache_.end()) { // 如果源节点不存在于缓存中,则创建一个新的缓存项,并将目标节点添加到该缓存项中
|
||
monad_control_edge_cache_[src] = std::set<AnfNodePtr>{dest};
|
||
} else { // 如果源节点已存在于缓存中,则将目标节点添加到该源节点的依赖节点集合中
|
||
// 使用insert函数插入目标节点,set确保不会重复插入重复的目标节点
|
||
(void)item->second.insert(dest);
|
||
}
|
||
}
|
||
|
||
//该函数为Load类型节点添加控制依赖边
|
||
void DfGraphConvertor::AddEdgeForLoad(const AnfNodePtr &node) {
|
||
auto func_graph = node->func_graph(); // 获取节点所属的函数图
|
||
MS_EXCEPTION_IF_NULL(func_graph);
|
||
auto mng = func_graph->manager(); // 获取函数图的管理器
|
||
if (mng == nullptr) { // 如果管理器为空,则创建一个新的管理器,并将其设置为函数图的管理器
|
||
mng = Manage(func_graph, true);
|
||
func_graph->set_manager(mng);
|
||
}
|
||
auto manager = func_graph->manager(); //再次获取函数图的管理器
|
||
MS_EXCEPTION_IF_NULL(manager);
|
||
if (manager->node_users().find(node) == manager->node_users().end()) { // 检查节点是否在管理器的节点用户集合中
|
||
MS_LOG(EXCEPTION) << "Can't find node in nodes_users.";
|
||
}
|
||
auto &users = manager->node_users()[node]; // 获取节点的用户集合
|
||
// 创建用于存储源节点和目标节点的共享指针列表
|
||
std::shared_ptr<std::vector<AnfNodePtr>> src_node_list = std::make_shared<std::vector<AnfNodePtr>>();
|
||
std::shared_ptr<std::vector<AnfNodePtr>> dst_node_list = std::make_shared<std::vector<AnfNodePtr>>();
|
||
for (const auto &iter : users) { // 遍历节点的用户集合,将相关的源节点和目标节点添加到对应的列表中
|
||
auto user_node = iter.first;
|
||
auto name = GetCNodeTargetFuncName(user_node->cast<CNodePtr>());
|
||
if (name == prim::kPrimUpdateState->name()) { // 如果用户节点是prim::kPrimUpdateState类型,则将其作为目标节点,并查找它的目标操作节点
|
||
FindDestOps(user_node, dst_node_list, false);
|
||
continue;
|
||
}
|
||
if (IsControlEdgeNode(user_node)) { // 如果用户节点是控制边节点(可能是ControlDepend类型),则将其作为源节点
|
||
src_node_list->push_back(user_node);
|
||
continue;
|
||
}
|
||
FindDestOps(user_node, src_node_list, false); // 否则,将用户节点作为普通的源节点,并查找它的目标操作节点
|
||
}
|
||
|
||
// add to cache
|
||
// 将源节点和目标节点的组合添加到控制依赖边缓存中
|
||
for (auto &dest : *dst_node_list) {
|
||
for (auto &src : *src_node_list) {
|
||
AddEdgeToCache(src, dest);
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
该函数的主要目的是递归地查找给定节点的目标操作节点,并将这些目标操作节点添加到node_list中。
|
||
top参数用于标识当前节点是否为最顶层节点,如果为true,则只有当用户节点是控制边节点时才会将其添加到node_list中。
|
||
如果为false,则不论用户节点类型,都会将其添加到node_list中。
|
||
*/
|
||
void DfGraphConvertor::FindDestOps(const AnfNodePtr &node, const std::shared_ptr<std::vector<AnfNodePtr>> &node_list,
|
||
bool top) {
|
||
MS_EXCEPTION_IF_NULL(node); // 检查输入节点是否为空
|
||
auto func_graph = node->func_graph(); // 获取节点所属的函数图
|
||
MS_EXCEPTION_IF_NULL(func_graph);
|
||
auto mng = func_graph->manager(); // 获取函数图的管理器
|
||
if (mng == nullptr) { // 如果管理器为空,则创建一个新的管理器,并将其设置为函数图的管理器
|
||
mng = Manage(func_graph, true);
|
||
func_graph->set_manager(mng);
|
||
}
|
||
auto manager = func_graph->manager(); // 再次获取函数图的管理器
|
||
MS_EXCEPTION_IF_NULL(manager);
|
||
|
||
auto users = manager->node_users()[node]; // 获取节点的用户集合
|
||
for (const auto &iter : users) { // 遍历节点的用户集合
|
||
auto user_node = iter.first;
|
||
if (IsControlEdgeNode(user_node)) { // 如果用户节点是控制边节点(可能是ControlDepend类型),并且不是最顶层节点,则将其添加到node_list中
|
||
if (!top) {
|
||
node_list->push_back(user_node);
|
||
}
|
||
} else { // 否则,递归地查找该用户节点的目标操作节点,并将其添加到node_list中
|
||
FindDestOps(user_node, node_list, false);
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
该函数主要用于自动收集Monad输入,并根据情况建立相应的控制依赖边。
|
||
在深度学习框架中,Monad通常是指一种特殊的数据依赖关系,用于控制计算图的执行顺序。
|
||
*/
|
||
void DfGraphConvertor::AutoMonadCollectInput(const AnfNodePtr &node) {
|
||
if (!IsSourceEdgeNode(node)) { // 检查节点是否为源边节点,如果不是,则不需要处理控制依赖
|
||
return;
|
||
}
|
||
|
||
// Add control edge if contain monad input.
|
||
// 如果是Load类型节点,则为其添加控制依赖边
|
||
std::string name = GetCNodeTargetFuncName(node->cast<CNodePtr>());
|
||
if (name == prim::kPrimLoad->name()) {
|
||
AddEdgeForLoad(node);
|
||
} else { // 否则,获取节点对应的操作对象
|
||
auto src_ops = ToOperatorPtr(node);
|
||
if (src_ops != nullptr) { // 如果操作对象存在,则查找其目标操作节点并为其添加控制依赖边
|
||
// Find dest ops list
|
||
// 查找目标操作节点列表
|
||
std::shared_ptr<std::vector<AnfNodePtr>> dst_node_list = std::make_shared<std::vector<AnfNodePtr>>();
|
||
FindDestOps(node, dst_node_list, true);
|
||
for (auto &dest : *dst_node_list) { // 将源节点与目标操作节点逐一添加为控制依赖边
|
||
AddEdgeToCache(node, dest);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
该函数用于自动设置Monad输入,即根据控制依赖边缓存(monad_control_edge_cache_)中的信息,
|
||
为给定节点建立控制依赖边。
|
||
*/
|
||
void DfGraphConvertor::AutoMonadSetInput(const AnfNodePtr &node) {
|
||
// 检查节点是否在控制依赖边缓存中,如果不在,则不需要进行设置
|
||
if (monad_control_edge_cache_.find(node) == monad_control_edge_cache_.end()) {
|
||
return;
|
||
}
|
||
|
||
auto src_ops = ToOperatorPtr(node); // 获取节点对应的操作对象
|
||
if (src_ops != nullptr) { // 如果操作对象存在,则遍历其对应的控制依赖目标节点,并为目标节点添加控制输入
|
||
for (auto &dest : monad_control_edge_cache_[node]) {
|
||
auto dest_ops = ToOperatorPtr(dest);
|
||
if (dest_ops == nullptr) { // 如果目标操作对象不存在,则跳过该目标节点
|
||
continue;
|
||
}
|
||
(void)dest_ops->AddControlInput(*src_ops); // 为目标操作对象添加控制输入,建立控制依赖边
|
||
#ifdef DRAW_GE_GRAPH // 在DEBUG模式下,绘制计算图时输出控制依赖关系
|
||
compute_sout_ << op_draw_name_[node.get()] << " -> " << op_draw_name_[dest.get()] << "[style=\"dotted\"]" << endl;
|
||
#endif
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
该函数主要调用了两个函数来自动设置控制依赖边。
|
||
这些控制依赖边是为了确保在深度学习框架中,计算图的执行顺序符合数据流和依赖关系的要求,以保证计算结果的正确性。
|
||
*/
|
||
void DfGraphConvertor::AutoMonadSetControlInput(const AnfNodePtr &node) {
|
||
AutoMonadCollectInput(node); // 自动收集Monad输入,建立控制依赖边
|
||
AutoMonadSetInput(node); // 自动设置Monad输入,建立控制依赖边
|
||
}
|
||
|
||
//该函数主要用于为操作节点设置控制输入,即建立控制依赖边。
|
||
void DfGraphConvertor::SetOpControlInput(const AnfNodePtr &node) {
|
||
MS_EXCEPTION_IF_NULL(node); // 检查输入节点是否为空
|
||
AutoMonadSetControlInput(node); // 自动设置Monad输入,建立控制依赖边
|
||
if (control_edge_cache_.find(node.get()) == control_edge_cache_.end()) { // 检查当前节点是否在控制边缓存中
|
||
return; //如果不在,则直接返回
|
||
}
|
||
// 获取当前节点的控制边缓存信息
|
||
std::vector<ControlEdge> control_edges = control_edge_cache_[node.get()];
|
||
if ((control_edges.empty())) { // 如果控制边缓存为空,则记录错误日志并返回
|
||
MS_LOG(ERROR) << "Get control edge node's src or dest operator failed";
|
||
return;
|
||
}
|
||
|
||
for (auto &item : control_edges) { // 为当前节点的目标操作节点添加控制输入
|
||
(void)item.dest_op->AddControlInput(*item.src_op);
|
||
}
|
||
}
|
||
//不可变的常量向量
|
||
const std::vector<std::string> trans_var_list = {string(kNameAssign), string(kNameAssignAdd), string(kNameAssignSub)};
|
||
|
||
//该函数用于从Load类型节点中获取对应的常数参数节点
|
||
AnfNodePtr DfGraphConvertor::ParseLoadInput(const CNodePtr &cnode) {
|
||
if (cnode->inputs().size() < 3) { // 检查CNode的输入数量是否小于3
|
||
MS_LOG(EXCEPTION) << "input size error, " << cnode->ToString();
|
||
}
|
||
const size_t para_index = 1; // 定义常数参数的索引为1(Load节点通常为cnode->inputs()[1])
|
||
return cnode->input(para_index); // 返回Load节点的常数参数对应的AnfNodePtr
|
||
}
|
||
|
||
//该函数用于处理元组类型节点的输入,并将它们设置为目标操作符的输入。
|
||
void DfGraphConvertor::SetTupleOpInput(const OpAdapterPtr &adpt, const CNodePtr &node, const AnfNodePtr &pred,
|
||
const OperatorPtr &src, int index) {
|
||
// 从元组的输出句柄缓存中获取处理器向量
|
||
std::shared_ptr<std::vector<OutHandler>> handler_vec = tuple_out_handle_cache_[pred.get()];
|
||
// 创建一个新的处理器向量用于保存没有Monad类型的元素
|
||
std::shared_ptr<std::vector<OutHandler>> handler_vec_without_monad = std::make_shared<std::vector<OutHandler>>();
|
||
bool with_monad = false; // 用于标记处理器向量中是否包含Monad类型元素
|
||
// 遍历处理器向量中的每个元素,判断是否包含Monad类型元素,并将非Monad类型的元素添加到新的处理器向量中
|
||
for (auto &handler : *handler_vec) {
|
||
// when tuple with monad type element, the handler operator is nullptr, should be ignored.
|
||
if (handler.op == nullptr) {
|
||
if ((handler.node != nullptr) && !HasAbstractMonad(handler.node)) {
|
||
MS_LOG(WARNING) << "Unsupported node in tuple : " << node->ToString();
|
||
}
|
||
continue;
|
||
}
|
||
with_monad = true;
|
||
handler_vec_without_monad->push_back(handler);
|
||
}
|
||
// 使用OpAdapter的setInput方法将新的处理器向量作为输入设置给目标操作符
|
||
int ret = adpt->setInput(src, index, handler_vec_without_monad);
|
||
// 如果设置成功,并且预期的上游节点是一个CNode且它的输入数量与处理器向量大小相符(不包含Monad类型的元素)
|
||
// 则添加控制依赖边,绘制计算图,同时将处理器向量中的元素作为图的常量输入添加
|
||
if ((ret == 0) && pred->isa<CNode>() && (pred->cast<CNodePtr>()->inputs().size() == handler_vec->size() + 1)) {
|
||
for (unsigned int j = 0; j < handler_vec_without_monad->size(); j++) {
|
||
AnfNodePtr input_node = pred->cast<CNodePtr>()->input(j + 1);
|
||
if (with_monad) {
|
||
input_node = handler_vec_without_monad->at(j).node;
|
||
}
|
||
compute_sout_ << op_draw_name_[input_node.get()] << " -> " << op_draw_name_[node.get()] << ":" << index << endl;
|
||
AddGraphConstInput(handler_vec_without_monad->at(j).op);
|
||
}
|
||
return;
|
||
}
|
||
// 如果设置失败或预期的上游节点不满足条件,则记录警告日志
|
||
MS_LOG(WARNING) << "This anf node is not supported as a tuple item : " << node->ToString();
|
||
}
|
||
|
||
//该函数主要用于获取实际的输入节点,以便进行后续处理或分析。
|
||
AnfNodePtr DfGraphConvertor::GetRealInputNode(const CNodePtr &node, const AnfNodePtr &input) {
|
||
if (input == nullptr || node == nullptr) { // 检查输入节点和CNode是否为空
|
||
return nullptr;
|
||
}
|
||
AnfNodePtr pred = input; // 获取上游节点
|
||
while (pred->isa<CNode>() && GetCNodeTargetFuncName(pred->cast<CNodePtr>()) == prim::kPrimDepend->name()) {
|
||
pred = pred->cast<CNodePtr>()->input(1);
|
||
}
|
||
// skip input of UMonad, IOMonad
|
||
// 跳过UMonad和IOMonad类型的节点
|
||
if (IsValueNode<UMonad>(pred) || IsValueNode<IOMonad>(pred)) {
|
||
return nullptr;
|
||
}
|
||
// skip input of the None, UpdateState
|
||
// 跳过None类型和UpdateState类型的节点
|
||
if (IsValueNode<None>(pred) || IsPrimitiveCNode(pred, prim::kPrimUpdateState)) {
|
||
return nullptr;
|
||
}
|
||
// 对于Load节点,解析其实际输入节点
|
||
if (IsPrimitiveCNode(pred, prim::kPrimLoad)) {
|
||
pred = ParseLoadInput(pred->cast<CNodePtr>());
|
||
}
|
||
|
||
// transform "Const" op to "Variable" op when the next node is "Assign" op.
|
||
// 当前节点是"Assign"类型节点,且下一个节点是"Const"类型或"Constant"类型的Parameter节点时,转换"Const" op为"Variable" op
|
||
std::string c_name = GetCNodeTargetFuncName(node);
|
||
auto pos = std::find(trans_var_list.begin(), trans_var_list.end(), c_name);
|
||
if (!training_ && pos != trans_var_list.end() && pred->isa<Parameter>()) {
|
||
std::string name = std::static_pointer_cast<Parameter>(pred)->name();
|
||
auto op_itor = op_cache_.find(pred.get());
|
||
if (op_itor == op_cache_.end()) {
|
||
MS_LOG(EXCEPTION) << "Can not find op for node " << pred->ToString() << ".";
|
||
}
|
||
if (op_itor->second != nullptr &&
|
||
(op_itor->second->GetOpType() == "Constant" || op_itor->second->GetOpType() == "Const") &&
|
||
vars_.find(name) != vars_.end()) {
|
||
auto variable = std::make_shared<Variable>(name);
|
||
auto desc = vars_[name]->GetOutputDesc("y");
|
||
(void)variable->update_output_desc_y(desc);
|
||
MS_LOG(DEBUG) << "Trans to variable, var = " << variable->GetName() << ".";
|
||
op_itor->second = variable; // replace parameter with variable
|
||
vars_[name] = variable;
|
||
}
|
||
}
|
||
return pred; // 返回实际的输入节点
|
||
}
|
||
|
||
//该函数用于设置操作节点的输入
|
||
void DfGraphConvertor::SetOpInput(const OpAdapterPtr &adpt, const CNodePtr &node) {
|
||
OperatorPtr src = Convert(node); // 将CNode节点转换为OperatorPtr
|
||
int case_flag = 0; // case_flag用于标记是否存在特殊处理的情况
|
||
auto &inputs = node->inputs(); // 获取CNode节点的输入列表和输入数量
|
||
size_t input_size = inputs.size();
|
||
// 如果该节点在case_input_handle_cache_中,则将case_flag设置为1,同时更新输入数量为cache中的大小+1
|
||
if (case_input_handle_cache_.find(node.get()) != case_input_handle_cache_.end()) {
|
||
case_flag = 1;
|
||
input_size = case_input_handle_cache_[node.get()]->size() + 1;
|
||
}
|
||
|
||
for (size_t i = 1; i < input_size; i++) { // 遍历节点的每个输入
|
||
AnfNodePtr pred = nullptr;
|
||
if (case_flag != 0) { // 如果存在特殊处理,则从case_input_handle_cache_中获取输入节点
|
||
pred = case_input_handle_cache_[node.get()]->at(i - 1);
|
||
} else { // 否则直接从inputs中获取输入节点
|
||
pred = inputs[i];
|
||
}
|
||
pred = GetRealInputNode(node, pred); // 获取实际的输入节点,过滤掉不需要的类型
|
||
if (pred == nullptr) {
|
||
continue;
|
||
}
|
||
|
||
int index = SizeToInt(i); // 计算在Operator中的输入索引
|
||
// find in out_hadnle_cache_ first
|
||
// 在out_handle_cache_中查找是否有对应的输出句柄
|
||
auto it = out_handle_cache_.find(pred.get());
|
||
if (it != out_handle_cache_.end()) { // 如果找到,则将输出句柄设置为输入
|
||
int ret = adpt->setInput(src, index, it->second);
|
||
if (ret == 0) { // 如果成功设置输入,则根据情况绘制计算图中的控制依赖边,并将句柄中的操作对象作为图的常量输入添加
|
||
if (pred->isa<CNode>() && GetCNodeTargetFuncName(pred->cast<CNodePtr>()) == prim::kTupleGetItem) {
|
||
compute_sout_ << op_draw_name_[pred->cast<CNodePtr>()->input(1).get()] << " -> " << op_draw_name_[node.get()]
|
||
<< ":" << i << endl;
|
||
} else if (pred->isa<Parameter>()) {
|
||
compute_sout_ << op_draw_name_[pred.get()] << " -> " << op_draw_name_[node.get()] << ":" << i << endl;
|
||
} else {
|
||
// don't draw anything.
|
||
// 不绘制任何内容
|
||
MS_LOG(INFO) << "DRAW_GE_GRAPH: Shouldn't have this case.";
|
||
}
|
||
AddGraphConstInput(it->second.op);
|
||
}
|
||
} else if (tuple_out_handle_cache_.find(pred.get()) != tuple_out_handle_cache_.end()) {
|
||
// 如果在tuple_out_handle_cache_中找到输出句柄,则进行元组类型节点的输入设置
|
||
SetTupleOpInput(adpt, node, pred, src, index);
|
||
} else {
|
||
// 如果在out_handle_cache_和tuple_out_handle_cache_中都没有找到输出句柄,则直接将输入节点转换为操作对象,并设置为输入
|
||
auto op = Convert(pred);
|
||
int ret = adpt->setInput(src, index, op);
|
||
if (ret == 0) {
|
||
// 如果成功设置输入,则绘制计算图中的控制依赖边,并将操作对象作为图的常量输入添加
|
||
compute_sout_ << op_draw_name_[pred.get()] << " -> " << op_draw_name_[node.get()] << ":" << i << endl;
|
||
AddGraphConstInput(op);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
//该函数用于向graph_const_inputs_向量中添加常量输入,以便后续在计算图中使用这些常量作为输入。
|
||
void DfGraphConvertor::AddGraphConstInput(const OperatorPtr &op) {
|
||
if (op->GetOpType() == "Constant" || op->GetOpType() == "Const") { // 判断操作对象的类型是否为"Constant"或"Const"
|
||
graph_const_inputs_.push_back(op); // 如果是常量类型的操作对象,则将其添加到graph_const_inputs_向量中
|
||
}
|
||
}
|
||
|
||
|
||
//函数会根据节点的类型和输出句柄的情况,正确地设置操作节点的输入,并在计算图中绘制相应的控制依赖边。
|
||
void DfGraphConvertor::SetNodeInput(const AnfNodePtr node) {
|
||
if (!node->isa<CNode>()) { // 判断节点是否是CNode,如果不是则返回
|
||
return;
|
||
}
|
||
if (op_cache_.find(node.get()) == op_cache_.end()) { // 判断节点是否在op_cache_中,如果不在则返回
|
||
return;
|
||
}
|
||
auto cnode = node->cast<CNodePtr>(); // 获取CNode节点,并查找对应的OpAdapter
|
||
OpAdapterPtr adpt = FindAdapter(cnode, training_);
|
||
if (adpt == nullptr) { // 如果找不到对应的OpAdapter,则将error_标志设置为NOT_FOUND,并返回
|
||
error_ = NOT_FOUND;
|
||
return;
|
||
}
|
||
|
||
// get Operator from op_cache_, use adapter to set Inputs
|
||
// 使用OpAdapter的SetOpInput函数设置CNode节点的输入
|
||
DfGraphConvertor::SetOpInput(adpt, cnode);
|
||
}
|
||
|
||
//该函数用于处理子图节点(Partial节点)
|
||
void DfGraphConvertor::ProcessSubgraph(const AnfNodePtr &node, const std::vector<AnfNodePtr> &inputs) {
|
||
// 判断节点是否是CNode类型且函数名称是否为"Partial",如果不满足条件则直接返回
|
||
if (!node->isa<CNode>() || GetCNodeFuncName(node->cast<CNodePtr>()) != "Partial") {
|
||
return;
|
||
}
|
||
// 获取子图节点对应的FuncGraph
|
||
auto graph_node = node->cast<CNodePtr>()->input(1)->cast<ValueNodePtr>();
|
||
MS_EXCEPTION_IF_NULL(graph_node);
|
||
FuncGraphPtr anf_graph = graph_node->value()->cast<FuncGraphPtr>();
|
||
|
||
// 创建新的DfGraphConvertor对象,并使用子图FuncGraph作为输入
|
||
DfGraphConvertor converter(anf_graph);
|
||
|
||
// 设置converter的use_inputs_为true,表示使用给定的inputs作为子图的输入
|
||
converter.use_inputs_ = true;
|
||
converter.inputs_ = inputs;
|
||
|
||
// 将子图转换为DfGraph
|
||
(void)converter.ConvertAllNode().BuildGraph();
|
||
#ifdef ENABLE_DUMP_IR // 根据配置决定是否绘制计算图
|
||
std::string name = graph_node->ToString() + "_ge_graph.dot";
|
||
if (MsContext::GetInstance()->get_param<bool>(MS_CTX_SAVE_GRAPHS_FLAG)) {
|
||
converter.DrawComputeGraph(name);
|
||
}
|
||
#endif // 将转换后的DfGraph存储到branches_map_中,键为子图节点的指针地址,值为转换后的DfGraph对象
|
||
branches_map_[node.get()] = *(converter.df_graph_);
|
||
}
|
||
|
||
// Update GE op's shape and type info
|
||
//该函数用于更新操作的描述信息
|
||
//将节点的形状(Shape)、类型(Type)和节点本身作为参数,来更新对应的操作的输出描述。
|
||
void DfGraphConvertor::UpdateOpDesc(const AnfNodePtr node) {
|
||
if (node == nullptr || !node->isa<CNode>()) { // 判断节点是否为空或非CNode类型,如果是则直接返回
|
||
return;
|
||
}
|
||
|
||
if (op_cache_.find(node.get()) == op_cache_.end()) { // 判断节点是否在op_cache_中,如果不在则直接返回
|
||
return;
|
||
}
|
||
|
||
OpAdapterPtr adpt = FindAdapter(node, training_); // 查找节点对应的OpAdapter
|
||
if (adpt == nullptr) { // 如果找不到对应的OpAdapter,则将error_标志设置为NOT_FOUND,并返回
|
||
error_ = NOT_FOUND;
|
||
return;
|
||
}
|
||
|
||
// get Operator from op_cache_
|
||
// 获取节点对应的Operator对象
|
||
OperatorPtr op = Convert(node);
|
||
|
||
// 使用OpAdapter的updateOutputDesc函数更新操作的输出描述信息
|
||
adpt->updateOutputDesc(op, node->Shape(), node->Type(), node);
|
||
}
|
||
|
||
//该函数用于将AnfNode节点转换为对应的Operator对象
|
||
OperatorPtr DfGraphConvertor::Convert(const AnfNodePtr node) {
|
||
if (node == nullptr) { // 判断节点是否为空,如果为空则设置error_标志为NOT_FOUND,并返回nullptr
|
||
MS_LOG(ERROR) << "node is nullptr";
|
||
error_ = NOT_FOUND;
|
||
return nullptr;
|
||
}
|
||
// find in cache
|
||
// 在op_cache_中查找节点对应的Operator,如果找到则直接返回
|
||
if (op_cache_.count(node.get())) {
|
||
return op_cache_[node.get()];
|
||
}
|
||
|
||
// do not convert primitive node, Load, UpdateState
|
||
// 对于原语节点(Primitive节点)、Load节点、UpdateState节点,直接返回nullptr,不进行转换
|
||
if (IsValueNode<Primitive>(node) || IsPrimitiveCNode(node, prim::kPrimLoad) ||
|
||
IsPrimitiveCNode(node, prim::kPrimUpdateState)) {
|
||
return nullptr;
|
||
}
|
||
|
||
// convert a new one
|
||
// 对于CNode节点,调用ConvertCNode函数进行转换
|
||
if (node->isa<CNode>()) {
|
||
return ConvertCNode(node->cast<CNodePtr>());
|
||
}
|
||
// 对于Parameter节点,调用ConvertParameter函数进行转换
|
||
if (node->isa<Parameter>()) {
|
||
return ConvertParameter(node);
|
||
}
|
||
// 对于ValueNode节点,根据节点是否为Monad类型来决定是否进行转换
|
||
if (node->isa<ValueNode>()) {
|
||
if (IsValueNode<Monad>(node)) {
|
||
return nullptr;
|
||
}
|
||
return ConvertValueNode(node->cast<ValueNodePtr>());
|
||
}
|
||
// 对于其他类型的节点,设置error_标志为INVALID_ARGUMENT,并返回nullptr
|
||
MS_LOG(ERROR) << "Invalid AnfNode";
|
||
error_ = INVALID_ARGUMENT;
|
||
return nullptr;
|
||
}
|
||
|
||
//该函数用于将MakeTuple节点转换为对应的OutHandler列表。
|
||
void DfGraphConvertor::ConvertMakeTuple(const CNodePtr node) {
|
||
// 创建一个共享指针,用于存储MakeTuple节点的输出项
|
||
std::shared_ptr<std::vector<OutHandler>> tuple_items = std::make_shared<std::vector<OutHandler>>();
|
||
// convert each tuple item to a OutHandler
|
||
// 遍历MakeTuple节点的输入项,并逐个转换为OutHandler
|
||
for (size_t i = 1; i < node->inputs().size(); i++) {
|
||
AnfNodePtr item = node->input(i);
|
||
if (IsPrimitiveCNode(item, prim::kPrimLoad)) { // 如果输入项是Load节点,需要解析其输入,即加载的数据节点
|
||
item = ParseLoadInput(item->cast<CNodePtr>());
|
||
}
|
||
OperatorPtr op = Convert(item); // 将AnfNode节点转换为对应的Operator对象
|
||
if (op != nullptr) { // 如果转换得到的Operator对象不为空,则将OutHandler添加到tuple_items中
|
||
(void)tuple_items->emplace_back(OutHandler(op, "", item));
|
||
} else if (out_handle_cache_.find(item.get()) != out_handle_cache_.end()) {
|
||
// 如果在out_handle_cache_中找到了输入项对应的OutHandler,则将其添加到tuple_items中
|
||
tuple_items->push_back(out_handle_cache_[item.get()]);
|
||
} else { // 否则,将一个空的OutHandler添加到tuple_items中
|
||
tuple_items->emplace_back(OutHandler(nullptr, "", item));
|
||
}
|
||
}
|
||
// 打印调试信息,并将转换得到的OutHandler列表存储到tuple_out_handle_cache_中
|
||
MS_LOG(DEBUG) << "ConvertMakeTuple: " << node.get() << " " << tuple_items->size();
|
||
tuple_out_handle_cache_[node.get()] = tuple_items;
|
||
}
|
||
|
||
//该函数用于将TopK节点转换为对应的Operator对象,并处理其第二个输入的类型转换。
|
||
void DfGraphConvertor::ConvertTopK(const CNodePtr node) {
|
||
MS_EXCEPTION_IF_NULL(node); // 判断节点是否为空
|
||
MS_LOG(INFO) << "Convert TopK second input's type from int64 to int32."; // 打印日志信息,提示将TopK节点的第二个输入的类型从int64转换为int32
|
||
auto value_ptr = node->input(2)->cast<ValueNodePtr>(); // 获取TopK节点的第二个输入(k值)
|
||
MS_EXCEPTION_IF_NULL(value_ptr);
|
||
std::ostringstream ss; // 为第二个输入节点生成一个唯一的标识符,并存储到op_draw_name_中,用于绘制计算图时标识该节点
|
||
ss << "op" << value_ptr.get();
|
||
op_draw_name_[value_ptr.get()] = ss.str();
|
||
// 绘制计算图节点信息,并将其存储到compute_sout_中
|
||
compute_sout_ << ss.str() << "[label= \"" << value_ptr->value()->ToString() << "\" shape=ellipse]" << endl;
|
||
// 获取第二个输入节点的值,并将其转换为int64类型
|
||
auto input_value = value_ptr->value();
|
||
auto int64_value = GetValue<int64_t>(input_value);
|
||
OpAdapterPtr adpt = FindAdapter(value_ptr, training_); // 查找第二个输入节点对应的OpAdapter
|
||
auto op = adpt->generate(value_ptr); // 使用OpAdapter的generate函数生成第二个输入节点对应的Operator对象
|
||
(void)adpt->setAttr(op, "value", static_cast<int32_t>(int64_value)); // 将第二个输入节点的值转换为int32类型,并设置为Operator的属性
|
||
op_cache_[value_ptr.get()] = op; // 将第二个输入节点对应的Operator对象存储到op_cache_中
|
||
}
|
||
|
||
//该函数用于将ValuePtr对象转换为std::vector<int64_t>类型的数据。
|
||
std::vector<int64_t> DfGraphConvertor::CastToInt(const ValuePtr &value) {
|
||
if (value == nullptr) { // 判断ValuePtr是否为空,如果为空则打印警告信息并返回空的std::vector<int64_t>
|
||
MS_LOG(WARNING) << "Value ptr is nullptr.";
|
||
return {};
|
||
}
|
||
std::vector<int64_t> cur_value = {};
|
||
if (utils::isa<ValueSequencePtr>(value)) { // 如果ValuePtr对象是ValueSequencePtr类型,表示它是一个值序列
|
||
auto val_seq_ptr = value->cast<ValueSequencePtr>();
|
||
MS_EXCEPTION_IF_NULL(val_seq_ptr);
|
||
if (!val_seq_ptr->value().empty()) {
|
||
auto first_val = val_seq_ptr->value().front();
|
||
MS_EXCEPTION_IF_NULL(first_val);
|
||
MS_EXCEPTION_IF_NULL(first_val->type());
|
||
if (first_val->type()->number_type() == kNumberTypeInt64) { // 如果值序列中的元素类型是int64,直接将其转换为std::vector<int64_t>
|
||
cur_value = GetValue<std::vector<int64_t>>(value);
|
||
} else { // 否则,将值序列中的元素转换为int类型,并转换为std::vector<int64_t>
|
||
auto origin_value = GetValue<std::vector<int>>(value);
|
||
(void)std::transform(origin_value.begin(), origin_value.end(), std::back_inserter(cur_value),
|
||
[](int index) { return static_cast<int64_t>(index); });
|
||
}
|
||
}
|
||
} else { // 如果ValuePtr对象不是值序列,直接将其转换为std::vector<int64_t>
|
||
MS_EXCEPTION_IF_NULL(value->type());
|
||
if (value->type()->number_type() == kNumberTypeInt64) {
|
||
cur_value.push_back(GetValue<int64_t>(value));
|
||
} else {
|
||
cur_value.push_back(static_cast<int64_t>(GetValue<int>(value)));
|
||
}
|
||
}
|
||
return cur_value;
|
||
}
|
||
|
||
//该函数用于将Reshape节点转换为对应的Operator对象,并处理其第二个输入。
|
||
void DfGraphConvertor::ConvertReshape(const CNodePtr node) {
|
||
// 打印日志信息,提示将Reshape节点的第二个输入转换为Op属性
|
||
MS_LOG(INFO) << "Convert the second input of reshape to op attr.";
|
||
const auto kInputNum = 3; // 定义常量kInputNum,表示Reshape节点应该具有的输入数量
|
||
if (node->size() < kInputNum) { // 判断Reshape节点的输入数量是否小于kInputNum,如果小于,则打印警告信息并返回
|
||
MS_LOG(WARNING) << "Reshape must have two inputs.";
|
||
return;
|
||
}
|
||
OpAdapterPtr adpt = FindAdapter(node, training_); // 查找Reshape节点对应的OpAdapter
|
||
if (adpt == nullptr) {
|
||
return;
|
||
}
|
||
auto op = adpt->generate(node); // 使用OpAdapter的generate函数生成Reshape节点对应的Operator对象
|
||
MS_EXCEPTION_IF_NULL(op);
|
||
// get shape form attr
|
||
// 获取Reshape节点的第一个输入(shape值)对应的ValueNodePtr
|
||
auto value_node = node->input(0)->cast<ValueNodePtr>();
|
||
MS_EXCEPTION_IF_NULL(value_node);
|
||
MS_EXCEPTION_IF_NULL(value_node->value());
|
||
auto primitive = value_node->value()->cast<PrimitivePtr>(); // 获取ValueNodePtr中的PrimitivePtr对象
|
||
MS_EXCEPTION_IF_NULL(primitive);
|
||
auto value = primitive->GetAttr("shape"); // 获取PrimitivePtr对象中的shape属性的值
|
||
std::vector<int64_t> list;
|
||
list = CastToInt(value); // 将shape属性的值转换为std::vector<int64_t>类型
|
||
|
||
(void)op->SetAttr("shape", list); // 将转换得到的shape属性值设置为Operator的属性
|
||
op_cache_[node.get()] = op; // 将Reshape节点对应的Operator对象存储到op_cache_中,以Reshape节点的指针地址作为键,Operator对象作为值
|
||
}
|
||
|
||
//该函数用于将Conv2D节点转换为对应的Operator对象,并处理其padding属性。
|
||
void DfGraphConvertor::ConvertConv2D(const CNodePtr node) {
|
||
MS_EXCEPTION_IF_NULL(node); // 判断输入的Conv2D节点是否为空,如果为空则返回
|
||
OpAdapterPtr adpt = FindAdapter(node, training_); // 查找Conv2D节点对应的OpAdapter
|
||
if (adpt == nullptr) {
|
||
return;
|
||
}
|
||
auto op = adpt->generate(node); // 使用OpAdapter的generate函数生成Conv2D节点对应的Operator对象
|
||
MS_EXCEPTION_IF_NULL(op);
|
||
auto value_node = node->input(0)->cast<ValueNodePtr>(); // 获取Conv2D节点的第一个输入对应的ValueNodePtr
|
||
MS_EXCEPTION_IF_NULL(value_node);
|
||
MS_EXCEPTION_IF_NULL(value_node->value());
|
||
auto primitive = value_node->value()->cast<PrimitivePtr>(); // 获取ValueNodePtr中的PrimitivePtr对象
|
||
MS_EXCEPTION_IF_NULL(primitive);
|
||
auto value = primitive->GetAttr("padding"); // 获取PrimitivePtr对象中的padding属性的值
|
||
if (value != nullptr) { // 如果padding属性的值不为空,表示Conv2D节点有padding属性
|
||
std::string pad_mode = GetValue<std::string>(value);
|
||
(void)op->SetAttr("padding", pad_mode); // 将padding属性的值设置为Operator的属性
|
||
}
|
||
op_cache_[node.get()] = op; // 将Conv2D节点对应的Operator对象存储到op_cache_中,以Conv2D节点的指针地址作为键,Operator对象作为值
|
||
}
|
||
|
||
//该函数用于追踪处理TupleGetItem节点,获取Item的输入,并返回该输入节点。
|
||
AnfNodePtr DfGraphConvertor::TraceTupleGetItem(const CNodePtr &node, uint64_t *index) {
|
||
const int TUPLE_GET_ITEM_INDEX = 2; // 定义常量TUPLE_GET_ITEM_INDEX,表示TupleGetItem节点的索引位置
|
||
if (node->inputs().size() < 3) { // "tuple_getitem" primitive must have 3 inputs
|
||
// // 判断"tuple_getitem" primitive的输入数量是否小于3,如果小于3,则抛出异常
|
||
MS_LOG(EXCEPTION) << "length of inputs of TupleGetItem is less than 3";
|
||
}
|
||
auto index_node = node->inputs()[TUPLE_GET_ITEM_INDEX]; // 获取TupleGetItem节点的第三个输入,即索引值
|
||
if (!index_node->isa<ValueNode>()) { // 判断索引值对应的节点是否为ValueNode,如果不是,则设置error_为INVALID_ARGUMENT,并抛出异常
|
||
error_ = INVALID_ARGUMENT;
|
||
MS_LOG(EXCEPTION) << "can't convert get item with non-constant index";
|
||
}
|
||
// 获取索引值的整数表示,并保存在index指针所指向的变量中
|
||
*index = LongToUlong(GetValue<int64_t>(GetValueNode(index_node)));
|
||
return node->inputs()[1]; // 返回TupleGetItem节点的第二个输入,即获取Item的输入
|
||
}
|
||
|
||
//该函数用于追踪处理Depend节点,获取control依赖的输入,并返回该输入节点。
|
||
AnfNodePtr DfGraphConvertor::TraceDepend(const CNodePtr &node) {
|
||
auto cnode = node->cast<CNodePtr>(); // 获取Depend节点的指针cnode
|
||
// 判断"Depend" primitive的输入数量是否小于3,如果小于3,则抛出异常
|
||
if (cnode->inputs().size() < 3) { // "Depend" primitive have 3 inputs
|
||
MS_LOG(EXCEPTION) << "length of inputs of depend is less than 3";
|
||
}
|
||
return cnode->inputs()[1]; // 返回Depend节点的第二个输入,即control依赖的输入
|
||
}
|
||
|
||
//该函数用于追踪处理MakeTuple节点,获取Tuple的第index个元素,并返回该输入节点。
|
||
AnfNodePtr DfGraphConvertor::TraceMakeTuple(const CNodePtr &node, uint64_t index) {
|
||
if (index + 1 >= node->inputs().size()) { // 判断index + 1是否大于等于make_tuple节点的输入数量,如果是,则抛出异常
|
||
MS_LOG(EXCEPTION) << "length of make_tuple is less than index: " << index;
|
||
}
|
||
return node->inputs()[index + 1]; // 返回make_tuple节点的第index + 1个输入节点,即获取Tuple的第index个元素
|
||
}
|
||
|
||
//该函数用于获取节点的处理器OutHandler,根据节点是否在Tuple内部进行不同的处理。
|
||
OutHandler DfGraphConvertor::GetHandler(const AnfNodePtr &node, const std::stack<uint64_t> &index_stack,
|
||
AnfNode *const draw_index) {
|
||
if (node == nullptr) { // 判断节点是否为nullptr,如果是,则输出错误日志并返回一个空的OutHandler
|
||
MS_LOG(ERROR) << "Get nullptr while trace real op";
|
||
return OutHandler(nullptr, "");
|
||
}
|
||
std::ostringstream ss; // 创建一个ostringstream对象,用于生成节点的字符串表示
|
||
ss << "op" << node.get();
|
||
if (index_stack.empty()) { // 判断index_stack是否为空 如果为空,则表示不在Tuple内部,直接生成OutHandler并返回
|
||
op_draw_name_[draw_index] = ss.str(); // 将节点的字符串表示保存到op_draw_name_中
|
||
return OutHandler(Convert(node), ""); // 调用Convert函数将节点转换为OperatorPtr,并生成OutHandler返回
|
||
} else {
|
||
// 如果index_stack不为空,则表示在Tuple内部
|
||
// 查找该节点的适配器OpAdapterPtr
|
||
OpAdapterPtr adpt = FindAdapter(node, training_);
|
||
if (adpt == nullptr) { // 如果适配器为空,则输出错误日志并返回一个空的OutHandler
|
||
MS_LOG(ERROR) << "Can not get node output as adpt is nullptr!";
|
||
error_ = NOT_FOUND;
|
||
return OutHandler(nullptr, "");
|
||
}
|
||
OperatorPtr op = Convert(node); // 调用Convert函数将节点转换为OperatorPtr
|
||
if (op == nullptr) { // 如果转换后的OperatorPtr为空,则输出错误日志并返回一个空的OutHandler
|
||
error_ = NOT_FOUND;
|
||
MS_LOG(ERROR) << "Can not convert node for trace real op";
|
||
return OutHandler(nullptr, "");
|
||
}
|
||
op_draw_name_[draw_index] = ss.str(); // 将节点的字符串表示保存到op_draw_name_中
|
||
// 调用适配器的getOutput函数获取输出的处理器OutHandler,并返回
|
||
return adpt->getOutput(Convert(node), static_cast<int32_t>(index_stack.top()));
|
||
}
|
||
}
|
||
|
||
// get the real operator through maketuple tuple_getitem depend
|
||
//该函数用于追踪获取节点的真实操作节点,即去除所有TupleGetItem、MakeTuple和Depend节点,并返回其处理器OutHandler。
|
||
OutHandler DfGraphConvertor::TraceRealOp(AnfNodePtr node) {
|
||
// 判断节点是否为TupleGetItem、MakeTuple或Depend节点
|
||
bool flag = IsPrimitiveCNode(node, prim::kPrimTupleGetItem) || IsPrimitiveCNode(node, prim::kPrimMakeTuple) ||
|
||
IsPrimitiveCNode(node, prim::kPrimDepend);
|
||
std::stack<uint64_t> index_stack; // 创建一个栈index_stack,用于保存TupleGetItem节点的索引
|
||
auto draw_index = node.get(); // 保存当前节点的指针地址,用于后续绘图
|
||
while (flag) { // 循环追踪真实操作节点
|
||
flag = false;
|
||
if (IsPrimitiveCNode(node, prim::kPrimTupleGetItem)) {
|
||
uint64_t index;
|
||
// 如果当前节点是TupleGetItem节点,调用TraceTupleGetItem函数获取其真实操作节点和索引
|
||
node = TraceTupleGetItem(node->cast<CNodePtr>(), &index);
|
||
// 将索引压入index_stack中
|
||
index_stack.push(index);
|
||
flag = true;
|
||
} else if (IsPrimitiveCNode(node, prim::kPrimMakeTuple)) {
|
||
if (index_stack.empty()) {
|
||
// 如果当前节点是MakeTuple节点且index_stack为空,表示存在错误,输出错误日志并返回一个空的OutHandler
|
||
MS_LOG(ERROR) << "TraceRealOp find a make_tuple node";
|
||
return OutHandler(nullptr, "");
|
||
} else {
|
||
// 如果当前节点是MakeTuple节点且index_stack不为空,调用TraceMakeTuple函数获取其真实操作节点并弹出索引
|
||
node = TraceMakeTuple(node->cast<CNodePtr>(), index_stack.top());
|
||
index_stack.pop();
|
||
flag = true;
|
||
}
|
||
} else if (IsPrimitiveCNode(node, prim::kPrimDepend)) {
|
||
// 如果当前节点是Depend节点,调用TraceDepend函数获取其真实操作节点
|
||
node = TraceDepend(node->cast<CNodePtr>());
|
||
flag = true;
|
||
}
|
||
}
|
||
return GetHandler(node, index_stack, draw_index); // 调用GetHandler函数获取节点的处理器OutHandler并返回
|
||
}
|
||
|
||
//该函数用于将TupleGetItem节点转换为对应的算子处理器。
|
||
void DfGraphConvertor::ConvertTupleGetItem(const CNodePtr node) {
|
||
auto handle = TraceRealOp(node); // 调用TraceRealOp函数获取TupleGetItem节点的真实操作节点处理器OutHandler
|
||
if (handle.op == nullptr) { // 如果真实操作节点处理器为空,输出错误日志并返回
|
||
MS_LOG(ERROR) << "Failed to trace tuple get item";
|
||
return;
|
||
}
|
||
out_handle_cache_[node.get()] = handle; // 将TupleGetItem节点和其真实操作节点处理器OutHandler添加到out_handle_cache_中缓存
|
||
}
|
||
|
||
// Get the real op for tuple_getitem through make tuple, or depend
|
||
//该函数用于处理TupleGetItem节点和Depend节点的情况,递归地获取这些节点的真实操作节点。
|
||
AnfNodePtr DfGraphConvertor::GetRealOpNode(AnfNodePtr node) {
|
||
const int TUPLE_GET_ITEM_INDEX = 2;
|
||
if (IsPrimitiveCNode(node, prim::kPrimTupleGetItem)) { //// 如果当前节点是TupleGetItem节点
|
||
auto node_inputs = node->cast<CNodePtr>()->inputs();
|
||
if (node_inputs.size() != 3) { // "tuple_getitem" primitive must have 3 inputs
|
||
MS_LOG(ERROR) << "tuple get item node not correct!";
|
||
error_ = FAILED;
|
||
return node;
|
||
}
|
||
MS_EXCEPTION_IF_NULL(node_inputs[TUPLE_GET_ITEM_INDEX]);
|
||
if (!node_inputs[TUPLE_GET_ITEM_INDEX]->isa<ValueNode>()) { // 获取TupleGetItem节点的索引值
|
||
error_ = INVALID_ARGUMENT;
|
||
MS_LOG(EXCEPTION) << "can't convert get item with non-constant index";
|
||
}
|
||
auto value_ptr = GetValueNode(node_inputs[TUPLE_GET_ITEM_INDEX])->cast<Int32ImmPtr>();
|
||
if (value_ptr == nullptr) {
|
||
MS_LOG(ERROR) << "Can not convert get item as value is nullptr!";
|
||
error_ = FAILED;
|
||
return node;
|
||
}
|
||
int64_t index = value_ptr->value();
|
||
|
||
// make_tuple apply inputs:make_tuple, [tuple_items,]
|
||
if (IsPrimitiveCNode(node_inputs[1], prim::kPrimMakeTuple)) { // 如果TupleGetItem节点的输入是MakeTuple节点
|
||
auto tuple_inputs = node->cast<CNodePtr>()->inputs();
|
||
if (tuple_inputs.size() < LongToSize(index + 1L)) {
|
||
MS_LOG(ERROR) << "make tuple input items node not correct! size:" << tuple_inputs.size()
|
||
<< ", item index:" << index;
|
||
error_ = FAILED;
|
||
return node;
|
||
}
|
||
return GetRealOpNode(tuple_inputs[LongToSize(index + 1L)]); // 递归调用GetRealOpNode函数获取MakeTuple节点的真实操作节点
|
||
}
|
||
return GetRealOpNode(node_inputs[1]); // 递归调用GetRealOpNode函数获取TupleGetItem节点的真实操作节点
|
||
}
|
||
|
||
// depend apply inputs: depend,output,depended_node
|
||
if (IsPrimitiveCNode(node, prim::kPrimDepend)) { // 如果当前节点是Depend节点
|
||
auto depend_inputs = node->cast<CNodePtr>()->inputs();
|
||
if (depend_inputs.size() != 3) { // "Depend" primitive have 3 inputs
|
||
MS_LOG(ERROR) << "depend input items not correct";
|
||
error_ = FAILED;
|
||
return node;
|
||
}
|
||
return GetRealOpNode(depend_inputs[1]); // 递归调用GetRealOpNode函数获取Depend节点的真实操作节点
|
||
}
|
||
return node; // 其他情况直接返回当前节点
|
||
}
|
||
|
||
// convert the anf node to corresponding operator list
|
||
/*
|
||
此函数的目的是将Depend节点以及MakeTuple节点中的子节点转换为操作符。
|
||
这样,在构建计算图时可以将Depend节点转换为控制边,而MakeTuple节点的子节点可以在后续处理中被正确处理。
|
||
*/
|
||
std::vector<OperatorPtr> DfGraphConvertor::ConvertDependNode(const AnfNodePtr node) {
|
||
if (IsPrimitiveCNode(node, prim::kPrimMakeTuple)) { //判断节点是否为MakeTuple节点
|
||
std::vector<OperatorPtr> op_lists; //如果是,则将其输入的各个元素节点逐个转换为操作符,并存储在op_lists中,然后返回op_lists
|
||
auto node_inputs = node->cast<CNodePtr>()->inputs();
|
||
for (size_t index = 1; index < node_inputs.size(); index++) {
|
||
auto op = Convert(GetRealOpNode(node_inputs[index]));
|
||
if (op == nullptr) {
|
||
MS_LOG(ERROR) << "Convert real op node to operator failed";
|
||
error_ = FAILED;
|
||
return std::vector<OperatorPtr>({});
|
||
}
|
||
op_lists.push_back(op);
|
||
}
|
||
return op_lists;
|
||
}
|
||
// 如果当前节点不是MakeTuple节点,则将其转换为操作符并返回
|
||
auto op = Convert(GetRealOpNode(node));
|
||
if (op == nullptr) {
|
||
MS_LOG(ERROR) << "Convert real op node to operator failed";
|
||
error_ = FAILED;
|
||
return std::vector<OperatorPtr>({});
|
||
}
|
||
return std::vector<OperatorPtr>({op});
|
||
}
|
||
|
||
/*
|
||
该函数检查给定的CNode(计算节点)的类型,并根据节点类型应用特定的操作。
|
||
它返回一个布尔值,表示是否需要进一步处理给定的节点。
|
||
*/
|
||
bool DfGraphConvertor::CheckCNode(const std::string &name, const CNodePtr node) {
|
||
// ignore apply node of return
|
||
// 忽略特定的特殊节点,并返回false以跳过进一步处理。
|
||
if (name == "" || name == prim::kPrimReturn->name() || name == prim::kPrimDepend->name() ||
|
||
name == prim::kPrimSwitchLayer->name() || name == prim::kPrimPartial->name()) {
|
||
return false;
|
||
}
|
||
|
||
// Convert TopK second input from int64 to int32.
|
||
// 将TopK节点的第二个输入从int64转换为int32。
|
||
if (name == prim::kPrimTopK->name()) {
|
||
ConvertTopK(node);
|
||
return true;
|
||
}
|
||
|
||
// Convert Reshape add const input to attr(shape)
|
||
// 转换Reshape节点,并将常量输入添加到属性(shape)中。
|
||
if (name == prim::kPrimReshape->name()) {
|
||
ConvertReshape(node);
|
||
return true;
|
||
}
|
||
|
||
// Add attr pad mode to Conv2D
|
||
// 为Conv2D、DepthwiseConv2dNative和Conv2DBackpropInputV2节点添加padding属性。
|
||
if (name == prim::kPrimConv2D->name() || name == prim::kPrimDepthwiseConv2dNative->name() ||
|
||
name == kNameConv2DBackpropInputV2) {
|
||
ConvertConv2D(node);
|
||
return true;
|
||
}
|
||
|
||
// make_tuple is used for a dynamic_input, convert it to a vector of OutHandlers
|
||
// 处理用于动态输入的make_tuple节点,将其转换为OutHandler对象的向量。
|
||
if (name == prim::kPrimMakeTuple->name()) {
|
||
ConvertMakeTuple(node);
|
||
return false; // 返回false以跳过make_tuple节点的进一步处理。
|
||
}
|
||
|
||
// As for nodes with multi outputs, convert tuple_getitem to OutHandle
|
||
// 处理具有多个输出的tuple_getitem节点,将其转换为OutHandler对象。
|
||
if (name == prim::kPrimTupleGetItem->name()) {
|
||
ConvertTupleGetItem(node);
|
||
return false; // 返回false以跳过tuple_getitem节点的进一步处理。
|
||
}
|
||
// 如果未满足上述特殊情况,则返回true,表示需要进一步处理该节点。
|
||
return true;
|
||
}
|
||
|
||
//该函数ConvertCNode用于将CNode(计算节点)转换为相应的运算符
|
||
OperatorPtr DfGraphConvertor::ConvertCNode(const CNodePtr node) {
|
||
SaveParamFormat(node); // 调用SaveParamFormat函数保存节点的参数格式
|
||
std::string name = GetCNodeTargetFuncName(node); //获取节点的类型名称
|
||
if (!CheckCNode(name, node)) { //如果通过CheckCNode函数检查该节点,并根据节点的类型应用相应的操作
|
||
return nullptr; //如果CheckCNode返回false,则表示该节点为特殊节点,不需要进一步处理,直接返回nullptr。
|
||
}
|
||
|
||
// get corresponding OpAdapter
|
||
// 获取相应的OpAdapter
|
||
OpAdapterPtr adpt = FindAdapter(node, training_); //通过调用FindAdapter函数获取适用于该节点的OpAdapter
|
||
if (adpt == nullptr) { //如果未找到适配器,则将error_设置为NOT_FOUND,并返回nullptr
|
||
error_ = NOT_FOUND;
|
||
return nullptr;
|
||
}
|
||
|
||
// get operator
|
||
// 获取运算符
|
||
OperatorPtr op = nullptr;
|
||
auto it_op = op_cache_.find(node.get());
|
||
if (it_op != op_cache_.end()) { //如果已经存在则直接使用
|
||
op = it_op->second;
|
||
} else {
|
||
op = adpt->generate(node); //否则通过适配器的generate函数创建运算符。
|
||
}
|
||
|
||
// set attribute for primitive
|
||
// 设置原语的属性
|
||
(void)adpt->setAttr(op, node); //根据节点类型特殊处理,设置不同的属性。
|
||
|
||
// add into cache
|
||
// 将运算符添加到缓存中
|
||
(void)op_cache_.emplace(node.get(), op);
|
||
|
||
DrawCNode(node, adpt); // 绘制节点信息,用于可视化
|
||
|
||
return op_cache_[node.get()]; //函数返回节点对应的运算符
|
||
}
|
||
|
||
//该函数用于将ANF中的Parameter(参数节点)转换为DataFlow中的变量
|
||
OperatorPtr DfGraphConvertor::ConvertParameter(const AnfNodePtr node) {
|
||
// convert Parameter in ANF to variable in DataFlow
|
||
// 将ANF中的Parameter转换为DataFlow中的变量
|
||
auto adpt = FindAdapter(node, training_); //通过调用FindAdapter函数获取适用于该节点的adpt
|
||
if (adpt == nullptr) { //如果未找到适配器,则抛出异常。
|
||
MS_LOG(EXCEPTION) << "Can not find adapter for Parameter";
|
||
}
|
||
auto op = adpt->generate(node); //通过适配器的generate函数创建运算符,并将其添加到缓存中
|
||
op_cache_[node.get()] = op;
|
||
|
||
// build index for parameter using name
|
||
// 使用名称为参数构建索引 将参数节点添加到params_中
|
||
std::string name = std::static_pointer_cast<Parameter>(node)->name();
|
||
params_[name] = node;
|
||
std::ostringstream ss;
|
||
ss << "op" << node.get();
|
||
op_draw_name_[node.get()] = ss.str(); //为该节点创建一个运算符的标识符,并将其保存在op_draw_name_中,用于可视化。
|
||
compute_sout_ << ss.str() << "[shape=octagon, label=\"" << name << "\"]" << endl;
|
||
return op_cache_[node.get()]; //在可视化输出中绘制该节点,并返回节点对应的运算符
|
||
}
|
||
|
||
//该函数用于保存参数的格式信息
|
||
void DfGraphConvertor::SaveParamFormat(const CNodePtr node) {
|
||
AnfNodePtr op = node->input(0);
|
||
if (IsValueNode<Primitive>(op)) { //检查节点的第一个输入是否是ValueNode类型
|
||
auto prim = GetValueNode<PrimitivePtr>(op); //如果是则获取该ValueNode对应的Primitive类型,并遍历它的属性。
|
||
for (auto attr : prim->attrs()) {
|
||
if (attr.first == "format") { //若其中有名为"format"的属性,则从属性值中获取格式信息
|
||
std::string format; //格式信息可能是字符串类型或整数类型,函数会根据类型进行处理。
|
||
if (attr.second->isa<Int64Imm>()) {
|
||
bool converted = CheckAndConvertUtils::ConvertAttrValueToString(prim->name(), "format", &attr.second);
|
||
if (converted) {
|
||
format = attr.second->ToString();
|
||
} else {
|
||
CheckAndConvertUtils::GetFormatStringVal(prim, &format);
|
||
}
|
||
} else if (attr.second->isa<StringImm>()) {
|
||
format = attr.second->ToString();
|
||
}
|
||
if (format != "NCDHW" && format != "NHWC") { //若格式信息是"NCDHW"或"NHWC",则继续遍历节点的其他输入
|
||
break;
|
||
}
|
||
for (size_t i = 1; i < node->size(); i++) {
|
||
auto input = node->input(i);
|
||
if (input->isa<Parameter>()) { //若为Parameter类型的输入,则将其对应的格式保存在param_format_中。
|
||
param_format_[input->DebugString()] = format;
|
||
MS_LOG(DEBUG) << "Save Param " << input->DebugString() << " format: " << format; //打印保存的参数格式信息,用于调试
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
//该函数用于将ValueNode转换成多个常量(Constant)节点。
|
||
Status DfGraphConvertor::TryConvertValueNodeToMultiConst(const ValueNodePtr node) {
|
||
MS_EXCEPTION_IF_NULL(node);
|
||
ValuePtr value = node->value();
|
||
MS_EXCEPTION_IF_NULL(value);
|
||
if (!value->isa<ValueList>() && !value->isa<ValueTuple>()) {
|
||
return FAILED;
|
||
}
|
||
// 检查值是否为 ValueList 或 ValueTuple 类型,如果不是,则返回 FAILED
|
||
auto vec = value->isa<ValueTuple>() ? value->cast<ValueTuplePtr>()->value() : value->cast<ValueListPtr>()->value();
|
||
if (vec.empty()) {
|
||
return FAILED;
|
||
}
|
||
//获取ValueList或ValueTuple中的元素,并遍历这些元素。如果其中有任何一个元素不是MeTensor类型,函数会返回FAILED。
|
||
std::shared_ptr<std::vector<OutHandler>> tuple_items = std::make_shared<std::vector<OutHandler>>();
|
||
for (size_t i = 0; i < vec.size(); i++) {
|
||
MS_EXCEPTION_IF_NULL(vec[i]);
|
||
if (vec[i]->isa<MeTensor>()) {
|
||
// 将 MeTensor 转换成 GeTensor
|
||
GeTensorPtr ge_tensor = transform::TransformUtil::ConvertTensor(vec[i]->cast<MeTensorPtr>(), kOpFormat_NCHW);
|
||
auto const_op = std::make_shared<Constant>(node->fullname_with_scope() + "/const/inputs/" + std::to_string(i));
|
||
(void)const_op->set_attr_value(*ge_tensor);
|
||
(void)const_op->update_output_desc_y(ge_tensor->GetTensorDesc());
|
||
(void)tuple_items->emplace_back(OutHandler(const_op, ""));
|
||
} else { // 如果列表或元组中的任何一个元素不是 MeTensor 类型,则返回 FAILED
|
||
return FAILED;
|
||
}
|
||
}
|
||
if (tuple_items->empty()) { // 如果转换后的列表或元组为空,则返回 FAILED
|
||
return FAILED;
|
||
}
|
||
|
||
tuple_out_handle_cache_[node.get()] = tuple_items; // 将转换后的列表或元组保存为 OutHandler 的向量
|
||
return SUCCESS;
|
||
}
|
||
|
||
//该函数用于将ValueNode转换成常量(Constant)操作符。
|
||
OperatorPtr DfGraphConvertor::ConvertValueNode(const ValueNodePtr node) {
|
||
// convert valuenode in ANF to Const in DataFlow
|
||
// find paramerte referenced by SymbolicKeyInstance of valuenode
|
||
// 将 ANF 中的 ValueNode 转换成 DataFlow 中的 Const
|
||
// 设置绘制图形所需的信息
|
||
std::ostringstream ss;
|
||
ss << "op" << node.get();
|
||
op_draw_name_[node.get()] = ss.str();
|
||
compute_sout_ << ss.str() << "[label= \"" << node->value()->ToString() << "\" shape=ellipse]" << endl;
|
||
// 尝试将 ValueNode 转换成多个常量(Constant)节点
|
||
if (TryConvertValueNodeToMultiConst(node) == SUCCESS) {
|
||
MS_LOG(INFO) << "Convert value node to multi Constant OP success";
|
||
return nullptr;
|
||
}
|
||
// 获取对应的 OpAdapter
|
||
OpAdapterPtr adpt = FindAdapter(node, training_);
|
||
if (adpt == nullptr) {
|
||
error_ = NOT_FOUND;
|
||
return nullptr;
|
||
}
|
||
// 生成对应的操作符 Operator
|
||
auto op = adpt->generate(node);
|
||
// set const's attrs
|
||
// 设置常量的属性值
|
||
if (adpt->setAttr(op, "value", node->value()) != 0) {
|
||
MS_LOG(WARNING) << "set attr value for const failed";
|
||
}
|
||
// 将操作符转换为 Constant 类型
|
||
auto const_op = std::static_pointer_cast<Constant>(op);
|
||
if (const_op == nullptr) {
|
||
MS_LOG(ERROR) << "Get Constant operator failed";
|
||
return nullptr;
|
||
}
|
||
// 更新输出描述
|
||
auto ge_tensor = const_op->get_attr_value();
|
||
auto ge_desc = ge_tensor.GetTensorDesc();
|
||
(void)const_op->update_output_desc_y(ge_desc);
|
||
// 将操作符保存在 op_cache_ 中,并返回
|
||
op_cache_[node.get()] = op;
|
||
return op_cache_[node.get()];
|
||
}
|
||
|
||
//该函数用于绘制CNode节点的图形表示。
|
||
void DfGraphConvertor::DrawCNode(const CNodePtr node, const OpAdapterPtr adpt) {
|
||
// 绘制 apply node,即 CNode 节点的图形表示
|
||
if (adpt == nullptr || node == nullptr) {
|
||
MS_LOG(ERROR) << "Failed to draw apply node as adpt or node is nullptr!";
|
||
return;
|
||
}
|
||
std::ostringstream ss;
|
||
ss << "op" << node.get();
|
||
op_draw_name_[node.get()] = ss.str();
|
||
// 绘制节点的表格表示
|
||
compute_sout_ << ss.str() << "[label=<";
|
||
compute_sout_ << "<table border='1' cellborder='1'>" << endl;
|
||
// 绘制输入端口的标签
|
||
auto input_map = adpt->getInputMap();
|
||
auto dyn_input_map = adpt->getDynInputMap();
|
||
if (input_map.size() + dyn_input_map.size() > 0) {
|
||
compute_sout_ << "<tr>";
|
||
for (auto &it : input_map) {
|
||
compute_sout_ << "<td port='" << it.first << "'>" << it.second.name << "</td>";
|
||
}
|
||
for (auto &it : dyn_input_map) {
|
||
compute_sout_ << "<td port='" << it.first << "'>" << it.second.name << "</td>";
|
||
}
|
||
compute_sout_ << "</tr>" << endl;
|
||
}
|
||
// 绘制节点的功能名称和内容
|
||
compute_sout_ << "<tr><td colspan=\"" << (input_map.size() + dyn_input_map.size()) << "\">\"" << node->ToString()
|
||
<< ":" << GetCNodeTargetFuncName(node) << "\"</td></tr>" << endl;
|
||
|
||
// print attrs' values
|
||
// 绘制节点的属性值
|
||
auto atts = adpt->GetAttrsFromDrawGraph();
|
||
for (auto &it : atts) {
|
||
compute_sout_ << "<tr><td colspan=\"" << (input_map.size() + dyn_input_map.size()) << "\">\"" << it
|
||
<< "\"</td></tr>";
|
||
}
|
||
// 清空属性的向量,为下一次绘制做准备
|
||
adpt->clearAttrVect();
|
||
|
||
compute_sout_ << "</table>> shape=plaintext]" << endl;
|
||
}
|
||
|
||
//该函数用于注册运算符适配器
|
||
void DfGraphConvertor::RegisterAdapter(const std::string &name, OpAdapterPtr adpt) {
|
||
// 注册运算符适配器,将适配器添加到OpAdapterMap中
|
||
// 使用OpAdapterDesc类对适配器进行包装
|
||
OpAdapterMap::get()[name] = std::make_shared<OpAdapterDesc>(adpt);
|
||
}
|
||
|
||
//该函数用于注册运算符适配器,同时支持传入训练适配器和推理适配器
|
||
void DfGraphConvertor::RegisterAdapter(const std::string &name, OpAdapterPtr train_adpt, OpAdapterPtr infer_adpt) {
|
||
// 注册运算符适配器,将训练适配器和推理适配器添加到OpAdapterMap中
|
||
// 使用OpAdapterDesc类对训练适配器和推理适配器进行包装
|
||
OpAdapterMap::get()[name] = std::make_shared<OpAdapterDesc>(train_adpt, infer_adpt);
|
||
}
|
||
} // namespace transform
|
||
} // namespace mindspore
|