16、深度学习推理优化:模型剪枝与量化、TensorRT动态形状、多流推理、Batch Size调优

各位同学,今天我们来聊聊推理优化。说实话,这块内容是我在Jetson平台上花时间最多的地方。模型训练好了,跑不起来,或者跑起来卡成PPT,那前面所有工作都白费了。

推理优化说白了就两件事:让模型跑得更快让模型占得更少。在Jetson这种资源受限的嵌入式平台上,这两件事尤其重要。我见过太多人,模型在服务器上跑得飞起,一部署到Jetson上就崩了——显存爆了,或者帧率只有个位数。

今天我会把模型剪枝、量化、TensorRT动态形状、多流推理、Batch Size调优这几个核心话题串起来讲。它们不是孤立的,而是环环相扣的一套组合拳。

Jetson 推理优化 模型剪枝 模型量化 动态形状 多流推理 四步组合拳:剪枝 → 量化 → 动态形状 → 多流并行

16.1 模型剪枝:去掉冗余,轻装上阵

模型剪枝,说白了就是把神经网络里那些「不干活」的神经元或者通道砍掉。你想想看,一个训练好的模型,很多权重其实接近于零,留着它们干嘛?

我个人习惯把剪枝分成两类:结构化剪枝非结构化剪枝

  • 结构化剪枝:直接砍掉整个通道或卷积核。好处是剪完后模型结构变了,推理引擎能直接受益。我在项目中遇到过,剪掉30%的通道,精度只掉了0.5%,但推理速度提升了近一倍。
  • 非结构化剪枝:把单个权重置零。这种方式比较细粒度,但说实话,在Jetson上效果一般——因为硬件对稀疏矩阵的支持有限。
我的建议:在Jetson上优先做结构化剪枝。别问我为什么,踩过坑的人都知道——非结构化剪枝后的模型,TensorRT优化起来很痛苦。

剪枝的流程一般是:训练→剪枝→微调。嗯,这里要注意,剪枝后一定要微调,否则精度会掉得你怀疑人生。

16.2 模型量化:FP16还是INT8?这是个问题

量化,就是把模型从FP32压缩到FP16甚至INT8。在Jetson上,这一步几乎是必做的。我记得第一次在Xavier上跑一个FP32的ResNet-50,显存直接爆了——后来换成INT8,不仅显存降了4倍,速度还快了3倍多。

TensorRT支持两种量化方式:

  • FP16量化:精度损失极小,几乎可以忽略。适合对精度要求高的场景。
  • INT8量化:需要校准数据集。精度会有一定损失,但速度提升明显。

INT8量化有个关键步骤——校准。你需要准备几百张有代表性的图片,让TensorRT去统计激活值的分布。我曾经因为偷懒,只用了50张图做校准,结果模型在边缘场景下精度崩了。后来老老实实用了500张,问题解决。

核心经验:INT8校准数据集一定要覆盖实际部署场景的分布。比如你做安防监控,就别拿网上随便下载的风景图去校准。

16.3 TensorRT动态形状:应对变尺寸输入

很多场景下,输入图像的尺寸不是固定的。比如目标检测,不同视频帧的分辨率可能不一样。这时候就需要TensorRT的动态形状支持。

动态形状说白了就是告诉TensorRT:我的输入尺寸会变,你帮我准备好应对方案。配置方式如下:

// C++ 示例:配置动态形状
IBuilder* builder = createInferBuilder(gLogger);
INetworkDefinition* network = builder->createNetwork();

// 定义输入张量,指定动态维度
ITensor* input = network->addInput(
    "input", DataType::kFLOAT, Dims4{-1, 3, -1, -1});

// 配置优化策略
IOptimizationProfile* profile = builder->createOptimizationProfile();
profile->setDimensions(input, OptProfileSelector::kMIN, Dims4{1, 3, 224, 224});
profile->setDimensions(input, OptProfileSelector::kOPT, Dims4{4, 3, 640, 640});
profile->setDimensions(input, OptProfileSelector::kMAX, Dims4{8, 3, 1280, 1280});

// 构建配置
IConfig* config = builder->createBuilderConfig();
config->addOptimizationProfile(profile);
注意:动态形状会带来额外的显存开销。因为TensorRT需要为不同尺寸都预留空间。我建议你根据实际场景,把MIN和MAX的范围控制得尽量紧凑,别留太大余量。

16.4 多流推理:榨干硬件每一分性能

多流推理,就是同时处理多个推理请求。在Jetson上,这招特别管用。为什么?因为GPU的并行能力很强,单流推理往往喂不饱它。

我做过一个测试:在Jetson Orin上,单流推理一个分类模型,GPU利用率只有40%左右。开了4个流之后,利用率直接飙到95%,吞吐量提升了2.5倍。

实现多流推理,核心是创建多个cudaStream

// 创建多个CUDA流
cudaStream_t streams[4];
for (int i = 0; i < 4; i++) {
    cudaStreamCreate(&streams[i]);
}

// 每个流独立执行推理
for (int i = 0; i < 4; i++) {
    context->enqueueV2(buffers, streams[i], nullptr);
}

// 同步等待所有流完成
cudaDeviceSynchronize();

这里有个坑——显存竞争。多个流同时访问显存,如果模型太大,可能会互相干扰。我的做法是:先估算每个流需要的显存,然后乘以流数量,确保总显存不超过硬件上限。

16.5 Batch Size调优:找到那个甜蜜点

Batch Size调优,说白了就是找「吞吐量最大」和「延迟最低」之间的平衡点。在Jetson上,这个平衡点往往比你想象的要小。

我整理了一份常见模型的调优参考:

模型 Jetson Orin (FP16) Jetson Orin (INT8) Jetson Xavier (FP16)
ResNet-50 Batch=8 Batch=16 Batch=4
YOLOv8s Batch=4 Batch=8 Batch=2
BERT-base Batch=2 Batch=4 Batch=1

调优方法其实很简单:从Batch=1开始,逐步增加,同时监控帧率和显存。当帧率不再明显提升,或者显存接近上限时,那就是你的甜蜜点。

一个小技巧:如果你做的是实时推理(比如视频流),Batch Size不要太大。因为要等凑够一个Batch才能推理,这会引入额外延迟。我一般控制在4以内。

16.6 组合拳:把优化串起来

上面讲的这些技术,单独用效果有限,组合起来才是王炸。我分享一个实际案例:

之前做一个智慧零售项目,需要在Jetson Orin上跑一个目标检测模型。原始模型FP32,单流推理,帧率只有15fps。我做了三步优化:

  1. 结构化剪枝:砍掉20%的通道,精度几乎不变。
  2. INT8量化:用500张门店图片做校准,精度损失不到1%。
  3. 多流推理 + Batch=4:开了3个流,每个流Batch=4。

最终帧率从15fps提升到了120fps,显存占用反而降了30%。嗯,这就是组合拳的威力。

好了,关于推理优化,核心就是这些。记住一个原则:先剪枝,再量化,最后调Batch和多流。顺序别搞反了,否则效果会打折扣。


公众号:蓝海资料掘金营,微信deep3321