ONNX Runtime 源码阅读:Graph::SetGraphInputsOutputs() 函数
前言
为了深入理解ONNX Runtime的底层机制,本文将对 Graph::SetGraphInputsOutputs() 的代码逐行分析。
正文
首先判断Graph是否从ONNX文件中加载所得:
if (is_loaded_from_model_file_) return Status::OK();
如果是,可直接返回;如果不是,则需要解析Graph中的节点,从而设置模型的输入和输出。
将Graph中的成员变量 value_info_、graph_inputs_excluding_initializers_、graph_inputs_including_initializers_ 以及 graph_outputs_ 全部清空:
value_info_.clear();
graph_inputs_excluding_initializers_.clear();
if (!graph_inputs_manually_set_) {
graph_inputs_including_initializers_.clear();
} else {
std::unordered_set<std::string> existing_names;
for (auto arg : graph_inputs_including_initializers_) {
const std::string& name = arg->Name();
if (existing_names.count(name) == 0) {
graph_inputs_excluding_initializers_.push_back(arg);
existing_names.insert(name);
}
}
}
if (!graph_outputs_manually_set_) {
graph_outputs_.clear();
}
设置一些局部变量,方便下面的使用分析:
std::unordered_map<std::string, size_t> output_name_to_node_arg_index;
std::vector<const NodeArg*> output_node_args_in_order;
std::unordered_set<std::string> added_input_names{outer_scope_node_arg_names_};
统计所有节点的输出,添加到以上局部变量(output_name_to_node_arg_index 和 output_node_args_in_order)中:
for (const auto& node : Nodes()) {
for (const auto* output_def : node.OutputDefs()) {
if (output_def->Exists()) {
output_node_args_in_order.push_back(output_def);
output_name_to_node_arg_index.insert({output_def->Name(), output_node_args_in_order.size() - 1});
}
}
}
auto graph_output_args = output_name_to_node_arg_index; // 拷贝一份输出节点map
然后遍历图中每个节点以及每个节点的输入:
for (const auto& node : Nodes()) {
// Go thru all node's inputs.
for (const auto* input_arg : node.InputDefs()) {
...
}
}
在输出节点name列表中查找当前输入name:
auto output_arg_iter = output_name_to_node_arg_index.find(input_arg->Name());
如果没有找到,说明这个节点的输入就是图的输入,接下来还需要判断这个输入是否已经放在局部变量added_input_names中:
if (output_name_to_node_arg_index.end() == output_arg_iter) {
// This input arg is not the output of another node so must come from either a graph input or an initializer.
const std::string& name = input_arg->Name();
if (added_input_names.end() == added_input_names.find(name)) {
...
}
}
如果已经放到局部变量added_input_names中,就可以判断节点的下一个输入或者下一个节点的输入。如果没有放到局部变量added_input_names中:
bool is_initializer = name_to_initial_tensor_.find(name) != name_to_initial_tensor_.end(); // 判断当前input_arg是否已初始化过的tensor,如果是就不可以再放置到 graph_inputs_excluding_initializers_ 中
if (!graph_inputs_manually_set_) { // 如果未主动调用 SetInputs() 方法
// if IR version < 4 all initializers must have a matching graph input
// (even though the graph input is not allowed to override the initializer).
// if IR version >= 4 initializers are not required to have a matching graph input.
// any graph inputs that are to override initializers must be specified by calling SetInputs.
if (!is_initializer || ir_version_ < 4) {
graph_inputs_including_initializers_.push_back(input_arg);
}
if (!is_initializer) {
// If input_arg is not of an initializer, we add it into graph_inputs_excluding_initializers_.
graph_inputs_excluding_initializers_.push_back(input_arg);
}
} else { // 如果主动调用了 SetInputs() 方法
// graph_inputs_including_initializers_ has been manually populated by SetInputs.
// Validation: the <input_arg> must be in graph inputs or initializers when it's manually set.
if (!is_initializer) {
const auto& inputs = graph_inputs_including_initializers_;
bool in_inputs = std::find(inputs.begin(), inputs.end(), input_arg) != inputs.end();
if (!in_inputs) {
return Status(ONNXRUNTIME, FAIL,
name + " must be either specified in graph inputs or graph initializers.");
}
} else {
// If arg_input is of an initializer, we remove it from graph_inputs_excluding_initializers_
// whose initial content has both initializers and non-initializers.
auto input_pos = std::find(graph_inputs_excluding_initializers_.begin(),
graph_inputs_excluding_initializers_.end(),
input_arg);
if (input_pos != graph_inputs_excluding_initializers_.end()) {
graph_inputs_excluding_initializers_.erase(input_pos);
}
}
}
added_input_names.insert(name);
可以看到,这里会把当前的 input_arg 分别放到 graph_inputs_including_initializers_ 和 graph_inputs_excluding_initializers_ 中,并将name放在added_input_names中。
如果该输入的name已经在输出节点name列表中,说明这个节点是中间输出结果,而非整个图的输出,因此应该将其从图的输出(graph_output_args)中删除,并放在 value_info_ 中:
if (output_name_to_node_arg_index.end() == output_arg_iter) {
...
}else if(graph_output_args.erase(output_arg_iter->first) >= 1){
value_info_.insert(input_arg);
}
以上我们对Graph的三个成员变量:graph_inputs_including_initializers_、graph_inputs_excluding_initializers_和value_info_分别进行了赋值,其中前两者存储输入,后者存储中间结果。我们还需要处理图的输出结果:`graph_outputs_`:
if (!graph_outputs_manually_set_) {
// Set graph outputs in order.
std::vector<size_t> graph_output_args_index;
graph_output_args_index.reserve(graph_output_args.size());
for (const auto& output_arg : graph_output_args) { // graph_output_args原本存储了所有节点的输出,但是前面的代码已经把中间节点的输出给移除了,因此剩下的就是整个Graph的输出
graph_output_args_index.push_back(output_arg.second);
}
std::sort(graph_output_args_index.begin(), graph_output_args_index.end());
for (auto& output_arg_index : graph_output_args_index) {
graph_outputs_.push_back(output_node_args_in_order[output_arg_index]);
}
}
最后,还需要对 graph_overridable_initializers_ 进行处理:
ComputeOverridableInitializers();
进入这个函数内部:
void Graph::ComputeOverridableInitializers() {
graph_overridable_initializers_.clear();
if (CanOverrideInitializer()) {
// graph_inputs_excluding_initializers_ and graph_inputs_including_initializers_
// are inserted in the same order. So we walk and compute the difference.
auto f_incl = graph_inputs_including_initializers_.cbegin();
const auto l_incl = graph_inputs_including_initializers_.cend();
auto f_excl = graph_inputs_excluding_initializers_.cbegin();
const auto l_excl = graph_inputs_excluding_initializers_.cend();
while (f_incl != l_incl) {
// Equal means not an initializer
if (f_excl != l_excl && *f_incl == *f_excl) {
++f_incl;
++f_excl;
continue;
}
graph_overridable_initializers_.push_back(*f_incl);
++f_incl;
}
}
}
这是一个很简单的算法,通过比较 graph_inputs_including_initializers_ 和 graph_inputs_excluding_initializers_,提取出 initializer 并放置到 graph_overridable_initializers_ 中。
至此,我们完成了对 Graph::SetGraphInputsOutputs() 函数的解析。
总结
针对这个函数的解析不仅理解了如何从Graph的nodes中分析出graph的输入和输出,而且懂得了graph_overridable_initializers_以及value_info_的作用。
ONNX Runtime 源码阅读:Graph::SetGraphInputsOutputs() 函数的更多相关文章
- linux源码阅读笔记 fork函数
在阅读源码的过程中,发现找不到fork函数的定义.后来在linux/init/main.c中找到了这样一条语句 static inline _syscall0(int,fork) 原来这里就是fork ...
- [PHP源码阅读]number_format函数
上次讲到PHP是如何解析大整数的,一笔带过了number_format的处理,再详细阅读该函数的源码,以下是小分析. 函数原型 string number_format ( float $number ...
- linux源码阅读笔记 asm函数
在linux源码中经常遇到__asm__函数.它其实是函数asm的宏定义 #define __asm__ asm,asm函数让系统执行汇编语句. __asm__常常与__volatile__一起出现. ...
- Runtime 源码阅读
Runtime 属性说明 /** * 每一个 Java 应用程序都有一个关联的运行时对象 * * @author unascribed * @see java.lang.Runtime#getRunt ...
- [PHP源码阅读]explode和implode函数
explode和implode函数主要用作字符串和数组间转换的操作,比如获取一段参数后根据某个字符分割字符串,或者将一个数组的结果使用一个字符合并成一个字符串输出.在PHP中经常会用到这两个函数,因此 ...
- CI框架源码阅读笔记3 全局函数Common.php
从本篇开始,将深入CI框架的内部,一步步去探索这个框架的实现.结构和设计. Common.php文件定义了一系列的全局函数(一般来说,全局函数具有最高的加载优先权,因此大多数的框架中BootStrap ...
- 3 EventTime 事件时间类和TimeNow函数——Live555源码阅读(一)基本组件类
这是Live555源码阅读的第一部分,包括了时间类,延时队列类,处理程序描述类,哈希表类这四个大类. 这里是时间相关类的第三个部分,也是最后一个部分. EventTime 事件时间类 这个类和Dela ...
- PHP源码阅读笔记一(explode和implode函数分析)
PHP源码阅读笔记一一.explode和implode函数array explode ( string separator, string string [, int limit] )此函数返回由字符 ...
- [PHP源码阅读]strtolower和strtoupper函数
字符串的操作函数中,字符串的大小写转换也算是比较常用的函数,其底层实现也比较简单,下面来一探究竟. 我在github上有对PHP源码更详细的注解.感兴趣的可以围观一下,给个star.PHP5.4源码注 ...
随机推荐
- Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?
启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了以下 3 个注解: @SpringBootConfiguration:组合了 ...
- 一张自增表里面总共有 7 条数据,删除了最后 2 条数据,重启 MySQL 数据库,又插入了一条数据,此时 id 是几?如何获取当前数据库版本?
一张自增表里面总共有 7 条数据,删除了最后 2 条数据,重启 MySQL 数据库,又插入了一条数据,此时 id 是几? 一般情况下,我们创建的表的类型是InnoDB,如果新增一条记录(不重启mysq ...
- 解释 AOP?
面向切面的编程,或 AOP, 是一种编程技术,允许程序模块化横向切割关注点,或横切典型的责任划分,如日志和事务管理.
- TCP(三)
1.三次握手 置位概念:根据TCP的包头字段,存在3个重要的标识ACK.SYN.FIN ACK:表示验证字段 SYN:位数置1,表示建立TCP连接 FIN:位数置1,表示断开TCP连接 三次握手过程说 ...
- 学习ELK日志平台(四)
一:需求及基础: 场景: 1.开发人员不能登录线上服务器查看详细日志 2.各个系统都有日志,日志数据分散难以查找 3.日志数据量大,查询速度慢,或者数据不够实时 4.一个调用会涉及到多个系统,难以在这 ...
- centos云服务器配置redis外网可访问
找到这行,注释 bind 127.0.0.1 #bind 127.0.0.1 找到这行,修改 protected-mode yes protected-mode no 重启服务 ./redis-ser ...
- Numpy怎样给数组增加一个维度
Numpy怎样给数组增加一个维度 背景:很多数据计算都是二维或三维的,对于一维的数据输入为了形状匹配,经常需升维变成二维 需要:在不改变数据的情况下,添加数组维度:(注意观察这个例子,维度变了,但数据 ...
- 基于vue2.0的在线电影APP,
基于vue2.0构建的在线电影网[film],webpack + vue + vuex + vue-loader + keepAlive + muse-ui + cordova 全家桶,cordova ...
- Kurento安装与入门12——Show DataChannel
Show DataChannel 本示例允许用户在文本框中输入任意文字,输入的文字将以字幕的形式显示在传回的视频中. 官网文档 [Show DataChannel[1] 首先从github上获取代码( ...
- input禁止输入空格 以及 input各种输入限制
1.禁止输入空格 方法1: oninput="value=value.replace(/\s+/g,'')" 在 ios中 用手机原生键盘 输入 会出现bug, 就是 ...