05 感知 3D目標檢測 模型導(dǎo)出和部署(基礎(chǔ)知識)
1. 模型導(dǎo)出基礎(chǔ)介紹
- torch.onnx.export中需要的模型實際上是一個torch.jit.ScriptModule。而要把普通 PyTorch 模型轉(zhuǎn)一個這樣的 TorchScript 模型,有跟蹤(trace)和記錄(script)兩種導(dǎo)出計算圖的方法。如果給torch.onnx.export傳入了一個普通 PyTorch 模型(torch.nn.Module),那么這個模型會默認使用跟蹤的方法導(dǎo)出。
- 跟蹤法只能通過實際運行一遍模型的方法導(dǎo)出模型的靜態(tài)圖,即無法識別出模型中的控制流(如循環(huán));記錄法則能通過解析模型來正確記錄所有的控制流。
2. 是否有些pytorch算子在onnx中不支持?如何在pytorch支持更多onnx算子?
Ref 1. 模型部署入門教程(四):在 PyTorch 中支持更多 ONNX 算子: https://zhuanlan.zhihu.com/p/513387413
1. 在最簡單的情況下,我們只要把 PyTorch 算子的輸入用g.op()一一對應(yīng)到 ONNX 算子上即可,并把g.op()的返回值作為符號函數(shù)的返回值。在情況更復(fù)雜時,我們轉(zhuǎn)換一個 PyTorch 算子可能要新建若干個 ONNX 算子。
- 為算子添加符號函數(shù)一般要經(jīng)過以下幾步:1. 獲取原算子的前向推理接口;2. 獲取目標 ONNX 算子的定義;3. 編寫符號函數(shù)并綁定。
2. torch.autograd.Function 能完成算子實現(xiàn)和算子調(diào)用的隔離。不管算子是怎么實現(xiàn)的,它封裝后的使用體驗以及 ONNX 導(dǎo)出方法會和原生的 PyTorch 算子一樣。這是我們比較推薦的為算子添加 ONNX 支持的方法。
- 為torch添加c++擴展
"""
這里的 my_lib 是我們未來要在 Python 里導(dǎo)入的模塊名。雙引號中的 my_add 是 Python 調(diào)用接口的名稱
"""
// my_add.cpp
#include <torch/torch.h>
torch::Tensor my_add(torch::Tensor a, torch::Tensor b)
{
return 2 * a + b;
}
PYBIND11_MODULE(my_lib, m)
{
m.def("my_add", my_add);
}
- 編寫如下的 Python 代碼并命名為 "setup.py",來編譯剛剛的 C++ 文件
from setuptools import setup
from torch.utils import cpp_extension
setup(name='my_add',
ext_modules=[cpp_extension.CppExtension('my_lib', ['my_add.cpp'])],
cmdclass={'build_ext': cpp_extension.BuildExtension})
- 直接用 Python 接口調(diào)用 C++ 函數(shù)不太“美觀”,一種比較優(yōu)雅的做法是把這個調(diào)用接口封裝起來。這里我們用 torch.autograd.Function 來封裝算子的底層調(diào)用
import torch
import my_lib
class MyAddFunction(torch.autograd.Function):
@staticmethod
def forward(ctx, a, b):
return my_lib.my_add(a, b)
@staticmethod
def symbolic(g, a, b):
two = g.op("Constant", value_t=torch.tensor([2]))
a = g.op('Mul', a, two)
return g.op('Add', a, b)
- 在 forward 函數(shù)中,我們用 my_lib.my_add(a, b) 就可以調(diào)用之前寫的C++函數(shù)了。這里 my_lib 是庫名,my_add 是函數(shù)名,這兩個名字是在前面C++的 PYBIND11_MODULE 中定義的。
- 在 ONNX 中,我們需要把新建常量當成一個算子來看待,盡管這個算子并不會以節(jié)點的形式出現(xiàn)在 ONNX 模型的可視化結(jié)果里。
補充:
1. 符號函數(shù) symbolic ?
- 符號函數(shù),可以看成是 PyTorch 算子類的一個靜態(tài)方法。在把 PyTorch 模型轉(zhuǎn)換成 ONNX 模型時,各個 PyTorch 算子的符號函數(shù)會被依次調(diào)用,以完成 PyTorch 算子到 ONNX 算子的轉(zhuǎn)換。第一個參數(shù)就固定叫 g,它表示和計算圖相關(guān)的內(nèi)容;后面的每個參數(shù)都表示算子的輸入,需要和算子的前向推理接口的輸入相同。
- g 有一個方法 op。在把 PyTorch 算子轉(zhuǎn)換成 ONNX 算子時,需要在符號函數(shù)中調(diào)用此方法來為最終的計算圖添加一個 ONNX 算子。其中,第一個參數(shù)是算子名稱。如果該算子是普通的 ONNX 算子,只需要把它在 ONNX 官方文檔里的名稱填進去即可。
def symbolic(g: torch._C.Graph, input_0: torch._C.Value, input_1: torch._C.Value, ...):
pass
def op(name: str, input_0: torch._C.Value, input_1: torch._C.Value, ...) :
pass
2. torch.autograd.Function介紹?
- Function 類本身表示 PyTorch 的一個可導(dǎo)函數(shù),只要為其定義了前向推理和反向傳播的實現(xiàn),我們就可以把它當成一個普通 PyTorch 函數(shù)來使用。
- PyTorch 會自動調(diào)度該函數(shù),合適地執(zhí)行前向和反向計算。對模型部署來說,F(xiàn)unction 類有一個很好的性質(zhì):如果它定義了 symbolic 靜態(tài)方法,該 Function 在執(zhí)行 torch.onnx.export() 時就可以根據(jù) symbolic 中定義的規(guī)則轉(zhuǎn)換成 ONNX 算子。這個 symbolic 就是前面提到的符號函數(shù),只是它的名稱必須是 symbolic 而已。
- apply是torch.autograd.Function 的一個方法,這個方法完成了 Function 在前向推理或者反向傳播時的調(diào)度。我們在使用 Function 的派生類做推理時,不應(yīng)該顯式地調(diào)用 forward(),而應(yīng)該調(diào)用其 apply 方法。
3. torch.autograd.Function backward函數(shù)如何定義?
3. 關(guān)于onnx,pytorch和onnx精度對齊
Ref 1. 模型部署入門教程(五):ONNX 模型的修改與調(diào)試: https://zhuanlan.zhihu.com/p/516920606
Ref 2. 模型部署入門教程(六):實現(xiàn) PyTorch-ONNX 精度對齊工具: https://zhuanlan.zhihu.com/p/543973749 待細讀
ONNX 在底層是用 Protobuf 定義的。Protobuf,全稱 Protocol Buffer,是 Google 提出的一套表示和序列化數(shù)據(jù)的機制。使用 Protobuf 時,用戶需要先寫一份數(shù)據(jù)定義文件,再根據(jù)這份定義文件把數(shù)據(jù)存儲進一份二進制文件。可以說,數(shù)據(jù)定義文件就是數(shù)據(jù)類,二進制文件就是數(shù)據(jù)類的實例。
4. onnx轉(zhuǎn)TensorRT,遇到不能轉(zhuǎn)換的算子,如何自定義實現(xiàn)轉(zhuǎn)換?
在具體的生產(chǎn)環(huán)境中,ONNX 模型常常需要被轉(zhuǎn)換成能被具體推理后端使用的模型格式。
補充:
1. CUDA vs CuDNN vs TensorRT,英偉達這些庫的關(guān)系?
在訓(xùn)練神經(jīng)網(wǎng)絡(luò)時,框架如TensorFlow或PyTorch會調(diào)用cuDNN中的函數(shù),而這些函數(shù)又依賴于CUDA的底層接口。所以cuDNN實際上是建立在CUDA之上的,專門為深度學(xué)習(xí)優(yōu)化的庫,兩者協(xié)同工作來加速計算。CUDA是基礎(chǔ),cuDNN加速訓(xùn)練中的特定操作,TensorRT優(yōu)化推理階段的性能。

浙公網(wǎng)安備 33010602011771號