1 PyTorch构建自己一种易用的计算图结构-德赢Vwin官网 网
0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

PyTorch构建自己一种易用的计算图结构

jf_pmFSk4VX 来源:GiantPandaCV 2023-02-01 14:26 次阅读

PNNX

PNNX项目 PyTorch Neural Network eXchange(PNNX)是PyTorch模型互操作性的开放标准.

PNNX为PyTorch提供了一种开源的模型格式, 它定义了与PyTorch相匹配的数据流图和运算操作, 我们的框架在PNNX之上封装了一层更加易用和简单的计算图格式. PyTorch训练好一个模型之后, 然后模型需要转换到PNNX格式, 然后PNNX格式我们再去读取, 形成计算图.

PyTorch到我们计算图?

PNNX帮我做了很多的图优化、算子融合的工作, 所以底层的用它PNNX的话, 我们可以吸收图优化的结果, 后面推理更快.

但是我们不直接在项目中用PNNX, 因为别人的工作和自己推理框架开发思路总是有不同的. 所以在这上面封装, 又快速又好用方便, 符合自己的使用习惯. PNNX的使用方法, 我们只是去读取PNNX导出的模型, 然后构建自己一种易用的计算图结构.

PNNX的格式定义

PNNX由操作数operand(运算数)和operator(运算符号), PNNX::Graph用来管理和操作这两者.

操作数(operand), 也可以通过操作数来方向访问到这个数字的产生者和使用者Customer

代码链接

Operand

定义链接

Operand有以下几个部分组成:

Producer: 类型是operator, 表示产生了这个操作数(operand)的运算符(operator). 也就是说这个操作数(operand)是Producer的输出.

比如Producer是有个Add, Operand就是对应的Add结果.

Customer:类型是operator, 表示需要这个操作数是下一个操作的运算符(operator)的输入. 值得注意的是生产者Producer作为产生这个操作数的operator只能有一个, 而消费者Customer可以有多个, 消费者将当前的操作数Operand作为输入.

Name: 类型是std::string, 表示这个操作数的名称.

Shape: 类型是std::vector , 用来表示操作数的大小.

Operator

定义链接

operator有以下几个部分组成:

Inputs: 类型为std::vector, 表示这个运算符计算过程中所需要的输入操作数(operand)

Outputs: 类型为std::vector, 表示这个运算符计算过程中得到的输出操作数(operand)

Type, Name 类型均为std::string, 分别表示运算符号的类型和名称

Params, 类型为std::map,用于存放该运算符的所有参数(例如对应Convolution operator的params中将存放stride, padding, kernel size等信息)

Attrs, 类型为std::map, 用于存放运算符号所需要的具体权重属性(例如对应Convolution operator的attrs中就存放着卷积的权重和偏移量)

我们对PNNX的封装

对Operands(运算数)的封装

structRuntimeOperand{
std::stringname;///操作数的名称
std::vectorshapes;///操作数的形状
std::vector>>datas;///存储操作数
RuntimeDataTypetype=RuntimeDataType::kTypeUnknown;///操作数的类型,一般是float
};

对Operator(运算符)的封装

对PNNX::operator的封装是RuntimeOperator, 下面会讲具体的PNNX到KuiperInfer计算图的转换过程.

///计算图中的计算节点
structRuntimeOperator{
~RuntimeOperator();
std::stringname;///运算符号节点的名称
std::stringtype;///运算符号节点的类型
std::shared_ptrlayer;///节点对应的计算Layer

std::vectoroutput_names;///运算符号的输出节点名称
std::shared_ptroutput_operands;///运算符号的输出操作数

std::map>input_operands;///运算符的输入操作数
std::vector>input_operands_seq;///运算符的输入操作数,顺序排列

std::mapparams;///算子的参数信息
std::map>attribute;///算子的属性信息,内含权重信息
};

从PNNX计算图到KuiperInfer计算图的过程

本节代码链接

1. 加载PNNX的计算图

intload_result=this->graph_->load(param_path_,bin_path_);

2. 获取PNNX计算图中的运算符(operators)

std::vectoroperators=this->graph_->ops;
if(operators.empty()){
LOG(ERROR)<< "Can not read the layers' define";
    return false;
}

3. 遍历PNNX计算图中的运算符, 构建KuiperInfer计算图

for(constpnnx::Operator*op:operators){
...
}

4. 初始化RuntimeOperator的输入

初始化RuntimeOperator中的RuntimeOperator.input_operands和RuntimeOperator.input_operands_seq两个属性.

通过解析pnnx的计算图来初始化KuiperInfer RuntimeOperator中的输入部分. 简单来说就是从pnnx::inputs转换得到KuiperInfer::inputs

structRuntimeOperator{
///本过程要初始化的两个属性
std::map>input_operands;///运算符的输入操作数
std::vector>input_operands_seq;///运算符的输入操作数,顺序排列
...
}

从PNNX::Input到KuiperInfer::Input的转换过程, 代码链接

constpnnx::Operator*op=...
conststd::vector&inputs=op->inputs;
if(!inputs.empty()){
InitInputOperators(inputs,runtime_operator);
}
....
voidRuntimeGraph::InitInputOperators(conststd::vector&inputs,
conststd::shared_ptr&runtime_operator){
//遍历输入pnnx的操作数类型(operands),去初始化KuiperInfer中的操作符(RuntimeOperator)的输入.
for(constpnnx::Operand*input:inputs){
if(!input){
continue;
}
//得到pnnx操作数对应的生产者(类型是pnnx::operator)
constpnnx::Operator*producer=input->producer;
//初始化RuntimeOperator的输入runtime_operand
std::shared_ptrruntime_operand=std::make_shared();
//赋值runtime_operand的名称和形状
runtime_operand->name=producer->name;
runtime_operand->shapes=input->shape;

switch(input->type){
case1:{
runtime_operand->type=RuntimeDataType::kTypeFloat32;
break;
}
case0:{
runtime_operand->type=RuntimeDataType::kTypeUnknown;
break;
}
default:{
LOG(FATAL)<< "Unknown input operand type: " << input->type;
}
}
//runtime_operand放入到KuiperInfer的运算符中
runtime_operator->input_operands.insert({producer->name,runtime_operand});
runtime_operator->input_operands_seq.push_back(runtime_operand);
}
}

5. 初始化RuntimeOperator中的输出

初始化RuntimeOperator.output_names属性. 通过解析PNNX的计算图来初始化KuiperInfer Operator中的输出部分.代码链接

简单来说就是从PNNX::outputs到KuiperInfer::output

voidRuntimeGraph::InitOutputOperators(conststd::vector&outputs,
conststd::shared_ptr&runtime_operator){
for(constpnnx::Operand*output:outputs){
if(!output){
continue;
}
constauto&consumers=output->consumers;
for(constauto&c:consumers){
runtime_operator->output_names.push_back(c->name);
}
}
}

6. 初始化RuntimeOperator的权重(Attr)属性

KuiperInfer::RuntimeAttributes. Attributes中存放的是operator计算时需要的权重属性, 例如Convolution Operator中的weights和bias.

//初始化算子中的attribute(权重)
constpnnx::Operator*op=...
conststd::map&attrs=op->attrs;
if(!attrs.empty()){
InitGraphAttrs(attrs,runtime_operator);
}

代码链接

voidRuntimeGraph::InitGraphAttrs(conststd::map&attrs,
conststd::shared_ptr&runtime_operator){
for(constauto&pair:attrs){
conststd::string&name=pair.first;
//1.得到pnnx中的Attribute
constpnnx::Attribute&attr=pair.second;
switch(attr.type){
case1:{
//2.根据Pnnx的Attribute初始化KuiperInferOperator中的Attribute
std::shared_ptrruntime_attribute=std::make_shared();
runtime_attribute->type=RuntimeDataType::kTypeFloat32;
//2.1赋值权重weight(此处的data是std::vector类型)
runtime_attribute->weight_data=attr.data;
runtime_attribute->shape=attr.shape;
runtime_operator->attribute.insert({name,runtime_attribute});
break;
}
default:{
LOG(FATAL)<< "Unknown attribute type";
      }
    }
  }
}

7. 初始化RuntimeOperator的参数(Param)属性

简单来说就是从pnnx::Params去初始化KuiperInfer::Params

conststd::map¶ms=op->params;
if(!params.empty()){
InitGraphParams(params,runtime_operator);
}

KuiperInfer::RuntimeParameter有多个派生类构成, 以此来对应中多种多样的参数, 例如ConvOperator中有std::string类型的参数, padding_mode, 也有像uint32_t类型的kernel_size和padding_size参数, 所以我们需要以多种参数类型去支持他.

换句话说, 一个KuiperInfer::Params, param可以是其中的任意一个派生类, 这里我们利用了多态的特性. KuiperInfer::RuntimeParameter具有多种派生类, 如下分别表示为Int参数和Float参数, 他们都是RuntimeParameter的派生类.

std::mapparams;///算子的参数信息
//用指针来实现多态

structRuntimeParameter{///计算节点中的参数信息
virtual~RuntimeParameter()=default;

explicitRuntimeParameter(RuntimeParameterTypetype=RuntimeParameterType::kParameterUnknown):type(type){

}
RuntimeParameterTypetype=RuntimeParameterType::kParameterUnknown;
};
///int类型的参数
structRuntimeParameterInt:publicRuntimeParameter{
RuntimeParameterInt():RuntimeParameter(RuntimeParameterType::kParameterInt){

}
intvalue=0;
};
///float类型的参数
structRuntimeParameterFloat:publicRuntimeParameter{
RuntimeParameterFloat():RuntimeParameter(RuntimeParameterType::kParameterFloat){

}
floatvalue=0.f;
};

从PNNX::param到RuntimeOperator::param的转换过程.代码链接

voidRuntimeGraph::InitGraphParams(conststd::map¶ms,
conststd::shared_ptr&runtime_operator){
for(constauto&pair:params){
conststd::string&name=pair.first;
constpnnx::Parameter¶meter=pair.second;
constinttype=parameter.type;
//根据PNNX的Parameter去初始化KuiperInfer::RuntimeOperator中的Parameter
switch(type){
caseint(RuntimeParameterType::kParameterUnknown):{
RuntimeParameter*runtime_parameter=newRuntimeParameter;
runtime_operator->params.insert({name,runtime_parameter});
break;
}
//在这应该使用派生类RuntimeParameterBool
caseint(RuntimeParameterType::kParameterBool):{
RuntimeParameterBool*runtime_parameter=newRuntimeParameterBool;
runtime_parameter->value=parameter.b;
runtime_operator->params.insert({name,runtime_parameter});
break;
}
//在这应该使用派生类RuntimeParameterInt
caseint(RuntimeParameterType::kParameterInt):{
RuntimeParameterInt*runtime_parameter=newRuntimeParameterInt;
runtime_parameter->value=parameter.i;
runtime_operator->params.insert({name,runtime_parameter});
break;
}

caseint(RuntimeParameterType::kParameterFloat):{
RuntimeParameterFloat*runtime_parameter=newRuntimeParameterFloat;
runtime_parameter->value=parameter.f;
runtime_operator->params.insert({name,runtime_parameter});
break;
}

caseint(RuntimeParameterType::kParameterString):{
RuntimeParameterString*runtime_parameter=newRuntimeParameterString;
runtime_parameter->value=parameter.s;
runtime_operator->params.insert({name,runtime_parameter});
break;
}

caseint(RuntimeParameterType::kParameterIntArray):{
RuntimeParameterIntArray*runtime_parameter=newRuntimeParameterIntArray;
runtime_parameter->value=parameter.ai;
runtime_operator->params.insert({name,runtime_parameter});
break;
}

caseint(RuntimeParameterType::kParameterFloatArray):{
RuntimeParameterFloatArray*runtime_parameter=newRuntimeParameterFloatArray;
runtime_parameter->value=parameter.af;
runtime_operator->params.insert({name,runtime_parameter});
break;
}
caseint(RuntimeParameterType::kParameterStringArray):{
RuntimeParameterStringArray*runtime_parameter=newRuntimeParameterStringArray;
runtime_parameter->value=parameter.as;
runtime_operator->params.insert({name,runtime_parameter});
break;
}
default:{
LOG(FATAL)<< "Unknown parameter type";
      }
    }
  }
}

8. 初始化成功

将通过如上步骤初始化好的KuiperInfer::RuntimeOperator存放到一个vector中

this->operators_.push_back(runtime_operator);

验证我们的计算图

我们先准备好了如下的一个计算图(准备过程不是本节的重点, 读者直接使用即可), 存放在tmp目录中, 它由两个卷积, 一个Add(expression)以及一个最大池化层组成.

3685b1f6-98fa-11ed-bfe3-dac502259ad0.png

TEST(test_runtime,runtime1){
usingnamespacekuiper_infer;
conststd::string¶m_path="./tmp/test.pnnx.param";
conststd::string&bin_path="./tmp/test.pnnx.bin";
RuntimeGraphgraph(param_path,bin_path);
graph.Init();
constautooperators=graph.operators();
for(constauto&operator_:operators){
LOG(INFO)<< "type: " << operator_->type<< " name: " << operator_->name;
}
}

如上为一个测试函数, Init就是我们刚才分析过的一个函数, 它定义了从PNNX计算图到KuiperInfer计算图的过程.

最后的输出

I202301071133.03383856358test_main.cpp:13]Starttest...
I202301071133.03441156358test_runtime1.cpp:17]type:pnnx.Inputname:pnnx_input_0
I202301071133.03442156358test_runtime1.cpp:17]type:nn.Conv2dname:conv1
I202301071133.03442556358test_runtime1.cpp:17]type:nn.Conv2dname:conv2
I202301071133.03443056358test_runtime1.cpp:17]type:pnnx.Expressionname:pnnx_expr_0
I202301071133.03443556358test_runtime1.cpp:17]type:nn.MaxPool2dname:max
I202301071133.03444056358test_runtime1.cpp:17]type:pnnx.Outputname:pnnx_output_0

可以看出, Init函数最后得到的结果和图1中定义的是一致的. 含有两个Conv层, conv1和conv2, 一个add层Expression以及一个最大池化MaxPool2d层.








审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表德赢Vwin官网 网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 运算符
    +关注

    关注

    0

    文章

    172

    浏览量

    11078
  • float
    +关注

    关注

    0

    文章

    9

    浏览量

    7772
  • pytorch
    +关注

    关注

    2

    文章

    807

    浏览量

    13197

原文标题:自制深度学习推理框架-第六课-构建自己的计算图

文章出处:【微信号:GiantPandaCV,微信公众号:GiantPandaCV】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    如何利用PyTorch API构建CNN?

      很多人对于卷积神经网络(CNN)并不了解,卷积神经网络是一种前馈神经网络,它包括卷积计算并具有很深的结构,卷积神经网络是深度学习的代表性算法之。那么如何利用
    发表于 07-16 18:13

    TVM整体结构,TVM代码的基本构成

    TIR是更接近硬件的表示结构。Relay中IR通过relay::function来描述,function描述了整个结构,是结构的另外
    发表于 01-07 17:21

    一种基于MapReduce的结构聚类算法

    (tril5)(m为图中边的条数),因此很难处理大规模的数据。为了解决SCAN算法的可扩展性问题,提出了一种新颖的基于MapReduce的海量结构聚类算法MRSCAN。具体地,提出
    发表于 12-19 11:05 0次下载
    <b class='flag-5'>一种</b>基于MapReduce的<b class='flag-5'>图</b><b class='flag-5'>结构</b>聚类算法

    教你用PyTorch快速准确地建立神经网络

    动态计算PyTorch被称为“由运行定义的”框架,这意味着计算结构(神经网络体系
    的头像 发表于 02-11 14:33 3268次阅读

    基于PyTorch的深度学习入门教程之PyTorch的安装和配置

    神经网络结构,并且运用各种深度学习算法训练网络参数,进而解决各种任务。 本文从PyTorch环境配置开始。PyTorch一种Python接口的深度学习框架,使用灵活,学习方便。还有其
    的头像 发表于 02-16 15:15 2588次阅读

    基于PyTorch的深度学习入门教程之PyTorch的自动梯度计算

    计算 Part3:使用PyTorch构建个神经网络 Part4:训练个神经网络分类器 Part5:数据并行化 本文是关于Part2的内容
    的头像 发表于 02-16 15:26 2024次阅读

    基于PyTorch的深度学习入门教程之使用PyTorch构建个神经网络

    PyTorch的自动梯度计算 Part3:使用PyTorch构建个神经网络 Part4:训练
    的头像 发表于 02-15 09:40 2097次阅读

    PyTorch教程5.3之前向传播、反向传播和计算

    德赢Vwin官网 网站提供《PyTorch教程5.3之前向传播、反向传播和计算.pdf》资料免费下载
    发表于 06-05 15:36 0次下载
    <b class='flag-5'>PyTorch</b>教程5.3之前向传播、反向传播和<b class='flag-5'>计算</b><b class='flag-5'>图</b>

    pytorch如何构建网络模型

      利用 pytorch构建网络模型有很多种方法,以下简单列出其中的四。  假设构建个网络模型如下:  卷积层--》Relu 层--
    发表于 07-20 11:51 0次下载

    中科曙光打造一种全新的计算体系构建与运营模式—“立体计算

    4月2日,中科曙光“立体计算湖南行”启动仪式在长沙成功举办。面对“加快发展新质生产力”的新要求,中科曙光提出“立体计算”新思路,旨在打造一种全新的计算体系
    的头像 发表于 04-03 09:52 444次阅读
    中科曙光打造<b class='flag-5'>一种</b>全新的<b class='flag-5'>计算</b>体系<b class='flag-5'>构建</b>与运营模式—“立体<b class='flag-5'>计算</b>”

    使用PyTorch构建神经网络

    PyTorch个流行的深度学习框架,它以其简洁的API和强大的灵活性在学术界和工业界得到了广泛应用。在本文中,我们将深入探讨如何使用PyTorch构建神经网络,包括从基础概念到高级
    的头像 发表于 07-02 11:31 702次阅读

    如何使用PyTorch建立网络模型

    PyTorch个基于Python的开源机器学习库,因其易用性、灵活性和强大的动态特性,在深度学习领域得到了广泛应用。本文将从PyTorch
    的头像 发表于 07-02 14:08 404次阅读

    PyTorch如何训练自己的数据集

    PyTorch个广泛使用的深度学习框架,它以其灵活性、易用性和强大的动态特性而闻名。在训练深度学习模型时,数据集是不可或缺的组成部分。然而,很多时候,我们可能需要使用
    的头像 发表于 07-02 14:09 1627次阅读

    PyTorch的特性和使用方法

    使用Python重新写了很多内容,使其更加灵活易用。它不仅是个拥有自动求导功能的深度神经网络框架,还可以看作是个加入了GPU支持的NumPy。PyTorch支持动态
    的头像 发表于 07-02 14:27 545次阅读

    pytorch如何训练自己的数据

    本文将详细介绍如何使用PyTorch框架来训练自己的数据。我们将从数据准备、模型构建、训练过程、评估和测试等方面进行讲解。 环境搭建 首先,我们需要安装PyTorch。可以通过访问
    的头像 发表于 07-11 10:04 519次阅读