NCNN 模型 INT8 量化
在移动设备上部署深度学习模型时,模型的推理速度和内存占用是关键考虑因素。NCNN 作为腾讯开源的轻量级神经网络推理框架,为移动端优化提供了强大的支持。其中,INT8 量化技术是提升模型性能、降低资源消耗的重要手段。最近公司刚好也有业务需求,需要在手机端部署数字人,故探索了下 NCNN 的量化。本文将基于 NCNN 官方文档,详细介绍如何将浮点模型 F32 量化为 INT8 模型,并在移动设备上实现高效推理。
什么是 INT8 量化
INT8 量化是一种模型优化技术,它将模型中的浮点(FP32)权重和激活值转换为 8 位整数(INT8)表示。相比于 FP32,INT8 数据类型占用更少的内存空间,并且显著提升推理速度。
NCNN 量化
NCNN 提供了一套通用的量化工具,可以将 FP32 模型转换为 INT8 模型。整个过程主要分为三个步骤:模型优化、校准表生成和模型量化。
1. 模型优化
在进行量化之前,首先需要对模型进行优化。NCNN 的 ncnnoptimize 工具可以帮助我们去除模型中不必要的层、融合操作,从而减小模型体积并提高推理效率。这一步是量化过程的基础,能够为后续的量化操作提供一个更“干净”的模型。
ncnnoptimize simplified_f32_jd_shu_90000_040715.ncnn.param simplified_f32_jd_shu_90000_040715.ncnn.bin mobilenet-opt.param mobilenet-opt.bin 0
2. 创建校准表
校准表是 INT8 量化过程中至关重要的一步。它通过分析少量代表性数据(校准数据集)来确定浮点值到 INT8 值的映射关系(即量化因子)。NCNN 提供了 ncnn2table
工具来生成校准表。
2.1 从图像数据校准(未采用)
官方建议使用验证数据集进行校准,通常需要 5000 张以上的图像以确保校准的准确性。校准图像可以是 ImageNet 等大型数据集的子集。
find images/ -type f > imagelist.txt
./ncnn2table mobilenet-opt.param mobilenet-opt.bin imagelist.txt mobilenet.table mean=[104,117,123] norm=[0.017,0.017,0.017] shape=[224,224,3] pixel=BGR thread=8 method=kl
mean
和norm
:这些值应与您在Mat::substract_mean_normalize()
中使用的值保持一致,用于图像预处理。shape
:模型的输入 blob 形状,可以是[w,h]
或[w,h,c]
。如果w
和h
都给定,图像将被精确缩放。如果都为零或负数,则不进行缩放。如果只有一个为零或负数,则按比例缩放以保持宽高比。pixel
:模型的像素格式,图像像素在Extractor::input()
之前会转换为此类型。thread
:用于并行推理的 CPU 线程数。method
:后训练量化算法,目前支持kl
(KL 散度)和aciq
。
如果模型有多个输入节点,可以指定多个列表文件和参数:
./ncnn2table mobilenet-opt.param mobilenet-opt.bin imagelist-bgr.txt,imagelist-depth.txt mobilenet.table mean=[104,117,123],[128] norm=[0.017,0.017,0.017],[0.0078125] shape=[224,224,3],[224,224,1] pixel=BGR,GRAY thread=8 method=kl
2.2 从 NPY 数据校准(采用)
除了 2.1 的方式,也可以使用 NPY 文件进行校准,因为我的模型输入较多,而且除了图像数据外还有音频特征,所以我是采用这种方式去生成校准表的。官方建议使用验证(开发)数据集进行校准,并使用与训练集相同的预处理方法获取输入向量。对于 batchsize=1
的情况,将每个输入向量存储为一个 NPY 文件,N 个输入对应 N 个 NPY 文件,实际存储的向量应去除批次维度。
例如: 我的模型有两个输入,切记 shape 要为 CHW
格式的,且没有批次这个维度 N
mel, shape=[1, 80, 16]
img, shape=[6, 192, 192]
然后每个实例我们存成一个 npy 文件,并将其记录到一个 txt 文件中
filelist_img.txt
./img_0.npy
./img_1.npy
./img_2.npy
filelist_mel.txt
./mel_0.npy
./mel_1.npy
./mel_2.npy
然后运行生成校准表的命令,切记这里的 shape
要设置成 WHC
格式的
ncnn2table mobilenet-opt.param mobilenet-opt.bin filelist_img.txt,filelist_mel.txt mobilenet-opt.table mean='[0,0,0],[0,0,0]' norm='[1,1,1],[1,1,1]' shape='[16,80,1],[192,192,6]' pixel=GRAY,BGR thread=8 method=kl
3. 量化模型
在生成校准表之后,就可以使用 ncnn2int8
工具将优化后的 FP32 模型和校准表转换为 INT8 模型。
ncnn2int8 mobilenet-opt.param mobilenet-opt.bin simplified-int8.param simplified-int8.bin mobilenet-opt.table
4. Python 侧加载 NCNN 模型并推理
python 侧可以验证量化的 NCNN 模型的可用性,速度等性能指标还是需要在具体的移动端设备上进行测试的
import ncnn
import numpy as np
# 初始化NCNN环境
ncnn.create_gpu_instance() # 启用GPU加速(可选)
net = ncnn.Net()
# # 加载模型
net.load_param("./simplified_int8.ncnn.param")
net.load_model("./simplified_int8.ncnn.bin")
# 准备输入数据
mel_list = ''
img_list = ''
input_mel = np.array(mel_list).astype(np.float32).reshape(1, 1, 80, 16)
input_img = np.array(img_list).astype(np.float32).reshape(1, 6, 192, 192)
# 调整维度:移除冗余维度并转置图像数据
squeezed_mel = input_mel.squeeze()
squeezed_img = input_img.squeeze().transpose(1, 2, 0)
squeezed_mel = np.ascontiguousarray(squeezed_mel)
squeezed_img = np.ascontiguousarray(squeezed_img)
mat_mel = ncnn.Mat(squeezed_mel)
mat_img = ncnn.Mat(squeezed_img)
# 推理
with net.create_extractor() as ex:
ex.input("mel", mat_mel)
ex.input("img", mat_img)
# 输出的变量名
ret, out = ex.extract("output_array")
output_array = np.array(out)