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)<

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)<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)<

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)<

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<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层.








审核编辑:刘清

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

    关注

    0

    文章

    173

    浏览量

    11574
  • float
    +关注

    关注

    0

    文章

    9

    浏览量

    7918
  • pytorch
    +关注

    关注

    2

    文章

    810

    浏览量

    14113

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

文章出处:【微信号: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 ?3561次阅读

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

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

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

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

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

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

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

    电子发烧友网站提供《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 ?786次阅读
    中科曙光打造<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 ?1150次阅读

    如何使用PyTorch建立网络模型

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

    PyTorch如何训练自己的数据集

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

    PyTorch的特性和使用方法

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

    pytorch如何训练自己的数据

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