文章目录
- 前言:TensorRT简介
- 0.1、TensorRT算法流程
- 0.2、TensorRT主要优化技术
- 一、TensorRT配置
- 1.1、TensorRT环境配置
- 1.1.1、CUDA安装
- 1.1.2、TensorRT下载
- 1.1.3、TensorRT CUDA配置
- 1.1.4、TensorRT配置
- 1.1.4.1、TensorRT python配置
- 1.1.4.2、TensorRT C++配置(Visual Studio)
- 二、模型转换(.h5 / .pt / .onnx / .engine)
- 2.0、百度飞桨:.pdmodel(生成模型 + 保存模型 + 加载模型)
- 2.1、TensorFlow - 模型转换(.h5 转 .onnx)
- 2.1.1、文件生成(.h5)
- 2.1.2、模型转换(加载模型.h5 + 转换为.onnx)
- 2.2、Torch - 模型转换(.pt 转 .onnx)
- 2.2.1、模型转换(加载模型.pt + 转换为.onnx)
- 2.2.2、模型转换(生成模型 + 保存为.onnx)
- 2.3、TensorRT - 模型转换(.onnx 转 .engine)
- 2.3.1、C++实现
- 2.3.2、cmd命令行实现
- 2.3.3、python实现(执行命令行指令)
- 备注:trtexec参数和用法
- 三、CUDA C++加速
- 3.1、CUDA C++配置(Visual Studio)
- 3.2、CUDA C++教程
- 3.2.1、声明GPU上的核函数:__global__ void GPUFunction()
- 3.2.2、执行GPU上的核函数:kernel < < < block_num, thread_num > > >
- 3.2.2.1、获取线程和块的索引:threadIdx.x + blockIdx.x
- 3.2.2.2、管理不同块之间的线程:blockIdx.x * blockDim.x + threadIdx.x
- 3.2.2.3、二维和三维的块和线程
- 3.2.3、分配和释放GPU内存:cudaMallocManaged、cudaFree
- 3.2.4、异步计算:cudaDeviceSynchronize()
- 3.2.5、检查错误:cudaGetLastError()
- 3.3、CUDA 实现:向量加法
- 3.3、CUDA 实现:矩阵相乘
- 四、TensorRT C++ 加速
- 4.1、项目实战1:C++构建engine引擎文件 + TensorRT模型推理
- 4.1.1、TensorRT C++ API 构建模型
- 4.1.2、TensorRT C++ API 模型推理
- 4.2、项目实战2:python执行图像前处理 + opencv加载图像 + TensorRT模型推理
NVIDIA TensorRT官网
前言:TensorRT简介
TensorRT(Tensor Runtime,TRT)
:由 NVIDIA 开发,专为 NVIDIA GPU 设计的,用于深度学习高性能推理的SDK (software development kit ,软件开发工具包),可为推理应用程序提供低延迟和高吞吐量。
- 支持的接口类型:
TensorRT C++
、TensorRT Python
- 支持的框架模型:
TensorFlow
、PyTorch
、ONNX
、Matlab
- TensorRT 头文件:
NvInfer.h
、NvInferRuntime.h
- TensorRT推理引擎(Inference Engine):
- 表示经过优化和编译的深度学习模型,以便在推理阶段获得最佳性能。
- 保存为二进制文件,称为
TensorRT 引擎文件(.engine)
。
0.1、TensorRT算法流程
- (1)将模型文件编译为 TensorRT 引擎文件(.engine)
- TensorFlow / PyTorch模型转换:
将.h5/.pt转换为.onnx
- TensorRT-API编译:
将.onnx编译为.engine
- (2)实现在 NVIDIA GPU 上进行推理,而无需深入了解 C++ 或 CUDA。
- TensorRT模型推理:
使用.engine文件进行高效推理
0.2、TensorRT主要优化技术
网络层融合(Layer Fusion)
:将多个网络层融合成一个更大的层,减少了计算和内存开销。支持权重和激活值精度调整(Precision Calibration)
:支持 FP16、INT8 或其他精度。使用低精度可以减小模型大小、降低内存占用,并提高计算速度,特别是在 NVIDIA GPU 中。动态 Tensor 决策(Dynamic Tensor Decision)
:根据输入数据的分布,动态选择适当的 Tensor 大小。自动选择最佳 kernel 算法
:根据硬件特性和模型结构自动选择最佳的 CUDA kernel 算法。内存优化
:包括共享内存(shared memory)和内存对齐(memory alignment),减小了内存占用。
一、TensorRT配置
1.1、TensorRT环境配置
1.1.1、CUDA安装
(1)CUDA + cuDNN安装;(2)Visual Studio安装;
1.1.2、TensorRT下载
详细步骤:TensorRT官网
+ Download Now + TensorRT 8 + I Agree + TensorRT 8.6 GA + 根据CUDA版本选择对应平台的TensorRT
- TensorRT-8.6.1.6.Windows10.x86_64.cuda-11.8.zip
1.1.3、TensorRT CUDA配置
- 11、zip解压到文件夹:TensorRT-8.6.1.6.Windows10.x86_64.cuda-11.8
- 22、获取TensorRT - lib路径:
D:\TensorRT-8.6.1.6.Windows10.x86_64.cuda-11.8\TensorRT-8.6.1.6\lib
- 33、将TensorRT - lib路径添加到环境变量:
电脑 + 属性 + 高级系统设置 + 环境变量 + 系统变量 + 双击Path + 新建
- 44、文件移植
1.1.4、TensorRT配置
1.1.4.1、TensorRT python配置
获取TensorRT的python路径:
D:\TensorRT-8.6.1.6.Windows10.x86_64.cuda-11.8\TensorRT-8.6.1.6\python
。
在该路径下,配置了多个python版本的TensorRT的轮子(.whl)。
- 11、激活虚拟环境
- 22、cd 到TensorRT的python路径
- 33、cmd安装(选择自定义python版本):
pip install tensorrt-8.6.1-cp39-none-win_amd64.whl
- 44、查看版本号:
python -c "import tensorrt as trt; print(trt.__version__)"
1.1.4.2、TensorRT C++配置(Visual Studio)
使用TensorRT自带测试案例演示效果
(1)使用Visual Studio双击并打开(sample_onnx_mnist.sln):D:\TensorRT-8.6.1.6.Windows10.x86_64.cuda-11.8\TensorRT-8.6.1.6\samples\sampleOnnxMNIST\sample_onnx_mnist.sln
(2)Visual Studio环境配置:
选中项目sample_onnx_mnist + 属性
nvinfer.lib
nvinfer_plugin.lib
nvonnxparser.lib
nvparsers.lib
cudnn.lib
cublas.lib
cudart.lib
(3)Visual Studio编译测试:
选中项目sample_onnx_mnist + 生成 + 执行
(4)编译成功之后,在TensorRT - bin 路径下将会生成可执行文件:
sample_onnx_mnist.exe
。
此时,双击sample_onnx_mnist.exe将出现闪退现象,可以通过以下方法解决。
11、双击sampleOnnxMNIST.cpp
22、在main()函数下,添加getchar();
33、重新编译
(5)找不到文件:zlibwapi.dll
解决办法:Win10+TensorRT 8.5安装+VS2022配置
二、模型转换(.h5 / .pt / .onnx / .engine)
文件 | .h5 | .pt/.pth | .pdmodel | .onnx | .engine |
---|---|---|---|---|---|
模型文件 | 深度学习框架:Keras、TensorFlow | 深度学习框架:Pytorch | 深度学习框架:PaddlePaddle(百度飞桨) | 开放标准文件(在不同的深度学习框架之间转换模型) | 加速引擎文件(由NVIDIA TensorRT库编译和优化) |
作用 | 保存模型的结构、权重参数、训练信息。 | (1).pt 保存模型的结构和权重参数;(2).pth 仅保存模型的权重参数,而不包括模型的架构; | 保存模型的结构和权重参数 | 保存模型的结构和权重参数 | 保存模型的结构、权重参数和优化信息 |
文件格式 | HDF5 格式 | Pytorch 自有格式 | PaddlePaddle 自有格式 | ONNX 格式 | TensorRT 自有格式 |
备注 | HDF5(Hierarchical Data Format version 5)是一种用于存储和组织大量数据的灵活的文件格式。 | \ | \ | ONNX(Open Neural Network Exchange)提供了一种跨框架的标准表示方式,便于模型在不同框架之间进行转换和共享。 | \ |
2.0、百度飞桨:.pdmodel(生成模型 + 保存模型 + 加载模型)
import paddle
from paddle.static import InputSpec
# 定义一个简单的模型
class SimpleModel(paddle.nn.Layer):
def __init__(self):
super(SimpleModel, self).__init__()
self.fc = paddle.nn.Linear(10, 2)
def forward(self, x):
x = self.fc(x)
return x
if __name__ == '__main__':
# (1)创建pd模型
model = SimpleModel() # 模型实例化
input_spec = [InputSpec(shape=[None, 10], dtype='float32', name='input')] # 定义输入形状,将输入从单个图像改为批量图像
model = paddle.jit.to_static(model, input_spec=input_spec) # 转换为静态图模型
# (2)保存模型: .pdmodel和.pdparams 文件
paddle.jit.save(model, "your_model.pdmodel")
paddle.save(model.state_dict(), "your_model.pdparams")
# (3)加载模型: .pdmodel和.pdparams 文件
loaded_model = paddle.jit.load("your_model.pdmodel")
params = paddle.load("your_model.pdparams")
input_spec = [InputSpec(shape=[None, 3], dtype='float32', name='input')] # 定义输入形状
loaded_model = paddle.jit.to_static(loaded_model, input_spec=input_spec) # 将模型转换为静态图
loaded_model.set_dict(params) # 设置模型参数
paddle.jit.save(loaded_model, "your_new_model.pdmodel") # 保存模型
2.1、TensorFlow - 模型转换(.h5 转 .onnx)
2.1.1、文件生成(.h5)
import numpy as np
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.models import save_model
# (1)生成一些示例数据
np.random.seed(42)
X = np.random.rand(100, 1)
y = 2 * X + 1 + 0.1 * np.random.randn(100, 1)
# (2)创建一个简单的神经网络模型
model = Sequential()
model.add(Dense(units=1, input_dim=1, activation='linear', kernel_initializer='random_uniform', bias_initializer='zeros'))
# (3)编译模型
optimizer = Adam(lr=0.01)
model.compile(optimizer=optimizer, loss='mean_squared_error')
# (4)训练模型
model.fit(X, y, epochs=50, batch_size=10, verbose=1)
# (5)保存模型为.h5文件
model.save('your_h5_model.h5')
print("模型保存成功!")
2.1.2、模型转换(加载模型.h5 + 转换为.onnx)
环境安装:
- (1)
pip install onnx
- (2)
pip install tf2onnx
- (3)
pip install tf2onnx==1.13.0 onnx==1.14.1
备注:有版本对应关系(实测没有): tensorflow - cpu/gpu安装(详解)
import onnx
import tf2onnx
from tensorflow.keras.models import load_model
model = load_model('your_h5_model.h5') # (1)加载 .h5 模型
onnx_model, _ = tf2onnx.convert.from_keras(model) # (2)转换为 .onnx 格式
onnx.save_model(onnx_model, 'your_onnx_model.onnx') # (3)保存为 .onnx 格式
2.2、Torch - 模型转换(.pt 转 .onnx)
环境安装:Pytorch安装(在线与离线)
2.2.1、模型转换(加载模型.pt + 转换为.onnx)
import torch
import torchvision.models as models
# model = models.resnet18() # 加载torch预训练模型(ResNet-18)
model = torch.load('your_torch_model.pt') # 加载模型
dummy_input = torch.randn(1, 3, 224, 224) # 创建一个示例输入
torch.onnx.export(model, dummy_input, 'your_torch_model.onnx') # 将模型导出为 ONNX 格式
2.2.2、模型转换(生成模型 + 保存为.onnx)
import os
import torch
import torch.nn as nn
import torchvision.models as models
import torchvision.transforms as transforms
import cv2
import matplotlib.pyplot as plt
# 定义模型
class Model(torch.nn.Module):
def __init__(self):
super().__init__()
# 定义卷积层
self.conv = nn.Conv2d(3, 1, 3, padding=1) # 输入通道数为3,输出通道数为1,卷积核大小为3x3,填充为1
self.relu = nn.ReLU() # 定义ReLU激活层
self.conv.weight.data.fill_(1) # 设置卷积核的权重(初始化为1)
self.conv.bias.data.fill_(0) # 设置卷积核的偏置(初始化为0)
def forward(self, x):
# 前向传播函数
x = self.conv(x)
x = self.relu(x)
return x # 输入 x 经过卷积层、ReLU激活层处理后返回
if __name__ == '__main__':
model = Model() # 模型实例化
# model = models.resnet18() # 加载torch预训练模型(ResNet-18)
# torch.load('model_weights.pth') # 加载自定义的预训练模型
# model.load_state_dict(torch.load('model_weights.pth')) # 加载预训练模型的权重(如果有的话)
dummy = torch.randn(1, 3, 224, 224)
torch.onnx.export(model, dummy, "your_torch_model.onnx",)
debug = 0
if debug == 1:
# 设置模型为评估模式
model.eval()
# 读取测试图像
image_path = "image.jpg"
image = cv2.imread(image_path)
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # 转换为RGB格式
# 将图像转换为PyTorch张量并进行归一化
transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])])
input_tensor = transform(image_rgb).unsqueeze(0)
# 使用模型进行推理
with torch.no_grad():
output_tensor = model(input_tensor)
# 将输出张量转换为NumPy数组,并将其可视化
output_image = output_tensor.squeeze().numpy()
# 显示原始图像和输出图像
plt.figure(figsize=(10, 5))
plt.subplot(1, 2, 1)
plt.imshow(image_rgb)
plt.title('Original Image')
plt.axis('off')
plt.subplot(1, 2, 2)
plt.imshow(output_image, cmap='gray')
plt.title('Output Image')
plt.axis('off')
plt.show()
2.3、TensorRT - 模型转换(.onnx 转 .engine)
trtexec.exe
:是 NVIDIA TensorRT 提供的命令行工具 —— 用于执行 TensorRT 的模型编译、推理和性能评估。
2.3.1、C++实现
(C++实现)tensorRT部署之 代码实现 onnx转engine/trt模型
(C++实现)TensorRT 推理 (onnx->engine)
2.3.2、cmd命令行实现
- (1)获取文件路径:
D:\TensorRT-8.6.1.6.Windows10.x86_64.cuda-11.8\TensorRT-8.6.1.6\bin\trtexec.exe
- TensorRT-8.6.1.6.Windows10.x86_64.cuda-11.8是 TensorRT 下载并解压的文件夹。
- (2)cmd命令行执行:
trtexec.exe --onnx=your_onnx_model.onnx --saveEngine=your_engine_model.engine
- trtexec.exe:指定 .exe 文件输入路径
- your_onnx_model.onnx:指定 .onnx 文件输入路径
- your_engine_model.engine:指定 .engine 文件保存路径
- 备注:trtexec.exe 或 trtexec 都可以执行。
2.3.3、python实现(执行命令行指令)
import subprocess
input_onnx = 'your_onnx_model.onnx'
output_engine = 'your_engine_model.engine'
trtexec_command = f'trtexec.exe --onnx={input_onnx} --saveEngine={output_engine}' # 构建命令
subprocess.run(trtexec_command, shell=True) # 执行命令
#########################################################
# trtexec.exe: 指定 .exe 文件输入路径
# your_onnx_model.onnx: 指定 .onnx 文件输入路径
# your_engine_model.engine: 指定 .engine 文件保存路径
#########################################################
备注:trtexec参数和用法
###########################################################
# 命令行指令:`trtexec -- help`
###########################################################
# trtexec参数
# Model Options: 模型选项(输入格式、输出格式等)
# Build Options: 编译选项(输入shape限制、精度等)
# Inference Options: 推理选项(加载输入,shape等)
# Reporting Options: 报告选项(小数精度,输出到文件等)
# System Options: 系统选项(设置设备ID等)
###########################################################
trtexec --onnx=input.onnx --saveEngine=output.engine
trtexec --onnx=weights_best.onnx --saveEngine=weights_best.engine --best --minShapes=input:1x64x64x1 --optShapes=input:256x64x64x1 --maxShapes=input:512x64x64x1
trtexec --onnx=weights_best.onnx --saveEngine=weights_best.engine --best --minShapes=input:1x2048x2048x1 --optShapes=input:1x2048x2048x1 --maxShapes=input:1x2048x2048x1
# 生成静态batchsize的engine
./trtexec --onnx=<onnx_file> \ #指定onnx模型文件
--explicitBatch \ #在构建引擎时使用显式批大小(默认=隐式)显示批处理
--saveEngine=<tensorRT_engine_file> \ #输出engine
--workspace=<size_in_megabytes> \ #设置工作空间大小单位是MB(默认为16MB)
--fp16 #除了fp32之外,还启用fp16精度(默认=禁用)
# 生成动态batchsize的engine
./trtexec --onnx=<onnx_file> \ #指定onnx模型文件
--minShapes=input:<shape_of_min_batch> \ #最小的NCHW
--optShapes=input:<shape_of_opt_batch> \ #最佳输入维度,跟maxShapes一样就好
--maxShapes=input:<shape_of_max_batch> \ #最大输入维度
--workspace=<size_in_megabytes> \ #设置工作空间大小单位是MB(默认为16MB)
--saveEngine=<engine_file> \ #输出engine
--fp16 #除了fp32之外,还启用fp16精度(默认=禁用)
#小尺寸图像(batchsize = 8x3x416x416)
/TensorRT-8.6.1.6/bin/trtexec --onnx=your_model.onnx \
--minShapes=input:1x3x416x416 \
--optShapes=input:8x3x416x416 \
--maxShapes=input:8x3x416x416 \
--workspace=4096 \
--saveEngine=yolov4_-1_3_416_416_dynamic_b8_fp16.engine \
--fp16
三、CUDA C++加速
CUDA 提供了一种可扩展的编码接口,适用于多种编程语言,包括 C、C++、Python 和 Fortran。通过并行化,优化后的代码能够在 NVIDIA GPU 上运行,显著提高应用程序的运行速度。
- CUDA 并行计算:将一个计算任务分解成多个子任务,并在多个处理单元上并行执行。
3.1、CUDA C++配置(Visual Studio)
环境配置:(1)CUDA + cuDNN安装;(2)Visual Studio安装;
VS配置cuda:windows10 + visual studio 配置cuda;
(1)新建项目:
打开Visual Studio + 文件 + 新建 + 项目 + 空项目
(2)配置参数:
(3)新建文件
.cu
是 CUDA 加速程序的文件扩展名。- .cu 与 .cpp 区别:
.cpp + CUDA代码 = .cu
(没有其他特殊之处)
3.2、CUDA C++教程
CUDA 提供了 C/C++ 接口,让开发人员可以在 GPU 上运行 CUDA 库函数。
CUDA C/C++ 教程一:加速应用程序
3.2.1、声明GPU上的核函数:global void GPUFunction()
/*************************************************************
* 函数功能;关键字(__global__)是CUDA C/C++中的一个修饰符,用于声明GPU上执行的全局核函数。
* 函数说明:__global__ void GPUFunction();
* 返回类型:必须为(void)
*
* 原因分析:并行计算不返回值给CPU,而是通过修改输入参数或在写入全局内存中来传递计算结果。
*************************************************************/
/*************************************************************
* 函数说明:cudaDeviceSynchronize();
*
* GPU核函数的启动方式为异步计算,添加上述函数将执行同步计算。
* (1)异步计算:CPU代码将继续执行,而不会等待核函数执行完成;
* (2)同步计算:使CPU等待GPU任务执行完毕后,再继续执行CPU任务。
*************************************************************/
/***************************************
// 文件.cu
***************************************/
// 在CPU上运行的函数
void CPUFunction() {
printf("This function is defined to run on the CPU.\n");
}
// 在GPU上运行的函数
__global__ void GPUFunction() {
printf("This function is defined to run on the GPU.\n");
}
int main() {
CPUFunction(); // 调用CPU函数
GPUFunction<<<1, 1>>>(); // 调用GPU函数
cudaDeviceSynchronize(); // 同步计算
}
3.2.2、执行GPU上的核函数:kernel < < < block_num, thread_num > > >
/***************************************************************************************************************
* 函数功能:在GPU上运行函数,又称为kernel(核)函数。
* 函数说明:GPUFunction<<<block_num, thread_num>>>();
* 输入参数: block_num 定义线程块(Block)的数量
* thread_num 定义Block中含有的线程(Thread)数量
*
*
* 使用<<< ... >>>语法配置GPU参数
* kernel<<<1, 1>>() 在GPU中为该核函数分配 1 个具有 1 个线程的线程块,核函数中的代码将运行1次;
* kernel<<<1, 10>>() 在GPU中为该核函数分配 1 个具有 10 个线程的线程块,核函数中的代码将运行10次;
* kernel<<<10, 1>>() 在GPU中为该核函数分配 10 个具有 1 个线程的线程块,核函数中的代码将运行10次;
* kernel<<<10, 10>>() 在GPU中为该核函数分配 10 个具有 10 个线程的线程块,核函数中的代码将运行100次;
*
* (1)线程组成块,块组成网格,而网格是CUDA线程层次结构中级别最高的实体,它没有索引。
* (2)每个块中的线程数量相同
* (3)每个块的线程线程数量<=1024
*
* 线程索引公式:blockIdx.x * blockDim.x + threadIdx.x
* blockIdx.x 块索引(从0开始)
* blockDim.x 每个块的线程数
* threadIdx.x 线程索引(从0开始)
*
* 配置参数<<<10, 10>>>:启动具有 100 个线程的网格,该网格有10个块,每个块中有10个线程(即blockDim.x=10)
* (1)块blockIdx.x 的索引范围(0 至 9)
* (2)线程threadIdx.x的索引范围(0 至 9)
*
* (3)若块blockIdx.x 的索引为 0,则 blockIdx.x * blockDim.x 为 0 。则可在网格中找到索引为 0 至 9 的线程。
* (4)若块blockIdx.x 的索引为 1,则 blockIdx.x * blockDim.x 为 10。则可在网格中找到索引为 10 至 19 的线程。
***************************************************************************************************************/
#include <stdio.h>
__global__ void firstParallel() {
printf("This is running in parallel.\n");
}
int main() {
// 在GPU中为核函数分配5个具有5个线程的线程块,将运行25次;
firstParallel << <5, 5 >> > ();
cudaDeviceSynchronize(); // 同步计算
}
3.2.2.1、获取线程和块的索引:threadIdx.x + blockIdx.x
#include <stdio.h>
__global__ void kernel() {
// 当执行到第255个线程块的第1023个线程时,才输出
if (threadIdx.x == 1023 && blockIdx.x == 255)
{
printf("threadIdx.x: %d\n", threadIdx.x); // 输出线程ID
printf("blockIdx.x: %d\n", blockIdx.x); // 输出线程块ID
}
}
int main() {
// 配置该核函数由256个含有1024个线程的线程块中执行
kernel << <256, 1024 >> > ();
cudaDeviceSynchronize(); // 同步计算
}
3.2.2.2、管理不同块之间的线程:blockIdx.x * blockDim.x + threadIdx.x
#include <stdio.h>
__global__ void loop()
{
// 在Grid中遍历所有thread
int i = blockIdx.x * blockDim.x + threadIdx.x;
printf("%d\n", i);
}
int main()
{
// 配置参数.例如:<<<5, 2>>>、<<<10, 1>>>
loop << <2, 5 >> > ();
cudaDeviceSynchronize();
}
3.2.2.3、二维和三维的块和线程
可以使用 dim3 数据结构来表示块和线程的大小。
threads_per_block
:用于定义一个(二位、三维)块,其中每个维度都包含 18 个线程。number_of_blocks
:用于定义一个(二位、三维)网格中的块的数量
- 二维数据的尺寸:
N、M
- 三维数据的尺寸:
N、M、P
// (1)在二维网格中
dim3 threads_per_block(16, 16);
dim3 number_of_blocks((N + threads_per_block.x - 1) / threads_per_block.x,
(M + threads_per_block.y - 1) / threads_per_block.y);
// (2)在三维网格中
dim3 threads_per_block(8, 8, 8);
dim3 number_of_blocks((N + threads_per_block.x - 1) / threads_per_block.x,
(M + threads_per_block.y - 1) / threads_per_block.y,
(P + threads_per_block.z - 1) / threads_per_block.z);
3.2.3、分配和释放GPU内存:cudaMallocManaged、cudaFree
-
CPU内存分配和释放:分配GPU内存 + 上传到GPU内存 + 调用GPU计算 + 修改输入参数传递结果
-
GPU内存分配和释放:(自动)分配GPU内存 + 调用GPU计算 + 修改输入参数传递结果
/*************************************************************
* 函数功能:内存分配和释放
* 函数说明:CPU:
* cudaMalloc 在GPU上分配内存(只支持 GPU 访问)
* cudaMemcpy 在CPU和GPU之间进行内存数据传输。
* free 释放由cudaMalloc分配的GPU内存
*
* 函数说明:GPU: cudaMallocManaged 内存由系统自动管理,而不需要显式的数据传输(同时支持CPU + GPU访问)
* cudaFree 释放由cudaMalloc或cudaMallocManaged分配的GPU内存。
*************************************************************/
// CPU ----------------------------------------------------
int N = 10;
size_t size = N * sizeof(int);
int* a;
a = (int*)cudaMalloc(size); // 分配CPU内存
free(a); // 释放CPU内存
// GPU ----------------------------------------------------
int N = 10;
size_t size = N * sizeof(int);
int* a;
cudaMallocManaged(&a, size);// 为a分配CPU和GPU内存
cudaFree(a); // 释放GPU内存
#include <stdio.h>
// 初始化数组a —— 将每个元素的值设置为其对应的索引。
void init(int* a, int N) {
int i;
for (i = 0; i < N; ++i) {
a[i] = i;
}
}
// 使用线程并行地将数组中的元素翻倍。
__global__ void doubleElements(int* a, int N) {
// 每个线程负责处理数组中的一个元素,通过计算索引来确保不同线程处理不同的元素。
int idx = blockIdx.x * blockDim.x + threadIdx.x;
int stride = gridDim.x * blockDim.x;
for (int i = idx; i < N; i += stride) {
a[i] *= 2;
}
}
int main() {
// (1)分配内存
int N = 10000;
int* a;
size_t size = N * sizeof(int);
cudaMallocManaged(&a, size);
// (2)初始化数组a
init(a, N);
// (3)CUDA计算
size_t number_of_blocks = 32; // block数量
size_t threads_per_block = 256; // 每个block的thread数量
doubleElements << <number_of_blocks, threads_per_block >> > (a, N);
cudaDeviceSynchronize(); // 同步计算
cudaFree(a); // 释放GPU内存
}
3.2.4、异步计算:cudaDeviceSynchronize()
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <iostream>
__global__ void VecAdd(int* A, int* B, int* C)
{
int i = threadIdx.x;
C[i] = A[i] + B[i];
}
int main()
{
// (1)获取GPU
cudaError_t cudaStatus; //CUDA状态
cudaStatus = cudaSetDevice(0); //选择GUP
if (cudaStatus != cudaSuccess)
{
fprintf(stderr, "选择GPU失败,您的电脑上没有GPU");
return 0;
}
// (2)定义设备变量
const int size = 3;
int a[size] = { 1,2,3 };
int b[size] = { 10,20,30 };
int c[size] = { 0 };
int* dev_a = 0;
int* dev_b = 0;
int* dev_c = 0;
// (3)分配 GUP内存
cudaStatus = cudaMalloc((void**)&dev_c, size * sizeof(int));
cudaStatus = cudaMalloc((void**)&dev_a, size * sizeof(int));
cudaStatus = cudaMalloc((void**)&dev_b, size * sizeof(int));
// (4)从主机内存复制数据到 GPU 内存
cudaStatus = cudaMemcpy(dev_a, a, size * sizeof(int), cudaMemcpyHostToDevice);
cudaStatus = cudaMemcpy(dev_b, b, size * sizeof(int), cudaMemcpyHostToDevice);
// (5)调用 GPU 计算
VecAdd << <1, size >> > (dev_a, dev_b, dev_c);
// (6)获取错误状态
cudaStatus = cudaGetLastError();
cudaStatus = cudaDeviceSynchronize();
// (7)将计算结果返回主机内存
cudaStatus = cudaMemcpy(c, dev_c, size * sizeof(int), cudaMemcpyDeviceToHost);
printf("[1, 2, 3] + [10, 20, 30] = [%d, %d, %d]\n", c[0], c[1], c[2]);
// (8)释放内存
cudaFree(dev_a);
cudaFree(dev_b);
cudaFree(dev_c);
return 0;
}
3.2.5、检查错误:cudaGetLastError()
cudaGetLastError
:用于获取最近一次CUDA运行时,函数调用产生的错误代码(如:配置错误)。
- 它没有输入参数,返回一个
cudaError_t
类型的错误码。- 若没有错误,将返回
cudaSuccess
。
- 由于核函数的返回值为void,因此不会返回 cudaError_t 值。
#include <cuda_runtime.h>
#include <stdio.h>
__global__ void kernel() {
// 简单的核函数(不进行具体计算)
}
int main() {
kernel << <1, 1 >> > ();
cudaDeviceSynchronize();
cudaError_t cudaError = cudaGetLastError();
if (cudaError != cudaSuccess) {
printf("CUDA Error: %s\n", cudaGetErrorString(cudaError));
}
else {
printf("CUDA Success!\n");
}
return 0;
}
3.3、CUDA 实现:向量加法
#include <stdio.h>
#include <assert.h>
// CUDA 错误处理宏
inline cudaError_t checkCuda(cudaError_t result)
{
if (result != cudaSuccess) {
fprintf(stderr, "CUDA Runtime Error: %s\n", cudaGetErrorString(result));
assert(result == cudaSuccess);
}
return result;
}
// 初始化数组 a
void initWith(float num, float* a, int N) {
for (int i = 0; i < N; ++i) {
a[i] = num;
}
}
// 核函数:向量加法
__global__ void addVectors(float* result, float* a, float* b, int N) {
int index = threadIdx.x + blockIdx.x * blockDim.x;
int stride = blockDim.x * gridDim.x;
for (int i = index; i < N; i += stride) {
result[i] = a[i] + b[i]; // 元素a[i] + 元素 b[i]
}
}
int main() {
const int num = 10;
size_t size = num * sizeof(float);
float* a;
float* b;
float* c;
// (1)分配内存,且检查执行期间发生的错误
checkCuda(cudaMallocManaged(&a, size));
checkCuda(cudaMallocManaged(&b, size));
checkCuda(cudaMallocManaged(&c, size));
// (2)初始化数组
initWith(3, a, num); // 将数组a中num个元素,初始化为3
initWith(4, b, num); // 将数组b中num个元素,初始化为4
initWith(0, c, num); // 将数组c中num个元素,初始化为0
// (3)CUDA计算(配置参数)
size_t thread_num = 256;
size_t block_num = (num + thread_num - 1) / thread_num;
addVectors << <block_num, thread_num >> > (c, a, b, num); // 执行核函数
checkCuda(cudaGetLastError()); // 检查核函数执行期间发生的错误
checkCuda(cudaDeviceSynchronize()); // 同步,且检查执行期间发生的错误
// (4)打印结果
for (int i = 0; i < num; ++i) {
printf("%f ", a[i]);
printf("%f ", b[i]);
printf("%f ", c[i]);
printf("\n");
}
checkCuda(cudaFree(a)); // 释放内存,且检查执行期间发生的错误
checkCuda(cudaFree(b));
checkCuda(cudaFree(c));
}
3.3、CUDA 实现:矩阵相乘
#include <stdio.h>
#define N 64
// CPU 矩阵乘法
void matrixMulCPU(int* a, int* b, int* c) {
int val = 0;
for (int row = 0; row < N; ++row)
for (int col = 0; col < N; ++col) {
val = 0;
for (int k = 0; k < N; ++k)
val += a[row * N + k] * b[k * N + col];
c[row * N + col] = val;
}
}
// GPU 矩阵乘法
__global__ void matrixMulGPU(int* a, int* b, int* c) {
int val = 0;
int row = blockIdx.y * blockDim.y + threadIdx.y;
int col = blockIdx.x * blockDim.x + threadIdx.x;
if (row < N && col < N) {
for (int k = 0; k < N; ++k)
val += a[row * N + k] * b[k * N + col];
c[row * N + col] = val;
}
}
int main() {
int* a, * b, * c_cpu, * c_gpu;
int size = N * N * sizeof(int); // Number of bytes of an N x N matrix
// (1)分配内存
cudaMallocManaged(&a, size);
cudaMallocManaged(&b, size);
cudaMallocManaged(&c_cpu, size);
cudaMallocManaged(&c_gpu, size);
// (2)初始化数组
for (int row = 0; row < N; ++row)
for (int col = 0; col < N; ++col)
{
a[row * N + col] = row;
b[row * N + col] = col + 2;
c_cpu[row * N + col] = 0;
c_gpu[row * N + col] = 0;
}
// (3)CUDA计算
dim3 threads_per_block(16, 16, 1); // 一个 16 * 16 的线程阵
dim3 number_of_blocks((N / threads_per_block.x) + 1, (N / threads_per_block.y) + 1, 1);
matrixMulGPU << < number_of_blocks, threads_per_block >> > (a, b, c_gpu); // 执行核函数
cudaDeviceSynchronize(); // 同步计算
// (4)CPU计算
matrixMulCPU(a, b, c_cpu); // 执行 CPU 版本的矩阵乘法
// 释放内存
cudaFree(a);
cudaFree(b);
cudaFree(c_cpu);
cudaFree(c_gpu);
}
四、TensorRT C++ 加速
4.1、项目实战1:C++构建engine引擎文件 + TensorRT模型推理
TensorRT C++ API (模型创建 + 模型推理)
(1)构建(model):基于 TensorRT C++ API 构建一个 TensorRT 模型的过程。
(2)推理(Inference):使用构建模型 / 预训练模型进行预测的过程。
TensorRT C++ API (接口详解)
TensorRTC++ 学习(接口详解)
4.1.1、TensorRT C++ API 构建模型
// tensorRT 头文件
#include <NvInfer.h>
#include <NvInferRuntime.h>
// CUDA 头文件
#include <cuda_runtime.h>
// C++ 头文件
#include <stdio.h>
/*******************************************************************************
* @brief 自定义的 TensorRT 日志类
*
* 该类继承自 TensorRT 中的 ILogger 接口,用于处理 TensorRT 的日志输出。
*******************************************************************************/
class TRTLogger : public nvinfer1::ILogger {
public:
/*******************************************
* @brief 覆盖基类的 log 函数,处理日志消息
*
* @param (1)severity 日志消息的严重性级别
* (2)msg 日志消息的内容
*******************************************/
virtual void log(Severity severity, nvinfer1::AsciiChar const* msg) noexcept override {
// 如果日志级别小于等于 Severity::kINFO(INFO 及以下级别),则将日志消息通过 printf 打印到控制台。
if (severity <= Severity::kINFO)
printf("%d: %s\n", severity, msg);
}
} logger;
/*********************************************************************
* @brief:创建一个nvinfer1::Weights对象,用于表示神经网络中的权重和偏置。
*
* @输入参数 ptr 指向包含权重或偏置数值的数组的指针。
* n 数组中的元素数量。
* @输出参数
* nvinfer1::Weights对象,包含权重或偏置的相关信息。
*********************************************************************/
nvinfer1::Weights make_weights(float* ptr, int n) {
nvinfer1::Weights w;
// 设置Weights对象的成员变量
w.count = n; // 数组中的元素数量
w.type = nvinfer1::DataType::kFLOAT; // 数据类型为float
w.values = ptr; // 指向包含权重或偏置数值的数组的指针
return w;
}
/*******************************************************************************
* @brief:定义一个 TensorRT 模型构建函数。
* —— 创建构建器、配置、网络模型、生成引擎、序列化引擎并存储、释放资源等步骤。
*******************************************************************************/
void model()
{
// ----------------------------- 1. 创建builder、config、network -----------------------------
TRTLogger logger; // 创建TRTLogger对象,用于捕获TensorRT运行时的信息和警告。
nvinfer1::IBuilder* builder = nvinfer1::createInferBuilder(logger); // 创建TensorRT的模型构建器(IBuilder),用于定义和优化深度学习模型。
nvinfer1::IBuilderConfig* config = builder->createBuilderConfig(); // 创建模型构建器的配置对象(IBuilderConfig ),用于配置模型构建器的各种参数,例如内存优化、精度配置等。
nvinfer1::INetworkDefinition* network = builder->createNetworkV2(1); // 创建网络定义(INetworkDefinition),用于定义深度学习模型结构。
// ----------------------------- 2. 创建网络模型 -----------------------------
// 定义输入和输出的维度
const int num_input = 3; // 输入特征的数量
const int num_output = 2; // 输出特征的数量
// 定义全连接层的权重和偏置
float layer1_weight_values[] = { 1.0, 2.0, 0.5, 0.1, 0.2, 0.5 }; // 权重数组
float layer1_bias_values[] = { 0.3, 0.8 }; // 偏置数组
nvinfer1::ITensor* input = network->addInput("image", nvinfer1::DataType::kFLOAT, nvinfer1::Dims4(1, num_input, 1, 1)); // 创建输入张量,其数据类型为 float,维度为 (1, num_input, 1, 1)。
// 创建全连接层的权重和偏置
nvinfer1::Weights layer1_weight = make_weights(layer1_weight_values, 6); // 创建全连接层的权重值
nvinfer1::Weights layer1_bias = make_weights(layer1_bias_values, 2); // 创建全连接层的偏置值
auto layer1 = network->addFullyConnected(*input, num_output, layer1_weight, layer1_bias); // 添加全连接层到网络中,连接到输入张量,输出节点数为 num_output。
auto prob = network->addActivation(*layer1->getOutput(0), nvinfer1::ActivationType::kSIGMOID); // 添加Sigmoid激活函数层到全连接层的输出上
network->markOutput(*prob->getOutput(0)); // 标记网络的输出为Sigmoid激活函数层的输出。
// ----------------------------- 3. 生成engine引擎文件 -----------------------------
printf("Workspace Size = %.2f MB\n", (1 << 28) / 1024.0f / 1024.0f); // 打印工作空间大小
config->setMaxWorkspaceSize(1 << 28); // 配置TensorRT构建器的最大工作空间大小
builder->setMaxBatchSize(1); // 配置TensorRT构建器的最大批次大小
nvinfer1::ICudaEngine* engine = builder->buildEngineWithConfig(*network, *config); // 使用(builder)和配置(config)构建CUDA引擎(engine)
// 检查引擎是否构建成功
if (engine == nullptr) {
printf("Build engine failed.\n");
}
// ----------------------------- 4. 序列化engine引擎文件并存储 -----------------------------
nvinfer1::IHostMemory* model_data = engine->serialize(); // 序列化引擎
FILE* f;
errno_t err = fopen_s(&f, "model.engine", "wb"); // 打开一个二进制文件用于写入序列化后的引擎数据
if (err == 0) {
fwrite(model_data->data(), 1, model_data->size(), f); // 将序列化后的引擎数据写入文件
fclose(f); // 关闭文件
}
else {
printf("Failed to open the file.\n"); // 打开文件失败,处理错误
}
// ----------------------------- 5. 释放相关资源 -----------------------------
model_data->destroy(); // 释放序列化后的引擎数据的内存
engine->destroy(); // 释放CUDA引擎资源
network->destroy(); // 释放网络定义资源
config->destroy(); // 释放模型构建器配置资源
builder->destroy(); // 释放模型构建器资源
printf("Done.\n"); // 输出完成信息
}
int main()
{
model();
return 0;
}
/*******************************************************************************
* C4996 'fopen': This function or variable may be unsafe.Consider using fopen_s instead.
*
* nvinfer1::IHostMemory* model_data = engine->serialize(); // 序列化引擎
* FILE* f = fopen("model.engine", "wb"); // 打开一个二进制文件用于写入序列化后的引擎数据
* fwrite(model_data->data(), 1, model_data->size(), f); // 将序列化后的引擎数据写入文件
* fclose(f); // 关闭文件
*******************************************************************************/
4.1.2、TensorRT C++ API 模型推理
// tensorRT 头文件
#include <NvInfer.h>
#include <NvInferRuntime.h>
// CUDA 头文件
#include <cuda_runtime.h>
// C++ 头文件
#include <stdio.h>
#include <math.h>
#include <iostream>
#include <fstream>
#include <vector>
using namespace std;
/*******************************************************************************
* @brief 自定义的 TensorRT 日志类
*
* 该类继承自 TensorRT 中的 ILogger 接口,用于处理 TensorRT 的日志输出。
*******************************************************************************/
class TRTLogger : public nvinfer1::ILogger {
public:
/*******************************************
* @brief 覆盖基类的 log 函数,处理日志消息
*
* @param (1)severity 日志消息的严重性级别
* (2)msg 日志消息的内容
*******************************************/
virtual void log(Severity severity, nvinfer1::AsciiChar const* msg) noexcept override {
// 如果日志级别小于等于 Severity::kINFO(INFO 及以下级别),则将日志消息通过 printf 打印到控制台。
if (severity <= Severity::kINFO)
printf("%d: %s\n", severity, msg);
}
} logger;
/*******************************************************************************
* @brief:从文件中加载二进制数据并将其存储在一个 std::vector<unsigned char> 中
*
* @输入参数: file 文件路径
* @输出参数: std::vector<unsigned char> 二进制向量(存储了文件数据)
*
* (1)若二进制文件可以打开,读取其中的数据并返回一个包含该数据的二进制向量。
* (2)若二进制文件可以打开或为空,则返回一个空的向量。
*******************************************************************************/
vector<unsigned char> load_model(const string& file) {
ifstream in(file, ios::in | ios::binary); // 打开二进制文件
if (!in.is_open()) // 如果文件无法打开,返回一个空的 vector
return {};
in.seekg(0, ios::end); // 定位到文件结束处以获取文件长度
size_t length = in.tellg(); //获取文件长度
std::vector<uint8_t> data; // 准备存储模型数据的 vector
if (length > 0) {
in.seekg(0, ios::beg); // 重新定位到文件开始处
data.resize(length); // 调整 vector 大小以容纳整个文件
in.read((char*)&data[0], length); // 从文件中读取数据到 vector
}
in.close(); // 关闭文件流
return data;
}
/*******************************************************************************
* @brief:推理(Inference):使用预训练模型来进行预测或分类的过程。
* 具体而言,推理阶段是模型从新输入数据中产生输出结果的过程。
*******************************************************************************/
void inference()
{
// ------------------------------ 1. 加载模型 ----------------------------
TRTLogger logger; // 创建 TensorRT 的日志记录器
auto engine_data = load_model("model.engine"); // 从文件中加载 TensorRT 模型
nvinfer1::IRuntime* runtime = nvinfer1::createInferRuntime(logger); // 创建 TensorRT 运行时实例
nvinfer1::ICudaEngine* engine = runtime->deserializeCudaEngine(engine_data.data(), engine_data.size()); // 从二进制数据中反序列化得到 TensorRT CUDA 引擎
// 检查引擎是否成功加载
if (engine == nullptr) {
printf("Deserialize cuda engine failed.\n");
runtime->destroy(); // 释放 TensorRT 运行时资源
return;
}
// ------------------------------ 2. 创建 TensorRT 的执行上下文和 CUDA 流 ----------------------------
nvinfer1::IExecutionContext* execution_context = engine->createExecutionContext(); // 创建 TensorRT 的执行上下文(用于推理)
// 创建 CUDA 流(用于异步推理)
cudaStream_t stream = nullptr;
cudaStreamCreate(&stream);
// ------------------------------ 3. 准备推理数据 ----------------------------
float input_data_host[] = { 1, 2, 3 }; // 定义输入数据(在主机上)
float* input_data_device = nullptr; // 定义输入数据的设备指针
float output_data_host[2]; // 定义输出数据(在主机上)
float* output_data_device = nullptr; // 定义输出数据的设备指针
// ------------------------------ 4. 将数据搬运到GPU ----------------------------
cudaMalloc(&input_data_device, sizeof(input_data_host)); // 在 GPU 上分配输入数据的内存
cudaMalloc(&output_data_device, sizeof(output_data_host)); // 在 GPU 上分配输出数据的内存
cudaMemcpyAsync(input_data_device, input_data_host, sizeof(input_data_host), cudaMemcpyHostToDevice, stream); // 将输入数据从主机异步搬运到 GPU 上
// ------------------------------ 5. 推理 ----------------------------
float* bindings[] = { input_data_device, output_data_device }; // 定义 TensorRT 推理输入和输出的指针数组
bool success = execution_context->enqueueV2((void**)bindings, stream, nullptr); // 在执行上下文中进行异步推理
// ------------------------------ 6. 将推理结果搬运回CPU ----------------------------
cudaMemcpyAsync(output_data_host, output_data_device, sizeof(output_data_host), cudaMemcpyDeviceToHost, stream); // 将输出数据从 GPU 异步搬运到主机上
cudaStreamSynchronize(stream); // 等待 GPU 操作完成
printf("output_data_host = %f, %f\n", output_data_host[0], output_data_host[1]); // 打印推理结果
// ------------------------------ 7. 释放内存 ----------------------------
cudaStreamDestroy(stream); // 销毁 CUDA 流
execution_context->destroy(); // 销毁 TensorRT 执行上下文
engine->destroy(); // 销毁 TensorRT 引擎
runtime->destroy(); // 销毁 TensorRT 运行时
}
// 手动推理验证Inference
void self_inference()
{
float input_data_host[] = { 1, 2, 3 }; // 定义输入数据(在主机上)
const int num_input = 3; // 输入数据的维度
const int num_output = 2; // 输出数据的维度
float layer1_weight_values[] = { 1.0, 2.0, 0.5, 0.1, 0.2, 0.5 }; // 手动设置的第一层权重值
float layer1_bias_values[] = { 0.3, 0.8 }; // 手动设置的第一层偏差值
printf("\n手动验证计算结果:\n");
for (int io = 0; io < num_output; ++io) {
float output_host = layer1_bias_values[io];
for (int ii = 0; ii < num_input; ++ii) {
// 手动计算每个输出的值,通过权重和输入数据的线性组合加上偏差
output_host += layer1_weight_values[io * num_input + ii] * input_data_host[ii];
}
// 对输出值进行 sigmoid 函数处理,得到最终的预测概率
float prob = 1 / (1 + exp(-output_host));
printf("output_prob[%d] = %f\n", io, prob);
}
}
int main() {
inference();
self_inference();
return 0;
}
4.2、项目实战2:python执行图像前处理 + opencv加载图像 + TensorRT模型推理
// tensorRT 头文件
#include <NvInfer.h>
#include <NvInferRuntime.h>
// CUDA 头文件
#include <cuda_runtime.h>
// C++ 头文件
#include <stdio.h>
#include <vector> // vector
#include <fstream> // ifstream
using namespace std; // ios
#include <opencv2/opencv.hpp>
#include <chrono> // 打印运行时间
#include <Python.h> // Python C API头文件
#include <iostream>
class TRTLogger : public nvinfer1::ILogger {
public:
virtual void log(Severity severity, nvinfer1::AsciiChar const* msg) noexcept override {
if (severity <= Severity::kINFO)
printf("%d: %s\n", severity, msg);
}
} logger;
vector<unsigned char> load_engine_model(const string& file) {
ifstream in(file, ios::in | ios::binary); // 打开二进制文件
if (!in.is_open()) // 如果文件无法打开,返回一个空的 vector
return {};
in.seekg(0, ios::end); // 定位到文件结束处以获取文件长度
size_t length = in.tellg(); //获取文件长度
std::vector<uint8_t> data; // 准备存储模型数据的 vector
if (length > 0) {
in.seekg(0, ios::beg); // 重新定位到文件开始处
data.resize(length); // 调整 vector 大小以容纳整个文件
in.read((char*)&data[0], length); // 从文件中读取数据到 vector
}
in.close(); // 关闭文件流
return data;
}
void inference(const cv::Mat& input_image, cv::Mat& output_image)
{
// ------------------------------ 1. 加载模型 ----------------------------
TRTLogger logger; // 创建 TensorRT 的日志记录器
auto engine_data = load_engine_model("your_engine_model.engine"); // 从文件中加载 TensorRT 模型
nvinfer1::IRuntime* runtime = nvinfer1::createInferRuntime(logger); // 创建 TensorRT 运行时实例
nvinfer1::ICudaEngine* engine = runtime->deserializeCudaEngine(engine_data.data(), engine_data.size()); // 从二进制数据中反序列化得到 TensorRT CUDA 引擎
// 检查引擎是否成功加载
if (engine == nullptr) {
printf("Deserialize cuda engine failed.\n");
runtime->destroy(); // 释放 TensorRT 运行时资源
return;
}
// ------------------------------ 2. 创建 TensorRT 的执行上下文和 CUDA 流 ----------------------------
nvinfer1::IExecutionContext* execution_context = engine->createExecutionContext(); // 创建 TensorRT 的执行上下文(用于推理)
// 创建 CUDA 流(用于异步推理)
cudaStream_t stream = nullptr;
cudaStreamCreate(&stream);
// ------------------------------ 3. 准备推理数据 ----------------------------
float* input_data_device = nullptr; // 定义输入数据的设备指针
float* output_data_device = nullptr; // 定义输出数据的设备指针
// ------------------------------ 4. 将数据搬运到GPU ----------------------------
//cudaMalloc(&input_data_device, sizeof(input_data_host)); // 在 GPU 上分配输入数据的内存
//cudaMalloc(&output_data_device, sizeof(output_data_host)); // 在 GPU 上分配输出数据的内存
//cudaMemcpyAsync(input_data_device, input_data_host, sizeof(input_data_host), cudaMemcpyHostToDevice, stream); // 将输入数据从主机异步搬运到 GPU 上
// 分配 GPU 内存并将输入图像数据拷贝到 GPU 内存中
cv::Mat input_data_float;
input_image.convertTo(input_data_float, CV_32F);
cudaMalloc(&input_data_device, input_data_float.total() * sizeof(float));
cudaMemcpyAsync(input_data_device, input_data_float.ptr<float>(), input_data_float.total() * sizeof(float), cudaMemcpyHostToDevice, stream);
// 分配 GPU 内存以存储输出图像数据
cv::Mat output_data_float(input_image.size(), CV_32F);
cudaMalloc(&output_data_device, output_data_float.total() * sizeof(float));
// ------------------------------ 5. 推理 ----------------------------
float* bindings[] = { input_data_device, output_data_device }; // 定义 TensorRT 推理输入和输出的指针数组
bool success = execution_context->enqueueV2((void**)bindings, stream, nullptr); // 在执行上下文中进行异步推理
// ------------------------------ 6. 将推理结果搬运回CPU ----------------------------
//cudaMemcpyAsync(output_data_host, output_data_device, sizeof(output_data_host), cudaMemcpyDeviceToHost, stream); // 将输出数据从 GPU 异步搬运到主机上
//cudaStreamSynchronize(stream); // 等待 GPU 操作完成
cudaMemcpyAsync(output_data_float.ptr<float>(), output_data_device, output_data_float.total() * sizeof(float), cudaMemcpyDeviceToHost, stream);
cudaStreamSynchronize(stream); // 等待 GPU 操作完成
output_data_float.convertTo(output_image, CV_8U);
// ------------------------------ 7. 释放内存 ----------------------------
cudaStreamDestroy(stream); // 销毁 CUDA 流
execution_context->destroy(); // 销毁 TensorRT 执行上下文
engine->destroy(); // 销毁 TensorRT 引擎
runtime->destroy(); // 销毁 TensorRT 运行时
}
int main()
{
// -----------------------------------------------------------------------------------------
// (1)C++调用Python函数,完成图像前处理
// -----------------------------------------------------------------------------------------
Py_Initialize(); // 初始化 python 解释器
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('C:/Users/Administrator/Desktop/Project1')"); // 备注:\是转义符
PyObject* module = PyImport_ImportModule("call_python");
PyObject* add_func = PyObject_GetAttrString(module, "add");
PyObject* args = PyTuple_New(2); // 创建一个包含两个元素的空元组
PyTuple_SetItem(args, 0, Py_BuildValue("i", 1)); // 在元组的第一个位置0,传入"i"整数 1(int类型)
PyTuple_SetItem(args, 1, Py_BuildValue("i", 2)); // 在元组的第二个位置1,传入"i"整数 2(int类型)
PyObject* ret = PyObject_CallObject(add_func, args);
int result; // 定义一个整数变量(result)
PyArg_Parse(ret, "i", &result); // 解析 Python 函数的返回值(ret);将其解析为整数并保存到(result)变量中
std::cout << "return value = " << result << std::endl;
// -----------------------------------------------------------------------------------------
Py_Finalize(); // 关闭 Python 解释器
// -----------------------------------------------------------------------------------------
// (2)OpenCV加载图像
// -----------------------------------------------------------------------------------------
cv::Mat input_image = cv::imread("image.jpg", cv::IMREAD_COLOR); // 读取输入图像
if (input_image.empty()) {
std::cerr << "Failed to load input image." << std::endl;
return 1;
}
cv::Mat output_image(input_image.size(), CV_8UC3); // 创建输出图像(与输入图像相同大小)
cv::imshow("input_image", input_image); // 显示图像
cv::waitKey(0); //等待键盘响应
// -----------------------------------------------------------------------------------------
// (3)TensorRT模型推理
// -----------------------------------------------------------------------------------------
auto start_time = std::chrono::high_resolution_clock::now(); // 初始时间
inference(input_image, output_image); // 模型推理
auto end_time = std::chrono::high_resolution_clock::now(); // 结束时间
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time).count(); // 计算时间
std::cout << "Inference time: " << duration << " milliseconds" << std::endl; // 打印时间
cv::imshow("output_image", output_image); // 显示输出图像
cv::waitKey(0); //等待键盘响应
return 0;
}