イベントアドレス:卒業シーズン・テクノロジーへの攻撃er
Pytorch は onnx モデルをエクスポートし、C++ を TensorRT に変換し、推論を実装します。
この記事は学習メモです。参考文献と相違がある場合は、黄色それをマークしてください。
主な参考文献:
1. Pytorch は onnx モデルをエクスポートし、C++ はそれを TensorRT に変換し、推論プロセスを実装します。
2. Onnxruntime のインストールと使用 (実際にはいくつかの問題が見つかりました)
3. TensorRT_Test
1.Pytorch エクスポート onnx モデル
-
新しいexport_onnx.pyファイルを作成します。完全な内容は次のとおりです。ResNet50_wSoftmax カスタム モデルは、公式のオリジナルに基づいており、softmax 操作が追加されています。
-
必要な後処理をモデルに追加して一緒にエクスポートすると、次の 2 つの利点があります: 1 )外部で後処理を行わずに
、エンドツーエンドの onnx/tensorrt モデルを直接取得できます。onnx モデルから tensorrt モデルへの変換プロセス中、tensorrt は特定の Nvidia GPU のモデルに対して推論の最適化後処理を onnx モデルにマージします。これにより、一部のオペレーター操作を tensorrt に変換できる可能性があります。 . 最適化されます。
# export_onnx.py
import torch
import torchvision.models as models
import cv2
import numpy as np
class ResNet50_wSoftmax(torch.nn.Module):
# 将softmax后处理合并到模型中,一起导出为onnx
def __init__(self):
super().__init__()
self.base_model = models.resnet50(pretrained=True)
self.softmax = torch.nn.Softmax(dim=1)
def forward(self, x):
y = self.base_model(x)
prob = self.softmax(y)
return prob
def preprocessing(img):
# 预处理:BGR->RGB、归一化/除均值减标准差
IMAGENET_MEAN = [0.485, 0.456, 0.406]
IMAGENET_STD = [0.229, 0.224, 0.225]
img = img[:, :, ::-1]
img = cv2.resize(img, (224, 224))
img = img / 255.0
img = (img - IMAGENET_MEAN) / IMAGENET_STD
img = img.transpose(2, 0, 1).astype(np.float32)
tensor_img = torch.from_numpy(img)[None] # 此处增加一维, 从[3,224,224] 到 [1, 3,224,224]
return tensor_img
if __name__ == '__main__':
# model = models.resnet50(pretrained=True)
image_path = 'test.jpg'
img = cv2.imread(image_path)
tensor_img = preprocessing(img)
model = ResNet50_wSoftmax() # 将后处理添加到模型中
model.eval()
pred = model(tensor_img)[0]
max_idx = torch.argmax(pred)
print(f"test_image: {image_path}, max_idx: {max_idx}, max_logit: {pred[max_idx].item()}")
dummpy_input = torch.zeros(1, 3, 224, 224) # onnx的导出需要指定一个输入,这里直接用上面的tenosr_img也可
torch.onnx.export(
model, dummpy_input, 'resnet50_wSoftmax.onnx',
input_names=['image'],
output_names=['predict'],
opset_version=11,
dynamic_axes={'image': {0: 'batch'}, 'predict': {0: 'batch'}} # 注意这里指定batchsize是动态可变的
)
- スクリプトを実行します。
python export_onnx.py
注: 操作の前に、test.jpg という名前の画像を準備する必要があります。インターネットから画像をダウンロードするだけで、次のような同様の出力結果が表示されます。
Downloading: "https://download.pytorch.org/models/resnet50-0676ba61.pth" to /home/cui/.cache/torch/hub/checkpoints/resnet50-0676ba61.pth
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 97.8M/97.8M [07:36<00:00, 225kB/s]
test_image: test.jpg, max_idx: 285, max_logit: 0.5382498502731323
2. onnxruntime 推論テスト
私たちはただ手に入れますresnet50_wSoftmax.onnx、onnxruntime を使用して推論テストを実行し、結果が同じかどうかを確認します。
ここで新しいものを作成します。onnxruntime_test.pyファイルの完全な内容は次のとおりです。このファイルでは、export_onnx.py の前処理操作が再利用されていますが、元の関数の出力結果は torch であるため、これは次のようにする必要があります。numpy 型に変換する。
# onnxruntime_test.py
import onnxruntime as ort #若缺少,使用pip install onnxruntime-gpu -i https://pypi.tuna.tsinghua.edu.cn/simple高速安装
import numpy as np
import cv2
from export_onnx import preprocessing
image_path = 'test.jpg'
ort_session = ort.InferenceSession("resnet50_wSoftmax.onnx", providers=['TensorrtExecutionProvider', 'CUDAExecutionProvider', 'CPUExecutionProvider']) # 创建一个推理session
img = cv2.imread(image_path)
input_img = preprocessing(img)[None]
input_img = input_img.numpy() ## 新添加
pred = ort_session.run(None, { 'image' : input_img } )[0][0]
max_idx = np.argmax(pred)
print(f"test_image: {image_path}, max_idx: {max_idx}, probability: {pred[max_idx]}")
埋め込む:
python onnxruntime_test.py
結果は以下の通りで、pytorch モデルの推論と基本的に一致しており、変換に問題がないことが証明されています。
test_image: test.jpg, max_idx: 285, probability: 0.5382504463195801
注意:
1) このファイルort.InferenceSession のパラメータリファレンスから少し変更があり、本家サイトのスクリプトを直接実行すると以下のエラーが発生します。
解決策の参照: [ValueError: この ORT ビルドには'TensorrtExecutionProvider'、'CUDAExecutionProvider'、'CPUExecutionP があります
ValueError: This ORT build has ['TensorrtExecutionProvider', 'CUDAExecutionProvider', 'CPUExecutionProvider'] enabled. Since ORT 1.9, you are required to explicitly set the providers parameter when instantiating InferenceSession. For example, onnxruntime.InferenceSession(..., providers=['TensorrtExecutionProvider', 'CUDAExecutionProvider', 'CPUExecutionProvider'], ...)
2) RuntimeError: 入力は辞書のリストまたは単一の numpy 配列である必要があります。元のテキストで直接推論を実行すると、「画像」が表示されます。解決策の参照: RuntimeError: 入力は辞書のリストまたは単一の numpy 配列である必要があり
ます。入力用 '画像
3) 元のテキストでエクスポートされた onnx モデルは int64 重みであり、TensorRT は 64 ビットをサポートしていませんが、その後の使用には影響しません。現在、32 ビットのみがサポートされており、次の変換時に関連する型の警告が報告されます。
Your ONNX model has been generated with INT64 weights, while TensorRT does not natively support INT64. Attempting to cast down to INT32
3. onnx モデルを tensorrt モデルに変換します
1. 環境のインストール
CUDA10.2+Cudnn8.4.1+TensorRT7.1.3.4+Onnx-tensorrt7.1、詳細なインストールについては、環境構成インストールの記事を参照してください。ここでは、特に拡張する必要はありません。たとえば、次のとおりです。
- LinuxにcuDNN8.4をインストールする
- Linux に CUDA+cuDNN をインストールする
- Ubuntu18配置CUDA10.2 cudnn8.0.1 TensorRT7.1.3.4
- Onnx-tensorrt7.1 バージョン
- Onnx-tensorrt のインストール中に発生する可能性のある問題
注:バージョン間に対応関係がなければなりません。そうでないと、不可解なエラーが発生します。すべてのインストールが完了したら、次のコマンドを使用してバージョンを確認します。
nvcc -V # 查看cuda版本,10.2
cat /usr/local/cuda-10.2/include/cudnn_version.h | grep CUDNN_MAJOR -A 2 # 查看cudnn8.4版本,与之前版本稍有不同,可参考第一个链接安装cudnn
find / -name NvInferVersion.h # 查看tensorRT版本号
2. フォルダーの作成
主にヘッダー ファイル、ロガー クラス、build_model 関数などを含む build_model.cc の新しい C++ ファイルを作成します。
- Logger クラス: tensorrt モデルの構築プロセスでエラーまたは警告を出力します。指定された重大度(severity)に従って、情報を出力します。
- build_model 関数: onnx を使用してビルド、エクスポートするエンジン型式(trt モデルと同等、どちらもシリアル化されたバイナリ ファイル)
2.1 ファイルの完全な内容は次のとおりです。
// tensorrt相关
#include <NvInfer.h>
#include <NvInferRuntime.h>
#include <NvInferRuntimeCommon.h> //新添加,否则找不到nvinfer1::AsciiChar
// onnx解析器相关
#include <NvOnnxParser.h> // 与原文不同,onnx-tensorrt build后,sudo make install
// cuda_runtime相关
#include <cuda_runtime.h>
// 常用头文件
#include <math.h>
#include <stdio.h>
#include <unistd.h>
#include <chrono>
#include <fstream>
#include <functional>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include <dirent.h>
// opencv
#include <opencv2/opencv.hpp>
inline const char* severity_string(nvinfer1::ILogger::Severity t) {
switch (t) {
case nvinfer1::ILogger::Severity::kINTERNAL_ERROR:
return "internal_error";
case nvinfer1::ILogger::Severity::kERROR:
return "error";
case nvinfer1::ILogger::Severity::kWARNING:
return "warning";
case nvinfer1::ILogger::Severity::kINFO:
return "info";
case nvinfer1::ILogger::Severity::kVERBOSE:
return "verbose";
default:
return "unknown";
}
}
class TRTLogger : public nvinfer1::ILogger {
public:
virtual void log(Severity severity, const char* msg) noexcept override {
if (severity <= Severity::kWARNING) {
if (severity == Severity::kWARNING)
printf("\033[33m%s: %s\033[0m\n", severity_string(severity), msg);
else if (severity == Severity::kERROR)
printf("\031[33m%s: %s\033[0m\n", severity_string(severity), msg);
else
printf("%s: %s\n", severity_string(severity), msg);
}
}
};
bool build_model() {
if (access("resnet50.engine", 0) == 0) {
printf("resnet50.engine already exists.\n");
return true;
}
TRTLogger logger;
// 下面的builder, config, network是基本需要的组件
// 形象的理解是你需要一个builder去build这个网络,网络自身有结构,这个结构可以有不同的配置
nvinfer1::IBuilder* builder = nvinfer1::createInferBuilder(logger);
// 创建一个构建配置,指定TensorRT应该如何优化模型,tensorRT生成的模型只能在特定配置下运行
nvinfer1::IBuilderConfig* config = builder->createBuilderConfig();
// 创建网络定义,其中createNetworkV2(1)表示采用显性batch
// size,新版tensorRT(>=7.0)时,不建议采用0非显性batch size
nvinfer1::INetworkDefinition* network = builder->createNetworkV2(1);
// onnx parser解析器来解析onnx模型
auto parser = nvonnxparser::createParser(*network, logger);
if (!parser->parseFromFile("../resnet50_wSoftmax.onnx", 1)) {
printf("Failed to parse resnet50_wSoftmax.onnx.\n");
return false;
}
// 设置工作区大小
printf("Workspace Size = %.2f MB\n", (1 << 28) / 1024.0f / 1024.0f);
config->setMaxWorkspaceSize(1 << 28);
// 需要通过profile来使得batchsize时动态可变的,这与我们之前导出onnx指定的动态batchsize是对应的
int maxBatchSize = 10;
auto profile = builder->createOptimizationProfile();
auto input_tensor = network->getInput(0);
auto input_dims = input_tensor->getDimensions();
// 设置batchsize的最大/最小/最优值
input_dims.d[0] = 1;
profile->setDimensions(input_tensor->getName(),
nvinfer1::OptProfileSelector::kMIN, input_dims);
profile->setDimensions(input_tensor->getName(),
nvinfer1::OptProfileSelector::kOPT, input_dims);
input_dims.d[0] = maxBatchSize;
profile->setDimensions(input_tensor->getName(),
nvinfer1::OptProfileSelector::kMAX, input_dims);
config->addOptimizationProfile(profile);
// 开始构建tensorrt模型engine
nvinfer1::ICudaEngine* engine =
builder->buildEngineWithConfig(*network, *config);
if (engine == nullptr) {
printf("Build engine failed.\n");
return false;
}
// 将构建好的tensorrt模型engine反序列化(保存成文件)
nvinfer1::IHostMemory* model_data = engine->serialize();
FILE* f = fopen("resnet50.engine", "wb");
fwrite(model_data->data(), 1, model_data->size(), f);
fclose(f);
// 逆序destory掉指针
model_data->destroy();
engine->destroy();
network->destroy();
config->destroy();
builder->destroy();
printf("Build Done.\n");
return true;
}
int main() {
if (!build_model()) {
printf("Couldn't build engine!\n");
}
return 0;
}
2.2 Cmakelists.txt ファイル
完全なプロジェクト リファレンス: TensorRT_Test
2.3 コンパイルと実行
mkdir build && cd build
cmake ..
make -j8
./build_model
2.4 結果
以下の状況が発生し、生成されましたrsenet50.エンジンファイルの変換は成功しました。
warning: [TRT]/home/cui/workspace/tools/onnx-tensorrt/onnx2trt_utils.cpp:220: Your ONNX model has been generated with INT64 weights, while TensorRT does not natively support INT64. Attempting to cast down to INT32.
Workspace Size = 256.00 MB
Build Done.
2.5 考えられる問題
Q1 : エラー: 'nvinfer1::AsciiChar' が宣言されていません
解決策: tensorRT バージョンの問題、const char* に変更します。
Q2 : エラー: ファイル resnet50_wSoftmax.onnx を開けませんでした。
解決策: build_model.cc ファイルの 78 行目、resnet50_wSoftmax.onnx モデル ファイルの対応するパスを変更します。
4. TensorRT モデル推論テスト
目的: 以前の Pytorch モデルと onnx モデルの推論結果が一致しているかどうかを検証するため。
- ファイルの完全な内容については、プロジェクトを参照してください。モデル.infer.cc元のテキストからわずかに変更を加えたドキュメント。増加したチェックランタイム達成:
#ifndef checkRuntime
#define checkRuntime(callstr)\
{\
cudaError_t error_code = callstr;\
if (error_code != cudaSuccess) {\
std::cerr << "CUDA error " << error_code << " at " << __FILE__ << ":" << __LINE__ << std::endl;\
assert(0);\
}\
}
#endif // checkRuntime
プロジェクトアドレス: TensorRT_Test
- コンパイルして実行する
mkdir build && cd build
cmake ..
make -j8
./model_infer
- 結果
test_image: ../test.jpg, max_idx: 285, probability: 0.538250(learning3d)
- 発生する可能性がある問題
Q1 : 共有ライブラリのロード中にエラーが発生しました: libcudnn.so.8: 共有オブジェクト ファイルを開けません: そのようなファイルまたはディレクトリはありません
解決策: cuda インストール環境が見つかりませんでした。conda によって構成された特定の環境に入ることができます。 , in 適切なアクションを実行してください。または、CUDAのインストール環境を参照し、環境変数に設定します。