第三章:感知算法优化——模型轻量化与部署加速
感知算法,说白了就是自动驾驶的「眼睛」和「大脑」。但问题来了——你想想看,一个ResNet-152跑在Jetson上,帧率可能只有个位数。车都撞上了,模型还没反应过来。所以,模型轻量化和部署加速,是每个感知工程师的必修课。
我个人习惯把这一章分成三块:模型轻量化(让模型变小变快)、TensorRT加速(NVIDIA家的王牌工具)、ONNX Runtime部署(跨平台的通用方案)。咱们一个一个来聊。
3.1 模型轻量化:剪枝、量化、蒸馏
模型轻量化,说白了就是「减肥」。一个200MB的模型,你不可能直接塞到车规级芯片里。我见过太多团队,模型精度98%,但部署上去延迟200ms,根本没法用。所以,轻量化是第一步。
3.1.1 剪枝(Pruning)
剪枝,就是砍掉不重要的神经元或通道。我在项目中遇到过一个问题:剪枝后模型精度掉得厉害。后来发现,是剪枝策略太粗暴了。
常用的剪枝方法有两种:
- 非结构化剪枝:把权重接近0的连接直接干掉。优点是压缩率高,但硬件不友好,稀疏矩阵运算慢。
- 结构化剪枝:直接砍掉整个通道或卷积核。硬件友好,但精度损失需要微调来弥补。
代码示例(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 | 轻量模型、端侧 |
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)
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})
3.4 总结与建议
好了,这一章的内容差不多就这些。我最后给你一个实战建议:
- 先剪枝,再量化,最后蒸馏。这个顺序能最大化压缩率。
- TensorRT适合NVIDIA平台,性能最优。但要注意算子兼容性。
- ONNX Runtime适合跨平台,尤其是异构计算场景。
- 一定要做端到端测试。模型在PC上跑得再快,上了车可能就卡了。
嗯,我记得刚入行时,总觉得模型轻量化是「锦上添花」。直到有一次,一个200MB的模型在嵌入式设备上跑出了500ms的延迟,客户直接拍桌子。从那以后,我再也不敢忽视部署优化了。
下一章,我们会聊规划与控制算法优化。到时候见。