BingBertSQuAD 微调

在本教程中,我们将为 SQuAD 微调任务(以下简称“BingBertSquad”)将 DeepSpeed 添加到 BingBert 模型中。我们还将演示性能提升。

概述

如果您还没有 DeepSpeed 仓库的副本,请立即克隆并检出包含 BingBertSquad 示例(DeepSpeedExamples/BingBertSquad)的 DeepSpeedExamples 子模块,我们将在本教程的其余部分中进行介绍。

git clone https://github.com/microsoft/DeepSpeed
cd DeepSpeed
git submodule update --init --recursive
cd DeepSpeedExamples/BingBertSquad

先决条件

您还需要 DeepSpeed、HuggingFaceTensorFlow 的预训练 BERT 模型检查点来运行微调。关于 DeepSpeed 模型,我们将使用 BERT 预训练教程中的检查点 160。

运行 BingBertSquad

  • 启用 DeepSpeed:我们提供了一个 shell 脚本,您可以调用它来开始使用 DeepSpeed 进行训练,它接受 4 个参数:bash run_squad_deepspeed.sh <NUM_GPUS> <PATH_TO_CHECKPOINT> <PATH_TO_DATA_DIR> <PATH_TO_OUTPUT_DIR>。第一个参数是要训练使用的 GPU 数量,第二个参数是预训练检查点的路径,第三个是训练和验证集的路径(例如,train-v1.1.json),第四个是结果将保存到的输出文件夹的路径。此脚本将调用 nvidia_run_squad_deepspeed.py
  • 未修改的基线如果您想运行未启用 DeepSpeed 的微调版本,我们提供了一个 shell 脚本,它与名为 run_squad_baseline.sh 的 DeepSpeed 脚本具有相同的参数。此脚本将调用 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 具有一个初始化函数来包装模型、优化器、LR 调度器和数据加载器。对于 BingBertSquad,我们只需使用初始化函数来包装模型并创建优化器,以增强基线脚本,如下所示

model, optimizer, _, _ = deepspeed.initialize(
    args=args,
    model=model,
    model_parameters=optimizer_grouped_parameters
)

前向传播

这在基线和 DeepSpeed 中都是相同的,并且通过 loss = model(input_ids, segment_ids, input_mask, start_positions, end_positions) 完成。

反向传播

在基线脚本中,您需要使用 enable_need_reduction() 后跟 optimizer.backward(loss)(在 FP16 中)和 loss.backward()(在 FP32 中)在梯度累积边界显式处理全减操作。在 DeepSpeed 中,您只需执行 model.backward(loss)

权重更新

在基线脚本中,您需要显式指定优化器为 FusedAdam(以及处理动态损失缩放)(在 FP16 中)和 BertAdam(在 FP32 中),然后调用 optimizer.step()optimizer.zero_grad()。DeepSpeed 在调用 initialize() 时在内部处理此操作(通过使用 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;我们将种子分别设置为 9041 和 19068 用于 HuggingFace 和 TensorFlow 模型。下表中链接了用于每种情况的检查点。

情况 模型 精度 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。

有关 Transformer 内核的更多详细信息,请参阅我们的使用教程关于最快 BERT 训练的技术深入

加载 HuggingFace 和 TensorFlow 预训练模型

BingBertSquad 支持 HuggingFace 和 TensorFlow 预训练模型。在这里,我们展示了两个模型示例

  1. test/huggingface,其中包含检查点Bert-large-uncased-whole-word-maskingbert json 配置
  2. 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

加载这两种类型的检查点使用三个参数。

  1. --model_file,指向预训练模型文件。
  2. --ckpt_type,指示检查点类型,TF 用于 Tensorflow,HF 用于 HuggingFace,默认值为 DS 用于 DeepSpeed。
  3. --origin_bert_config_file,指向 BERT 配置文件,通常保存在 model_file 的同一个文件夹中。

我们可以在 run_squad_deepspeed.sh 中的微调 shell 脚本中添加以下内容,以运行上面的 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 \

注意

  1. --deepspeed_transformer_kernel 标志是使用 HuggingFace 或 TensorFlow 预训练模型所必需的。

  2. --preln 标志不能与 HuggingFace 或 TensorFlow 预训练模型一起使用,因为它们使用的是层归一化后。

  3. 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 OOM 63.01 1.3

表 3. 使用 PyTorch 和 DeepSpeed Transformer 内核在 NVIDIA V100 (32GB) 上运行 SQuAD 微调的每秒样本数。

如前所述,我们可以将每个 GPU 的微批次大小从 3 增加到 24,甚至更高,如果需要更大的批次大小。为了支持更大的微批次大小,我们可能需要为 Transformer 内核启用不同的内存优化标志,如 [DeepSpeed Transformer 内核](/tutorials/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 OOM
> 24 gelu_checkpoint OOM

表 4. 在 16GB 和 32GB V100 上针对不同范围的微批次大小的内存优化标志设置。

使用 DeepSpeed Transformer 内核预训练的微调模型

使用 DeepSpeed Transformer 和 [DeepSpeed 快速 BERT 训练](https://www.deepspeed.org.cn/2020/05/27/fastest-bert-training.html) 中的配方预训练的模型应该产生 90.5 的 F1 分数,并且如果预训练时间比教程中建议的时间更长,则预计会提高。

为了获得这些结果,我们需要对 dropout 设置进行一些调整,如下所述。

丢弃设置

对于微调,我们只使用确定性 Transformer 来获得可重复的微调结果。但是,我们根据预训练是使用确定性 Transformer 还是随机 Transformer 进行的,选择不同的 dropout 值(有关选择这两种模式的更多详细信息,请参见 [Transformer 教程](/tutorials/transformer_kernel/))。

对于使用确定性 Transformer 预训练的模型,我们使用预训练中使用的相同 dropout 比例 (0.1)。但是,在使用随机 Transformer 预训练的模型进行微调时,我们会稍微提高 dropout 比例,以弥补微调期间缺乏随机噪声的影响。

预训练模式 Dropout 比例
确定性 0.1
随机 0.12 - 0.14

更新: