Back to Mnn

🚀 MNN 基于后训练的模型压缩指南

docs/tools/compress.md

3.5.013.1 KB
Original Source

🚀 MNN 基于后训练的模型压缩指南

一句话总结:无需训练、一键压缩 —— 适配绝大多数部署场景。

📌 基于后训练的模型压缩方案

基于后训练的模型压缩,无需训练,包括如下方案:

  • 权值量化(Weight Quantization) —— 模型体积缩小 75%,结合动态量化可以提速+省内存
  • FP16 压缩 —— 模型体积缩小 50%,精度基本无损
  • 自动压缩策略(auto_quant.py) —— 基于权值量化,自动为各算子选择不同的量化方案,以保障模型精度
  • 离线量化(少量校准数据) —— 全图 int8 推理,提速 + 省内存 + 省体积

🧰 压缩方案总览

压缩类型是否需要数据是否需要训练压缩率推理加速使用复杂度
权值量化(2-8bit)75%-87% ↓❌(默认)✅(开启动态量化)
FP16 压缩50% ↓
自动量化调优(4-8bit)✅(测试数据集)75%-87% ↓❌(默认)✅(开启动态量化)⭐⭐
离线量化(8bit)✅(少量校准图)75% ↓⭐⭐

✅ 推荐优先使用:权值量化 + 动态量化加速,或 离线量化

🛠️ 一、安装模型转换工具

1. C++ 工具(推荐用于生产环境)

bash
cd MNN
mkdir build && cd build
cmake .. -DMNN_BUILD_CONVERTER=ON -DMNN_BUILD_QUANTOOLS=ON
make -j8

生成工具:

  • MNNConvert:模型转换 + 权值量化 / FP16
  • quantized.out:离线量化工具

2. Python 工具(推荐用于快速实验)

bash
pip install MNN

安装后命令行工具:

  • mnnconvertMNNConvert 的 Python 封装
  • mnnquantquantized.out 的 Python 封装
  • mnn → 工具总入口

📦 二、权值量化(推荐首选)

仅压缩模型体积,不改变计算精度,推理速度不变,一键完成

✅ 适用场景

  • 模型太大,需减小体积
  • 不希望改变推理行为
  • 需要快速部署,无校准数据

🧪 使用方法

bash
# ONNX → MNN + 权值量化
./MNNConvert -f ONNX --modelFile model.onnx --MNNModel model_quant.mnn --weightQuantBits 8

# 使用 HQQ 量化算法(量化时间增长,一般情况下精度增加)
./MNNConvert ... --weightQuantBits 8 --hqq

# 或分块量化(精度更高,体积略增)【可与HQQ叠加使用】
./MNNConvert ... --weightQuantBits 8 --weightQuantBlock 128

📌 --weightQuantBlock 越小精度越高,建议 32~128

📌 --hqq 可以和 --weightQuantBlock 叠加使用

⚡ 开启动态量化加速(真正提速)

权值量化默认推理时还原为 float,开启动态量化可真正使用 int8 计算

1. 编译时开启低内存模式:

bash
cmake .. -DMNN_LOW_MEMORY=ON

2. 推理时设置 Memory Mode:

cpp
MNN::ScheduleConfig config;
BackendConfig backendConfig;
backendConfig.memory = BackendConfig::Memory_Low; // ✅ 关键!
config.backendConfig = &backendConfig;

✅ 开启后,卷积等核心算子将使用 int8 计算(权重存储使用 int4 或 int8),内存占用更低,速度更快

🧊 三、FP16 压缩(无损半精度)

**模型体积减半,精度几乎无损,不影响推理性能 **

✅ 适用场景

  • 模型体积敏感
  • 不希望损失精度

🧪 使用方法

bash
./MNNConvert -f ONNX --modelFile model.onnx --MNNModel model_fp16.mnn --fp16

⚡ 推理时开启 FP16 加速:

cpp
BackendConfig backendConfig;
backendConfig.precision = BackendConfig::Precision_Low; // ✅ 开启低精度加速
config.backendConfig = &backendConfig;

📌 注意:--fp16存储压缩Precision_Low运行时加速,两者是独立使用的 📌 没有使用--fp16压缩的模型,也可以通过设置 Precision_Low 以支持运行时加速

🤖 四、自动压缩工具(auto_quant.py)

自动搜索最优量化策略,跳过敏感层,保障精度

✅ 适用场景

  • 不确定哪些层该量化
  • 量化后精度下降明显
  • 希望自动化压缩流程

🧪 使用步骤

1. 将模型转成MNN格式,示例:

./MNNConvert -f ONNX --modelFile src.onnx --MNNModel float.mnn

2. 参考正确性校验,构建测试文件夹 mnntest

结构:

mnntest/
├── input.json   # 输入输出配置
├── input0.txt   # 输入0数据
├── input1.txt   # 输入1数据
├── output.txt  # 输出数据

3. 执行自动压缩

bash
python tools/converter/tools/auto_quant.py \
    --model float.mnn \
    --quant_model auto_quant.mnn \
    --test_dir mnntest \
    --rate 0.05  # 允许最大误差率

✅ 自动生成 auto_quant.mnnauto_quant.mnn.json(压缩策略文件)

4. (可选)手动调整策略

编辑 auto_quant.mnn.json,将敏感层 bits 设为 0(跳过量化)或 16(高精度):

json
      "weight": [
       {
        "name": "Convolution11",
        "bits": 8,
        "asymmetric": true,
        "blockSize": -1
       }
      ],

重新转换:

bash
./MNNConvert -f ONNX --modelFile float.mnn --MNNModel auto_quant.mnn --compressionParamsFile auto_quant.mnn.json --hqq

🔍 五、离线量化(推荐用于加速)

使用少量校准数据,将模型转为全 int8 推理,体积缩小 75%,推理加速

✅ 适用场景

  • 需要最大推理速度
  • 有少量代表性数据(100~1000 张图片)
  • 不想训练,但接受轻微精度损失
  • 输入输出尺寸大,需要减少这部分的内存

🧪 使用方法

Step 1: 先转为 float MNN

bash
./MNNConvert -f ONNX --modelFile model.onnx --MNNModel model_float.mnn

Step 2: 准备数据集,编写量化json文件

参考如下文件编写 quant.json:

json
{
    "format":"RGB",
    "mean":[
        103.94,
        116.78,
        123.68
    ],
    "normal":[
        0.017,
        0.017,
        0.017
    ],
    "width":224,
    "height":224,
    "path":"../resource/images/",
    "used_image_num":2,
    "feature_quantize_method":"KL",
    "weight_quantize_method":"MAX_ABS",
    "model":"mobilenet.mnn"
}

该Json的配置信息如下表所示:

keyvalue说明
format"RGB", "BGR", "RGBA", "GRAY"图片统一按RGBA读取,然后转换到format指定格式
mean/normal[float]dst = (src - mean) * normal
width/heightint模型输入的宽高
pathstr存放校正特征量化系数的图片目录
used_image_numint用于指定使用上述目录下多少张图片进行校正,默认使用path下全部图片
feature_quantize_method"KL", "ADMM", "EMA"指定计算特征量化系数的方法,默认:"KL"
weight_quantize_method"MAX_ABS", "ADMM"指定权值量化方法,默认:"MAX_ABS"
feature_clamp_valueint特征的量化范围,默认为127,即[-127, 127]对称量化,有时,量化之后溢出会很多,造成误差较大,可适当减小此范围,如减小至120,但范围减小太多会导致分辨率下降,使用时需测试
weight_clamp_valueint权值的量化范围,默认127,作用同feature_clamp_value,由于权值精度模型效果影响较大,建议调整feature_clamp_value即可
batch_sizeintEMA方法中指定batch size,和模型训练时差不多
quant_bitsint量化后的bit数,默认为8
skip_quant_op_names[str]跳过不量化的op的卷积op名字,因为有些层,如第一层卷积层,对模型精度影响较大,可以选择跳过不量化,可用netron可视化模型,找到相关op名字
input_typestr输入数据的类型,默认为"image"
debugbool是否输出debug信息,true或者false,输出的debug信息包含原始模型和量化模型各层输入输出的余弦距离和溢出率
feature_quantize_method说明
KL使用KL散度进行特征量化系数的校正,一般需要100 ~ 1000张图片(若发现精度损失严重,可以适当增减样本数量,特别是检测/对齐等回归任务模型,样本建议适当减少)
ADMM使用ADMM(Alternating Direction Method of Multipliers)方法进行特征量化系数的校正,一般需要一个batch的数据
EMA使用指数滑动平均来计算特征量化参数,这个方法会对特征进行非对称量化,精度可能比上面两种更好。使用这个方法时batch size应设置为和训练时差不多最好。
weight_quantize_method说明
MAX_ABS使用权值的绝对值的最大值进行对称量化
ADMM使用ADMM方法进行权值量化

多输入模型的配置

对于多输入模型,quant.json文件需要特别指定参数

需要特别指定的参数设置值
input_typestr:输入数据的类型,"sequence"
pathstr:存放校正特征量化系数的输入数据目录

例如在quant.json文件中 "path": "/home/data/inputs_dir/",你所构造的矫正数据集有两个,分别存放在input_0和input_1子目录下,即"/home/data/inputs_dir/input_0"和"/home/data/inputs_dir/input_1".由GetMNNInfo工具可以得到模型的输入输出名称,例如该模型的输入有三个:data0, data1, data2,输出有两个:out1, out2. 那么在input_0和input_1子目录下分别有六个文件:data0.txt, data1.txt, data2.txt, out1.txt, out2.txt, input.json. 其中的五个文件名要和模型的输入输出名对应,最后一个input.json文件则描述的是输入名和对应的shape内容:

json
{
    "inputs": [
        {
            "name": "data0",
            "shape": [
                2,
                4,
		        64,
		        64
            ]
        },
	        {
            "name": "data1",
            "shape": [
                1
            ]
        },
        {
            "name": "data2",
            "shape": [
                2,
                512,
                768
            ]
        }
    ],
    "outputs": [
        "out1", "out2"
    ]
}

Step 3: 使用 quantized.outmnnquant 进行离线量化

bash
./quantized.out model_float.mnn model_quant_int8.mnn quant.json

或 Python:

bash
mnnquant model_float.mnn model_quant_int8.mnn quant.json

🎯 六、推荐压缩策略

需求推荐方案命令示例
只想缩小模型体积权值量化 8bit--weightQuantBits 8
想缩小体积 + 加速权值量化 + 动态量化--weightQuantBits 8 + 编译 -DMNN_LOW_MEMORY=ON + 推理 Memory_Low
怕精度掉,想自动调优auto_quant.pyauto_quant.py --rate 0.05
想最大加速 + 有校准数据离线量化quantized.out + 校准数据集
想无损压缩 + 用 GPU 加速FP16 存储 + Precision_Low--fp16 + 推理 Precision_Low

📈 压缩效果参考(ImageNet 模型,华为 P20 Pro)

模型原始体积权值量化体积离线量化体积离线量化加速(ARMv8)
ResNet-1845M12M (-73%)12M187ms → 167ms
MobileNetV214M3.5M (-75%)3.5M62ms → 42ms
EfficientNet-B021M5.3M (-75%)5.3M128ms → 100ms

❓ 常见问题

Q1: 权值量化后精度下降怎么办?

  • 尝试 --hqq
  • 尝试 --weightQuantBlock 128
  • 使用 auto_quant.py 自动跳过敏感层

Q2: 动态量化没加速?

  • 确认编译时加了 -DMNN_LOW_MEMORY=ON
  • 确认推理时设置了 Memory_Low
  • 某些小模型或算子可能无加速效果

Q3: 离线量化需要多少数据?

  • 100~1000 张有代表性图片即可
  • 数据分布应覆盖真实场景

Q4: FP16 会损失精度吗?

  • 在视觉模型上通常 <0.1% 精度损失
  • 数值敏感任务(如语音、检测小目标)建议测试

📚 附录:命令行参数速查

MNNConvert 常用压缩参数

bash
--weightQuantBits 8          # 权值量化 8bit ,可选 2-8
--hqq      # 启用 HQQ 量化算法
--weightQuantBlock 128        # 分块量化大小
--fp16                       # FP16 存储压缩
--compressionParamsFile xxx.json  # 自定义压缩策略

✅ 总结:三步完成模型压缩

  1. 选方案

    • 只缩体积 → 权值量化
    • 要加速 → 离线量化 or 权值量化 + 动态量化
    • 怕掉点 → auto_quant.py
  2. 转模型

    bash
    ./MNNConvert ... --weightQuantBits 8
    
  3. (可选)调推理配置

    cpp
    backendConfig.memory = BackendConfig::Memory_Low; // 开启动态量化
    backendConfig.precision = BackendConfig::Precision_Low; // 开启FP16加速
    

如需基于训练实现的更高精度或压缩率的方案(剪枝/低秩/训练量化),请参考 mnncompress 文档,但90% 场景转换压缩已足够