图优化模块模块

图优化模块

MindCompiler(编译优化层):图层的核心编译器,主要基于端云统一的MindIR实现三大功能。包括硬件无关的优化(类型推导、自动微分、表达式化简等)、硬件相关优化(自动并行、内存优化、图算融合、流水线执行等)、部署推理相关的优化(量化、剪枝等),其中我负责的模块就是MindIR模块的前俩个硬件无关的优化和硬件相关的优化

1、MindIR的简介

中间表示(IR)是程序编译过程中介于源语言和目标语言之间的程序表示,以方便编译器进行程序分析和优化,因此IR的设计需要考虑从源语言到目标语言的转换难度,同时考虑程序分析和优化的易用性和性能。

MindIR是一种基于图表示的函数式IR,其最核心的目的是服务于自动微分变换。自动微分采用的是基于函数式编程框架的变换方法,因此IR采用了接近于ANF函数式的语义。

2、文法定义

ANF是函数式编程中常用且简洁的中间表示,其文法定义如下所示:

<aexp> ::= NUMBER | STRING | VAR | BOOLEAN | PRIMOP
          |  (lambda (VAR …) <exp>)
<cexp> ::= (<aexp> <aexp> …)
          |  (if <aexp> <exp> <exp>)
<exp> ::= (let ([VAR <cexp>]) <exp>) | <cexp> | <aexp>

ANF中表达式分为俩种,一种是原子表达式,一种是复合表达式。

  • 原子表达式表示一个常数值或一个变量或一个匿名函数

  • 复合表达式由多个原子表达式复合组成,表示一个匿名函数或原语函数调用,组合的第一个输入是调用的函数,其余输入是调用的参数。

MindIR文法继承于ANF,其定义如下所示:

<ANode> ::= <ValueNode> | <ParameterNode>
<ParameterNode> ::= Parameter
<ValueNode> ::= Scalar | Named | Tensor | Type | Shape
               | Primitive | MetaFuncGraph | FuncGraph 
<CNode> ::= (<AnfNode> …)
<AnfNode> ::= <CNode> | <ANode>

MindIR中的ANode对应于ANF的原子表达式,ANode有两个子类分别为ValueNode和ParameterNode

  • ValueNode表示常数节点,可承载一个常数值(标量、符号、张量、类型、维度等),也可以是一个原语函数或一个元函数或一个普通函数,因为在函数式编程中函数定义本身也是一个值。ParameterNode是参数节点,表示函数的形参。

MindIR中CNode对应于ANF的复合表达式,表示一次函数调用。

3、使用

在MindSpore自动微分时,会计算ParameterNode和CNode的梯度贡献,并返回最终ParameterNode的梯度,而不计算ValueNode的梯度。

在MindIR中,一个函数图表示一个普通函数的定义,函数图一般由ParameterNode、ValueNode和CNode组成有向无环图,可以清晰地表达出从参数到返回值的计算过程。在上图中可以看出,python代码中两个函数test_ffunc转换成了两个函数图,其参数xy转换为函数图的ParameterNode,每一个表达式转换为一个CNode。CNode的第一个输入链接着调用的函数,例如图中的addfuncreturn。值得注意的是这些节点均是ValueNode,因为它们被理解为常数函数值。CNode的其他输入链接这调用的参数,参数值可以来自于ParameterNode、ValueNode和其他CNode。

在ANF中每个表达式都用let表达式绑定为一个变量,通过对变量的引用来表示对表达式输出的依赖,而在MindIR中每个表达式都绑定为一个节点,通过节点与节点之间的有向边表示依赖关系。

4、保存IR

通过context.set_context(save_graphs=True)来保存各个编译阶段的中间代码。被保存的中间代码有两种格式,一个是后缀名为.ir的文本格式,一个是后缀名为.dot的图形化格式。当网络规模不大时,建议使用更直观的图形化格式来查看,当网络规模较大时建议使用更高效的文本格式来查看。

5、函数式语义

MindIR较传统计算图的一个重要特性是不仅可以表达算子之间的数据依赖,还可以表达丰富的函数式语义。

5.1、高阶函数

在MindIR中,函数的定义是由一个子图来定义,但其本身可以是一个被传递的值,作为其他高阶函数的输入或输出。 比如:函数f作为参数传入了函数g,因此函数g是一个接收函数输入的高阶函数,函数f真正的调用点是在函数g内部。

5.2、控制流

控制流在MindIR中是以高阶函数选择调用的形式表达。这样的形式把控制流转换为高阶函数的数据流,从而使得自动微分算法更加强大。不仅可以支持数据流的自动微分,还可以支持条件跳转、循环和递归等控制流的自动微分。

5.3、自由变量和闭包

闭包是一种编程语言特性,它指的是代码块和作用域环境的结合。自由变量是指在代码块中引用作用域环境中的变量而非局部变量。在MindIR中,代码块是以函数图呈现的,而作用域环境可以理解为该函数被调用时的上下文环境,自由变量的捕获方式是值拷贝而非引用。

6、优秀代码赏析

/* 支持 utils 定义的命名空间 */
namespace mindspore {
bool CheckIfNeedExpand(const std::vector<BaseRef> &list) {
  return std::any_of(list.begin(), list.end(), [](#) { return utils::isa<Seq>(any); }); // 遍历
}
// 智能指针shared_ptr
std::shared_ptr<VectorRef> ExpandList(const std::vector<BaseRef> &list) {
  std::shared_ptr<VectorRef> new_list = std::make_shared<VectorRef>();
  for (auto &item : list) { // 遍历list
    if (utils::isa<Seq>(item)) {
      const Seq &seq = utils::cast<Seq>(item); // const == utils::cast<Seq>
      new_list->insert(new_list->end(), seq.begin(), seq.end()); // seq的遍历
    } else {
      new_list->push_back(item); // 尾插item
    }
  }
  return new_list;
}

bool DefaultVisitor::Visit(const VectorRef &v_any, BaseRef *const visit_out) const {
  std::vector<BaseRef> out; // 定义out列表
  (void)std::transform(v_any.begin(), v_any.end(), std::back_inserter(out), 
                       [this](#) { return fn_(item); });
  if (visit_out != nullptr) {
    *visit_out = ExpandList(out); // 指针
  }
  return true;
}

bool DefaultVisitor::Visit(const BaseRef &any, BaseRef *const visit_out) const {
  if (utils::isa<Seq>(any)) {
    return Visit(utils::cast<Seq>(any), visit_out);
  } else if (utils::isa<AnfNodePtr>(any)) {
    auto nodeptr = utils::cast<AnfNodePtr>(any); // auto == utils::cast
    AnfNodePtr output; // 定义output
    AnfNodePtr *p_output = &output; // 定义AnfNodePtr类型的指针
    if (visit_out == nullptr) {
      p_output = nullptr;
    }
    Visit(nodeptr, fn_, p_output); // 函数调用
    if (visit_out != nullptr) {
      *visit_out = output;
    }
    return true;
  }
  // 访问错误,不支持访问类型
  MS_LOG(DEBUG) << "VisitError, not support type to Visit: " + any.ToString();
  return false;
}

void DefaultVisitor::Visit(const AnfNodePtr &node, const VisitFn &fn, AnfNodePtr *output) const {
  if (node->isa<CNode>()) { // 链表插入
    Visit(node->cast<CNodePtr>(), fn, output);
    return;
  }

  if (node->isa<ValueNode>()) {
    Visit(node->cast<ValueNodePtr>(), fn, output);
    return;
  }

  if (output != nullptr) {
    *output = node; // 链表定义
  }
}

void DefaultVisitor::Visit(const CNodePtr &cnode, const VisitFn &fn, AnfNodePtr *output) const {
  // 如果输出为 nullptr,则不需要创建新的 CNode 节点。
  if (output == nullptr) {
    for (auto &inp : cnode->inputs()) { // auto == cnode的类型
      (void)fn(inp);
    }

    if (cnode->func_graph() != nullptr) {
      (void)fn(cnode->func_graph()); // 插入
    } else {
      (void)fn(cnode->func_graph_as_var());
    }
    return;
  }
  // 初始化列表
  std::vector<AnfNodePtr> new_inputs;
  std::vector<BaseRef> after_cnode_fn;
  std::shared_ptr<VectorRef> out; // 智能指针
  (void)std::transform(cnode->inputs().begin(), cnode->inputs().end(), std::back_inserter(after_cnode_fn), fn); // 遍历
  if (CheckIfNeedExpand(after_cnode_fn)) {
    out = ExpandList(after_cnode_fn);
  }

  std::vector<BaseRef> &outs = after_cnode_fn;
  if (out != nullptr) {
    outs = out->elements();
  }

  for (auto &any_item : outs) {
    if (!utils::isa<AnfNodePtr>(any_item)) {
      // VisitError, fn 不返回相同类型的 AnfNodePtr
      MS_LOG(EXCEPTION) << "VisitError, fn not return the same type AnfNodePtr";
    }
    new_inputs.push_back(utils::cast<AnfNodePtr>(any_item)); // 尾部插入
  }

  BaseRef any_fg;
  AnfNodePtr new_cnode = nullptr;
  if (cnode->func_graph() != nullptr) {
    any_fg = fn(cnode->func_graph());
    if (!utils::isa<FuncGraphPtr>(any_fg)) {
      // VisitError, fn 不返回相同类型的 FuncGraphPtr
      MS_LOG(EXCEPTION) << "VisitError, fn not return the same type FuncGraphPtr";
    }
    // 智能指针
    new_cnode = std::make_shared<CNode>(new_inputs, utils::cast<FuncGraphPtr>(any_fg));
  } else {
    any_fg = fn(cnode->func_graph_as_var());
    if (utils::isa<VarPtr>(any_fg)) {
      new_cnode = std::make_shared<CNode>(new_inputs, utils::cast<VarPtr>(any_fg));
    } else if (utils::isa<FuncGraphPtr>(any_fg)) {
      new_cnode = std::make_shared<CNode>(new_inputs, utils::cast<FuncGraphPtr>(any_fg));
    } else {
      // VisitError, fn 不返回相同类型的 VarPtr 或者 FuncGraphPtr
      MS_LOG(EXCEPTION) << "VisitError, fn not return VarPtr or FuncGraphPtr";
    }
  }
  new_cnode->set_abstract(cnode->abstract());
  *output = new_cnode;
}

void DefaultVisitor::Visit(const ValueNodePtr &vnode, const VisitFn &fn, AnfNodePtr *output) const {
  const BaseRef &value = utils::cast<ValuePtr>(fn(vnode->value()));
  if (utils::isa<ValuePtr>(value)) {
    if (output != nullptr) {
      auto ct = NewValueNode(utils::cast<ValuePtr>(value)); // auto == bool
      // abstract可以用来修饰类,方法,属性,索引器和时间,这里不包括字段. 
      // 使用abstrac修饰的类,该类只能作为其他类的基类,不能实例化,
      // 而且abstract修饰的成员在派生类中必须全部实现,不允许部分实现,否则编译异常
      ct->set_abstract(vnode->abstract());
      *output = ct;
    }
    return;
  }
  // 访问结果不是 ValuePtr
  MS_LOG(EXCEPTION) << "Visit result is not ValuePtr.";
}
}  // 命名空间 mindspore
// 双重命名空间
namespace mindspore {
namespace opt {
// 实例化函数
const std::vector<PassPtr> &PassManager::Passes() const { return passes_; }

// 尾部插入pass
void PassManager::AddPass(const PassPtr &pass) {
  if (pass != nullptr) {
    passes_.push_back(pass);
  }
}

bool PassManager::Run(const FuncGraphPtr &func_graph, const std::vector<PassPtr> &passes) const {
  if (func_graph == nullptr) { // 为空时返回false
    return false;
  }
  auto context_ptr = MsContext::GetInstance(); // auto == bool
  MS_EXCEPTION_IF_NULL(context_ptr); // 判断context_ptr是否为空
  bool save_graphs = context_ptr->get_param<bool>(MS_CTX_SAVE_GRAPHS_FLAG);
  bool changed = false;
/*
size_t在C语言中就有了。
它是一种“整型”类型,里面保存的是一个整数,就像int, long那样。
这种整数用来记录一个大小(size)。
size_t的全称应该是size type,就是说“一种用来记录大小的数据类型”
*/
  size_t num = 0; 
  // 遍历passes
  for (const auto &pass : passes) {
    if (pass != nullptr) {
#if defined(_WIN32) || defined(_WIN64) // 在window32或者window64系统时:
      auto start_time = std::chrono::steady_clock::now();
#else // 非win32或者win64时:
      struct timeval start_time {}; // 结构体定义
      struct timeval end_time {};
      (void)gettimeofday(&start_time, nullptr); // 函数调用
#endif
      if (pass->Run(func_graph)) {
        changed = true;
      }
#if defined(_WIN32) || defined(_WIN64)
      auto end_time = std::chrono::steady_clock::now();
      std::chrono::duration<double, std::ratio<1, 1000000>> cost = end_time - start_time;
      // 运行通过 hwopt
      MS_LOG(INFO) << "Run pass hwopt_" + name() + "_" << num << "_" + pass->name() + " in " << cost.count() << " us";
#else
      (void)gettimeofday(&end_time, nullptr);
      // 时间单位
      uint64_t cost = 1000000 * static_cast<uint64_t>(end_time.tv_sec - start_time.tv_sec);
      cost += static_cast<uint64_t>(end_time.tv_usec - start_time.tv_usec);
      // 运行通过 hwopt
      MS_LOG(INFO) << "Run pass hwopt_" + name() + "_" << num << "_" + pass->name() + " in " << cost << " us";
#endif
      // ENV不能倾倒通过IR
      static const auto enable_dump = (common::GetEnv("ENV_NO_DUMP_BE_PASS_IR") != "1");
      if (save_graphs && enable_dump) {
        std::ostringstream oss;
        // 详细的ir文件
        oss << "verbose_ir_files"
            << "/";
        oss << "hwopt_" + name() + "_" + std::to_string(num) + "_" + pass->name() + ".ir";
        DumpIR(oss.str(), func_graph, true);
      }
      num++;
    }
  }
  return changed;
}

bool PassManager::Run(const FuncGraphPtr &func_graph) const {
  bool changed = false;
  // 运行所有通行证
  bool change = true;
  while (change) { // 循环直到change为空
    change = Run(func_graph, passes_);
    changed = change || changed;
    if (run_only_once_) {
      break;
    }
  }
  return changed;
}
}  // 命名空间 opt
}  // 命名空间 mindspore
// 双重命名空间
namespace mindspore {
namespace opt {
PatternProcessPass::PatternProcessPass(const std::string &name, bool multigraph)
    : NodePass(name),
      multigraph_(multigraph),
      pattern_engine_(PatternEngine(std::make_shared<DefaultVisitor>(),
                                    std::function<bool(const BaseRef &, const BaseRef &)>(AnfEqual),
                                    std::function<bool(const BaseRef &, const BaseRef &)>(CNodeTypeEqual))),
      primitive_vars_(std::make_shared<PrimitiveVarMap>()) {}
// 智能指针 make_shared
const BaseRef PatternProcessPass::DefinePattern() const {
  VarPtr X = std::make_shared<Var>();
  return BaseRef({X});
}
void PatternProcessPass::Build() {
  VarPtr fg = std::make_shared<Var>("RootG"); // 智能指针
  BaseRef pattern = std::move(DefinePattern()); // 移除
  pattern_ = SexpToNode(pattern, fg, primitive_vars_.get(), multigraph_);
}
AnfNodePtr PatternProcessPass::Run(const FuncGraphPtr &func_graph, const AnfNodePtr &node) {
  if (pattern_ == nullptr) {
    Build();
  }
  // auto可以自己转换对应的类型,即empty_equiv为std::make_shared类型
  auto empty_equiv = std::make_shared<Equiv>();
  MS_EXCEPTION_IF_NULL(primitive_vars_); // 判断是否为空
  EquivPtr equiv = pattern_engine_.Match(pattern_, node, *primitive_vars_, empty_equiv);
  if (equiv != nullptr && !equiv->empty()) { // 如果不为空,运行后续函数
    return Process(func_graph, node, equiv);
  }
  return nullptr;
}
bool MultipleOutputPatternProcessPass::MatchAnotherPattern(const AnfNodePtr &node, const EquivPtr &equiv) const {
  MS_EXCEPTION_IF_NULL(node); // 判断是否为空
  MS_EXCEPTION_IF_NULL(equiv);
  VarPtr fg = std::make_shared<Var>("RootG"); // 智能指针
  auto empty_equiv = std::make_shared<Equiv>(); // auto == std::make_shared
  MS_EXCEPTION_IF_NULL(child_primitive_vars_); // 判断是否为空
  EquivPtr another_equiv =
    child_pattern_engine_.Match(SexpToNode(DefineAnotherPattern(), fg, child_primitive_vars_.get(), true), node,
                                *child_primitive_vars_, empty_equiv);
  if (another_equiv != nullptr && !another_equiv->empty()) { // 非空的运行后续函数
    return IsShareNodes(equiv, another_equiv);
  }
  return false;
}
// 函数尾插pass_manager
void GraphOptimizer::AddPassManager(const PassManagerPtr &pass_manager) {
  if (pass_manager != nullptr) {
    pass_managers_.push_back(pass_manager);
  }
}
FuncGraphPtr GraphOptimizer::Optimize(const FuncGraphPtr &func_graph, bool run_only_once) {
  MS_EXCEPTION_IF_NULL(func_graph); // 判断是否为空
  run_only_once_ = (pass_managers_.size() == 1) ? true : run_only_once; // 三目运算符
  // 每次都创建新经理带来的绩效风险
  auto manager = Manage(func_graph, true); // auto = bool
  bool changed = true;
  while (changed) {
    changed = false;
    // 遍历pass_managers
    for (size_t i = 0; i < pass_managers_.size(); ++i) {
      const PassManagerPtr &pm = pass_managers_[i];
      if (pm != nullptr && pm->Run(func_graph)) {
        changed = true;
      }
    }
    if (run_only_once_) {
      break;
    }
  }
  // 容器定义
  std::vector<FuncGraphPtr> func_graphs;
  func_graphs.push_back(func_graph); // 尾部插入
  manager->KeepRoots(func_graphs);
  (void)TopoSort(func_graph->get_return());
  return func_graph;
}
}  // 命名空间 opt
}  // 命名空间 mindspore
// 双重命名空间
namespace mindspore {
namespace parallel {
// 对头文件的DeviceMatrix类里面的方法实例化
DeviceMatrix::DeviceMatrix(int64_t rank, RankList dev_list, Shape dev_shape)
    : rank_(rank), dev_list_(std::move(dev_list)), dev_shape_(std::move(dev_shape)) {
  // any_of是算法库新增的判断算法
  // any_of:检查区间[first, last)中是否至少有一个元素都满足一元判断式p,只要有一个元素满足条件就返回true,否则返回false
  if (!std::any_of(dev_list_.begin(), dev_list_.end(), [rank](#) { return a == rank; })) {
   // rank不在现阶段!
    MS_LOG(EXCEPTION) << "Rank " << rank << " is not in the current stage!";
  }
  int64_t total = std::accumulate(dev_shape_.begin(), dev_shape_.end(), 1, std::multiplies<int64_t>());
  if (LongToSize(total) != dev_list_.size()) {
    // 设备形状与设备列表的大小不匹配
    MS_LOG(EXCEPTION) << "Device shape does not match the size of the device list!";
  }
}
// 对头文件的DeviceMatrix类里面的方法实例化
Status DeviceMatrix::CreateGroupList() {
  /* size_t是标准C库中定义的,在64位系统中为long long unsigned int
     非64位系统中为long unsigned int */ 
  size_t size = dev_shape_.size();
  RankList group; // Rank列表 group
  for (size_t i = 0; i < size; i++) { // 遍历size
    Status status = GetDevicesAlongDim(SizeToUlong(i), &group);
    group_list_.push_back(group); // 在group_list_尾部插入group列表
    if (status == Status::FAILED) {
      return Status::FAILED; // 返回FAILED对应的数值
    }
  }
  return Status::SUCCESS; // 返回Status里面SUCCESS对应的数值
}
// 对头文件的DeviceMatrix类里面的方法GetDevicesAlongDim实例化
Status DeviceMatrix::GetDevicesAlongDim(const uint64_t &dim, RankList *devices) { // 传入维度
  if (dim >= dev_shape_.size()) {
    // 维度已经超出设备形状的大小
    MS_LOG(EXCEPTION) << "The dimension " << dim << " is out of the size of the device shape!";
  }
  if (dev_shape_[dim] == 1) {
    *devices = {rank_};
    return Status::SUCCESS;
  }
  RankList group;
  // 创建一个vector<RankList>的链表为local_group_list
  std::vector<RankList> local_group_list; 
  // 维度过低
  /*
  int16_t    : typedef signed short ;
  uint16_t  : typedef unsigned short ;
  int32_t    : typedef signed int;
  uint32_t  : typedef unsigned int;
  int64_t    : typedef signed  long long;
  uint64_t  : typedef unsigned long long;
  */
  int64_t step = 1; // signed  long long
  // 由于维度过低,对i进行+1运算,对step进行运算,直到i等于dev_shape_的长度
  for (uint64_t i = dim + 1; i < dev_shape_.size(); i++) { 
    step = step * dev_shape_[i];
  }
  int64_t num = *dev_list_.begin();
  for (int64_t i = 0; i < dev_shape_[dim]; i++) {
    group.push_back(num); // 在group尾部插入num值
    num += step;
  }
  for (int64_t i = 0; i < step; i++) {
    local_group_list.push_back(group);
    /*for_each用于逐个遍历容器元素,
    它对迭代器区间[first,last)所指的每一个元素,执行由单参数函数对象f所定义的操作。
    它是for循环的一种替代方案*/
    (void)std::for_each(group.begin(), group.end(), [](#) { a++; });
  }
  // 比维度高
  // 赋值step与len
  step = step * dev_shape_[dim];
  int64_t len = SizeToLong(dev_list_.size()) / step;
  // 查找 rank
  int64_t target = rank_;
  for (int64_t i = 0; i < len; i++) {
    for (RankList &temp : local_group_list) {
      // 检查区间[first, last)中是否至少有一个元素都满足一元判断式p
      if (std::any_of(temp.begin(), temp.end(), [target](#) { return a == target; })) {
        *devices = temp;
        return Status::SUCCESS;
      }
      // 逐个遍历容器元素
      (void)std::for_each(temp.begin(), temp.end(), [step](#) { a = a + step; });
    }
  }
  // 在设备列表中找不到等级的组
  MS_LOG(ERROR) << "Can't find groups for rank" << rank_ << " in device list!";
  return Status::FAILED;
}
Shape ConvertRankToCoordinate(int64_t rank, const Shape &dev_shape) {
  Shape dev_coordinate;
  for (size_t i = 0; i < dev_shape.size(); ++i) {
    int64_t size = dev_shape[dev_shape.size() - i - 1];
    if (size == 0) {
      // 无效的开发形状
      MS_LOG(EXCEPTION) << "Invalid dev shape: " << ShapeToString(dev_shape);
    } else {
      int64_t index = rank % size;
      // 尾部的index位插入dev_coordinate.begin()
      (void)dev_coordinate.insert(dev_coordinate.begin(), index);
      rank = rank / size;
    }
  }
  return dev_coordinate;
}
// 对头文件的DeviceMatrix类里面的方法GetDevicesByTensorMap实例化
Status DeviceMatrix::GetDevicesByTensorMap(const Shape &tensor_map, RankList *rank_list) {
  for (auto &element : tensor_map) {
    // -1 表示不拆分对应的维度
    if (element == MAP_NONE) {
      continue;
    } else if ((element < 0) || (LongToSize(element) >= dev_shape_.size())) {
      // 创建group用的tensor_map:tensor_map无效
      MS_LOG(ERROR) << "create group by tensor map: the tensor map is invalid";
      return FAILED;
    }
  }
  // 将全局rank转换为局部rank(数组的索引)来计算坐标
  uint32_t local_rank = 0;
  for (auto &tmp_rank : dev_list_) {
    if (tmp_rank == rank_) {
      break;
    }
    ++local_rank;
  }
  if (local_rank == dev_list_.size()) {
    // Rank的id不在设备列表list中
    MS_LOG(ERROR) << "Rank id: " << local_rank << "is not in the device list.";
    return FAILED;
  }
// 定义字符串ShapeToString
std::string ShapeToString(const Shape &shape) {
  std::string str = "[";
  for (size_t i = 0; i < shape.size(); ++i) { // 遍历i,直到i==shape.size
    str += std::to_string(shape[i]);
    if (i < shape.size() - 1) {
      str += ", ";
    }
  }
  return str + "]"; // 返回带有中括号的字符串
}
// 字符串函数,参数传入列表
std::string ListToString(const RankList &list) {
  std::string str = "[";
  // atuo = const RankList
  for (auto &element : list) { // 遍历list,对str运算
    str += std::to_string(element) + ", ";
  }
  return str + "]"; // 返回带有中括号的字符串
}
}  // 命名空间 parallel
}  // 命名空间 mindspore

总结

MindIR • 全称MindSpore IR,是MindSpore的一种基于图表示的函数式IR,定义了可扩展的图 结构以及算子的IR表示。它消除了不同后端的模型差异,一般用于跨硬件平台执行推理任务。MindIR是MindSpore提供的中间表达形式,可以帮助我们实现一次训练多处部署,实现端云互通。MindIR选择的技术路线是采用Functional Graph IR,理论可以同时拥有函数和计算图方式的优点

  • MindSpore通过统一IR定义了网络的逻辑结构和算子的属性,将MindIR格式的模型文件 与硬件平台解耦,实现一次训练多次部署。

  • MindIR作为MindSpore的统一模型文件,同时存储了网络结构和权重参数值。同时支持 部署到云端Serving和端侧Lite平台执行推理任务。

  • 同一个MindIR文件支持多种硬件形态的部署:

学习于MindSpore官方文档:https://www.mindspore.cn/docs/zh-CN/r0.7/design/mindspore/ir.html

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