0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

PyTorch構(gòu)建自己一種易用的計算圖結(jié)構(gòu)

jf_pmFSk4VX ? 來源:GiantPandaCV ? 2023-02-01 14:26 ? 次閱讀

PNNX

PNNX項目 PyTorch Neural Network eXchange(PNNX)是PyTorch模型互操作性的開放標(biāo)準.

PNNX為PyTorch提供了一種開源的模型格式, 它定義了與PyTorch相匹配的數(shù)據(jù)流圖和運算操作, 我們的框架在PNNX之上封裝了一層更加易用和簡單的計算圖格式. PyTorch訓(xùn)練好一個模型之后, 然后模型需要轉(zhuǎn)換到PNNX格式, 然后PNNX格式我們再去讀取, 形成計算圖.

PyTorch到我們計算圖?

PNNX幫我做了很多的圖優(yōu)化、算子融合的工作, 所以底層的用它PNNX的話, 我們可以吸收圖優(yōu)化的結(jié)果, 后面推理更快.

但是我們不直接在項目中用PNNX, 因為別人的工作和自己推理框架開發(fā)思路總是有不同的. 所以在這上面封裝, 又快速又好用方便, 符合自己的使用習(xí)慣. PNNX的使用方法, 我們只是去讀取PNNX導(dǎo)出的模型, 然后構(gòu)建自己一種易用的計算圖結(jié)構(gòu).

PNNX的格式定義

PNNX由操作數(shù)operand(運算數(shù))和operator(運算符號), PNNX::Graph用來管理和操作這兩者.

操作數(shù)(operand), 也可以通過操作數(shù)來方向訪問到這個數(shù)字的產(chǎn)生者和使用者Customer

代碼鏈接

Operand

定義鏈接

Operand有以下幾個部分組成:

Producer: 類型是operator, 表示產(chǎn)生了這個操作數(shù)(operand)的運算符(operator). 也就是說這個操作數(shù)(operand)是Producer的輸出.

比如Producer是有個Add, Operand就是對應(yīng)的Add結(jié)果.

Customer:類型是operator, 表示需要這個操作數(shù)是下一個操作的運算符(operator)的輸入. 值得注意的是生產(chǎn)者Producer作為產(chǎn)生這個操作數(shù)的operator只能有一個, 而消費者Customer可以有多個, 消費者將當(dāng)前的操作數(shù)Operand作為輸入.

Name: 類型是std::string, 表示這個操作數(shù)的名稱.

Shape: 類型是std::vector , 用來表示操作數(shù)的大小.

Operator

定義鏈接

operator有以下幾個部分組成:

Inputs: 類型為std::vector, 表示這個運算符計算過程中所需要的輸入操作數(shù)(operand)

Outputs: 類型為std::vector, 表示這個運算符計算過程中得到的輸出操作數(shù)(operand)

Type, Name 類型均為std::string, 分別表示運算符號的類型和名稱

Params, 類型為std::map,用于存放該運算符的所有參數(shù)(例如對應(yīng)Convolution operator的params中將存放stride, padding, kernel size等信息)

Attrs, 類型為std::map, 用于存放運算符號所需要的具體權(quán)重屬性(例如對應(yīng)Convolution operator的attrs中就存放著卷積的權(quán)重和偏移量)

我們對PNNX的封裝

對Operands(運算數(shù))的封裝

structRuntimeOperand{
std::stringname;///操作數(shù)的名稱
std::vectorshapes;///操作數(shù)的形狀
std::vector>>datas;///存儲操作數(shù)
RuntimeDataTypetype=RuntimeDataType::kTypeUnknown;///操作數(shù)的類型,一般是float
};

對Operator(運算符)的封裝

對PNNX::operator的封裝是RuntimeOperator, 下面會講具體的PNNX到KuiperInfer計算圖的轉(zhuǎn)換過程.

///計算圖中的計算節(jié)點
structRuntimeOperator{
~RuntimeOperator();
std::stringname;///運算符號節(jié)點的名稱
std::stringtype;///運算符號節(jié)點的類型
std::shared_ptrlayer;///節(jié)點對應(yīng)的計算Layer

std::vectoroutput_names;///運算符號的輸出節(jié)點名稱
std::shared_ptroutput_operands;///運算符號的輸出操作數(shù)

std::map>input_operands;///運算符的輸入操作數(shù)
std::vector>input_operands_seq;///運算符的輸入操作數(shù),順序排列

std::mapparams;///算子的參數(shù)信息
std::map>attribute;///算子的屬性信息,內(nèi)含權(quán)重信息
};

從PNNX計算圖到KuiperInfer計算圖的過程

本節(jié)代碼鏈接

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計算圖中的運算符, 構(gòu)建KuiperInfer計算圖

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

4. 初始化RuntimeOperator的輸入

初始化RuntimeOperator中的RuntimeOperator.input_operands和RuntimeOperator.input_operands_seq兩個屬性.

通過解析pnnx的計算圖來初始化KuiperInfer RuntimeOperator中的輸入部分. 簡單來說就是從pnnx::inputs轉(zhuǎn)換得到KuiperInfer::inputs

structRuntimeOperator{
///本過程要初始化的兩個屬性
std::map>input_operands;///運算符的輸入操作數(shù)
std::vector>input_operands_seq;///運算符的輸入操作數(shù),順序排列
...
}

從PNNX::Input到KuiperInfer::Input的轉(zhuǎn)換過程, 代碼鏈接

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的操作數(shù)類型(operands),去初始化KuiperInfer中的操作符(RuntimeOperator)的輸入.
for(constpnnx::Operand*input:inputs){
if(!input){
continue;
}
//得到pnnx操作數(shù)對應(yīng)的生產(chǎn)者(類型是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的權(quán)重(Attr)屬性

KuiperInfer::RuntimeAttributes. Attributes中存放的是operator計算時需要的權(quán)重屬性, 例如Convolution Operator中的weights和bias.

//初始化算子中的attribute(權(quán)重)
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.根據(jù)Pnnx的Attribute初始化KuiperInferOperator中的Attribute
std::shared_ptrruntime_attribute=std::make_shared();
runtime_attribute->type=RuntimeDataType::kTypeFloat32;
//2.1賦值權(quán)重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的參數(shù)(Param)屬性

簡單來說就是從pnnx::Params去初始化KuiperInfer::Params

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

KuiperInfer::RuntimeParameter有多個派生類構(gòu)成, 以此來對應(yīng)中多種多樣的參數(shù), 例如ConvOperator中有std::string類型的參數(shù), padding_mode, 也有像uint32_t類型的kernel_size和padding_size參數(shù), 所以我們需要以多種參數(shù)類型去支持他.

換句話說, 一個KuiperInfer::Params, param可以是其中的任意一個派生類, 這里我們利用了多態(tài)的特性. KuiperInfer::RuntimeParameter具有多種派生類, 如下分別表示為Int參數(shù)和Float參數(shù), 他們都是RuntimeParameter的派生類.

std::mapparams;///算子的參數(shù)信息
//用指針來實現(xiàn)多態(tài)

structRuntimeParameter{///計算節(jié)點中的參數(shù)信息
virtual~RuntimeParameter()=default;

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

}
RuntimeParameterTypetype=RuntimeParameterType::kParameterUnknown;
};
///int類型的參數(shù)
structRuntimeParameterInt:publicRuntimeParameter{
RuntimeParameterInt():RuntimeParameter(RuntimeParameterType::kParameterInt){

}
intvalue=0;
};
///float類型的參數(shù)
structRuntimeParameterFloat:publicRuntimeParameter{
RuntimeParameterFloat():RuntimeParameter(RuntimeParameterType::kParameterFloat){

}
floatvalue=0.f;
};

從PNNX::param到RuntimeOperator::param的轉(zhuǎn)換過程.代碼鏈接

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;
//根據(jù)PNNX的Parameter去初始化KuiperInfer::RuntimeOperator中的Parameter
switch(type){
caseint(RuntimeParameterType::kParameterUnknown):{
RuntimeParameter*runtime_parameter=newRuntimeParameter;
runtime_operator->params.insert({name,runtime_parameter});
break;
}
//在這應(yīng)該使用派生類RuntimeParameterBool
caseint(RuntimeParameterType::kParameterBool):{
RuntimeParameterBool*runtime_parameter=newRuntimeParameterBool;
runtime_parameter->value=parameter.b;
runtime_operator->params.insert({name,runtime_parameter});
break;
}
//在這應(yīng)該使用派生類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);

驗證我們的計算圖

我們先準備好了如下的一個計算圖(準備過程不是本節(jié)的重點, 讀者直接使用即可), 存放在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;
}
}

如上為一個測試函數(shù), Init就是我們剛才分析過的一個函數(shù), 它定義了從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函數(shù)最后得到的結(jié)果和圖1中定義的是一致的. 含有兩個Conv層, conv1和conv2, 一個add層Expression以及一個最大池化MaxPool2d層.








審核編輯:劉清

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 運算符
    +關(guān)注

    關(guān)注

    0

    文章

    169

    瀏覽量

    11038
  • float
    +關(guān)注

    關(guān)注

    0

    文章

    8

    瀏覽量

    7757
  • pytorch
    +關(guān)注

    關(guān)注

    2

    文章

    795

    瀏覽量

    13091

原文標(biāo)題:自制深度學(xué)習(xí)推理框架-第六課-構(gòu)建自己的計算圖

文章出處:【微信號:GiantPandaCV,微信公眾號:GiantPandaCV】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    如何利用PyTorch API構(gòu)建CNN?

      很多人對于卷積神經(jīng)網(wǎng)絡(luò)(CNN)并不了解,卷積神經(jīng)網(wǎng)絡(luò)是一種前饋神經(jīng)網(wǎng)絡(luò),它包括卷積計算并具有很深的結(jié)構(gòu),卷積神經(jīng)網(wǎng)絡(luò)是深度學(xué)習(xí)的代表性算法之。那么如何利用
    發(fā)表于 07-16 18:13

    TVM整體結(jié)構(gòu),TVM代碼的基本構(gòu)成

    TIR是更接近硬件的表示結(jié)構(gòu)。Relay中IR通過relay::function來描述,function描述了整個結(jié)構(gòu),是結(jié)構(gòu)的另外
    發(fā)表于 01-07 17:21

    一種基于MapReduce的結(jié)構(gòu)聚類算法

    (tril5)(m為圖中邊的條數(shù)),因此很難處理大規(guī)模的數(shù)據(jù)。為了解決SCAN算法的可擴展性問題,提出了一種新穎的基于MapReduce的海量結(jié)構(gòu)聚類算法MRSCAN。具體地,提出
    發(fā)表于 12-19 11:05 ?0次下載
    <b class='flag-5'>一種</b>基于MapReduce的<b class='flag-5'>圖</b><b class='flag-5'>結(jié)構(gòu)</b>聚類算法

    教你用PyTorch快速準確地建立神經(jīng)網(wǎng)絡(luò)

    動態(tài)計算PyTorch被稱為“由運行定義的”框架,這意味著計算結(jié)構(gòu)(神經(jīng)網(wǎng)絡(luò)體系
    的頭像 發(fā)表于 02-11 14:33 ?3230次閱讀

    基于PyTorch的深度學(xué)習(xí)入門教程之PyTorch的安裝和配置

    神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu),并且運用各種深度學(xué)習(xí)算法訓(xùn)練網(wǎng)絡(luò)參數(shù),進而解決各種任務(wù)。 本文從PyTorch環(huán)境配置開始。PyTorch一種Python接口的深度學(xué)習(xí)框架,使用靈活,學(xué)習(xí)方便。還有其
    的頭像 發(fā)表于 02-16 15:15 ?2524次閱讀

    基于PyTorch的深度學(xué)習(xí)入門教程之PyTorch的自動梯度計算

    計算 Part3:使用PyTorch構(gòu)建個神經(jīng)網(wǎng)絡(luò) Part4:訓(xùn)練個神經(jīng)網(wǎng)絡(luò)分類器 Part5:數(shù)據(jù)并行化 本文是關(guān)于Part2的內(nèi)容
    的頭像 發(fā)表于 02-16 15:26 ?1968次閱讀

    基于PyTorch的深度學(xué)習(xí)入門教程之使用PyTorch構(gòu)建個神經(jīng)網(wǎng)絡(luò)

    PyTorch的自動梯度計算 Part3:使用PyTorch構(gòu)建個神經(jīng)網(wǎng)絡(luò) Part4:訓(xùn)練
    的頭像 發(fā)表于 02-15 09:40 ?2052次閱讀

    PyTorch教程5.3之前向傳播、反向傳播和計算

    電子發(fā)燒友網(wǎng)站提供《PyTorch教程5.3之前向傳播、反向傳播和計算.pdf》資料免費下載
    發(fā)表于 06-05 15:36 ?0次下載
    <b class='flag-5'>PyTorch</b>教程5.3之前向傳播、反向傳播和<b class='flag-5'>計算</b><b class='flag-5'>圖</b>

    pytorch如何構(gòu)建網(wǎng)絡(luò)模型

      利用 pytorch構(gòu)建網(wǎng)絡(luò)模型有很多種方法,以下簡單列出其中的四。  假設(shè)構(gòu)建個網(wǎng)絡(luò)模型如下:  卷積層--》Relu 層--
    發(fā)表于 07-20 11:51 ?0次下載

    中科曙光打造一種全新的計算體系構(gòu)建與運營模式—“立體計算

    4月2日,中科曙光“立體計算湖南行”啟動儀式在長沙成功舉辦。面對“加快發(fā)展新質(zhì)生產(chǎn)力”的新要求,中科曙光提出“立體計算”新思路,旨在打造一種全新的計算體系
    的頭像 發(fā)表于 04-03 09:52 ?392次閱讀
    中科曙光打造<b class='flag-5'>一種</b>全新的<b class='flag-5'>計算</b>體系<b class='flag-5'>構(gòu)建</b>與運營模式—“立體<b class='flag-5'>計算</b>”

    使用PyTorch構(gòu)建神經(jīng)網(wǎng)絡(luò)

    PyTorch個流行的深度學(xué)習(xí)框架,它以其簡潔的API和強大的靈活性在學(xué)術(shù)界和工業(yè)界得到了廣泛應(yīng)用。在本文中,我們將深入探討如何使用PyTorch構(gòu)建神經(jīng)網(wǎng)絡(luò),包括從基礎(chǔ)概念到高級
    的頭像 發(fā)表于 07-02 11:31 ?581次閱讀

    如何使用PyTorch建立網(wǎng)絡(luò)模型

    PyTorch個基于Python的開源機器學(xué)習(xí)庫,因其易用性、靈活性和強大的動態(tài)特性,在深度學(xué)習(xí)領(lǐng)域得到了廣泛應(yīng)用。本文將從PyTorch
    的頭像 發(fā)表于 07-02 14:08 ?319次閱讀

    PyTorch如何訓(xùn)練自己的數(shù)據(jù)集

    PyTorch個廣泛使用的深度學(xué)習(xí)框架,它以其靈活性、易用性和強大的動態(tài)特性而聞名。在訓(xùn)練深度學(xué)習(xí)模型時,數(shù)據(jù)集是不可或缺的組成部分。然而,很多時候,我們可能需要使用
    的頭像 發(fā)表于 07-02 14:09 ?1025次閱讀

    PyTorch的特性和使用方法

    使用Python重新寫了很多內(nèi)容,使其更加靈活易用。它不僅是個擁有自動求導(dǎo)功能的深度神經(jīng)網(wǎng)絡(luò)框架,還可以看作是個加入了GPU支持的NumPy。PyTorch支持動態(tài)
    的頭像 發(fā)表于 07-02 14:27 ?423次閱讀

    pytorch如何訓(xùn)練自己的數(shù)據(jù)

    本文將詳細介紹如何使用PyTorch框架來訓(xùn)練自己的數(shù)據(jù)。我們將從數(shù)據(jù)準備、模型構(gòu)建、訓(xùn)練過程、評估和測試等方面進行講解。 環(huán)境搭建 首先,我們需要安裝PyTorch??梢酝ㄟ^訪問
    的頭像 發(fā)表于 07-11 10:04 ?385次閱讀