TensorRT C++ API模型加速 —— TensorRT配置、模型转换、CUDA C++加速、TensorRT C++ 加速

news2025/2/26 23:07:12

文章目录

  • 前言: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
  • 支持的框架模型: TensorFlowPyTorchONNXMatlab
  • TensorRT 头文件:NvInfer.hNvInferRuntime.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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2261418.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RPC 服务与 gRPC 的入门案例

RPC 协议 RPC&#xff08;Remote Procedure Call Protocol&#xff09;即远程过程调用协议&#xff0c;它是一种通过网络从远程计算机程序上请求服务的协议&#xff0c;允许一个计算机程序可以像调用本地服务一样调用远程服务 。 RPC的主要作用是不同的服务间方法调用就像本地…

基于Spring Boot的体育商品推荐系统

一、系统背景与目的 随着电子商务的快速发展和人们健康意识的提高&#xff0c;体育商品市场呈现出蓬勃发展的态势。然而&#xff0c;传统的体育商品销售方式存在商品种类繁多、用户选择困难、个性化需求无法满足等问题。为了解决这些问题&#xff0c;基于Spring Boot的体育商品…

在 DDD 中优雅的发送 Kafka 消息

前言 1:host 映射 下载 SwitchHost 配置一个映射地址。点击 添加一个本地环境&#xff0c;之后配置你的 IP kafka 这样就能找这个地址了。IP 为你本地的IP&#xff0c;如果是云服务器就是公网IP地址 使用docker-compose.yml进行一键部署安装 version: 3.0 # docker-compose …

数字电视标准与分类

数字电视相关内容是一个极其成熟且久远的领域&#xff0c;并不像其它的技术方面那么前沿。但是学习技术的另外一个方面也不就是可以维持咱们的好奇心以及认识生活中多个事务后面的技术本质。 近年来&#xff0c;电视领域发生了一系列的变化&#xff0c;电视数字化的进程明显加快…

AI视频配音技术创新应用与商业机遇

随着人工智能技术的飞速发展&#xff0c;AI视频配音技术已经成为内容创作者和营销人员的新宠。这项技术不仅能够提升视频内容的吸引力&#xff0c;还能为特定行业带来创新的解决方案。本文将探讨AI视频配音技术的应用场景&#xff0c;并讨论如何合法合规地利用这一技术。 AI视频…

Airbus结构数字样机理念及实践(转)

关注作者 1、数字样机的背景 早期的设计文档通过二维工程图来描述&#xff0c;对工程师来说&#xff0c;绘制工程图足够表达设计思想&#xff0c;工程图成为了标准的“工程师语言”。但是外围的用户通常通过透视图来表达设计意图&#xff0c;不得不产生了大量针对不同教育背景…

UNIAPP框架uView初步集成与开发设计

uView UI&#xff0c;是uni-app生态最优秀的UI框架&#xff0c;全面的组件和便捷的工具会让您信手拈来&#xff0c;如鱼得水。本文章分享UNIAPP集成使用uView页面动态开发设计。 一、使用HBuilder X 直接导入插件&#xff0c;下载后重启 uView - DCloud 插件市场 二、配置样…

【图像处理lec7】图像恢复、去噪

目录 一、图像退化与恢复概述 二、图像退化中的噪声模型 1、使用 imnoise 函数添加噪声 &#xff08;1&#xff09;imnoise 函数的概述 &#xff08;2&#xff09;函数语法 &#xff08;3&#xff09;支持的噪声类型与具体语法 &#xff08;4&#xff09;噪声类型的详细…

dpdk中udp包的接受与发送

预备知识 dpdk中一些关键宏定义与结构体定义 以太网帧相关 宏RTE_ETHER_ADDR_LEN mac地址长度&#xff0c;6字节48位 宏RTE_ETHER_TYPE_IPV4 代表ipv4 struct rte_ether_hdr 以太网帧头结构体&#xff0c;包含了三个成员变量&#xff0c;目的地址&#xff0c;源地址&#…

C语言进阶(2) ---- 指针的进阶

前言&#xff1a;指针的主题&#xff0c;我们在初阶的《指针》章节已经接触过了&#xff0c;我们知道了指针的概念&#xff1a; 1.指针就是个变量&#xff0c;用来存放地址&#xff0c;地址唯一标识一块内存空间。 2.指针的大小是固定的4/8个字节(32位平台/64位平台)。 3.指针是…

项目整体结构优化

文章目录 1.依赖配置方式1.作为专门管理依赖的模块2.作为父模块3.作为子模块4.注意事项1.关于relativePath的配置2.关于打包的配置3.遇到maven报错的解决方案1.首先刷新maven2.从子模块开始clean-install3.最终:在最顶级的模块clean-package 2.概览3.新建一个子模块sun-depende…

计算机视觉-边缘检测

图片分类 一张图片中可能有多个需要识别的物体&#xff0c;会用方框标注他们的位置和类别 例&#xff1a; 给出一张照片&#xff0c;计算机需要从中识别出这是一只猫 一张图片的计算量是较大的&#xff0c;这张图片的尺寸虽然是6464&#xff0c;因为每张图片有3个颜色通道&am…

多模块应用、发布使用第三方库(持续更新中)

目录: 1、多模块概述&#xff08;HAP、HSP、HAR&#xff09; HAR与HSP两种共享包的主要区别体现在&#xff1a; 2、三类模块&#xff1a; 3、创建项目&#xff1a;项目名&#xff1a;meituan &#xff08;1&#xff09;创建Ability类型的Module&#xff0c;编译后为HAP文件…

安卓 文件管理相关功能记录

文件管理细分为图片、视频、音乐、文件四类 目录 权限 静态声明权限 动态检查和声明权限方法 如何开始上述动态申请的流程 提示 图片 获取图片文件的对象列表 展示 删除 视频 获取视频文件的对象列表 获取视频file列表 按日期装载视频文件列表 展示 播放 删除…

CHIMA网络安全攻防大赛经验分享

比赛模式 第一轮&#xff1a;20分钟基础知识赛&#xff08;50道题&#xff09; 安全运维&#xff0c;法律法规&#xff0c;linux操作系统等 第二轮&#xff1a;50分钟CTF夺旗&#xff08;5道题&#xff09; 题目涵盖 密码学 运用多种工具&#xff0c;如ASCII对照&#xff0c…

QT 国际化(翻译)

QT国际化&#xff08;Internationalization&#xff0c;简称I18N&#xff09;是指将一个软件应用程序的界面、文本、日期、数字等元素转化为不同的语言和文化习惯的过程。这使得软件能够在不同的国家和地区使用&#xff0c;并且可以根据用户的语言和地区提供本地化的使用体验。…

[Java] 使用 VSCode 来开发 Java

目录 前言Java 环境怎么看自己是否已经配置完成&#xff1f;安装 JDK安装 Maven 环境修改 Maven 依赖源 完善 VS Code配置插件配置 Maven配置 Maven Settings配置 Maven 可执行文件地址 前言 由于使用 VSCode 编码已经成为习惯&#xff0c;并且它确实相对其他的 IDE 较为轻量化…

如何高效获取Twitter数据:Apify平台上的推特数据采集解决方案

引言 在数据分析和市场研究领域&#xff0c;Twitter&#xff08;现在的X&#xff09;数据一直是重要的信息来源。但是&#xff0c;自从Twitter更改API定价策略后&#xff0c;获取数据的成本大幅提升。本文将介绍一个经济实惠的替代方案。 为什么需要Twitter数据&#xff1f; …

vue3+ant design vue实现日期选择器不展示清除按钮

1、代码&#xff1a;只需设置:allowClear"false"即可 <a-date-pickerv-model:value"value1":disabledDate"disabledDate"change"queryRate":allowClear"false" />const disabledDate (current: Dayjs) > {// 获取…

S2CRNet 图像测评笔记 图像融合

空间分离曲线渲染网络用于高效高分辨率图像协调 开源地址&#xff1a; https://github.com/stefanLeong/S2CRNet 效果图&#xff1a; 左边是输入&#xff0c;最右边是效果&#xff1a;效果不是很理想&#xff0c;色差问题还在 本地代码&#xff1a; S2CRNet-demos-main