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

先决条件

你还需要一个预训练的BERT模型检查点,可以从DeepSpeed、HuggingFaceTensorFlow获取,以运行微调。关于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 的预训练模型。在这里,我们展示两个模型示例:

  1. test/huggingface 包含检查点 Bert-large-uncased-whole-word-maskingbert json config
  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中添加以下内容,以运行上述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 内存溢出 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

更新: