NVIDIA TensorRT 深度学习推理加速引擎详解

文章目录
- [NVIDIA TensorRT 深度学习推理加速引擎详解](#NVIDIA TensorRT 深度学习推理加速引擎详解)
- 第一部分:TensorRT概述
-
- 什么是TensorRT?
- TensorRT的核心功能和优势
-
- [1. 图优化](#1. 图优化)
- [2. 量化支持](#2. 量化支持)
- [3. 动态形状支持](#3. 动态形状支持)
- [4. 多平台支持](#4. 多平台支持)
- [5. 编程接口](#5. 编程接口)
- [6. 性能优势](#6. 性能优势)
- TensorRT的工作原理
-
- [1. 模型导入](#1. 模型导入)
- [2. 网络定义和优化](#2. 网络定义和优化)
- [3. 引擎构建](#3. 引擎构建)
- [4. 推理执行](#4. 推理执行)
- [5. 工作流程图示](#5. 工作流程图示)
- TensorRT的应用场景
-
- [1. 实时计算机视觉](#1. 实时计算机视觉)
- [2. 自然语言处理](#2. 自然语言处理)
- [3. 推荐系统](#3. 推荐系统)
- [4. 边缘计算](#4. 边缘计算)
- [5. 医疗影像分析](#5. 医疗影像分析)
- TensorRT与其他推理框架的比较
-
- [1. 与ONNX Runtime的比较](#1. 与ONNX Runtime的比较)
- [2. 与TensorFlow Lite的比较](#2. 与TensorFlow Lite的比较)
- [3. 与PyTorch JIT/TorchScript的比较](#3. 与PyTorch JIT/TorchScript的比较)
- 总结
- 第二部分:TensorRT安装与部署指南
-
- 系统要求和前提条件
- 不同平台的安装方法
- 验证安装
- 常见安装问题及解决方案
-
- [1. 库依赖问题](#1. 库依赖问题)
- [2. Python包导入错误](#2. Python包导入错误)
- [3. CUDA版本不兼容](#3. CUDA版本不兼容)
- 升级TensorRT
- 卸载TensorRT
- 总结
- 第三部分:TensorRT基础使用教程
-
- TensorRT工作流程概述
- 模型准备
- [使用C++ API创建TensorRT引擎](#使用C++ API创建TensorRT引擎)
- [使用Python API创建TensorRT引擎](#使用Python API创建TensorRT引擎)
- 使用TensorRT执行推理
- 高级配置选项
- 性能优化技巧
-
- [1. 批处理推理](#1. 批处理推理)
- [2. 使用CUDA图](#2. 使用CUDA图)
- [3. 内存优化](#3. 内存优化)
- [4. 选择最佳精度](#4. 选择最佳精度)
- 总结
- [第四部分:TensorRT API详解与示例](#第四部分:TensorRT API详解与示例)
-
- [TensorRT API概述](#TensorRT API概述)
- [C++ API详解](#C++ API详解)
-
- 核心类和对象
- [详细的C++ API示例](#详细的C++ API示例)
- [高级C++ API功能](#高级C++ API功能)
-
- [1. 动态形状支持](#1. 动态形状支持)
- [2. INT8量化](#2. INT8量化)
- [3. 使用CUDA图](#3. 使用CUDA图)
- [Python API详解](#Python API详解)
-
- 核心类和对象
- [详细的Python API示例](#详细的Python API示例)
- [高级Python API功能](#高级Python API功能)
-
- [1. 动态形状支持](#1. 动态形状支持)
- [2. INT8量化与校准](#2. INT8量化与校准)
- [3. 使用CUDA图](#3. 使用CUDA图)
- [TensorRT API最佳实践](#TensorRT API最佳实践)
-
- [1. 内存管理](#1. 内存管理)
- [2. 错误处理](#2. 错误处理)
- [3. 性能优化](#3. 性能优化)
- [4. 可移植性](#4. 可移植性)
- 总结
- 第五部分:TensorRT应用场景实战
- 第六部分:TensorRT高级特性与优化技巧
-
- 量化技术
- 动态形状支持
- 层间融合和张量核心加速
-
- 层间融合
- [使用Tensor Cores](#使用Tensor Cores)
- 多流并行处理
- 内存优化
- 性能分析和调试
-
- 使用CUDA性能分析器
- [使用TensorRT的Inspector API](#使用TensorRT的Inspector API)
- 最佳实践和优化技巧
-
- [1. 选择合适的精度](#1. 选择合适的精度)
- [2. 使用动态批处理](#2. 使用动态批处理)
- [3. 优化输入数据处理](#3. 优化输入数据处理)
- [4. 模型优化](#4. 模型优化)
- [5. 使用TensorRT插件](#5. 使用TensorRT插件)
- [6. 使用ONNX-TensorRT解析器](#6. 使用ONNX-TensorRT解析器)
- [7. 使用TF-TRT或PyTorch-TensorRT](#7. 使用TF-TRT或PyTorch-TensorRT)
- 总结
引言
在当今人工智能和深度学习快速发展的时代,模型推理性能已成为实际应用中的关键瓶颈。无论是需要实时响应的自动驾驶系统,还是要处理海量数据的推荐引擎,亦或是资源受限的边缘设备上的AI应用,都对推理速度、延迟和能耗提出了严苛的要求。NVIDIA TensorRT作为一个高性能的深度学习推理优化器和运行时环境,正是为解决这些挑战而生。
本文将全面深入地介绍NVIDIA TensorRT,从基本概念到安装部署,再到实际应用案例,帮助读者全面掌握这一强大工具。无论您是AI研究人员、深度学习工程师,还是对推理优化感兴趣的开发者,本文都将为您提供宝贵的指导和参考。
文章结构
本文将分为以下几个主要部分:
-
TensorRT概述:介绍TensorRT的基本概念、核心功能和工作原理,帮助读者建立对TensorRT的整体认识。
-
安装与部署:详细说明TensorRT在不同平台(Linux、Windows、Docker等)上的安装步骤、系统要求和配置方法。
-
基础使用教程:通过实例讲解TensorRT的基本使用流程,包括模型转换、优化和部署等关键步骤。
-
API详解与示例:分别介绍C++和Python API的使用方法,提供丰富的代码示例(附中文注释)。
-
应用场景实战:展示TensorRT在图像分类、目标检测、语义分割等实际应用中的使用方法和性能优势。
-
高级特性与优化:探讨TensorRT的高级功能,如量化、动态形状支持、自定义层等,以及性能优化的最佳实践。
-
常见问题与解决方案:总结使用TensorRT过程中可能遇到的问题及其解决方法。
让我们开始这段深入了解NVIDIA TensorRT的旅程,探索如何将您的深度学习模型转变为高性能的推理引擎。
第一部分:TensorRT概述
什么是TensorRT?
NVIDIA TensorRT是一个高性能深度学习推理优化器和运行时引擎,专为NVIDIA GPU设计,旨在提供低延迟和高吞吐量的模型推理。作为一个C++库,TensorRT可以读取通过各种框架(如TensorFlow、PyTorch、Caffe等)训练的模型,然后针对特定的GPU架构和应用需求进行优化,生成高度优化的推理引擎。
TensorRT不仅仅是一个简单的模型转换工具,它是一个完整的推理平台,提供了从模型优化到高效执行的全套功能。通过使用TensorRT,开发者可以显著提高深度学习应用的推理性能,同时减少延迟和能耗,这对于实时应用、边缘计算和大规模部署尤为重要。
TensorRT的核心功能和优势
1. 图优化
TensorRT通过一系列复杂的图优化技术来提高模型的执行效率:
-
层融合(Layer Fusion):将多个连续的层(如卷积+偏置+激活)合并为单一的优化操作,减少内存访问和计算开销。例如,将卷积层、批量归一化层和ReLU激活层融合为一个单一的优化层。
-
内核自动调优(Kernel Auto-Tuning):TensorRT会为特定的GPU架构和层配置选择最优的CUDA内核实现,确保最佳性能。
-
动态张量内存管理:优化内存分配和重用,减少内存占用和数据传输开销。
-
多精度优化:支持FP32(单精度浮点)、FP16(半精度浮点)和INT8(8位整数)等多种精度模式,可根据应用需求在精度和性能之间取得平衡。
2. 量化支持
TensorRT提供了强大的量化功能,可以将模型从FP32精度转换为FP16或INT8精度,在保持模型准确性的同时显著提高性能:
-
FP16精度:相比FP32,可以减少内存使用和带宽需求,同时在支持半精度计算的GPU上获得更高的计算吞吐量。
-
INT8量化:通过将浮点权重和激活值量化为8位整数,可以进一步提高性能,特别适合推理工作负载。TensorRT提供了校准工具,帮助确保量化后的模型保持准确性。
-
稀疏性利用:能够利用模型中的结构化稀疏性来加速计算。
3. 动态形状支持
TensorRT支持动态输入形状,使得同一个优化引擎可以处理不同大小的输入:
- 优化配置文件:允许为不同的输入形状范围定义优化配置文件。
- 运行时形状调整:在推理时可以动态指定输入形状,无需重新构建引擎。
4. 多平台支持
TensorRT可以在各种NVIDIA平台上运行,从数据中心的高性能GPU到嵌入式设备:
- 数据中心GPU:如Tesla V100、A100等。
- 工作站GPU:如GeForce RTX系列、Quadro系列。
- 嵌入式平台:如Jetson系列(Nano、TX2、Xavier、Orin等)。
5. 编程接口
TensorRT提供了多种编程接口,满足不同开发者的需求:
- C++API:提供最完整的功能和最高的性能。
- Python API:提供更便捷的开发体验,特别适合快速原型设计和实验。
- 与深度学习框架的集成:如TensorFlow-TensorRT(TF-TRT)和ONNX解析器,简化了从训练框架到TensorRT的转换过程。
6. 性能优势
使用TensorRT可以获得显著的性能提升:
- 低延迟:通过优化计算图和内存访问,减少推理延迟,对实时应用至关重要。
- 高吞吐量:优化的批处理能力,适合需要处理大量数据的应用场景。
- 能效提升:通过精度降低和计算优化,减少能耗,特别适合边缘设备和移动平台。
根据NVIDIA的测试,使用TensorRT优化后的模型相比原始框架可以获得数倍甚至数十倍的性能提升,具体提升幅度取决于模型结构、硬件平台和优化配置。
TensorRT的工作原理
TensorRT的工作流程可以分为以下几个关键阶段:
1. 模型导入
TensorRT首先需要导入训练好的深度学习模型。它支持多种导入方式:
- 直接通过API构建网络:使用TensorRT的Network Definition API从头构建网络。
- 解析预训练模型:使用内置的解析器(如ONNX解析器)导入预训练模型。
- 通过框架特定的转换器:如TF-TRT(TensorFlow-TensorRT)。
2. 网络定义和优化
导入模型后,TensorRT会创建一个网络定义,然后应用一系列优化:
- 层融合:识别可以合并的层模式,如卷积+批归一化+ReLU。
- 张量精度校准:对于INT8量化,TensorRT会使用代表性数据集进行校准,确定最佳的量化参数。
- 内核选择:为每个操作选择最优的CUDA内核实现。
- 内存优化:规划最佳的内存分配和重用策略。
3. 引擎构建
优化后的网络被编译成一个高度优化的引擎,专门针对目标GPU架构:
- 代码生成:生成针对特定GPU架构优化的CUDA代码。
- 内存规划:确定张量的内存布局和分配策略。
- 序列化:可以将构建好的引擎序列化到磁盘,以便后续快速加载。
4. 推理执行
使用构建好的引擎执行推理:
- 内存分配:为输入和输出张量分配GPU内存。
- 数据传输:将输入数据从主机内存复制到GPU内存。
- 执行推理:调用优化后的CUDA内核执行计算。
- 结果获取:将输出结果从GPU内存复制回主机内存。
5. 工作流程图示
以下是TensorRT工作流程的简化图示:
训练好的模型 → 模型导入 → 网络定义 → 优化 → 引擎构建 → 序列化 → 推理执行
↑
校准数据集(用于INT8量化)
TensorRT的应用场景
TensorRT广泛应用于各种需要高性能深度学习推理的场景:
1. 实时计算机视觉
- 自动驾驶:需要低延迟处理来自多个传感器的数据。
- 视频分析:实时监控和分析视频流。
- 增强现实:需要在移动设备上实时处理视觉信息。
2. 自然语言处理
- 语音识别:实时转录和理解语音输入。
- 聊天机器人和虚拟助手:需要快速响应用户查询。
- 机器翻译:实时翻译文本或语音。
3. 推荐系统
- 电子商务:实时生成个性化产品推荐。
- 内容平台:为用户推荐相关内容。
4. 边缘计算
- 智能摄像头:在设备上进行对象检测和识别。
- 机器人:实时感知和决策。
- 物联网设备:在资源受限的设备上运行AI模型。
5. 医疗影像分析
- 诊断辅助:快速分析医学图像。
- 实时手术辅助:在手术过程中提供实时分析和指导。
TensorRT与其他推理框架的比较
与其他深度学习推理框架相比,TensorRT具有以下特点:
1. 与ONNX Runtime的比较
- 性能:在NVIDIA GPU上,TensorRT通常比ONNX Runtime提供更高的性能。
- 优化程度:TensorRT提供更深度的GPU特定优化。
- 灵活性:ONNX Runtime支持更多硬件平台,而TensorRT专注于NVIDIA GPU。
2. 与TensorFlow Lite的比较
- 目标平台:TensorFlow Lite主要针对移动和嵌入式设备,而TensorRT覆盖从嵌入式设备到高性能服务器的NVIDIA平台。
- 性能:在NVIDIA硬件上,TensorRT通常提供更高的性能。
- 生态系统:TensorFlow Lite与TensorFlow生态系统紧密集成。
3. 与PyTorch JIT/TorchScript的比较
- 优化深度:TensorRT提供更深层次的硬件特定优化。
- 使用便捷性:PyTorch JIT对PyTorch用户来说更为直接和简单。
- 性能:在NVIDIA GPU上,TensorRT通常能提供更高的推理性能。
总结
NVIDIA TensorRT是一个功能强大的深度学习推理优化器和运行时引擎,通过一系列先进的优化技术,显著提高了深度学习模型的推理性能。它支持多种精度模式、动态形状和各种NVIDIA平台,为开发者提供了灵活而高效的推理解决方案。
无论是需要低延迟的实时应用,还是高吞吐量的批处理任务,或是在资源受限设备上的部署,TensorRT都能提供卓越的性能和效率。随着深度学习在各行各业的广泛应用,TensorRT作为推理优化的关键工具,将继续发挥重要作用。
在接下来的章节中,我们将详细介绍如何安装和配置TensorRT,以及如何使用它来优化和部署您的深度学习模型。
第二部分:TensorRT安装与部署指南
在开始使用NVIDIA TensorRT进行深度学习模型优化和推理之前,我们首先需要正确安装和配置TensorRT环境。本章将详细介绍TensorRT的系统要求、不同平台的安装方法以及相关配置步骤,帮助您快速搭建TensorRT开发环境。
系统要求和前提条件
在安装TensorRT之前,请确保您的系统满足以下基本要求:
硬件要求
-
支持的GPU:NVIDIA GPU,包括:
- 数据中心GPU:Tesla、A100、A30、A10等
- 工作站GPU:GeForce RTX系列、Quadro系列
- 嵌入式平台:Jetson系列(Nano、TX2、Xavier、Orin等)
-
最低GPU计算能力:
- 对于TensorRT 8.x版本:支持CUDA计算能力3.0或更高版本的GPU
- 对于最新特性(如稀疏性、多实例GPU等):建议使用计算能力7.0或更高版本的GPU
软件要求
-
操作系统:
- Linux:Ubuntu 18.04/20.04/22.04 LTS、RHEL/CentOS 7/8
- Windows:Windows 10/11、Windows Server 2019/2022
- 对于Jetson平台:JetPack SDK(包含特定版本的L4T操作系统)
-
CUDA工具包:
- TensorRT 8.x需要CUDA 11.0或更高版本
- 建议安装最新的CUDA版本以获得最佳性能
-
cuDNN库:
- 与所安装的CUDA版本兼容的cuDNN版本
- 通常需要cuDNN 8.x或更高版本
-
Python(可选,用于Python API):
- Python 3.6或更高版本
- 推荐使用Python 3.8或3.9以获得最佳兼容性
不同平台的安装方法
Linux平台安装
Linux是TensorRT的主要支持平台,提供了多种安装方式。
方法1:使用包管理器安装(推荐)
通过NVIDIA软件包存储库安装是最简单的方法:
bash
# 添加NVIDIA软件包存储库
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/cuda-ubuntu2004.pin
sudo mv cuda-ubuntu2004.pin /etc/apt/preferences.d/cuda-repository-pin-600
sudo apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/7fa2af80.pub
sudo add-apt-repository "deb https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/ /"
# 更新软件包列表
sudo apt-get update
# 安装TensorRT
sudo apt-get install libnvinfer8 libnvinfer-plugin8 libnvparsers8 libnvonnxparsers8 libnvinfer-dev libnvinfer-plugin-dev libnvparsers-dev libnvonnxparsers-dev libnvinfer-samples tensorrt-dev
# 安装Python API(可选)
sudo apt-get install python3-libnvinfer
方法2:使用TAR包安装
如果您需要特定版本或无法使用包管理器,可以下载TAR包手动安装:
- 访问NVIDIA TensorRT下载页面
- 下载适合您系统的TAR包
- 解压TAR包:
bash
tar -xzvf TensorRT-8.x.x.x.Linux.x86_64-gnu.cuda-xx.x.tar.gz
- 设置环境变量:
bash
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:<TensorRT-安装路径>/lib
- 安装Python包(可选):
bash
cd <TensorRT-安装路径>/python
pip install tensorrt-*-cp3x-none-linux_x86_64.whl
cd <TensorRT-安装路径>/graphsurgeon
pip install graphsurgeon-0.4.x-py2.py3-none-any.whl
cd <TensorRT-安装路径>/onnx_graphsurgeon
pip install onnx_graphsurgeon-0.3.x-py2.py3-none-any.whl
方法3:使用NGC容器(适用于容器化部署)
NVIDIA NGC提供了预装TensorRT的Docker容器:
bash
# 拉取TensorRT容器
docker pull nvcr.io/nvidia/tensorrt:22.12-py3
# 运行容器
docker run --gpus all -it --rm nvcr.io/nvidia/tensorrt:22.12-py3
Windows平台安装
Windows平台上安装TensorRT的步骤如下:
-
确保已安装兼容版本的CUDA和cuDNN
-
下载适用于Windows的ZIP包
-
解压ZIP包到所需位置
-
将TensorRT的bin目录添加到系统PATH环境变量:
C:\Program Files\NVIDIA\TensorRT\lib
-
安装Python包(可选):
cmd
cd C:\Program Files\NVIDIA\TensorRT\python
pip install tensorrt-*-cp3x-none-win_amd64.whl
cd C:\Program Files\NVIDIA\TensorRT\graphsurgeon
pip install graphsurgeon-0.4.x-py2.py3-none-any.whl
cd C:\Program Files\NVIDIA\TensorRT\onnx_graphsurgeon
pip install onnx_graphsurgeon-0.3.x-py2.py3-none-any.whl
使用Docker安装
使用Docker是跨平台使用TensorRT的便捷方式,避免了环境配置的复杂性:
bash
# 拉取官方TensorRT容器
docker pull nvcr.io/nvidia/tensorrt:22.12-py3
# 运行容器,挂载本地目录以便访问模型和数据
docker run --gpus all -it --rm -v /path/to/your/models:/models nvcr.io/nvidia/tensorrt:22.12-py3
Jetson平台安装
对于NVIDIA Jetson设备(如Jetson Nano、TX2、Xavier或Orin),TensorRT已包含在JetPack SDK中:
- 下载并安装最新版本的JetPack SDK
- 使用NVIDIA SDK Manager安装JetPack到您的Jetson设备
- TensorRT将自动安装为JetPack的一部分
验证安装
安装完成后,可以通过以下方法验证TensorRT是否正确安装:
检查TensorRT版本
bash
# 在Linux终端或Windows命令提示符中
dpkg -l | grep TensorRT # 仅适用于Debian/Ubuntu
# 或者使用Python验证
python3 -c "import tensorrt as trt; print(trt.__version__)"
运行示例程序
TensorRT安装包中包含多个示例程序,可以用来验证安装:
bash
# 在Linux上
cd <TensorRT-安装路径>/samples
make
cd bin
./sample_mnist
# 在Windows上
cd C:\Program Files\NVIDIA\TensorRT\samples
msbuild samples_vs2019.sln
cd C:\Program Files\NVIDIA\TensorRT\bin
sample_mnist.exe
常见安装问题及解决方案
1. 库依赖问题
问题:安装后出现库依赖错误。
解决方案:
bash
# 检查并安装缺失的依赖
sudo apt-get install libcudnn8 libcublas10
2. Python包导入错误
问题:无法导入tensorrt模块。
解决方案:
bash
# 确认Python包安装路径
pip list | grep tensorrt
# 重新安装Python包
pip uninstall tensorrt
cd <TensorRT-安装路径>/python
pip install tensorrt-*-cp3x-none-linux_x86_64.whl
3. CUDA版本不兼容
问题:TensorRT与已安装的CUDA版本不兼容。
解决方案:
bash
# 检查CUDA版本
nvcc --version
# 安装兼容的TensorRT版本或更新CUDA
升级TensorRT
当需要升级到新版本的TensorRT时,请按照以下步骤操作:
Linux平台升级
bash
# 使用包管理器升级
sudo apt-get update
sudo apt-get upgrade libnvinfer* tensorrt*
# 或者使用TAR包手动升级
# 1. 下载新版本的TAR包
# 2. 解压到新目录
# 3. 更新环境变量指向新目录
Windows平台升级
- 下载新版本的ZIP包
- 解压到新位置或覆盖旧版本
- 更新PATH环境变量(如果安装位置变更)
- 重新安装Python包
卸载TensorRT
如果需要卸载TensorRT,可以按照以下步骤操作:
Linux平台卸载
bash
# 使用包管理器卸载
sudo apt-get remove --purge libnvinfer* tensorrt*
# 如果是手动安装,删除安装目录并移除环境变量设置
rm -rf <TensorRT-安装路径>
# 从~/.bashrc或~/.profile中移除相关环境变量设置
Windows平台卸载
- 从控制面板的"程序和功能"中卸载TensorRT
- 或者直接删除安装目录
- 从系统PATH环境变量中移除TensorRT路径
总结
本章详细介绍了TensorRT的安装和部署方法,涵盖了不同平台(Linux、Windows、Docker和Jetson)的安装步骤、验证方法以及常见问题的解决方案。通过正确安装和配置TensorRT,您已经为后续的深度学习模型优化和部署工作奠定了基础。
在下一章中,我们将开始探索TensorRT的基本使用流程,包括如何导入模型、优化网络以及执行推理。
第三部分:TensorRT基础使用教程
在完成TensorRT的安装和配置后,本章将带您了解TensorRT的基本使用流程。我们将通过详细的步骤和丰富的代码示例,展示如何使用TensorRT来优化深度学习模型并执行高效推理。
TensorRT工作流程概述
使用TensorRT进行模型优化和推理的典型工作流程包括以下几个主要步骤:
- 模型准备:获取预训练模型或从深度学习框架导出模型
- 创建TensorRT网络:通过解析器导入模型或使用API构建网络
- 构建优化引擎:配置优化参数并构建TensorRT引擎
- 序列化引擎:将引擎保存到磁盘以便后续使用
- 执行推理:加载引擎并执行高效推理
下面我们将详细介绍每个步骤的具体操作方法。
模型准备
在使用TensorRT之前,您需要准备好要优化的深度学习模型。TensorRT支持多种模型导入方式,最常用的是通过ONNX格式。
从PyTorch导出ONNX模型
python
# 从PyTorch导出ONNX模型
import torch
import torchvision.models as models
# 加载预训练模型
model = models.resnet50(pretrained=True)
model.eval()
# 创建示例输入
dummy_input = torch.randn(1, 3, 224, 224)
# 导出ONNX模型
torch.onnx.export(model, # 要导出的模型
dummy_input, # 模型输入
"resnet50.onnx", # 输出文件名
export_params=True, # 存储训练好的参数权重
opset_version=13, # ONNX算子集版本
do_constant_folding=True, # 是否执行常量折叠优化
input_names=["input"], # 输入名称
output_names=["output"], # 输出名称
dynamic_axes={"input": {0: "batch_size"}, # 动态轴
"output": {0: "batch_size"}})
print("ONNX模型已导出到resnet50.onnx")
从TensorFlow导出ONNX模型
对于TensorFlow模型,可以使用tf2onnx
工具进行转换:
python
# 从TensorFlow导出ONNX模型
import tensorflow as tf
import tf2onnx
# 加载预训练模型
model = tf.keras.applications.ResNet50(weights='imagenet')
# 转换为ONNX
spec = (tf.TensorSpec((None, 224, 224, 3), tf.float32, name="input"),)
output_path = "resnet50.onnx"
model_proto, _ = tf2onnx.convert.from_keras(model, input_signature=spec, opset=13, output_path=output_path)
print("ONNX模型已导出到resnet50.onnx")
使用C++ API创建TensorRT引擎
下面是使用C++ API从ONNX模型创建TensorRT引擎的完整示例:
cpp
#include "NvInfer.h"
#include "NvOnnxParser.h"
#include <fstream>
#include <iostream>
#include <memory>
// 自定义日志记录器类
class Logger : public nvinfer1::ILogger {
public:
void log(Severity severity, const char* msg) noexcept override {
// 忽略INFO级别的消息
if (severity <= Severity::kWARNING)
std::cout << msg << std::endl;
}
} logger;
// 智能指针辅助函数,用于自动释放TensorRT对象
template <typename T>
struct TRTDestroy {
void operator()(T* obj) const {
if (obj)
obj->destroy();
}
};
template <typename T>
using TRTUniquePtr = std::unique_ptr<T, TRTDestroy<T>>;
// 从文件读取二进制数据
std::vector<unsigned char> readFile(const std::string& fileName) {
std::ifstream file(fileName, std::ios::binary | std::ios::ate);
size_t size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<unsigned char> buffer(size);
file.read(reinterpret_cast<char*>(buffer.data()), size);
file.close();
return buffer;
}
// 将引擎序列化并保存到文件
void saveEngine(const nvinfer1::ICudaEngine& engine, const std::string& fileName) {
TRTUniquePtr<nvinfer1::IHostMemory> serializedEngine{engine.serialize()};
std::ofstream file(fileName, std::ios::binary);
file.write(reinterpret_cast<const char*>(serializedEngine->data()), serializedEngine->size());
file.close();
}
int main(int argc, char* argv[]) {
if (argc < 3) {
std::cerr << "用法: " << argv[0] << " <onnx_model> <engine_file>" << std::endl;
return 1;
}
std::string onnxFile = argv[1];
std::string engineFile = argv[2];
// 创建构建器
TRTUniquePtr<nvinfer1::IBuilder> builder{nvinfer1::createInferBuilder(logger)};
if (!builder) {
std::cerr << "创建构建器失败" << std::endl;
return 1;
}
// 创建网络定义
const auto explicitBatch = 1U << static_cast<uint32_t>(nvinfer1::NetworkDefinitionCreationFlag::kEXPLICIT_BATCH);
TRTUniquePtr<nvinfer1::INetworkDefinition> network{builder->createNetworkV2(explicitBatch)};
if (!network) {
std::cerr << "创建网络定义失败" << std::endl;
return 1;
}
// 创建ONNX解析器
TRTUniquePtr<nvonnxparser::IParser> parser{nvonnxparser::createParser(*network, logger)};
if (!parser) {
std::cerr << "创建ONNX解析器失败" << std::endl;
return 1;
}
// 解析ONNX模型
auto onnxData = readFile(onnxFile);
if (!parser->parse(onnxData.data(), onnxData.size())) {
std::cerr << "解析ONNX模型失败" << std::endl;
return 1;
}
// 创建构建配置
TRTUniquePtr<nvinfer1::IBuilderConfig> config{builder->createBuilderConfig()};
if (!config) {
std::cerr << "创建构建配置失败" << std::endl;
return 1;
}
// 设置最大工作空间大小(1GB)
config->setMemoryPoolLimit(nvinfer1::MemoryPoolType::kWORKSPACE, 1ULL << 30);
// 启用FP16精度(如果GPU支持)
if (builder->platformHasFastFp16()) {
std::cout << "启用FP16精度" << std::endl;
config->setFlag(nvinfer1::BuilderFlag::kFP16);
}
// 构建引擎
std::cout << "构建TensorRT引擎..." << std::endl;
TRTUniquePtr<nvinfer1::ICudaEngine> engine{builder->buildEngineWithConfig(*network, *config)};
if (!engine) {
std::cerr << "构建引擎失败" << std::endl;
return 1;
}
// 保存引擎到文件
std::cout << "将引擎序列化到: " << engineFile << std::endl;
saveEngine(*engine, engineFile);
std::cout << "引擎创建成功!" << std::endl;
return 0;
}
编译上述代码:
bash
g++ -o build_engine build_engine.cpp -I/usr/local/cuda/include -I/usr/include/x86_64-linux-gnu -L/usr/local/cuda/lib64 -lnvinfer -lnvonnxparser -lcudart
使用Python API创建TensorRT引擎
以下是使用Python API从ONNX模型创建TensorRT引擎的示例:
python
import os
import tensorrt as trt
import numpy as np
def build_engine_from_onnx(onnx_file_path, engine_file_path, precision='fp32'):
"""
从ONNX模型构建TensorRT引擎
参数:
onnx_file_path: ONNX模型文件路径
engine_file_path: 输出的TensorRT引擎文件路径
precision: 精度模式,可选 'fp32', 'fp16', 'int8'
返回:
构建好的TensorRT引擎
"""
# 创建logger和builder
TRT_LOGGER = trt.Logger(trt.Logger.WARNING)
builder = trt.Builder(TRT_LOGGER)
# 创建网络定义
network = builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH))
# 创建ONNX解析器
parser = trt.OnnxParser(network, TRT_LOGGER)
# 解析ONNX文件
with open(onnx_file_path, 'rb') as model:
if not parser.parse(model.read()):
print('ERROR: 解析ONNX模型失败')
for error in range(parser.num_errors):
print(parser.get_error(error))
return None
# 创建构建配置
config = builder.create_builder_config()
# 设置最大工作空间大小(1GB)
config.max_workspace_size = 1 << 30
# 根据指定精度设置标志
if precision == 'fp16' and builder.platform_has_fast_fp16:
print('启用FP16精度')
config.set_flag(trt.BuilderFlag.FP16)
elif precision == 'int8' and builder.platform_has_fast_int8:
print('启用INT8精度')
config.set_flag(trt.BuilderFlag.INT8)
# 注意:INT8需要校准器,这里简化处理
# 构建引擎
print('构建TensorRT引擎...')
serialized_engine = builder.build_serialized_network(network, config)
# 保存引擎到文件
with open(engine_file_path, 'wb') as f:
f.write(serialized_engine)
# 创建运行时并反序列化引擎
runtime = trt.Runtime(TRT_LOGGER)
engine = runtime.deserialize_cuda_engine(serialized_engine)
print(f'引擎已保存到: {engine_file_path}')
return engine
# 使用示例
if __name__ == '__main__':
onnx_model = 'resnet50.onnx'
engine_file = 'resnet50.engine'
# 构建FP16精度的引擎
engine = build_engine_from_onnx(onnx_model, engine_file, precision='fp16')
使用TensorRT执行推理
C++推理示例
以下是使用C++ API加载TensorRT引擎并执行推理的示例:
cpp
#include "NvInfer.h"
#include <fstream>
#include <iostream>
#include <memory>
#include <vector>
#include <cuda_runtime_api.h>
// 自定义日志记录器类
class Logger : public nvinfer1::ILogger {
public:
void log(Severity severity, const char* msg) noexcept override {
if (severity <= Severity::kWARNING)
std::cout << msg << std::endl;
}
} logger;
// 智能指针辅助函数
template <typename T>
struct TRTDestroy {
void operator()(T* obj) const {
if (obj)
obj->destroy();
}
};
template <typename T>
using TRTUniquePtr = std::unique_ptr<T, TRTDestroy<T>>;
// 从文件读取引擎
std::vector<unsigned char> readEngineFile(const std::string& fileName) {
std::ifstream file(fileName, std::ios::binary | std::ios::ate);
size_t size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<unsigned char> buffer(size);
file.read(reinterpret_cast<char*>(buffer.data()), size);
file.close();
return buffer;
}
int main(int argc, char* argv[]) {
if (argc < 2) {
std::cerr << "用法: " << argv[0] << " <engine_file>" << std::endl;
return 1;
}
std::string engineFile = argv[1];
// 创建运行时
TRTUniquePtr<nvinfer1::IRuntime> runtime{nvinfer1::createInferRuntime(logger)};
if (!runtime) {
std::cerr << "创建运行时失败" << std::endl;
return 1;
}
// 加载引擎
auto engineData = readEngineFile(engineFile);
TRTUniquePtr<nvinfer1::ICudaEngine> engine{
runtime->deserializeCudaEngine(engineData.data(), engineData.size())
};
if (!engine) {
std::cerr << "反序列化引擎失败" << std::endl;
return 1;
}
// 创建执行上下文
TRTUniquePtr<nvinfer1::IExecutionContext> context{engine->createExecutionContext()};
if (!context) {
std::cerr << "创建执行上下文失败" << std::endl;
return 1;
}
// 获取绑定索引
int inputIndex = engine->getBindingIndex("input");
int outputIndex = engine->getBindingIndex("output");
// 获取输入和输出维度
auto inputDims = engine->getBindingDimensions(inputIndex);
auto outputDims = engine->getBindingDimensions(outputIndex);
// 计算输入和输出大小
int batchSize = 1; // 假设批处理大小为1
int inputSize = batchSize;
int outputSize = batchSize;
for (int i = 1; i < inputDims.nbDims; i++)
inputSize *= inputDims.d[i];
for (int i = 1; i < outputDims.nbDims; i++)
outputSize *= outputDims.d[i];
// 分配主机和设备内存
std::vector<float> inputData(inputSize, 0.5f); // 示例输入数据
std::vector<float> outputData(outputSize);
void* deviceBuffers[2];
cudaMalloc(&deviceBuffers[inputIndex], inputSize * sizeof(float));
cudaMalloc(&deviceBuffers[outputIndex], outputSize * sizeof(float));
// 创建CUDA流
cudaStream_t stream;
cudaStreamCreate(&stream);
// 将输入数据复制到设备
cudaMemcpyAsync(deviceBuffers[inputIndex], inputData.data(),
inputSize * sizeof(float), cudaMemcpyHostToDevice, stream);
// 执行推理
context->enqueueV2(deviceBuffers, stream, nullptr);
// 将输出数据复制回主机
cudaMemcpyAsync(outputData.data(), deviceBuffers[outputIndex],
outputSize * sizeof(float), cudaMemcpyDeviceToHost, stream);
// 同步流
cudaStreamSynchronize(stream);
// 输出结果(仅显示前5个值)
std::cout << "推理结果 (前5个值):" << std::endl;
for (int i = 0; i < std::min(5, outputSize); i++) {
std::cout << " " << i << ": " << outputData[i] << std::endl;
}
// 清理资源
cudaFree(deviceBuffers[inputIndex]);
cudaFree(deviceBuffers[outputIndex]);
cudaStreamDestroy(stream);
return 0;
}
Python推理示例
以下是使用Python API加载TensorRT引擎并执行推理的示例:
python
import tensorrt as trt
import numpy as np
import pycuda.driver as cuda
import pycuda.autoinit
class TensorRTInference:
def __init__(self, engine_path):
"""
初始化TensorRT推理器
参数:
engine_path: TensorRT引擎文件路径
"""
# 创建logger和运行时
self.logger = trt.Logger(trt.Logger.WARNING)
self.runtime = trt.Runtime(self.logger)
# 加载引擎
with open(engine_path, 'rb') as f:
self.engine = self.runtime.deserialize_cuda_engine(f.read())
# 创建执行上下文
self.context = self.engine.create_execution_context()
# 创建CUDA流
self.stream = cuda.Stream()
# 获取绑定索引
self.input_binding_idxs = []
self.output_binding_idxs = []
for i in range(self.engine.num_bindings):
if self.engine.binding_is_input(i):
self.input_binding_idxs.append(i)
else:
self.output_binding_idxs.append(i)
# 为输出分配设备内存
self.output_buffers = {}
for i in self.output_binding_idxs:
name = self.engine.get_binding_name(i)
dtype = trt.nptype(self.engine.get_binding_dtype(i))
shape = self.context.get_binding_shape(i)
size = trt.volume(shape)
# 分配主机和设备内存
host_mem = cuda.pagelocked_empty(size, dtype)
device_mem = cuda.mem_alloc(host_mem.nbytes)
self.output_buffers[name] = {
'host': host_mem,
'device': device_mem,
'shape': shape
}
def infer(self, input_data):
"""
执行推理
参数:
input_data: 输入数据字典,键为输入名称,值为numpy数组
返回:
输出数据字典,键为输出名称,值为numpy数组
"""
# 准备输入绑定
bindings = []
input_buffers = {}
# 添加输入绑定
for i in self.input_binding_idxs:
name = self.engine.get_binding_name(i)
if name not in input_data:
raise ValueError(f"输入 '{name}' 未提供")
data = input_data[name]
# 设置动态形状(如果需要)
if self.engine.is_shape_binding(i) or -1 in self.context.get_binding_shape(i):
self.context.set_binding_shape(i, data.shape)
# 分配主机和设备内存
dtype = trt.nptype(self.engine.get_binding_dtype(i))
host_mem = cuda.pagelocked_empty(data.size, dtype)
device_mem = cuda.mem_alloc(host_mem.nbytes)
# 复制数据
np.copyto(host_mem, data.ravel())
cuda.memcpy_htod_async(device_mem, host_mem, self.stream)
input_buffers[name] = {
'host': host_mem,
'device': device_mem
}
bindings.append(int(device_mem))
# 添加输出绑定
for i in self.output_binding_idxs:
name = self.engine.get_binding_name(i)
bindings.append(int(self.output_buffers[name]['device']))
# 执行推理
self.context.execute_async_v2(bindings=bindings, stream_handle=self.stream.handle)
# 将输出从设备复制到主机
outputs = {}
for i in self.output_binding_idxs:
name = self.engine.get_binding_name(i)
output_shape = self.context.get_binding_shape(i)
output_size = trt.volume(output_shape)
cuda.memcpy_dtoh_async(self.output_buffers[name]['host'],
self.output_buffers[name]['device'],
self.stream)
# 重塑输出
outputs[name] = self.output_buffers[name]['host'].reshape(output_shape)
# 同步流
self.stream.synchronize()
# 清理输入缓冲区
for name, buffer in input_buffers.items():
buffer['device'].free()
return outputs
def __del__(self):
"""清理资源"""
for name, buffer in self.output_buffers.items():
buffer['device'].free()
# 使用示例
if __name__ == '__main__':
# 加载引擎
engine_path = 'resnet50.engine'
inference = TensorRTInference(engine_path)
# 准备输入数据(示例:随机数据)
input_shape = (1, 3, 224, 224) # 批大小=1, 3通道, 224x224图像
input_data = {
'input': np.random.random(input_shape).astype(np.float32)
}
# 执行推理
outputs = inference.infer(input_data)
# 处理输出(假设输出名为'output')
if 'output' in outputs:
# 获取前5个最高概率的类别
probs = outputs['output'][0]
top5_indices = np.argsort(probs)[-5:][::-1]
print("Top 5预测结果:")
for i, idx in enumerate(top5_indices):
print(f" {i+1}. 类别 {idx}: {probs[idx]:.6f}")
高级配置选项
启用FP16精度
TensorRT支持FP16(半精度浮点)计算,可以显著提高性能,同时只有轻微的精度损失:
python
# 在Python中启用FP16
config = builder.create_builder_config()
if builder.platform_has_fast_fp16:
config.set_flag(trt.BuilderFlag.FP16)
cpp
// 在C++中启用FP16
if (builder->platformHasFastFp16()) {
config->setFlag(nvinfer1::BuilderFlag::kFP16);
}
启用INT8量化
INT8量化可以进一步提高性能,但需要校准数据来确保精度:
python
# 在Python中启用INT8(简化版,实际使用需要校准器)
config = builder.create_builder_config()
if builder.platform_has_fast_int8:
config.set_flag(trt.BuilderFlag.INT8)
# 这里需要添加校准器配置
cpp
// 在C++中启用INT8(简化版,实际使用需要校准器)
if (builder->platformHasFastInt8()) {
config->setFlag(nvinfer1::BuilderFlag::kINT8);
// 这里需要添加校准器配置
}
配置动态形状
TensorRT支持动态输入形状,使同一引擎可以处理不同大小的输入:
python
# 在Python中配置动态形状
profile = builder.create_optimization_profile()
profile.set_shape(
"input", # 输入名称
(1, 3, 224, 224), # 最小形状
(4, 3, 224, 224), # 优化形状
(8, 3, 224, 224) # 最大形状
)
config.add_optimization_profile(profile)
cpp
// 在C++中配置动态形状
auto profile = builder->createOptimizationProfile();
profile->setDimensions(
"input", // 输入名称
nvinfer1::OptProfileSelector::kMIN, nvinfer1::Dims4(1, 3, 224, 224));
profile->setDimensions(
"input", // 输入名称
nvinfer1::OptProfileSelector::kOPT, nvinfer1::Dims4(4, 3, 224, 224));
profile->setDimensions(
"input", // 输入名称
nvinfer1::OptProfileSelector::kMAX, nvinfer1::Dims4(8, 3, 224, 224));
config->addOptimizationProfile(profile);
性能优化技巧
1. 批处理推理
对多个输入进行批处理可以提高吞吐量:
python
# 批处理推理示例
batch_size = 8
input_data = np.random.random((batch_size, 3, 224, 224)).astype(np.float32)
2. 使用CUDA图
对于固定输入大小的推理,可以使用CUDA图进一步提高性能:
python
# 使用CUDA图的示例(需要CUDA 10.0或更高版本)
import pycuda.driver as cuda
# 创建CUDA图
stream = cuda.Stream()
cuda.graph_begin_capture(stream, cuda.graph_capture_mode.GLOBAL)
# 执行推理
context.execute_async_v2(bindings=bindings, stream_handle=stream.handle)
# 结束捕获
graph = cuda.graph_end_capture(stream)
# 实例化图
graph_exec = cuda.graph_instantiate(graph)
# 使用图执行推理
graph_exec.launch(stream)
stream.synchronize()
3. 内存优化
减少主机和设备之间的数据传输:
python
# 使用页锁定内存
host_input = cuda.pagelocked_empty(input_size, dtype=np.float32)
host_output = cuda.pagelocked_empty(output_size, dtype=np.float32)
4. 选择最佳精度
根据应用需求选择合适的精度:
- FP32:最高精度,但性能较低
- FP16:平衡精度和性能
- INT8:最高性能,但精度较低,需要校准
总结
本章详细介绍了TensorRT的基本使用流程,包括模型准备、引擎构建和推理执行。我们提供了完整的C++和Python代码示例,展示了如何从ONNX模型创建TensorRT引擎,以及如何使用引擎执行高效推理。此外,我们还介绍了一些高级配置选项和性能优化技巧,帮助您充分发挥TensorRT的性能潜力。
在下一章中,我们将深入探讨TensorRT的API详解和更多实际应用场景,帮助您更全面地掌握TensorRT的使用方法。
第四部分:TensorRT API详解与示例
在前面的章节中,我们已经了解了TensorRT的基本概念、安装方法和基础使用流程。本章将深入探讨TensorRT的API,包括C++和Python两种接口,并提供详细的代码示例和中文注释,帮助读者全面掌握TensorRT的编程方法。
TensorRT API概述
TensorRT提供了两套主要的API:
- C++ API:提供最完整的功能和最高的性能,适合对性能要求极高或需要深度定制的应用场景。
- Python API:提供更便捷的开发体验,特别适合快速原型设计和实验,同时保持与C++ API相似的结构。
两种API都遵循相似的工作流程:创建网络定义、构建优化引擎、执行推理。下面我们将分别详细介绍这两套API的使用方法。
C++ API详解
核心类和对象
TensorRT C++ API的核心类包括:
ILogger
:日志接口,用于记录TensorRT操作过程中的消息IBuilder
:构建器,用于创建网络定义和构建引擎INetworkDefinition
:网络定义,表示深度学习模型的计算图IBuilderConfig
:构建配置,控制引擎构建过程中的优化选项ICudaEngine
:CUDA引擎,表示优化后的模型IExecutionContext
:执行上下文,用于执行推理
详细的C++ API示例
下面是一个完整的C++ API使用示例,包含详细的中文注释:
cpp
#include "NvInfer.h"
#include "NvOnnxParser.h"
#include <fstream>
#include <iostream>
#include <memory>
#include <vector>
#include <cuda_runtime_api.h>
// 自定义日志记录器类,继承自ILogger接口
class Logger : public nvinfer1::ILogger {
public:
void log(Severity severity, const char* msg) noexcept override {
// 根据日志级别过滤消息,只显示警告及以上级别
if (severity <= Severity::kWARNING)
std::cout << msg << std::endl;
}
} logger;
// 智能指针辅助函数,用于自动释放TensorRT对象
template <typename T>
struct TRTDestroy {
void operator()(T* obj) const {
if (obj)
obj->destroy();
}
};
template <typename T>
using TRTUniquePtr = std::unique_ptr<T, TRTDestroy<T>>;
// 从文件读取二进制数据的辅助函数
std::vector<unsigned char> readFile(const std::string& fileName) {
std::ifstream file(fileName, std::ios::binary | std::ios::ate);
if (!file) {
std::cerr << "无法打开文件: " << fileName << std::endl;
return {};
}
size_t size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<unsigned char> buffer(size);
file.read(reinterpret_cast<char*>(buffer.data()), size);
file.close();
return buffer;
}
// 将引擎序列化并保存到文件的辅助函数
void saveEngine(const nvinfer1::ICudaEngine& engine, const std::string& fileName) {
// 序列化引擎
TRTUniquePtr<nvinfer1::IHostMemory> serializedEngine{engine.serialize()};
// 将序列化数据写入文件
std::ofstream file(fileName, std::ios::binary);
file.write(reinterpret_cast<const char*>(serializedEngine->data()), serializedEngine->size());
file.close();
}
// 主函数:从ONNX模型构建TensorRT引擎并执行推理
int main(int argc, char* argv[]) {
if (argc < 3) {
std::cerr << "用法: " << argv[0] << " <onnx_model> <engine_file>" << std::endl;
return 1;
}
std::string onnxFile = argv[1];
std::string engineFile = argv[2];
// 第一部分:构建TensorRT引擎
// 创建构建器
TRTUniquePtr<nvinfer1::IBuilder> builder{nvinfer1::createInferBuilder(logger)};
if (!builder) {
std::cerr << "创建构建器失败" << std::endl;
return 1;
}
// 创建网络定义,使用显式批处理维度
const auto explicitBatch = 1U << static_cast<uint32_t>(nvinfer1::NetworkDefinitionCreationFlag::kEXPLICIT_BATCH);
TRTUniquePtr<nvinfer1::INetworkDefinition> network{builder->createNetworkV2(explicitBatch)};
if (!network) {
std::cerr << "创建网络定义失败" << std::endl;
return 1;
}
// 创建ONNX解析器
TRTUniquePtr<nvonnxparser::IParser> parser{nvonnxparser::createParser(*network, logger)};
if (!parser) {
std::cerr << "创建ONNX解析器失败" << std::endl;
return 1;
}
// 解析ONNX模型
auto onnxData = readFile(onnxFile);
if (onnxData.empty()) {
std::cerr << "读取ONNX模型失败" << std::endl;
return 1;
}
if (!parser->parse(onnxData.data(), onnxData.size())) {
std::cerr << "解析ONNX模型失败" << std::endl;
for (int i = 0; i < parser->getNbErrors(); ++i) {
std::cerr << parser->getError(i)->desc() << std::endl;
}
return 1;
}
// 创建构建配置
TRTUniquePtr<nvinfer1::IBuilderConfig> config{builder->createBuilderConfig()};
if (!config) {
std::cerr << "创建构建配置失败" << std::endl;
return 1;
}
// 设置最大工作空间大小(1GB)
config->setMemoryPoolLimit(nvinfer1::MemoryPoolType::kWORKSPACE, 1ULL << 30);
// 启用FP16精度(如果GPU支持)
if (builder->platformHasFastFp16()) {
std::cout << "启用FP16精度" << std::endl;
config->setFlag(nvinfer1::BuilderFlag::kFP16);
}
// 构建引擎
std::cout << "构建TensorRT引擎..." << std::endl;
TRTUniquePtr<nvinfer1::ICudaEngine> engine{builder->buildEngineWithConfig(*network, *config)};
if (!engine) {
std::cerr << "构建引擎失败" << std::endl;
return 1;
}
// 保存引擎到文件
std::cout << "将引擎序列化到: " << engineFile << std::endl;
saveEngine(*engine, engineFile);
// 第二部分:使用TensorRT引擎执行推理
// 创建执行上下文
TRTUniquePtr<nvinfer1::IExecutionContext> context{engine->createExecutionContext()};
if (!context) {
std::cerr << "创建执行上下文失败" << std::endl;
return 1;
}
// 获取输入和输出的绑定索引
int inputIndex = engine->getBindingIndex("input"); // 假设输入名称为"input"
int outputIndex = engine->getBindingIndex("output"); // 假设输出名称为"output"
if (inputIndex == -1 || outputIndex == -1) {
std::cerr << "无法找到输入或输出绑定" << std::endl;
return 1;
}
// 获取输入和输出维度
auto inputDims = engine->getBindingDimensions(inputIndex);
auto outputDims = engine->getBindingDimensions(outputIndex);
// 计算输入和输出大小
int batchSize = 1; // 假设批处理大小为1
int inputSize = batchSize;
int outputSize = batchSize;
for (int i = 1; i < inputDims.nbDims; i++)
inputSize *= inputDims.d[i];
for (int i = 1; i < outputDims.nbDims; i++)
outputSize *= outputDims.d[i];
// 分配主机内存
std::vector<float> inputData(inputSize, 0.5f); // 示例输入数据,全部填充为0.5
std::vector<float> outputData(outputSize);
// 分配设备内存
void* deviceBuffers[2];
cudaMalloc(&deviceBuffers[inputIndex], inputSize * sizeof(float));
cudaMalloc(&deviceBuffers[outputIndex], outputSize * sizeof(float));
// 创建CUDA流
cudaStream_t stream;
cudaStreamCreate(&stream);
// 将输入数据从主机复制到设备
cudaMemcpyAsync(deviceBuffers[inputIndex], inputData.data(),
inputSize * sizeof(float), cudaMemcpyHostToDevice, stream);
// 执行推理
context->enqueueV2(deviceBuffers, stream, nullptr);
// 将输出数据从设备复制回主机
cudaMemcpyAsync(outputData.data(), deviceBuffers[outputIndex],
outputSize * sizeof(float), cudaMemcpyDeviceToHost, stream);
// 同步CUDA流,确保所有操作完成
cudaStreamSynchronize(stream);
// 输出结果(仅显示前5个值)
std::cout << "推理结果 (前5个值):" << std::endl;
for (int i = 0; i < std::min(5, outputSize); i++) {
std::cout << outputData[i] << " ";
}
std::cout << std::endl;
// 清理资源
cudaFree(deviceBuffers[inputIndex]);
cudaFree(deviceBuffers[outputIndex]);
cudaStreamDestroy(stream);
std::cout << "推理完成!" << std::endl;
return 0;
}
高级C++ API功能
1. 动态形状支持
cpp
// 创建优化配置文件,支持动态输入形状
TRTUniquePtr<nvinfer1::IOptimizationProfile> profile{builder->createOptimizationProfile()};
// 设置输入的最小、最优和最大形状
// 参数:输入名称,选择器类型,维度
profile->setDimensions("input", nvinfer1::OptProfileSelector::kMIN, nvinfer1::Dims4(1, 3, 224, 224));
profile->setDimensions("input", nvinfer1::OptProfileSelector::kOPT, nvinfer1::Dims4(4, 3, 224, 224));
profile->setDimensions("input", nvinfer1::OptProfileSelector::kMAX, nvinfer1::Dims4(16, 3, 224, 224));
// 将优化配置文件添加到构建配置
config->addOptimizationProfile(profile.get());
// 在推理时设置实际输入形状
context->setBindingDimensions(inputIndex, nvinfer1::Dims4(batchSize, 3, 224, 224));
2. INT8量化
cpp
// 创建INT8校准器(这里使用一个自定义校准器示例)
class MyCalibrator : public nvinfer1::IInt8Calibrator {
// 实现校准器接口方法
// ...
};
// 创建校准器实例
MyCalibrator calibrator(/* 校准参数 */);
// 启用INT8模式并设置校准器
if (builder->platformHasFastInt8()) {
std::cout << "启用INT8精度" << std::endl;
config->setFlag(nvinfer1::BuilderFlag::kINT8);
config->setInt8Calibrator(&calibrator);
}
3. 使用CUDA图
cpp
// 创建CUDA图捕获器
cudaGraph_t graph;
cudaGraphExec_t graphExec;
// 开始捕获
cudaStreamBeginCapture(stream, cudaStreamCaptureModeGlobal);
// 执行要捕获的操作
context->enqueueV2(deviceBuffers, stream, nullptr);
// 结束捕获
cudaStreamEndCapture(stream, &graph);
// 实例化图
cudaGraphInstantiate(&graphExec, graph, nullptr, nullptr, 0);
// 使用图执行推理(可以重复执行)
cudaGraphLaunch(graphExec, stream);
cudaStreamSynchronize(stream);
// 清理资源
cudaGraphDestroy(graph);
cudaGraphExecDestroy(graphExec);
Python API详解
Python API提供了与C++ API类似的功能,但使用更简洁的Python语法。
核心类和对象
TensorRT Python API的核心类包括:
trt.Logger
:日志记录器trt.Builder
:构建器trt.NetworkDefinition
:网络定义trt.BuilderConfig
:构建配置trt.ICudaEngine
:CUDA引擎trt.IExecutionContext
:执行上下文
详细的Python API示例
下面是一个完整的Python API使用示例,包含详细的中文注释:
python
import os
import tensorrt as trt
import numpy as np
import pycuda.driver as cuda
import pycuda.autoinit
class TensorRTModel:
"""TensorRT模型封装类"""
def __init__(self):
"""初始化TensorRT模型"""
# 创建logger
self.logger = trt.Logger(trt.Logger.WARNING)
# 创建builder和网络
self.builder = trt.Builder(self.logger)
self.network = None
self.engine = None
self.context = None
self.stream = None
self.host_inputs = None
self.host_outputs = None
self.device_inputs = None
self.device_outputs = None
self.bindings = None
def build_engine_from_onnx(self, onnx_file_path, engine_file_path=None, precision='fp32'):
"""
从ONNX模型构建TensorRT引擎
参数:
onnx_file_path: ONNX模型文件路径
engine_file_path: 输出的TensorRT引擎文件路径(可选)
precision: 精度模式,可选 'fp32', 'fp16', 'int8'
返回:
构建好的TensorRT引擎
"""
# 创建网络定义,使用显式批处理维度
EXPLICIT_BATCH = 1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)
self.network = self.builder.create_network(EXPLICIT_BATCH)
# 创建ONNX解析器
parser = trt.OnnxParser(self.network, self.logger)
# 解析ONNX文件
print(f"正在解析ONNX模型: {onnx_file_path}")
with open(onnx_file_path, 'rb') as model:
if not parser.parse(model.read()):
print('ERROR: 解析ONNX模型失败')
for error in range(parser.num_errors):
print(parser.get_error(error))
return None
# 创建构建配置
config = self.builder.create_builder_config()
# 设置最大工作空间大小(1GB)
config.max_workspace_size = 1 << 30
# 根据指定精度设置标志
if precision == 'fp16' and self.builder.platform_has_fast_fp16:
print('启用FP16精度')
config.set_flag(trt.BuilderFlag.FP16)
elif precision == 'int8' and self.builder.platform_has_fast_int8:
print('启用INT8精度')
config.set_flag(trt.BuilderFlag.INT8)
# 注意:INT8需要校准器,这里简化处理
# 构建引擎
print('构建TensorRT引擎...')
serialized_engine = self.builder.build_serialized_network(self.network, config)
if not serialized_engine:
print('ERROR: 引擎构建失败')
return None
# 创建运行时并反序列化引擎
runtime = trt.Runtime(self.logger)
self.engine = runtime.deserialize_cuda_engine(serialized_engine)
# 保存引擎到文件(如果指定了文件路径)
if engine_file_path:
with open(engine_file_path, 'wb') as f:
f.write(serialized_engine)
print(f'引擎已保存到: {engine_file_path}')
return self.engine
def load_engine(self, engine_file_path):
"""
从文件加载TensorRT引擎
参数:
engine_file_path: TensorRT引擎文件路径
返回:
加载的TensorRT引擎
"""
print(f"从文件加载引擎: {engine_file_path}")
# 读取引擎文件
with open(engine_file_path, 'rb') as f:
serialized_engine = f.read()
# 创建运行时并反序列化引擎
runtime = trt.Runtime(self.logger)
self.engine = runtime.deserialize_cuda_engine(serialized_engine)
if not self.engine:
print('ERROR: 引擎加载失败')
return None
return self.engine
def prepare_inference(self):
"""准备推理环境"""
if not self.engine:
print('ERROR: 引擎未初始化')
return False
# 创建执行上下文
self.context = self.engine.create_execution_context()
# 创建CUDA流
self.stream = cuda.Stream()
# 获取绑定索引
input_binding_idxs = []
output_binding_idxs = []
for i in range(self.engine.num_bindings):
if self.engine.binding_is_input(i):
input_binding_idxs.append(i)
else:
output_binding_idxs.append(i)
# 分配输出缓冲区
self.output_buffers = {}
for i in output_binding_idxs:
name = self.engine.get_binding_name(i)
dtype = trt.nptype(self.engine.get_binding_dtype(i))
shape = self.context.get_binding_shape(i)
size = trt.volume(shape)
# 分配主机和设备内存
host_mem = cuda.pagelocked_empty(size, dtype)
device_mem = cuda.mem_alloc(host_mem.nbytes)
self.output_buffers[name] = {
'host': host_mem,
'device': device_mem,
'shape': shape
}
return True
def infer(self, input_data):
"""
执行推理
参数:
input_data: 输入数据,可以是numpy数组或字典(键为输入名称,值为numpy数组)
返回:
输出数据列表
"""
if not self.engine or not self.context:
if not self.prepare_inference():
return None
# 如果输入是numpy数组,假设只有一个输入
if isinstance(input_data, np.ndarray):
input_data = {self.engine.get_binding_name(0): input_data}
# 准备输入绑定
bindings = []
input_buffers = {}
# 添加输入绑定
for i in range(self.engine.num_bindings):
if self.engine.binding_is_input(i):
name = self.engine.get_binding_name(i)
if name not in input_data:
print(f"ERROR: 输入 '{name}' 未提供")
return None
data = input_data[name]
# 设置动态形状(如果需要)
if self.engine.is_shape_binding(i) or -1 in self.context.get_binding_shape(i):
self.context.set_binding_shape(i, data.shape)
# 分配主机和设备内存
dtype = trt.nptype(self.engine.get_binding_dtype(i))
host_mem = cuda.pagelocked_empty(data.size, dtype)
device_mem = cuda.mem_alloc(host_mem.nbytes)
# 复制数据
np.copyto(host_mem, data.ravel())
cuda.memcpy_htod_async(device_mem, host_mem, self.stream)
input_buffers[name] = {
'host': host_mem,
'device': device_mem
}
bindings.append(int(device_mem))
else:
name = self.engine.get_binding_name(i)
bindings.append(int(self.output_buffers[name]['device']))
# 执行推理
self.context.execute_async_v2(bindings=bindings, stream_handle=self.stream.handle)
# 将输出从设备复制到主机
outputs = []
for i in range(self.engine.num_bindings):
if not self.engine.binding_is_input(i):
name = self.engine.get_binding_name(i)
shape = self.context.get_binding_shape(i)
size = trt.volume(shape)
cuda.memcpy_dtoh_async(self.output_buffers[name]['host'],
self.output_buffers[name]['device'],
self.stream)
# 同步流
self.stream.synchronize()
# 重塑输出
outputs.append(self.output_buffers[name]['host'][:size].reshape(shape))
# 清理输入缓冲区
for name, buffer in input_buffers.items():
buffer['device'].free()
return outputs
def __del__(self):
"""清理资源"""
# 释放设备内存
if hasattr(self, 'device_inputs') and self.device_inputs:
for mem in self.device_inputs:
mem.free()
if hasattr(self, 'device_outputs') and self.device_outputs:
for mem in self.device_outputs:
mem.free()
# 使用示例
if __name__ == '__main__':
# 创建TensorRT模型
model = TensorRTModel()
# 从ONNX模型构建引擎
onnx_file = 'resnet50.onnx'
engine_file = 'resnet50.engine'
# 如果引擎文件已存在,直接加载
if os.path.exists(engine_file):
model.load_engine(engine_file)
else:
# 否则从ONNX模型构建引擎
model.build_engine_from_onnx(onnx_file, engine_file, precision='fp16')
# 准备输入数据
input_shape = (1, 3, 224, 224) # 批大小=1, 3通道, 224x224图像
input_data = np.random.random(input_shape).astype(np.float32)
# 执行推理
outputs = model.infer(input_data)
# 处理输出
if outputs:
# 获取前5个最高概率的类别
probs = outputs[0][0]
top5_indices = np.argsort(probs)[-5:][::-1]
print("\nTop 5预测结果:")
for i, idx in enumerate(top5_indices):
print(f" {i+1}. 类别 {idx}: {probs[idx]:.6f}")
高级Python API功能
1. 动态形状支持
python
# 创建优化配置文件,支持动态输入形状
profile = builder.create_optimization_profile()
# 设置输入的最小、最优和最大形状
profile.set_shape(
"input", # 输入名称
min=(1, 3, 224, 224), # 最小形状
opt=(4, 3, 224, 224), # 优化形状
max=(16, 3, 224, 224) # 最大形状
)
# 将优化配置文件添加到构建配置
config.add_optimization_profile(profile)
# 在推理时设置实际输入形状
context.set_binding_shape(0, (batch_size, 3, 224, 224))
2. INT8量化与校准
python
# 自定义校准器
class MyCalibrator(trt.IInt8Calibrator):
def __init__(self, calibration_data, batch_size, cache_file):
"""
初始化校准器
参数:
calibration_data: 校准数据集
batch_size: 批大小
cache_file: 校准缓存文件路径
"""
super().__init__()
self.calibration_data = calibration_data
self.batch_size = batch_size
self.cache_file = cache_file
self.current_index = 0
self.device_input = None
# 分配设备内存
input_size = batch_size * 3 * 224 * 224 # 假设输入是224x224的RGB图像
self.device_input = cuda.mem_alloc(input_size * 4) # 4字节的float32
def get_batch_size(self):
"""返回批大小"""
return self.batch_size
def get_batch(self, names):
"""获取下一批校准数据"""
if self.current_index >= len(self.calibration_data):
return None
# 获取当前批次数据
batch = self.calibration_data[self.current_index:self.current_index+self.batch_size]
self.current_index += self.batch_size
# 如果批次不完整,填充到完整批次
if len(batch) < self.batch_size:
batch = np.pad(batch, ((0, self.batch_size - len(batch)), (0, 0), (0, 0), (0, 0)), 'constant')
# 将数据复制到设备
cuda.memcpy_htod(self.device_input, np.ascontiguousarray(batch))
return [self.device_input]
def read_calibration_cache(self):
"""读取校准缓存"""
if os.path.exists(self.cache_file):
with open(self.cache_file, "rb") as f:
return f.read()
return None
def write_calibration_cache(self, cache):
"""写入校准缓存"""
with open(self.cache_file, "wb") as f:
f.write(cache)
# 使用校准器
calibration_data = load_calibration_data() # 加载校准数据
calibrator = MyCalibrator(calibration_data, batch_size=8, cache_file="calibration.cache")
# 启用INT8并设置校准器
config.set_flag(trt.BuilderFlag.INT8)
config.int8_calibrator = calibrator
3. 使用CUDA图
python
# 使用CUDA图加速推理
import pycuda.driver as cuda
# 创建CUDA流
stream = cuda.Stream()
# 开始捕获
cuda.graph_begin_capture(stream, cuda.graph_capture_mode.GLOBAL)
# 执行要捕获的操作
context.execute_async_v2(bindings=bindings, stream_handle=stream.handle)
# 结束捕获
graph = cuda.graph_end_capture(stream)
# 实例化图
graph_exec = cuda.graph_instantiate(graph)
# 使用图执行推理(可以重复执行)
graph_exec.launch(stream)
stream.synchronize()
TensorRT API最佳实践
1. 内存管理
- 使用智能指针:在C++中使用智能指针管理TensorRT对象的生命周期
- 使用页锁定内存:使用CUDA页锁定内存(pinned memory)减少主机和设备之间的数据传输开销
- 重用缓冲区:对于多次推理,重用已分配的内存缓冲区
2. 错误处理
- 检查返回值:TensorRT API的大多数函数都返回状态或指针,应该检查这些返回值
- 使用日志记录器:自定义ILogger实现,根据需要记录不同级别的消息
- 解析错误处理:特别是在解析ONNX模型时,检查并处理解析错误
3. 性能优化
- 批处理:尽可能使用批处理来提高吞吐量
- 选择合适的精度:根据应用需求选择FP32、FP16或INT8精度
- 使用CUDA图:对于固定输入大小的推理,使用CUDA图进一步提高性能
- 优化内存访问模式:减少主机和设备之间的数据传输
4. 可移植性
- 序列化引擎:将构建好的引擎序列化到文件,避免每次运行时重新构建
- 检查硬件兼容性:在使用特定功能(如FP16或INT8)前检查硬件支持
- 处理动态形状:使用优化配置文件支持动态输入形状
总结
本章详细介绍了TensorRT的C++和Python API,包括核心类、基本用法和高级功能。通过丰富的代码示例和中文注释,读者应该能够全面了解如何使用TensorRT API来优化和部署深度学习模型。
在下一章中,我们将探讨TensorRT在各种实际应用场景中的使用方法,包括图像分类、目标检测、语义分割等,并提供完整的端到端示例。
第五部分:TensorRT应用场景实战
在前面的章节中,我们已经详细介绍了TensorRT的基本概念、安装方法、基础使用流程以及API详解。本章将通过几个实际应用场景的完整示例,展示TensorRT在不同深度学习任务中的应用,帮助读者将所学知识应用到实际项目中。
图像分类实战
图像分类是深度学习中最基础也是最常见的应用之一。下面我们将展示如何使用TensorRT优化和部署一个预训练的ResNet-50模型用于图像分类任务。
准备预训练模型
首先,我们需要准备一个预训练的ResNet-50模型并将其转换为ONNX格式:
python
# 准备预训练的ResNet-50模型并转换为ONNX格式
import torch
import torchvision.models as models
def prepare_resnet50_model():
"""
准备预训练的ResNet-50模型并导出为ONNX格式
返回:
导出的ONNX模型文件路径
"""
print("准备预训练的ResNet-50模型...")
# 加载预训练模型
model = models.resnet50(pretrained=True)
model.eval()
# 创建示例输入
dummy_input = torch.randn(1, 3, 224, 224)
# 导出ONNX模型
onnx_file_path = "resnet50.onnx"
torch.onnx.export(model, # 要导出的模型
dummy_input, # 模型输入
onnx_file_path, # 输出文件名
export_params=True, # 存储训练好的参数权重
opset_version=13, # ONNX算子集版本
do_constant_folding=True, # 是否执行常量折叠优化
input_names=["input"], # 输入名称
output_names=["output"], # 输出名称
dynamic_axes={"input": {0: "batch_size"}, # 动态轴
"output": {0: "batch_size"}})
print(f"ONNX模型已导出到: {onnx_file_path}")
return onnx_file_path
构建图像分类应用
接下来,我们创建一个完整的图像分类应用,包括图像预处理、TensorRT推理和结果后处理:
python
import os
import numpy as np
import tensorrt as trt
import pycuda.driver as cuda
import pycuda.autoinit
from PIL import Image
class ImageClassifier:
"""使用TensorRT的图像分类器"""
def __init__(self, engine_file_path, labels_file_path):
"""
初始化图像分类器
参数:
engine_file_path: TensorRT引擎文件路径
labels_file_path: 类别标签文件路径
"""
# 加载类别标签
self.labels = self._load_labels(labels_file_path)
# 创建TensorRT推理引擎
self.logger = trt.Logger(trt.Logger.WARNING)
self.runtime = trt.Runtime(self.logger)
# 加载引擎
with open(engine_file_path, 'rb') as f:
self.engine = self.runtime.deserialize_cuda_engine(f.read())
# 创建执行上下文
self.context = self.engine.create_execution_context()
# 获取输入和输出的详细信息
self.input_idx = self.engine.get_binding_index("input")
self.output_idx = self.engine.get_binding_index("output")
# 获取输入形状
self.input_shape = self.engine.get_binding_shape(self.input_idx)
# 创建CUDA流
self.stream = cuda.Stream()
# 分配缓冲区
self._allocate_buffers()
def _load_labels(self, labels_file_path):
"""
加载类别标签
参数:
labels_file_path: 标签文件路径
返回:
标签列表
"""
with open(labels_file_path, 'r') as f:
return [line.strip() for line in f.readlines()]
def _allocate_buffers(self):
"""分配输入和输出缓冲区"""
# 计算输入和输出大小
self.input_size = trt.volume(self.input_shape)
self.output_shape = self.engine.get_binding_shape(self.output_idx)
self.output_size = trt.volume(self.output_shape)
# 分配主机和设备内存
self.host_input = cuda.pagelocked_empty(self.input_size, dtype=np.float32)
self.host_output = cuda.pagelocked_empty(self.output_size, dtype=np.float32)
self.device_input = cuda.mem_alloc(self.host_input.nbytes)
self.device_output = cuda.mem_alloc(self.host_output.nbytes)
# 准备绑定
self.bindings = [int(self.device_input), int(self.device_output)]
def preprocess_image(self, image_path):
"""
预处理图像
参数:
image_path: 图像文件路径
返回:
预处理后的图像数据
"""
# 加载图像
image = Image.open(image_path).convert('RGB')
# 调整大小
image = image.resize((224, 224))
# 转换为numpy数组
image_data = np.array(image).astype(np.float32)
# 调整通道顺序:从HWC到CHW
image_data = image_data.transpose((2, 0, 1))
# 标准化
mean = np.array([0.485, 0.456, 0.406]).reshape((3, 1, 1))
std = np.array([0.229, 0.224, 0.225]).reshape((3, 1, 1))
image_data = (image_data / 255.0 - mean) / std
# 添加批次维度
image_data = np.expand_dims(image_data, axis=0)
return image_data
def classify(self, image_path):
"""
对图像进行分类
参数:
image_path: 图像文件路径
返回:
分类结果(类别ID、类别名称和置信度)
"""
# 预处理图像
input_data = self.preprocess_image(image_path)
# 复制输入数据到主机内存
np.copyto(self.host_input, input_data.ravel())
# 将输入数据从主机复制到设备
cuda.memcpy_htod_async(self.device_input, self.host_input, self.stream)
# 执行推理
self.context.execute_async_v2(bindings=self.bindings, stream_handle=self.stream.handle)
# 将输出数据从设备复制回主机
cuda.memcpy_dtoh_async(self.host_output, self.device_output, self.stream)
# 同步流
self.stream.synchronize()
# 处理输出
output = self.host_output.reshape(self.output_shape)
# 获取前5个最高概率的类别
top5_indices = np.argsort(output[0])[-5:][::-1]
results = []
for i, idx in enumerate(top5_indices):
if idx < len(self.labels):
results.append({
'rank': i + 1,
'class_id': int(idx),
'class_name': self.labels[idx],
'confidence': float(output[0][idx])
})
return results
def __del__(self):
"""清理资源"""
# 释放设备内存
if hasattr(self, 'device_input'):
self.device_input.free()
if hasattr(self, 'device_output'):
self.device_output.free()
# 使用示例
def main():
# 准备模型
onnx_file_path = prepare_resnet50_model()
# 构建TensorRT引擎
from tensorrt_utils import build_engine_from_onnx
engine_file_path = "resnet50.engine"
if not os.path.exists(engine_file_path):
build_engine_from_onnx(onnx_file_path, engine_file_path, precision='fp16')
# 加载ImageNet类别标签
labels_file_path = "imagenet_classes.txt"
# 创建图像分类器
classifier = ImageClassifier(engine_file_path, labels_file_path)
# 对图像进行分类
image_path = "cat.jpg"
results = classifier.classify(image_path)
# 打印结果
print(f"\n对图像 {image_path} 的分类结果:")
for result in results:
print(f" {result['rank']}. {result['class_name']} ({result['confidence']:.4f})")
if __name__ == "__main__":
main()
目标检测实战
目标检测是计算机视觉中的一个重要任务,它不仅需要识别图像中的对象,还需要定位它们的位置。下面我们将展示如何使用TensorRT优化和部署YOLOv5模型进行目标检测。
准备YOLOv5模型
首先,我们需要准备一个预训练的YOLOv5模型并将其转换为ONNX格式:
python
# 准备YOLOv5模型并转换为ONNX格式
import torch
def prepare_yolov5_model():
"""
准备YOLOv5模型并导出为ONNX格式
返回:
导出的ONNX模型文件路径
"""
print("准备YOLOv5模型...")
# 从PyTorch Hub加载预训练模型
model = torch.hub.load('ultralytics/yolov5', 'yolov5s', pretrained=True)
model.eval()
# 导出ONNX模型
onnx_file_path = "yolov5s.onnx"
# 设置导出参数
img_size = (640, 640) # 输入图像大小
batch_size = 1 # 批处理大小
# 导出模型
torch.onnx.export(model, # 要导出的模型
torch.zeros(batch_size, 3, *img_size), # 模型输入
onnx_file_path, # 输出文件名
export_params=True, # 存储训练好的参数权重
opset_version=13, # ONNX算子集版本
do_constant_folding=True, # 是否执行常量折叠优化
input_names=["input"], # 输入名称
output_names=["output"], # 输出名称
dynamic_axes={"input": {0: "batch_size"}, # 动态轴
"output": {0: "batch_size"}})
print(f"ONNX模型已导出到: {onnx_file_path}")
return onnx_file_path
构建目标检测应用
接下来,我们创建一个完整的目标检测应用,包括图像预处理、TensorRT推理和结果后处理:
python
import os
import cv2
import numpy as np
import tensorrt as trt
import pycuda.driver as cuda
import pycuda.autoinit
class ObjectDetector:
"""使用TensorRT的目标检测器"""
def __init__(self, engine_file_path, class_names_file):
"""
初始化目标检测器
参数:
engine_file_path: TensorRT引擎文件路径
class_names_file: 类别名称文件路径
"""
# 加载类别名称
self.class_names = self._load_class_names(class_names_file)
# 创建TensorRT推理引擎
self.logger = trt.Logger(trt.Logger.WARNING)
self.runtime = trt.Runtime(self.logger)
# 加载引擎
with open(engine_file_path, 'rb') as f:
self.engine = self.runtime.deserialize_cuda_engine(f.read())
# 创建执行上下文
self.context = self.engine.create_execution_context()
# 获取输入和输出的详细信息
self.input_idx = self.engine.get_binding_index("input")
self.output_idx = self.engine.get_binding_index("output")
# 获取输入形状
self.input_shape = self.engine.get_binding_shape(self.input_idx)
self.img_size = (self.input_shape[2], self.input_shape[3]) # HW
# 创建CUDA流
self.stream = cuda.Stream()
# 分配缓冲区
self._allocate_buffers()
# 设置NMS参数
self.conf_threshold = 0.25
self.iou_threshold = 0.45
def _load_class_names(self, class_names_file):
"""
加载类别名称
参数:
class_names_file: 类别名称文件路径
返回:
类别名称列表
"""
with open(class_names_file, 'r') as f:
return [line.strip() for line in f.readlines()]
def _allocate_buffers(self):
"""分配输入和输出缓冲区"""
# 计算输入和输出大小
self.input_size = trt.volume(self.input_shape)
self.output_shape = self.engine.get_binding_shape(self.output_idx)
self.output_size = trt.volume(self.output_shape)
# 分配主机和设备内存
self.host_input = cuda.pagelocked_empty(self.input_size, dtype=np.float32)
self.host_output = cuda.pagelocked_empty(self.output_size, dtype=np.float32)
self.device_input = cuda.mem_alloc(self.host_input.nbytes)
self.device_output = cuda.mem_alloc(self.host_output.nbytes)
# 准备绑定
self.bindings = [int(self.device_input), int(self.device_output)]
def preprocess_image(self, image_path):
"""
预处理图像
参数:
image_path: 图像文件路径
返回:
预处理后的图像数据和原始图像信息
"""
# 读取图像
img = cv2.imread(image_path)
if img is None:
raise ValueError(f"无法读取图像: {image_path}")
# 保存原始图像信息
original_img = img.copy()
original_shape = img.shape
# 调整大小并保持宽高比
h, w = original_shape[:2]
scale = min(self.img_size[0] / h, self.img_size[1] / w)
new_h, new_w = int(h * scale), int(w * scale)
# 调整图像大小
img = cv2.resize(img, (new_w, new_h))
# 创建填充图像
padded_img = np.zeros((self.img_size[0], self.img_size[1], 3), dtype=np.uint8)
padded_img[:new_h, :new_w] = img
# 转换为RGB并归一化
padded_img = cv2.cvtColor(padded_img, cv2.COLOR_BGR2RGB)
padded_img = padded_img.astype(np.float32) / 255.0
# 调整通道顺序:从HWC到CHW
padded_img = padded_img.transpose((2, 0, 1))
# 添加批次维度
padded_img = np.expand_dims(padded_img, axis=0)
return padded_img, original_img, scale, (new_h, new_w)
def postprocess(self, output, original_img, scale, new_shape):
"""
后处理检测结果
参数:
output: 模型输出
original_img: 原始图像
scale: 缩放比例
new_shape: 调整大小后的形状
返回:
处理后的检测结果
"""
# 重塑输出
output = output.reshape((-1, self.output_shape[-1]))
# 应用置信度阈值
valid = output[:, 4] > self.conf_threshold
output = output[valid]
if len(output) == 0:
return []
# 分离坐标、置信度和类别概率
boxes = output[:, :4]
scores = output[:, 4]
classes = output[:, 5:]
# 获取类别ID和置信度
class_ids = np.argmax(classes, axis=1)
confidences = scores * np.max(classes, axis=1)
# 应用置信度阈值
valid = confidences > self.conf_threshold
boxes = boxes[valid]
class_ids = class_ids[valid]
confidences = confidences[valid]
if len(boxes) == 0:
return []
# 转换坐标格式:从[x_center, y_center, width, height]到[x1, y1, x2, y2]
x_center = boxes[:, 0]
y_center = boxes[:, 1]
width = boxes[:, 2]
height = boxes[:, 3]
x1 = x_center - width / 2
y1 = y_center - height / 2
x2 = x_center + width / 2
y2 = y_center + height / 2
# 调整坐标到原始图像
new_h, new_w = new_shape
x1 = x1 * new_w / scale
y1 = y1 * new_h / scale
x2 = x2 * new_w / scale
y2 = y2 * new_h / scale
# 应用非极大值抑制
indices = cv2.dnn.NMSBoxes(
np.column_stack((x1, y1, x2 - x1, y2 - y1)).tolist(),
confidences.tolist(),
self.conf_threshold,
self.iou_threshold
)
# 准备结果
results = []
for idx in indices:
if isinstance(idx, list): # OpenCV 4.5.4之前的版本
idx = idx[0]
x1_val, y1_val = max(0, int(x1[idx])), max(0, int(y1[idx]))
x2_val, y2_val = min(original_img.shape[1], int(x2[idx])), min(original_img.shape[0], int(y2[idx]))
class_id = int(class_ids[idx])
class_name = self.class_names[class_id] if class_id < len(self.class_names) else f"Unknown-{class_id}"
confidence = float(confidences[idx])
results.append({
'class_id': class_id,
'class_name': class_name,
'confidence': confidence,
'box': [x1_val, y1_val, x2_val, y2_val]
})
return results
def detect(self, image_path):
"""
检测图像中的对象
参数:
image_path: 图像文件路径
返回:
检测结果和可视化图像
"""
# 预处理图像
input_data, original_img, scale, new_shape = self.preprocess_image(image_path)
# 复制输入数据到主机内存
np.copyto(self.host_input, input_data.ravel())
# 将输入数据从主机复制到设备
cuda.memcpy_htod_async(self.device_input, self.host_input, self.stream)
# 执行推理
self.context.execute_async_v2(bindings=self.bindings, stream_handle=self.stream.handle)
# 将输出数据从设备复制回主机
cuda.memcpy_dtoh_async(self.host_output, self.device_output, self.stream)
# 同步流
self.stream.synchronize()
# 后处理检测结果
results = self.postprocess(self.host_output, original_img, scale, new_shape)
# 可视化结果
vis_img = original_img.copy()
for result in results:
# 获取边界框和类别信息
x1, y1, x2, y2 = result['box']
class_name = result['class_name']
confidence = result['confidence']
# 绘制边界框
color = (0, 255, 0) # 绿色
cv2.rectangle(vis_img, (x1, y1), (x2, y2), color, 2)
# 绘制标签
label = f"{class_name} {confidence:.2f}"
(label_width, label_height), baseline = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)
cv2.rectangle(vis_img, (x1, y1 - label_height - baseline), (x1 + label_width, y1), color, -1)
cv2.putText(vis_img, label, (x1, y1 - baseline), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1)
return results, vis_img
def __del__(self):
"""清理资源"""
# 释放设备内存
if hasattr(self, 'device_input'):
self.device_input.free()
if hasattr(self, 'device_output'):
self.device_output.free()
# 使用示例
def main():
# 准备模型
onnx_file_path = prepare_yolov5_model()
# 构建TensorRT引擎
from tensorrt_utils import build_engine_from_onnx
engine_file_path = "yolov5s.engine"
if not os.path.exists(engine_file_path):
build_engine_from_onnx(onnx_file_path, engine_file_path, precision='fp16')
# 加载COCO类别名称
class_names_file = "coco_classes.txt"
# 创建目标检测器
detector = ObjectDetector(engine_file_path, class_names_file)
# 对图像进行检测
image_path = "street.jpg"
results, vis_img = detector.detect(image_path)
# 保存可视化结果
output_path = "detection_result.jpg"
cv2.imwrite(output_path, vis_img)
# 打印结果
print(f"\n对图像 {image_path} 的检测结果:")
for i, result in enumerate(results):
print(f" {i+1}. {result['class_name']} ({result['confidence']:.4f}) at {result['box']}")
print(f"\n可视化结果已保存到: {output_path}")
if __name__ == "__main__":
main()
语义分割实战
语义分割是像素级的分类任务,为图像中的每个像素分配一个类别标签。下面我们将展示如何使用TensorRT优化和部署DeepLabV3模型进行语义分割。
准备DeepLabV3模型
首先,我们需要准备一个预训练的DeepLabV3模型并将其转换为ONNX格式:
python
# 准备DeepLabV3模型并转换为ONNX格式
import torch
import torchvision.models.segmentation as models
def prepare_deeplabv3_model():
"""
准备DeepLabV3模型并导出为ONNX格式
返回:
导出的ONNX模型文件路径
"""
print("准备DeepLabV3模型...")
# 加载预训练模型
model = models.deeplabv3_resnet50(pretrained=True)
model.eval()
# 创建示例输入
dummy_input = torch.randn(1, 3, 513, 513)
# 导出ONNX模型
onnx_file_path = "deeplabv3.onnx"
torch.onnx.export(model, # 要导出的模型
dummy_input, # 模型输入
onnx_file_path, # 输出文件名
export_params=True, # 存储训练好的参数权重
opset_version=13, # ONNX算子集版本
do_constant_folding=True, # 是否执行常量折叠优化
input_names=["input"], # 输入名称
output_names=["output"], # 输出名称
dynamic_axes={"input": {0: "batch_size"}, # 动态轴
"output": {0: "batch_size"}})
print(f"ONNX模型已导出到: {onnx_file_path}")
return onnx_file_path
构建语义分割应用
接下来,我们创建一个完整的语义分割应用,包括图像预处理、TensorRT推理和结果可视化:
python
import os
import cv2
import numpy as np
import tensorrt as trt
import pycuda.driver as cuda
import pycuda.autoinit
from PIL import Image
class SemanticSegmenter:
"""使用TensorRT的语义分割器"""
def __init__(self, engine_file_path, num_classes=21):
"""
初始化语义分割器
参数:
engine_file_path: TensorRT引擎文件路径
num_classes: 类别数量(默认为21,对应Pascal VOC数据集)
"""
self.num_classes = num_classes
# 创建TensorRT推理引擎
self.logger = trt.Logger(trt.Logger.WARNING)
self.runtime = trt.Runtime(self.logger)
# 加载引擎
with open(engine_file_path, 'rb') as f:
self.engine = self.runtime.deserialize_cuda_engine(f.read())
# 创建执行上下文
self.context = self.engine.create_execution_context()
# 获取输入和输出的详细信息
self.input_idx = self.engine.get_binding_index("input")
self.output_idx = self.engine.get_binding_index("output")
# 获取输入形状
self.input_shape = self.engine.get_binding_shape(self.input_idx)
self.img_size = (self.input_shape[2], self.input_shape[3]) # HW
# 创建CUDA流
self.stream = cuda.Stream()
# 分配缓冲区
self._allocate_buffers()
# 创建颜色映射
self.color_map = self._create_pascal_label_colormap()
def _create_pascal_label_colormap(self):
"""
创建Pascal VOC数据集的颜色映射
返回:
颜色映射数组
"""
colormap = np.zeros((self.num_classes, 3), dtype=np.uint8)
for i in range(self.num_classes):
r = i * 10 % 255
g = (i * 100) % 255
b = (i * 29) % 255
colormap[i] = np.array([r, g, b])
return colormap
def _allocate_buffers(self):
"""分配输入和输出缓冲区"""
# 计算输入和输出大小
self.input_size = trt.volume(self.input_shape)
self.output_shape = self.engine.get_binding_shape(self.output_idx)
self.output_size = trt.volume(self.output_shape)
# 分配主机和设备内存
self.host_input = cuda.pagelocked_empty(self.input_size, dtype=np.float32)
self.host_output = cuda.pagelocked_empty(self.output_size, dtype=np.float32)
self.device_input = cuda.mem_alloc(self.host_input.nbytes)
self.device_output = cuda.mem_alloc(self.host_output.nbytes)
# 准备绑定
self.bindings = [int(self.device_input), int(self.device_output)]
def preprocess_image(self, image_path):
"""
预处理图像
参数:
image_path: 图像文件路径
返回:
预处理后的图像数据和原始图像
"""
# 读取图像
img = cv2.imread(image_path)
if img is None:
raise ValueError(f"无法读取图像: {image_path}")
# 保存原始图像
original_img = img.copy()
# 调整大小
img = cv2.resize(img, (self.img_size[1], self.img_size[0]))
# 转换为RGB并归一化
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = img.astype(np.float32) / 255.0
# 标准化
mean = np.array([0.485, 0.456, 0.406]).reshape((1, 1, 3))
std = np.array([0.229, 0.224, 0.225]).reshape((1, 1, 3))
img = (img - mean) / std
# 调整通道顺序:从HWC到CHW
img = img.transpose((2, 0, 1))
# 添加批次维度
img = np.expand_dims(img, axis=0)
return img, original_img
def segment(self, image_path):
"""
对图像进行语义分割
参数:
image_path: 图像文件路径
返回:
分割结果和可视化图像
"""
# 预处理图像
input_data, original_img = self.preprocess_image(image_path)
# 复制输入数据到主机内存
np.copyto(self.host_input, input_data.ravel())
# 将输入数据从主机复制到设备
cuda.memcpy_htod_async(self.device_input, self.host_input, self.stream)
# 执行推理
self.context.execute_async_v2(bindings=self.bindings, stream_handle=self.stream.handle)
# 将输出数据从设备复制回主机
cuda.memcpy_dtoh_async(self.host_output, self.device_output, self.stream)
# 同步流
self.stream.synchronize()
# 处理输出
output = self.host_output.reshape(self.output_shape)
# 获取分割掩码
output = output[0]['out'][0] # 假设输出是字典,包含'out'键
mask = np.argmax(output, axis=0)
# 调整掩码大小以匹配原始图像
mask = cv2.resize(mask.astype(np.uint8), (original_img.shape[1], original_img.shape[0]),
interpolation=cv2.INTER_NEAREST)
# 创建可视化图像
vis_img = self._create_colored_mask(mask, original_img)
return mask, vis_img
def _create_colored_mask(self, mask, original_img, alpha=0.5):
"""
创建彩色分割掩码
参数:
mask: 分割掩码
original_img: 原始图像
alpha: 混合透明度
返回:
彩色分割可视化
"""
# 创建彩色掩码
colored_mask = np.zeros_like(original_img)
for i in range(self.num_classes):
colored_mask[mask == i] = self.color_map[i]
# 混合原始图像和彩色掩码
vis_img = cv2.addWeighted(original_img, 1 - alpha, colored_mask, alpha, 0)
return vis_img
def __del__(self):
"""清理资源"""
# 释放设备内存
if hasattr(self, 'device_input'):
self.device_input.free()
if hasattr(self, 'device_output'):
self.device_output.free()
# 使用示例
def main():
# 准备模型
onnx_file_path = prepare_deeplabv3_model()
# 构建TensorRT引擎
from tensorrt_utils import build_engine_from_onnx
engine_file_path = "deeplabv3.engine"
if not os.path.exists(engine_file_path):
build_engine_from_onnx(onnx_file_path, engine_file_path, precision='fp16')
# 创建语义分割器
segmenter = SemanticSegmenter(engine_file_path)
# 对图像进行分割
image_path = "street.jpg"
mask, vis_img = segmenter.segment(image_path)
# 保存可视化结果
output_path = "segmentation_result.jpg"
cv2.imwrite(output_path, vis_img)
print(f"\n对图像 {image_path} 的分割结果已保存到: {output_path}")
if __name__ == "__main__":
main()
文本分类实战
自然语言处理(NLP)是深度学习的另一个重要应用领域。下面我们将展示如何使用TensorRT优化和部署BERT模型进行文本分类。
准备BERT模型
首先,我们需要准备一个预训练的BERT模型并将其转换为ONNX格式:
python
# 准备BERT模型并转换为ONNX格式
import torch
from transformers import BertTokenizer, BertForSequenceClassification
def prepare_bert_model():
"""
准备BERT模型并导出为ONNX格式
返回:
导出的ONNX模型文件路径和分词器
"""
print("准备BERT模型...")
# 加载预训练模型和分词器
model_name = "bert-base-uncased"
tokenizer = BertTokenizer.from_pretrained(model_name)
model = BertForSequenceClassification.from_pretrained(model_name)
model.eval()
# 创建示例输入
text = "TensorRT is a high-performance deep learning inference optimizer and runtime."
encoded_input = tokenizer(text, padding='max_length', max_length=128, truncation=True, return_tensors='pt')
# 导出ONNX模型
onnx_file_path = "bert.onnx"
torch.onnx.export(model, # 要导出的模型
(encoded_input['input_ids'], encoded_input['attention_mask']), # 模型输入
onnx_file_path, # 输出文件名
export_params=True, # 存储训练好的参数权重
opset_version=13, # ONNX算子集版本
do_constant_folding=True, # 是否执行常量折叠优化
input_names=["input_ids", "attention_mask"], # 输入名称
output_names=["output"], # 输出名称
dynamic_axes={"input_ids": {0: "batch_size"}, # 动态轴
"attention_mask": {0: "batch_size"},
"output": {0: "batch_size"}})
print(f"ONNX模型已导出到: {onnx_file_path}")
return onnx_file_path, tokenizer
构建文本分类应用
接下来,我们创建一个完整的文本分类应用,包括文本预处理、TensorRT推理和结果后处理:
python
import os
import numpy as np
import tensorrt as trt
import pycuda.driver as cuda
import pycuda.autoinit
class BERTClassifier:
"""使用TensorRT的BERT文本分类器"""
def __init__(self, engine_file_path, tokenizer):
"""
初始化BERT文本分类器
参数:
engine_file_path: TensorRT引擎文件路径
tokenizer: BERT分词器
"""
self.tokenizer = tokenizer
# 创建TensorRT推理引擎
self.logger = trt.Logger(trt.Logger.WARNING)
self.runtime = trt.Runtime(self.logger)
# 加载引擎
with open(engine_file_path, 'rb') as f:
self.engine = self.runtime.deserialize_cuda_engine(f.read())
# 创建执行上下文
self.context = self.engine.create_execution_context()
# 获取输入和输出的详细信息
self.input_ids_idx = self.engine.get_binding_index("input_ids")
self.attention_mask_idx = self.engine.get_binding_index("attention_mask")
self.output_idx = self.engine.get_binding_index("output")
# 获取输入形状
self.input_ids_shape = self.engine.get_binding_shape(self.input_ids_idx)
self.attention_mask_shape = self.engine.get_binding_shape(self.attention_mask_idx)
self.output_shape = self.engine.get_binding_shape(self.output_idx)
# 创建CUDA流
self.stream = cuda.Stream()
# 分配缓冲区
self._allocate_buffers()
def _allocate_buffers(self):
"""分配输入和输出缓冲区"""
# 计算输入和输出大小
self.input_ids_size = trt.volume(self.input_ids_shape)
self.attention_mask_size = trt.volume(self.attention_mask_shape)
self.output_size = trt.volume(self.output_shape)
# 分配主机和设备内存
self.host_input_ids = cuda.pagelocked_empty(self.input_ids_size, dtype=np.int32)
self.host_attention_mask = cuda.pagelocked_empty(self.attention_mask_size, dtype=np.int32)
self.host_output = cuda.pagelocked_empty(self.output_size, dtype=np.float32)
self.device_input_ids = cuda.mem_alloc(self.host_input_ids.nbytes)
self.device_attention_mask = cuda.mem_alloc(self.host_attention_mask.nbytes)
self.device_output = cuda.mem_alloc(self.host_output.nbytes)
# 准备绑定
self.bindings = [int(self.device_input_ids), int(self.device_attention_mask), int(self.device_output)]
def preprocess_text(self, text, max_length=128):
"""
预处理文本
参数:
text: 输入文本
max_length: 最大序列长度
返回:
预处理后的输入ID和注意力掩码
"""
# 使用分词器处理文本
encoded_input = self.tokenizer(text, padding='max_length', max_length=max_length,
truncation=True, return_tensors='np')
return encoded_input['input_ids'], encoded_input['attention_mask']
def classify(self, text):
"""
对文本进行分类
参数:
text: 输入文本
返回:
分类结果(类别ID和置信度)
"""
# 预处理文本
input_ids, attention_mask = self.preprocess_text(text)
# 复制输入数据到主机内存
np.copyto(self.host_input_ids, input_ids.ravel())
np.copyto(self.host_attention_mask, attention_mask.ravel())
# 将输入数据从主机复制到设备
cuda.memcpy_htod_async(self.device_input_ids, self.host_input_ids, self.stream)
cuda.memcpy_htod_async(self.device_attention_mask, self.host_attention_mask, self.stream)
# 执行推理
self.context.execute_async_v2(bindings=self.bindings, stream_handle=self.stream.handle)
# 将输出数据从设备复制回主机
cuda.memcpy_dtoh_async(self.host_output, self.device_output, self.stream)
# 同步流
self.stream.synchronize()
# 处理输出
output = self.host_output.reshape(self.output_shape)
# 应用softmax
exp_output = np.exp(output - np.max(output, axis=1, keepdims=True))
softmax_output = exp_output / np.sum(exp_output, axis=1, keepdims=True)
# 获取预测类别和置信度
class_id = np.argmax(softmax_output, axis=1)[0]
confidence = softmax_output[0, class_id]
return class_id, confidence
def __del__(self):
"""清理资源"""
# 释放设备内存
if hasattr(self, 'device_input_ids'):
self.device_input_ids.free()
if hasattr(self, 'device_attention_mask'):
self.device_attention_mask.free()
if hasattr(self, 'device_output'):
self.device_output.free()
# 使用示例
def main():
# 准备模型
onnx_file_path, tokenizer = prepare_bert_model()
# 构建TensorRT引擎
from tensorrt_utils import build_engine_from_onnx
engine_file_path = "bert.engine"
if not os.path.exists(engine_file_path):
build_engine_from_onnx(onnx_file_path, engine_file_path, precision='fp16')
# 创建文本分类器
classifier = BERTClassifier(engine_file_path, tokenizer)
# 对文本进行分类
text = "TensorRT is a high-performance deep learning inference optimizer and runtime."
class_id, confidence = classifier.classify(text)
# 打印结果
print(f"\n对文本的分类结果:")
print(f" 类别ID: {class_id}")
print(f" 置信度: {confidence:.4f}")
if __name__ == "__main__":
main()
总结
本章通过四个实际应用场景的完整示例,展示了如何使用TensorRT优化和部署不同类型的深度学习模型:
- 图像分类:使用ResNet-50模型对图像进行分类,展示了基本的图像预处理、TensorRT推理和结果后处理流程。
- 目标检测:使用YOLOv5模型进行目标检测,展示了如何处理更复杂的模型输出和可视化检测结果。
- 语义分割:使用DeepLabV3模型进行语义分割,展示了如何处理像素级预测任务和创建分割可视化。
- 文本分类:使用BERT模型进行文本分类,展示了TensorRT在自然语言处理任务中的应用。
这些示例涵盖了TensorRT在不同应用领域的使用方法,包括模型准备、引擎构建、推理执行和结果处理等关键步骤。通过这些实战案例,读者应该能够掌握如何将TensorRT应用到自己的实际项目中,充分发挥其性能优势。
在下一章中,我们将探讨TensorRT的高级特性和优化技巧,帮助读者进一步提升模型的推理性能。
第六部分:TensorRT高级特性与优化技巧
在前面的章节中,我们已经介绍了TensorRT的基本概念、安装方法、基础使用流程、API详解以及实际应用场景。本章将深入探讨TensorRT的高级特性和优化技巧,帮助读者进一步提升模型的推理性能。
量化技术
量化是TensorRT中最重要的优化技术之一,它通过降低计算精度来提高推理速度和减少内存占用。TensorRT支持多种量化精度,包括FP32(单精度浮点)、FP16(半精度浮点)和INT8(8位整数)。
FP16量化
FP16量化是最简单的量化方法,它将模型的权重和激活值从FP32转换为FP16,可以在不显著影响精度的情况下将内存使用量减少一半,并在支持FP16的GPU上获得更高的吞吐量。
python
# 使用FP16精度构建TensorRT引擎
import tensorrt as trt
def build_fp16_engine(onnx_file_path, engine_file_path):
"""
使用FP16精度构建TensorRT引擎
参数:
onnx_file_path: ONNX模型文件路径
engine_file_path: 输出的TensorRT引擎文件路径
"""
# 创建logger和builder
logger = trt.Logger(trt.Logger.WARNING)
builder = trt.Builder(logger)
# 创建网络定义
network_flags = 1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)
network = builder.create_network(network_flags)
# 创建ONNX解析器
parser = trt.OnnxParser(network, logger)
# 解析ONNX模型
with open(onnx_file_path, 'rb') as model:
if not parser.parse(model.read()):
print("ERROR: Failed to parse the ONNX file.")
for error in range(parser.num_errors):
print(parser.get_error(error))
return False
# 配置builder
config = builder.create_builder_config()
config.max_workspace_size = 1 << 30 # 1GB
# 启用FP16精度
if builder.platform_has_fast_fp16:
config.set_flag(trt.BuilderFlag.FP16)
print("FP16模式已启用")
else:
print("平台不支持FP16")
# 构建引擎
print("构建TensorRT引擎...")
engine = builder.build_engine(network, config)
# 序列化引擎并保存到文件
with open(engine_file_path, 'wb') as f:
f.write(engine.serialize())
print(f"TensorRT引擎已保存到: {engine_file_path}")
return True
INT8量化
INT8量化可以进一步减少内存使用量和提高推理速度,但需要校准数据来确定最佳的量化参数。TensorRT提供了多种校准方法,包括熵校准、熵校准2和遗留校准。
python
# 使用INT8精度和校准数据构建TensorRT引擎
import tensorrt as trt
import numpy as np
import os
from PIL import Image
import calibrator
class ImageBatchStream:
"""
图像批次流,用于INT8校准
"""
def __init__(self, calibration_files, batch_size, input_shape, preprocess_func):
"""
初始化图像批次流
参数:
calibration_files: 校准图像文件列表
batch_size: 批次大小
input_shape: 输入形状 (C, H, W)
preprocess_func: 图像预处理函数
"""
self.batch_size = batch_size
self.input_shape = input_shape
self.calibration_files = calibration_files
self.preprocess_func = preprocess_func
self.batch_idx = 0
self.max_batches = len(calibration_files) // batch_size
def reset(self):
"""重置批次索引"""
self.batch_idx = 0
def next_batch(self):
"""获取下一批次数据"""
if self.batch_idx < self.max_batches:
batch_files = self.calibration_files[self.batch_idx * self.batch_size:
(self.batch_idx + 1) * self.batch_size]
batch_data = np.zeros((self.batch_size, *self.input_shape), dtype=np.float32)
for i, file_path in enumerate(batch_files):
img = Image.open(file_path).convert('RGB')
batch_data[i] = self.preprocess_func(img)
self.batch_idx += 1
return np.ascontiguousarray(batch_data)
else:
return None
class ImageEntropyCalibrator(trt.IInt8EntropyCalibrator2):
"""
INT8熵校准器
"""
def __init__(self, batch_stream, cache_file):
"""
初始化校准器
参数:
batch_stream: 批次数据流
cache_file: 校准缓存文件路径
"""
trt.IInt8EntropyCalibrator2.__init__(self)
self.batch_stream = batch_stream
self.cache_file = cache_file
self.device_input = None
# 分配设备内存
input_size = trt.volume(batch_stream.input_shape) * batch_stream.batch_size
self.device_input = cuda.mem_alloc(input_size * np.dtype(np.float32).itemsize)
def get_batch_size(self):
"""返回批次大小"""
return self.batch_stream.batch_size
def get_batch(self, names):
"""获取下一批次数据"""
batch = self.batch_stream.next_batch()
if batch is None:
return None
# 将数据复制到设备
cuda.memcpy_htod(self.device_input, batch)
return [int(self.device_input)]
def read_calibration_cache(self):
"""读取校准缓存"""
if os.path.exists(self.cache_file):
with open(self.cache_file, "rb") as f:
return f.read()
return None
def write_calibration_cache(self, cache):
"""写入校准缓存"""
with open(self.cache_file, "wb") as f:
f.write(cache)
def build_int8_engine(onnx_file_path, engine_file_path, calibration_files, input_shape, preprocess_func):
"""
使用INT8精度和校准数据构建TensorRT引擎
参数:
onnx_file_path: ONNX模型文件路径
engine_file_path: 输出的TensorRT引擎文件路径
calibration_files: 校准图像文件列表
input_shape: 输入形状 (C, H, W)
preprocess_func: 图像预处理函数
"""
# 创建logger和builder
logger = trt.Logger(trt.Logger.WARNING)
builder = trt.Builder(logger)
# 创建网络定义
network_flags = 1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)
network = builder.create_network(network_flags)
# 创建ONNX解析器
parser = trt.OnnxParser(network, logger)
# 解析ONNX模型
with open(onnx_file_path, 'rb') as model:
if not parser.parse(model.read()):
print("ERROR: Failed to parse the ONNX file.")
for error in range(parser.num_errors):
print(parser.get_error(error))
return False
# 配置builder
config = builder.create_builder_config()
config.max_workspace_size = 1 << 30 # 1GB
# 启用INT8精度
if builder.platform_has_fast_int8:
config.set_flag(trt.BuilderFlag.INT8)
print("INT8模式已启用")
# 创建校准器
batch_size = 1
cache_file = "calibration.cache"
batch_stream = ImageBatchStream(calibration_files, batch_size, input_shape, preprocess_func)
calibrator = ImageEntropyCalibrator(batch_stream, cache_file)
# 设置校准器
config.int8_calibrator = calibrator
else:
print("平台不支持INT8")
return False
# 构建引擎
print("构建TensorRT引擎...")
engine = builder.build_engine(network, config)
# 序列化引擎并保存到文件
with open(engine_file_path, 'wb') as f:
f.write(engine.serialize())
print(f"TensorRT引擎已保存到: {engine_file_path}")
return True
动态形状支持
TensorRT支持动态输入形状,这对于处理可变大小的输入(如不同分辨率的图像或不同长度的序列)非常有用。
python
# 使用动态形状构建TensorRT引擎
import tensorrt as trt
def build_dynamic_shape_engine(onnx_file_path, engine_file_path, min_shape, opt_shape, max_shape):
"""
使用动态形状构建TensorRT引擎
参数:
onnx_file_path: ONNX模型文件路径
engine_file_path: 输出的TensorRT引擎文件路径
min_shape: 最小输入形状 (batch_size, channels, height, width)
opt_shape: 最优输入形状
max_shape: 最大输入形状
"""
# 创建logger和builder
logger = trt.Logger(trt.Logger.WARNING)
builder = trt.Builder(logger)
# 创建网络定义
network_flags = 1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)
network = builder.create_network(network_flags)
# 创建ONNX解析器
parser = trt.OnnxParser(network, logger)
# 解析ONNX模型
with open(onnx_file_path, 'rb') as model:
if not parser.parse(model.read()):
print("ERROR: Failed to parse the ONNX file.")
for error in range(parser.num_errors):
print(parser.get_error(error))
return False
# 配置builder
config = builder.create_builder_config()
config.max_workspace_size = 1 << 30 # 1GB
# 创建优化配置文件
profile = builder.create_optimization_profile()
# 设置动态形状
input_name = "input" # 输入张量的名称
profile.set_shape(input_name, min_shape, opt_shape, max_shape)
config.add_optimization_profile(profile)
# 构建引擎
print("构建TensorRT引擎...")
engine = builder.build_engine(network, config)
# 序列化引擎并保存到文件
with open(engine_file_path, 'wb') as f:
f.write(engine.serialize())
print(f"TensorRT引擎已保存到: {engine_file_path}")
return True
使用动态形状的引擎进行推理:
python
# 使用动态形状的引擎进行推理
import tensorrt as trt
import pycuda.driver as cuda
import pycuda.autoinit
import numpy as np
class DynamicShapeInferencer:
"""使用动态形状的TensorRT推理器"""
def __init__(self, engine_file_path):
"""
初始化推理器
参数:
engine_file_path: TensorRT引擎文件路径
"""
# 创建TensorRT推理引擎
self.logger = trt.Logger(trt.Logger.WARNING)
self.runtime = trt.Runtime(self.logger)
# 加载引擎
with open(engine_file_path, 'rb') as f:
self.engine = self.runtime.deserialize_cuda_engine(f.read())
# 创建执行上下文
self.context = self.engine.create_execution_context()
# 获取输入和输出的详细信息
self.input_idx = self.engine.get_binding_index("input")
self.output_idx = self.engine.get_binding_index("output")
# 创建CUDA流
self.stream = cuda.Stream()
def infer(self, input_data):
"""
执行推理
参数:
input_data: 输入数据,形状为(batch_size, channels, height, width)
返回:
推理结果
"""
# 获取输入形状
input_shape = input_data.shape
# 设置执行上下文的输入形状
self.context.set_binding_shape(self.input_idx, input_shape)
# 获取输出形状
output_shape = self.context.get_binding_shape(self.output_idx)
# 分配主机和设备内存
host_input = cuda.pagelocked_empty(trt.volume(input_shape), dtype=np.float32)
host_output = cuda.pagelocked_empty(trt.volume(output_shape), dtype=np.float32)
device_input = cuda.mem_alloc(host_input.nbytes)
device_output = cuda.mem_alloc(host_output.nbytes)
# 准备绑定
bindings = [int(device_input), int(device_output)]
# 复制输入数据到主机内存
np.copyto(host_input, input_data.ravel())
# 将输入数据从主机复制到设备
cuda.memcpy_htod_async(device_input, host_input, self.stream)
# 执行推理
self.context.execute_async_v2(bindings=bindings, stream_handle=self.stream.handle)
# 将输出数据从设备复制回主机
cuda.memcpy_dtoh_async(host_output, device_output, self.stream)
# 同步流
self.stream.synchronize()
# 释放设备内存
device_input.free()
device_output.free()
# 返回结果
return host_output.reshape(output_shape)
层间融合和张量核心加速
TensorRT会自动执行多种优化,包括层间融合和使用Tensor Cores(如果可用)来加速计算。
层间融合
层间融合是TensorRT的一项重要优化技术,它将多个相邻的层合并为一个层,减少内存访问和计算开销。例如,卷积层、批量归一化层和ReLU激活层可以融合为一个单一的层。
以下是一个示例,展示如何检查TensorRT执行的层间融合:
python
# 检查TensorRT执行的层间融合
import tensorrt as trt
def inspect_engine_layers(engine_file_path):
"""
检查TensorRT引擎中的层
参数:
engine_file_path: TensorRT引擎文件路径
"""
# 创建logger和runtime
logger = trt.Logger(trt.Logger.WARNING)
runtime = trt.Runtime(logger)
# 加载引擎
with open(engine_file_path, 'rb') as f:
engine = runtime.deserialize_cuda_engine(f.read())
# 获取层信息
num_layers = engine.num_layers
print(f"引擎中的层数: {num_layers}")
# 创建检查器
inspector = engine.create_engine_inspector()
# 获取引擎的JSON表示
json_str = inspector.get_engine_information(trt.LayerInformationFormat.JSON)
# 打印层信息
print("引擎层信息:")
print(json_str)
return json_str
使用Tensor Cores
Tensor Cores是NVIDIA Volta、Turing和Ampere架构GPU中的专用硬件单元,可以显著加速矩阵乘法和卷积操作。TensorRT会自动使用Tensor Cores(如果可用)来加速计算。
要启用Tensor Cores,只需在构建引擎时启用FP16或INT8精度:
python
# 启用Tensor Cores
config.set_flag(trt.BuilderFlag.FP16) # 对于FP16精度
# 或
config.set_flag(trt.BuilderFlag.INT8) # 对于INT8精度
多流并行处理
在处理多个输入时,可以使用CUDA流来并行执行推理,提高吞吐量。
python
# 使用多流并行处理
import tensorrt as trt
import pycuda.driver as cuda
import pycuda.autoinit
import numpy as np
import threading
class MultiStreamInferencer:
"""使用多流的TensorRT推理器"""
def __init__(self, engine_file_path, num_streams=4):
"""
初始化推理器
参数:
engine_file_path: TensorRT引擎文件路径
num_streams: 流的数量
"""
# 创建TensorRT推理引擎
self.logger = trt.Logger(trt.Logger.WARNING)
self.runtime = trt.Runtime(self.logger)
# 加载引擎
with open(engine_file_path, 'rb') as f:
self.engine = self.runtime.deserialize_cuda_engine(f.read())
# 创建执行上下文
self.contexts = [self.engine.create_execution_context() for _ in range(num_streams)]
# 获取输入和输出的详细信息
self.input_idx = self.engine.get_binding_index("input")
self.output_idx = self.engine.get_binding_index("output")
# 获取输入和输出形状
self.input_shape = self.engine.get_binding_shape(self.input_idx)
self.output_shape = self.engine.get_binding_shape(self.output_idx)
# 创建CUDA流
self.streams = [cuda.Stream() for _ in range(num_streams)]
# 分配主机和设备内存
self.host_inputs = []
self.host_outputs = []
self.device_inputs = []
self.device_outputs = []
self.bindings = []
for _ in range(num_streams):
host_input = cuda.pagelocked_empty(trt.volume(self.input_shape), dtype=np.float32)
host_output = cuda.pagelocked_empty(trt.volume(self.output_shape), dtype=np.float32)
device_input = cuda.mem_alloc(host_input.nbytes)
device_output = cuda.mem_alloc(host_output.nbytes)
bindings = [int(device_input), int(device_output)]
self.host_inputs.append(host_input)
self.host_outputs.append(host_output)
self.device_inputs.append(device_input)
self.device_outputs.append(device_output)
self.bindings.append(bindings)
# 创建线程锁
self.lock = threading.Lock()
# 创建线程池
self.threads = []
self.results = [None] * num_streams
def infer_async(self, input_data_list):
"""
异步执行推理
参数:
input_data_list: 输入数据列表
返回:
推理结果列表
"""
num_inputs = len(input_data_list)
num_streams = len(self.streams)
# 确保输入数量不超过流的数量
if num_inputs > num_streams:
raise ValueError(f"输入数量 ({num_inputs}) 超过了流的数量 ({num_streams})")
# 清空线程列表
self.threads = []
self.results = [None] * num_streams
# 为每个输入创建一个线程
for i in range(num_inputs):
thread = threading.Thread(target=self._infer_single, args=(i, input_data_list[i]))
self.threads.append(thread)
thread.start()
# 等待所有线程完成
for thread in self.threads:
thread.join()
# 返回结果
return self.results[:num_inputs]
def _infer_single(self, stream_idx, input_data):
"""
在单个流上执行推理
参数:
stream_idx: 流索引
input_data: 输入数据
"""
# 获取对应的资源
host_input = self.host_inputs[stream_idx]
host_output = self.host_outputs[stream_idx]
device_input = self.device_inputs[stream_idx]
device_output = self.device_outputs[stream_idx]
bindings = self.bindings[stream_idx]
stream = self.streams[stream_idx]
context = self.contexts[stream_idx]
# 复制输入数据到主机内存
np.copyto(host_input, input_data.ravel())
# 将输入数据从主机复制到设备
cuda.memcpy_htod_async(device_input, host_input, stream)
# 执行推理
context.execute_async_v2(bindings=bindings, stream_handle=stream.handle)
# 将输出数据从设备复制回主机
cuda.memcpy_dtoh_async(host_output, device_output, stream)
# 同步流
stream.synchronize()
# 保存结果
with self.lock:
self.results[stream_idx] = host_output.reshape(self.output_shape).copy()
def __del__(self):
"""清理资源"""
# 释放设备内存
for device_input, device_output in zip(self.device_inputs, self.device_outputs):
device_input.free()
device_output.free()
内存优化
TensorRT提供了多种内存优化技术,包括权重共享、激活值重用和内存池。
python
# 配置内存优化
import tensorrt as trt
def configure_memory_optimization(config):
"""
配置内存优化
参数:
config: TensorRT构建器配置
"""
# 设置最大工作空间大小
config.max_workspace_size = 1 << 30 # 1GB
# 启用激活值重用
config.set_flag(trt.BuilderFlag.REFIT)
# 启用内存池
config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 1 << 30) # 1GB
性能分析和调试
TensorRT提供了多种工具来分析和调试模型性能。
使用CUDA性能分析器
python
# 使用CUDA性能分析器
import tensorrt as trt
import pycuda.driver as cuda
import pycuda.autoinit
import numpy as np
import time
class Profiler(trt.IProfiler):
"""TensorRT性能分析器"""
def __init__(self):
trt.IProfiler.__init__(self)
self.layers = []
self.times = []
def report_layer_time(self, layer_name, ms):
"""
报告层的执行时间
参数:
layer_name: 层名称
ms: 执行时间(毫秒)
"""
self.layers.append(layer_name)
self.times.append(ms)
def print_report(self):
"""打印性能报告"""
total_time = sum(self.times)
print("=== 性能分析报告 ===")
print(f"总层数: {len(self.layers)}")
print(f"总执行时间: {total_time:.2f} ms")
# 按执行时间排序
sorted_indices = sorted(range(len(self.times)), key=lambda i: self.times[i], reverse=True)
print("\n前10个最耗时的层:")
for i in range(min(10, len(sorted_indices))):
idx = sorted_indices[i]
layer_name = self.layers[idx]
time_ms = self.times[idx]
percentage = time_ms / total_time * 100
print(f" {i+1}. {layer_name}: {time_ms:.2f} ms ({percentage:.2f}%)")
print("\n总执行时间: {total_time:.2f} ms")
def profile_engine(engine_file_path, input_data):
"""
分析TensorRT引擎性能
参数:
engine_file_path: TensorRT引擎文件路径
input_data: 输入数据
"""
# 创建logger和runtime
logger = trt.Logger(trt.Logger.WARNING)
runtime = trt.Runtime(logger)
# 加载引擎
with open(engine_file_path, 'rb') as f:
engine = runtime.deserialize_cuda_engine(f.read())
# 创建执行上下文
context = engine.create_execution_context()
# 获取输入和输出的详细信息
input_idx = engine.get_binding_index("input")
output_idx = engine.get_binding_index("output")
# 获取输入和输出形状
input_shape = engine.get_binding_shape(input_idx)
output_shape = engine.get_binding_shape(output_idx)
# 分配主机和设备内存
host_input = cuda.pagelocked_empty(trt.volume(input_shape), dtype=np.float32)
host_output = cuda.pagelocked_empty(trt.volume(output_shape), dtype=np.float32)
device_input = cuda.mem_alloc(host_input.nbytes)
device_output = cuda.mem_alloc(host_output.nbytes)
# 准备绑定
bindings = [int(device_input), int(device_output)]
# 复制输入数据到主机内存
np.copyto(host_input, input_data.ravel())
# 将输入数据从主机复制到设备
cuda.memcpy_htod(device_input, host_input)
# 创建性能分析器
profiler = Profiler()
# 预热
for _ in range(10):
context.execute_v2(bindings=bindings)
# 使用性能分析器执行推理
context.execute_v2(bindings=bindings, profiler=profiler)
# 将输出数据从设备复制回主机
cuda.memcpy_dtoh(host_output, device_output)
# 打印性能报告
profiler.print_report()
# 测量端到端延迟
num_runs = 100
start_time = time.time()
for _ in range(num_runs):
context.execute_v2(bindings=bindings)
end_time = time.time()
# 计算平均延迟
avg_latency = (end_time - start_time) * 1000 / num_runs
print(f"平均端到端延迟: {avg_latency:.2f} ms")
# 释放设备内存
device_input.free()
device_output.free()
使用TensorRT的Inspector API
TensorRT 8.0引入了Inspector API,可以用来检查引擎的内部结构和配置:
python
# 使用TensorRT的Inspector API
def inspect_engine(engine_file_path):
"""
检查TensorRT引擎
参数:
engine_file_path: TensorRT引擎文件路径
"""
# 创建logger和runtime
logger = trt.Logger(trt.Logger.WARNING)
runtime = trt.Runtime(logger)
# 加载引擎
with open(engine_file_path, 'rb') as f:
engine = runtime.deserialize_cuda_engine(f.read())
# 创建检查器
inspector = engine.create_engine_inspector()
# 获取引擎信息
engine_info = inspector.get_engine_information(trt.LayerInformationFormat.JSON)
# 打印引擎信息
print("=== 引擎信息 ===")
print(engine_info)
最佳实践和优化技巧
以下是一些使用TensorRT进行模型优化和部署的最佳实践和技巧:
1. 选择合适的精度
根据应用需求和硬件支持选择合适的精度:
- 对于需要高精度的应用,使用FP32
- 对于大多数应用,FP16通常能提供良好的精度和性能平衡
- 对于对精度要求不高的应用,INT8可以提供最佳性能
2. 使用动态批处理
如果应用需要处理不同大小的批次,使用动态批处理可以避免为每个批次大小构建单独的引擎:
python
# 设置动态批处理
profile = builder.create_optimization_profile()
profile.set_shape(input_name, (1, *input_shape[1:]), (opt_batch, *input_shape[1:]), (max_batch, *input_shape[1:]))
config.add_optimization_profile(profile)
3. 优化输入数据处理
- 使用页锁定内存(pinned memory)减少主机到设备的数据传输时间
- 使用异步数据传输和CUDA流重叠计算和数据传输
- 批量处理数据以提高吞吐量
4. 模型优化
- 移除不必要的操作和层
- 合并可以合并的层(例如,卷积+批量归一化+ReLU)
- 使用量化感知训练提高量化模型的精度
5. 使用TensorRT插件
对于TensorRT原生不支持的操作,可以使用自定义插件:
python
# 定义自定义插件
class MyPlugin(trt.IPluginV2):
# 实现插件接口
pass
# 注册插件
plugin_registry = trt.get_plugin_registry()
plugin_creator = plugin_registry.get_plugin_creator("MyPlugin", "1")
plugin = plugin_creator.create_plugin("my_plugin_instance", trt.PluginFieldCollection([]))
# 在网络中使用插件
layer = network.add_plugin_v2([input_tensor], plugin)
6. 使用ONNX-TensorRT解析器
使用ONNX作为中间表示可以简化从各种深度学习框架到TensorRT的转换过程:
python
# 从ONNX模型构建TensorRT引擎
parser = trt.OnnxParser(network, logger)
with open(onnx_file_path, 'rb') as model:
parser.parse(model.read())
7. 使用TF-TRT或PyTorch-TensorRT
对于TensorFlow或PyTorch用户,可以使用TF-TRT或PyTorch-TensorRT直接在原始框架中集成TensorRT优化:
python
# TF-TRT示例
import tensorflow as tf
from tensorflow.python.compiler.tensorrt import trt_convert as trt
# 转换TensorFlow模型为TF-TRT模型
converter = trt.TrtGraphConverterV2(input_saved_model_dir="saved_model")
converter.convert()
converter.save("trt_saved_model")
# 加载并使用TF-TRT模型
trt_model = tf.saved_model.load("trt_saved_model")
output = trt_model(input_data)
python
# PyTorch-TensorRT示例
import torch
import torch_tensorrt
# 转换PyTorch模型为TorchScript
scripted_model = torch.jit.script(model)
# 使用PyTorch-TensorRT编译模型
trt_model = torch_tensorrt.compile(scripted_model,
inputs=[torch_tensorrt.Input((1, 3, 224, 224))],
enabled_precisions={torch.float16})
# 使用编译后的模型
output = trt_model(input_data)
总结
本章深入探讨了TensorRT的高级特性和优化技巧,包括量化技术、动态形状支持、层间融合、张量核心加速、多流并行处理、内存优化、性能分析和调试等。通过应用这些技术和最佳实践,可以显著提高深度学习模型的推理性能,满足各种实际应用场景的需求。
在实际应用中,应根据具体需求和硬件条件选择合适的优化策略,并通过性能分析工具不断调整和优化,以达到最佳的性能和精度平衡。