From 6aa43ab4ecc2c4e5cbf5bd0c6933642a29a6dc46 Mon Sep 17 00:00:00 2001
From: LiuChiachi <709153940@qq.com>
Date: Wed, 17 Aug 2022 12:14:46 +0000
Subject: [PATCH 1/4] update compression doc
---
docs/compression.md | 422 +++++++++++++++++---------
model_zoo/ernie-3.0/README.md | 10 +-
paddlenlp/trainer/compression_args.py | 2 +-
paddlenlp/trainer/trainer_compress.py | 10 +-
paddlenlp/transformers/ofa_utils.py | 7 +-
5 files changed, 296 insertions(+), 155 deletions(-)
diff --git a/docs/compression.md b/docs/compression.md
index 226f160008ad..d18e97dc9156 100644
--- a/docs/compression.md
+++ b/docs/compression.md
@@ -1,151 +1,135 @@
-# PaddleNLP Compression API
+# PaddleNLP 模型压缩 API
-## 一、压缩 API 使用介绍
+ **目录**
+ * [模型压缩 API 功能简介](#模型压缩API功能介绍)
+ * [如何启动模型压缩](#如何启动模型压缩)
+ * [Step1:获取 CompressionArguments 对象](#获取CompressionArguments对象)
+ * [Step2:实例化 Trainer 并调用 compress()](#实例化Trainer并调用compress())
+ * [Trainer 实例化参数介绍](#Trainer实例化参数介绍)
+ * [Step3:实现自定义评估函数和loss计算函数(可选)](#实现自定义评估函数和loss计算函数(可选))
+ * [Step4:传参并运行压缩脚本](#传参并运行压缩脚本)
+ * [CompressionArguments 参数介绍](#CompressionArguments参数介绍)
+ * [三大场景模型压缩 API 使用示例](#三大场景模型压缩 API 使用示例)
+ * [模型部署与评价](#模型部署与评价)
+ * [FAQ](#FAQ)
+ * [参考文献](#References)
-### 1. 获取压缩功能所需参数
-压缩功能需要参数 `CompressionArguments`,主要通过 python 脚本启动命令传入,下方是 `CompressionArguments`中的参数介绍。
+
-#### 1.1 CompressionArguments 参数介绍
+## 模型压缩 API 功能简介
-CompressionArguments 中的参数一部分是压缩所使用的参数,另一部分继承自 TrainingArguments,用于设置压缩训练时的超参数。
-
-**公共参数**
-
-公共参数中的参数和具体的压缩策略无关。
-```python
---strategy
- 压缩策略,目前支持 'dynabert+ptq'、 'dynabert' 和 'ptq'。其中 'dynabert' 代表基于 DynaBERT 的宽度裁剪策略,'ptq' 表示静态离线量化。'dynabert+ptq',代表先裁剪后量化。三种策略可以都选择,然后选最优结果。默认是 'dynabert+ptq';
-
---output_dir
- 压缩后模型保存目录;
+PaddleNLP 模型压缩 API 功能支持对 ERNIE 类下游任务上微调后的模型进行裁剪、量化,以缩小模型体积、减少内存占用、减少计算、提升推理速度从而减少部署难度。模型压缩 API 效果好,且简洁易用。目前裁剪功能现在支持 DynaBERT 中的宽度自适应裁剪策略;量化现在支持静态离线量化方法(PTQ),即无需训练,只需少量校准数据,即可导出量化模型。
+- **效果好**:目前已经在分类(包含文本分类、文本匹配、自然语言推理、代词消歧、阅读理解等任务)、序列标注、抽取式阅读理解任务上进行过验证,基本达到精度无损。例如,对于 12L768H 结构的模型,宽度保留比例为 2/3 基本可以达到精度无损,对于 6L768H 模型,宽度保留比例 2/3 基本可以达到精度无损。裁剪后推理速度能够达到原先的 1-2 倍;6L768H 结构的模型量化后推理速度能够达到量化前的 2-3 倍。
---input_infer_model_path
- 待压缩的静态图模型,该参数是为了支持对静态图模型的压缩。不需使用时可忽略。默认为 None;
-```
+- **简洁易用**:只需要简单几步即可开展模型压缩任务
-**DynaBERT 裁剪参数**
+如下表所示,ERNIE 3.0-Medium (6-layer, 384-hidden, 12-heads) 模型在三类任务(文本分类、序列标注、抽取式阅读理解)经过裁剪 + 量化后加速比均达到 3 倍左右,所有任务上平均精度损失可控制在 0.5 以内(0.46)。
-当用户使用了 DynaBERT 裁剪策略时需要传入以下可选参数:
+| | TNEWS 性能 | TNEWS 精度 | MSRA_NER 性能 | MSRA_NER 精度 | CMRC2018 性能 | CMRC2018 精度 |
+| -------------------------- | ------------- | ------------ | ------------- | ------------- | ------------- | ------------- |
+| ERNIE 3.0-Medium+FP32 | 1123.85(1.0x) | 57.45 | 366.75(1.0x) | 93.04 | 146.84(1.0x) | 66.95 |
+| ERNIE 3.0-Medium+INT8 | 3226.26(2.9x) | 56.99(-0.46) | 889.33(2.4x) | 92.70(-0.34) | 348.84(2.4x) | 66.32(-0.63 |
+| ERNIE 3.0-Medium+裁剪+FP32 | 1424.01(1.3x) | 57.31(-0.14) | 454.27(1.2x) | 93.27(+0.23) | 183.77(1.3x) | 65.92(-1.03) |
+| ERNIE 3.0-Medium+裁剪+INT8 | 3635.48(3.2x) | 57.26(-0.19) | 1105.26(3.0x) | 93.20(+0.16) | 444.27(3.0x) | 66.17(-0.78) |
-```python
---width_mult_list
- 裁剪宽度保留的搜索列表,对 6 层模型推荐 `3/4` ,对 12 层模型推荐 `2/3`,表示对 `q`、`k`、`v` 以及 `ffn` 权重宽度的保留比例,假设 12 层模型原先有 12 个 attention heads,裁剪后只剩 9 个 attention heads。默认是 `[3/4]`;
+(以上数据来自 [ERNIE 3.0 性能测试文档](../model_zoo/ernie-3.0/#性能测试),文档包含测试环境介绍)
---per_device_train_batch_size
- 用于裁剪训练的每个 GPU/CPU 核心 的 batch 大小。默认是 8;
+
---per_device_eval_batch_size
- 用于裁剪评估的每个 GPU/CPU 核心 的 batch 大小。默认是 8;
+## 如何启动模型压缩
---num_train_epochs
- 裁剪训练所需要的 epochs 数。默认是 3.0;
+模型压缩 API 中的压缩功能依赖 `paddleslim` 包。可运行以下命令安装:
---max_steps
- 如果设置为正数,则表示要执行的训练步骤总数。
- 覆盖 `num_train_epochs`。默认为 -1;
+```shell
+pip install paddleslim
+```
---logging_steps
- 两个日志之间的更新步骤数。默认为 500;
+大致分为四步:
---save_steps
- 评估模型的步数。默认为 500;
+- Step 1: 获取 `CompressionArguments` 对象
+- Step 2: 实例化 Trainer 并调用 `compress()`
+- Step 3: 实现自定义评估函数和 loss 计算函数(可选)
+- Step 4:传参并运行压缩脚本
---optim
- 裁剪训练使用的优化器名称,默认为adamw,默认为 'adamw';
+**示例代码**
---learning_rate
- 裁剪训练使用优化器的初始学习率,默认为 5e-05;
+```python
+from paddlenlp.trainer import PdArgumentParser, CompressionArguments
---weight_decay
- 除了所有 bias 和 LayerNorm 权重之外,应用于所有层裁剪训练时的权重衰减数值。默认为 0.0;
+# Step1: 获取 CompressionArguments 对象
+parser = PdArgumentParser(CompressionArguments)
+compression_args = parser.parse_args_into_dataclasses()
---adam_beta1
- 裁剪训练使用 AdamW 的优化器时的 beta1 超参数。默认为 0.9;
+# Step2: 实例化 Trainer 并调用 compress()
+trainer = Trainer(
+ model=model,
+ args=compression_args,
+ data_collator=data_collator,
+ train_dataset=train_dataset,
+ eval_dataset=eval_dataset,
+ criterion=criterion)
---adam_beta2
- 裁剪训练使用 AdamW 优化器时的 beta2 超参数。默认为 0.999;
+# Step 3: 使用内置模型和评估方法,不需要实现自定义评估函数和 loss 计算函数
+trainer.compress()
+```
---adam_epsilon
- 裁剪训练使用 AdamW 优化器时的 epsilon 超参数。默认为 1e-8;
+```shell
+# Step4: 传参并运行压缩脚本
+python compress.py \
+ --output_dir ./compress_models \
+ --per_device_train_batch_size 32 \
+ --per_device_eval_batch_size 32 \
+ --num_train_epochs 4
+ --width_mult_list 0.75 \
+ --batch_size_list 4 8 16 \
+ --batch_num_list 1 \
---max_grad_norm
- 最大梯度范数(用于梯度裁剪)。默认为 1.0;
+```
---lr_scheduler_type
- 要使用的学习率调度策略。默认为 'linear';
---warmup_ratio
- 用于从 0 到 `learning_rate` 的线性 warmup 的总训练步骤的比例。默认为 0.0;
+
---warmup_steps
- 用于从 0 到 `learning_rate` 的线性 warmup 的步数。覆盖warmup_ratio 参数。默认是 0;
+### Step 1:获取 CompressionArguments 对象
---seed
- 设置的随机种子。为确保多次运行的可复现性。默认为 42;
---device
- 运行的设备名称。支持 cpu/gpu,默认为 'gpu';
+`CompressionArguments` 对象用于获取模型压缩参数,并传给 `Trainer` 对象。获取 `CompressionArguments` 对象的方法通常如下:
---remove_unused_columns
- 是否去除 Dataset 中不用的字段数据。默认是 True;
+```python
+from paddlenlp.trainer import PdArgumentParser, CompressionArguments
+# Step1: 获取 CompressionArguments 对象
+parser = PdArgumentParser(CompressionArguments)
+compression_args = parser.parse_args_into_dataclasses()
```
+
+### Step 2:实例化 Trainer 并调用 compress
-**PTQ 量化参数**
-
-当用户使用了 PTQ 量化策略时需要传入以下可选参数:
-
-```python
+
---algo_list
- 量化策略搜索列表,目前支持 'KL'、'abs_max'、'min_max'、'avg'、'hist'、'mse' 和 'emd',不同的策略计算量化比例因子的方法不同。建议传入多种策略,可批量得到由多种策略产出的多个量化模型,可从中选择效果最优模型。ERNIE 类模型较推荐 'hist', 'mse', 'KL','emd' 等策略。默认是 ['mse', 'KL'];
+#### Trainer 实例化参数介绍
---batch_num_list
- batch_nums 的超参搜索列表,batch_nums 表示采样需要的 batch 数。校准数据的总量是 batch_size * batch_nums。如 batch_num 为 None,则 data loader 提供的所有数据均会被作为校准数据。默认是 [1];
+- **--model** 待压缩的模型,目前支持 ERNIE 等模型,是在下游任务中微调后的模型。以分类任务为例,可通过`AutoModelForSequenceClassification.from_pretrained(model_name_or_path)` 等方式来获取,这种情况下,`model_name_or_path`目录下需要有 model_config.json, model_state.pdparams 文件;
+- **--data_collator** 三类任务均可使用 PaddleNLP 预定义好的 [DataCollator 类](../../paddlenlp/data/data_collator.py),`data_collator` 可对数据进行 `Pad` 等操作。使用方法参考 [示例代码](../model_zoo/ernie-3.0/compress_seq_cls.py) 即可;
+- **--train_dataset** 裁剪训练需要使用的训练集,是任务相关的数据。自定义数据集的加载可参考 [文档](https://huggingface.co/docs/datasets/loading);
+- **--eval_dataset** 裁剪训练使用的评估集,也是量化使用的校准数据,是任务相关的数据。自定义数据集的加载可参考 [文档](https://huggingface.co/docs/datasets/loading);
+- **--tokenizer** 模型 `model` 对应的 `tokenizer`,可使用 `AutoTokenizer.from_pretrained(model_name_or_path)` 来获取。
+- **--criterion** 模型的 loss 对象,是一个 nn.Layer 对象,用于在 ofa_utils.py 计算模型的 loss 用于计算梯度从而确定神经元相似度。
---batch_size_list
- 校准样本的 batch_size 搜索列表。并非越大越好,也是一个超参数,建议传入多种校准样本数,最后可从多个量化模型中选择最优模型。默认是 `[4]`;
+用以上参数实例化 Trainer 对象,之后直接调用 `compress()` 。`compress()` 会根据选择的策略进入不同的分支,以进行裁剪或者量化的过程。
---weight_quantize_type
- 权重的量化类型,支持 'abs_max' 和 'channel_wise_abs_max' 两种方式。通常使用 'channel_wise_abs_max', 这种方法得到的模型通常精度更高;
-
---round_type
- 权重值从 FP32 到 INT8 的转化方法,目前支持 'round' 和 '[adaround](https://arxiv.org/abs/2004.10568.)',默认是 'round';
-
---bias_correction
- 如果是 True,表示使用[bias correction](https://arxiv.org/abs/1810.05723)功能,默认为 False。
-```
-#### 1.2 获取 CompressionArguments 对象
+**示例代码**
```python
from paddlenlp.trainer import PdArgumentParser, CompressionArguments
+
+# Step1: 获取 CompressionArguments 对象
parser = PdArgumentParser(CompressionArguments)
compression_args = parser.parse_args_into_dataclasses()
-```
-
-### 2. 实例化 Trainer
-
-#### 2.1 Trainer 实例化参数介绍
-
-```python
---model
- 待压缩的模型,目前支持 ERNIE 等模型,是在下游任务中微调后的模型。以 seq_cls 任务为例,可通过`AutoModelForSequenceClassification.from_pretrained(model_name_or_path)` 等方式来获取,这种情况下,`model_name_or_path`目录下需要有 model_config.json, model_state.pdparams 文件;
---data_collator
- 三类任务均可使用 PaddleNLP 预定义好的[DataCollator 类](../../paddlenlp/data/data_collator.py),`data_collator` 可对数据进行 `Pad` 等操作。使用方法参考[示例代码](../model_zoo/ernie-3.0/compress_seq_cls.py)即可;
---train_dataset
- 裁剪训练需要使用的训练集;
---eval_dataset
- 裁剪训练使用的评估集,也是量化使用的校准数据;
---tokenizer
- 模型 `model`对应的 `tokenizer`,可使用 `AutoTokenizer.from_pretrained(model_name_or_path)` 来获取。
-```
-**示例代码**
-
-```python
+# Step2: 实例化 Trainer 并调用 compress()
trainer = Trainer(
model=model,
args=compression_args,
@@ -153,48 +137,33 @@ trainer = Trainer(
train_dataset=train_dataset,
eval_dataset=eval_dataset,
criterion=criterion)
-```
-
-
-### 3. 调用 compress()
-Trainer 只需直接调用 `compress()` 即可,可以通过传入命令行参数来控制模型压缩的一些超参数:
-
-```python
trainer.compress()
```
-### 4. 运行压缩脚本
+
-这一步主要是将压缩需要用到的参数通过命令行传入,并启动压缩脚本。
+### Step3:实现自定义评估函数和 loss 计算函数(可选)
-```shell
-python compress.py \
- --dataset "clue cluewsc2020" \
- --model_name_or_path best_models/CLUEWSC2020 \
- --output_dir ./compress_models \
- --per_device_train_batch_size 32 \
- --per_device_eval_batch_size 32 \
- --num_train_epochs 4
- --width_mult_list 0.75 \
- --batch_size_list 4 8 16 \
- --batch_num_list 1 \
+当使用 DynaBERT 裁剪功能时,如果模型、Metrics 不符合下表的情况,那么模型压缩 API 中自带的评估函数和计算 loss 的参数可能需要自定义。
-```
+目前 DynaBERT 裁剪功能只支持 SequenceClassification 等三类 PaddleNLP 内置 class,并且内置评估器对应为 Accuracy、F1、Squad。
-### 5. 压缩自定义 ERNIE 类模型(按需可选)
+| Model class name | SequenceClassification | TokenClassification | QuestionAnswering |
+| ---------------- | ------------------------- | --------------------- | ----------------- |
+| Metrics | Accuracy | F1 | Squad |
-如果使用 DynaBERT 裁剪功能,并且待压缩的模型是自定义模型,即非 PaddleNLP 定义的模型,还需要满足以下三个条件:
+需要注意以下三个条件:
-- 能够通过 `from_pretrained()` 导入模型,且只含 `pretrained_model_name_or_path` 一个必选参数;
+- 如果模型是自定义模型,模型需要支持调用 `from_pretrained()` 导入模型,且只含 `pretrained_model_name_or_path` 一个必选参数,`forward` 函数返回 `logits` 或者 `tuple of logits`;
-- 实现自定义 `custom_dynabert_evaluate` 评估函数,需要同时支持 `paddleslim.nas.ofa.OFA` 模型和 `paddle.nn.layer` 模型。可参考下方示例代码;
- - 输入`model` 和 `dataloader`,返回模型的评价指标(单个 float 值)。
- - 将该函数传入 `compress()` 中的 `custom_dynabert_evaluate` 参数;
-- 实现自定义 `custom_dynabert_calc_loss` 函数。便于反向传播计算梯度,从而计算神经元的重要性以便后续裁剪使用。可参考下方示例代码;
- - 输入每个batch的数据,返回模型的loss。
+- 如果模型是自定义模型,或者数据集比较特殊,压缩 API 中 loss 的计算不符合使用要求,需要自定义 `custom_dynabert_calc_loss` 函数。计算 loss 后计算梯度,从而得出计算神经元的重要性以便裁剪使用。可参考下方示例代码。
+ - 输入每个 batch 的数据,返回模型的 loss。
- 将该函数传入 `compress()` 中的 `custom_dynabert_calc_loss` 参数;
+- 如果评估器也不满足上述所支持情况,需实现自定义 `custom_dynabert_evaluate` 评估函数,需要同时支持 `paddleslim.nas.ofa.OFA` 模型和 `paddle.nn.layer` 模型。可参考下方示例代码。
+ - 输入`model` 和 `dataloader`,返回模型的评价指标(单个 float 值)。
+ - 将该函数传入 `compress()` 中的 `custom_dynabert_evaluate` 参数;
`custom_dynabert_evaluate()` 函数定义示例:
@@ -237,23 +206,118 @@ def calc_loss(loss_fct, model, batch, head_mask):
```python
trainer.compress(custom_dynabert_evaluate=evaluate_seq_cls,
- custom_dynabert_calc_loss=calc_loss
- )
+ custom_dynabert_calc_loss=calc_loss)
+```
+
+
+
+
+### Step 4:传参并运行压缩脚本
+
+这一步主要是将压缩需要用到的参数通过命令行传入,并启动压缩脚本。
+
+压缩启动命令:
+
+**示例代码**
+
+```shell
+# Step4: 运行压缩脚本
+python compress.py \
+ --output_dir ./compress_models \
+ --per_device_train_batch_size 32 \
+ --per_device_eval_batch_size 32 \
+ --num_train_epochs 4
+ --width_mult_list 0.75 \
+ --batch_size_list 4 8 16 \
+ --batch_num_list 1 \
+
```
-## 二、压缩 API 使用 TIPS
+下面会介绍模型压缩启动命令可以传递的超参数。
+
+
+
+#### CompressionArguments 参数介绍
-1. 模型压缩主要用于推理加速,因此压缩后的模型都是静态图模型,不能再通过 `from_pretrained()` API 导入继续训练;
+`CompressionArguments` 中的参数一部分是模型压缩功能特定参数,另一部分继承自 `TrainingArguments`,是压缩训练时需要设置的超参数。下面会进行具体介绍,
+
+**公共参数**
+
+公共参数中的参数和具体的压缩策略无关。
+
+- **--strategy** 模型压缩策略,目前支持 `'dynabert+ptq'`、 `'dynabert'` 和 `'ptq'`。
+其中 `'dynabert'` 代表基于 DynaBERT 的宽度裁剪策略,`'ptq'` 表示静态离线量化, `'dynabert+ptq'` 代表先裁剪后量化。默认是 `'dynabert+ptq'`;
+
+- **--output_dir** 模型压缩后模型保存目录;
+
+- **--input_infer_model_path** 待压缩的静态图模型,该参数是为了支持对静态图模型的压缩。不需使用时可忽略。默认为 `None`;
+
+**DynaBERT 裁剪参数**
+
+当用户使用了 DynaBERT 裁剪策略时需要传入以下可选参数:
-2. 压缩 API `compress()` 默认会启动裁剪和量化,用户可以设置参数 `--strategy` 来选择压缩的策略。目前裁剪策略有训练过程,需要下游任务的训练数据,其训练时间视下游任务数据量而定,且和微调的训练时间是一个量级。量化则不需要额外的训练,更快,通常来说量化的加速比比裁剪更明显。建议裁剪和量化同时选择,有些情况下可能比单独量化效果更好;
+- **--width_mult_list** 裁剪宽度保留的搜索列表,对 6 层模型推荐 `3/4` ,对 12 层模型推荐 `2/3`,表示对 `q`、`k`、`v` 以及 `ffn` 权重宽度的保留比例,假设 12 层模型原先有 12 个 attention heads,裁剪后只剩 9 个 attention heads。默认是 `[3/4]`;
-3. DynaBERT 裁剪类似蒸馏过程,方便起见,可以直接使用微调时的超参。如果想要进一步提升精度,可以对 `batch_size`、`learning_rate`、`epoch` 等超参进行 Grid Search;
+- **--per_device_train_batch_size** 用于裁剪训练的每个 GPU/CPU 核心 的 batch 大小。默认是 8;
+
+- **--per_device_eval_batch_size** 用于裁剪评估的每个 GPU/CPU 核心 的 batch 大小。默认是 8;
+
+- **--num_train_epochs** 裁剪训练所需要的 epochs 数。默认是 3.0;
+
+- **--max_steps** 如果设置为正数,则表示要执行的训练步骤总数。覆盖 `num_train_epochs`。默认为 -1;
+
+- **--logging_steps** 两个日志之间的更新步骤数。默认为 500;
+
+- **--save_steps** 评估模型的步数。默认为 500;
+
+- **--optim** 裁剪训练使用的优化器名称,默认为adamw,默认为 'adamw';
+
+- **--learning_rate** 裁剪训练使用优化器的初始学习率,默认为 5e-05;
+
+- **--weight_decay** 除了所有 bias 和 LayerNorm 权重之外,应用于所有层裁剪训练时的权重衰减数值。 默认为 0.0;
+
+- **--adam_beta1** 裁剪训练使用 AdamW 的优化器时的 beta1 超参数。默认为 0.9;
+
+- **--adam_beta2** 裁剪训练使用 AdamW 优化器时的 beta2 超参数。默认为 0.999;
+
+- **--adam_epsilon** 裁剪训练使用 AdamW 优化器时的 epsilon 超参数。默认为 1e-8;
+
+- **--max_grad_norm** 最大梯度范数(用于梯度裁剪)。默认为 1.0;
+
+- **--lr_scheduler_type** 要使用的学习率调度策略。默认为 'linear';
+
+- **--warmup_ratio** 用于从 0 到 `learning_rate` 的线性 warmup 的总训练步骤的比例。 默认为 0.0;
+
+- **--warmup_steps** 用于从 0 到 `learning_rate` 的线性 warmup 的步数。覆盖 warmup_ratio 参数。默认是 0;
+
+- **--seed** 设置的随机种子。为确保多次运行的可复现性。默认为 42;
+
+- **--device** 运行的设备名称。支持 cpu/gpu。默认为 'gpu';
+
+- **--remove_unused_columns** 是否去除 Dataset 中不用的字段数据。默认是 True;
+
+**PTQ 量化参数**
+
+当用户使用了 PTQ 量化策略时需要传入以下可选参数:
-4. 使用量化时,`eval_dataset` 不可以是 `TensorDataset` 对象,因为量化功能内部在静态图模式下执行,而 `TensorDataset` 只能在动态图下使用,两者同时使用会导致错误;
+- **--algo_list** 量化策略搜索列表,目前支持 `'KL'`、`'abs_max'`、`'min_max'`、`'avg'`、`'hist'`、`'mse'` 和 `'emd'`,不同的策略计算量化比例因子的方法不同。建议传入多种策略,可批量得到由多种策略产出的多个量化模型,可从中选择效果最优模型。ERNIE 类模型较推荐 `'hist'`, `'mse'`, `'KL'`,`'emd'` 等策略。默认是 ['mse', 'KL'];
-## 三、压缩 API 使用案例
+- **--batch_num_list** batch_nums 的超参搜索列表,batch_nums 表示采样需要的 batch 数。校准数据的总量是 batch_size * batch_nums。如 batch_num 为 None,则 data loader 提供的所有数据均会被作为校准数据。默认是 [1];
-本项目提供了压缩 API 在分类(包含文本分类、文本匹配、自然语言推理、代词消歧等任务)、序列标注、阅读理解三大场景下的使用样例,可以分别参考 [ernie-3.0](../model_zoo/ernie-3.0/) 目录下的 `compress_seq_cls.py` 、`compress_token_cls.py`、`compress_qa.py` 脚本,启动方式如下:
+- **--batch_size_list** 校准样本的 batch_size 搜索列表。并非越大越好,也是一个超参数,建议传入多种校准样本数,最后可从多个量化模型中选择最优模型。默认是 `[4]`;
+
+- **--weight_quantize_type** 权重的量化类型,支持 'abs_max' 和 'channel_wise_abs_max' 两种方式。通常使用 'channel_wise_abs_max', 这种方法得到的模型通常精度更高;
+
+- **--round_type** 权重值从 FP32 到 INT8 的转化方法,目前支持 'round' 和 '[adaround](https://arxiv.org/abs/2004.10568.)',默认是 'round';
+
+- **--bias_correction** 如果是 True,表示使用 [bias correction](https://arxiv.org/abs/1810.05723) 功能,默认为 False。
+
+
+
+
+### 三大场景模型压缩 API 使用示例
+
+本项目提供了压缩 API 在分类(包含文本分类、文本匹配、自然语言推理、代词消歧等任务)、序列标注、抽取式阅读理解三大场景下的使用样例,可以分别参考 [ERNIE 3.0](../model_zoo/ernie-3.0/) 目录下的 [compress_seq_cls.py](../model_zoo/ernie-3.0/compress_seq_cls.py) 、[compress_token_cls.py](../model_zoo/ernie-3.0/compress_token_cls.py)、[compress_qa.py](../model_zoo/ernie-3.0/compress_qa.py) 脚本,启动方式如下:
```shell
# 分类任务
@@ -288,3 +352,75 @@ python compress_qa.py \
--max_answer_length 50 \
```
+
+示例代码中压缩使用的是 datasets 内置的数据集,若想要使用自定义数据集压缩,可参考 [datasets 加载自定义数据集文档](https://huggingface.co/docs/datasets/loading)。
+
+
+
+## 模型部署与评价
+
+裁剪、量化后的模型不能再通过 `from_pretrained` 导入进行预测,而是需要使用 Paddle 部署工具才能完成预测。
+
+压缩后的模型部署可以参考 [部署文档](../model_zoo/ernie-3.0/deploy) 完成。
+
+### Python 部署
+
+服务端部署可以从这里开始。可以利用 [预测 backend 脚本](https://github.com/PaddlePaddle/PaddleNLP/blob/develop/model_zoo/ernie-3.0/deploy/python/ernie_predictor.py),并参考 [infer_cpu.py](https://github.com/PaddlePaddle/PaddleNLP/blob/develop/model_zoo/ernie-3.0/deploy/python/infer_cpu.py) 或者 [infer_gpu.py](https://github.com/PaddlePaddle/PaddleNLP/blob/develop/model_zoo/ernie-3.0/deploy/python/infer_gpu.py) 来编写自己的预测脚本。并根据 [Python 部署指南](https://github.com/PaddlePaddle/PaddleNLP/tree/develop/model_zoo/ernie-3.0/deploy/python) 的介绍安装预测环境,对压缩后的模型进行精度评估、性能测试以及部署。
+
+
+
+
+### 服务化部署
+
+- [Triton Inference Server 服务化部署指南](../model_zoo/ernie-3.0/deploy/triton/README.md)
+- [Paddle Serving 服务化部署指南](../model_zoo/ernie-3.0/deploy/serving/README.md)
+
+
+
+### Paddle2ONNX 部署
+
+ONNX 导出及 ONNXRuntime 部署请参考:[ONNX 导出及 ONNXRuntime 部署指南](./deploy/paddle2onnx/README.md)
+
+
+### Paddle Lite 移动端部署
+
+即将支持,敬请期待
+
+
+
+
+## FAQ
+
+**Q:模型压缩需要数据吗?**
+
+A:裁剪过程类似微调,需要使用训练集进行训练,验证集进行评估,量化需要验证集(对样本量要求较低,一般 4-16 个样本就可能可以满足要求);
+
+**Q:示例代码里是内置的数据集,如何使用我自己的数据呢**
+
+A:可以参考 [datasets 加载自定义数据集文档](https://huggingface.co/docs/datasets/loading);
+
+**Q:模型压缩后的模型还能继续训练吗?**
+
+A:模型压缩主要用于推理加速,因此压缩后的模型都是静态图(预测)模型,不能再通过 `from_pretrained()` API 导入继续训练;
+
+**Q:裁剪和量化怎么选?**
+
+A:可以设置参数 `--strategy` 来选择压缩的策略,默认是裁剪和量化同时选择,先裁剪后量化。目前裁剪策略有训练过程,需要下游任务的训练数据,其训练时间视下游任务数据量而定,且和微调的训练时间是一个量级。静态离线量化则不需要额外的训练,更快,通常来说量化的加速比比裁剪更明显。建议裁剪和量化同时选择,有些情况下可能比单独量化效果更好;
+
+**Q:裁剪中也有训练过程吗?**
+
+A:DynaBERT 裁剪类似蒸馏过程,也会有模型训练时用到的超参,方便起见,可以直接使用微调时所用的最佳的超参。如果想进一步提升精度,可以对 `batch_size`、`learning_rate`、`epoch` 等超参数进行 Grid Search;
+
+**Q:使用 `TensorDataset` 对象做量化报错了,为什么?**
+
+A:使用量化时,`eval_dataset` 不可以是 `TensorDataset` 对象,因为量化功能内部在静态图模式下执行,而 `TensorDataset` 只能在动态图下使用,两者同时使用会导致错误;
+
+
+
+## 参考文献
+
+1.Hou L, Huang Z, Shang L, Jiang X, Chen X and Liu Q. DynaBERT: Dynamic BERT with Adaptive Width and Depth[J]. arXiv preprint arXiv:2004.04037, 2020.
+
+2.Cai H, Gan C, Wang T, Zhang Z, and Han S. Once for all: Train one network and specialize it for efficient deployment[J]. arXiv preprint arXiv:1908.09791, 2020.
+
+3.Wu H, Judd P, Zhang X, Isaev M and Micikevicius P. Integer Quantization for Deep Learning Inference: Principles and Empirical Evaluation[J]. arXiv preprint arXiv:2004.09602v1, 2020.
diff --git a/model_zoo/ernie-3.0/README.md b/model_zoo/ernie-3.0/README.md
index 9fb0663367ca..02563da36790 100644
--- a/model_zoo/ernie-3.0/README.md
+++ b/model_zoo/ernie-3.0/README.md
@@ -1356,12 +1356,12 @@ python compress_qa.py \
--dataset "clue cmrc2018" \
--model_name_or_path best_models/CMRC2018 \
--output_dir ./ \
+ --max_answer_length 50 \
--max_seq_length 512 \
--learning_rate 0.00003 \
--num_train_epochs 8 \
--per_device_train_batch_size 24 \
--per_device_eval_batch_size 24 \
- --max_answer_length 50 \
```
@@ -1504,20 +1504,20 @@ AutoTokenizer.from_pretrained("ernie-3.0-medium-zh", use_faster=True)
### Python 部署
-Python部署请参考:[Python部署指南](./deploy/python/README.md)
+Python部署请参考:[Python 部署指南](./deploy/python/README.md)
### 服务化部署
-- [Triton Inference Server服务化部署指南](./deploy/triton/README.md)
-- [Paddle Serving服务化部署指南](./deploy/serving/README.md)
+- [Triton Inference Server 服务化部署指南](./deploy/triton/README.md)
+- [Paddle Serving 服务化部署指南](./deploy/serving/README.md)
### Paddle2ONNX 部署
-ONNX 导出及 ONNXRuntime 部署请参考:[ONNX导出及ONNXRuntime部署指南](./deploy/paddle2onnx/README.md)
+ONNX 导出及 ONNXRuntime 部署请参考:[ONNX 导出及 ONNXRuntime 部署指南](./deploy/paddle2onnx/README.md)
### Paddle Lite 移动端部署
diff --git a/paddlenlp/trainer/compression_args.py b/paddlenlp/trainer/compression_args.py
index c4fcc38bdb2e..22c25090bea6 100644
--- a/paddlenlp/trainer/compression_args.py
+++ b/paddlenlp/trainer/compression_args.py
@@ -50,7 +50,7 @@ class CompressionArguments(TrainingArguments):
},
)
# dynabert
- width_mult_list: Optional[List[float]] = field(
+ width_mult_list: Optional[List[str]] = field(
default=None,
metadata={
"help":
diff --git a/paddlenlp/trainer/trainer_compress.py b/paddlenlp/trainer/trainer_compress.py
index 8b9f3f02f537..3cabd6600662 100644
--- a/paddlenlp/trainer/trainer_compress.py
+++ b/paddlenlp/trainer/trainer_compress.py
@@ -49,6 +49,10 @@ def compress(self,
args = self.args
if "dynabert" in args.strategy:
try_import('paddleslim')
+ if self.args.width_mult_list is not None:
+ self.args.width_mult_list = [
+ eval(width_mult) for width_mult in self.args.width_mult_list
+ ]
self.custom_dynabert_evaluate = custom_dynabert_evaluate
self.custom_dynabert_calc_loss = custom_dynabert_calc_loss
class_name = self.model.__class__.__name__
@@ -441,7 +445,7 @@ def evaluate_token_cls(model, data_loader):
return ofa_model
for idx, width_mult in enumerate(self.args.width_mult_list):
- logger.info("Best acc of width_mult %s: %.4f" %
+ logger.info("Best result of width_mult %s: %.4f" %
(width_mult, best_acc[idx]))
return ofa_model
@@ -568,9 +572,9 @@ def auto_model_forward(self,
"You cannot specify both input_ids and inputs_embeds at the same time."
)
elif input_ids is not None:
- input_shape = input_ids.shape
+ input_shape = paddle.shape(input_ids)
elif inputs_embeds is not None:
- input_shape = inputs_embeds.shape[:-1]
+ input_shape = paddle.shape(inputs_embeds)[:-1]
else:
raise ValueError(
"You have to specify either input_ids or inputs_embeds")
diff --git a/paddlenlp/transformers/ofa_utils.py b/paddlenlp/transformers/ofa_utils.py
index 12a65b53a29a..f1df4fead0d5 100644
--- a/paddlenlp/transformers/ofa_utils.py
+++ b/paddlenlp/transformers/ofa_utils.py
@@ -266,11 +266,12 @@ def calc_loss(loss_fct, model, batch, head_mask):
logits = model(batch["input_ids"],
batch["token_type_ids"],
attention_mask=[None, head_mask])
- if "QuestionAnswering" in model.__class__.__name__:
+ class_name = model.__class__.__name__
+ if "QuestionAnswering" in class_name:
start_logits, end_logits = logits
loss = (loss_fct(start_logits, batch["start_positions"]) +
loss_fct(end_logits, batch["end_positions"])) / 2
- elif "TokenClassification" in model.__class__.__name__ or "SequenceClassification" in model.__class__.__name__:
+ elif "TokenClassification" in class_name or "SequenceClassification" in class_name:
loss = loss_fct(logits, batch["labels"])
else:
raise NotImplementedError(
@@ -336,7 +337,7 @@ def compute_neuron_head_importance(model,
for w in intermediate_weight:
neuron_importance.append(np.zeros(shape=[w.shape[1]], dtype='float32'))
- for batch in data_loader:
+ for i, batch in enumerate(data_loader):
if custom_dynabert_calc_loss is not None:
loss = custom_dynabert_calc_loss(loss_fct, model, batch, head_mask)
else:
From e22121511c9204d25a7a1cd7d27fc0bd0e8a920b Mon Sep 17 00:00:00 2001
From: LiuChiachi <709153940@qq.com>
Date: Wed, 14 Sep 2022 13:40:40 +0000
Subject: [PATCH 2/4] update compression doc
---
docs/compression.md | 12 ++++++------
paddlenlp/trainer/trainer_compress.py | 8 ++++----
2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/docs/compression.md b/docs/compression.md
index 73e6e0e9d855..69de40f8b90c 100644
--- a/docs/compression.md
+++ b/docs/compression.md
@@ -117,7 +117,7 @@ compression_args = parser.parse_args_into_dataclasses()
#### Trainer 实例化参数介绍
-- **--model** 待压缩的模型,目前支持 ERNIE 类模型或者自定义模型,是在下游任务中微调后的模型,当预训练模型选择 ERNIE 时,需要继承 `ErniePretrainedModel`。以分类任务为例,可通过`AutoModelForSequenceClassification.from_pretrained(model_name_or_path)` 等方式来获取,这种情况下,`model_name_or_path`目录下需要有 model_config.json, model_state.pdparams 文件;
+- **--model** 待压缩的模型,目前支持 ERNIE、BERT、ERNIE-M、TinyBERT 等结构相似的模型,是在下游任务中微调后的模型,当预训练模型选择 ERNIE 时,需要继承 `ErniePretrainedModel`。以分类任务为例,可通过`AutoModelForSequenceClassification.from_pretrained(model_name_or_path)` 等方式来获取,这种情况下,`model_name_or_path`目录下需要有 model_config.json, model_state.pdparams 文件;
- **--data_collator** 三类任务均可使用 PaddleNLP 预定义好的 [DataCollator 类](../../paddlenlp/data/data_collator.py),`data_collator` 可对数据进行 `Pad` 等操作。使用方法参考 [示例代码](../model_zoo/ernie-3.0/compress_seq_cls.py) 即可;
- **--train_dataset** 裁剪训练需要使用的训练集,是任务相关的数据。自定义数据集的加载可参考 [文档](https://huggingface.co/docs/datasets/loading)。不启动裁剪时,可以为 None;
- **--eval_dataset** 裁剪训练使用的评估集,也是量化使用的校准数据,是任务相关的数据。自定义数据集的加载可参考 [文档](https://huggingface.co/docs/datasets/loading)。是 Trainer 的必选参数;
@@ -184,8 +184,8 @@ trainer.compress()
model.eval()
metric.reset()
for batch in data_loader:
- logits = model(batch['input_ids'],
- batch['token_type_ids'],
+ logits = model(input_ids=batch['input_ids'],
+ token_type_ids=batch['token_type_ids'],
#必须写这一行
attention_mask=[None, None])
# Supports paddleslim.nas.ofa.OFA model and nn.layer model.
@@ -203,9 +203,9 @@ trainer.compress()
```python
def calc_loss(loss_fct, model, batch, head_mask):
- logits = model(batch["input_ids"],
- batch["token_type_ids"],
- # 必须写这一行
+ logits = model(input_ids=batch["input_ids"],
+ token_type_ids=batch["token_type_ids"],
+ # 必须写下面这行
attention_mask=[None, head_mask])
loss = loss_fct(logits, batch["labels"])
return loss
diff --git a/paddlenlp/trainer/trainer_compress.py b/paddlenlp/trainer/trainer_compress.py
index e522e138a387..52b0e9dbafbd 100644
--- a/paddlenlp/trainer/trainer_compress.py
+++ b/paddlenlp/trainer/trainer_compress.py
@@ -291,8 +291,8 @@ def evaluate_qa(model, data_loader):
all_start_logits = []
all_end_logits = []
for batch in data_loader:
- logits = model(batch['input_ids'],
- batch['token_type_ids'],
+ logits = model(input_ids=batch['input_ids'],
+ token_type_ids=batch['token_type_ids'],
attention_mask=[None, None])
if isinstance(model, OFA):
start_logits_tensor, end_logits_tensor = logits[0]
@@ -341,8 +341,8 @@ def evaluate_token_cls(model, data_loader):
model.eval()
metric.reset()
for batch in data_loader:
- logits = model(batch['input_ids'],
- batch['token_type_ids'],
+ logits = model(input_ids=batch['input_ids'],
+ token_type_ids=batch['token_type_ids'],
attention_mask=[None, None])
if isinstance(model, OFA):
logits = logits[0]
From 5fcd4970bb0d52fd5c6987fa9168b4a1793b8d6b Mon Sep 17 00:00:00 2001
From: LiuChiachi <709153940@qq.com>
Date: Thu, 15 Sep 2022 08:06:29 +0000
Subject: [PATCH 3/4] support more models and update compression api
---
docs/compression.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/compression.md b/docs/compression.md
index 69de40f8b90c..a339c9b7238a 100644
--- a/docs/compression.md
+++ b/docs/compression.md
@@ -117,7 +117,7 @@ compression_args = parser.parse_args_into_dataclasses()
#### Trainer 实例化参数介绍
-- **--model** 待压缩的模型,目前支持 ERNIE、BERT、ERNIE-M、TinyBERT 等结构相似的模型,是在下游任务中微调后的模型,当预训练模型选择 ERNIE 时,需要继承 `ErniePretrainedModel`。以分类任务为例,可通过`AutoModelForSequenceClassification.from_pretrained(model_name_or_path)` 等方式来获取,这种情况下,`model_name_or_path`目录下需要有 model_config.json, model_state.pdparams 文件;
+- **--model** 待压缩的模型,目前支持 ERNIE、BERT、RoBERTa、ERNIE-M、ERNIE-Gram、PP-MiniLM、TinyBERT 等结构相似的模型,是在下游任务中微调后的模型,当预训练模型选择 ERNIE 时,需要继承 `ErniePretrainedModel`。以分类任务为例,可通过`AutoModelForSequenceClassification.from_pretrained(model_name_or_path)` 等方式来获取,这种情况下,`model_name_or_path`目录下需要有 model_config.json, model_state.pdparams 文件;
- **--data_collator** 三类任务均可使用 PaddleNLP 预定义好的 [DataCollator 类](../../paddlenlp/data/data_collator.py),`data_collator` 可对数据进行 `Pad` 等操作。使用方法参考 [示例代码](../model_zoo/ernie-3.0/compress_seq_cls.py) 即可;
- **--train_dataset** 裁剪训练需要使用的训练集,是任务相关的数据。自定义数据集的加载可参考 [文档](https://huggingface.co/docs/datasets/loading)。不启动裁剪时,可以为 None;
- **--eval_dataset** 裁剪训练使用的评估集,也是量化使用的校准数据,是任务相关的数据。自定义数据集的加载可参考 [文档](https://huggingface.co/docs/datasets/loading)。是 Trainer 的必选参数;
From 1d10df17d1f5e639f780796eace22d470cb1e4de Mon Sep 17 00:00:00 2001
From: LiuChiachi <709153940@qq.com>
Date: Thu, 15 Sep 2022 08:52:17 +0000
Subject: [PATCH 4/4] update inputspec info, avoid error
---
paddlenlp/trainer/trainer_compress.py | 33 ++++++++++++++++++---------
1 file changed, 22 insertions(+), 11 deletions(-)
diff --git a/paddlenlp/trainer/trainer_compress.py b/paddlenlp/trainer/trainer_compress.py
index 92d53666183e..b2657435681c 100644
--- a/paddlenlp/trainer/trainer_compress.py
+++ b/paddlenlp/trainer/trainer_compress.py
@@ -78,12 +78,19 @@ def compress(self,
else:
# Prefix of `export_model` is 'model'
self.args.input_filename_prefix = "model"
- input_spec = [
- paddle.static.InputSpec(shape=[None, None],
- dtype="int64"), # input_ids
- paddle.static.InputSpec(shape=[None, None], dtype="int64")
- if "token_type_ids" in self.train_dataset[0] else None
- ]
+ if 'token_type_ids' in self.train_dataset[0]:
+ input_spec = [
+ paddle.static.InputSpec(shape=[None, None],
+ dtype="int64"), # input_ids
+ paddle.static.InputSpec(shape=[None, None],
+ dtype="int64") # token_type_ids
+ ]
+ else:
+ input_spec = [
+ paddle.static.InputSpec(shape=[None, None],
+ dtype="int64") # input_ids
+ ]
+
input_dir = args.output_dir
export_model(model=self.model,
input_spec=input_spec,
@@ -479,11 +486,15 @@ def _dynabert_export(self, ofa_model):
for name, sublayer in origin_model_new.named_sublayers():
if isinstance(sublayer, paddle.nn.MultiHeadAttention):
sublayer.num_heads = int(width_mult * sublayer.num_heads)
- input_shape = [
- paddle.static.InputSpec(shape=[None, None], dtype='int64'),
- paddle.static.InputSpec(shape=[None, None], dtype='int64')
- if 'token_type_ids' in self.train_dataset[0] else None
- ]
+ if 'token_type_ids':
+ input_shape = [
+ paddle.static.InputSpec(shape=[None, None], dtype='int64'),
+ paddle.static.InputSpec(shape=[None, None], dtype='int64')
+ ]
+ else:
+ input_shape = [
+ paddle.static.InputSpec(shape=[None, None], dtype='int64')
+ ]
pruned_infer_model_dir = os.path.join(model_dir, "pruned_model")
net = paddle.jit.to_static(origin_model_new, input_spec=input_shape)