模型推理是指使用已经训练好的机器学习或深度学习模型来进行预测或处理新的输入数据。在训练阶段,模型通过学习训练数据的模式和特征来建立模型参数。而在推理阶段,这些参数被用于对新的未见过的数据进行预测或分析。
模型推理可以在各种应用领域中使用,例如图像识别、语音识别、自然语言处理、推荐系统等。根据模型的复杂性和推理的要求,可以选择使用不同的推理平台和框架,包括CPU、GPU、专用硬件加速器,以及针对特定任务优化的推理库和框架(如TensorFlow、PyTorch、ONNX Runtime等)。
模型推理是将训练好的模型应用于新数据的过程,通过模型的计算和预测能力,使机器能够进行智能化的决策、分析和预测。
假设我们有一个目标检测模型,可以接收输入图像并检测其中的目标,并输出每个目标的位置和类别。我们将使用 TensorRT C++ API 来加载预训练的模型,并将其用于推断。
一)导入TensorRT 库和 C++ 标准库
我们需要导入必要的 TensorRT 库和 C++ 标准库:
#include "NvInfer.h"
#include "NvOnnxParser.h"
#include "NvInferPlugin.h"
#include "NvInferRuntimeCommon.h"
#include <iostream>
#include <fstream>
#include <vector>
#include <chrono>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
二)加载预训练的 TensorRT 模型
我们需要加载我们预训练的 TensorRT 模型:
// 创建一个TensorRT的Builder对象
auto builder = nv::infer::createInferBuilder(gLogger);
// 创建一个TensorRT的NetworkDefinition对象
auto network = builder->createNetworkV2(0);
// 创建一个TensorRT的OnnxParser对象
auto parser = nv::onnxParser::createParser(*network, gLogger);
// 从ONNX模型文件中解析模型
parser->parseFromFile(modelFile.c_str(), static_cast<int>(nv::onnxParser::ParserTarget::kTensorRT));
// 配置TensorRT的推断引擎
auto config = builder->createBuilderConfig();
config->setMaxWorkspaceSize(maxWorkspaceSize);
// 使用builder和config对象创建一个TensorRT的ICudaEngine对象
auto engine = builder->buildEngineWithConfig(*network, *config);
modelFile 是我们预训练模型的 ONNX 文件路径,maxWorkspaceSize 是我们允许的最大内存大小,以字节为单位。
三)创建 TensorRT 的执行上下文
创建一个 TensorRT 的 IExecutionContext 对象,我们可以使用它来执行模型推断:
// 创建TensorRT的执行上下文
auto context = engine->createExecutionContext();
四)输入数据执行推理逻辑
我们需要将输入数据准备为 TensorRT 引擎期望的格式。假设我们的模型期望输入大小为[1, 3, 416, 416]的图像,其中416是图像的宽度和高度,3是通道数(RGB颜色通道):
// 准备输入数据
const int BATCH_SIZE = 1;
const int CHANNELS = 3;
const int INPUT_H = 416;
const int INPUT_W = 416;
// 读取输入图像
cv::Mat image = cv::imread(imageFile, cv::IMREAD_COLOR);
// 调整图像大小
cv::resize(image, image, cv::Size(INPUT_W, INPUT_H));
// 转换图像数据类型
cv::Mat floatImage;
image.convertTo(floatImage, CV_32FC3);
// 转换图像格式为NCHW
std::vector<cv::Mat> inputBlobs;
cv::split(floatImage, inputBlobs);
std::vector<float> inputData(BATCH_SIZE * CHANNELS * INPUT_H* INPUT_W);
// Allocate memory for output data
std::vector<float> outputData(BATCH_SIZE * OUTPUT_SIZE);
// Run inference
for (int i = 0; i < NUM_LOOPS; ++i) {
std::cout << "Iteration " << i << std::endl;
// Fill input data
for (int j = 0; j < BATCH_SIZE * INPUT_SIZE; ++j) {
inputData[j] = static_cast<float>(rand()) / RAND_MAX;
}
// Copy input data to device memory
CUDA_CHECK(cudaMemcpy(inputDeviceBuffer, inputData.data(),
BATCH_SIZE * INPUT_SIZE * sizeof(float),
cudaMemcpyHostToDevice));
// Run inference
context->enqueue(BATCH_SIZE, buffers, stream, nullptr);
// Copy output data from device to host memory
CUDA_CHECK(cudaMemcpy(outputData.data(), outputDeviceBuffer,
BATCH_SIZE * OUTPUT_SIZE * sizeof(float),
cudaMemcpyDeviceToHost));
// Print some output data for debugging
for (int j = 0; j < PRINT_COUNT; ++j) {
std::cout << outputData[j] << " ";
}
std::cout << std::endl;
}
// Release resources
context->destroy();
engine->destroy();
runtime->destroy();
CUDA_CHECK(cudaFree(inputDeviceBuffer));
CUDA_CHECK(cudaFree(outputDeviceBuffer));
return 0;
}
整个代码的主要步骤如下:
-
定义了一些常量,包括批处理大小(BATCH_SIZE)、图像通道数(CHANNELS)以及输入图像的高度(INPUT_H)和宽度(INPUT_W)。
-
通过调用
cv::imread
函数读取图像文件,并存储在cv::Mat
对象image
中。图像以彩色方式(cv::IMREAD_COLOR
)进行读取。 -
使用
cv::resize
函数将图像大小调整为输入网络的大小(INPUT_W x INPUT_H)。 -
将图像数据类型转换为
CV_32FC3
,即每个像素点的颜色值由8位整数转换为32位浮点数。转换后的图像存储在floatImage
中。 -
将图像数据按通道分离,并存储在
std::vector<cv::Mat>
对象inputBlobs
中。 -
创建一个
std::vector<float>
对象inputData
,用于存储转换后的输入数据。inputData
的大小为 BATCH_SIZE * CHANNELS * INPUT_H * INPUT_W。 -
创建一个
std::vector<float>
对象outputData
,用于存储输出数据。outputData
的大小为 BATCH_SIZE * OUTPUT_SIZE,其中 OUTPUT_SIZE 是一个未在代码中给出的常量。 -
进行推理的循环。在每次循环中,先填充输入数据
inputData
。在这个示例中,输入数据是随机生成的,通过调用rand()
函数生成一个范围在0到1之间的随机浮点数。接着,将输入数据从主机内存复制到设备内存中,以便在 CUDA 中进行计算。 -
调用
context->enqueue()
方法运行推理。这是一个用于执行推理的函数调用,其中的参数包括批处理大小、输入和输出缓冲区、CUDA 流(stream)以及用于传递附加信息的指针。 -
将输出数据从设备内存复制到主机内存,以便进一步处理和分析。
-
打印一些输出数据进行调试。在这个示例中,输出数据是一个浮点数数组,通过遍历输出数据数组的前 PRINT_COUNT 个元素,并打印每个元素的值。
-
释放使用的资源,包括销毁上下文(context)、引擎(engine)和运行时(runtime),以及释放设备内存。
-
返回0,表示程序执行成功。