动态图模块总结

在比赛一开始,我们队长就将每个人的模块和区域进行分划,采取自主选择模式,我选择的是核心代码模块中的前四个模块,分别是数据处理与数据增强模块,分布式并行模块,图优化模块和动态图模块。起初我对C++语言并不是十分熟悉,所以花了一部分时间学习,在这四个模块中,我主攻的是动态图模块和数据处理与数据增强模块中的dataset模块,其他的模块只是有一个简单的了解。

动态图模块

首先MindSpore是支持两种运行模式,一种是Graph模式,一种是PyNative模式,我选择的是动态图模块,也就是PyNative模式,在此之前,先简单的介绍一下Graph和PyNative。(以下为MindSpore官网提供)

什么是Graph模式和PyNative模式

  • Graph模式被称为静态图模式或者图模式,将神经网络模型编译成一整张图,然后下发执行。该模式利用图优化等技术提高运行性能,同时有助于规模部署和跨平台运行。
  • PyNative模式被称为动态图模式,它是将神经网络中的各个算子逐一下发执行,方便用户编写和调试神经网络模型。

Graph与PyNative互相转换性

  • 在一般的情况下,MindSpore处于Graph模式,可以通过context.set_context(mode=context.PYNATIVE_MODE)切换为PyNative模式;

  • 同样地,MindSpore处于PyNative模式时,可以通过 context.set_context(mode=context.GRAPH_MODE)切换为Graph模式。

Graph和PyNative两种模式的区别

1.使用场景(整体性和分散性)

  • Graph模式需要一开始就构建好网络结构,然后框架做整图优化和执行,比较适合网络固定没有变化,且需要高性能的场景。
  • PyNative模式逐行执行算子,支持单独求梯度。

2.网络执行

  • Graph模式和PyNative模式在执行相同的网络和算子时,精度效果是一致的
  • 由于Graph模式运用了图优化、计算图整图下沉等技术,所以Graph模式执行网络的性能和效率更高。

3.代码调试

  • 在脚本开发和网络流程调试中,推荐使用PyNative模式进行调试。
  • 在PyNative模式下,可以方便地设置断点,获取网络执行的中间结果,也可以通过pdb的方式对网络进行调试
  • 但是Graph模式无法设置断点,只能先指定算子进行打印,然后在网络执行完成后查看输出结果。

PyNative模块的具体使用

  • MindSpore提供了Staging功能,该功能可以在PyNative模式下将Python函数或者Python类的方法编译成计算图,通过图优化等技术提高运行速度,是一种混合运行机制。

  • Staging功能的使用通过ms_function装饰器达成,该装饰器会将模块编译成计算图,在给定输入之后,以图的形式下发执行。

  • PyNative模式中支持单独的梯度求取操作,具体操作可通过GradOperation求该函数或者网络所有的输入梯度。需要注意的是:输入类型仅支持Tensor。

部分优秀代码解析

std::string GetSingleOpGraphInfo(const OpExecInfoPtr &op_exec_info, const std::vector<tensor::TensorPtr> &input_tensors,
                                 const std::vector<int64_t> &tensors_mask) {
  MS_EXCEPTION_IF_NULL(op_exec_info);
  // tensors的大小应该等于tensors_mask的大小
  if (input_tensors.size() != tensors_mask.size()) {
    MS_LOG(EXCEPTION) << "Input tensors size " << input_tensors.size() << " should be equal to tensors mask size "
                      << tensors_mask.size();
  }
  std::string graph_info;
  // 获取输入的tensors的信息
  for (size_t index = 0; index < input_tensors.size(); ++index) {
    MS_EXCEPTION_IF_NULL(input_tensors[index]);
    // auto可以在声明变量时根据变量初始值的类型自动为此变量选择匹配的类型
    auto tensor_shape = input_tensors[index]->shape();
    // 将graph_info的信息用_分隔开,方便阅读与查看
    (void)std::for_each(tensor_shape.begin(), tensor_shape.end(), [&](#) {
      (void)graph_info.append(std::to_string(dim));
      graph_info += "_";
    });
    (void)graph_info.append(std::to_string(input_tensors[index]->data_type()));
    graph_info += "_";
    (void)graph_info.append(input_tensors[index]->padding_type());
    graph_info += "_";
    auto tensor_addr = input_tensors[index]->device_address();
    if (tensor_addr != nullptr) { // 如果tensor_addr为空,添加_在graph_info中
      (void)graph_info.append(std::to_string(std::dynamic_pointer_cast<device::DeviceAddress>(tensor_addr)->type_id()));
      graph_info += "_";
      (void)graph_info.append(std::dynamic_pointer_cast<device::DeviceAddress>(tensor_addr)->format());
      graph_info += "_";
    }
    if (tensors_mask[index] == kValueNodeTensorMask) {
      if (input_tensors[index]->Dtype()->type_id() == kNumberTypeInt64) {
        (void)graph_info.append(std::to_string(*reinterpret_cast<int *>(input_tensors[index]->data_c())));
        graph_info += "_";
      } else if (input_tensors[index]->Dtype()->type_id() == kNumberTypeFloat32 ||
                 input_tensors[index]->Dtype()->type_id() == kNumberTypeFloat16) {
        (void)graph_info.append(std::to_string(*reinterpret_cast<float *>(input_tensors[index]->data_c())));
        graph_info += "_";
      } else {
        // 常量输入的 dtype 不是 int64 或 float32!
        MS_LOG(EXCEPTION) << "The dtype of the constant input is not int64 or float32!";
      }
    }
  }
  // 获取原始和抽象信息
  graph_info += (op_exec_info->op_name);
  graph_info += "_";
  // 获取属性的信息
  const auto &op_prim = op_exec_info->py_primitive;
  MS_EXCEPTION_IF_NULL(op_prim);
  const auto &attr_map = op_prim->attrs();
  (void)std::for_each(attr_map.begin(), attr_map.end(), [&](#) {
    graph_info += (element.second->ToString());
    graph_info += "_";
  });

  // 将算子的输出信息(shape, type id)添加到graph_info中,解决缓存丢失的问题
  // 由像 DropoutGenMask 这样的操作符引起,当输入形状相同但值不同
  auto abstr = op_exec_info->abstract;
  MS_EXCEPTION_IF_NULL(abstr);
  auto build_shape = abstr->BuildShape();
  MS_EXCEPTION_IF_NULL(build_shape);
  graph_info += (build_shape->ToString());
  graph_info += "_";
  auto build_type = abstr->BuildType();
  MS_EXCEPTION_IF_NULL(build_type);
  graph_info += std::to_string(build_type->type_id());
  graph_info += "_";

  return graph_info;
}
......
void CheckPyNativeContext() {
  // auto可以在声明变量时根据变量初始值的类型自动为此变量选择匹配的类型
  auto parallel_context = parallel::ParallelContext::GetInstance();
  // 判断parallel_context是否为空
  MS_EXCEPTION_IF_NULL(parallel_context);
  auto ms_context = MsContext::GetInstance();
  // 判断ms_context是否为空
  MS_EXCEPTION_IF_NULL(ms_context);
  auto parallel_mode = parallel_context->parallel_mode();
  // PyNative 只支持 STAND_ALONE 和 DATA_PARALLEL,但是得到了parallel_mode
  if (parallel_mode != parallel::STAND_ALONE && parallel_mode != parallel::DATA_PARALLEL &&
      ms_context->get_param<int>(MS_CTX_EXECUTION_MODE) == kPynativeMode) {
    MS_LOG(EXCEPTION) << "PyNative Only support STAND_ALONE and DATA_PARALLEL, but got:" << parallel_mode;
  }
}
......
    void ForwardExecutor::RunOpInner(py::object *ret, const OpExecInfoPtr &op_exec_info) {
  // 判断ret和op_exec_info是否为空
  MS_EXCEPTION_IF_NULL(ret);
  MS_EXCEPTION_IF_NULL(op_exec_info);
  if (op_exec_info->op_name == prim::kPrimMixedPrecisionCast->name()) {
    py::tuple res = RunOpWithInitBackendPolicy(op_exec_info);
    if (res.size() == 1) {
      *ret = res[0];
      return;
    }
    *ret = std::move(res);
    return;
  }
  // 如果设置了 grad 标志,则制作用于构建 grad 图的 cnode
  abstract::AbstractBasePtrList args_spec_list;
  std::vector<int64_t> op_masks;
  auto cnode = MakeCNode(op_exec_info, &op_masks, &args_spec_list);
  op_exec_info->inputs_mask = op_masks;
  // 获取输出摘要信息
  bool is_find = false;
  GetOpOutputAbstract(op_exec_info, args_spec_list, &is_find);
  MS_LOG(DEBUG) << "Run op infer " << op_exec_info->op_name << " " << op_exec_info->abstract->ToString();
  // 推断 const prim 的输出值
  auto prim = op_exec_info->py_primitive;
  MS_EXCEPTION_IF_NULL(prim);
  py::dict output = abstract::ConvertAbstractToPython(op_exec_info->abstract);
  if (!output["value"].is_none()) {
    *ret = output["value"];
    return;
  }
  if (prim->is_const_prim()) {
    *ret = py::cast("");
    return;
  }
  // 将输出抽象信息添加到缓存中
  if (!is_find && !op_exec_info->is_dynamic_shape) {
    // const_value 需要推断每一步
    auto &out = prim_abs_list_[prim->id()];
    out[args_spec_list].abs = op_exec_info->abstract;
    out[args_spec_list].attrs = prim->evaluate_added_attrs();
    MS_LOG(DEBUG) << "Set prim " << op_exec_info->op_name << mindspore::ToString(args_spec_list);
  }
  // 使用选定的后端运行操作
  auto result = RunOpWithInitBackendPolicy(op_exec_info);
  py::object out_real;
  if (result.size() == 1 && op_exec_info->abstract != nullptr &&
      !op_exec_info->abstract->isa<abstract::AbstractSequeue>()) {
    out_real = result[0];
  } else {
    out_real = result;
  }
  // 更新 cnode 的输出摘要
  if (cnode != nullptr) {
    cnode->set_abstract(op_exec_info->abstract);
  }
  std::string obj_id = GetId(out_real);
  node_abs_map_[obj_id] = op_exec_info->abstract;
  // 保存用于构建毕业图的信息
  if (grad()->grad_flag() && grad()->in_grad_process()) {
    grad()->SaveOutputNodeMap(obj_id, out_real, cnode);
    grad()->SaveAllResult(op_exec_info, cnode, out_real);
    // 用梯度图中的张量更新值节点的抽象和设备地址
    UpdateAbstractAndDeviceAddress(op_exec_info, out_real);
  }
  *ret = out_real;
}

OpExecInfoPtr ForwardExecutor::GenerateOpExecInfo(const py::args &args) {
  // RunOp 需要三个参数
  if (args.size() != PY_ARGS_NUM) {
    MS_LOG(ERROR) << "Three args are needed by RunOp";
    return nullptr;
  }
  auto op_exec_info = std::make_shared<OpExecInfo>();
  auto op_name = py::cast<std::string>(args[PY_NAME]);
  op_exec_info->op_name = op_name;
  // 需要const grad图
  if (grad()->grad_flag()) {
    // 获取前向操作指数
    op_exec_info->op_index = op_name + "_" + std::to_string(grad()->op_index_map()[op_name]);
    if (!grad()->cell_op_info_stack().empty()) {
      std::string &cell_op_info = grad()->cell_op_info_stack().top();
      cell_op_info += op_exec_info->op_index;
    }
    grad()->op_index_map()[op_name]++;
  }
  // auto可以在声明变量时根据变量初始值的类型自动为此变量选择匹配的类型
  auto adapter = py::cast<PrimitivePyAdapterPtr>(args[PY_PRIM]);
  // 判断adapter是否为空
  MS_EXCEPTION_IF_NULL(adapter);
  auto prim = adapter->attached_primitive();
  if (prim == nullptr) {
    prim = std::make_shared<PrimitivePy>(args[PY_PRIM], adapter);
    adapter->set_attached_primitive(prim);
  }
  // 判断Pyobj是否为空,如果是,输出"Pyobj is empty"
  if (!prim->HasPyObj()) {
    MS_LOG(EXCEPTION) << "Pyobj is empty";
  }
  // 对op_exec_info里面的参数进行赋值
  op_exec_info->py_primitive = prim;
  op_exec_info->op_inputs = args[PY_INPUTS];
  return op_exec_info;
}
bool BuildSingleOpGraph(const OpExecInfoPtr &op_exec_info, const std::vector<GeTensorPtr> &inputs,
                        const std::unordered_map<std::string, ValuePtr> &attrs, const GeGraphPtr &graph) {
  MS_EXCEPTION_IF_NULL(op_exec_info);
  std::string op_name = op_exec_info->op_name;
  auto op_inputs = op_exec_info->op_inputs; //auto = const std::vector<GeTensorPtr>
  transform::OpAdapterPtr adapter = transform::DfGraphConvertor::FindAdapter(op_name, true);
  if (adapter == nullptr) { //找不到适配器
    MS_LOG(ERROR) << "Unable to find Adapter for " << ((std::string)py::str(op_name));
    return false;
  }
  OperatorPtr op = adapter->generate(op_name);
  MS_EXCEPTION_IF_NULL(op);
  std::vector<GeOperator> graph_input_nodes;
  // 设置图形的输入和输出后保存参数节点
  // 设置输入
  if (!SetInputsForSingleOpGraph(op_exec_info, inputs, op, &graph_input_nodes)) {
    return false;
  }
  // 设置属性
  for (auto attr : attrs) { //auto = const std::unordered_map<std::string, ValuePtr>
    (void)adapter->setAttr(op, attr.first, attr.second);
  }
  // 设置默认属性
  auto extra_attrs = adapter->GetExtraAttr();
  for (auto attr : extra_attrs) {
    (void)adapter->setAttr(op, attr.first, attr.second);
  }
  // 设置输入属性
  auto &input_attr_map = adapter->getInputAttrMap();
  for (auto &it : input_attr_map) { 
  //it.first 获取的是key,也就是键
  //it.second 获取的是value,也就是值
    if (op_inputs.size() < it.first) {
      continue;
    }
    auto const_value = PyAttrValue(op_inputs[it.first - 1]);
    if (const_value->isa<None>()) {
      continue;
    }
    it.second.set_attr(op, const_value);
  }
  // 构建输出数据节点
  std::vector<GeOperator> graph_outputs{*op};
  // 设置图形的输入和输出节点
  MS_EXCEPTION_IF_NULL(graph);
  (void)graph->SetInputs(graph_input_nodes).SetOutputs(graph_outputs);
  MS_LOG(INFO) << "BuildSingleOpGraph done"; //BuildSingleOpGraph 完成
  return true;
}
......
    PynativeStatusCode ConvertAttributes(const OpExecInfoPtr &op_exec_info, const std::vector<GeTensorPtr> &inputs) {
  MS_EXCEPTION_IF_NULL(op_exec_info);
  auto op_attrs = op_exec_info->op_attrs;
  std::unordered_map<std::string, ValuePtr> attrs{};
  for (auto &item : op_attrs) { // auto = const OpExecInfoPtr
    if (!py::isinstance<py::str>(item.first)) { //在py dict转换中键入错误
      MS_LOG(ERROR) << "Type error in py dict convert";
      return PYNATIVE_OP_ATTRS_ERR;
    }
    std::string name = py::cast<std::string>(item.first);
    auto attr_value = PyAttrValue(py::cast<py::object>(item.second));
    (void)attrs.emplace(name, attr_value);
  }
  // 构建图
  GeGraphPtr graph = std::make_shared<GeGraph>(op_exec_info->op_name);
  //无法构建SingleOpGraph
  if (BuildSingleOpGraph(op_exec_info, inputs, attrs, graph) == false) {
    MS_LOG(ERROR) << "Failed to BuildSingleOpGraph";
    return PYNATIVE_GRAPH_GE_BUILD_ERR;
  }
  // 将单个操作图添加到图形管理器中,它将通过会话进行迭代
  transform::Status ret =
    transform::DfGraphManager::GetInstance().AddGraph(SINGLE_OP_GRAPH, std::shared_ptr<transform::DfGraph>(graph));
  if (ret != transform::SUCCESS) { //无法将图形添加到图形管理器中
    MS_LOG(ERROR) << "Failed to AddGraph into graph manager";
    return PYNATIVE_GRAPH_MANAGER_ERR;
  }
  return PYNATIVE_SUCCESS;
}
......
    std::vector<MeTensorPtr> ConvertOutputTensors(const OpExecInfoPtr &op_exec_info,
                                              const std::vector<GeTensorPtr> &ge_tensors) {
  //声明:               
  std::vector<MeTensorPtr> outputs;
  AbstractBasePtr abs_base = op_exec_info->abstract;
  std::vector<std::vector<int64_t>> shapes;
  //abs_base不为空的情况下,俩种判断语句if对outputs进行填充
  if (abs_base != nullptr && abs_base->isa<abstract::AbstractTensor>()) {
    auto arg_tensor = dyn_cast<abstract::AbstractTensor>(abs_base);
    shapes.emplace_back(arg_tensor->shape()->shape()); //尾部插入
    outputs = transform::TransformUtil::ConvertGeTensors(ge_tensors, shapes);
    return outputs;
  }
  if (abs_base != nullptr && abs_base->isa<abstract::AbstractTuple>()) {
    auto arg_tuple = dyn_cast<abstract::AbstractTuple>(abs_base);
    size_t len = arg_tuple->size();
    for (size_t i = 0; i < len; i++) {
      if (arg_tuple->elements()[i]->isa<abstract::AbstractTensor>()) {
        auto arg_tensor = dyn_cast<abstract::AbstractTensor>(arg_tuple->elements()[i]);
        shapes.emplace_back(arg_tensor->shape()->shape());
      }
    }
    outputs = transform::TransformUtil::ConvertGeTensors(ge_tensors, shapes);
    return outputs;
  }
  for (auto &it : ge_tensors) {
    auto tensor = transform::TransformUtil::ConvertGeTensor(it);
    if (tensor != nullptr) {
      outputs.emplace_back(tensor);
    }
  }
  return outputs;
}
py::object RunOpInGE(const OpExecInfoPtr &op_exec_info, PynativeStatusCode *status) {
  MS_LOG(INFO) << "RunOpInGe start"; //运行操作开始
  MS_EXCEPTION_IF_NULL(op_exec_info);
  MS_EXCEPTION_IF_NULL(status);
  // 出错时返回空 py::tuple
  py::tuple err_ret(0);
  auto op_name = op_exec_info->op_name;
  transform::OpAdapterPtr adapter = transform::DfGraphConvertor::FindAdapter(op_name, true);
  if (adapter == nullptr) { //无法找到 GE 适配器
    MS_LOG(ERROR) << "Unable to find GE Adapter for " << ((std::string)py::str(op_name));
    *status = PYNATIVE_OP_NOT_IMPLEMENTED_ERR;
    return std::move(err_ret);
  }
  std::vector<GeTensorPtr> inputs{};
  ToTensorPtr(op_exec_info, &inputs);
  //将我的 attr 转换为 ge AttrValue
  PynativeStatusCode ret = ConvertAttributes(op_exec_info, inputs);
  if (ret != PYNATIVE_SUCCESS) {
    *status = ret;
    return std::move(err_ret);
  }
  // 运行图
  transform::RunOptions run_options;
  run_options.name = SINGLE_OP_GRAPH;
  std::vector<GeTensorPtr> ge_inputs;
  std::vector<GeTensorPtr> ge_outputs;
  transform::GraphRunnerOptions graph_runner_options;
  graph_runner_options.options["ge.trainFlag"] = "1";
  auto graph_runner = std::make_shared<transform::GraphRunner>(graph_runner_options);
  transform::Status run_ret;
  {
    // 在调用(可能长时间运行)C++ 代码之前释放 GIL
    py::gil_scoped_release release;
    run_ret = graph_runner->RunGraph(run_options, ge_inputs, &ge_outputs);
  }
  //GraphRunner 无法运行图
  if (run_ret != transform::Status::SUCCESS) {
    MS_LOG(ERROR) << "GraphRunner fails to run graph";
    *status = PYNATIVE_GRAPH_GE_RUN_ERR;
    return std::move(err_ret);
  }
  std::vector<MeTensorPtr> graph_outputs = ConvertOutputTensors(op_exec_info, ge_outputs);
  size_t output_size = graph_outputs.size();
  py::tuple result(output_size);
  for (size_t i = 0; i < output_size; i++) {
    MS_EXCEPTION_IF_NULL(graph_outputs[i]);
    result[i] = *graph_outputs[i];
  }
  *status = PYNATIVE_SUCCESS;
  //运行开放
  MS_LOG(INFO) << "RunOpInGe end";
  return std::move(result);
}

总结

PyNative模式是在执行的过程中才建立计算图,不像Graph模式那样预先构建好了计算图。它的优点就是能让用户很方便地去调试神经网络。虽然从原理上来说PyNative模式比Graph模式要慢一点,但是也可以通过一些比如ms_function等功能,通过图优化等技术提高PyNative模式的运行速度

posted @ 2021-12-20 15:44  MS小白  阅读(111)  评论(0)    收藏  举报