4. 推理速度优化:使用TensorRT、ONNX Runtime、FP16推理加速实战
模型训练好了,检测效果也不错。但一上生产线,帧率卡在个位数。客户在旁边看着,你心里急不急?
我遇到过这种情况,还不止一次。说实话,模型精度差一点可以调,但速度上不去,项目根本没法交付。这一章,咱们就聊聊怎么把YOLO的推理速度提上去。
核心思路:模型加速不是玄学,本质就三件事——减少计算精度(FP16/INT8)、优化计算图(TensorRT/ONNX Runtime)、减少数据搬运(异步/流水线)。
4.1 为什么需要推理加速?
你想想看,YOLOv8s在GPU上跑FP32,大概能到100 FPS左右。听着不错对吧?但实际场景里,你还要做预处理、后处理、多路视频流、加上业务逻辑。帧率直接腰斩。
更别说嵌入式设备了。Jetson Nano上跑YOLOv8n,FP32模式也就十几帧。做实时检测?门都没有。
| 部署场景 | 目标帧率 | FP32瓶颈 | 加速方案 |
|---|---|---|---|
| 服务器端(A100) | ≥ 200 FPS | 带宽利用率低 | TensorRT + FP16 |
| 边缘端(Jetson Orin) | ≥ 60 FPS | 显存/算力受限 | TensorRT + INT8 |
| CPU部署 | ≥ 15 FPS | 无GPU加速 | ONNX Runtime + OpenVINO |
4.2 加速三板斧:ONNX → TensorRT → FP16
我个人习惯的流程是这样的:先把PyTorch模型转成ONNX,再用TensorRT优化,最后开启FP16推理。每一步都有坑,咱们一个一个说。
4.2.1 第一步:导出ONNX模型
ONNX是个中间格式,说白了就是让模型脱离PyTorch环境也能跑。YOLO官方已经支持直接导出:
# 导出ONNX,注意opset版本要≥11
yolo export model=yolov8s.pt format=onnx opset=12
# 或者用Python
import torch
model = torch.load('yolov8s.pt', map_location='cpu')
dummy_input = torch.randn(1, 3, 640, 640)
torch.onnx.export(model, dummy_input, 'yolov8s.onnx',
opset_version=12,
input_names=['images'],
output_names=['output0'])
我的经验:opset版本别用太新的,12或13最稳。我遇到过opset=15导出的模型在TensorRT里某些算子不兼容,排查了两天才发现是版本问题。
4.2.2 第二步:TensorRT优化
TensorRT会做三件事:层融合、精度校准、内存复用。效果立竿见影。
import tensorrt as trt
# 构建TensorRT引擎
TRT_LOGGER = trt.Logger(trt.Logger.WARNING)
builder = trt.Builder(TRT_LOGGER)
network = builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH))
parser = trt.OnnxParser(network, TRT_LOGGER)
# 解析ONNX模型
with open('yolov8s.onnx', 'rb') as f:
parser.parse(f.read())
# 配置builder
config = builder.create_builder_config()
config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 1 << 30) # 1GB
# 开启FP16
if builder.platform_has_fast_fp16:
config.set_flag(trt.BuilderFlag.FP16)
# 构建引擎(这一步最耗时)
engine = builder.build_serialized_network(network, config)
with open('yolov8s_fp16.engine', 'wb') as f:
f.write(engine)
注意:第一次构建引擎可能很慢,几分钟到十几分钟不等。别以为是卡死了。我曾在客户现场等了20分钟,差点重启机器……后来发现是正常的。
4.2.3 第三步:FP16推理实战
FP16就是把模型权重和激活值从32位浮点变成16位。精度损失很小(通常mAP下降不到0.5%),但速度能翻倍。
import tensorrt as trt
import pycuda.driver as cuda
import pycuda.autoinit
import numpy as np
# 加载引擎
with open('yolov8s_fp16.engine', 'rb') as f:
engine_data = f.read()
runtime = trt.Runtime(TRT_LOGGER)
engine = runtime.deserialize_cuda_engine(engine_data)
# 创建执行上下文
context = engine.create_execution_context()
# 分配GPU内存
input_buf = cuda.mem_alloc(1 * 3 * 640 * 640 * 2) # FP16: 2字节
output_buf = cuda.mem_alloc(1 * 84 * 8400 * 2)
# 推理循环
for frame in video_frames:
# 预处理 + 拷贝到GPU
input_data = preprocess(frame).astype(np.float16)
cuda.memcpy_htod(input_buf, input_data)
# 执行推理(异步)
context.execute_async_v2([int(input_buf), int(output_buf)], stream.handle)
# 取回结果
output_data = np.empty(1 * 84 * 8400, dtype=np.float16)
cuda.memcpy_dtoh(output_data, output_buf)
# 后处理
detections = postprocess(output_data)
4.3 性能对比:FP32 vs FP16 vs INT8
我在一个实际项目中做过完整对比。用的是YOLOv8s,输入640×640,GPU是RTX 3060。
| 推理模式 | 帧率(FPS) | mAP@0.5 | 显存占用 | 构建时间 |
|---|---|---|---|---|
| PyTorch FP32 | 95 | 0.537 | 2.1 GB | — |
| ONNX Runtime FP32 | 112 | 0.537 | 1.8 GB | — |
| TensorRT FP32 | 168 | 0.537 | 1.2 GB | 3 min |
| TensorRT FP16 | 289 | 0.534 | 0.7 GB | 5 min |
| TensorRT INT8 | 352 | 0.521 | 0.5 GB | 15 min |
看到没?FP16比PyTorch快了3倍,mAP只掉了0.003。INT8更快,但精度下降明显,需要校准数据集。我个人建议:FP16是性价比最高的选择。
4.4 避坑指南
我曾经在部署时踩过不少坑,这里列几个最常见的:
- 动态shape问题:TensorRT默认要求固定输入尺寸。如果你的业务需要变尺寸输入,记得开启动态shape配置,但会牺牲一点性能。
- 后处理瓶颈:很多人只优化模型推理,忘了后处理。NMS用CPU跑?那加速效果全白费。建议用TensorRT的EfficientNMS插件,或者自己写CUDA NMS。
- 显存泄漏:每次推理都重新创建context?显存会慢慢涨满。复用context,用异步流管理。
- 精度校准:INT8需要校准数据集,选100-500张有代表性的图片就行。别用训练集,用验证集。
我的习惯:每次换硬件平台,我都会重新构建引擎。不同GPU的TensorRT优化策略不一样,直接拷贝.engine文件可能跑不起来,或者性能很差。
4.5 知识体系总览
下面这张图把整个推理加速的流程串起来了。你可以把它当作一个检查清单:
4.6 总结
推理加速这件事,说白了就是用工程手段把模型的潜力榨干。FP16不是银弹,但它是投入产出比最高的方案。我建议你从ONNX Runtime开始,确认模型没问题,再上TensorRT FP16。如果帧率还不够,再考虑INT8。
嗯,这一章的内容就到这里。记住:先跑通,再优化,最后才上生产。别一上来就搞INT8,调试起来够你喝一壶的。