 RKNN 混合精度量化实战:自动与手动选层)
RK3588学习日记三 RKNN 混合精度量化实战自动与手动选层上一篇完成了 INT8 和 FP16 转换。这一篇继续做 RKNN 混合精度量化先用自动混合跑通流程再分别走 proposal 选层和 routeB 手动选层同时用 accuracy_analysis 定位量化敏感层自己决定哪些层保留 FP16。文中只比较 NPU cycles 与文件大小等确定指标不用小样本精度差异判断方案优劣。一、 这篇做什么1.1 什么是混合精度量化时大部分层用 INT8快、省内存少数对量化敏感的层保留 FP16精度高。混合精度就是手动或自动地决定「哪些层用 float16」。1.2 三种方式先跑通再精细化方式操作方法复杂程度特点方式 A自动混合设置两个config参数通过build一步完成最低toolkit 自动决定 float16 层流程最短但不能精确控制选层方式 Bproposal 选层step1 生成建议选层保持 cfg 不变并直接执行 step2中等可以查看工具生成的custom_quantize_layers但最终仍采用 proposal 的选层结果方式 CrouteB 手动选层用accuracy_analysis分析逐层误差再修改选层配置并执行 step2最高可以自行控制哪些层保留 float16可以对实际使用场景做适配新手建议先走方式 A最快跑通等想自己控制选层时再看本篇「用 accuracy_analysis 找敏感层」小节 方式 C。1.3 前置条件本篇采用上一篇的yolo模型和适配环境已会 INT8/FP16 转换见上一篇环境已装好rknn-toolkit2 2.3.2、Python 3.10模型YOLOv8s-P2nc1输入 1024×1024校准集400 张calibration.txt。二、方式 A自动混合量化最简单2.2 config 设两个参数 build 一步修改三处写auto_hybrid_build.py标了 ← 的三处要改成你自己的fromrknn.apiimportRKNN rknnRKNN(verboseTrue)rknn.config(mean_values[[0,0,0]],std_values[[255,255,255]],target_platformrk3588,quantized_hybrid_level1,# 启用自动混合量化auto_hybrid_cos_thresh0.98,# 余弦相似度阈值)assertrknn.load_onnx(modelmix20.onnx)0# ← 你的 ONNX 路径assertrknn.build(do_quantizationTrue,datasetcalibration.txt)0# ← 你的校准集路径assertrknn.export_rknn(mix20_auto_hybrid.rknn)0# ← 你的输出名rknn.release()两个参数含义quantized_hybrid_level1自动混合开关。0是关闭默认就是普通 INT81是启用auto_hybrid_cos_thresh0.98余弦相似度阈值。toolkit 会算每个算子量化前后的相似度低于阈值的层量化误差大自动保留 float16。阈值调高→更多层 float16更保守、略慢调低→更少层 float16更快。运行 看到什么算成功python3 auto_hybrid_build.pyI Quantizating 40/40: 100%|...| 194/194 I HybridProposal Step 4/4: ... 135/135 I RKNN: Meet hybrid type, dtype: float16, tensor: /model.0/act/Mul_output_0 ...共 93 条 float16 张量 I rknn building done. AUTO_HYBRID OK: mix20_auto_hybrid.rknn看到HybridProposal和一条条Meet hybrid type, dtype: float16就说明自动混合生效了。产物约 15.3 MB15,263,855 字节。一个必须记住的坑必须build(do_quantizationTrue, dataset...)。如果只设了quantized_hybrid_level1但do_quantizationFalse不会报错但产物会变成纯 FP16——因为不量化时混合精度没意义。三、方式 B最小可用proposal 选层3.1 跑 step1 生成配置文件改哪里两处写hybrid_step1.pyfromrknn.apiimportRKNN rknnRKNN(verboseTrue)rknn.config(mean_values[[0,0,0]],std_values[[255,255,255]],target_platformrk3588)assertrknn.load_onnx(modelmix20.onnx)0# ← 你的 ONNX 路径assertrknn.hybrid_quantization_step1(datasetcalibration.txt,# ← 你的校准集路径rknn_batch_size1,proposalTrue)0rknn.release()运行 看到什么算成功python3 hybrid_step1.pyI Quantizating 40/40: 100%|...| 194/194 D quant_optimizer done. I HybridProposal Step 4/4: 100%|...| 135/135step1 会在当前目录生成 3 个文件文件名取 ONNX 前缀mix20.model约 42.6MB、mix20.data约 1.2MB、mix20.quantization.cfg约 85KB。耗时约 25~30 分钟。3.2 直接 step2 导出不改 cfg路径 B 的关键step1 的proposalTrue已自动选好了层写进了 cfg 的custom_quantize_layers段。不改直接 step2。fromrknn.apiimportRKNN rknnRKNN(verboseTrue)rknn.config(mean_values[[0,0,0]],std_values[[255,255,255]],target_platformrk3588)# 关键这里不调用 load_onnxretrknn.hybrid_quantization_step2(model_inputmix20.model,# ← step1 生成的 .modeldata_inputmix20.data,# ← step1 生成的 .datamodel_quantization_cfgmix20.quantization.cfg)# ← step1 生成的 .cfgassertret0assertrknn.export_rknn(mix20_hybrid.rknn)0# ← 你的输出名rknn.release()看到[export] OK: mix20_hybrid.rknn得到一个混合模型。四、方式 CrouteB 手动选层方式 B 是「让工具自动选层」但你无法控制它选了哪些所以有时候无法匹配我们的使用场景这个时候就需要手动选层这样最大的好处就是可以根据自己的使用场景所要求的精度和推理速度来权衡量化的那些层和多少层数而手动选层要需要知道「哪些层量化后误差大」4.1用 accuracy_analysis 找量化敏感层做逐层误差分析4.1.1 完整命令必须先 INT8 build再调用accuracy_analysisfromrknn.apiimportRKNN ONNXmix20.onnxDATASETcalibration.txtIMGcalib_v8_0000.jpg# 分析用图1024×10241 张即可OUTDIRanalysis_snapshotrknnRKNN(verboseTrue)rknn.config(mean_values[[0,0,0]],std_values[[255,255,255]],target_platformrk3588)rknn.load_onnx(modelONNX)rknn.build(do_quantizationTrue,datasetDATASET)# 必须先 build 出量化模型retrknn.accuracy_analysis(inputs[IMG],# ← 1~2 张图output_dirOUTDIR,targetNone)rknn.release()accuracy_analysis 会将逐层分析结果写入 output_dir实际空间占用取决于模型结构、输入尺寸和工具版本。建议先用 1 张代表性图片跑通并提前预留充足磁盘空间完成后用 du -sh output_dir 查看实际占用再决定是否增加分析图片。4.1.2 输出是什么output_dir下生成analysis_snapshot/ ├── error_analysis.txt ← 核心逐层误差表只看这个 ├── map_name_to_file.txt ← 层名 → 快照文件映射 └── golden/ simulator/ ← 每层数值快照教程用不到可删error_analysis.txt长这样每层一行两列误差layer_name simulator_error entire single cos euc cos euc [Conv] /model.0/conv/Conv_output_0 0.99997 | 54.086 0.99997 | 54.086 [exSwish] /model.0/act/Mul_output_0 0.99983 | 109.86 0.99984 | 100.614.1.3 怎么解读哪层敏感看single-cos列单层误差不被前面的层累积干扰single-cos越接近 1.0 这一层量化越好single-cos越低 这一层量化误差越大 敏感层该设 float16。entire-cos是累计误差从输入到这层越积越大判断单层敏感度不用它。4.1.4 从误差表 → 决定 float16 的判定逻辑取single-cos低于某个阈值如 0.9999的层过滤掉输出边界节点images、386、456、onnx::ReduceSum_*这些误差大但不是该动的地方是数据转换/DFL 输出边界误设会出错优先保留conv和actMul类输出——它们对精度影响大且 NPU 支持 float16按误差从大到小挑 N 层写进custom_quantize_layers。4.1.5 实测规律哪些层容易敏感检测头分类分支 actMul最敏感/model.28/cv3.1、cv3.2的act/Mul_output_0排在最前backbone 的 C3 模块model.2/4/6 的 m.0/m.1 子块的 convact 也偏敏感算子类型exSwishSiLU 融合、conv比Split/Concat/Add更容易敏感。4.1.6 坑必须先 INT8 build直接调 accuracy_analysis 会因没有量化模型而无意义输出节点 cos 极低别误判onnx::ReduceSum_452等 cos 只有 0.6~0.9是 DFL softmax 边界不是该设 float16 的目标1 张图就够1 张图与多张图的最差层排序基本一致检测头 cv3.x 早期 backbone。4.2 读懂 quantization.cfg手动调层前先看懂 cfg 的两个顶层段custom_quantize_layers: ← 第 1 段float16 层清单这里就是你要调的 /model.6/Split_output_0: float16 ... quantize_parameters: ← 第 2 段每层 int8 参数一般不动字段qtypeasym、qmethodlayer/channel、dtypeint8/float16、scale/zero_point。层名规律/model.模块/子模块/op_output_0。4.3 根据分析结果配置routeB 方案在 proposal 基础上手动补检测头关键分支cv3.1/cv3.2/cv3.3 分类、cv2.0 P2 box到 85 层——这些是accuracy_analysis里误差最大的地方。用脚本合并层清单不是手动一行行改# 1. 读层清单生成 custom_quantize_layers 段linesopen(routeB_85_layers.txt).read().splitlines()# ← 你的层清单路径custom_block[custom_quantize_layers:]custom_block[f{name}: float16fornameinlinesifname.strip()]# 2. 从 step1 的 cfg 取 quantize_parameters 段new_cfgopen(mix20.quantization.cfg).read().splitlines()# ← step1 的 cfgn_endnext(ifori,linenumerate(new_cfg)ifl.startswith(quantize_parameters:))qp_blocknew_cfg[n_end:]# 3. 合并写回新 cfgoutcustom_block[]qp_blockopen(routeB_85fp16.cfg,w).write(\n.join(out))# ← 输出 cfg 名改前/改后对照例子层名改前proposal改后routeB/model.28/cv3.3/cv3.3.1/conv/Conv_output_0int8float16/model.28/cv2.0/cv2.0.0/conv/Conv_output_0float16float164.4 用改好的 cfg 做 step2和方法 B 的 step2 一样只是model_quantization_cfg换成改好的retrknn.hybrid_quantization_step2(model_inputmix20.model,# ← step1 生成的 .modeldata_inputmix20.data,# ← step1 生成的 .datamodel_quantization_cfgrouteB_85fp16.cfg)# ← 改好的 cfgassertrknn.export_rknn(mix20_routeB_85fp16.rknn)0最容易踩的坑手动方式通用step2 之前绝对不能调用load_onnx否则 KeyErrorSigmoid_output_0甚至段错误。正确新建 RKNN() → config() → 直接 step2。step2 输出两个 warning 是正常的输入/输出 dtype 因 float16 层而变W: The default input dtype of images is changed from float32 to float16 W: The default output dtype of 386 is changed from float32 to float165. 三种方式怎么选基于确定可靠的指标混合精度量化的提高程度和实际场景使用的关系较大建议在实际场景测试每种量化模型的精确度和推理速度来选择量化模型方式NPU cycles文件大小说明INT8全量化49.1M12.8 MB最快自动混合level170.6M15.3 MB比 INT8 慢约 44%routeB 手动 85 层71.6M16.5 MB比 INT8 慢约 46%FP16不量化96.0M24.6 MB最慢约 INT8 的 2 倍可靠结论NPU cycles 确定可靠INT8 最快49.1M三种混合精度都明显更慢70~72M约慢 44%FP16 最慢约 2 倍。混合精度不会让模型更快只会更慢——这是确定的事实。它唯一的价值是「可能找回一点 INT8 损失的精度」但找回多少需要你自己实测本文不替它打保票。给新手的建议基于可靠结论性能优先直接用 INT8最快、文件最小想试混合精度先用方式 A自动最简单跑通想自己控制选层就用 accuracy_analysis 方式 C 手动挑层不要指望混合精度比 INT8 快它只会更慢除非你的场景对精度要求高、且实测确实从混合精度中获益。严谨提醒真正比较「自动 vs 手动 vs INT8」的精度高低需要更大、覆盖更多场景的数据集 多次重复实验看统计显著性而不是一次几十张的结果。所以本文只告诉你可靠的部分速度、大小精度高低留给你自己实测。6. 常见问题现象原因解决自动混合产物是纯 FP16只设了quantized_hybrid_level但do_quantizationFalsebuild(do_quantizationTrue, dataset...)step2 报 KeyErrorSigmoid_output_0step2 前调了load_onnxstep2 前不 load_onnx输入 dtype 从 float32 变 float16custom 段有 float16 层正常部署时输入用对应精度step1 产物不在预期目录产物生成在运行命令的当前目录先 cd 到目标目录accuracy_analysis 输出节点 cos 极低onnx::ReduceSum_*是 DFL 输出边界误判不设 float16层名写错 step2 报 invalid层名与 cfg 不一致从 error_analysis.txt 复制层名7. 下一篇下一篇回到推理侧用 Python 在 RK3588 上加载.rknn跑单线程推理讲清楚 rknnlite 的输入准备NCHW、uint8、输出解析和 DFL 后处理。附录routeB 85 层清单前 20 层 规律/model.6/Split_output_0 /model.12/cv1/conv/Conv_output_0 /model.12/cv1/act/Mul_output_0 /model.18/cv1/conv/Conv_output_0 /model.18/cv1/act/Mul_output_0 /model.1/conv/Conv_output_0 /model.1/act/Mul_output_0 /model.0/conv/Conv_output_0 /model.0/act/Mul_output_0 /model.6/cv1/conv/Conv_output_0 /model.6/cv1/act/Mul_output_0 /model.11/Concat_output_0 /model.2/cv1/conv/Conv_output_0 /model.2/cv1/act/Mul_output_0 /model.6/m.1/cv2/conv/Conv_output_0 /model.6/m.1/cv2/act/Mul_output_0 /model.6/m.1/cv1/conv/Conv_output_0 /model.6/m.1/cv1/act/Mul_output_0 /model.4/cv1/conv/Conv_output_0 /model.4/cv1/act/Mul_output_0 ...共 85 层85 层分布backbone C3/C2 模块model.2/4/6/12/15/18 等的 convact加检测头 model.28 的 cv2.0P2 box cv3.1/3.2/3.3P3/P4/P5 cls分支。注意85 层清单的层名必须和你的 ONNX 导出的层名一致。如果你不想照抄这份清单就用accuracy_analysis 挑出你自己模型误差最大的层——这才是一劳永逸的做法。