3. 模型转换与量化:把PyTorch/TensorFlow模型塞进Eyeriss
好,到了第三章。前面两章我们把Eyeriss的硬件架构和工具链摸了个大概,现在该动真格的了——把训练好的模型,从PyTorch或者TensorFlow那边,搬到Eyeriss能认得的地盘上,顺便给它做个“瘦身”,也就是INT8量化。
这一步,说白了就是“翻译+压缩”。翻译不好,模型跑不起来;压缩不好,精度崩给你看。我当年第一次做这个环节,差点被一个量化后的精度掉点搞到通宵,后来发现是校准集没选对。嗯,这些坑咱们今天一个个填上。
3.1 模型转换:从框架到Eyeriss中间表示
Eyeriss不认PyTorch的.pth,也不认TensorFlow的.pb。它有自己的中间表示格式,通常是基于ONNX或者自定义的IR(Intermediate Representation)。我个人习惯走ONNX这条路,生态好,工具链成熟。
3.4.1 PyTorch → ONNX → Eyeriss IR
流程其实不复杂,但细节多。我给你拆成几步:
- 导出ONNX:用
torch.onnx.export(),注意设置opset_version,我一般用13或更高。 - ONNX简化:用
onnx-simplifier去掉一些冗余节点,比如Identity、Cast。 - ONNX转Eyeriss IR:调用Eyeriss SDK提供的
eyeriss_convert工具。
核心要点:ONNX导出时,dynamic_axes参数要谨慎使用。Eyeriss目前对动态shape支持有限,我建议固定batch size为1,省得后面报错。
import torch
import torchvision.models as models
model = models.resnet18(pretrained=True)
model.eval()
dummy_input = torch.randn(1, 3, 224, 224)
torch.onnx.export(
model,
dummy_input,
"resnet18.onnx",
opset_version=13,
input_names=['input'],
output_names=['output'],
dynamic_axes={
'input': {0: 'batch_size'},
'output': {0: 'batch_size'}
}
)
print("ONNX导出完成")
然后跑简化:
python -m onnxsim resnet18.onnx resnet18_sim.onnx
最后转成Eyeriss IR:
eyeriss_convert --input resnet18_sim.onnx --output resnet18.eyir --target eyeriss_v2
3.1.2 TensorFlow → ONNX → Eyeriss IR
TensorFlow这边稍微麻烦点,因为TF的算子命名和ONNX不完全对齐。我建议用tf2onnx这个工具。
import tensorflow as tf
import tf2onnx
model = tf.keras.applications.MobileNetV2(weights='imagenet')
spec = (tf.TensorSpec((1, 224, 224, 3), tf.float32, name="input"),)
output_path = model.output_names[0]
onnx_model, _ = tf2onnx.convert.from_keras(
model, input_signature=spec, opset=13, output_path="mobilenetv2.onnx"
)
print("TF转ONNX完成")
注意:TF的BatchNormalization层在转换时可能会被折叠,导致ONNX里的scale/mean参数精度丢失。我曾经因为这个,量化后精度掉了3个点。解决办法:转换前先冻结BN层,或者用tf2onnx的--fold_const选项。
3.2 INT8量化:精度与速度的博弈
量化,就是把FP32的权重和激活值,用INT8来表示。好处很明显:模型体积缩小4倍,推理速度提升2-4倍,功耗也降下来。但代价是精度损失。
Eyeriss支持两种量化方式:训练后量化(PTQ)和量化感知训练(QAT)。我一般先试PTQ,不行再上QAT。
3.2.1 训练后量化(PTQ)
PTQ不需要重新训练,只需要准备一小部分校准数据,让Eyeriss的量化工具统计出每层激活值的范围,然后算出scale和zero_point。
步骤:
- 准备校准集:从训练集里抽200-500张图片,覆盖各类场景。
- 运行校准:用
eyeriss_quantize工具,指定校准数据路径。 - 验证精度:对比量化前后的精度差异。
eyeriss_quantize \
--model resnet18.eyir \
--calib_data ./calib_images/ \
--calib_size 200 \
--output resnet18_int8.eyir \
--mode ptq
我的经验:校准集不要只用同一类图片。比如做分类任务,每个类别至少抽几张。否则量化后的模型会对某些类别“偏科”,精度掉得厉害。
3.2.2 量化感知训练(QAT)
如果PTQ后精度掉点超过1%,那就得上QAT了。QAT在训练过程中模拟量化效果,让模型自己学会适应低精度。
Eyeriss SDK提供了QAT的PyTorch插件,用法和PyTorch自带的torch.quantization类似:
import torch
import eyeriss_quant
model = torch.load("resnet18.pth")
model.qconfig = eyeriss_quant.get_default_qconfig('eyeriss_v2')
# 插入量化/反量化节点
eyeriss_quant.prepare_qat(model, inplace=True)
# 继续训练几个epoch
for epoch in range(5):
for data, target in train_loader:
output = model(data)
loss = criterion(output, target)
loss.backward()
optimizer.step()
# 转换并导出
eyeriss_quant.convert(model, inplace=True)
torch.save(model.state_dict(), "resnet18_qat.pth")
注意:QAT训练时,学习率要调小,一般是原来的1/10。我见过有人直接用原学习率,结果模型直接不收敛了。
3.3 量化参数与精度对比
下面这张表,是我在实际项目中总结的,不同量化方式的效果对比:
| 量化方式 | 模型大小 | 推理速度 | 精度损失 | 适用场景 |
|---|---|---|---|---|
| FP32(基准) | 100% | 1x | 0% | 精度优先 |
| INT8 PTQ | 25% | 2-3x | 0.5%-1% | 快速部署 |
| INT8 QAT | 25% | 2-4x | 0.1%-0.5% | 精度敏感 |
你看,QAT虽然训练时间长一点,但精度损失小很多。如果你的模型要部署在医疗、自动驾驶这类场景,我建议直接上QAT。
3.4 常见问题与避坑指南
- 算子不支持:Eyeriss的IR不是所有ONNX算子都支持。遇到不支持的,要么替换成等价算子组合,要么走Eyeriss的自定义算子接口。我遇到过
Softmax不支持,后来用Exp+ReduceSum+Div替代了。 - 量化后精度崩了:先检查校准集是否合理,再检查是否有异常值(比如输入数据没归一化)。我曾经因为一张全黑图片混进校准集,导致量化后的模型输出全是0。
- 转换后模型跑不起来:多半是输入输出的shape对不上。用
eyeriss_inspect工具查看IR的输入输出信息,和你的预处理代码对比一下。
3.5 本章知识体系
下面这张图,把模型转换和量化的核心流程串起来了。你一看就明白:
嗯,到这里,模型转换和量化的核心内容就讲完了。你手头如果有现成的模型,可以照着上面的步骤跑一遍。遇到问题别慌,翻翻前面的避坑指南,大部分都能解决。