1. 从pytorch模型转换为tensorrt模型有几种方法?每种模型转换的步骤是什么?
pytorch – onnx – engine
pytorch – onnx 大体两种方式
使用torch.onnx.export
使用torch2onnx 或 pt2onnx 等第三方库
onnx–>engine转换具有多种实现方式。大概有三种方法:
1.使用TensorRT python API
2.使用TensorRT C++ API
3.使用TensorRT trtexec 可执行文件
pytorch -- wts --engine
pytorch -- wts
使用tensorrtx的gen_wts.py脚本即可。或者自己写一个脚本
wts--engine
使用TensorRT定义网络并导入权重的代码实现可以参考TensorRT-Developer-Guide.pdf 6.4节。
pytorch -- torch script --engine
使用PyTorch的torch.jit.trace或torch.jit.script将PyTorch模型转换为Torch Script模型。
将Torch Script模型保存为 .ptsc 文件。
使用TensorRT的Python API trt.TorchScriptPlugin将Torch Script模型转换为TensorRT模型。
需要注意的是,这种转换方法可能无法完全利用TensorRT的所有功能和优化,因为Torch Script模型相对于原始的PyTorch模型可能会丢失一些信息和结构。因此,在某些情况下,可能需要手动进行一些调整和优化。
此外,这种方法可能不适用于所有PyTorch版本和CUDA版本,需要确保使用的PyTorch版本和CUDA版本与TensorRT兼容。
使用NVIDIA的PyTorch到TensorRT转换工具
NVIDIA提供了一个名为“tensorrt-converter”的工具,它可以自动将PyTorch模型转换为TensorRT模型。该工具通常在NVIDIA的CUDA和TensorRT工具包中提供。
手动构建TensorRT模型
对于一些简单的模型,可能可以直接在TensorRT中手动构建。这种方法需要手动指定每一层的类型和参数,然后通过TensorRT的API构建出模型。
使用插件
对于某些特定的PyTorch版本和CUDA版本,NVIDIA提供了一些插件,这些插件可以自动将PyTorch模型转换为TensorRT模型。
2. 得到tensorrt引擎文件后执行推理的步骤是什么?
第一种方式
1. 创建ILogger对象,用于记录运行时的日志信息。
2. 反序列化CUDA引擎,获取ICudaEngine对象。
3. 创建IExecutionContext对象,通过ICudaEngine对象的
createExecutionContext函数实现。
4. 准备输入数据,将输入数据拷贝到设备内存中。
5. 调用IExecutionContext对象的execute函数,执行推理过程。
6. 从设备内存中获取输出数据。
7. 处理输出数据,完成推理结果的解析或后处理。
8. 释放内存,销毁相应的对象。
示例代码
#include <NvInfer.h>
#include <iostream>
#include <fstream>
#include <vector>
#include <numeric>
#include <cstring>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace nvinfer1;
const string INPUT_VAR_NAME = "input_var_name";
const string OUTPUT_VAR_NAME = "output_var_name";
int main() {
// 1. 创建ILogger对象
ILogger logger;
logger.setSeverity(ILogger::Severity::kINFO);
// 2. 反序列化CUDA引擎
const char* engine_file_path = "engine_file_path";
const void* engine_data = nullptr;
size_t engine_size = 0;
std::ifstream file(engine_file_path, std::ios::in | std::ios::binary);
if (file) {
file.seekg(0, std::ios::end);
engine_size = file.tellg();
file.seekg(0, std::ios::beg);
engine_data = file.readsome(engine_size);
}
ICudaEngine engine;
logger.log(Severity::kINFO, "Deserializing engine...");
engine.deserialize(engine_data, engine_size, &logger);
// 3. 创建IExecutionContext对象
IExecutionContext* context = engine.createExecutionContext();
context->setBatchSize(1);
// 4. 准备输入数据,将输入数据拷贝到设备内存中
const int INPUT_C = 3; // 输入通道数,例如RGB图像为3
const int INPUT_H = 224; // 输入高度
const int INPUT_W = 224; // 输入宽度
const int INPUT_D = 1; // 输入深度,此处为1,表示输入为2D图像而非3D体积数据
const int INPUT_SIZE = INPUT_C * INPUT_H * INPUT_W * INPUT_D;
void* input_ptr = malloc(INPUT_SIZE * sizeof(float)); // 分配输入数据的内存空间
float* input = (float*)input_ptr; // 将指针转换为float指针,以便后续操作
cv::Mat img = cv::imread("image_path", cv::IMREAD_COLOR); // 读取输入图像,此处为示例代码,实际应用中需要根据需求读取图像或视频帧等数据
cv::resize(img, img, cv::Size(INPUT_W, INPUT_H)); // 根据模型输入尺寸调整图像大小,确保与模型输入一致
img = img.mul(255.0); // 将像素值归一化到[0, 1]区间内,与模型训练时的归一化方式保持一致
std::memcpy(input, img.data, INPUT_SIZE * sizeof(float)); // 将图像数据拷贝到输入内存中
const void* bindings[2] = {input_ptr, context->getOutputTensor(OUTPUT_VAR_NAME, 0)->pointer<void>()}; // 设置输入输出张量的指针,此处假设输出只有一个张量
int64_t dims[2] = {1, INPUT_SIZE}; // 设置输入张量的维度,此处假设输入为单个张量,维度为[1, INPUT_SIZE]
context->enqueue(1, bindings, dims, &logger); // 将输入数据送入执行上下文进行推理计算
// 5. 从设备内存中获取输出数据
float* output = (float*)context->getOutputTensor(OUTPUT_VAR_NAME, 0)->pointer<void>(); // 获取输出张量的指针,此处假设输出只有一个张量
// 处理输出数据,完成推理结果的解析或后处理,例如将输出转换为图像形式并保存或进行其他后续处理等。此处为示例代码,实际应用中需要根据模型输出类型和需求进行相应的处理。
cv::Mat output_img = cv::Mat::zeros(INPUT_H, INPUT_W, CV_8UC3); // 创建一个与输入图像大小相同的空图像用于保存输出结果
std::memcpy(output_img.data, output, OUTPUT_SIZE * sizeof(float)); // 将输出数据拷贝到输出
第二种方式
第一阶段 build phase
自己创建一个日志类继承ILogger接口类。然后通过日志对
象创建builder对象。
创建一个网络定义对象
使用onnxparser导入一个模型
创建一个构建配置文件
使用网络定义和配置文件构建并且序列化引擎
第二阶段 反序列化阶段
使用runtime反序列化一个执行计划,也就是反序列化一个
引擎
第三阶段 推理阶段
创建一个执行上下文,一个引擎可有多个执行上下文
指定输入缓存和输出缓存 需要在GPU上分配显存
利用execute_async_v3 函数在CUDA stream上做推理
示例代码如下。在这个示例中,我们假设你已经得到了一个ONNX模型文件,并且你想要在C++环境中使用TensorRT进行推理。
#include <iostream>
#include <NvInfer.h>
#include <NvOnnxParser.h>
#include <fstream>
#include <vector>
// 假设你有一个继承自ILogger的日志类
class Logger : public nvinfer1::ILogger {
public:
void log(nvinfer1::ILogger::Severity severity, const char* msg) override {
// 在此处处理日志信息,例如写入文件、打印到控制台等
// 你可以根据需要添加更多的逻辑
std::cout << msg << std::endl;
}
};
int main() {
// 第一阶段: 构建阶段
Logger logger; // 创建一个日志对象
nvinfer1::IBuilder* builder = nvinfer1::createInferBuilder(logger); // 使用日志对象创建builder对象
nvinfer1::INetworkDefinition* network = builder->createNetwork(); // 创建一个网络定义对象
nvinfer1::IBuilderConfig* config = builder->createBuilderConfig(); // 创建一个构建配置文件
nvinfer1::InputTensor* inputTensor = network->getInput(0); // 获取模型的输入张量
inputTensor->setDimensions(筋输入的shape); // 设置输入张量的维度
config->setFlag(nvinfer1::BuilderFlag::kEXPLICIT_Batch); // 设置需要显式地支持batch维度
builder->setMaxBatchSize(1); // 设置最大批处理大小
builder->setMaxWorkspaceSize(1 << 20); // 设置最大工作空间大小
nvinfer1::ICudaEngine* engine = builder->buildSerializedNetwork(*network, *config); // 使用网络定义和配置文件构建并且序列化引擎
delete network;
delete config;
delete builder;
// 第二阶段: 反序列化阶段
nvinfer1::IRuntime* runtime = nvinfer1::createInferRuntime(logger); // 使用runtime反序列化一个执行计划,也就是反序列化一个引擎
engine = runtime->deserializeCudaEngine(engine->getPlan(), logger); // 反序列化引擎
delete runtime;
// 第三阶段: 推理阶段
nvinfer1::IExecutionContext* context = engine->createExecutionContext(); // 创建一个执行上下文,一个引擎可有多个执行上下文
void* buffers[1] = {0}; // 指定输入缓存和输出缓存,需要在GPU上分配显存
engine->bindLayer(*context, "layer_name", 1, buffers, nullptr, nullptr); // 绑定层到执行上下文,假设你的模型有一个名为"layer_name"的输出层
void* outputBuffer = new float[筋输出的大小]; // 创建输出缓存
context->enqueue(1, &buffers[0], &outputBuffer, nullptr); // 利用execute_async_v3函数在CUDA stream上做推理
// 处理输出结果,例如将输出缓存中的数据转换为图像形式并保存或进行其他后续处理等。这个部分需要根据你的具体需求进行编写。
delete context;
delete engine;
delete[] buffers[0];
delete[] outputBuffer;
return 0;
}