BingBertSQuAD 微调
在本教程中,我们将为SQuAD微调任务(此后称为“BingBertSquad”)的BingBert模型添加DeepSpeed。我们还将展示性能提升。
概述
如果您还没有DeepSpeed仓库的副本,请现在克隆并检出包含BingBertSquad示例的DeepSpeedExamples子模块(DeepSpeedExamples/training/BingBertSquad),我们将在本教程的其余部分中讨论。
git clone https://github.com/microsoft/DeepSpeed
cd DeepSpeed
git submodule update --init --recursive
cd DeepSpeedExamples/training/BingBertSquad
先决条件
- 下载SQuAD数据:
- 训练集:train-v1.1.json
- 验证集:dev-v1.1.json
你还需要一个预训练的BERT模型检查点,可以从DeepSpeed、HuggingFace或TensorFlow获取,以运行微调。关于DeepSpeed模型,我们将使用BERT预训练教程中的检查点160。
运行BingBertSquad
- 支持DeepSpeed: 我们提供了一个shell脚本,您可以调用它来开始使用DeepSpeed进行训练,它需要4个参数:
bash run_squad_deepspeed.sh。第一个参数是用于训练的GPU数量,第二个参数是预训练检查点的路径,第三个是训练和验证集的路径(例如,train-v1.1.json),第四个是保存结果的输出文件夹的路径。此脚本将调用nvidia_run_squad_deepspeed.py。 - 未修改的基线 如果您想运行一个未启用DeepSpeed的微调版本,我们提供了一个与DeepSpeed脚本同名的shell脚本
run_squad_baseline.sh。该脚本将调用nvidia_run_squad_baseline.py。
DeepSpeed 集成
训练的主要部分在nvidia_run_squad_deepspeed.py中完成,该文件已经修改为使用DeepSpeed。run_squad_deepspeed.sh脚本有助于调用训练并设置与训练过程相关的几个不同超参数。在接下来的几节中,我们将介绍我们对基线所做的更改以启用DeepSpeed,您不必自己进行这些更改,因为我们已经为您完成了这些更改。
配置
deepspeed_bsz24_config.json 文件允许用户根据批量大小、微批量大小、学习率和其他参数来指定 DeepSpeed 选项。在运行 nvidia_run_squad_deepspeed.py 时,除了使用 --deepspeed 标志启用 DeepSpeed 外,还必须使用 --deepspeed_config deepspeed_bsz24_config.json 指定适当的 DeepSpeed 配置文件。表 1 显示了我们实验中使用的微调配置。
| 参数 | 值 |
|---|---|
| 总批量大小 | 24 |
| 每个GPU的训练微批次大小 | 3 |
| 优化器 | Adam |
| 学习率 | 3e-5 |
| 序列长度 | 384 |
| 权重衰减 | 0.0 |
| 训练轮数 | 2 |
表1. 微调配置
参数解析
应用DeepSpeed的第一步是向BingBertSquad添加参数,使用deepspeed.add_config_arguments()在主入口点的开头,如nvidia_run_squad_deepspeed.py中的main()函数所示。传递给add_config_arguments()的参数是从utils.py中的get_argument_parser()函数获取的。
parser = get_argument_parser()
# Include DeepSpeed configuration arguments
parser = deepspeed.add_config_arguments(parser)
args = parser.parse_args()
与此类似,所有选项及其相应的描述都可以在utils.py中找到。
训练
初始化
DeepSpeed 有一个初始化函数来封装模型、优化器、学习率调度器和数据加载器。对于 BingBertSquad,我们只需在基线脚本中使用初始化函数来封装模型并创建优化器,如下所示:
model, optimizer, _, _ = deepspeed.initialize(
args=args,
model=model,
model_parameters=optimizer_grouped_parameters
)
前向传播
这在Baseline和DeepSpeed中是相同的,由loss = model(input_ids, segment_ids, input_mask, start_positions, end_positions)执行。
反向传播
在基线脚本中,您需要通过使用enable_need_reduction()后跟FP16中的optimizer.backward(loss)和FP32中的loss.backward()来显式处理梯度累积边界处的全归约操作。在DeepSpeed中,您可以简单地执行model.backward(loss)。
权重更新
在基线脚本中,您需要明确指定优化器为FusedAdam(以及动态损失缩放的处理)在FP16中,以及在FP32中为BertAdam,然后调用optimizer.step()和optimizer.zero_grad()。当调用initialize()时,DeepSpeed内部处理此操作(通过使用JSON配置设置优化器),因此您不需要显式编写代码,只需执行model.step()。
恭喜!迁移到DeepSpeed已完成。
评估
训练完成后,可以从以下命令获取EM和F1分数:
python evaluate-v1.1.py <PATH_TO_DATA_DIR>/dev-v1.1.json <PATH_TO_DATA_DIR>/predictions.json
微调结果
下表总结了结果。在所有情况下(除非另有说明),总批量大小设置为24,并在DGX-2节点上的4个GPU上进行2个周期的训练。尝试了一组参数(种子和学习率),并选择了最佳的一组。所有学习率均为3e-5;我们分别为HuggingFace和TensorFlow模型设置了种子为9041和19068。每个案例使用的检查点在下表中链接。
| 案例 | 模型 | 精确度 | EM | F1 |
|---|---|---|---|---|
| TensorFlow | Bert-large-uncased-L-24_H-1024_A-16 | FP16 | 84.13 | 91.03 |
| HuggingFace | Bert-large-uncased-whole-word-masking | FP16 | 87.27 | 93.33 |
启用DeepSpeed的Transformer内核以提高吞吐量
DeepSpeed的优化transformer内核可以在微调期间启用,以提高训练吞吐量。除了支持使用DeepSpeed预训练的模型外,该内核还可以与TensorFlow和HuggingFace检查点一起使用。
启用Transformer内核
一个参数 --deepspeed_transformer_kernel 已经在 utils.py 中创建,我们通过在 shell 脚本中添加它来启用 transformer 内核。
parser.add_argument(
'--deepspeed_transformer_kernel',
default=False,
action='store_true',
help='Use DeepSpeed transformer kernel to accelerate.'
)
在建模源文件的BertEncoder类中,当使用--deepspeed_transformer_kernel参数启用时,DeepSpeed transformer内核的创建如下所示。
if args.deepspeed_transformer_kernel:
from deepspeed import DeepSpeedTransformerLayer, \
DeepSpeedTransformerConfig, DeepSpeedConfig
ds_config = DeepSpeedConfig(args.deepspeed_config)
cuda_config = DeepSpeedTransformerConfig(
batch_size=ds_config.train_micro_batch_size_per_gpu,
max_seq_length=args.max_seq_length,
hidden_size=config.hidden_size,
heads=config.num_attention_heads,
attn_dropout_ratio=config.attention_probs_dropout_prob,
hidden_dropout_ratio=config.hidden_dropout_prob,
num_hidden_layers=config.num_hidden_layers,
initializer_range=config.initializer_range,
seed=args.seed,
fp16=ds_config.fp16_enabled
)
self.layer = nn.ModuleList([
copy.deepcopy(DeepSpeedTransformerLayer(i, cuda_config))
for i in range(config.num_hidden_layers)
])
else:
layer = BertLayer(config)
self.layer = nn.ModuleList([
copy.deepcopy(layer)
for _ in range(config.num_hidden_layers)
])
所有配置设置都来自DeepSpeed配置文件和命令行参数,因此我们必须在此模型中传递args变量。
注意:batch_size 是输入数据的最大批次大小,所有的微调训练数据或预测数据都不应超过这个阈值,否则会抛出异常。在 DeepSpeed 配置文件中,微批次大小定义为 train_micro_batch_size_per_gpu,例如,如果设置为 8,那么 --predict_batch_size 也应该为 8。
有关变压器内核的更多详细信息,请参阅我们关于最快BERT训练的使用教程和技术深入探讨。
加载HuggingFace和TensorFlow预训练模型
BingBertSquad 支持 HuggingFace 和 TensorFlow 的预训练模型。在这里,我们展示两个模型示例:
test/huggingface包含检查点 Bert-large-uncased-whole-word-masking 和 bert json config。test/tensorflow来自Google的一个检查点压缩文件 Bert-large-uncased-L-24_H-1024_A-16。
[test/huggingface]
bert-large-uncased-whole-word-masking-config.json
bert-large-uncased-whole-word-masking-pytorch_model.bin
[test/tensorflow]
bert_config.json
bert_model.ckpt.data-00000-of-00001
bert_model.ckpt.index
bert_model.ckpt.meta
加载这两种类型的检查点使用了三个参数。
--model_file,指向预训练模型文件。--ckpt_type,表示检查点类型,TF用于 Tensorflow,HF用于 HuggingFace,默认值为DS用于 DeepSpeed。--origin_bert_config_file,指向BERT配置文件,通常保存在model_file的同一文件夹中。
我们可以在微调脚本run_squad_deepspeed.sh中添加以下内容,以运行上述HuggingFace和TensorFlow示例。
[HuggingFace]
--model_file test/huggingface/bert-large-uncased-whole-word-masking-pytorch_model.bin \
--ckpt_type HF \
--origin_bert_config_file test/huggingface/bert-large-uncased-whole-word-masking-config.json \
[TensorFlow]
--model_file /test/tensorflow/bert_model.ckpt \
--ckpt_type TF \
--origin_bert_config_file /test/tensorflow/bert_config.json \
注意:
-
--deepspeed_transformer_kernel标志是使用 HuggingFace 或 TensorFlow 预训练模型所必需的。 -
--preln标志不能与 HuggingFace 或 TensorFlow 预训练模型一起使用,因为它们使用的是后层归一化。 -
BingBertSquad 将检查预训练模型是否具有相同的词汇量,如果存在任何不匹配,将无法运行。我们建议您使用上述风格的模型检查点或 DeepSpeed bing_bert 检查点。
性能调优
为了进行微调,我们将总批量大小设置为24,如表1所示。然而,我们可以调整每个GPU的微批量大小以获得高性能训练。在这方面,我们已经在NVIDIA V100上尝试了不同的微批量大小,使用16GB或32GB内存。如表2和表3所示,通过增加微批量可以提高性能。与PyTorch相比,我们可以在16GB V100上实现高达1.5倍的加速,同时支持每个GPU的批量大小增加2倍。另一方面,使用32GB V100,我们可以支持高达32的批量大小(比PyTorch高2.6倍),同时在端到端微调训练中提供1.3倍的加速。请注意,我们使用每秒最佳样本数来计算在PyTorch内存不足(OOM)情况下的加速。
| 微批次大小 | PyTorch | DeepSpeed | 加速比 (x) |
|---|---|---|---|
| 4 | 36.34 | 50.76 | 1.4 |
| 6 | OOM | 54.28 | 1.5 |
| 8 | OOM | 54.16 | 1.5 |
表2. 使用PyTorch和DeepSpeed transformer内核在NVIDIA V100(16GB)上运行SQuAD微调的每秒样本数。
| 微批次大小 | PyTorch | DeepSpeed | 加速比 (x) |
|---|---|---|---|
| 4 | 37.78 | 50.82 | 1.3 |
| 6 | 43.81 | 55.97 | 1.3 |
| 12 | 49.32 | 61.41 | 1.2 |
| 24 | OOM | 60.70 | 1.2 |
| 32 | 内存溢出 | 63.01 | 1.3 |
表3. 使用PyTorch和DeepSpeed transformer内核在NVIDIA V100(32GB)上运行SQuAD微调的每秒样本数。
如前所述,如果希望更大的批量大小,我们可以将每个GPU的微批量大小从3增加到24甚至更高。为了支持更大的微批量大小,我们可能需要为我们的transformer内核启用不同的内存优化标志,如DeepSpeed Transformer Kernel教程中所述。表4显示了运行不同范围的微批量大小所需的优化标志。
| 微批次大小 | NVIDIA V100 (32-GB) | NVIDIA V100 (16-GB) |
|---|---|---|
| > 4 | - | normalize_invertible |
| > 6 | - | attn_dropout_checkpoint, gelu_checkpoint |
| > 12 | normalize_invertible, attn_dropout_checkpoint |
内存不足 |
| > 24 | gelu_checkpoint |
内存不足 |
表4. 在16GB和32GB V100上针对一系列微批次大小的内存优化标志设置。
使用DeepSpeed Transformer Kernels预训练的FineTuning模型
使用DeepSpeed Transformer和DeepSpeed Fast-Bert Training中的方法对预训练模型进行微调,应该能够获得90.5的F1分数,并且如果你让预训练时间比教程中建议的更长,预计分数还会增加。
为了获得这些结果,我们需要对dropout设置进行一些调整,如下所述:
Dropout 设置
对于微调,我们仅使用确定性变压器以确保微调结果的可重复性。但是,我们根据预训练是使用确定性还是随机性变压器来选择不同的dropout值(有关选择这两种模式的更多详细信息,请参阅Transformer教程)。
对于使用确定性变压器预训练的模型,我们使用与预训练中相同的dropout比例(0.1)。然而,当微调使用随机变压器预训练的模型时,我们略微增加dropout比例,以补偿微调过程中随机噪声的缺乏。
| 预训练模式 | Dropout比例 |
|---|---|
| 确定性 | 0.1 |
| 随机 | 0.12 - 0.14 |