前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >训练BERT,我只花了一半的时间

训练BERT,我只花了一半的时间

作者头像
godweiyang
发布于 2021-07-19 06:44:08
发布于 2021-07-19 06:44:08
98200
代码可运行
举报
文章被收录于专栏:算法码上来算法码上来
运行总次数:0
代码可运行

相信很多人都知道Hugging Face,也都用过它的Transformers预训练语言模型,但你们有没有觉得它训练的有点太慢了呢?

这时候,字节第二快的男人要站出来了(第一快是我mentor),手把手教你怎么让训练时间缩短一半。

训练BERT

首先我们要安装Transformers库,这很简单:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
pip install transformers

然后我们直接把官方的例子拷贝下来,这里我们用的是GLUE任务,地址是https://github.com/huggingface/transformers/blob/master/examples/pytorch/text-classification/run_glue.py。因为代码太长了,这里就不放了,拷贝下来后文件名是run_glue.py

接着我们就可以直接运行这个代码了,我们采用mrpc数据集,开启FP16训练,命令如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
python run_glue.py \
  --model_name_or_path bert-base-cased \
  --task_name mrpc \
  --do_train \
  --do_eval \
  --max_seq_length 128 \
  --per_device_train_batch_size 32 \
  --num_train_epochs 3 \
  --output_dir /tmp/mrpc/ \
  --overwrite_output_dir \
  --fp16

我这里是单卡训练的,训练完后输出如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
***** train metrics *****
  epoch                    =        3.0
  train_loss               =     0.3921
  train_runtime            = 0:00:45.06
  train_samples            =       3668
  train_samples_per_second =    244.166
  train_steps_per_second   =      7.655

可以看出,训练总共耗时「45秒」,是不是有点等不及了呢?

加速训练

首先我们需要安装训练加速库,这里我们用到的是LightSeq,项目地址是https://github.com/bytedance/lightseq。不过我们还是直接pip安装:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
pip install lightseq

然后我们需要做的就是将Hugging Face的BERT替换成LightSeq的BERT,代码如下,放在文件replace_module.py中。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
from lightseq.training.ops.pytorch.transformer_encoder_layer import (
    LSTransformerEncoderLayer,
)

class LSHFTransformerEncoderLayer(LSTransformerEncoderLayer):
    def __init__(self, *args, **kwargs):
        super(LSHFTransformerEncoderLayer, self).__init__(*args, **kwargs)

    def forward(self, hidden_states, encoder_padding_mask, *args, **kwargs):
        encoder_padding_mask /= -10000.0
        output = super().forward(hidden_states, encoder_padding_mask)
        return (output, None, None, None)

def gen_ls_bert_config(training_args, config):
    bert_config = LSTransformerEncoderLayer.get_config(
        max_batch_tokens=4096,
        max_seq_len=config.max_position_embeddings,
        hidden_size=config.hidden_size,
        intermediate_size=config.intermediate_size,
        nhead=config.num_attention_heads,
        attn_prob_dropout_ratio=config.attention_probs_dropout_prob,
        activation_dropout_ratio=0.1,
        hidden_dropout_ratio=config.hidden_dropout_prob,
        pre_layer_norm=False,
        fp16=training_args.fp16,
        local_rank=training_args.local_rank,
    )
    return bert_config

def inject_ls_enc_layer(model, training_args, config):
    for i in range(config.num_hidden_layers):
        bert_config = gen_ls_bert_config(training_args, config)
        model.bert.encoder.layer[i] = LSHFTransformerEncoderLayer(bert_config)

这里LSHFTransformerEncoderLayer是继承的LightSeq中的LSTransformerEncoderLayer类,然后重写了forward函数。原因是Hugging Face的输入格式和LightSeq略有不同,需要在forward之前转换一下。

gen_ls_bert_config函数是用来定义LightSeq的encoder参数配置,这里直接从Hugging Face的主函数入口获取即可。

inject_ls_enc_layer函数就是用来替换BERT中的每一层encoder的,首先定义每一层的参数配置,然后用LSHFTransformerEncoderLayer类去替换原始的encoder层即可。

然后我们打开run_glue.py,在头文件处加上inject_ls_enc_layer的引用:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
from replace_module import inject_ls_enc_layer

最后在定义完model后,将model中的encoder替换即可,利用上面引用的替换函数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
model = AutoModelForSequenceClassification.from_pretrained(
    model_args.model_name_or_path,
    from_tf=bool(".ckpt" in model_args.model_name_or_path),
    config=config,
    cache_dir=model_args.cache_dir,
    revision=model_args.model_revision,
    use_auth_token=True if model_args.use_auth_token else None,
)

# 在model定义后立刻替换
inject_ls_enc_layer(model, training_args, config)

我们重新运行上一次运行的命令:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
python run_glue.py \
  --model_name_or_path bert-base-cased \
  --task_name mrpc \
  --do_train \
  --do_eval \
  --max_seq_length 128 \
  --per_device_train_batch_size 32 \
  --num_train_epochs 3 \
  --output_dir /tmp/mrpc/ \
  --overwrite_output_dir \
  --fp16

最终输出如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
***** train metrics *****
  epoch                    =        3.0
  train_loss               =     0.6077
  train_runtime            = 0:00:25.08
  train_samples            =       3668
  train_samples_per_second =    438.603
  train_steps_per_second   =     13.751

这次运行时间只有「25秒」!不愧是字节最快的男人。

加载预训练参数

有眼尖的小伙伴可能发现了,上面加速后效果变差了呀。没错,因为新建了encoder类之后,参数都是随机初始化的了,所以要重新加载一下预训练参数。

LightSeq的encoder类初始化的时候提供了预训练参数初始化的选项,我们只需要将预训练参数从Hugging Face的BERT中提取出来即可:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
def get_hf_bert_enc_layer_params(layer):
    init_ws = []
    init_bs = []

    init_ws.append(layer.attention.self.query.weight.detach().clone())
    init_bs.append(layer.attention.self.query.bias.detach().clone())
    init_ws.append(layer.attention.self.key.weight.detach().clone())
    init_bs.append(layer.attention.self.key.bias.detach().clone())
    init_ws.append(layer.attention.self.value.weight.detach().clone())
    init_bs.append(layer.attention.self.value.bias.detach().clone())
    init_ws.append(layer.attention.output.dense.weight.detach().clone())
    init_bs.append(layer.attention.output.dense.bias.detach().clone())
    init_ws.append(layer.attention.output.LayerNorm.weight.detach().clone())
    init_bs.append(layer.attention.output.LayerNorm.bias.detach().clone())

    init_ws.append(layer.intermediate.dense.weight.detach().clone())
    init_bs.append(layer.intermediate.dense.bias.detach().clone())
    init_ws.append(layer.output.dense.weight.detach().clone())
    init_bs.append(layer.output.dense.bias.detach().clone())
    init_ws.append(layer.output.LayerNorm.weight.detach().clone())
    init_bs.append(layer.output.LayerNorm.bias.detach().clone())

    return init_ws, init_bs

注意参数在列表中的顺序不能错了,然后将这两个列表加入到LSHFTransformerEncoderLayer类的初始化参数中去:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
def inject_ls_enc_layer(model, training_args, config):
    for i in range(config.num_hidden_layers):
        bert_config = gen_ls_bert_config(training_args, config)
        # 提取预训练参数
        init_ws, init_bs = get_hf_bert_enc_layer_params(model.bert.encoder.layer[i])
        # 利用预训练参数进行初始化
        model.bert.encoder.layer[i] = LSHFTransformerEncoderLayer(
            bert_config, init_ws, init_bs
        )

接着运行命令不变,效果就上来啦。

和竞品比如何?

另一款知名的训练加速库DeepSpeed你们可能也听过,那和它比速度怎么样呢?

Hugging Face已经内置了DeepSpeed,可以直接开启。不过它并没有替换掉encoder,所以模型还是用PyTorch写的,速度依然很慢。因此我们需要手动替换一下encoder。

代码和上面类似,也是定义参数配置和encoder类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
from deepspeed.ops.transformer import (
    DeepSpeedTransformerConfig,
    DeepSpeedTransformerLayer
)

def gen_ds_bert_config(training_args, config):
    bert_config = DeepSpeedTransformerConfig(
        batch_size=4096,
        hidden_size=config.hidden_size,
        intermediate_size=config.intermediate_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=0.02,
        layer_norm_eps=1e-8,
        local_rank=training_args.local_rank,
        fp16=training_args.fp16,
        pre_layer_norm=False,
        huggingface=True,
        training=True
    )
    return bert_config

def inject_ds_enc_layer(model, training_args, config):
    for i in range(config.num_hidden_layers):
        bert_config = gen_ds_bert_config(training_args, config)
        model.bert.encoder.layer[i] = DeepSpeedTransformerLayer(bert_config)

然后在run_glue.py里引用inject_ds_enc_layer替换函数,并对model进行替换:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
from replace_module import inject_ds_enc_layer

model = AutoModelForSequenceClassification.from_pretrained(
    model_args.model_name_or_path,
    from_tf=bool(".ckpt" in model_args.model_name_or_path),
    config=config,
    cache_dir=model_args.cache_dir,
    revision=model_args.model_revision,
    use_auth_token=True if model_args.use_auth_token else None,
)

# 在model定义后立刻替换
inject_ds_enc_layer(model, training_args, config)

最后我们还需要定义一个DeepSpeed需要用到的运行参数配置ds_config.json

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{
  "train_micro_batch_size_per_gpu": "auto",
  "optimizer": {
    "type": "AdamW",
    "params": {
      "lr": "auto",
      "betas": [
        0.9,
        0.999
      ],
      "eps": 1e-8,
      "weight_decay": "auto",
      "torch_adam": true
    }
  },
  "scheduler": {
    "type": "WarmupDecayLR",
    "params": {
      "warmup_num_steps": "auto",
      "warmup_min_lr": "auto",
      "warmup_max_lr": "auto",
      "total_num_steps": "auto"
    }
  },
  "gradient_clipping": "auto",
  "fp16": {
    "enabled": "auto",
    "loss_scale": 0,
    "initial_scale_power": 7
  }
}

运行命令需要稍稍修改,采用DeepSpeed的启动器:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
deepspeed --num_gpus=1 run_glue.py \
  --model_name_or_path bert-base-cased \
  --task_name mrpc \
  --do_train \
  --do_eval \
  --max_seq_length 128 \
  --per_device_train_batch_size 32 \
  --num_train_epochs 3 \
  --output_dir /tmp/mrpc/ \
  --overwrite_output_dir \
  --fp16 \
  --deepspeed ds_config.json

输出结果如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
***** train metrics *****
  epoch                    =        3.0
  train_loss               =     0.5865
  train_runtime            = 0:00:37.17
  train_samples            =       3668
  train_samples_per_second =    296.032
  train_steps_per_second   =      9.281

发现DeepSpeed用了整整「37秒」才训练完,和LightSeq的「25秒」相比还是有差距的。

总结

最终对比下来,Hugging Face花了「45秒」训练完成,DeepSpeed花了「37秒」,而LightSeq只花了「25秒」

「项目地址:」 https://github.com/bytedance/lightseq

「技术原理:」 https://zhuanlan.zhihu.com/p/383657837

「其它使用例子:」 https://zhuanlan.zhihu.com/p/382961951

- END -

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-07-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 算法码上来 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
pytorch-pretrained-BERT:BERT PyTorch实现,可加载Google BERT预训练模型
Github上刚刚开源了一个Google BERT的PyTorch实现版本,同时包含可加载Google BERT预训练模型的脚本,感兴趣的同学可以关注:
AINLP
2019/10/10
5.1K0
BERT详解
BERT(Bidirectional Encoder Representations from Transformers) 是一个语言表示模型(language representation model)。它的主要模型结构是trasnformer的encoder堆叠而成,它其实是一个2阶段的框架,分别是pretraining,以及在各个具体任务上进行finetuning。
Don.huang
2020/09/22
4.8K1
[源码解析] 模型并行分布式训练Megatron (2) --- 整体架构
NVIDIA Megatron 是一个基于 PyTorch 的分布式训练框架,用来训练超大Transformer语言模型,其通过综合应用了数据并行,Tensor并行和Pipeline并行来复现 GPT3,值得我们深入分析其背后机理。
罗西的思考
2022/05/09
2.9K0
[源码解析] 模型并行分布式训练Megatron (2) --- 整体架构
一文读懂最强中文NLP预训练模型ERNIE
基于飞桨开源的持续学习的语义理解框架ERNIE 2.0,及基于此框架的ERNIE 2.0预训练模型,在共计16个中英文任务上超越了BERT和XLNet, 取得了SOTA效果。本文带你进一步深入了解ERNIE的技术细节。
AINLP
2019/10/23
1.6K0
一文读懂最强中文NLP预训练模型ERNIE
【NLP】NLP实战篇之bert源码阅读(run_classifier)
(https://github.com/google-research/bert )中run_classifier.py文件,已完成modeling.py、optimization.py、run_pretraining.py、tokenization.py、create_pretraining_data.py、extract_feature.py文件的源码阅读,后续会陆续阅读bert的理解任务训练等源码。本文介绍了run_classifier.py中的主要内容,包括不同分类任务的数据读取,用于分类的bert模型结构,和整体的训练流程。代码中还涉及很多其他内容,如运行参数,特征转为tfrecord文件等等,由于在之前的阅读中,出现过非常相似的内容,所以这里不再重复。
黄博的机器学习圈子
2021/07/07
8840
【NLP】NLP实战篇之bert源码阅读(run_classifier)
聊聊预训练模型的微调
翻译自:Fine-tuning a model with the Trainer API
Ryan_OVO
2023/10/19
6140
文本分类上分微调技巧实战
Truncation methods 截断法 文章的关键信息位于开头和结尾。 我们可以使用三种不同的截断文本方法来执行 BERT 微调。
致Great
2021/07/29
1.7K0
文本分类上分微调技巧实战
超详细的 Bert 文本分类源码解读 | 附源码
在本文中,我将以run_classifier.py以及MRPC数据集为例介绍关于bert以及transformer的源码,官方代码基于tensorflow-gpu 1.x,若为tensorflow 2.x版本,会有各种错误,建议切换版本至1.14。
红色石头
2022/01/10
2.1K0
超详细的 Bert 文本分类源码解读 | 附源码
DeepSpeed结合Megatron-LM训练GPT2模型笔记(上)
本文基于DeepSpeedExamples仓库中给出的Megatron相关例子探索一下训练GPT2模型的流程。主要包含3个部分,第一个部分是基于原始的Megatron如何训练GPT2模型,第二个部分是如何结合DeepSpeed的特性进行训练Megatron GPT2,由于篇幅原因这篇文章只写了第一部分,主要是非常细致的记录了跑起来Megatron GPT2训练流程碰到的一些问题和如何解决的。本文主要以 https://github.com/microsoft/DeepSpeedExamples/tree/bdf8e59aede8c8e0577e8d4d557298ca8515268f 这里的codebase展开写作。
BBuf
2023/08/22
2.4K0
DeepSpeed结合Megatron-LM训练GPT2模型笔记(上)
横扫各项NLP任务的BERT模型有了PyTorch实现!提供转换脚本
上周,谷歌最强NLP模型BERT开源了官方TensorFlow代码和预训练模型,引起大量关注。
新智元
2018/12/12
2.3K0
横扫各项NLP任务的BERT模型有了PyTorch实现!提供转换脚本
使用“BERT”作为编码器和解码器(BERT2BERT)来改进Seq2Seq文本摘要模型
来源:Deephub Imba本文约1500字,建议阅读5分钟在本文中,想展示如何使用仅编码器模型的预训练权重来为我们的微调提供一个良好的开始。 BERT是一个著名的、强大的预先训练的“编码器”模型。让我们看看如何使用它作为“解码器”来形成编码器-解码器架构。 Transformer 架构由两个主要构建块组成——编码器和解码器——我们将它们堆叠在一起形成一个 seq2seq 模型。从头开始训练基于Transformer 的模型通常很困难,因为它需要大型数据集和高 GPU 内存。我们可以使用许多具有不同目标的
数据派THU
2022/07/19
6560
使用“BERT”作为编码器和解码器(BERT2BERT)来改进Seq2Seq文本摘要模型
BERT模型解析
Bidirectional Encoder Representation from Transformers(BERT)[1],即双向Transformer的Encoder表示,是2018年提出的一种基于上下文的预训练模型,通过大量语料学习到每个词的一般性embedding形式,学习到与上下文无关的语义向量表示,以此实现对多义词的建模。与预训练语言模型ELMo[2]以及GPT[3]的关系如下图所示:
felixzhao
2022/09/27
2.2K0
BERT模型解析
PyTorch 2.2 中文官方教程(十四)
在本教程中,您将学习如何实现并使用此模式来对模型进行约束。这样做就像编写自己的nn.Module一样容易。
ApacheCN_飞龙
2024/02/05
7180
PyTorch 2.2 中文官方教程(十四)
BERT模型实战之多文本分类(附源码)
BERT模型也出来很久了,之前看了论文学习过它的大致模型(可以参考前些日子写的笔记NLP大杀器BERT模型解读),但是一直有杂七杂八的事拖着没有具体去实现过真实效果如何。今天就趁机来动手写一写实战,顺便复现一下之前的内容。这篇文章的内容还是以比较简单文本分类任务入手,数据集选取的是新浪新闻cnews,包括了[‘体育’, ‘财经’, ‘房产’, ‘家居’, ‘教育’, ‘科技’, ‘时尚’, ‘时政’, ‘游戏’, ‘娱乐’]总共十个主题的新闻数据。那么我们就开始吧!
全栈程序员站长
2022/06/29
1.7K0
BERT模型实战之多文本分类(附源码)
从头开始实现LoRA以及一些实用技巧
LoRA是Low-Rank Adaptation或Low-Rank Adaptors的缩写,它提供了一种用于对预先存在的语言模型进行微调的高效且轻量级的方法。
deephub
2023/12/19
1.6K0
从头开始实现LoRA以及一些实用技巧
1美元训练BERT,教你如何薅谷歌TPU羊毛 | 附Colab代码
BERT是谷歌去年推出的NLP模型,一经推出就在各项测试中碾压竞争对手,而且BERT是开源的。只可惜训练BERT的价格实在太高,让人望而却步。
量子位
2019/07/30
1.5K0
Kaggle文本可读性识别大赛银牌方案复盘
历史三个月,文本可读性识别大赛终于落下帷幕,我们队伍的ID为wordpeace,队员分别为:致Great,firfile,heshien,heng zheng,XiaobaiLan,私榜取得91名成绩,排名top2%,整体比赛竞争比较大,邻近手分数非常接近甚至片段并列,同时私榜出现大面积抖动。
致Great
2021/11/24
2690
Kaggle文本可读性识别大赛银牌方案复盘
Bert+seq2seq 周公解梦,看AI如何解析你的梦境?
作者:saiwaiyanyu 链接:https://juejin.im/post/5dd9e07b51882572f00c4523
Ai学习的老章
2019/12/05
7340
【LLM训练系列04】手把手教你Qlora微调
IGNORE_TOKEN_ID 是一个常量,通常用于在训练过程中忽略某些特定的标签或输入。它的作用是告诉模型在计算损失时不考虑这些特定的标签或输入。
致Great
2024/12/21
2490
【LLM训练系列04】手把手教你Qlora微调
命名实体识别之bert+bilstm(基于tensorflow)
我们可以直接调用官方的tensorflow的bert模型来使用bert,接下来,我们使用output_layer = model.get_sequence_output()来获得最后一层的特征,然后接下来在添加bilstm层,
西西嘛呦
2020/12/16
1.9K0
推荐阅读
相关推荐
pytorch-pretrained-BERT:BERT PyTorch实现,可加载Google BERT预训练模型
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验