PaddleNLP 模型压缩实战指南:从 BERT 蒸馏到 Bi-LSTM 与 DynaBERT 宽度压缩
2026/9/23 10:52:25 网站建设 项目流程

PaddleNLP 模型压缩实战指南:从 BERT 蒸馏到 Bi-LSTM 与 DynaBERT 宽度压缩

【免费下载链接】PaddleNLPEasy-to-use and powerful LLM and SLM library with awesome model zoo.项目地址: https://gitcode.com/gh_mirrors/pa/PaddleNLP

本文基于 docs/zh/advanced_guide/model_compression 系列文档,系统讲解 PaddleNLP 中两类经典模型压缩路线:由 BERT 到 Bi-LSTM 的知识蒸馏基于 DynaBERT 策略的 BERT 宽度压缩。读完本文,你将掌握模型裁剪、量化和蒸馏的基本原理,能够复现从 BERT 微调、小模型单独训练到最终蒸馏的全流程,并了解如何借助 PaddleSlim 的 OFA(Once-For-All)超网络在推理阶段直接裁剪 BERT 宽度而不损失精度。

一、模型压缩概述

近些年,基于 Transformer 的语言模型在机器翻译、阅读理解、文本匹配、自然语言推理等自然语言处理任务上取得了实质性进展。然而,海量的参数和计算资源的大量耗费,使 BERT 及其变体在部署中困难重重。模型压缩的发展使得这些问题得到了缓解。

模型压缩在保证一定精度的情况下,能够降低模型的存储、加速模型的推理时间。常见的模型压缩方法主要包括模型裁剪(Pruning)量化(Quantization)蒸馏(Distillation)三种,下文分别介绍。

1. 模型裁剪

模型裁剪是通过对已经训练好的模型中不重要的网络连接进行裁剪,减少模型的冗余和计算量,从而减少网络存储、大幅度进行加速的模型压缩方法。裁剪的对象可以是神经元、注意力头(Head)或整个层,其核心难点在于如何判断"哪些部分不重要",DynaBERT 教程中利用参数梯度与参数大小计算重要性的做法正是这一思路的典型实现。

2. 量化

一般而言,神经网络模型的参数都用 32 bit 长度的浮点型数表示。实际上,有时不需要保留那么高的精度,可以通过量化方法减少模型的存储空间,通常用 INT8 代替 Float32 存储。例如,SGD(Stochastic Gradient Descent)所需要的精度仅为 6~8 bit,因此合理的量化网络也可在保证精度的情况下减小模型的存储体积,并且能够大幅度加速,使得神经网络在 CPU 上的运行成为可能。通常,量化包含多种方法,例如:二值神经网络、三元权重网络以及 XNOR 网络。

3. 蒸馏

蒸馏的本质是 student 模型(参数量较少的模型)对 teacher 模型(参数量较多的模型)的拟合,student 模型从 teacher 模型中学到知识,比自己单独学习效果更好。比较常见的方法通常是由 BERT-base 蒸馏到 Bi-LSTM 或 Transformer 层数更少的 BERT 小模型。例如 DistilBERT 保留了 BERT-base 97% 的精度,减少了 40% 的参数,推理速度快了 60%。

4. 模型压缩示例总览

本系列文档介绍了两个基于飞桨实现的常见模型压缩示例:

  • 《由BERT到Bi-LSTM的知识蒸馏》:可以作为蒸馏实验的 "Hello World" 示例,完整走通"微调教师模型 → 训练学生模型 → 蒸馏"三步流程,对应仓库 slm/examples/model_compression/distill_lstm;
  • 《使用DynaBERT中的策略对BERT进行压缩》:使用 DynaBERT 策略同时对不同尺寸的子网络进行训练,训练完成后可在推理阶段直接对模型裁剪,对应仓库 slm/examples/model_compression/ofa。

下面分别展开两篇教程的完整实操流程。

二、由 BERT 到 Bi-LSTM 的知识蒸馏

本例将特定任务下 BERT 模型的知识蒸馏到基于 Bi-LSTM 的小模型中,主要参考论文Distilling Task-Specific Knowledge from BERT into Simple Neural Networks实现。完整实验代码位于 slm/examples/model_compression/distill_lstm,目录结构如下:

distill_lstm/ ├── small.py # 小模型结构以及对小模型单独训练的脚本 ├── bert_distill.py # 用教师模型 BERT 蒸馏学生模型的蒸馏脚本 ├── data.py # 定义了 dataloader 等数据读取接口 ├── utils.py # 定义了将样本转成 id 的转换接口 ├── args.py # 参数配置脚本 └── README.md # 文档

1. 整体原理

  1. 在本例中,较大的模型 BERT 被称为教师模型,Bi-LSTM 被称为学生模型
  2. 小模型学习大模型的知识,需要学习蒸馏相关的损失函数。在本实验中,损失函数是均方误差损失函数(MSE),传入函数的两个参数分别是学生模型的输出和教师模型的输出。
  3. 在论文的模型蒸馏阶段,作者为了让教师模型表达出更多的"暗知识"(dark knowledge,通常指分类任务中低概率类别与高概率类别的关系)供学生模型学习,对训练数据进行了数据增强。通过数据增强可以产生更多无标签的训练数据,在训练过程中,学生模型可借助教师模型的"暗知识"在更大的数据集上进行训练,产生更好的蒸馏效果。论文作者使用了三种数据增强方式:
    • A. Masking:以一定的概率将原数据中的 word token 替换成[MASK]
    • B. POS-guided word replacement:以一定的概率将原数据中的词用与其有相同 POS tag 的词替换;
    • C. n-gram sampling:以一定的概率从每条数据中采样 n-gram,其中 n 的范围可通过人工设置。

需要指出的是,仓库实现(见 distill_lstm/README.md)在英文数据集任务上使用了 Google News 语料预训练的 Word Embedding 初始化小模型的 Embedding 层,实际实验只使用了第 1 种和第 3 种数据增强方式。

2. 三个训练阶段总览

本实验分为三个训练过程:

  1. 在特定任务上对 BERT 进行微调(得到教师模型);
  2. 在特定任务上对基于 Bi-LSTM 的小模型进行单独训练(用于评价蒸馏效果);
  3. 将 BERT 模型的知识蒸馏到基于 Bi-LSTM 的小模型上。

3. 数据与预训练模型获取

本实验使用 GLUE 中的 SST-2、QQP 以及中文情感分类数据集 ChnSentiCorp 中的训练集作为训练语料,用数据集中的验证集评估模型效果。运行实验时,数据集会被自动下载到paddlenlp.utils.env.DATA_HOME路径下。例如在 Linux 系统下,GLUE 中的 QQP 数据集默认存储路径是~/.paddlenlp/datasets/glue/QQP,ChnSentiCorp 数据集则会下载到~/.paddlenlp/datasets/chnsenticorp

对于 BERT 微调任务,实验使用了预训练模型bert-base-uncasedbert-wwm-ext-chinesebert-base-chinese,这些模型在训练时会被自动下载到paddlenlp.utils.env.MODEL_HOME路径下,例如bert-base-uncased在 Linux 系统下位于~/.paddlenlp/models/bert-base-uncased

在中文数据集上训练小模型时,输入利用 jieba 分词,词表可下载senta_word_dict.txt(下载方式见 distill_lstm/README.md)。为节省显存和运行时间,可以对 ChnSentiCorp 中未出现的词先进行过滤,并将最后的词表文件名和词表大小配置到参数--vocab_path--vocab_size中。

4. 第一步:微调 BERT 教师模型

以 GLUE 的 SST-2 任务为例,使用bert-base-uncased做微调之后可以得到一个在 SST-2 任务上的教师模型,把在 dev 上取得最好 Accuracy 的模型保存下来用于第三步的蒸馏。参考命令如下(对应 slm/examples/benchmark/glue 目录下的run_glue.py):

cd slm/examples/benchmark/glue export CUDA_VISIBLE_DEVICES=0 export TASK_NAME=SST-2 python -u ./run_glue.py \ --model_type bert \ --model_name_or_path bert-base-uncased \ --task_name $TASK_NAME \ --max_seq_length 128 \ --batch_size 128 \ --learning_rate 3e-5 \ --num_train_epochs 3 \ --logging_steps 10 \ --save_steps 10 \ --output_dir ../model_compression/distill_lstm/pretrained_models/$TASK_NAME/ \ --device gpu

如果需要训练基于 ChnSentiCorp 数据集的 BERT 微调模型,可以进入 slm/applications/text_classification/multi_class 目录,将预训练模型改成 BERT,并基于bert-base-chinesebert-wwm-ext-chinese模型进行微调训练。

训练完成之后,将训练效果最好的模型保存在pretrained_models/$TASK_NAME/目录下,模型目录下包含model_config.jsonmodel_state.pdparamstokenizer_config.jsonvocab.txt这几个文件。

5. 第二步:定义 Bi-LSTM 小模型并单独训练

在本示例中,小模型是双向 LSTM 分类模型,网络层分别是EmbeddingLSTM、带有tanh激活函数的Linear层,最后经过一个全连接的输出层得到 logits。LSTM网络层定义如下(见 small.py):

self.lstm = nn.LSTM(embed_dim, hidden_size, num_layers, 'bidirectional', dropout=dropout_prob)

基于 Bi-LSTM 的小模型的forward函数定义如下:

def forward(self, x, seq_len): x_embed = self.embedder(x) lstm_out, (hidden, _) = self.lstm( x_embed, sequence_length=seq_len) # 双向LSTM out = paddle.concat((hidden[-2, :, :], hidden[-1, :, :]), axis=1) out = paddle.tanh(self.fc(out)) logits = self.output_layer(out) return logits

从源码看,BiLSTMforward还针对 QQP 这类句对任务做了扩展:当输入x_2不为None时,会将两个句子的隐状态拼接为[out_1, out_2, out_1 + out_2, |out_1 - out_2|]四元特征后过fc_1,这正是文本匹配任务常用的交互特征构造方式。

单独训练小模型,可以分别基于 ChnSentiCorp、SST-2、QQP 数据集运行:

# ChnSentiCorp CUDA_VISIBLE_DEVICES=0 python small.py \ --task_name chnsenticorp \ --max_epoch 20 \ --vocab_size 1256608 \ --batch_size 64 \ --model_name bert-wwm-ext-chinese \ --optimizer adam \ --lr 3e-4 \ --dropout_prob 0.2 \ --vocab_path senta_word_dict.txt \ --save_steps 10000 \ --output_dir small_models/chnsenticorp/
# SST-2 CUDA_VISIBLE_DEVICES=0 python small.py \ --task_name sst-2 \ --vocab_size 30522 \ --max_epoch 10 \ --batch_size 64 \ --lr 1.0 \ --dropout_prob 0.4 \ --output_dir small_models/SST-2 \ --save_steps 10000 \ --embedding_name w2v.google_news.target.word-word.dim300.en
# QQP CUDA_VISIBLE_DEVICES=0 python small.py \ --task_name qqp \ --vocab_size 30522 \ --max_epoch 35 \ --batch_size 256 \ --lr 2.0 \ --dropout_prob 0.4 \ --output_dir small_models/QQP \ --save_steps 10000 \ --embedding_name w2v.google_news.target.word-word.dim300.en

各参数的具体说明可参阅 args.py,注意在训练不同任务时需要调整对应的超参数。

6. 第三步:数据增强与蒸馏

蒸馏时使用的训练数据集并不只包含数据集中原有的数据,而是按照上文原理介绍中的 A(Masking)、C(n-gram sampling)两种方法进行数据增强后的总数据。在多数情况下,alpha会被设置为 0,表示无视硬标签,学生模型只利用数据增强后的无标签数据进行训练。根据教师模型提供的软标签teacher_logits,对比学生模型的logits,计算均方误差损失。由于数据增强过程产生了更多的数据,学生模型可以从教师模型中学到更多的暗知识。

数据增强的核心代码如下:

def ngram_sampling(words, words_2=None, p_ng=0.25, ngram_range=(2, 6)): if np.random.rand() < p_ng: ngram_len = np.random.randint(ngram_range[0], ngram_range[1] + 1) ngram_len = min(ngram_len, len(words)) start = np.random.randint(0, len(words) - ngram_len + 1) words = words[start:start + ngram_len] if words_2: words_2 = words_2[start:start + ngram_len] return words if not words_2 else (words, words_2) def data_augmentation(data, whole_word_mask=whole_word_mask): # 1. Masking words = [] if not whole_word_mask: tokenized_list = tokenizer.tokenize(data) words = [ tokenizer.mask_token if np.random.rand() < p_mask else word for word in tokenized_list ] else: for word in data.split(): words += [[tokenizer.mask_token]] if np.random.rand( ) < p_mask else [tokenizer.tokenize(word)] # 2. N-gram sampling words = ngram_sampling(words, p_ng=p_ng, ngram_range=ngram_range) words = flatten(words) if isinstance(words[0], list) else words new_text = " ".join(words) return words, new_text

蒸馏阶段,主要是让学生模型(Bi-LSTM)去学习教师模型的输出 logits,核心训练循环如下(与 bert_distill.py 中的实现一致):

ce_loss = nn.CrossEntropyLoss() # 交叉熵损失函数 mse_loss = nn.MSELoss() # 均方误差损失函数 for epoch in range(args.max_epoch): for i, batch in enumerate(train_data_loader): bert_input_ids, bert_segment_ids, student_input_ids, seq_len, labels = batch # Calculate teacher model's forward. with paddle.no_grad(): teacher_logits = teacher.model(bert_input_ids, bert_segment_ids) # Calculate student model's forward. logits = model(student_input_ids, seq_len) # Calculate the loss, usually args.alpha equals to 0. loss = args.alpha * ce_loss(logits, labels) + ( 1 - args.alpha) * mse_loss(logits, teacher_logits) loss.backward() optimizer.step()

在源码实现中,教师模型通过TeacherModel类封装(BertForSequenceClassification.from_pretrained(teacher_dir)后置为eval()),教师前向计算被包裹在paddle.no_grad()中,不参与梯度更新;损失函数alpha * ce_loss + (1 - alpha) * mse_loss与文档代码完全一致,alpha默认 0.0(见 args.py 中--alpha参数的说明)。

运行蒸馏的命令分别基于 ChnSentiCorp、SST-2、QQP 数据集:

# ChnSentiCorp CUDA_VISIBLE_DEVICES=0 python bert_distill.py \ --task_name chnsenticorp \ --vocab_size 1256608 \ --max_epoch 6 \ --lr 1.0 \ --dropout_prob 0.1 \ --batch_size 64 \ --model_name bert-wwm-ext-chinese \ --teacher_dir pretrained_models/chnsenticorp/best_bert_wwm_ext_model_880 \ --vocab_path senta_word_dict.txt \ --output_dir distilled_models/chnsenticorp \ --save_steps 10000
# SST-2 CUDA_VISIBLE_DEVICES=0 python bert_distill.py \ --task_name sst-2 \ --vocab_size 30522 \ --max_epoch 6 \ --lr 1.0 \ --dropout_prob 0.2 \ --batch_size 128 \ --model_name bert-base-uncased \ --output_dir distilled_models/SST-2 \ --teacher_dir pretrained_models/SST-2/best_model_610 \ --save_steps 10000 \ --embedding_name w2v.google_news.target.word-word.dim300.en
# QQP CUDA_VISIBLE_DEVICES=0 python bert_distill.py \ --task_name qqp \ --vocab_size 30522 \ --max_epoch 6 \ --lr 1.0 \ --dropout_prob 0.2 \ --batch_size 256 \ --model_name bert-base-uncased \ --n_iter 10 \ --output_dir distilled_models/QQP \ --teacher_dir pretrained_models/QQP/best_model_17000 \ --save_steps 10000 \ --embedding_name w2v.google_news.target.word-word.dim300.en

7. 蒸馏实验结果

本蒸馏实验基于 GLUE 的 SST-2、QQP 与中文情感分类 ChnSentiCorp 数据集,使用各自验证集(dev)评估,评价指标为准确率(acc),QQP 中额外包含 F1 值。利用 BERT 教师模型蒸馏 Bi-LSTM 学生模型,相比 Bi-LSTM 小模型单独训练,在 SST-2、QQP、ChnSentiCorp 上分别有 3.3%、1.9%、1.4% 的提升:

ModelSST-2 (dev acc)QQP (dev acc/f1)ChnSentiCorp (dev acc)ChnSentiCorp (dev acc)
Teacher modelbert-base-uncasedbert-base-uncasedbert-base-chinesebert-wwm-ext-chinese
BERT-base0.9300460.905813 / 0.8734720.9516670.955000
Bi-LSTM0.8543580.856616 / 0.7996820.9200000.920000
Distilled Bi-LSTM0.8876150.875216 / 0.8312540.9325000.934167

8. 关键参数说明

以下参数定义见 args.py,在训练不同任务时需相应调整:

参数默认值说明
--task_namesst-2任务名,支持sst-2qqpchnsenticorp
--optimizeradadelta优化器,仅支持adamadadelta;训练小模型时常用adam
--lr1.0学习率,SST-2/QQP 蒸馏常用 1.0,ChnSentiCorp 单独训练小模型常用 3e-4
--num_layers1LSTM 层数
--emb_dim300Embedding 维度
--hidden_size300LSTM 隐层大小
--output_dim2分类类别数
--batch_size64训练 batch size
--max_epoch12最大训练轮数
--max_seq_length128句子最大长度
--n_iter20数据增强中每个样本的迭代次数
--dropout_prob0.0Dropout 概率
--init_scale0.1参数初始化范围
--padding_idx0Embedding 的 padding 索引
--model_namebert-base-uncased教师模型名(其 tokenizer 会被小模型复用)
--teacher_dir教师模型目录
--vocab_pathBERT 词表默认路径学生模型词表路径
--vocab_size10000学生模型词表大小
--alpha0.0交叉熵损失与均方误差损失的权重平衡系数
--whole_word_maskFalse数据增强中是否使用整词掩码
--devicegpu运行设备,支持gpucpuxpu
--seed2021随机种子

三、使用 DynaBERT 策略对 BERT 进行压缩

本教程使用DynaBERT: Dynamic BERT with Adaptive Width and Depth论文中的训练策略,把原始模型作为超网络中最大的子模型(超网络指包含所有搜索空间在内的一个网络)。原始模型包含多个相同大小的 Transformer Block;每次训练前会选择当前轮次要训练的子模型,每个子模型包含多个相同大小的 Sub Transformer Block,每个 Sub Transformer Block 是选择不同宽度的 Transformer Block 得到的。一个 Transformer Block 包含一个 Multi-Head Attention 和一个 Feed-Forward Network,Sub Transformer Block 的获得方式为:

  1. 一个Multi-Head Attention层中有多个 Head,每次选择不同宽度的子模型时,会同时对 Head 数量进行等比例减少。例如:原始模型有 12 个 Head,本次训练选择宽度为原始宽度 75% 的子模型,则本次训练中所有 Transformer Block 的 Head 数量为 9。
  2. Feed-Forward Network层中Linear的参数大小进行等比例减少。例如:原始模型 FFN 层特征维度为 3072,本次训练选择宽度为原始宽度 75% 的子模型,则本次训练中所有 Transformer Block 中 FFN 层的特征维度为 2304。

完整实验代码位于 slm/examples/model_compression/ofa,包含run_glue_ofa.pyrun_glue_ofa_depth.pyexport_model.py等脚本,其中run_glue_ofa_depth.py还扩展了深度维度的搜索。

1. 整体原理与流程

整体流程如下图所示:

  1. 首先对预训练模型的参数和 head 根据其重要性进行重排序,把重要的参数和 head 排在参数的前侧,保证训练过程中的参数裁剪不会裁剪掉这些重要的参数。参数重要性的计算:先使用 dev 数据计算一遍每个参数的梯度,然后根据梯度和参数的整体大小来计算当前参数的重要性;head 重要性的计算:传入一个全 1 的 head mask,计算该 mask 的梯度,根据 mask 的梯度判断每个Multi-Head Attention层中每个 Head 的重要性。
  2. 使用原本的预训练模型作为蒸馏过程中的教师网络。同时定义一个超网络,这个超网络中最大的子网络的结构和教师网络相同,其他小的子网络是对最大网络进行不同的宽度选择得到的。宽度选择具体指对网络中的参数进行裁剪,所有子网络在整个训练过程中都是参数共享的
  3. 使用重排序之后的预训练模型参数初始化超网络,并把这个超网络作为学生网络。分别为Embedding层、每个 transformer block 层和最后的 logits 添加蒸馏损失。
  4. 每个 batch 数据在训练前首先会选择当前要训练的子网络配置(子网络配置目前仅包括对整个模型宽度的选择),参数更新时仅会更新当前子网络计算中用到的那部分参数。
  5. 通过以上方式优化整个超网络参数,训练完成后选择满足加速要求和精度要求的子模型。

2. 压缩前准备:微调 BERT

在本例中,也需要训练基于特定任务的 BERT 模型,方法同《由BERT到Bi-LSTM的知识蒸馏》教程中所述(基于 GLUE 数据集微调bert-base-uncased)。下面重点介绍模型压缩过程。

3. 基于 PaddleSlim 的压缩实现步骤

3.1 定义初始网络

定义原始 BERT-base 模型并定义一个字典保存原始模型参数。普通模型转换为超网络之后,由于其组网 OP 的改变导致原始模型加载的参数失效,所以需要定义一个字典保存原始模型的参数,并用来初始化超网络:

model = BertForSequenceClassification.from_pretrained('bert', num_classes=2) origin_weights = {} for name, param in model.named_parameters(): origin_weights[name] = param
3.2 构建超网络

定义搜索空间,并根据搜索空间把普通网络转换为超网络:

# 定义搜索空间 sp_config = supernet(expand_ratio=[0.25, 0.5, 0.75, 1.0]) # 转换模型为超网络 model = Convert(sp_config).convert(model) paddleslim.nas.ofa.utils.set_state_dict(model, origin_weights)

其中expand_ratio列表定义了可选的宽度倍数(25%、50%、75%、100%),set_state_dict负责把保存的原始参数按重排序后的位置载入超网络。

3.3 定义教师网络

构造教师网络:

teacher_model = BertForSequenceClassification.from_pretrained('bert', num_classes=2)
3.4 配置蒸馏相关参数

需要配置的参数包括:教师模型实例;需要添加蒸馏的层——在教师网络和学生网络的Embedding层和每一个Transformer Block层之间添加蒸馏损失(中间层的蒸馏损失使用默认的 MSE 损失函数);配置lambda_distill参数表示整体蒸馏损失的缩放比例:

mapping_layers = ['bert.embeddings'] for idx in range(model.bert.config['num_hidden_layers']): mapping_layers.append('bert.encoder.layers.{}'.format(idx)) default_distill_config = { 'lambda_distill': 0.1, 'teacher_model': teacher_model, 'mapping_layers': mapping_layers, } distill_config = DistillConfig(**default_distill_config)
3.5 定义 Once-For-All 模型

将普通模型和蒸馏相关配置传给OFA接口,自动添加蒸馏过程并把超网络训练方式转为OFA训练方式:

ofa_model = paddleslim.nas.ofa.OFA(model, distill_config=distill_config)
3.6 计算神经元和 head 的重要性并重排序
head_importance, neuron_importance = utils.compute_neuron_head_importance( 'sst-2', ofa_model.model, dev_data_loader, num_layers=model.bert.config['num_hidden_layers'], num_heads=model.bert.config['num_attention_heads']) reorder_neuron_head(ofa_model.model, head_importance, neuron_importance)
3.7 传入当前 OFA 训练所处的阶段
ofa_model.set_epoch(epoch) ofa_model.set_task('width')
3.8 传入网络配置,开始训练

本示例使用 DynaBERT 的策略进行超网络训练。在每个 batch 内依次以不同宽度倍数(1.0、0.75、0.5、0.25)切出子网络,收集各宽度下的表示蒸馏损失与 logits 软标签损失,累加后统一反向传播:

width_mult_list = [1.0, 0.75, 0.5, 0.25] lambda_logit = 0.1 for width_mult in width_mult_list: net_config = paddleslim.nas.ofa.utils.dynabert_config(ofa_model, width_mult) ofa_model.set_net_config(net_config) logits, teacher_logits = ofa_model(input_ids, segment_ids, attention_mask=[None, None]) rep_loss = ofa_model.calc_distill_loss() logit_loss = soft_cross_entropy(logits, teacher_logits.detach()) loss = rep_loss + lambda_logit * logit_loss loss.backward() optimizer.step() lr_scheduler.step() ofa_model.model.clear_gradients()

可以看到,每个宽度子网络的 loss 由两部分构成:rep_loss(中间层表示蒸馏损失,来自calc_distill_loss())与lambda_logit * logit_loss(输出 logits 的软交叉熵损失),这正是 DynaBERT 的核心训练目标。

4. 关键注意点:monkey patch BERTModel 的 forward

由于在计算 head 重要性时会利用一个 mask 来收集梯度,所以需要通过 monkey patch 的方式重新实现BERTModel类的forward函数。示例如下:

from paddlenlp.transformers import BertModel def bert_forward(self, input_ids, token_type_ids=None, position_ids=None, attention_mask=[None, None]): wtype = self.pooler.dense.fn.weight.dtype if hasattr( self.pooler.dense, 'fn') else self.pooler.dense.weight.dtype if attention_mask[0] is None: attention_mask[0] = paddle.unsqueeze( (input_ids == self.pad_token_id).astype(wtype) * -1e9, axis=[1, 2]) embedding_output = self.embeddings( input_ids=input_ids, position_ids=position_ids, token_type_ids=token_type_ids) encoder_outputs = self.encoder(embedding_output, attention_mask) sequence_output = encoder_outputs pooled_output = self.pooler(sequence_output) return sequence_output, pooled_output BertModel.forward = bert_forward

5. 压缩结果参考

依据 ofa/README.md 中公开的实验记录:利用bert-base-uncased模型在 GLUE 数据集上微调得到待压缩模型后,基于 PaddleSlim 压缩,压缩后模型参数大小减小 26%(从 110M 减少到 81M),且压缩后模型在 GLUE dev 数据集上的精度与压缩前基本持平甚至略有提升:

TaskMetricResultResult with PaddleSlim
SST-2Accuracy0.930050.931193
QNLIAccuracy0.917810.920740
CoLAMattehew's corr0.595570.601244
MRPCF1/Accuracy0.91667 / 0.882350.91740 / 0.88480
STS-BPerson/Spearman corr0.88847 / 0.883500.89271 / 0.88958
QQPAccuracy/F10.90581 / 0.873470.90994 / 0.87947
MNLIMatched acc / MisMatched acc0.84422 / 0.848250.84687 / 0.85242
RTEAccuracy0.7111910.718412

四、总结与进阶方向

从本文两篇教程可以看到,PaddleNLP 覆盖了模型压缩的两条经典路线:

  • 逐任务蒸馏(BERT → Bi-LSTM):以数据增强 + MSE 软标签损失为核心,适合把任务特定的小模型快速部署到资源受限场景,是理解蒸馏机制的 "Hello World";
  • 超网络宽度压缩(DynaBERT / OFA):一次训练得到多个宽度共享参数的子网络,推理时按需裁剪,兼顾精度与加速,适合需要对同一模型做多档位部署的场景。

如果需要进一步深入,可以继续阅读:

  • 蒸馏实现:slm/examples/model_compression/distill_lstm(small.pybert_distill.pyargs.pydata.pyutils.py);
  • OFA 压缩实现:slm/examples/model_compression/ofa(run_glue_ofa.pyrun_glue_ofa_depth.pyexport_model.py);
  • 模型压缩文档入口:docs/zh/advanced_guide/model_compression 与 slm/examples/model_compression 下的 minilmv2、pp-minilm 等更多压缩示例(包含通用蒸馏、裁剪与后量化脚本)。

上述代码与文档均位于当前仓库,可直接查看源码、配置文件与测试数据来验证文中描述的实现细节。

【免费下载链接】PaddleNLPEasy-to-use and powerful LLM and SLM library with awesome model zoo.项目地址: https://gitcode.com/gh_mirrors/pa/PaddleNLP

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询