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 知识体系总览

下面这张图把整个推理加速的流程串起来了。你可以把它当作一个检查清单:

推理加速流程总览 推理加速流程总览 PyTorch模型 ONNX导出 ONNX模型 TensorRT Builder 层融合 | 精度校准 | 内存优化 | 内核自动调优 FP32引擎 FP16引擎 ★ INT8引擎 推理部署 异步流 | 内存池复用 | 预处理/后处理优化 | 多路并行 ★ 推荐方案:FP16在精度与速度之间取得最佳平衡,适用于大多数部署场景

4.6 总结

推理加速这件事,说白了就是用工程手段把模型的潜力榨干。FP16不是银弹,但它是投入产出比最高的方案。我建议你从ONNX Runtime开始,确认模型没问题,再上TensorRT FP16。如果帧率还不够,再考虑INT8。

嗯,这一章的内容就到这里。记住:先跑通,再优化,最后才上生产。别一上来就搞INT8,调试起来够你喝一壶的。