模型转换与量化
1. 概述
1.1 功能简介
模型转换是将深度学习模型(ONNX/PyTorch/TensorFlow/Caffe/Darknet)转换为 RKNN 格式的过程,量化是将 FP32 模型压缩为 INT8/INT16 以提升推理速度、降低内存占用。RK3576 的 6 TOPS NPU 在 INT8 量化下性能最佳,相比 FP32 可获得约 4 倍推理加速和约 75% 的模型体积缩减。
RKNN Toolkit2 提供完整的模型转换工具链,支持多种主流深度学习框架,并提供灵活的量化策略以平衡精度与性能。
1.2 应用场景
- 深度学习模型部署:将训练完成的模型部署到 RK3576 NPU 上运行
- 嵌入式端 AI 推理:在资源受限的嵌入式设备上实现高效推理
- 模型压缩与加速:通过量化减小模型体积、提升推理速度
- 多模型并发部署:INT8 量化减小模型体积后,可同时加载多个模型
1.3 工作原理
模型转换与量化的完整流程如下:
┌─────────────┐ ┌─────────────┐ ┌──────────────┐ ┌─────────────────┐
│ FP32 模型 │ ──▶ │ 量化校准 │ ──▶ │ RKNN 图构建 │ ──▶ │ INT8/INT16 模型 │
│ (ONNX/PyTorch)│ │ (Calibration)│ │ (Graph Build) │ │ (.rknn 文件) │
└─────────────┘ └─────────────┘ └──────────────┘ └─────────────────┘
│ │
使用校准数据集 图优化与算子融合
计算量化参数 适配 NPU 硬件
│ │
▼ ▼
┌─────────────────────────────────┐
│ RK3576 NPU 高效推理 (6 TOPS) │
└─────────────────────────────────┘1.4 RK3576 NPU 量化特性
| 特性 | 说明 |
|---|---|
| 最大算力 | 6 TOPS(INT8) |
| 支持精度 | FP16、INT8、INT16 |
| 量化算法 | normal、mmse、and |
| Per-channel 量化 | 支持,精度更高 |
| 对称/非对称量化 | 支持 |
| 算子融合 | 自动 Conv+BN+ReLU 融合 |
量化算法等软件特性以实际发布的 RK3576 SDK 版本为准。
1.5 前置条件
- 已完成 RKNN Toolkit2 安装(参考
ch02-rknn-toolkit2-setup-and-usage.md) - 已掌握基础 Python 编程
- 已准备好待转换的深度学习模型(ONNX 格式优先)
2. 硬件准备
2.1 硬件清单
| 组件 | 型号/规格 | 数量 | 备注 |
|---|---|---|---|
| 主控板 | C-3576BQ 核心板 + M4-R2 底板 | 1 | 已烧录 Linux 系统 |
| 调试串口线 | USB 转 TTL(3.3V) | 1 | 用于串口调试 |
| 电源适配器 | 5V/2A 电源(Type-C 或 6pin 供电) | 1 | 空载最小 1000mA |
2.2 注意事项
- 模型转换在 PC 端完成,无需连接开发板
- 转换后的 RKNN 模型通过 SCP 或 ADB 传输到开发板
- 确保 PC 端 Python 环境与 RKNN Toolkit2 版本匹配
3. 软件准备
3.1 RKNN Toolkit2 安装确认
在 PC 端确认 RKNN Toolkit2 已正确安装:
python3 -c "from rknn.api import RKNN; print('RKNN Toolkit2 OK')"预期输出:
RKNN Toolkit2 OK3.2 安装方法(如未安装)
# Ubuntu 20.04/22.04 推荐安装方式
pip3 install rknn-toolkit2
# 或使用离线安装包
pip3 install rknn_toolkit2-<version>-cp310-cp310-linux_x86_64.whl3.3 环境验证
# 验证版本
python3 -c "import rknn; print(rknn.__version__)"
# 验证依赖
python3 -c "import numpy; import onnx; print('Dependencies OK')"3.4 目录结构准备
mkdir -p ~/rknn_project
cd ~/rknn_project
# 目录结构
rknn_project/
├── model/ # 原始模型文件
│ └── resnet50.onnx
├── dataset/ # 校准数据集
│ └── calibration_images/
├── output/ # 输出目录
│ └── resnet50.rknn
└── convert.py # 转换脚本4. 操作步骤
4.1 ONNX 模型转换
4.1.1 基础转换(不量化)
from rknn.api import RKNN
# 创建 RKNN 对象
rknn = RKNN()
# 配置模型参数
rknn.config(
mean_values=[[123.675, 116.28, 103.53]],
std_values=[[58.395, 57.12, 57.375]],
target_platform='rk3576',
quantized_dtype='asymmetric_quantized-8',
optimization_level=3
)
# 加载 ONNX 模型
ret = rknn.load_onnx(model='resnet50.onnx')
if ret != 0:
print('加载模型失败')
exit(ret)
# 构建 RKNN 模型(不量化)
ret = rknn.build(do_quantization=False)
if ret != 0:
print('构建模型失败')
exit(ret)
# 导出 RKNN 模型
ret = rknn.export_rknn('output/resnet50.rknn')
if ret != 0:
print('导出模型失败')
exit(ret)
# 释放资源
rknn.release()
print('模型转换完成')4.1.2 INT8 量化转换(推荐)
from rknn.api import RKNN
rknn = RKNN()
# 配置模型参数
rknn.config(
mean_values=[[123.675, 116.28, 103.53]],
std_values=[[58.395, 57.12, 57.375]],
target_platform='rk3576',
quantized_dtype='asymmetric_quantized-8',
optimization_level=3
)
# 加载 ONNX 模型
ret = rknn.load_onnx(model='resnet50.onnx')
if ret != 0:
print('加载模型失败')
exit(ret)
# 构建 RKNN 模型(INT8 量化)
ret = rknn.build(
do_quantization=True,
dataset='calibration_data.txt',
quantized_algorithm='normal'
)
if ret != 0:
print('构建模型失败')
exit(ret)
# 导出 RKNN 模型
ret = rknn.export_rknn('output/resnet50_int8.rknn')
if ret != 0:
print('导出模型失败')
exit(ret)
rknn.release()
print('INT8 量化转换完成')4.2 PyTorch 模型转换
PyTorch 模型需要先转换为 ONNX 格式,再进行 RKNN 转换。
4.2.1 PyTorch 导出 ONNX
import torch
import torch.nn as nn
# 加载 PyTorch 模型
model = torch.load('model.pth')
model.eval()
# 创建 dummy input
dummy = torch.randn(1, 3, 224, 224)
# 导出 ONNX
torch.onnx.export(
model,
dummy,
'model.onnx',
opset_version=11,
input_names=['input'],
output_names=['output'],
dynamic_axes={
'input': {0: 'batch_size'},
'output': {0: 'batch_size'}
}
)
print('ONNX 导出完成')4.2.2 转换为 RKNN
from rknn.api import RKNN
rknn = RKNN()
rknn.config(
mean_values=[[123.675, 116.28, 103.53]],
std_values=[[58.395, 57.12, 57.375]],
target_platform='rk3576',
quantized_dtype='asymmetric_quantized-8'
)
ret = rknn.load_onnx(model='model.onnx')
if ret != 0:
print('加载模型失败')
exit(ret)
ret = rknn.build(
do_quantization=True,
dataset='calibration_data.txt'
)
if ret != 0:
print('构建模型失败')
exit(ret)
ret = rknn.export_rknn('output/model.rknn')
if ret != 0:
print('导出模型失败')
exit(ret)
rknn.release()4.3 TensorFlow 模型转换
from rknn.api import RKNN
rknn = RKNN()
rknn.config(
mean_values=[[127.5, 127.5, 127.5]],
std_values=[[127.5, 127.5, 127.5]],
target_platform='rk3576',
quantized_dtype='asymmetric_quantized-8'
)
# 加载 TensorFlow 模型(.pb 格式)
ret = rknn.loadtensorflow(tf_path='model.pb',
inputs=['input:0'],
outputs=['output:0'])
if ret != 0:
print('加载模型失败')
exit(ret)
ret = rknn.build(
do_quantization=True,
dataset='calibration_data.txt'
)
if ret != 0:
print('构建模型失败')
exit(ret)
ret = rknn.export_rknn('output/model.rknn')
if ret != 0:
print('导出模型失败')
exit(ret)
rknn.release()4.4 量化策略详解
4.4.1 INT8 量化(推荐)
INT8 量化是 RK3576 NPU 的最佳性能配置,6 TOPS 算力在此模式下完全释放。
rknn.build(
do_quantization=True,
dataset='calibration_data.txt',
quantized_algorithm='normal',
quantized_dtype='asymmetric_quantized-8'
)适用场景:大多数视觉模型、目标检测模型
优势:推理速度最快、模型体积最小
注意事项:部分对精度敏感的模型可能出现精度下降
4.4.2 INT16 量化
INT16 量化提供更高精度,但性能约为 INT8 的一半。
rknn.build(
do_quantization=True,
dataset='calibration_data.txt',
quantized_algorithm='normal',
quantized_dtype='asymmetric_quantized-16'
)适用场景:对精度要求较高的模型、图像分割模型
优势:精度接近 FP32
注意事项:推理速度约为 INT8 的 50%
4.4.3 不量化(FP32)
不量化直接使用 FP32 精度,性能最低但精度最高。
rknn.build(
do_quantization=False
)适用场景:调试阶段、精度验证
注意事项:推理速度最慢、模型体积最大
4.4.4 混合精度量化
通过 per-channel 配置实现更精细的量化控制。
rknn.config(
per-channel=True,
quantized_dtype='asymmetric_quantized-8'
)
rknn.build(
do_quantization=True,
dataset='calibration_data.txt'
)4.4.5 MMSE 量化算法
MMSE(最小均方误差)量化算法通过优化量化参数减少量化误差。
rknn.build(
do_quantization=True,
dataset='calibration_data.txt',
quantized_algorithm='mmse',
mmse_per_layer=True
)MMSE 算法优势:
- 比普通量化精度更高
- 适合精度敏感的模型
- 推理速度与普通 INT8 量化相同
4.5 校准数据集准备
校准数据集用于确定量化参数,数据集质量直接影响量化后模型精度。
4.5.1 数据集格式
calibration_data.txt 文件格式(每行一个图片路径):
/path/to/img1.jpg
/path/to/img2.jpg
/path/to/img3.jpg
...4.5.2 自动生成校准数据集
import os
import random
def generate_calibration_dataset(image_dir, output_file, num_images=100):
"""自动生成校准数据集"""
images = []
for root, dirs, files in os.walk(image_dir):
for file in files:
if file.lower().endswith(('.jpg', '.jpeg', '.png', '.bmp')):
images.append(os.path.join(root, file))
# 随机采样
if len(images) > num_images:
images = random.sample(images, num_images)
with open(output_file, 'w') as f:
for img_path in images:
f.write(f'{img_path}\n')
print(f'生成校准数据集:{len(images)} 张图片')
# 使用示例
generate_calibration_dataset(
image_dir='calibration_images/',
output_file='calibration_data.txt',
num_images=200
)4.5.3 校准数据集要求
| 要求 | 说明 |
|---|---|
| 图片数量 | 建议 100-500 张 |
| 图片格式 | JPG、PNG、BMP |
| 图片尺寸 | 与模型输入一致(如 224x224) |
| 数据分布 | 覆盖实际应用场景的数据分布 |
| 图片质量 | 避免模糊、过曝、过暗的图片 |
4.6 完整转换流程
以下是一个完整的模型转换脚本示例:
#!/usr/bin/env python3
"""
RK3576 模型转换完整流程
"""
from rknn.api import RKNN
import os
import sys
# 配置参数
ONNX_MODEL = 'model.onnx'
RKNN_MODEL = 'output/model.rknn'
CALIBRATION_DATA = 'calibration_data.txt'
TARGET_PLATFORM = 'rk3576'
# 输入预处理参数(以 ImageNet 为例)
MEAN_VALUES = [[123.675, 116.28, 103.53]]
STD_VALUES = [[58.395, 57.12, 57.375]]
def convert_model():
"""模型转换主函数"""
rknn = RKNN()
# 1. 配置
print('1. 配置模型参数...')
rknn.config(
mean_values=MEAN_VALUES,
std_values=STD_VALUES,
target_platform=TARGET_PLATFORM,
quantized_dtype='asymmetric_quantized-8',
optimization_level=3
)
# 2. 加载模型
print('2. 加载 ONNX 模型...')
ret = rknn.load_onnx(model=ONNX_MODEL)
if ret != 0:
print('错误:加载模型失败')
return ret
# 3. 构建模型(量化)
print('3. 构建 RKNN 模型(INT8 量化)...')
ret = rknn.build(
do_quantization=True,
dataset=CALIBRATION_DATA,
quantized_algorithm='normal'
)
if ret != 0:
print('错误:构建模型失败')
return ret
# 4. 导出模型
print('4. 导出 RKNN 模型...')
os.makedirs(os.path.dirname(RKNN_MODEL), exist_ok=True)
ret = rknn.export_rknn(RKNN_MODEL)
if ret != 0:
print('错误:导出模型失败')
return ret
# 5. 释放资源
rknn.release()
print(f'模型转换完成:{RKNN_MODEL}')
return 0
if __name__ == '__main__':
ret = convert_model()
sys.exit(ret)5. 代码详解
5.1 RKNN API 核心方法
5.1.1 RKNN() 构造函数
创建 RKNN 对象实例,初始化转换环境。
rknn = RKNN()5.1.2 config() 方法
配置模型转换参数,必须在 load_onnx() 之前调用。
主要参数:
| 参数 | 类型 | 说明 |
|---|---|---|
| mean_values | list | 输入均值,格式 [[R, G, B]] |
| std_values | list | 输入标准差,格式 [[R, G, B]] |
| target_platform | str | 目标平台,RK3576 使用 'rk3576' |
| quantized_dtype | str | 量化数据类型 |
| optimization_level | int | 优化级别(0-3) |
| per-channel | bool | 是否启用 per-channel 量化 |
5.1.3 load_onnx() 方法
加载 ONNX 格式模型文件。
ret = rknn.load_onnx(model='model.onnx')返回值:0 表示成功,非 0 表示失败
5.1.4 build() 方法
构建 RKNN 模型,执行量化和图优化。
主要参数:
| 参数 | 类型 | 说明 |
|---|---|---|
| do_quantization | bool | 是否执行量化 |
| dataset | str | 校准数据集文件路径 |
| quantized_algorithm | str | 量化算法(normal/mmse/and) |
| quantized_dtype | str | 量化数据类型 |
| mmse_per_layer | bool | MMSE 逐层优化 |
5.1.5 export_rknn() 方法
导出构建完成的 RKNN 模型文件。
ret = rknn.export_rknn('model.rknn')5.1.6 release() 方法
释放 RKNN 对象占用的资源。
rknn.release()5.2 量化原理
5.2.1 量化计算公式
FP32 权重量化过程:
┌─────────────────────────────────────────────────────────┐
│ FP32 权重范围: [min, max] │
│ │
│ Scale = (max - min) / 255 │
│ Zero_Point = round(-min / Scale) │
│ │
│ 量化公式: │
│ Q(x) = clamp(round(x / Scale) + Zero_Point, 0, 255) │
│ │
│ 反量化公式: │
│ DQ(x) = (x - Zero_Point) * Scale │
└─────────────────────────────────────────────────────────┘5.2.2 Per-Channel 量化
Per-channel 量化对每个通道独立计算量化参数,精度更高:
普通量化(Per-Tensor):
整个权重矩阵共享一组 Scale 和 Zero_Point
Per-Channel 量化:
每个输出通道独立计算 Scale 和 Zero_Point
精度更高,但模型体积略大5.3 RKNN 图结构
RKNN 模型内部结构:
RKNN 模型结构:
├── 输入层(Input)
│ └── 支持 FP32/FP16 输入
├── 预处理层
│ ├── Mean 归一化
│ └── Std 归一化
├── 卷积层(Conv)
│ ├── 支持 INT8/INT16 计算
│ ├── 自动 Conv+BN 融合
│ └── 自动 Conv+ReLU 融合
├── 池化层(Pool)
│ ├── Max Pooling
│ └── Average Pooling
├── 激活层(ReLU)
│ ├── ReLU
│ ├── ReLU6
│ └── LeakyReLU
├── 全连接层(FC)
│ └── 支持 INT8 计算
├── 归一化层
│ └── Batch Normalization
└── 输出层(Output)
└── 支持 FP32/FP16 输出5.4 量化精度与性能对比
| 量化方式 | 模型大小 | 推理速度 | 精度 | 适用场景 |
|---|---|---|---|---|
| FP32(不量化) | 1x | 1x | 基准 | 调试、精度验证 |
| INT16 | ~0.5x | ~2x | 略低 | 高精度需求 |
| INT8 | ~0.25x | ~4x | 略低 | 通用部署(推荐) |
| INT8 + MMSE | ~0.25x | ~4x | 接近 INT16 | 精度敏感场景 |
| INT8 + Per-channel | ~0.25x | ~4x | 高于普通 INT8 | 高精度+高性能 |
5.5 不同框架模型转换对比
| 框架 | 输入格式 | 转换方法 | 注意事项 |
|---|---|---|---|
| ONNX | .onnx | load_onnx() | 推荐使用,转换最稳定 |
| PyTorch | .pth/.pt | 先导出 ONNX 再转换 | 需要设置 opset_version=11+ |
| TensorFlow | .pb | loadtensorflow() | 需指定 inputs/outputs 名称 |
| Caffe | .prototxt+.caffemodel | loadcaffe() | 较老框架,建议迁移到 ONNX |
| Darknet | .cfg+.weights | loaddarknet() | YOLO 系列模型 |
6. 常见问题
6.1 模型转换报错 "not support op"
现象: 转换时报错提示某些 ONNX 算子不支持。
原因分析:
- ONNX 模型使用了 RKNN 不支持的算子
- ONNX opset_version 版本过高或过低
解决方案:
# 1. 查看不支持的算子
python3 -c "
from rknn.api import RKNN
r = RKNN()
ret = r.load_onnx('model.onnx')
print(f'加载结果: {ret}')
r.release()
"# 2. 使用 opset_version=11-13 重新导出 ONNX
torch.onnx.export(model, dummy, 'model.onnx', opset_version=11)# 3. 替换为支持的等价算子
# 例如:将 SiLU 替换为 Sigmoid + Mul
class SiLUReplace(nn.Module):
def forward(self, x):
return x * torch.sigmoid(x)6.2 量化后精度下降严重
现象: INT8 量化后准确率大幅下降(超过 5%)。
原因分析:
- 校准数据集分布与实际数据差异大
- 模型对量化敏感
- 校准数据集数量不足
解决方案:
# 1. 使用 MMSE 算法
rknn.build(
do_quantization=True,
dataset='calibration_data.txt',
quantized_algorithm='mmse'
)
# 2. 增加校准数据量(建议 100-500 张)
# 3. 使用 per-channel 量化
rknn.config(per-channel=True)
# 4. 使用 INT16 量化
rknn.build(
do_quantization=True,
quantized_dtype='asymmetric_quantized-16'
)
# 5. 检查校准数据分布
# 确保校准数据与实际应用场景的数据分布一致6.3 导出的 RKNN 模型过大
现象: RKNN 文件比原始模型还大。
原因分析:
- 未启用量化
- 模型包含大量不支持的算子(被软件模拟,不会压缩)
解决方案:
# 1. 确保启用了量化
rknn.build(do_quantization=True, ...)
# 2. 检查不支持的算子
# 不支持的算子会被软件模拟,不会被压缩
# 3. 简化模型结构
# 减少不必要的层和分支6.4 转换时报错 "load onnx failed"
现象: 加载 ONNX 模型失败。
解决方案:
# 1. 验证 ONNX 模型是否有效
python3 -c "
import onnx
model = onnx.load('model.onnx')
onnx.checker.check_model(model)
print('ONNX 模型验证通过')
"
# 2. 检查 ONNX 版本
pip3 install onnx --upgrade
# 3. 简化 ONNX 模型
pip3 install onnxsim
python3 -c "
import onnx
from onnxsim import simplify
model = onnx.load('model.onnx')
model_simp, check = simplify(model)
onnx.save(model_simp, 'model_simplified.onnx')
print('模型简化完成')
"6.5 量化校准失败
现象: 校准过程中报错或校准数据无法加载。
解决方案:
# 1. 检查校准数据文件路径
cat calibration_data.txt | head -5
# 2. 确认图片存在
ls -l $(head -1 calibration_data.txt)
# 3. 检查图片格式
python3 -c "
from PIL import Image
img = Image.open('test.jpg')
print(f'图片尺寸: {img.size}')
print(f'图片模式: {img.mode}')
"
# 4. 确保图片尺寸与模型输入一致
# 如 ResNet50 需要 224x2246.6 目标平台配置错误
现象: 转换时报错或运行时报错。
解决方案:
# RK3576 必须使用 target_platform='rk3576'
rknn.config(
target_platform='rk3576', # 正确
# target_platform='rk3568', # 错误
...
)相关文档
- NPU架构与性能概览 — RK3576 NPU 架构和性能指标
- RKNN Toolkit2安装与使用 — RKNN Toolkit2 环境搭建
- 分类模型部署(LeNet-ResNet) — 分类模型部署实战
- 目标检测部署(YOLOv5-v7-v8) — 目标检测模型部署
