所属合集 RK3588 端侧 AI 部署 第 4 / 6 篇
RK3588 端侧 AI 部署(四):量化、评估与模型优化
模型能在 NPU 上运行,只能说明“链路通了”。这一篇要回答三个工程问题:结果准不准、速度够不够、内存放不放得下。
一、优化前先建立基线
每个模型至少保留两组基线:
- 原框架/ONNX 浮点基线;
- RKNN FP16 或非量化基线。
再逐步加入 INT8、混合量化、图编辑、线程池、零拷贝等优化。每次只改一个主要变量。
建议用下面的表记录:
| 指标 | 原框架 | ONNX | RKNN FP16 | RKNN INT8 | 优化版 |
|---|---|---|---|---|---|
| Accuracy/mAP/Recall | |||||
| 纯推理平均延迟 | |||||
| 端到端 P50/P95 | |||||
| 吞吐 FPS | |||||
| 峰值内存 | |||||
| 模型文件大小 | |||||
| CPU/NPU/DDR 频率 | |||||
| 温度/是否降频 |
没有统一输入、频率和统计口径的数字不能直接比较。
二、INT8 量化的本质
线性量化可以抽象为:
q = round(x / scale) + zero_pointx ≈ (q - zero_point) × scale将 FP32/FP16 映射到 INT8 的收益通常包括:
- 模型和权重更小;
- 中间张量内存下降;
- 更好利用 RK3588 的整数 NPU 吞吐;
- 功耗和延迟可能下降。
代价是表示精度降低。对异常值、激活分布尖锐、注意力或小目标敏感的网络,量化误差可能明显放大。
校准集原则
- 必须来自真实业务分布;
- 覆盖暗光、逆光、遮挡、不同尺度和负样本;
- 不要只使用“最好识别”的图片;
- 预处理必须和部署一致;
- 数据划分不能泄漏验证集结论。
课程介绍的 normal、mmse、kl_divergence 等算法只是不同阈值选择策略,最终效果必须以自己的模型实测。
三、逐层精度分析
基本流程:
原模型输出(golden) ↕RKNN 模拟器输出 ↕RK3588 Runtime 输出示意代码:
from rknn.api import RKNN
rknn = RKNN(verbose=True)
# 精度分析通常需要保留原始模型构建上下文,# 具体 load/config/build 过程按自己的模型填写。
ret = rknn.accuracy_analysis( inputs=["./test.jpg"], output_dir="./accuracy_analysis", target="rk3588",)if ret != 0: raise RuntimeError(f"accuracy_analysis failed: {ret}")
rknn.release()报告常见字段:
single cosine:当前单层输出与基准的相似度;entire cosine:误差从网络前部累计到当前层后的相似度;- 欧氏距离/误差统计;
- golden、simulator、runtime 的逐层 Tensor。
先寻找 single 指标突然下降的层,再确认该层是否真的导致最终任务指标下降。不能把逐层 cosine 当作 mAP。
四、混合量化
如果普通 INT8 只有少数层损失明显,可以让敏感层保留 FP16,其他层继续 INT8:
普通 INT8 基线→ accuracy_analysis 找到敏感层→ hybrid_quantization_step1 生成配置→ 修改量化配置→ hybrid_quantization_step2 构建→ 重新测试任务精度、延迟和内存保留越多 FP16 层通常越容易恢复精度,但性能收益会减少。目标不是让逐层相似度全部变成 1,而是用最少的非 INT8 层达到业务精度。
混合量化 API 参数随 Toolkit2 版本变化,直接参考当前 Toolkit2 API 文档。
五、性能评估
from rknn.api import RKNN
rknn = RKNN(verbose=True)rknn.load_rknn("model.rknn")rknn.init_runtime(target="rk3588", perf_debug=True)rknn.eval_perf(is_print=True, fix_freq=True)rknn.release()报告应重点看:
- 整网耗时;
- 各算子的耗时和占比;
- 算子运行在 CPU、GPU 还是 NPU;
- Tensor/权重读写量;
- reshape、transpose 等布局操作;
- NPU 核心和频率状态。
正确的基准方法
- 先预热;
- 固定或明确记录 CPU/NPU/DDR governor;
- 连续运行足够次数;
- 报平均值以及 P50/P95,不能只报最好一次;
- 同时区分纯 NPU 推理和端到端延迟;
- 监控温度和长时间降频;
- 保留测试脚本、输入和版本。
如果 CPU 算子占比高,先检查不支持算子和前后处理;如果 Transpose/Reshape 明显,检查布局;如果三个 NPU 核负载很低,检查核心配置、Context 并发和数据供给。
六、内存评估
from rknn.api import RKNN
rknn = RKNN(verbose=True)rknn.load_rknn("model.rknn")rknn.init_runtime(target="rk3588", eval_mem=True)rknn.eval_memory(is_print=True)rknn.release()常见组成:
weight_memory:权重;internal_memory:中间 Tensor 和工作区;other_memory:Runtime 其他开销;total_memory:上述总和。
.rknn 文件大小不等于运行内存。产品还要计算应用 RSS、输入输出队列、OpenCV/RGA 缓冲、视频解码、多个 Context 和系统余量。动态 shape 和多 Batch 应分别测峰值。
七、模型剪枝和加密
1. 自动剪枝
rknn.config( target_platform="rk3588", model_pruning=True,)Toolkit2 自动剪枝利用模型已有稀疏性,不等同于训练阶段的结构化剪枝。正确比较方式是:
开/关 pruning 各转换一次→ 比较日志、模型大小和 GFLOPs→ 比较任务精度→ 比较板端真实延迟和内存如果要获得更显著收益,通常需要“训练阶段结构化剪枝 → 微调恢复精度 → 导出 → RKNN 转换”。
2. 模型加密
先得到可正常推理的目标平台 RKNN,再执行加密:
from rknn.api import RKNN
rknn = RKNN(verbose=True)rknn.export_encrypted_rknn_model( "model.rknn", "model-encrypted.rknn", 1,)rknn.release()加密提高直接分析模型的门槛,但不是完整安全方案。正式产品还需要文件权限、安全启动、密钥管理、应用加固和升级验证。
八、多 Batch、多输入和动态 Shape
1. 多 Batch
多 Batch 主要提升吞吐,而不是保证单帧延迟降低:
rknn.build( do_quantization=True, dataset="dataset.txt", rknn_batch_size=3,)实际输入必须沿 batch 维拼接,输出也按同一索引拆分。Batch 增大会增加 Context、输入输出和中间张量内存;实时任务还要考虑等待凑 Batch 的延迟。
2. 多输入模型
多输入是一个任务同时需要多种 Tensor,例如图像+文本或双分支特征。每个输入都必须分别明确:
- 节点名称和列表顺序;
- shape、dtype 和 layout;
- mean/std;
- 数据来源和生命周期。
推理时:
outputs = rknn.inference( inputs=[input0, input1, input2], data_format=["nhwc", "nhwc", "nchw"],)多输入不能当成多 Batch 直接拼接。
3. 动态 Shape
RKNN 的动态输入通常是“预先声明的有限集合”,不是任意尺寸:
rknn.config( target_platform="rk3588", dynamic_input=[ [[1, 3, 160, 160]], [[1, 3, 224, 224]], [[1, 3, 256, 256]], ],)运行时只能选择已声明 shape。每种 shape 都要单独验证精度、延迟和内存;跨度过大还可能共享一组不理想的量化参数。
九、ONNX 图编辑和布局优化
模型输入输出附近的大量 Transpose/Reshape 可能落到 CPU 或增加内存。处理顺序应是:
- 用 Netron 和性能报告确认问题;
- 使用标准 ONNX 工具检查图;
- 必要时使用 Toolkit2 的
onnx_edit()或修改导出逻辑; - 对比修改前后模型的输出;
- 再测 RKNN 精度、性能和内存。
一个基础检查脚本:
import onnx
model = onnx.load("edited.onnx")onnx.checker.check_model(model, full_check=True)print("edited model is valid")图变短不代表语义不变。任何维度重排都要同步修改预处理和后处理。
十、推荐优化顺序
保证模型和前后处理正确→ 建立 FP16 与 INT8 基线→ 改善校准集→ 用精度分析定位敏感层→ 少量混合量化→ 检查 CPU 回退和布局转换→ 图编辑/硬件友好结构→ C++ 零拷贝、多 Context 和流水线→ 长时间压力与温度测试跳过正确性直接做零拷贝或多线程,会让错误更难排查。
十一、本篇实践验收
- 能对同一模型建立 FP16、INT8 和优化版对照表;
- 能使用精度、性能和内存评估 API;
- 能区分自动剪枝、结构化剪枝和混合量化;
- 能解释多 Batch、多输入、动态 Shape 的差异;
- 每次优化后都重新测任务精度和端到端性能。
Some information may be outdated