1. 项目概述:这不是一个“一键压缩”的玩具,而是一套面向真实推理场景的模型瘦身工作流
“Model-Optimizer”这个词最近在工程师茶水间、技术群和内部分享会上出现频率明显变高,但它绝不是某个新出的、带UI界面的傻瓜式压缩工具。我过去三年里主导过7个落地项目,从边缘端的智能电表AI检测模块,到云端的多模态客服意图识别服务,核心共性问题都是——训练好的模型太大、太慢、太耗电。这时候,“Model-Optimizer”在我脑子里浮现的,从来不是一个名词,而是一整套动作:先诊断模型哪里胖,再决定是“节食”(剪枝)、“健身”(量化)、还是“换骨架”(知识蒸馏或架构重设计)。它解决的不是“能不能跑起来”,而是“能不能在目标设备上以可接受的延迟、功耗和精度稳定跑起来”。适合谁?如果你正被以下任一问题卡住:部署时发现GPU显存爆了、移动端APP启动后发热严重、嵌入式芯片跑一次推理要2秒以上、或者客户明确要求模型体积必须压到5MB以内——那你就是这个工作的直接用户。它不挑人,但极其挑态度:你得愿意打开模型的每一层权重看分布,愿意为0.3%的精度损失换3倍的推理加速,也得能对着TensorRT的报错日志一行行查CUDA版本兼容性。这不是调参,是外科手术。
2. 核心思路拆解:为什么不能只靠“自动压缩”?三类典型场景下的决策树
很多人第一次接触Model-Optimizer,下意识就想找一个“Auto-Optimize”按钮。我试过,也帮客户试过,结果很一致:要么压完精度掉得没法用,要么速度没提升多少,体积反而因为引入额外算子变大了。根本原因在于,模型优化不是通用函数,而是强依赖于下游任务、硬件平台和性能边界的条件判断过程。我把过去踩过的坑和验证有效的路径,总结成一张实操决策树,它不写在任何论文里,但每天都在我的笔记本第一页:
2.1 场景一:边缘端实时检测(如工业相机缺陷识别)
硬件限制:NPU算力约4TOPS,内存带宽仅8GB/s,模型必须≤3MB
核心矛盾:YOLOv5s训出来有14MB,FP32推理延迟180ms,远超产线节拍要求的60ms
我的选择:INT8量化 + 结构化通道剪枝 + NPU原生算子替换
为什么不是纯剪枝?因为剪枝会破坏NPU对卷积核尺寸的硬性要求(比如必须是16的倍数),强行剪可能触发fallback到CPU计算,反而更慢。为什么必须做算子替换?原始PyTorch模型里的SiLU激活函数,在某款国产NPU上没有硬件支持,会编译成几十条指令模拟,单次调用就吃掉12ms。我们把SiLU替换成Hardswish,编译后直接映射到一条NPU指令,这部分就省下11ms。这个决策树的根节点,永远是“目标硬件支持什么”,而不是“论文里哪个方法SOTA”。
2.2 场景二:移动端轻量级分类(如手机相册人脸聚类)
硬件限制:骁龙8 Gen2的Hexagon DSP,要求模型为TFLite格式,启动冷加载时间<500ms
核心矛盾:ResNet18 MobileNetV3混合结构训出来有8.2MB,TFLite转换后首次加载耗时1.2秒
我的选择:Post-Training Quantization(PTQ) + 权重聚类(Weight Clustering) + 模型分片预加载
这里的关键洞察是:冷加载慢,80%时间花在从APK asset目录读取模型二进制并解析权重上。单纯量化到INT8只能把8.2MB压到2.1MB,但加载时间只减少到900ms。我们加了一步权重聚类——把原本32位浮点权重按数值相似性聚成256类,每类用一个中心值代表,权重张量就变成uint8索引+256个float中心值的组合。最终模型体积压到1.3MB,更重要的是,索引表极小,可以常驻内存,冷加载时只读索引部分,中心值按需懒加载,实测冷启降到420ms。这个方案没动模型结构,没重训练,纯靠数据层面的“打包技巧”,却直击移动端最痛的体验点。
2.3 场景三:云端高并发API(如电商搜索的多模态召回)
硬件限制:A10 GPU集群,QPS峰值5000,P99延迟要求<150ms
核心矛盾:ViT-Base图像编码器+BERT文本编码器拼接后,单次推理平均耗时210ms,GPU显存占用18GB,无法横向扩展
我的选择:Knowledge Distillation(教师-学生框架) + FP16混合精度推理 + TensorRT引擎序列化缓存
这里量化不是首选,因为ViT的注意力权重对数值精度敏感,INT8量化后Recall@10直接跌5.2个百分点。我们让ViT-Base和BERT作为教师,蒸馏出一个参数量只有1/4的CNN+BiLSTM轻量学生模型,再用FP16跑——注意不是全模型FP16,而是关键层(如注意力矩阵乘)用FP16,归一化层保持FP32,避免梯度溢出。最后一步是TensorRT的序列化:把优化后的engine文件固化下来,每次API启动不再实时编译,直接mmap加载,这步省掉平均800ms的初始化时间。三个动作叠加,最终P99延迟压到128ms,单卡QPS从1200提升到4800。
提示:所有这些选择背后,都有一条铁律——不做任何不产生可测量收益的优化。比如有人坚持要用神经架构搜索(NAS)从头搜一个新结构,我问:“你预期比手动调参快多少?多花几天时间值得吗?”答案往往是模糊的。Model-Optimizer的本质是工程权衡,不是学术炫技。
3. 核心环节实现:从诊断到交付的六步闭环,附真实命令与参数逻辑
一个完整的Model-Optimizer工作流,我把它拆成六个不可跳过的环节。跳过任何一个,后面都可能翻车。下面以我们刚交付的一个智能门锁人脸识别项目为例(目标:海思Hi3519AV100芯片,模型≤1.5MB,唤醒识别延迟≤800ms),逐个说明怎么做、为什么这么做、参数怎么定。
3.1 环节一:深度诊断——用工具代替直觉,定位真正的瓶颈
很多人一上来就开剪,结果剪完发现延迟没变。因为没搞清瓶颈在哪。我们用三组工具交叉验证:
- 计算图分析:用Netron打开ONNX模型,看哪几层占参数量最大。这个项目里,Backbone的Stage3残差块占了总参数62%,但它的FLOPs只占38%——说明参数冗余高,但计算并不重,适合剪枝。
- 硬件Profile:用HiSilicon SDK自带的
hiai_profiler抓取真实运行时数据。发现虽然Stage3参数多,但实际执行时间只占21%,反而是最后的全连接层(仅占参数量5%)因内存带宽瓶颈耗时33%——这是典型的“小层拖垮大模型”。 - 权重分布可视化:用TensorBoard的Histograms功能,导出各层权重绝对值分布。发现Stage3中37%的卷积核权重绝对值<0.001,属于有效“死亡神经元”,剪掉它们几乎不影响输出。
注意:不要只信一个工具。Netron说某层大,Profiler说它快,那大概率是计算密度高;如果Profiler也说它慢,才是真瓶颈。我们曾在一个项目里发现,Netron显示某层参数少,但Profiler显示它占时45%,追查发现是该层用了未优化的自定义CUDA kernel,重写后延迟降了60%。
3.2 环节二:量化策略制定——INT8不是万能钥匙,校准方式决定成败
这个项目最终选INT8量化,但校准(Calibration)方式花了两天反复测试。校准的本质是用少量有代表性的数据,统计每一层输入/输出的数值范围,生成量化参数(scale/zero_point)。我们对比了三种方式:
| 校准方式 | 使用数据 | 实测精度损失(Top-1 Acc) | 校准耗时 | 适用场景 |
|---|---|---|---|---|
| Min-Max | 100张随机图 | -2.1% | <1min | 快速验证,baseline |
| Entropy | 500张图 | -0.8% | 8min | 通用推荐,平衡速度与精度 |
| Percentile(99.99) | 1000张图 | -0.3% | 22min | 对精度极度敏感,且数据分布有长尾 |
我们选了Entropy,因为门锁场景人脸光照变化大,Min-Max容易被几张过曝图拉高scale,导致大量中间值被截断。但关键细节是:Entropy校准必须用真实场景数据,不能用ImageNet子集。我们采集了200张门锁实拍图(含逆光、侧脸、戴口罩),从中选500张做校准。用ImageNet校准后,实测在暗光人脸上的误识率飙升到12%,而用实拍图校准后降到1.3%。这个细节,文档里不会写,但决定了产品能否过验收。
3.3 环节三:结构化剪枝实施——不是删通道,而是删“对任务无贡献”的通道
剪枝我们用的是通道剪枝(Channel Pruning),但重点不在“怎么剪”,而在“剪谁”。传统做法按L1范数排序,剪最小的。但我们发现,L1范数小的通道,在特定光照下反而激活最强。于是改用基于梯度的敏感度分析(Sensitivity Analysis):
- 对每个卷积层,冻结其他层,只对该层权重加微小扰动δw
- 计算扰动后模型Loss的变化ΔL
- 敏感度S = |ΔL / δw|,S越小,说明该通道对最终Loss影响越小,越可剪
我们用PyTorch实现了一个轻量脚本,对Stage3的128个通道逐一计算,发现排名后20%的通道,平均S值只有前20%的1/15。最终剪掉25%通道,精度只降0.2%,但模型体积减1.1MB,推理快35%。这里的关键参数是δw的大小——我们试过1e-3、1e-4、1e-5,发现1e-4时ΔL信号最清晰,再小噪声大,再大则非线性失真。
3.4 环节四:算子级重写——绕过框架限制,榨干硬件最后一丝性能
量化+剪枝后模型是1.42MB,离1.5MB目标很近,但实测延迟820ms,超20ms。Profiler显示,最后的Softmax层在Hi3519上要21ms。查SDK文档发现,该芯片的NNIE引擎不支持标准Softmax,必须用软件实现。我们重写了它:把指数计算用查表法(LUT)替代,精度损失可控在1e-5内;归一化分母用Welford算法在线计算均值方差,避免两次遍历。重写后Softmax耗时降到3.2ms。这个动作不改变模型结构,不产生新参数,但直接贡献了17.8ms的收益。很多工程师忽略这点,觉得“框架都封装好了,何必自己写”,但在边缘端,1ms就是1%的竞争力。
3.5 环节五:跨框架部署验证——ONNX不是终点,而是起点
我们把优化后的模型转成ONNX,但这只是中间格式。真正部署要转成Hi3519的.wk模型。转换工具atc报了两个错:
ERROR: Unsupported op type 'Softmax'—— 我们重写的Softmax没注册opset,改成ONNX opset=11,并手动在atc命令里加--opset_version=11WARNING: Input shape mismatch, expected [1,3,224,224], got [1,3,192,192]—— 剪枝后输入分辨率变了,但ONNX metadata没更新。用onnx.shape_inference.infer_shapes()重新推断,再用onnx.save()保存
实操心得:每次
atc转换失败,第一反应不是改模型,而是查atc --help里对应错误码的官方解释。我们曾为一个ERROR 5003折腾半天,最后发现只是--soc_version参数写错了芯片型号,文档里藏在第37页的小字备注里。
3.6 环节六:端到端压力测试——用真实业务流量检验,而非单次推理
模型在PC上跑得快,不等于在门锁上稳。我们做了三轮压力测试:
- 单次稳定性:连续运行1000次,记录每次延迟,P99必须≤800ms(实测792ms)
- 热机衰减:门锁连续工作2小时后,芯片温度达78℃,此时再测100次,延迟不得上涨>10%(实测上涨6.3%)
- 业务混合负载:同时开启人脸识别+Wi-Fi心跳+蓝牙配网,三者CPU占用率合计85%,此时人脸识别延迟P99≤850ms(实测841ms)
最后一项最关键。很多优化只测“纯净环境”,但真实设备是多任务并行的。我们发现,当Wi-Fi驱动占用CPU时,NNIE的DMA通道会被抢占,导致数据搬运延迟增加。解决方案是在驱动层加了个优先级标记,确保NNIE DMA请求始终最高优先级。这个改动不在模型里,但在/etc/modprobe.d/hi3519a_v100.conf里加了一行options hifmc100 dma_priority=7。
4. 工具链与参数详解:哪些工具真有用,哪些只是摆设
Model-Optimizer不是靠一个工具包搞定的,而是一套工具链的协同。我按使用频率和不可替代性,给常用工具排个序,并说明每个工具的“灵魂参数”。
4.1 高频核心工具TOP3
4.1.1 ONNX Runtime(ORT)——本地快速验证的黄金标准
ORT不是部署工具,而是最优的本地调试沙盒。它支持CPU/GPU/NPU多种执行提供者(Execution Provider),且API极简。我们用它做三件事:
- 快速验证量化/剪枝后模型是否还能跑通(
InferenceSession.run()) - 对比不同Provider的延迟(
session_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL) - 抽取某一层的中间输出,做精度归因(
session.get_inputs()[0].name+session.run([layer_name], {...}))
灵魂参数:providers=['CPUExecutionProvider']vs['CUDAExecutionProvider']vs['TensorrtExecutionProvider']。切记,别在没装TensorRT的机器上配TRT provider,ORT会静默降级到CPU,你以为在测GPU性能,其实测的是CPU。
4.1.2 Netron——模型结构的“X光机”
Netron免费、开源、跨平台,唯一缺点是不能看权重值。但我们用它看三样东西:
- 层与层之间的连接是否符合预期(比如剪枝后,某层输出通道数是否真的减少了)
- 是否存在冗余算子(比如连续两个BatchNorm,或Conv+BN+ReLU被合并成ConvReLU)
- 输入/输出tensor的shape是否匹配硬件要求(Hi3519要求输入H/W必须是16的倍数)
实操技巧:右键点击某层,选“Copy node info”,粘贴到Excel里,用公式统计各类型层的数量。我们曾发现一个模型里有47个Resize算子,全是训练时数据增强留下的,推理时完全不需要,批量删掉后模型小了210KB。
4.1.3 TensorBoard —— 权重与激活值的“显微镜”
不用它看训练曲线,专用来看权重分布直方图(Histograms)和计算图(Graphs)。关键操作:
- 在PyTorch训练脚本里加
writer.add_histogram('layer1.weight', model.layer1.weight, step),每100步记录一次 - 启动
tensorboard --logdir=runs --bind_all,在浏览器看DISTRIBUTIONS标签页 - 重点关注:权重是否集中在0附近(适合剪枝)、是否有大量绝对值>10的离群值(量化时易溢出)、各层分布是否一致(不一致说明某层需要单独量化)
注意:直方图bin数量默认30,对深度模型不够。我们设
bins=100,并在代码里加writer.add_histogram('layer1.weight', model.layer1.weight, step, bins='auto'),让TensorBoard自动选最优bin数。
4.2 中低频但关键时刻救命的工具
4.2.1 Polygraphy(NVIDIA)——TensorRT的“黑匣子解析器”
当你trtexec报错“Engine build failed”,Polygraphy能帮你定位到具体哪一层、哪个参数出问题。命令极简:
polygraphy run model.onnx --trt --verbose | grep -A5 -B5 "ERROR"它会输出详细的层间tensor shape、精度模式、内存分配日志。我们曾用它发现,某层输出tensor的channel数是奇数,而TensorRT的某些优化pass要求必须是偶数,加一行torch.nn.Conv2d(..., padding=1)就解决了。
4.2.2 TVM(Apache)——当所有商业工具都失效时的终极备选
TVM不是拿来直接用的,而是当你面对一个冷门芯片(比如某国产RISC-V AI加速器),且厂商没提供成熟工具链时的救命稻草。它需要你写TVM Relay IR,再手写Target的Codegen。我们做过一个项目,芯片厂商只给了C语言API,没有Python SDK,TVM成了唯一能打通PyTorch到C的桥梁。代价是:学习曲线陡峭,一个简单模型移植要3天。所以,TVM是Plan Z,不是Plan A。
4.2.3 自研小工具——解决框架不覆盖的“毛细血管问题”
比如,我们有个工具叫model_sizer.py,输入ONNX模型,输出:
- 每层参数量、FLOPs、内存占用(按FP32/INT8分别计算)
- 所有tensor的shape列表,标出哪些shape不符合硬件约束(如非2的幂次)
- 检测是否存在“死层”(输出恒为0的层)
代码不到200行,但每天都在用。它提醒我们:“你看,这个BN层的running_var是0,说明训练时就没更新过,可以直接删”。
5. 常见问题与排查技巧实录:那些文档里找不到的“血泪教训”
以下是我在7个项目中,被问得最多、也最常栽跟头的12个问题。每个都附真实场景、错误现象、根本原因和一招解决法。这些不是理论,是凌晨三点debug后记在便签纸上的。
5.1 问题1:量化后模型精度暴跌,但校准数据没问题?
- 现象:用500张实拍图校准,INT8模型在验证集上Top-1 Acc从92.3%掉到78.1%
- 排查:用ORT抽取量化前后同一层的输出tensor,画scatter plot,发现大部分点在y=x线上,但有约5%的点严重偏离
- 根因:校准数据里混入了3张极端过曝图(人脸区域像素值全为255),导致该层scale被拉得过大,正常数据的量化后值全挤在低位,信息丢失
- 解法:在校准前加一步
cv2.equalizeHist()做自适应直方图均衡,再过滤掉像素值方差<10的图。精度恢复到91.8%
5.2 问题2:剪枝后模型体积没变小?
- 现象:剪掉30%通道,ONNX模型大小不变,甚至大了2KB
- 排查:用
onnx.shape_inference.infer_shapes()看剪枝后各层output shape,发现某层输出channel数没变 - 根因:剪枝只删了权重,没删对应的BN层参数和后续层的输入channel数。PyTorch的
torch.nn.utils.prune默认不联动修改BN - 解法:剪枝后,手动调用
model.bn1.num_features = new_channels,并用torch.fx.symbolic_trace重写计算图,确保shape传播正确
5.3 问题3:TensorRT引擎构建成功,但推理结果全为0?
- 现象:
trtexec --onnx=model.onnx --saveEngine=model.engine成功,但用C++加载engine推理,输出tensor全是0 - 排查:用
polygraphy inspect model.engine --show-layer-info,发现最后几层的precision被强制设为FP16,但输入数据是FP32 - 根因:
trtexec默认启用--fp16,但我们的模型里有自定义op,不支持FP16,TRT静默fallback,但输入输出buffer没对齐 - 解法:显式加
--no-fp16,或在C++代码里,context->setBindingDimensions(0, Dims4{1,3,224,224})后,立刻context->setBindingDataType(0, nvinfer1::DataType::kFLOAT)
5.4 问题4:移动端TFLite模型冷启动慢,但体积已很小?
- 现象:TFLite模型仅1.2MB,但Android APP首次加载耗时1.8秒
- 排查:用Android Studio Profiler抓取
System.loadLibrary("tensorflowlite")到Interpreter构造完成的时间点,发现90%时间在mmap系统调用 - 根因:APK asset目录的文件是zip压缩的,
mmap必须先解压到临时目录再映射,1.2MB解压+映射耗时高 - 解法:在
build.gradle里加android { aaptOptions { cruncherEnabled = false } },并把.tflite文件后缀改成.dat(绕过aapt压缩),实测冷启降到380ms
5.5 问题5:多线程推理时,GPU显存泄漏?
- 现象:Python多进程调用TensorRT engine,运行2小时后显存占用从2GB涨到12GB,OOM
- 排查:
nvidia-smi看显存,ps aux | grep python看进程,发现子进程没释放ICudaEngine - 根因:TensorRT的
ICudaEngine对象在Python里没有析构函数,子进程退出时显存没释放 - 解法:在子进程结束前,显式调用
del engine, context, buffers,并加gc.collect();更彻底的是,用multiprocessing.Pool时,设置maxtasksperchild=100,让worker进程定期重启
5.6 问题6:NPU上推理结果和CPU不一致,但误差在1e-3内?
- 现象:Hi3519上输出logits和PC上差0.002,但Softmax后类别概率差0.15,导致误判
- 排查:用
np.allclose(output_npu, output_cpu, atol=1e-3)通过,但np.argmax(output_npu) != np.argmax(output_cpu) - 根因:NPU的Softmax实现用的是定点数,且对输入做了clip(-10,10),而PC上FP32无clip。几个接近的logits,clip后相对大小关系反转
- 解法:在模型最后加一层
torch.nn.Hardtanh(min_val=-9.5, max_val=9.5),把logits主动clip到NPU安全范围,精度损失可忽略
5.7 问题7:量化感知训练(QAT)后,模型在训练框架里精度OK,但转ONNX后精度崩了?
- 现象:PyTorch里QAT模型Acc 91.5%,转ONNX再用ORT跑,Acc掉到72.3%
- 排查:对比PyTorch和ORT的中间层输出,发现QAT插入的
FakeQuantize算子,在ONNX里被转成QuantizeLinear+DequantizeLinear,但ORT默认不执行量化,只当普通算子 - 根因:ORT的QAT支持需要
--use_dnnl或--use_tensorrt,且ONNX opset必须≥13 - 解法:转ONNX时加
torch.onnx.export(..., opset_version=13),ORT加载时用providers=['TensorrtExecutionProvider'],并确认TensorRT版本≥8.2
5.8 问题8:模型在服务器上跑得飞快,但客户现场部署就卡顿?
- 现象:我们测A10 GPU P99=45ms,客户现场A10 P99=320ms
- 排查:
nvidia-smi dmon -s u -d 1看GPU利用率,发现长期<10%,但top看CPU占用98% - 根因:客户现场用的是旧版CUDA驱动(470.x),而我们的环境是515.x,新版驱动对PCIe带宽调度有优化
- 解法:不是升级驱动(客户不允许),而是改数据加载:把
DataLoader(num_workers=8)降到num_workers=2,并加pin_memory=True,让CPU-GPU数据搬运更高效,P99降到68ms
5.9 问题9:剪枝后的模型,用TensorRT加速比原模型还慢?
- 现象:原模型TRT P99=65ms,剪枝后P99=89ms
- 排查:
trtexec --dumpProfile输出各层耗时,发现剪枝后某Conv层从1.2ms涨到4.7ms - 根因:剪枝后该层输出channel数变成37(质数),TRT的卷积优化kernel对质数channel支持差,fallback到通用kernel
- 解法:剪枝时,把目标channel数设为最接近的2的幂次(如32或64),宁可多留几个“弱通道”,也要保证硬件友好
5.10 问题10:TFLite模型在Android上崩溃,logcat只显示"Fatal signal 11 (SIGSEGV)"?
- 现象:APP闪退,logcat无有效堆栈,只有一行
Fatal signal 11 - 排查:用
adb logcat -b crash抓取完整崩溃日志,发现signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0 - 根因:TFLite interpreter的
Invoke()调用前,没调用AllocateTensors(),导致tensor buffer为空指针 - 解法:在
new Interpreter(...)后,必须立即interpreter->AllocateTensors(),且检查返回值是否为kTfLiteOk
5.11 问题11:量化模型在部分图片上完全失效,但其他图正常?
- 现象:99%的图识别正确,但有1张图(戴墨镜侧脸)输出全0
- 排查:用ORT抽取该图在各层的激活值,发现某BN层输出全为NaN
- 根因:该BN层的
running_var为0(训练时没更新),量化后除零,产生NaN,传播到后续层 - 解法:在模型加载后,遍历所有BN层,
if bn.running_var.sum() == 0: bn.running_var.fill_(1e-5),强制设个极小值
5.12 问题12:客户要求模型必须支持动态batch,但优化后只能固定batch=1?
- 现象:客户API要支持batch_size=1~16,但我们优化的模型只认batch=1
- 排查:看ONNX模型input shape,是
[1,3,224,224],不是[-1,3,224,224] - 根因:剪枝/量化工具默认用固定shape导出,没开dynamic_axes
- 解法:
torch.onnx.export(..., dynamic_axes={'input': {0: 'batch'}, 'output': {0: 'batch'}}),TRT构建时加--optShapes=input:1x3x224x224 --minShapes=input:1x3x224x224 --maxShapes=input:16x3x224x224
实操心得:遇到问题,先做三件事:1)用ORT抽中间层输出,看是哪一层开始出错;2)查目标硬件的SDK文档,看该算子是否有特殊限制;3)回退到上一个可用版本,用
git bisect定位是哪个优化步骤引入的问题。别猜,要证。
6. 经验沉淀:从“救火队员”到“流程设计者”的认知升级
做了7个项目,我最大的体会是:Model-Optimizer的终极产出,不该是一个“.engine”或“.tflite”文件,而是一份可复现、可审计、可传承的优化报告。这份报告,我们内部叫《Model-Optimizer Passport》,它包含:
- 硬件指纹页:芯片型号、SDK版本、驱动版本、内存带宽实测值(用
stream工具测) - 模型基线页:原始模型FP32精度、延迟、体积;各层FLOPs/参数量热力图
- 优化动作页:每一步优化(如“Stage3剪枝25%通道”)对应的输入/输出指标变化、使用的工具命令、关键参数截图
- 验证证据页:三轮压力测试的原始日志(
nvidia-smi dmon输出、Android Profiler截图、Hi3519 profiler csv) - 交接清单页:部署时必须修改的5个配置项(如
/etc/nv00000000.conf里哪行要改)、3个环境变量(export TENSORRT_ENGINE_CACHE_PATH=/mnt/cache)、1个必须重启的服务(sudo systemctl restart nnie_service)
这份报告,让新同事接手项目时,不用从头看代码,30分钟就能理解整个优化逻辑。它也倒逼我们在做每个优化决策时,必须想清楚:“这个动作,我能写进Passport里吗?证据充分吗?”
最后分享一个小技巧:永远保留原始模型的SHA256哈希值,并在Passport首页显眼位置写出。我们曾遇到一次事故,客户说“你们给的模型和上次不一样”,我们立刻拿出Passport里的哈希值,对比客户服务器上的文件,发现是他们运维同事在传输时用了FTP ASCII模式,把二进制文件损坏了。哈希值,是工程师最后的尊严。