我先说一个特别常见、也特别让人头大的场景:模型在 GPU 上测下来精度不错,随手一跑 P99 延迟也就 30 毫秒,可一旦要部署到边缘盒子、手机或者车机上,模型体积、显存占用、首次推理耗时全都成了问题。Model-Optimizer这类的模型优化工具,就是专门用来处理这一段的——把训练好的模型做图优化、量化、剪枝、蒸馏,最终交付一个体积更小、推理更快、精度尽量不掉的部署版本。这篇内容我按自己实际做过的优化流程来写,会重点讲优化手段怎么选、工具链怎么搭、以及那些文档里通常不会写的精度和性能坑。适合手里已经有训练好的模型、正准备做部署的算法工程师,也适合刚接触模型优化、想建立整体认知的新手。
1. Model-Optimizer 到底在优化什么:先看懂精度、延迟和体积的三角关系
1.1 训练完成不等于能上线:模型部署的真实痛点
训练阶段的指标和上线阶段的指标,从来就不是同一套衡量标准。训练时我们关心 loss 曲线、验证集 accuracy、收敛速度;到了部署阶段,大家问的是模型文件多大、单帧延迟多少、显存/内存峰值多高、会不会被打满、能不能扛住并发。
举个我经手过的例子:一个 1.2GB 的检测模型,放 A10 显卡上做离线推理,单张 1080P 图片大概 35ms,一切岁月静好。但换到边缘盒子上问题立刻出来了。那台机器 GPU 显存只有 8GB,而 1.2GB 还只是权重文件的体积,推理过程中把特征图展开,内存峰值能到 3~4GB,稍微来两路输入就直接 OOM。
到了这种节点,再回头去改模型结构就太晚了,最划算的做法就是用优化工具在现有模型上做手术。Model-Optimizer这类工具要解决的问题也很明确:在尽量不损失预测质量的前提下,把模型的计算量、内存带宽占用、存储体积一起降下来。
这里要先建立一个认知:模型优化不是“玄学”,不是随便压一压精度赌运气,而是一套有明确边界和数学基础的工程流程。它要做的事情通常分成四类——降低数值精度、砍掉冗余参数、用更小的模型去模仿大模型、以及把图中可以合并的算子合并掉。后面的内容我会把这四类挨个拆开。
1.2 量化、剪枝、蒸馏、图优化:四种手段分别解决什么问题
先说说量化。量化是把模型从 FP32 精度压到 FP16、INT8 甚至更低。FP16 属于“白拿”收益,很多显卡原生就跑得飞快,模型体积直接减半,精度损失往往可以忽略不计。INT8 就复杂一些,它要把浮点数值映射到 -128 到 127 的整数区间,这个映射过程需要两个核心参数:scale(缩放系数)和 zero point(零点偏移)。
很多人一开始会误以为 INT8 量化就是简单地把数值除以 255,其实不是。实际做法是先统计权重或者激活值的分布,比如权重的范围是 -1.2 到 0.8,那就按这个区间找一个 scale,再进行饱和映射。关键难点在于:权重分布相对稳定,好量化;而激活值的分布会随着输入变化,量化误差更容易累积。所以做 INT8 量化的时候,通常需要准备一个有代表性的校准数据集,让模型先跑一遍,统计典型输入的激活分布,然后用这些统计值去确定 scale 和 zero point。
如果模型对数值变化很敏感,直接后训练量化(PTQ)会掉点厉害,这时候就得考虑量化感知训练(QAT)。说白了就是在训练阶段就模拟量化误差,让网络在训练过程中自己适应低精度带来的扰动,等训练完了再转 INT8,精度比纯后训练量化稳得多。
再来看剪枝。核心思路是找到网络里不那么重要的权重通道,把它们去掉。这里要区分两种剪法:非结构化剪枝和结构化剪枝。非结构化剪枝是把单个权重中的小值清零,结果是一个稀疏矩阵,看起来理论计算量降了,但普通硬件根本加速不了多少;结构化剪枝是直接剪掉整个卷积通道或者神经元,得到的是一个更窄的模型,这种剪法在 CPU 和 GPU 上能真正吃到收益。我的经验是,如果目标设备不支持稀疏卷积加速,就别图省事做非结构化剪枝,老老实实做通道级剪枝,后面再接几轮微调恢复精度。
蒸馏走的是另一条路。用一个大模型当老师,把小模型当学生,让学生去学老师的预测分布。这里有个关键细节:蒸馏的时候不能只学 hard label,还要学 soft label,也就是老师模型输出的概率分布。为了让分布的信息更充分,通常会引入温度参数 T,把 logits 除以 T 再算 softmax,让概率分布更“平缓”,从而保留更多暗知识。损失函数一般是学生和真实标签的交叉熵,加上学生和老师软化后分布之间的 KL 散度。
很多时候蒸馏会和其他手段搭配使用。比如模型压缩项目中,我先用大模型蒸馏出一个小模型,再对小模型做 INT8 量化,精度比直接对大模型做量化还要稳。原因不难理解:蒸馏本身就把教师的知识揉进了更小的结构里,后面的量化失真空间自然更小。
图优化是另一个容易被忽视但是性价比极高的手段。它不改变权重数值,而是修改计算图结构。典型操作包括常量折叠、删除冗余的 Identity 节点、算子融合。最常见的融合是 Conv+BatchNorm+ReLU。BatchNorm 在推理阶段可以用一组固定参数表示,这组参数可以提前折算进卷积层的 weight 和 bias 里,运行时根本不需要再单独执行 BatchNorm。融合之后少了两三次 kernel 启动,也少了两三个中间张量在内存里读写,节省下来的开销在 CPU 上尤其明显。实际推导不复杂:推理时的 BatchNorm 对每个通道做一个仿射变换,把gamma / sqrt(running_var + eps)乘到卷积权重上,把beta - running_mean * scale加到卷积偏置上就行。
1.3 先定目标再选路线:精度、延迟、体积的取舍没有标准答案
很多新手一上来就问:我应该用哪种优化手段?我通常先反问:你的目标设备是什么、卡在哪个指标上?
如果目标是快速部到 CPU 服务上,可能一个带算子融合和 FP16 的版本就够用;如果目标是手机端或者嵌入式设备,内存带宽有限,那 INT8 量化基本是必选项;如果模型结构本身太大,设备上连足够的内存都没有,那就要考虑蒸馏或者结构化剪枝,先把模型“瘦”到一个能装得下的程度。
这里我做过一个对比测试,可以直观感受一下不同方案之间的差异。以我自己手头一个 ResNet50 分类模型为例:原始 FP32 模型权重 98MB,单张 224x224 输入的 CPU 推理延迟大约 42ms;转成 FP16 后权重变成 49MB,精度损失在 0.1% 以内,CPU 延迟大概降到 38ms,收益有限但无风险;转成 INT8 后权重只有 25MB,CPU 延迟降到 18ms 左右,精度下降约 0.6%,这就是一个可以接受的交易;如果再叠加通道剪枝加蒸馏,精度反而比纯 INT8 高一点,延迟还能再低一些。
所以我的核心建议是:任何一次优化,动手之前先明确一个硬指标。比如“top-1 精度下降不超过 1%”“单次推理延迟低于 20ms”“模型体积小于 30MB”。没有指标约束的优化,很容易陷入反复试错还不自知的境地。后面我会详细讲,有了目标之后,怎么选择工具链、怎么一步步跑通整个流程。
2. 工具选型解析:不同部署场景该选哪个优化器
2.1 训练后优化工具:手头只有模型时的最快路径
如果模型已经训练完,手头没有任何改造训练过程的余地,那就只能走训练后优化路线。这个路线下,工具选择会很直接。
先说 ONNX Runtime。它自带了多层图优化,使用的时候只需要把模型转成 ONNX,然后设置graph_optimization_level。ORT_ENABLE_BASIC会做常量折叠和冗余节点删除;ORT_ENABLE_EXTENDED会做算子融合;ORT_ENABLE_ALL会把能开的优化全打开。大部分场景直接开满就行,因为图优化基本不会改变数值结果,风险很低。
代码大概是这样的:
import onnxruntime as ort sess_options = ort.SessionOptions() sess_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL session = ort.InferenceSession("model.onnx", sess_options, providers=["CPUExecutionProvider"])如果是 TensorFlow 生态,直接考虑 TFLite Converter。它的转换脚本很短,关键是把optimizations设为DEFAULT,再提供一个representative_dataset,这样才能诱导它做权重量化和激活量化:
import tensorflow as tf converter = tf.lite.TFLiteConverter.from_saved_model("saved_model_dir") converter.optimizations = [tf.lite.Optimize.DEFAULT] converter.representative_dataset = representative_dataset_gen converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8] tflite_model = converter.convert()如果是 OpenVINO 用户,它会自带一个叫 Model Optimizer 的转换工具,正好和题目同名。旧版命令是mo,新版改成了ovc。最基础的用法就是指定输入模型,转换时会自动做 FP32 到 FP16 的压缩,并且做一些图结构的调整:
ovc model.onnx --output_dir ./ir如果是 NVIDIA GPU 平台,那就绕不开 TensorRT。它的转换命令行工具叫trtexec,可以直接把 ONNX 模型转成 TensorRT engine:
trtexec --onnx=model.onnx --saveEngine=model.trt --fp16TensorRT 的优化力度比 ONNX Runtime 更大,但代价是绑定了 NVIDIA 硬件。它会对模型做层融合、精度选择、显存规划,在 GPU 上跑起来通常比原始框架快不少。INT8 模式还需要提供校准数据文件:
trtexec --onnx=model.onnx --saveEngine=model.trt --int8 --calib=calibration_data.txt2.2 训练时优化:把量化感知训练和蒸馏写进训练流程
如果模型还没有完全定稿,或者训练完发现后训练量化掉点严重,那就应该把优化前置到训练阶段。
PyTorch 的量化感知训练入口在torch.ao.quantization。要用起来,核心是给模块配置 QConfig,指定权重和激活的 observer 类型。一个典型流程是:定义模型 → 设置 QConfig →prepare_qat→ 训练 →convert。
import torch import torch.ao.quantization as tq model.qconfig = tq.QConfig( activation=tq.MinMaxObserver.with_args(dtype=torch.quint8, qscheme=torch.per_tensor_affine), weight=tq.MinMaxObserver.with_args(dtype=torch.qint8, qscheme=torch.per_tensor_symmetric) ) model = tq.prepare_qat(model, inplace=False) # 正常训练若干 epoch,保留训练流程中的 dropout 等操作 model = tq.convert(model, inplace=False)注意一个细节:QAT 训练阶段需要保留训练模式,convert之后才切到推理模式。很多人在这上面踩坑,提前把模型eval()了,结果 observer 统计不到正确的分布,转换后精度反而不如直接后训练量化。
蒸馏的话,我通常直接在原始训练脚本里加一个分支。学生网络正常计算输出,然后和教师网络的输出算一个 KL 散度。温度 T 一般取 3~5,太大的温度会把类别间差异抹平,太小又起不到软化分布的作用。
import torch.nn.functional as F def distillation_loss(student_logits, teacher_logits, labels, T=4.0, alpha=0.7): ce_loss = F.cross_entropy(student_logits, labels) distill_loss = F.kl_div( F.log_softmax(student_logits / T, dim=-1), F.softmax(teacher_logits / T, dim=-1), reduction="batchmean" ) * (T * T) return (1 - alpha) * ce_loss + alpha * distill_loss别忘了 KL 散度后面要乘T * T,这是因为软化后的梯度和温度平方成正比。不乘回去的话,蒸馏部分在总损失里的比重会被稀释,很多照着博客抄的代码都会漏这一项。
2.3 按硬件平台选工具:我的判断顺序和踩坑经验
我自己做选型的时候,顺序很固定:先定目标设备,再查算子支持,最后才决定用哪个优化器。反过来的话,很容易出现“工具选得漂亮,但生产环节用不上”的情况。
举个例子,TensorRT 在 GPU 服务端很强,但没法跑到 ARM 核上;TFLite 在移动端是主流,但拿到树莓派或者某些工控机上,性能反而不如优化过的 ONNX Runtime;OpenVINO 对 Intel CPU 优化很深,但如果部署机上全是 AMD,收益就说不准了。所以我的判断表大概是这样的:
| 目标平台 | 推荐工具 | 典型收益 | 注意事项 |
|---|---|---|---|
| 服务器 NVIDIA GPU | TensorRT 或 ONNX Runtime + GPU EP | 延迟降低明显,支持 FP16/INT8 | 引擎绑定 GPU 型号和驱动 |
| 服务器普通 CPU | ONNX Runtime + OpenVINO | 算子融合收益高,内存占用下降 | 优先开全图优化,再看量化 |
| 手机 ARM CPU | TFLite / CoreML | 模型体积小,电池友好 | 需要处理的代表性数据要足够 |
| 嵌入式/工控机 | OpenVINO / ONNX Runtime | 图优化加降精度组合 | 先确认算子兼容,再上量化 |
我之前在一个边缘项目里,最开始选了 ONNX Runtime 做 INT8,结果发现设备上的 CPU 指令集版本太老,INT8 的矩阵乘实现效率一般,反而比 FP32 慢。后来换成 FP16 后发现根本的问题在于内存带宽,而不是计算单元,把模型压到 FP16 之后延迟立刻降下来了。这件事给我的启发是:工具选型不能凭名气,要先做个最小的 POC,用真实输入量一下延迟分布和内存占用,再决定是否全量优化。
3. 实操过程与核心环节实现:以 ONNX 为主干跑通一次模型优化
3.1 为什么要用 ONNX 作为统一中间表示
我看到不少团队做优化的流程很乱:PyTorch 模型直接拿到 TensorRT 工具转,TensorFlow 模型直接丢给 TFLite Converter,一旦出了问题,排查起来每个工具链之间没有共同的调试入口。我的习惯是先统一转成 ONNX,再往后端走。
ONNX 的好处在于它是一个开放的计算图格式,主流框架都支持导出,绝大多数推理框架也支持把 ONNX 作为输入。这意味着我可以在模型优化阶段只盯着一个格式做图优化、算子简化和精度检查;确定没问题之后,再根据目标硬件转成 TensorRT engine、OpenVINO IR 或者 TFLite。这样整个流程从“多对多”变成了“多对一对多”,排查链路清晰得多。
还有一个很实际的好处:ONNX 生态里有很多顺手的小工具,比如onnxsim可以做常量折叠和公共子表达式消除,onnxruntime.transformers里有专门针对 Transformer 的图优化。这些都能在模型落到具体后端之前先把图洗干净,减少后面转换的报错概率。
3.2 从 PyTorch 导出优化前的 ONNX 模型
导出这一步是最简单、也最容易埋坑的一步。标准做法是用torch.onnx.export,但有几个点必须注意。
第一,导出前要确保模型处于eval()模式,并且整个导出过程包在torch.no_grad()里。否则 BatchNorm 和 Dropout 的状态不对,导出的图虽然能跑,但输出结果和真实推理不一致。
第二,输入要构造一个真实的 dummy input,shape、数据类型尽量和目标实际输入一致。如果模型要支持动态 batch,就需要通过dynamic_axes参数声明哪些维度是动态的。
第三,opset 版本要选和推理后端匹配的版本。opset 太高,老版本后端不认识;opset 太低,某些算子可能导出不了。我一般先选 opset 17,大多数后端都兼容,再高就等真碰到算子不支持再往上调。
一个标准的导出脚本是这样的:
import torch model.eval() with torch.no_grad(): torch.onnx.export( model, dummy_input, "model.onnx", input_names=["input"], output_names=["output"], dynamic_axes={"input": {0: "batch"}, "output": {0: "batch"}}, opset_version=17 )导出完之后,我习惯先用onnxsim简化一遍,去掉那些只存在于训练过程的辅助节点和多余形状计算:
pip install onnxsim python -m onnxsim model.onnx model_sim.onnx简化之后用onnxruntime加载跑一次,对比原模型的输出。如果两边数值差在 1e-5 以内,就说明导出阶段没有引入额外误差,可以放心往下走。
3.3 执行图优化与静态量化并完成精度回测
图优化阶段其实很简单,只要在加载 ONNX 模型的时候把graph_optimization_level设置成ORT_ENABLE_ALL就行。这个阶段做的操作基本不改变数值结果,所以不需要太多心理负担,直接开满。
真正需要小心的是静态量化。这里我强烈建议先用静态量化,而不是动态量化。动态量化实现简单,很多框架一行代码就能搞定,但它对权重层做量化、对激活层还是 FP32,所以内存带宽节省有限;静态量化会把激活值也量成 INT8,收益更大,代价是必须准备校准数据集。
校准数据集的选择,直接决定量化精度。我通常从验证集里挑一个子集,覆盖真实使用场景中可能出现的高、中、低三种分布。比如目标场景是街景,那就白天晚上各挑一部分,晴天雨天都覆盖到,而不是简单拿前 100 张验证集图片去凑数。校准集数量不用太多,一两百张足够,关键是覆盖度,而不是数量。
静态量化参考代码,用 ONNX Runtime 的 API 大概是这样:
from onnxruntime.quantization import quantize_static, QuantType, CalibrationMethod from onnxruntime.quantization.shape_inference import quant_pre_process quant_pre_process("model_sim.onnx", "model_pre.onnx", skip_symbolic_shape=False) # calibration_dataset 需要是一个数据流,每次 yield 一批输入 quantize_static( "model_pre.onnx", "model_int8.onnx", calibration_dataset=calibration_dataset, quant_format=QuantType.QOperator, per_channel=True, activation_type=QuantType.QUInt8, weight_type=QuantType.QInt8, calibration_method=CalibrationMethod.MinMax )这里我再说一个细节:calibration_method有三种常见选项,MinMax、Entropy 和 Percentile。MinMax 实现简单,直接取统计到的最大值最小值做映射,适合权重这类分布稳定的 tensor;Entropy 会选择让量化损失最小的阈值,激活分布长尾明显时通常比 MinMax 稳;Percentile 会砍掉最小和最大的极端值,适合有噪声的场景。我的默认选择是先用 MinMax 试一把,如果掉点明显,再换成 Entropy 对比。
量化完成后,要把优化前后的模型放到同一个推理框架里做基准测试和精度回测。基准测试要控制变量,预热几轮之后,多次推理取中位数,不要看单次最大值,否则会被缓存、变频等噪声干扰。精度回测则按部署指标来,分类看 top-1/top-5,检测看 mAP,分割看 mIoU。优化前后指标差在一个可接受的阈值内,比如 top-1 下降不超过 1%、mAP 下降不超过 0.5%,就可以认为这轮优化是成功的。
如果还需要走到 TensorRT,把优化后的 ONNX 转成 engine 即可:
trtexec --onnx=model_int8.onnx --saveEngine=model.trt --int8但注意,TensorRT 转出来的 engine 绑定具体 GPU 型号,换机器就得重新构建。所以我通常的做法是:开发阶段在服务器上做验证,部署阶段在目标机器的启动脚本里再做一次构建,避免在开发机上构建的 engine 跑到生产机上因为架构不匹配直接报错。
4. 常见问题与排查技巧实录
4.1 量化后精度大幅下降,问题可能不在量化本身
遇到过太多人一看到 INT8 量化后精度掉了 5 个百分点,第一反应就是“这模型不适合量化”。但真正的原因往往出在更早的地方。
有一次,我把一个语义分割模型从 FP32 转 INT8,mIoU 从 0.72 掉到了 0.63,怎么看都觉得不正常。后来我把校准集翻出来一看,问题立刻清楚了:那 20 张校准图全是白天的街景,而线上真实流量里 40% 是晚上和阴雨天气。模型在量化时从来没有见过低光照条件下的激活分布,scale 和 zero point 自然估计得不准,精度掉得再大也不奇怪。
换成覆盖了白天、夜晚、雨天的高中低光照样本之后,mIoU 回到了 0.70,差距缩小到可接受范围。所以我做量化的流程里,校准集的构建优先级远高于后面调阈值参数。
如果校准集没问题,精度还是掉,那就要看敏感层。现在很多量化工具都支持逐层分析误差,把每层量化前后的激活误差打印出来。通常最后发现是少数几个层对数值扰动特别敏感,比如某些检测边框回归分支、某些均值方差大的通道。处理方法有两种:一种是把这些敏感层保留 FP16,也就是混合精度量化;另一种是用QOperator格式而不是QDQ格式,让算子在执行时再决定是否反量化,有时候同一个模型在不同格式下精度表现会差很多,值得都试一下。
4.2 算子转换报错:优先检查动态 shape 和自定义算子
转换过程报错这件事,几乎每个做部署的人都会碰到。最经典的报错场景是:PyTorch 导出的 ONNX 里有aten::index或者动态 Resize 节点,TensorRT 或者 OpenVINO 转换器直接拒绝处理。
我处理这种问题的经验是有固定顺序的。
第一步,先把 ONNX 简化掉。很多报错其实只是因为图上残留了不必要的形状推导节点,用onnxsim跑一遍基本能消掉一半问题。第二步,看报错信息具体指向什么算子,去查目标后端的算子支持列表。比如 TensorRT 对Resize的coordinate_transformation_mode要求很高,PyTorch 导出时默认用asymmetric,而某些后端只支持half_pixel。这种时候要么改导出代码里的onnx::Resize参数,要么直接替换成目标后端支持的等价算子组合。第三步,如果某个自定义算子实在绕不开,就把它拆成多个基础算子在 ONNX 图上重写,而不是和转换工具硬刚。
我之前处理过一个模型,导出之后才发现控制流里有一个动态shape操作,ONNX 图里出现了一个Shape加Gather的组合。TensorRT 在推理时拿到的是固定 batch,这个动态分支本来就是多余的。我用 ONNX GraphSurgeon 把那个子图直接替换成常量折叠结果后,转换就顺利通过了。这类问题说到底还是对目标后端的执行机制不够熟悉造成的,模型本身并不复杂。
4.3 优化后反而更慢:延迟测试的坑和正确测量姿势
还有一个很反直觉的场景:模型优化完,理论上应该更快,实际一跑反而慢了。遇到这种情况先别急,先用正确的测量姿势验证一遍再说。
很多“优化后更慢”的判断,其实是测出来的假象。第一次推理会触发权重加载、内存布局转换、算子编译,延迟天然就高;如果拿第一次推理的时间和优化前的稳定延迟比,结论没有任何意义。正确的做法是:先跑几轮预热,让框架完成初始化,然后连续测 50 到 100 次,取 P50 或者 P99,而不是看平均值或第一次的值。
import time import onnxruntime as ort session = ort.InferenceSession("model_int8.onnx", providers=["CPUExecutionProvider"]) input_data = ... # 构造真实输入 for _ in range(10): session.run(None, {"input": input_data}) timings = [] for _ in range(100): start = time.perf_counter() session.run(None, {"input": input_data}) timings.append(time.perf_counter() - start) timings.sort() print(f"P50: {timings[50] * 1000:.2f} ms, P99: {timings[99] * 1000:.2f} ms")排除测量问题之后,还有一个常见原因:INT8 量化在 CPU 上的确可能不如 FP32。尤其是小输入、小 batch 的情况下,量化的数据转换和反量化开销可能抵消掉矩阵乘法加速的收益。遇到这种情况,我不会硬上 INT8,而是退回 FP16 或者只对权重做量化,把收益点找对再说。
我还在一个 ARM 设备上遇到过内存布局问题。同一个 INT8 模型,在 x86 上跑得飞快,到了 ARM 上就慢。后来查资料发现是内存布局没有对齐到 ARM 的 SIMD 指令偏好,换成NHWC布局并调整算子执行顺序之后才恢复正常。所以做平台迁移优化时,本地验证通过不等于目标设备上验证通过,一定要在目标平台上做最终的 benchmark。
4.4 问题排查速查表
| 现象 | 常见原因 | 排查方向 |
|---|---|---|
| INT8 量化后精度大幅下降 | 校准集分布不匹配 | 重做覆盖场景更全面的校准集,再试 Entropy 校准 |
| 量化后个别类别预测错乱 | 敏感层量化误差叠加 | 逐层误差分析,敏感层保留 FP16 |
| 转 TensorRT/OpenVINO 报算子不支持 | 动态 shape 或自定义算子 | 先用 onnxsim 化简,再替换为等价算子子图 |
| 优化后延迟反而更高 | 测量姿势不对或小 batch 下量化开销大 | 预热后取 P50/P99,必要时退回 FP16 或动态量化 |
| 导出的 ONNX 在推理框架里结果不一致 | 导出时没转 eval 模式 | 导出前设置model.eval(),并对比输出数值 |
| 模型在本地快、目标设备慢 | 后端内存布局/指令集差异 | 目标设备上重新 benchmark,检查数据布局和算子实现 |
最后再分享一个我调整过很多次的经验:优化这种事,最忌讳一次把所有手段全上。先把图优化开满,转 FP16 测一版;再单独做 INT8 测一版;最后才考虑剪枝加蒸馏。每一步都留一个可回滚的中间产物,标好精度和延迟数据。这样出了问题,你能很快定位是哪一步引入的,而不是在乱七八糟的组合里翻车。我自己就是把优化前后模型、校准集、配置文件全部按版本存在同一个目录里,每次改动都跑一遍固定的评估脚本。这套习惯帮我省下的时间,远远超过当初建立这套流程的成本。