第三章:感知算法优化——模型轻量化与部署加速

感知算法,说白了就是自动驾驶的「眼睛」和「大脑」。但问题来了——你想想看,一个ResNet-152跑在Jetson上,帧率可能只有个位数。车都撞上了,模型还没反应过来。所以,模型轻量化和部署加速,是每个感知工程师的必修课。

我个人习惯把这一章分成三块:模型轻量化(让模型变小变快)、TensorRT加速(NVIDIA家的王牌工具)、ONNX Runtime部署(跨平台的通用方案)。咱们一个一个来聊。

3.1 模型轻量化:剪枝、量化、蒸馏

模型轻量化,说白了就是「减肥」。一个200MB的模型,你不可能直接塞到车规级芯片里。我见过太多团队,模型精度98%,但部署上去延迟200ms,根本没法用。所以,轻量化是第一步。

3.1.1 剪枝(Pruning)

剪枝,就是砍掉不重要的神经元或通道。我在项目中遇到过一个问题:剪枝后模型精度掉得厉害。后来发现,是剪枝策略太粗暴了。

常用的剪枝方法有两种:

  • 非结构化剪枝:把权重接近0的连接直接干掉。优点是压缩率高,但硬件不友好,稀疏矩阵运算慢。
  • 结构化剪枝:直接砍掉整个通道或卷积核。硬件友好,但精度损失需要微调来弥补。
我的经验:结构化剪枝配合微调,一般能砍掉30%-50%的参数,精度损失控制在1%以内。我建议从最后一层开始剪,因为浅层特征更重要。

代码示例(PyTorch结构化剪枝):

import torch.nn.utils.prune as prune

# 对卷积层进行结构化剪枝(L1范数)
prune.l1_unstructured(conv_layer, name='weight', amount=0.3)
# 剪掉30%的通道
prune.remove(conv_layer, 'weight')  # 使剪枝永久化

3.1.2 量化(Quantization)

量化,就是把FP32的模型变成INT8甚至INT4。说白了,就是用更少的比特数表示权重。为什么能这么做?因为神经网络对噪声有天然的鲁棒性。

我曾经踩过一个坑:直接做后训练量化,结果模型在夜间场景下直接崩了。后来才发现,量化校准集必须覆盖所有典型场景,尤其是边缘case。

量化类型 精度损失 加速比 适用场景
FP32 → FP16 几乎无损失 1.5x - 2x 所有模型
FP32 → INT8 0.5% - 2% 3x - 4x 分类、检测
FP32 → INT4 1% - 5% 5x - 8x 轻量模型、端侧
注意:量化后的模型一定要在目标硬件上做端到端测试。我见过一个案例,INT8模型在GPU上跑得好好的,换到NPU上直接精度掉到60%。不同硬件的量化支持不一样。

3.1.3 知识蒸馏(Knowledge Distillation)

知识蒸馏,就是让一个大模型(教师)教一个小模型(学生)。学生模型虽然参数少,但能学到教师模型的「暗知识」——比如类别之间的相似性。

我个人习惯用软标签来训练学生模型。教师模型的输出经过温度系数软化后,包含了更多信息。举个例子,教师模型可能输出「猫0.7、狗0.2、兔子0.1」,这比硬标签「猫1.0」有用得多。

蒸馏的损失函数一般是:

loss = alpha * KL_div(student_soft, teacher_soft) + (1-alpha) * CE(student_hard, ground_truth)

alpha一般取0.7左右。我试过alpha=0.5,学生模型学得不够好;alpha=0.9,又太依赖教师,泛化能力差。嗯,这个参数需要调。

3.2 TensorRT加速

TensorRT是NVIDIA的推理优化引擎。说白了,它能把你的模型编译成GPU上跑得最快的版本。我刚开始用TensorRT时,觉得它就是个黑盒子——丢进去一个模型,出来一个engine,速度翻倍。但后来发现,里面门道很多。

3.2.1 TensorRT的核心优化

  • 层融合(Layer Fusion):把Conv+BN+ReLU合并成一个kernel,减少显存读写。
  • 精度校准(INT8 Calibration):用校准集计算量化阈值,减少精度损失。
  • 动态张量(Dynamic Shapes):支持可变输入尺寸,适合多分辨率场景。

我记得有一次,一个YOLOv5模型在TensorRT上从30ms降到了8ms。但注意,TensorRT对某些算子支持不好,比如自定义的ROI Align。遇到这种情况,我建议用Plugin来扩展。

TensorRT部署流程:

# 1. 导出ONNX模型
torch.onnx.export(model, dummy_input, "model.onnx")

# 2. 构建TensorRT engine
import tensorrt as trt
logger = trt.Logger(trt.Logger.WARNING)
builder = trt.Builder(logger)
network = builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH))
parser = trt.OnnxParser(network, logger)
parser.parse_from_file("model.onnx")

# 3. 配置优化
config = builder.create_builder_config()
config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 1 << 30)  # 1GB
config.set_flag(trt.BuilderFlag.FP16)  # 启用FP16

# 4. 序列化engine
serialized_engine = builder.build_serialized_network(network, config)
关键点:TensorRT的INT8量化需要校准集。校准集大小一般100-500张图就够了,但一定要覆盖各种光照和天气条件。我吃过亏——只用白天数据校准,晚上场景直接崩。

3.3 ONNX Runtime部署

ONNX Runtime是微软开源的推理引擎。它的优势是跨平台——你可以在Windows、Linux、ARM上跑同一个模型。而且它支持多种硬件后端:CPU、GPU、NPU、甚至FPGA。

我为什么推荐ONNX Runtime?因为不是所有项目都用NVIDIA的卡。有些客户用瑞芯微、地平线的芯片,TensorRT就用不了。这时候,ONNX Runtime就是通用方案。

3.3.1 ONNX Runtime的优化技巧

  • 会话优化(Session Options):设置线程数、图优化级别。
  • 执行提供者(Execution Providers):选择CUDA、TensorRT、OpenVINO等后端。
  • 内存模式:用OrtMemoryInfo控制内存分配策略。

代码示例(ONNX Runtime推理):

import onnxruntime as ort

# 创建会话
sess_options = ort.SessionOptions()
sess_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
sess_options.intra_op_num_threads = 4

# 选择执行提供者(优先TensorRT,其次CUDA)
providers = ['TensorrtExecutionProvider', 'CUDAExecutionProvider', 'CPUExecutionProvider']
session = ort.InferenceSession("model.onnx", sess_options, providers=providers)

# 推理
input_name = session.get_inputs()[0].name
output = session.run(None, {input_name: input_data})
避坑指南:我曾经在ARM平台上用ONNX Runtime,发现推理速度比预期慢很多。后来发现是线程数没设对。ARM的CPU核心数少,设太多线程反而导致上下文切换开销。一般设为核心数-1。

3.4 总结与建议

好了,这一章的内容差不多就这些。我最后给你一个实战建议

  1. 先剪枝,再量化,最后蒸馏。这个顺序能最大化压缩率。
  2. TensorRT适合NVIDIA平台,性能最优。但要注意算子兼容性。
  3. ONNX Runtime适合跨平台,尤其是异构计算场景。
  4. 一定要做端到端测试。模型在PC上跑得再快,上了车可能就卡了。

嗯,我记得刚入行时,总觉得模型轻量化是「锦上添花」。直到有一次,一个200MB的模型在嵌入式设备上跑出了500ms的延迟,客户直接拍桌子。从那以后,我再也不敢忽视部署优化了。

下一章,我们会聊规划与控制算法优化。到时候见。