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

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

训练BERT

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

pip install transformers

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

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

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

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

***** train metrics *****epoch                    =        3.0train_loss               =     0.3921train_runtime            = 0:00:45.06train_samples            =       3668train_samples_per_second =    244.166train_steps_per_second   =      7.655

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

加速训练

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

pip install lightseq

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

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.0output = 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_configdef 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的引用:

from replace_module import inject_ls_enc_layer

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

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)

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

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

最终输出如下:

***** train metrics *****epoch                    =        3.0train_loss               =     0.6077train_runtime            = 0:00:25.08train_samples            =       3668train_samples_per_second =    438.603train_steps_per_second   =     13.751

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

加载预训练参数

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

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

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类的初始化参数中去:

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类:

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_configdef 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进行替换:

from replace_module import inject_ds_enc_layermodel = 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

{"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的启动器:

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

输出结果如下:

***** train metrics *****epoch                    =        3.0train_loss               =     0.5865train_runtime            = 0:00:37.17train_samples            =       3668train_samples_per_second =    296.032train_steps_per_second   =      9.281

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

总结

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

「项目地址:」

bytedance/lightseq

「技术原理:」

godweiyang:训练加速3倍!字节跳动推出业界首个NLP模型全流程加速引擎

「其它使用例子:」

godweiyang:只用几行代码,我让模型『训练』加速了3倍以上!

如果你对字节的技术比较感兴趣,欢迎加入我们,一起开发牛X的项目,做最快的男人。

「我的内推码:」
A7FSJMK
「内推链接:」

加入字节跳动

训练BERT,我只花了一半的时间相关推荐

  1. 用MATLAB画nyquist图时,怎样只花出一半的图?

    目录 1 步骤 1 步骤

  2. 谷歌TPU训练BERT只要23秒,华为AI芯片达国际领先水平,MLPerf v0.7出炉

    晓查 发自 凹非寺  量子位 报道 | 公众号 QbitAI 今天,人工智能行业权威"跑分"MLPerf训练v0.7出炉,这是该跑分推出以来第三次放榜. 英伟达刚发布的A100 G ...

  3. 使用大batch优化深度学习:训练BERT仅需76分钟 | ICLR 2020

    作者 | Yang You, Jing Li等 译者 | 刘畅 在海量数据集上训练大型深度神经网络,是非常具有挑战性的.最近,有许多研究均使用大batch随机优化方法来解决此问题.在该研究领域中,目前 ...

  4. 我用24小时、8块GPU、400美元在云上完成训练BERT!

    点击上方"视学算法",选择加"星标"或"置顶" 重磅干货,第一时间送达 丰色 发自 凹非寺 量子位 报道 | 公众号 QbitAI 大型语言 ...

  5. 我用24小时、8块GPU、400美元在云上完成训练BERT!特拉维夫大学新研究

    丰色 发自 凹非寺 量子位 报道 | 公众号 QbitAI 大型语言模型BERT,熟悉NLP领域的同学没人不知道它的名气吧? 只可惜它太太太贵了! 之前有做过统计,使用谷歌云TPU或英伟达GPU训练完 ...

  6. 1美元训练BERT,教你如何薅谷歌TPU羊毛 | 附Colab代码

    晓查 发自 凹非寺 量子位 出品 | 公众号 QbitAI BERT是谷歌去年推出的NLP模型,一经推出就在各项测试中碾压竞争对手,而且BERT是开源的.只可惜训练BERT的价格实在太高,让人望而却步 ...

  7. 1美元从零开始训练Bert,手把手教你优雅地薅谷歌云TPU羊毛

    大数据文摘出品 来源:Google Colab 编译:武帅.曹培信 2018年10月,Google AI团队推出了Bert,可以说Bert一出生就自带光环. 在斯坦福大学机器阅读理解水平测试SQuAD ...

  8. bert 中文 代码 谷歌_1美元从零开始训练Bert,手把手教你优雅地薅谷歌云TPU羊毛...

    大数据文摘出品 来源:Google Colab 编译:武帅.曹培信 2018年10月,Google AI团队推出了Bert,可以说Bert一出生就自带光环. 在斯坦福大学机器阅读理解水平测试SQuAD ...

  9. 如何从零开始训练BERT模型

    我的许多文章都专注于 BERT--这个模型出现并主导了自然语言处理 (NLP) 的世界,标志着语言模型的新时代. 对于那些之前可能没有使用过 Transformer 模型(例如 BERT 是什么)的人 ...

  10. 基于朴素贝叶斯和预训练Bert模型的中文句子情感分类实践

    基于朴素贝叶斯和预训练Bert模型的中文句子情感分类实践 1.任务介绍   本次实践选题为AI研习社2019年9月份举办的中文对话情感分析任务,并在原任务基础上进行了拓展.任务首先给定一中文语句数据集 ...

最新文章

  1. 如何挑选深度学习 GPU?
  2. QB:基于深度学习的病毒序列识别
  3. windows8系统设置×××虚拟连接教程
  4. 共享卫士完全设置教程图解
  5. mysql查询某个字段数量最多_查询一个表某个字段中出现次数最多的那个数据的前30名...
  6. oracle11.2.03,升级Oracle11.2.0.3后遭遇ORA-00600[kfioTranslateIO03][17090]
  7. VB CreateObject函数
  8. bzoj 2152 聪聪可可
  9. Ubuntu下TP5隐藏入口文件
  10. java给byte赋值_关于JAVA中Byte数据类型二进制赋值运算报错问题
  11. 小米蓝牙音响驱动_广场舞阿姨的最爱,户外野营者的必备,小米华为都甘拜下风的便携音箱...
  12. 《HTTP 权威指南》—— 连接管理
  13. html文字左侧居中,HTML如何让文字靠左居中?
  14. advapi32 无法定位_无法定位程序输入点RegSetKeyValueA 于动态链接库 ADVAPI32.dll上 解决方案...
  15. 明光杂感之四:足球与情境觉知(上)
  16. 权限管理大升级,开源智能客服系统春松客服 v6 版本发布 | Chatopera
  17. android 电池容量检测,电池容量检测优化app
  18. sql升序null排在顶部
  19. 如何禁止NavigationController的向右滑动返回
  20. SAP ERP数据表清单

热门文章

  1. 电够动力足——认识主板上的CPU供电模块
  2. 321电商学院 与华中师大联手 - 2014-10-22
  3. 关于javascript中apply()和call()方法的区别
  4. shell脚本基础练习题
  5. 第二期!团队开发spring会议~day8
  6. (转)JVM中的OopMap(zz)
  7. umask设置导致的weblogic中的应用上传的文件没有权限打开
  8. LaTeX(2)——LaTeX文档基本结构
  9. 51单片机电子制作------篮球比赛计分器
  10. 1.阿里云短信验证操作步骤