transform/convert.cc

2329 lines
114 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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; // 定义常数参数的索引为1Load节点通常为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