关于深度学习:GLM国产大模型训练加速性能最高提升3倍显存节省13低成本上手

0次阅读

共计 10252 个字符,预计需要花费 26 分钟才能阅读完成。

作者|BBuf、谢子鹏、冯文

2017 年,Google 提出了 Transformer 架构,随后 BERT、GPT、T5 等预训练模型不断涌现,并在各项任务中都一直刷新 SOTA 纪录。去年,清华提出了 GLM 模型(https://github.com/THUDM/GLM),不同于上述预训练模型架构,它采纳了一种自回归的空白填充办法,在 NLP 畛域三种次要的工作(自然语言了解、无条件生成、有条件生成)上都获得了不错的后果。

很快,清华基于 GLM 架构又推出了 GLM-130B(https://keg.cs.tsinghua.edu.c…),这是一个开源凋谢的双语(中文和英文)双向浓密模型,领有 1300 亿参数,在语言了解、语言建模、翻译、Zero-Shot 等方面都更加杰出。

预训练模型的背地离不开开源深度学习框架的助力。在此之前,GLM 的开源代码次要是由 PyTorch、DeepSpeed 以及 Apex 来实现,并且基于 DeepSpeed 提供的数据并行和模型并行技术训练了 GLM-Large(335M),GLM-515M(515M),GLM-10B(10B)等大模型,这在肯定水平上升高了 GLM 预训练模型的应用门槛。

即便如此,对更宽广范畴的普通用户来说,训练 GLM 这样的模型仍然令人头秃,同时,预训练模型的性能优化还有更大的晋升空间。

为此,咱们近期将原始的 GLM 我的项目移植到了应用 OneFlow 后端进行训练的 One-GLM 我的项目。得益于 OneFlow 和 PyTorch 无缝兼容性,咱们疾速且平滑地移植了 GLM,并胜利跑通了预训练任务(训练 GLM-large)。

此外,因为 OneFlow 原生反对 DeepSpeed 和 Apex 的很多性能和优化技术,用户不再须要这些插件就可训练 GLM 等大模型。更重要的是,针对以后 OneFlow 移植的 GLM 模型,在简略调优后就能在性能以及显存占用上有大幅晋升。

具体是怎么做到的?下文将进行揭晓。

  • One-GLM:https://github.com/Oneflow-In…
  • OneFlow:https://github.com/Oneflow-In…

1、GLM-large 训练性能和显存的体现

首先先展现一下别离应用官网的 GLM 仓库以及 One-GLM 仓库训练 GLM-large 网络的性能和显存体现(数据并行技术),硬件环境为 A100 PCIE 40G,BatchSize 设置为 8。

能够看到,在 GLM-large 的训练任务中,相比原始的基于 PyTorch、DeepSpeed、Apex 的 GLM 实现,OneFlow 的性能有 120% – 276% 的减速,并且显存占用升高了 10% -30%(测试后果均可用 oneflow >=0.9.0 复现)。

2、GLM 迁徙,只需批改几行代码

因为 OneFlow 无缝兼容了 PyTorch 的生态,只需改变几行代码,就能够让用户轻松迁徙 GLM 大模型到 One-GLM:

  • 将 import torch 替换为  import oneflow as torch
  • 将 import torch.xx 替换为 import oneflow.xx
  • 将 from apex.optimizers import FusedAdam as Adam 替换为 from oneflow.optim import Adam
  • 将 from apex.normalization.fused_layer_norm import FusedLayerNorm as LayerNorm 替换为 from oneflow.nn import LayerNorm
  • 正文掉 torch.distributed.ReduceOp,torch.distributed.new_group,,torch.distributed.TCPStore,torch.distributed.all_reduce 这些 API,它们是 PyTorch DDP 所须要的,但 OneFlow 的数据并行是由外部的 SBP 和 Global Tensor 机制实现,并不需要这些 API。

其它许多模型的迁徙更简略,比方在和 torchvision 对标的 flowvision 中,许多模型只需通过在 torchvision 模型文件中退出 import oneflow as torch 即可失去,让用户简直没有额定老本。

此外,OneFlow 还提供全局“mock torch”性能(https://docs.oneflow.org/mast…),在命令行运行 eval $(oneflow-mock-torch) 就能够让接下来运行的所有 Python 脚本里的 import torch 都主动指向 oneflow。

3、两大调优伎俩

loss 计算局部的优化

在原始的 GLM 实现中,loss 计算局部应用到了 mpu.vocab_parallel_cross_entropy 这个函数 (https://github.com/THUDM/GLM/…)。

通过剖析这个函数,发现它实现了 sparse_softmax_cross_entropy 的性能,但在实现过程中,原始的 GLM 仓库应用了 PyTorch 的 autograd.Function 模块,并且应用了大量的小算子来拼接出 sparse_softmax_cross_entropy 整体的性能。而在 OneFlow 的算子库中,曾经有 sparse_softmax_cross_entropy 这个算子对应的 CUDA 实现了,也就是 flow.sparse_softmax_cross_entropy 这个 API。

所以,咱们将 GLM 对 sparse_softmax_cross_entropy 的 naive 实现替换为 flow.sparse_softmax_cross_entropy 这个 API,并进行了 loss 对齐试验。

后果如何?下图展现了基于 OneFlow 的 Graph 模式训练 GLM-large 模型前 1000 轮的 loss 对齐状况,并别离测试了 FP32 和 AMP 模式:

能够看到,将原始 GLM 的 naive sparse_softmax_cross_entropy 实现替换为 flow.sparse_softmax_cross_entropy 之后 loss 是齐全对齐的,能够保障正确性。

相比原始的 GLM 的单卡性能,这个替换使得 One-GLM 的单卡性能有大幅晋升, 次要起因是 OneFlow 对 sparse_softmax_cross_entropy 算子做了极致的性能优化,并且缩小了原始 GLM 中大量的碎算子拼凑带来的访存开销。此外,这样做也升高了 torch.autograd.Function 自身带来的一些零碎开销。

CUDA Kernel Fuse

除上述优化外,GLM 模型实质上就是一个编解码的 Transformer 架构,所以咱们将之前优化 GPT、BERT 的一些 Fuse Pattern 也带给了 One-GLM 模型。具体蕴含以下两个 Fuse Pattern :

  • fused_bias_add_gelu: 将 bias_add 和 gelu 算子交融在一起。
  • fused_bias_add_dropout:将 bias_add 和 dropout 算子交融在一起。

这两个 fuse 都能够显著改善计算的访存,并缩小 Kernel Launch 带来的开销,因为 GLM 模型越大则层数就会越多,那么这种 Fuse Pattern 带来的的劣势也会一直放大。

最终,在上述两方面的优化作用下, 在 A100 PCIE 40G,batch_size = 8 环境中的训练 GLM-large 的工作时,单卡 FP32 模式的性能相比原始的 GLM 获得了 280%(FP32 模式)和 307%(AMP 模式)的训练减速。

4、LiBai 也能轻松搞定 GLM 推理

当模型规模过于宏大,单个 GPU 设施无奈包容大规模模型参数时,便捷好用的分布式训练和推理需要就相继呈现,业内也随之推出相应的工具。

基于 OneFlow 构建的 LiBai 模型库让分布式上手难度降到最低,用户不须要关注模型如何调配在不同的显卡设施,只须要批改几个配置数据就能够设置不同的分布式策略。当然,减速性能更是出众。

  • LiBai:https://github.com/Oneflow-In…
  • LiBai 相干介绍:大模型训练之难,难于上青天?预训练易用、效率超群的「李白」模型库来了!
  • GLM:https://github.com/Oneflow-In…

用 LiBai 搭建的 GLM 能够便捷地实现 model parallel + pipeline parallel 推理, 很好地解决单卡放不下大规模模型的问题。

那么,用户如何利用大规模模型训练与推理仓库 LiBai 来构建 GLM 的分布式推理局部?上面用一个小例子解释一下。

分布式推理具备人造劣势

要晓得,模型的参数其实就是许多 tensor,也就是以矩阵的模式呈现,大模型的参数也就是大矩阵,并行策略就是把大矩阵分为多个小矩阵,并调配到不同的显卡或不同的设施上,根底的 LinearLayer 在 LiBai 中的实现代码如下:

class Linear1D(nn.Module):
    def __init__(self, in_features, out_features, parallel="data", layer_idx=0, ...):
        super().__init__()

        if parallel == "col":
            weight_sbp = dist.get_nd_sbp([flow.sbp.broadcast, flow.sbp.split(0)])
        elif parallel == "row":
            weight_sbp = dist.get_nd_sbp([flow.sbp.broadcast, flow.sbp.split(1)])
        elif parallel == "data":
            weight_sbp = dist.get_nd_sbp([flow.sbp.broadcast, flow.sbp.broadcast])
        else:
            raise KeyError(f"{parallel} is not supported! Only support ('data','row'and'col')")

        self.weight = flow.nn.Parameter(
            flow.empty((out_features, in_features),
                dtype=flow.float32,
                placement=dist.get_layer_placement(layer_idx),  # for pipeline parallelism placement
                sbp=weight_sbp,
            )
        )
        init_method(self.weight)
        ...
    
    def forward(self, x):
        ...
# demo.py
import oneflow as flow
from projects.GLM.tokenizer.glm_tokenizer import GLMGPT2Tokenizer
from libai.utils import distributed as dist
from projects.GLM.configs.glm_inference import cfg
from projects.GLM.modeling_glm import GLMForConditionalGeneration
from projects.GLM.utils.glm_loader import GLMLoaderHuggerFace
from omegaconf import DictConfig

tokenizer = GLMGPT2Tokenizer.from_pretrained("/data/home/glm-10b")
input_ids = tokenizer.encode(
    ["Ng is an adjunct professor at [MASK] (formerly associate professor and Director of its Stanford AI Lab or SAIL). Also a pioneer in online education, Ng co-founded Coursera and deeplearning.ai."
    ],
    return_tensors="of",
)
inputs = {"input_ids": input_ids, "attention_mask": flow.ones(input_ids.size())}
inputs = tokenizer.build_inputs_for_generation(inputs, max_gen_length=512)

sbp = dist.get_nd_sbp([flow.sbp.broadcast, flow.sbp.broadcast])
placement = dist.get_layer_placement(0)

dist.set_device_type("cpu")
loader = GLMLoaderHuggerFace(GLMForConditionalGeneration, cfg, "/path/to/glm-10b")
model = loader.load()
model = model.half().cuda()

dist.set_device_type("cuda")
outputs = model.generate(inputs=inputs['input_ids'].to_global(sbp=sbp, placement=placement), 
    position_ids=inputs['position_ids'].to_global(sbp=sbp, placement=placement), 
    generation_attention_mask=inputs['generation_attention_mask'].to_global(sbp=sbp, placement=placement).half(), 
    max_length=512
)
res = tokenizer.decode(outputs[0])
print(res)
>>> [CLS] Ng is an adjunct professor at [MASK] (formerly associate professor and Director of its Stanford AI Lab or SAIL). Also a pioneer in online education, Ng co-founded Coursera and deeplearning.ai.<|endoftext|> <|startofpiece|>  Stanford University and a co-founder of <|endofpiece|>

在这里,用户可抉择去如何切分 Linear 层的矩阵,如何切分数据矩阵,而 OneFlow 中的 SBP 管制竖着切、横着切以及其余拆分矩阵的计划(模型并行、数据并行),以及通过设置 Placement 来管制这个 LinearLayer 是放在第几张显卡上(流水并行)。

所以,依据 LiBai 中各种 layer 的设计原理以及基于 OneFlow 中 tensor 自带的 SBP 和 Placement 属性的人造劣势,使得用户搭建的模型可能很简略地就实现数据并行、模型并行以及流水并行操作。

GLM 推理的 Demo 演示

这里为用户展现 LiBai 中 GLM 的单卡和便捷的多卡推理 Demo,模型可在 HuggingFace 上获取:https://huggingface.co/models…

  • 单卡 generate 工作,咱们抉择 glm-10b 模型:

python demo.py

# demo.py
import oneflow as flow
from projects.GLM.tokenizer.glm_tokenizer import GLMGPT2Tokenizer
from libai.utils import distributed as dist
from projects.GLM.configs.glm_inference import cfg
from projects.GLM.modeling_glm import GLMForConditionalGeneration
from projects.GLM.utils.glm_loader import GLMLoaderHuggerFace
from omegaconf import DictConfig

tokenizer = GLMGPT2Tokenizer.from_pretrained("/data/home/glm-10b")
input_ids = tokenizer.encode(
    ["Ng is an adjunct professor at [MASK] (formerly associate professor and Director of its Stanford AI Lab or SAIL). Also a pioneer in online education, Ng co-founded Coursera and deeplearning.ai."
    ],
    return_tensors="of",
)
inputs = {"input_ids": input_ids, "attention_mask": flow.ones(input_ids.size())}
inputs = tokenizer.build_inputs_for_generation(inputs, max_gen_length=512)

sbp = dist.get_nd_sbp([flow.sbp.broadcast, flow.sbp.broadcast])
placement = dist.get_layer_placement(0)

dist.set_device_type("cpu")
loader = GLMLoaderHuggerFace(GLMForConditionalGeneration, cfg, "/path/to/glm-10b")
model = loader.load()
model = model.half().cuda()

dist.set_device_type("cuda")
outputs = model.generate(inputs=inputs['input_ids'].to_global(sbp=sbp, placement=placement), 
    position_ids=inputs['position_ids'].to_global(sbp=sbp, placement=placement), 
    generation_attention_mask=inputs['generation_attention_mask'].to_global(sbp=sbp, placement=placement).half(), 
    max_length=512
)
res = tokenizer.decode(outputs[0])
print(res)
>>> [CLS] Ng is an adjunct professor at [MASK] (formerly associate professor and Director of its Stanford AI Lab or SAIL). Also a pioneer in online education, Ng co-founded Coursera and deeplearning.ai.<|endoftext|> <|startofpiece|>  Stanford University and a co-founder of <|endofpiece|>
  • 4 卡 model parallel+pipeline parallel generate 工作,抉择 glm-10b 模型:
python3 -m oneflow.distributed.launch --nproc_per_node 4 demo.py
# demo.py
import oneflow as flow
from projects.GLM.tokenizer.glm_tokenizer import GLMGPT2Tokenizer
from libai.utils import distributed as dist
from projects.GLM.configs.glm_inference import cfg
from projects.GLM.modeling_glm import GLMForConditionalGeneration
from projects.GLM.utils.glm_loader import GLMLoaderHuggerFace
from omegaconf import DictConfig

# 只需简略配置并行计划
parallel_config = DictConfig(
    dict(
        data_parallel_size=1,
        tensor_parallel_size=2,
        pipeline_parallel_size=2,
        pipeline_num_layers=2 * 24
    )
)
dist.setup_dist_util(parallel_config)

tokenizer = GLMGPT2Tokenizer.from_pretrained("/data/home/glm-10b")
input_ids = tokenizer.encode(
    ["Ng is an adjunct professor at [MASK] (formerly associate professor and Director of its Stanford AI Lab or SAIL). Also a pioneer in online education, Ng co-founded Coursera and deeplearning.ai."
    ],
    return_tensors="of",
)
inputs = {"input_ids": input_ids, "attention_mask": flow.ones(input_ids.size())}
inputs = tokenizer.build_inputs_for_generation(inputs, max_gen_length=512)

sbp = dist.get_nd_sbp([flow.sbp.broadcast, flow.sbp.broadcast])
placement = dist.get_layer_placement(0)

loader = GLMLoaderHuggerFace(GLMForConditionalGeneration, cfg, "/path/to/glm-10b")
model = loader.load()

outputs = model.generate(inputs=inputs['input_ids'].to_global(sbp=sbp, placement=placement), 
    position_ids=inputs['position_ids'].to_global(sbp=sbp, placement=placement), 
    generation_attention_mask=inputs['generation_attention_mask'].to_global(sbp=sbp, placement=placement), 
    max_length=512
)
res = tokenizer.decode(outputs[0])
if dist.is_main_process():
    print(res)
>>> [CLS] Ng is an adjunct professor at [MASK] (formerly associate professor and Director of its Stanford AI Lab or SAIL). Also a pioneer in online education, Ng co-founded Coursera and deeplearning.ai.<|endoftext|> <|startofpiece|>  Stanford University and a co-founder of <|endofpiece|>
  • 应用 One- GLM 训练的模型进行推理

LiBai 对于 OneFlow 的模型加载同样不便,如果你心愿应用 one-glm 训练后的模型进行推理,只需简略的将上述 demo 中的 GLMLoaderHuggerFace 替换为 GLMLoaderLiBai。

5、结语

基于 OneFlow 来移植 GLM 大模型非常简单,相比于原始版本 PyTorch GLM  训练 GLM-large 模型,OneFlow 能大幅晋升性能和节俭显存。

此外,通过应用 GLM-10B 这个百亿级大模型做推理,表明基于 OneFlow 的 LiBai 来做大模型推理能够开箱即用,并实现更高的推理速度,如果你想配置不同的并行形式来推理大模型,只须要简略配置文件的几个参数即可。

将来,OneFlow 团队将摸索应用 OneFlow 训练更大的 GLM-130B 千亿模型的可行性, 置信基于 OneFlow 能够更快地训练 GLM-130B 千亿级别模型,减速国产大模型训练和推理工作。

欢送 Star、试用 One-GLM:

  • One-GLM:https://github.com/Oneflow-In…
  • OneFlow:https://github.com/Oneflow-In…

欢送 Star、试用 OneFlow 最新版本:https://github.com/Oneflow-In…

正文完
 0