纸翼 · 加载中
1919 words
10 minutes
RK3588 端侧 AI 部署(四):量化、评估与模型优化
所属合集 RK3588 端侧 AI 部署 第 4 / 6 篇
  1. 1 RK3588 端侧 AI 部署(一):平台、NPU 与 RKNN 工具链
  2. 2 RK3588 端侧 AI 部署(二):主机与板端环境搭建
  3. 3 RK3588 端侧 AI 部署(三):ONNX 转 RKNN 与推理验证
  4. 4 RK3588 端侧 AI 部署(四):量化、评估与模型优化 正在阅读
  5. 5 RK3588 端侧 AI 部署(五):Lite2、C API 与零拷贝工程化
  6. 6 RK3588 端侧 AI 部署(六):MobileNet、YOLO 与 RKLLM 实战

RK3588 端侧 AI 部署(四):量化、评估与模型优化#

模型能在 NPU 上运行,只能说明“链路通了”。这一篇要回答三个工程问题:结果准不准、速度够不够、内存放不放得下。

上一篇:ONNX 转 RKNN 与推理验证

一、优化前先建立基线#

每个模型至少保留两组基线:

  1. 原框架/ONNX 浮点基线;
  2. RKNN FP16 或非量化基线。

再逐步加入 INT8、混合量化、图编辑、线程池、零拷贝等优化。每次只改一个主要变量。

建议用下面的表记录:

指标原框架ONNXRKNN FP16RKNN INT8优化版
Accuracy/mAP/Recall
纯推理平均延迟
端到端 P50/P95
吞吐 FPS
峰值内存
模型文件大小
CPU/NPU/DDR 频率
温度/是否降频

没有统一输入、频率和统计口径的数字不能直接比较。

二、INT8 量化的本质#

线性量化可以抽象为:

q = round(x / scale) + zero_point
x ≈ (q - zero_point) × scale

将 FP32/FP16 映射到 INT8 的收益通常包括:

  • 模型和权重更小;
  • 中间张量内存下降;
  • 更好利用 RK3588 的整数 NPU 吞吐;
  • 功耗和延迟可能下降。

代价是表示精度降低。对异常值、激活分布尖锐、注意力或小目标敏感的网络,量化误差可能明显放大。

校准集原则#

  • 必须来自真实业务分布;
  • 覆盖暗光、逆光、遮挡、不同尺度和负样本;
  • 不要只使用“最好识别”的图片;
  • 预处理必须和部署一致;
  • 数据划分不能泄漏验证集结论。

课程介绍的 normalmmsekl_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 核心和频率状态。

正确的基准方法#

  1. 先预热;
  2. 固定或明确记录 CPU/NPU/DDR governor;
  3. 连续运行足够次数;
  4. 报平均值以及 P50/P95,不能只报最好一次;
  5. 同时区分纯 NPU 推理和端到端延迟;
  6. 监控温度和长时间降频;
  7. 保留测试脚本、输入和版本。

如果 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 或增加内存。处理顺序应是:

  1. 用 Netron 和性能报告确认问题;
  2. 使用标准 ONNX 工具检查图;
  3. 必要时使用 Toolkit2 的 onnx_edit() 或修改导出逻辑;
  4. 对比修改前后模型的输出;
  5. 再测 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 的差异;
  • 每次优化后都重新测任务精度和端到端性能。

下一篇:RK3588 端侧 AI 部署(五):Lite2、C API 与零拷贝工程化

RK3588 端侧 AI 部署(四):量化、评估与模型优化
https://blog.huangzy.xyz/posts/rk3588-端侧-ai-部署四/
Author
纸翼
Published at
2026-07-21
License
CC BY-NC-SA 4.0

Some information may be outdated